feat(freebsd): select the RTL unit list per target (Step 5)

EnsureRTLObjects hard-coded the Linux RTL adapter set: rtl.platform.layout.linux
in the base list, and for a --static link the Linux kernel leaf
(runtime.start.static.linux, runtime.syscall.linux, runtime.libc.linux,
runtime.libc2.linux, runtime.thread.static.linux). A --target freebsd-x86_64
program therefore always linked Linux/libc RTL code regardless of the target.

Extract the selection into a pure, unit-tested helper BuildRTLUnitList(AStatic,
AOS) that follows the target: the platform-layout adapter becomes
rtl.platform.layout.<os>, and the --static kernel leaf swaps to the freestanding
per-OS units (runtime.start.static.<os>, runtime.syscall.<os>,
runtime.libc.<os>, runtime.libc2.<os>, runtime.thread.static.<os>) plus the
OS-invariant runtime.cstub. EnsureRTLObjects now drives it off AOpts.Static and
AOpts.Target.OS. Linux is the default suffix, so the existing Linux link path is
byte-for-byte unchanged; FreeBSD selects its full adapter set with no Linux RTL
unit present.

Tests: TBackendDriverContractTests.TestRTLUnits_* cover the Linux-dynamic,
Linux-static, FreeBSD-static-swaps and FreeBSD-static-no-Linux-leaf cases.
Verified: --static Linux hello-world still runs (regression check); all four
fixpoints + QBE & native TestRunner green (4024 tests).
This commit is contained in:
Graeme Geldenhuys 2026-07-01 10:28:13 +01:00
parent a1ba257da7
commit d0032269d6
2 changed files with 179 additions and 19 deletions

View file

