From 75628f3e2003e29eb23223e15e0f0701b40623a5 Mon Sep 17 00:00:00 2001 From: Graeme Geldenhuys Date: Fri, 26 Jun 2026 19:15:25 +0100 Subject: [PATCH] feat(rtl): complete --static self-host support (chmod, strtod-free, TLS align) Closes the gaps that blocked a --static (libc-free) build of the compiler itself: - runtime.syscall.linux: add the chmod(2) syscall stub (SYS_chmod=90) defining the bare 'chmod' the internal linker's MakeFileExecutable imports. - uSemantic: route RawStrToDouble through the RTL's pure-Pascal _StrToDouble (runtime.float) instead of libc's strtod, dropping the last libc dependency in the compiler's float-literal constant folding. Mirrors the existing _DoubleToStr routing and is correct for both static and dynamic builds. - blaise.linker.elf: align FTlsSize to FTlsAlign in both the static and dynamic TLS layout so TPOFF32 (sym - FTlsAddr - FTlsSize) matches the thread pointer the freestanding _start places at AlignUp(memsz, align). Previously these diverged when the raw .tdata+.tbss sum was not a multiple of the TLS alignment, shifting every threadvar read by the padding (latent: today's 104-byte/8-align TLS happens to coincide). With these, `blaise --static` can link the compiler itself; the resulting binary is a fully static, libc-free ET_EXEC. --- .../src/main/pascal/blaise.linker.elf.pas | 9 +++++++++ .../src/main/pascal/runtime.syscall.linux.pas | 10 ++++++++++ compiler/src/main/pascal/uSemantic.pas | 19 ++++++++++--------- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/compiler/src/main/pascal/blaise.linker.elf.pas b/compiler/src/main/pascal/blaise.linker.elf.pas index 9ee3df8..48e4898 100644 --- a/compiler/src/main/pascal/blaise.linker.elf.pas +++ b/compiler/src/main/pascal/blaise.linker.elf.pas @@ -898,6 +898,12 @@ begin FTlsSize := FTlsSize + M.Size; if M.Align > FTlsAlign then FTlsAlign := M.Align; end; + { Variant-II TLS: the thread pointer sits at the ALIGNED end of the block, and + TPOFF32 is computed as (sym - FTlsAddr - FTlsSize). The freestanding _start + places TP at AlignUp(memsz, align); align FTlsSize to match so both agree even + when the raw section sum is not a multiple of FTlsAlign (else every threadvar + read is off by the alignment padding). } + FTlsSize := LkAlignUp(FTlsSize, FTlsAlign); end; function TLinker.FindSymbol(const AName: string): TLinkSymbol; @@ -1565,6 +1571,9 @@ begin FTlsSize := FTlsSize + M.Size; if M.Align > FTlsAlign then FTlsAlign := M.Align; end; + { TP sits at the aligned end of the TLS block (variant II); align FTlsSize so + TPOFF32 (sym - FTlsAddr - FTlsSize) matches the aligned thread pointer. } + FTlsSize := LkAlignUp(FTlsSize, FTlsAlign); { Build .dynamic section. 'libc.so.6' is at offset 1 in .dynstr. } FDynamicData := ''; diff --git a/compiler/src/main/pascal/runtime.syscall.linux.pas b/compiler/src/main/pascal/runtime.syscall.linux.pas index 6b10880..0476da1 100644 --- a/compiler/src/main/pascal/runtime.syscall.linux.pas +++ b/compiler/src/main/pascal/runtime.syscall.linux.pas @@ -54,6 +54,7 @@ function rmdir(Path: PChar): Integer; function unlink(Path: PChar): Integer; function rename(OldPath, NewPath: PChar): Integer; function chdir(Path: PChar): Integer; +function chmod(Path: PChar; Mode: Integer): Integer; { Process. } function getpid: Integer; @@ -128,6 +129,7 @@ const SYS_getpid = 39; SYS_rename = 82; SYS_mkdir = 83; + SYS_chmod = 90; SYS_rmdir = 84; SYS_unlink = 87; SYS_chdir = 80; @@ -213,6 +215,14 @@ asm ret end; +function chmod(Path: PChar; Mode: Integer): Integer; + assembler; nostackframe; +asm + movq $90, %rax { SYS_chmod } + syscall + ret +end; + function rmdir(Path: PChar): Integer; assembler; nostackframe; asm diff --git a/compiler/src/main/pascal/uSemantic.pas b/compiler/src/main/pascal/uSemantic.pas index f9bb814..b393c76 100644 --- a/compiler/src/main/pascal/uSemantic.pas +++ b/compiler/src/main/pascal/uSemantic.pas @@ -535,14 +535,15 @@ type implementation -{ Double->string conversion uses the RTL's pure-Pascal _DoubleToStr, NOT libc's - snprintf. snprintf is a genuinely variadic function; declaring it with a fixed - Double parameter violates the SysV x86-64 variadic ABI (the %al vector-register - count is left unset), so glibc may read the double from the wrong place and emit - a wrong string. The symptom is environment-dependent miscompilation of folded - float constants (passes locally, fails in CI). _DoubleToStr is FormatFloat(.,15), - i.e. %.15g, and is ABI-safe. strtod is NOT variadic, so it is kept as-is. } -function _strtod(S: PChar; EndPtr: Pointer): Double; external name 'strtod'; +{ Float<->string conversion uses the RTL's pure-Pascal _DoubleToStr / _StrToDouble, + NOT libc's snprintf / strtod. snprintf is a genuinely variadic function; + declaring it with a fixed Double parameter violates the SysV x86-64 variadic ABI + (the %al vector-register count is left unset), so glibc may read the double from + the wrong place and emit a wrong string (environment-dependent miscompilation of + folded float constants — passes locally, fails in CI). Routing both directions + through the RTL also drops the last libc dependency in this path, so a --static + (libc-free) build of the compiler links without strtod. } +function _StrToDouble(S: Pointer): Double; external name '_StrToDouble'; function _DoubleToStr(V: Double): string; external name '_DoubleToStr'; function RawDoubleToStr(V: Double): string; @@ -552,7 +553,7 @@ end; function RawStrToDouble(const S: string): Double; begin - Result := _strtod(PChar(S), nil); + Result := _StrToDouble(PChar(S)); end; function TSemanticAnalyser.GetSymbolTable: TSymbolTable;