rtl-unification: move E2E test helpers off blaise_rtl.a (source-built RTL)

The E2E test helpers linked Blaise programs against the shipped blaise_rtl.a.
With the archive on its way out (RTL-unification Stage 3), migrate them to the
source-built RTL:

- cp.test.e2e.base gains LinkWithRTL: assembles the program object, builds the
  RTL from source via scripts/build-rtl-objects.sh (--exclude-defined-by drops
  the RTL units the whole-program assembly already inlined), and links the loose
  objects.  Its five cc-link sites and the ToolchainAvailable gate now use it /
  the RTL source instead of the archive.
- New cp.test.rtllink unit factors the same link helper for the standalone
  helpers (publishedrtti, proctypes_ofobject, attributes) that lower programs
  themselves rather than via the base class.
- cp.test.cli and cp.test.assembler drive an external blaise binary that
  source-builds its own RTL, so their gates now require the binary + RTL source,
  not the archive.
- cp.test.linker's TestArchive_ParsesRTL kept its archive-parser coverage
  (including the GNU long-name member) by building a throwaway .a from the
  source-built objects with ar — a test-time-only ar use, not a product one.

build-rtl-objects.sh resolves its RTL source relative to its own location (so it
works from any CWD — the fixpoint scripts run from the project root, the test
runner from compiler/), caches a per-object .syms list to keep repeated
--exclude-defined-by calls cheap, and skips rebuilding fresh objects.

Full suite green (3829 tests) on both QBE- and native-built runners; the
previously-skipped TInternalAsmE2ETests / TCLIContractTests and the archive
parser test now run.
This commit is contained in:
Graeme Geldenhuys 2026-06-25 20:49:00 +01:00
parent 406a8c303c
commit 773d773008
9 changed files with 308 additions and 51 deletions

View file

@ -83,7 +83,6 @@ type
FCompiler: string;
FRTLPath: string;
FStdlibPath: string;
FRTL: string;
FScratch: string;
FCounter: Integer;
function ProjectRoot: string;
@ -752,7 +751,6 @@ begin
FCompiler := '/tmp/fp_blaise3';
FRTLPath := ProjectRoot() + 'compiler/src/main/pascal';
FStdlibPath := ProjectRoot() + 'stdlib/src/main/pascal';
FRTL := ProjectRoot() + 'compiler/target/blaise_rtl.a';
FScratch := ProjectRoot() + 'compiler/target/asm_scratch/';
ForceDirectories(FScratch);
FCounter := 0;
@ -763,12 +761,15 @@ var
SrcFile, OutFile, CompOut, RunOut: string;
Rc: Integer;
begin
Result := FileExists(FCompiler) and FileExists(FRTL);
{ The compiler binary source-builds the RTL itself (no blaise_rtl.a); only
the binary and the RTL source need to be present. }
Result := FileExists(FCompiler)
and FileExists(FRTLPath + '/runtime.arc.pas');
if (not Result) and (not GInternalAsmSkipNoted) then
begin
GInternalAsmSkipNoted := True;
WriteLn(StdErr, 'note: TInternalAsmE2ETests skipped — compiler binary "',
FCompiler, '" or RTL "', FRTL, '" not found ',
FCompiler, '" or RTL source not found ',
'(set BLAISE_QBE_COMPILER to a QBE-backend blaise binary to run them)');
Exit;
end;

View file