@ -247,6 +247,22 @@ type
function RunProcess(const AExe: string; AArgs: TStringList; function RunProcess(const AExe: string; AArgs: TStringList;
out AOutput: string): Integer; out AOutput: string): Integer;
{ Build the leaf-first RTL unit list for a target. The base list is shared;
the OS-specific bits are selected from AOS:
* the platform-layout adapter (rtl.platform.layout.<os>), and
* for a --static (freestanding, no-libc) link, the kernel leaf that
replaces libc — the freestanding runtime.start.static.<os> in place of
the libc runtime.start, plus runtime.syscall.<os> / runtime.libc.<os> /
runtime.libc2.<os> / runtime.thread.static.<os> and the OS-invariant
runtime.cstub.
A dynamic (libc) link keeps the plain list with only the layout adapter
swapped. Exposed for unit testing; EnsureRTLObjects drives it off
AOpts.Static and AOpts.Target.OS. Caller owns and frees the result. }
function BuildRTLUnitList(AStatic: Boolean;
AOS: TTargetOS): TStringList;
{ Format one --help flag line with the shared 2-space indent and flag { Format one --help flag line with the shared 2-space indent and flag
column. Single source of truth for the column width so the common column. Single source of truth for the column width so the common
usage block (Blaise.pas PrintUsage) and each driver's DescribeOptions usage block (Blaise.pas PrintUsage) and each driver's DescribeOptions
@ -433,6 +449,50 @@ const
'runtime.weak', 'runtime.float', 'runtime.thread', 'runtime.exc', 'runtime.weak', 'runtime.float', 'runtime.thread', 'runtime.exc',
'rtl.platform.layout.linux', 'rtl.platform.posix'); 'rtl.platform.layout.linux', 'rtl.platform.posix');
{ Lower-case OS suffix used in the OS-specific RTL unit names
(rtl.platform.layout.<os>, runtime.syscall.<os>, …). }
function RTLOSSuffix(AOS: TTargetOS): string;
begin
case AOS of
osFreeBSD: Result := 'freebsd';
else
{ Linux is the default host leaf; other OSes gain their own suffix as
their adapter set lands. }
Result := 'linux';
end;
end;
function BuildRTLUnitList(AStatic: Boolean; AOS: TTargetOS): TStringList;
var
I: Integer;
OS: string;
begin
OS := RTLOSSuffix(AOS);
Result := TStringList.Create();
{ The base list, in leaf-first order. Two OS-specific substitutions:
the platform-layout adapter always follows the target, and for a --static
link the libc entry point (runtime.start) is replaced by the freestanding
per-OS start. A dynamic link keeps runtime.start. }
for I := 0 to High(RTL_UNITS) do
if SameText(RTL_UNITS[I], 'rtl.platform.layout.linux') then
Result.Add('rtl.platform.layout.' + OS)
else if AStatic and SameText(RTL_UNITS[I], 'runtime.start') then
Result.Add('runtime.start.static.' + OS)
else
Result.Add(RTL_UNITS[I]);
{ The freestanding kernel leaf that replaces libc under --static: the raw
syscall stubs, the OS-invariant freestanding C functions (runtime.cstub),
the libc-shim layers, and the static-TLS thread leaf. }
if AStatic then
begin
Result.Add('runtime.syscall.' + OS);
Result.Add('runtime.cstub');
Result.Add('runtime.libc.' + OS);
Result.Add('runtime.libc2.' + OS);
Result.Add('runtime.thread.static.' + OS);
end;
end;
function TBackendDriver.EnsureRTLObjects(AOpts: TBackendOpts; function TBackendDriver.EnsureRTLObjects(AOpts: TBackendOpts;
AIncludeStartup: Boolean; AAlreadyProvided, AObjPaths: TStringList): string; AIncludeStartup: Boolean; AAlreadyProvided, AObjPaths: TStringList): string;
var var
@ -492,25 +552,12 @@ begin
ForceDirectories(BuildDir); ForceDirectories(BuildDir);
BlaiseBin := ParamStr(0); BlaiseBin := ParamStr(0);
{ The unit list to build/link, in leaf-first order. For a --static link the { The unit list to build/link, in leaf-first order. Selection is driven off
kernel leaf replaces libc: drop runtime.start (libc/__libc_start_main entry) the target (AOpts.Target.OS) and link mode (AOpts.Static): the platform
in favour of the freestanding runtime.start.static.<os>, and add the raw layout adapter follows the target, and a --static link swaps in the
syscall stubs (runtime.syscall.<os>) + the freestanding C functions freestanding per-OS kernel leaf (start / syscall / libc shims / thread) in
(runtime.cstub). A dynamic (libc) link uses the plain RTL_UNITS. } place of libc. See BuildRTLUnitList. }
Units := TStringList.Create(); Units := BuildRTLUnitList(AOpts.Static, AOpts.Target.OS);
for I := 0 to High(RTL_UNITS) do
if AOpts.Static and SameText(RTL_UNITS[I], 'runtime.start') then
Units.Add('runtime.start.static.linux')
else
Units.Add(RTL_UNITS[I]);
if AOpts.Static then
begin
Units.Add('runtime.syscall.linux');
Units.Add('runtime.cstub');
Units.Add('runtime.libc.linux');
Units.Add('runtime.libc2.linux');
Units.Add('runtime.thread.static.linux');
end;
for I := 0 to Units.Count - 1 do for I := 0 to Units.Count - 1 do
begin begin

View file

