diff --git a/compiler/src/main/pascal/Blaise.pas b/compiler/src/main/pascal/Blaise.pas index 64f6c9f..3d68755 100644 --- a/compiler/src/main/pascal/Blaise.pas +++ b/compiler/src/main/pascal/Blaise.pas @@ -231,6 +231,11 @@ begin Inc(I); AFront.UnitCacheDir := ParamStr(I); end + else if (Arg = '--rtl-src') and (I < ParamCount()) then + begin + Inc(I); + AOpts.RTLSrcDir := ParamStr(I); + end else if Arg = '--emit-ir' then AFront.EmitIR := True else if Arg = '--emit-asm' then diff --git a/compiler/src/main/pascal/blaise.codegen.driver.pas b/compiler/src/main/pascal/blaise.codegen.driver.pas index b7ac93f..55309ee 100644 --- a/compiler/src/main/pascal/blaise.codegen.driver.pas +++ b/compiler/src/main/pascal/blaise.codegen.driver.pas @@ -88,6 +88,10 @@ type UseInternalLinker: Boolean; { --linker internal (native backend) } LinkerExplicit: Boolean; { True when user passed --linker } LinkerChoiceBad: Boolean; { --linker value not 'internal' or 'external' } + RTLSrcDir: string; { --rtl-src DIR: explicit RTL source directory. + Overrides the binary/CWD-relative lookup, for + a relocated/release binary whose RTL source + lives elsewhere (empty = use default lookup) } end; TBackendDriver = class diff --git a/compiler/src/main/pascal/blaise.codegen.native.driver.pas b/compiler/src/main/pascal/blaise.codegen.native.driver.pas index eba7e49..4c2cb1b 100644 --- a/compiler/src/main/pascal/blaise.codegen.native.driver.pas +++ b/compiler/src/main/pascal/blaise.codegen.native.driver.pas @@ -69,6 +69,13 @@ type private function LinkViaInternalLinker(const AObjFile, AOutputFile: string; AOpts: TBackendOpts; AExtraObjects: TStringList): string; + { Compile the implicit RTL units to a per-compiler object cache and return + their .o paths in AObjPaths (link order). Each unit is (re)compiled only + when its cached .o is missing or older than the source. Replaces the + pre-built blaise_rtl.a: the RTL is now built from source by the compiler + itself (docs/rtl-unification-plan.adoc). '' on success, else an error. } + function EnsureRTLObjects(AOpts: TBackendOpts; + AObjPaths: TStringList): string; end; implementation @@ -182,19 +189,90 @@ begin Result := 'cc -c error (exit ' + IntToStr(ExitCode) + '): ' + Msg; end; +{ The implicit RTL units, in archive/link order. The compiler emits calls to + their symbols (_SetArgs, _BlaiseGetMem, _start, ARC helpers, …) in every + program, so every program links them. Names are the dotted-flat unit names; + the source files are .pas in the RTL source directory. } +const + RTL_UNITS: array[0..13] of string = ( + 'runtime.start', 'runtime.atomic', 'runtime.setjmp', 'runtime.utf8', + 'runtime.mem', 'runtime.str', 'runtime.set', 'runtime.arc', + 'runtime.weak', 'runtime.float', 'runtime.thread', 'runtime.exc', + 'rtl.platform.layout.linux', 'rtl.platform.posix'); + +function TNativeBackendDriver.EnsureRTLObjects(AOpts: TBackendOpts; + AObjPaths: TStringList): string; +var + SrcDir, CacheDir, BlaiseBin: string; + SrcFile, ObjFile: string; + I, ExitCode: Integer; + Args: TStringList; + Msg: string; +begin + Result := ''; + { RTL source lives in the compiler's own source tree. Resolution order: + 1. --rtl-src DIR (AOpts.RTLSrcDir) — explicit, for a relocated binary; + 2. $BLAISE_RTL_SRC; + 3. binary/CWD-relative default (RTLSourceDir). } + if AOpts.RTLSrcDir <> '' then + SrcDir := ExpandFileName(AOpts.RTLSrcDir) + else + begin + SrcDir := GetEnvironmentVariable('BLAISE_RTL_SRC'); + if SrcDir = '' then + SrcDir := ExpandFileName(RTLSourceDir()); + end; + if not DirectoryExists(SrcDir) then + Exit('internal linker: RTL source directory not found (' + SrcDir + + '); pass --rtl-src DIR or set $BLAISE_RTL_SRC to compiler/src/main/pascal'); + + { Object cache lives beside the compiler binary so repeated program builds + reuse it (compiler/target/rtl/). } + CacheDir := IncludeTrailingPathDelimiter(CompilerBinDir()) + 'rtl'; + ForceDirectories(CacheDir); + BlaiseBin := ParamStr(0); + + for I := 0 to High(RTL_UNITS) do + begin + SrcFile := IncludeTrailingPathDelimiter(SrcDir) + RTL_UNITS[I] + '.pas'; + ObjFile := IncludeTrailingPathDelimiter(CacheDir) + RTL_UNITS[I] + '.o'; + if not FileExists(SrcFile) then + Exit('internal linker: RTL unit source missing: ' + SrcFile); + + { Recompile only when the cached object is missing or stale. } + if (not FileExists(ObjFile)) or (FileAge(ObjFile) < FileAge(SrcFile)) then + begin + Args := TStringList.Create(); + try + Args.Add('--no-incremental'); + Args.Add('--assembler'); Args.Add('internal'); + Args.Add('--source'); Args.Add(SrcFile); + Args.Add('--unit-path'); Args.Add(SrcDir); + Args.Add('--output'); Args.Add(ObjFile); + ExitCode := RunProcess(BlaiseBin, Args, Msg); + finally + Args.Free(); + end; + if ExitCode <> 0 then + Exit('internal linker: failed to build RTL unit ' + RTL_UNITS[I] + + ' (exit ' + IntToStr(ExitCode) + '): ' + Msg); + end; + AObjPaths.Add(ObjFile); + end; +end; + function TNativeBackendDriver.LinkViaInternalLinker( const AObjFile, AOutputFile: string; AOpts: TBackendOpts; AExtraObjects: TStringList): string; var Lk: TLinker; Obj: TElfObjectFile; - TC: TToolchain; Toolkit: TTargetToolkit; LinkTarget: TLinkTarget; + RTLObjs: TStringList; I: Integer; begin Result := ''; - TC := ResolveToolchain(AOpts.Target); { Build the linker with the resolved target's TLinkTarget so the emitted ELF carries the right EI_OSABI / machine / load base for AOpts.Target — without @@ -206,49 +284,53 @@ begin Exit('internal linker: no toolkit registered for target ' + TargetName(AOpts.Target)); - { Every Blaise program links blaise_rtl.a — the program entry emits - _SetArgs and the runtime supplies ARC, strings, exceptions, the platform - layer, etc. An empty RTLPath means FindRTLArchive could not locate - blaise_rtl.a beside the compiler binary (or via BLAISE_RTL). Linking - anyway is never correct: every RTL symbol becomes an undefined dynamic - import that silently links but dies at run time (e.g. `undefined symbol: - _SetArgs`). Fail loudly instead of emitting a broken executable. } - if TC.RTLPath = '' then - Exit('internal linker: runtime archive blaise_rtl.a not found ' - + '(looked beside the compiler binary and in $BLAISE_RTL); ' - + 'cannot link a Blaise program without the RTL'); - - { TLinker.Create(ATarget) borrows the target — we own and free it here. } - LinkTarget := Toolkit.MakeLinkTarget(); - Lk := TLinker.Create(LinkTarget); + { Build the implicit RTL objects from source (cached beside the compiler). + Every Blaise program emits calls to RTL symbols (_SetArgs, _BlaiseGetMem, + _start, ARC/string/exception helpers, …) as undefined externals; these + objects supply them. Replaces the pre-built blaise_rtl.a — the RTL is now + compiled by the compiler itself. } + RTLObjs := TStringList.Create(); try + Result := Self.EnsureRTLObjects(AOpts, RTLObjs); + if Result <> '' then Exit; + + { TLinker.Create(ATarget) borrows the target — we own and free it here. } + LinkTarget := Toolkit.MakeLinkTarget(); + Lk := TLinker.Create(LinkTarget); try - Lk.SetDynamic(True); + try + Lk.SetDynamic(True); - Obj := ReadElfObjectFile(AObjFile); - Lk.AddOwnedObject(Obj); + Obj := ReadElfObjectFile(AObjFile); + Lk.AddOwnedObject(Obj); - if AExtraObjects <> nil then - for I := 0 to AExtraObjects.Count - 1 do + if AExtraObjects <> nil then + for I := 0 to AExtraObjects.Count - 1 do + begin + Obj := ReadElfObjectFile(AExtraObjects.Strings[I]); + Lk.AddOwnedObject(Obj); + end; + + { The RTL objects — including _start (runtime.start) and the implicit + runtime symbols — built from source above. } + for I := 0 to RTLObjs.Count - 1 do begin - Obj := ReadElfObjectFile(AExtraObjects.Strings[I]); + Obj := ReadElfObjectFile(RTLObjs.Strings[I]); Lk.AddOwnedObject(Obj); end; - { _start now lives in blaise_rtl.a (blaise_start_x86_64.s) — no system - Scrt1.o / crtbegin / crtend / crti / crtn needed. AddArchive adds all - members unconditionally, so the entry symbol is always present. } - Lk.AddArchive(TC.RTLPath); - - Lk.Link('_start', AOutputFile); - except - on E: Exception do - Result := 'internal linker error [' + Exception(E).ClassName + ']: ' + - Exception(E).Message; + Lk.Link('_start', AOutputFile); + except + on E: Exception do + Result := 'internal linker error [' + Exception(E).ClassName + ']: ' + + Exception(E).Message; + end; + finally + Lk.Free(); + LinkTarget.Free(); end; finally - Lk.Free(); - LinkTarget.Free(); + RTLObjs.Free(); end; end; diff --git a/compiler/src/main/pascal/uToolchain.pas b/compiler/src/main/pascal/uToolchain.pas index e36916d..c977a6d 100644 --- a/compiler/src/main/pascal/uToolchain.pas +++ b/compiler/src/main/pascal/uToolchain.pas @@ -89,6 +89,14 @@ function ResolveAssembler: TTool; function ResolveLinker(const ATarget: TTargetDesc): TTool; function FindRTLArchive(const ATarget: TTargetDesc): string; +{ Directory containing the running compiler binary (ParamStr(0)). } +function CompilerBinDir: string; + +{ RTL source directory (compiler/src/main/pascal), relative to the binary. + The driver builds the RTL objects from here when linking; overridable via + $BLAISE_RTL_SRC. } +function RTLSourceDir: string; + { Walks $PATH for the first file named ABaseName that exists. Returns the absolute path on hit, '' on miss. On Windows hosts also tries '.exe'. } function WhichInPath(const ABaseName: string): string; @@ -319,6 +327,42 @@ begin Result := ExtractFilePath(ParamStr(0)); end; +function RTLDirHasUnits(const ADir: string): Boolean; +begin + Result := (ADir <> '') and + FileExists(IncludeTrailingPathDelimiter(ADir) + 'runtime.arc.pas'); +end; + +function RTLSourceDir: string; +var + Cand: string; +begin + { The RTL units (runtime.*, rtl.platform.*) live in the compiler's own source + tree. Resolve the directory robustly across the ways the compiler is run: + + 1. binary-relative: /../src/main/pascal — the in-tree + compiler/target/blaise. + 2. CWD-relative: compiler/src/main/pascal then src/main/pascal — the + binary may be copied elsewhere (e.g. /tmp during a fixpoint) but + invoked from the project root by the build. + + The driver consults $BLAISE_RTL_SRC before calling this, for a fully + relocated binary (release) that ships its RTL source elsewhere. } + Cand := IncludeTrailingPathDelimiter(CompilerBinDir()) + '../src/main/pascal'; + if RTLDirHasUnits(ExpandFileName(Cand)) then Exit(ExpandFileName(Cand)); + + Cand := 'compiler/src/main/pascal'; + if RTLDirHasUnits(ExpandFileName(Cand)) then Exit(ExpandFileName(Cand)); + + Cand := 'src/main/pascal'; + if RTLDirHasUnits(ExpandFileName(Cand)) then Exit(ExpandFileName(Cand)); + + { Fall back to the binary-relative path so callers get a meaningful "not + found" message naming a concrete location. } + Result := ExpandFileName(IncludeTrailingPathDelimiter(CompilerBinDir()) + + '../src/main/pascal'); +end; + function FindRTLArchive(const ATarget: TTargetDesc): string; var BinDir: string; diff --git a/compiler/src/test/pascal/cp.test.linker.pas b/compiler/src/test/pascal/cp.test.linker.pas index 7c89fcf..c8510c5 100644 --- a/compiler/src/test/pascal/cp.test.linker.pas +++ b/compiler/src/test/pascal/cp.test.linker.pas @@ -1459,17 +1459,7 @@ begin FCounter := FCounter + 1; IsoDir := FScratch + 'no_rtl_' + IntToStr(FCounter) + '/'; ForceDirectories(IsoDir); - IsoCompiler := IsoDir + 'blaise'; - { Symlink the real compiler into the isolated dir — argv[0]'s dirname is the - isolated dir, which has NO blaise_rtl.a beside it, so FindRTLArchive - returns ''. (A symlink avoids copying the multi-MB binary and the - byte-exactness pitfalls of a string round-trip.) } - if FileExists(IsoCompiler) then DeleteFile(IsoCompiler); - if _test_symlink(PChar(FCompiler), PChar(IsoCompiler)) <> 0 then - begin - Ignore(''); - Exit; - end; + IsoCompiler := FCompiler; SrcFile := IsoDir + 'prog.pas'; OutFile := IsoDir + 'prog'; @@ -1479,21 +1469,23 @@ begin ' WriteLn(6 * 7)' + LineEnding + 'end.'); - { BLAISE_RTL is normally unset in the test environment; the isolated dir has - no blaise_rtl.a, so FindRTLArchive returns '' and the guard must fire. } + { The RTL is now compiled from SOURCE by the compiler at link time (no prebuilt + blaise_rtl.a). Point --rtl-src at a nonexistent directory so the RTL source + is unreachable: the driver must fail loudly rather than emit a broken binary. } Rc := Self.RunProc(IsoCompiler, [ '--source', SrcFile, - '--unit-path', FRTLPath, '--unit-path', FStdlibPath, '--output', OutFile, '--backend', 'native', '--assembler', 'internal', - '--linker', 'internal' + '--linker', 'internal', + '--rtl-src', IsoDir + 'no_such_rtl_src' ], CompOut); - AssertTrue('compiler must FAIL when RTL is unreachable (got rc=0)', Rc <> 0); - AssertTrue('error must name the missing RTL archive: ' + CompOut, - Pos('blaise_rtl.a', CompOut) >= 0); + AssertTrue('compiler must FAIL when the RTL source is unreachable (got rc=0)', + Rc <> 0); + AssertTrue('error must name the missing RTL source: ' + CompOut, + Pos('RTL source', CompOut) >= 0); AssertFalse('no output binary should be produced on RTL-missing failure', FileExists(OutFile)); end;