@ -20,7 +20,8 @@ interface
uses
Classes, SysUtils, Process, blaise.testing,
uLexer, uParser, uAST, uSymbolTable, uSemantic, blaise.codegen.qbe;
uLexer, uParser, uAST, uSymbolTable, uSemantic, blaise.codegen.qbe,
cp.test.rtllink;
function ProjectRootAttr: string;
function RunCmdAttr(const AExe: string; const AArgs: array of string): Integer;
@ -154,7 +155,7 @@ function TCustomAttributeTests.CompileAndRun(const ASrc: string): string;
var
IR: string;
Root: string;
QBE, RTL, Scratch: string;
QBE, Scratch: string;
IRFile, AsmFile, BinFile: string;
Lst: TStringList;
Proc: TProcess;
@ -163,8 +164,7 @@ begin
Result := '';
Root := ProjectRootAttr();
QBE := Root + 'vendor/qbe/qbe';
RTL := Root + 'compiler/target/blaise_rtl.a';
if not (FileExists(QBE) and FileExists(RTL)) then
if not RTLLinkToolchainAvailable(Root) then
begin
Result := '<toolchain-missing>';
Exit;
@ -190,7 +190,7 @@ begin
Exit;
end;
if RunCmdAttr('cc', ['-o', BinFile, AsmFile, RTL]) <> 0 then
if LinkProgramWithRTL(Root, AsmFile, BinFile) <> 0 then
begin
Result := '<link-failed>';
Exit;

View file

@ -35,7 +35,6 @@ type
FCompiler: string;
FRTLPath: string;
FStdlibPath: string;
FRTL: string;
FScratch: string;
FCounter: Integer;
function ProjectRoot: string;
@ -144,7 +143,6 @@ begin
FCompiler := '/tmp/fp_blaise2';
FRTLPath := ProjectRoot() + 'compiler/src/main/pascal';
FStdlibPath := ProjectRoot() + 'stdlib/src/main/pascal';
FRTL := ProjectRoot() + 'compiler/target/blaise_rtl.a';
FScratch := ProjectRoot() + 'compiler/target/cli_scratch/';
ForceDirectories(FScratch);
FCounter := 0;
@ -155,12 +153,15 @@ var
Out_: string;
EC: Integer;
begin
Result := FileExists(FCompiler) and FileExists(FRTL);
{ The compiler binary source-builds the RTL itself (no blaise_rtl.a); only
the binary and the RTL source need to be present. }
Result := FileExists(FCompiler)
and FileExists(FRTLPath + '/runtime.arc.pas');
if (not Result) and (not GCLISkipNoted) then
begin
GCLISkipNoted := True;
WriteLn(StdErr, 'note: TCLIContractTests skipped — compiler binary "',
FCompiler, '" or RTL "', FRTL, '" not found ',
FCompiler, '" or RTL source not found ',
'(set BLAISE_QBE_COMPILER to a QBE-backend blaise binary to run them)');
Exit;
end;

View file