@ -22,12 +22,24 @@ interface
uses uses
SysUtils, Classes, blaise.testing, SysUtils, Classes, blaise.testing,
blaise.codegen.driver, blaise.codegen.driver,
blaise.codegen.target, { TTargetOS: osLinux / osFreeBSD }
blaise.codegen.qbe.driver, { registers the QBE driver } blaise.codegen.qbe.driver, { registers the QBE driver }
blaise.codegen.native.driver; { registers the native driver } blaise.codegen.native.driver; { registers the native driver }
type type
TBackendDriverContractTests = class(TTestCase) TBackendDriverContractTests = class(TTestCase)
private
function ListContains(AList: TStringList; const AName: string): Boolean;
published published
{ Per-target RTL unit-list selection (FreeBSD Step 5). BuildRTLUnitList is
the pure selection helper EnsureRTLObjects drives off AOpts.Static +
AOpts.Target.OS; these assert Linux vs FreeBSD swaps without invoking the
whole compile+link pipeline. }
procedure TestRTLUnits_LinuxDynamic_UsesLinuxLayout;
procedure TestRTLUnits_LinuxStatic_SwapsLinuxLeaf;
procedure TestRTLUnits_FreeBSDStatic_SwapsFreeBSDLeaf;
procedure TestRTLUnits_FreeBSDStatic_NoLinuxLeaf;
{ ClaimsEmitIR selection policy. } { ClaimsEmitIR selection policy. }
procedure TestQBE_ClaimsEmitIR_True; procedure TestQBE_ClaimsEmitIR_True;
procedure TestNative_ClaimsEmitIR_False; procedure TestNative_ClaimsEmitIR_False;
@ -54,6 +66,107 @@ type
implementation implementation
function TBackendDriverContractTests.ListContains(AList: TStringList;
const AName: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := 0 to AList.Count - 1 do
if SameText(AList.Strings[I], AName) then
Exit(True);
end;
procedure TBackendDriverContractTests.TestRTLUnits_LinuxDynamic_UsesLinuxLayout;
var
U: TStringList;
begin
{ A dynamic (libc) Linux link keeps the plain RTL list: the Linux layout
adapter is present and no freestanding kernel leaf is pulled in. }
U := BuildRTLUnitList(False, osLinux);
try
AssertTrue('linux layout present',
ListContains(U, 'rtl.platform.layout.linux'));
AssertFalse('no static start in dynamic mode',
ListContains(U, 'runtime.start.static.linux'));
AssertFalse('no syscall leaf in dynamic mode',
ListContains(U, 'runtime.syscall.linux'));
finally
U.Free();
end;
end;
procedure TBackendDriverContractTests.TestRTLUnits_LinuxStatic_SwapsLinuxLeaf;
var
U: TStringList;
begin
{ A --static Linux link swaps runtime.start for the freestanding
runtime.start.static.linux and adds the Linux syscall/cstub/libc leaf. }
U := BuildRTLUnitList(True, osLinux);
try
AssertTrue('freestanding start present',
ListContains(U, 'runtime.start.static.linux'));
AssertFalse('libc start dropped',
ListContains(U, 'runtime.start'));
AssertTrue('linux syscall leaf present',
ListContains(U, 'runtime.syscall.linux'));
AssertTrue('linux libc2 present',
ListContains(U, 'runtime.libc2.linux'));
AssertTrue('linux static thread leaf present',
ListContains(U, 'runtime.thread.static.linux'));
AssertTrue('linux layout present',
ListContains(U, 'rtl.platform.layout.linux'));
finally
U.Free();
end;
end;
procedure TBackendDriverContractTests.TestRTLUnits_FreeBSDStatic_SwapsFreeBSDLeaf;
var
U: TStringList;
begin
{ A --static FreeBSD link selects the FreeBSD adapter set: the FreeBSD
layout, freestanding start, syscall leaf, libc2 and static thread leaf. }
U := BuildRTLUnitList(True, osFreeBSD);
try
AssertTrue('freebsd layout present',
ListContains(U, 'rtl.platform.layout.freebsd'));
AssertTrue('freebsd freestanding start present',
ListContains(U, 'runtime.start.static.freebsd'));
AssertTrue('freebsd syscall leaf present',
ListContains(U, 'runtime.syscall.freebsd'));
AssertTrue('freebsd libc2 present',
ListContains(U, 'runtime.libc2.freebsd'));
AssertTrue('freebsd static thread leaf present',
ListContains(U, 'runtime.thread.static.freebsd'));
finally
U.Free();
end;
end;
procedure TBackendDriverContractTests.TestRTLUnits_FreeBSDStatic_NoLinuxLeaf;
var
U: TStringList;
begin
{ The FreeBSD list must contain NO Linux-specific RTL unit — the whole point
of the per-target swap. }
U := BuildRTLUnitList(True, osFreeBSD);
try
AssertFalse('no linux layout',
ListContains(U, 'rtl.platform.layout.linux'));
AssertFalse('no linux start',
ListContains(U, 'runtime.start.static.linux'));
AssertFalse('no linux syscall leaf',
ListContains(U, 'runtime.syscall.linux'));
AssertFalse('no linux libc2',
ListContains(U, 'runtime.libc2.linux'));
AssertFalse('no linux static thread leaf',
ListContains(U, 'runtime.thread.static.linux'));
finally
U.Free();
end;
end;
procedure TBackendDriverContractTests.TestQBE_ClaimsEmitIR_True; procedure TBackendDriverContractTests.TestQBE_ClaimsEmitIR_True;
begin begin
AssertTrue('QBE must claim --emit-ir', AssertTrue('QBE must claim --emit-ir',