@ -34,7 +34,6 @@ type
TE2ETestCase = class(TTestCase)
private
FQBE: string;
FRTL: string;
FRTLUnitPath: string;
FStdlibUnitPath: string;
FScratch: string;
@ -43,6 +42,13 @@ type
function RunProc(const AExe: string; const AArgs: array of string;
out AStdout: string): Integer;
function RunProcNoArgs(const AExe: string; out AStdout: string): Integer;
{ Link an assembled program (AAsmFile) into ABinFile against the RTL. The
RTL is built from source by scripts/build-rtl-objects.sh (no blaise_rtl.a
archive); --exclude-defined-by drops the RTL objects the whole-program
assembly already inlines, so the loose objects do not double-define.
Returns the cc exit code; AStdout carries any tool output. }
function LinkWithRTL(const AAsmFile, ABinFile: string;
out AStdout: string): Integer;
protected
function ToolchainAvailable(): Boolean;
function ValgrindAvailable(): Boolean;
@ -169,7 +175,11 @@ end;
function TE2ETestCase.ToolchainAvailable(): Boolean;
begin
Result := FileExists(FQBE) and FileExists(FRTL)
{ Need the QBE assembler, the compiler binary (build-rtl-objects.sh drives it
to source-build the RTL), and the RTL source. No blaise_rtl.a archive. }
Result := FileExists(FQBE)
and FileExists(ProjectRoot() + 'compiler/target/blaise')
and FileExists(ProjectRoot() + 'compiler/src/main/pascal/runtime.arc.pas')
end;
function TE2ETestCase.ValgrindAvailable(): Boolean;
@ -186,9 +196,6 @@ begin
FQBE := GetEnvironmentVariable('BLAISE_QBE');
if FQBE = '' then
FQBE := ProjectRoot() + 'vendor/qbe/qbe';
FRTL := GetEnvironmentVariable('BLAISE_RTL');
if FRTL = '' then
FRTL := ProjectRoot() + 'runtime/target/blaise_rtl.a';
{ RTL units (runtime.*, rtl.platform.*) now live in the compiler's own source
tree after the RTL-unification move; the old runtime/src/main/pascal is empty. }
FRTLUnitPath := ProjectRoot() + 'compiler/src/main/pascal';
@ -228,6 +235,62 @@ begin
end
end;
function TE2ETestCase.LinkWithRTL(const AAsmFile, ABinFile: string;
out AStdout: string): Integer;
var
ProgObj, ObjDir, Compiler, ScriptOut: string;
Objs: TStringList;
Proc: TProcess;
I: Integer;
begin
{ 1. Assemble the program to an object so build-rtl-objects.sh can see which
RTL symbols it already defines (it inlines the RTL units it uses). }
ProgObj := AAsmFile + '.o';
Result := RunProc('cc', ['-c', '-o', ProgObj, AAsmFile], AStdout);
if Result <> 0 then Exit;
{ 2. Build the RTL objects from source, excluding the units the program
already inlined. Compiler binary is the freshly-built compiler/target. }
Compiler := ProjectRoot() + 'compiler/target/blaise';
ObjDir := IncludeTrailingPathDelimiter(FScratch) + 'rtlobj';
Result := RunProc(ProjectRoot() + 'scripts/build-rtl-objects.sh',
[Compiler, ObjDir, '--exclude-defined-by', ProgObj],
ScriptOut);
if Result <> 0 then
begin
AStdout := 'build-rtl-objects failed: ' + ScriptOut;
Exit;
end;
{ 3. Link: cc -o Bin ProgObj <rtl objects> -lm -lpthread. Build the TProcess
directly so the object list (variable length) can be appended. }
Objs := TStringList.Create();
Proc := TProcess.Create(nil);
try
Objs.Text := ScriptOut;
Proc.Executable := 'cc';
Proc.Parameters.Add('-o');
Proc.Parameters.Add(ABinFile);
Proc.Parameters.Add(ProgObj);
for I := 0 to Objs.Count - 1 do
if Trim(Objs.Strings[I]) <> '' then
Proc.Parameters.Add(Trim(Objs.Strings[I]));
Proc.Parameters.Add('-lm');
Proc.Parameters.Add('-lpthread');
Proc.Execute();
AStdout := '';
repeat
ScriptOut := Proc.ReadOutput();
AStdout := AStdout + ScriptOut
until (ScriptOut = '') and not Proc.Running;
Proc.WaitOnExit();
Result := Proc.ExitCode;
finally
Proc.Free();
Objs.Free();
end;
end;
function TE2ETestCase.RunProcNoArgs(const AExe: string;
out AStdout: string): Integer;
var
@ -316,7 +379,7 @@ begin
Rc := RunProc(FQBE, ['-o', AsmFile, IRFile], ToolOut);
if Rc <> 0 then begin AStdout := 'qbe failed: ' + ToolOut; AExitCode := Rc; Exit end
end;
Rc := RunProc('cc', ['-o', BinFile, AsmFile, FRTL, '-lm', '-lpthread'], ToolOut);
Rc := LinkWithRTL(AsmFile, BinFile, ToolOut);
if Rc <> 0 then begin AStdout := 'cc failed: ' + ToolOut; AExitCode := Rc; Exit end;
AExitCode := RunProcNoArgs(BinFile, AStdout);
Result := True
@ -428,7 +491,7 @@ begin
WriteFile(IRFile, IR);
Rc := RunProc(FQBE, ['-o', AsmFile, IRFile], ToolOut);
if Rc <> 0 then begin AStdout := 'qbe failed: ' + ToolOut; AExitCode := Rc; Exit end;
Rc := RunProc('cc', ['-o', BinFile, AsmFile, FRTL, '-lm', '-lpthread'], ToolOut);
Rc := LinkWithRTL(AsmFile, BinFile, ToolOut);
if Rc <> 0 then begin AStdout := 'cc failed: ' + ToolOut; AExitCode := Rc; Exit end;
AExitCode := RunProc(BinFile, AExtraArgs, AStdout);
Result := True
@ -472,7 +535,7 @@ begin
WriteFile(IRFile, IR);
Rc := RunProc(FQBE, ['-o', AsmFile, IRFile], ToolOut);
if Rc <> 0 then Exit;
Rc := RunProc('cc', ['-o', BinFile, AsmFile, FRTL, '-lm', '-lpthread'], ToolOut);
Rc := LinkWithRTL(AsmFile, BinFile, ToolOut);
if Rc <> 0 then Exit;
Rc := RunProc('valgrind',
@ -664,7 +727,7 @@ begin
Rc := RunProc(FQBE, ['-o', AsmFile, IRFile], ToolOut);
if Rc <> 0 then begin AStdout := 'qbe failed: ' + ToolOut; AExitCode := Rc; Exit end
end;
Rc := RunProc('cc', ['-o', BinFile, AsmFile, FRTL, '-lm', '-lpthread'], ToolOut);
Rc := LinkWithRTL(AsmFile, BinFile, ToolOut);
if Rc <> 0 then begin AStdout := 'cc failed: ' + ToolOut; AExitCode := Rc; Exit end;
AExitCode := RunProcNoArgs(BinFile, AStdout);
Result := True
@ -764,7 +827,7 @@ begin
Rc := RunProc(FQBE, ['-o', AsmFile, IRFile], ToolOut);
if Rc <> 0 then begin AStdout := 'qbe failed: ' + ToolOut; AExitCode := Rc; Exit end
end;
Rc := RunProc('cc', ['-o', BinFile, AsmFile, FRTL, '-lm', '-lpthread'], ToolOut);
Rc := LinkWithRTL(AsmFile, BinFile, ToolOut);
if Rc <> 0 then begin AStdout := 'cc failed: ' + ToolOut; AExitCode := Rc; Exit end;
AExitCode := RunProcNoArgs(BinFile, AStdout);
Result := True

View file

@ -18,7 +18,7 @@ unit cp.test.linker;
interface
uses
SysUtils, process, blaise.testing, Generics.Collections,
classes, SysUtils, process, blaise.testing, Generics.Collections,
blaise.elfreader, blaise.linker.elf, blaise.assembler.x86_64;
type
@ -330,22 +330,82 @@ end;
procedure TElfReaderTests.TestArchive_ParsesRTL;
var
RTLPath: string;
Root, ObjDir, ArPath, ScriptOut: string;
Compiler: string;
Members: TList<TArchiveMember>;
ObjList: TStringList;
Proc: TProcess;
Chunk: string;
I: Integer;
M: TArchiveMember;
Obj: TElfObjectFile;
SawLongName: Boolean;
begin
RTLPath := ProjectRoot() + 'compiler/target/blaise_rtl.a';
if not FileExists(RTLPath) then
{ The shipped blaise_rtl.a is gone (RTL-unification Stage 3). To still
exercise the archive parser — including the GNU long-name (//) table on the
>15-char member rtl.platform.layout.linux.o — build the RTL objects from
source and bundle them into a THROWAWAY .a with `ar` just for this test.
`ar` is a test-time dependency only; the product/bootstrap no longer use it. }
Root := ProjectRoot();
Compiler := Root + 'compiler/target/blaise';
if (not FileExists(Compiler))
or (not FileExists(Root + 'compiler/src/main/pascal/runtime.arc.pas')) then
begin
Ignore('<toolchain-missing>');
Exit;
end;
ObjDir := Root + 'compiler/target/test-archive-rtl';
ScriptOut := '';
Proc := TProcess.Create(nil);
try
Proc.Executable := Root + 'scripts/build-rtl-objects.sh';
Proc.Parameters.Add(Compiler);
Proc.Parameters.Add(ObjDir);
Proc.Parameters.Add('--with-startup');
Proc.Execute();
repeat
Chunk := Proc.ReadOutput();
ScriptOut := ScriptOut + Chunk
until (Chunk = '') and not Proc.Running;
Proc.WaitOnExit();
if Proc.ExitCode <> 0 then
begin
Ignore('<toolchain-missing>');
Exit;
end;
finally
Proc.Free();
end;
{ Bundle the built objects into a temp archive with ar. Split the script's
stdout (one object path per line) via TStringList. }
ArPath := IncludeTrailingPathDelimiter(ObjDir) + 'rtl_test.a';
DeleteFile(ArPath);
ObjList := TStringList.Create();
Proc := TProcess.Create(nil);
try
ObjList.Text := ScriptOut;
Proc.Executable := 'ar';
Proc.Parameters.Add('rcs');
Proc.Parameters.Add(ArPath);
for I := 0 to ObjList.Count - 1 do
if Trim(ObjList.Strings[I]) <> '' then
Proc.Parameters.Add(Trim(ObjList.Strings[I]));
Proc.Execute();
Proc.WaitOnExit();
if (Proc.ExitCode <> 0) or (not FileExists(ArPath)) then
begin
Ignore('<toolchain-missing>');
Exit;
end;
finally
Proc.Free();
ObjList.Free();
end;
Members := TList<TArchiveMember>.Create();
try
ReadArchiveFile(RTLPath, Members);
ReadArchiveFile(ArPath, Members);
AssertTrue('expected several RTL members, got '
+ IntToStr(Members.Count), Members.Count >= 5);
{ A member whose name exceeds 15 chars exercises the GNU long-name (//)

View file

@ -20,7 +20,8 @@ interface
uses
Classes, SysUtils, Process, blaise.testing,
uLexer, uParser, uAST, uSymbolTable, uSemantic, blaise.codegen.qbe;
uLexer, uParser, uAST, uSymbolTable, uSemantic, blaise.codegen.qbe,
cp.test.rtllink;
function ProjectRootOFO: string;
function RunCmdOFO(const AExe: string; const AArgs: array of string): Integer;
@ -157,7 +158,7 @@ function TProcTypesOfObjectTests.CompileAndRun(const ASrc: string): string;
var
IR: string;
Root: string;
QBE, RTL, Scratch: string;
QBE, Scratch: string;
IRFile, AsmFile, BinFile: string;
Lst: TStringList;
Proc: TProcess;
@ -166,8 +167,7 @@ begin
Result := '';
Root := ProjectRootOFO();
QBE := Root + 'vendor/qbe/qbe';
RTL := Root + 'compiler/target/blaise_rtl.a';
if not (FileExists(QBE) and FileExists(RTL)) then
if not RTLLinkToolchainAvailable(Root) then
begin
Result := '<toolchain-missing>';
Exit;
@ -193,7 +193,7 @@ begin
Exit;
end;
if RunCmdOFO('cc', ['-o', BinFile, AsmFile, RTL]) <> 0 then
if LinkProgramWithRTL(Root, AsmFile, BinFile) <> 0 then
begin
Result := '<link-failed>';
Exit;

View file

@ -21,7 +21,8 @@ interface
uses
Classes, SysUtils, Process, blaise.testing,
uLexer, uParser, uAST, uSymbolTable, uSemantic, blaise.codegen.qbe;
uLexer, uParser, uAST, uSymbolTable, uSemantic, blaise.codegen.qbe,
cp.test.rtllink;
function ProjectRootRTTI: string;
function RunCmd(const AExe: string; const AArgs: array of string): Integer;
@ -152,7 +153,7 @@ function TPublishedRTTITests.CompileAndRun(const ASrc: string): string;
var
IR: string;
Root: string;
QBE, RTL, Scratch: string;
QBE, Scratch: string;
IRFile, AsmFile, BinFile: string;
Lst: TStringList;
Proc: TProcess;
@ -161,8 +162,7 @@ begin
Result := '';
Root := ProjectRootRTTI();
QBE := Root + 'vendor/qbe/qbe';
RTL := Root + 'compiler/target/blaise_rtl.a';
if not (FileExists(QBE) and FileExists(RTL)) then
if not RTLLinkToolchainAvailable(Root) then
begin
Result := '<toolchain-missing>';
Exit;
@ -188,7 +188,7 @@ begin
Exit;
end;
if RunCmd('cc', ['-o', BinFile, AsmFile, RTL]) <> 0 then
if LinkProgramWithRTL(Root, AsmFile, BinFile) <> 0 then
begin
Result := '<link-failed>';
Exit;

View file

@ -0,0 +1,117 @@
{
Blaise - An Object Pascal Compiler
Copyright (c) 2026 Graeme Geldenhuys
SPDX-License-Identifier: Apache-2.0 WITH Swift-exception
Licensed under the Apache License v2.0 with Runtime Library Exception.
See LICENSE file in the project root for full license terms.
}
unit cp.test.rtllink;
{ Shared RTL link helper for the standalone E2E test helpers (those that lower a
program to assembly themselves and link it, rather than via cp.test.e2e.base).
The RTL is built from source by scripts/build-rtl-objects.sh — there is no
blaise_rtl.a archive (RTL-unification Stage 3). A whole-program assembly dump
inlines the RTL units the program uses, so --exclude-defined-by drops the RTL
objects whose symbols the program already defines, avoiding double-definition. }
interface
{ Link AAsmFile (assembled program, .s) into ABinFile against the RTL.
AProjectRoot must end with a path delimiter. Returns the cc exit code; 0 on
success. Non-zero (and a negative value for a failed RTL build) on failure. }
function LinkProgramWithRTL(const AProjectRoot, AAsmFile,
ABinFile: string): Integer;
{ True when the toolchain needed by LinkProgramWithRTL is present: the QBE
assembler, the compiler binary, and the RTL source. }
function RTLLinkToolchainAvailable(const AProjectRoot: string): Boolean;
implementation
uses
classes, sysutils, process;
function RTLLinkToolchainAvailable(const AProjectRoot: string): Boolean;
begin
Result := FileExists(AProjectRoot + 'vendor/qbe/qbe')
and FileExists(AProjectRoot + 'compiler/target/blaise')
and FileExists(AProjectRoot + 'compiler/src/main/pascal/runtime.arc.pas');
end;
function RunCapture(const AExe: string; const AArgs: array of string;
out AOut: string): Integer;
var
Proc: TProcess;
I: Integer;
Chunk: string;
begin
Proc := TProcess.Create(nil);
try
Proc.Executable := AExe;
for I := 0 to High(AArgs) do
Proc.Parameters.Add(AArgs[I]);
Proc.Execute();
AOut := '';
repeat
Chunk := Proc.ReadOutput();
AOut := AOut + Chunk
until (Chunk = '') and not Proc.Running;
Proc.WaitOnExit();
Result := Proc.ExitCode
finally
Proc.Free()
end
end;
function LinkProgramWithRTL(const AProjectRoot, AAsmFile,
ABinFile: string): Integer;
var
ProgObj, ObjDir, Compiler, ScriptOut: string;
Objs: TStringList;
Proc: TProcess;
I: Integer;
begin
{ Assemble the program so the RTL build can see which RTL symbols it inlined. }
ProgObj := AAsmFile + '.o';
Result := RunCapture('cc', ['-c', '-o', ProgObj, AAsmFile], ScriptOut);
if Result <> 0 then Exit;
Compiler := AProjectRoot + 'compiler/target/blaise';
ObjDir := ExtractFilePath(AAsmFile) + 'rtlobj';
Result := RunCapture(AProjectRoot + 'scripts/build-rtl-objects.sh',
[Compiler, ObjDir, '--exclude-defined-by', ProgObj],
ScriptOut);
if Result <> 0 then
begin
Result := -1;
Exit;
end;
Objs := TStringList.Create();
Proc := TProcess.Create(nil);
try
Objs.Text := ScriptOut;
Proc.Executable := 'cc';
Proc.Parameters.Add('-o');
Proc.Parameters.Add(ABinFile);
Proc.Parameters.Add(ProgObj);
for I := 0 to Objs.Count - 1 do
if Trim(Objs.Strings[I]) <> '' then
Proc.Parameters.Add(Trim(Objs.Strings[I]));
Proc.Parameters.Add('-lm');
Proc.Parameters.Add('-lpthread');
Proc.Execute();
repeat
ScriptOut := Proc.ReadOutput();
until (ScriptOut = '') and not Proc.Running;
Proc.WaitOnExit();
Result := Proc.ExitCode;
finally
Proc.Free();
Objs.Free();
end;
end;
end.

View file

@ -53,7 +53,15 @@ if [ ! -x "$BLAISE" ]; then
exit 2
fi
SRC=compiler/src/main/pascal
# Resolve the RTL source relative to THIS script (scripts/ is a sibling of
# compiler/), so the script works regardless of the caller's CWD — the fixpoint
# scripts run from the project root, the test runner from compiler/.
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
SRC="$SCRIPT_DIR/../compiler/src/main/pascal"
if [ ! -f "$SRC/runtime.arc.pas" ]; then
# Fall back to a CWD-relative path (e.g. an unusual invocation layout).
SRC=compiler/src/main/pascal
fi
if [ ! -f "$SRC/runtime.arc.pas" ]; then
echo "build-rtl-objects: RTL source not found under $SRC" >&2
exit 2
@ -81,23 +89,30 @@ fi
OBJS=()
for u in "${RTL_UNITS[@]}"; do
obj="$OUTDIR/$u.o"
"$BLAISE" --backend native --assembler internal \
--source "$SRC/$u.pas" \
--unit-path "$SRC" --unit-cache "$OUTDIR" \
--output "$obj" >/dev/null 2>"$OUTDIR/$u.build.err" || {
echo "build-rtl-objects: failed to build $u" >&2
tail -5 "$OUTDIR/$u.build.err" >&2
exit 1
}
src="$SRC/$u.pas"
# Rebuild only when the cached object is missing or older than its source, so
# a persistent $OUTDIR (e.g. reused across a test suite) builds the RTL once.
if [ ! -f "$obj" ] || [ "$src" -nt "$obj" ]; then
"$BLAISE" --backend native --assembler internal \
--source "$src" \
--unit-path "$SRC" --unit-cache "$OUTDIR" \
--output "$obj" >/dev/null 2>"$OUTDIR/$u.build.err" || {
echo "build-rtl-objects: failed to build $u" >&2
tail -5 "$OUTDIR/$u.build.err" >&2
exit 1
}
# Cache this object's defined symbols so repeated --exclude-defined-by calls
# (e.g. one per test in a suite) don't re-run nm on the stable RTL objects.
nm "$obj" 2>/dev/null | grep -E ' [TDBR] ' | awk '{print $3}' | sort -u \
> "$obj.syms"
fi
if [ "$u" = "runtime.start" ] && [ "$WITH_STARTUP" -eq 0 ]; then
continue
fi
# Drop this object if it re-defines any symbol the main program already owns
# (it was inlined into the program) — otherwise the loose .o collides.
if [ -n "$EXCL_DEFS" ]; then
this_defs=$(nm "$obj" 2>/dev/null | grep -E ' [TDBR] ' | awk '{print $3}' | sort -u)
if [ -n "$this_defs" ] && \
[ -n "$(comm -12 <(printf '%s\n' "$this_defs") <(printf '%s\n' "$EXCL_DEFS"))" ]; then
if [ -n "$EXCL_DEFS" ] && [ -f "$obj.syms" ] && [ -s "$obj.syms" ]; then
if [ -n "$(comm -12 "$obj.syms" <(printf '%s\n' "$EXCL_DEFS"))" ]; then
continue
fi
fi