feat(codegen): target toolkit + registry; register freebsd-x86_64
Introduce TTargetToolkit (Abstract Factory) and TTargetRegistry so per-target compiler-side objects (native backend + ELF link target) are resolved by name rather than hard-coded case statements. CreateNativeBackend now delegates to the registry; adding a target is a new toolkit + one registration with no edits to existing dispatch. Registers linux-x86_64 (unchanged behaviour) and freebsd-x86_64 (link target with EI_OSABI=9; reuses TX86_64Backend since FreeBSD shares the System V AMD64 ABI). FreeBSD codegen/runtime lands in later steps. The registry uses TOrderedDictionary<string, TTargetToolkit> (name -> toolkit) per the Generics.Collections policy. It is built publish-after-populate: the --incremental compile worker pool is threads, so a half-built global would let a worker wrongly conclude a target has no backend. Step 0a of docs/native-target-architecture.adoc. Behaviour-preserving: FIXPOINT_OK, NATIVE_FIXPOINT_OK, NATIVE_INTERNAL_OK, WARMCACHE_FIXPOINT_OK; full suite 3761 tests green (QBE- and native-built runners).
This commit is contained in:
parent
4384b272c3
commit
e723e80063
|
|
@ -76,19 +76,24 @@ function CreateNativeBackend(const ATarget: TTargetDesc): TNativeBackend;
|
|||
implementation
|
||||
|
||||
uses
|
||||
blaise.codegen.native.x86_64;
|
||||
blaise.codegen.toolkit;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Backend factory }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
function CreateNativeBackend(const ATarget: TTargetDesc): TNativeBackend;
|
||||
var
|
||||
Toolkit: TTargetToolkit;
|
||||
begin
|
||||
if (ATarget.OS = osLinux) and (ATarget.CPU = cpuX86_64) then
|
||||
Result := TX86_64Backend.Create(ATarget)
|
||||
else
|
||||
{ Resolve the per-target adapter family from the registry (Abstract
|
||||
Factory). Adding a target is a new toolkit + one registration — this
|
||||
dispatch never changes. See docs/native-target-architecture.adoc. }
|
||||
Toolkit := ResolveToolkit(ATarget);
|
||||
if Toolkit = nil then
|
||||
raise ENativeCodeGenError.Create(
|
||||
'native backend not yet implemented for target ' + TargetName(ATarget));
|
||||
Result := Toolkit.MakeBackend();
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
|
|
|||
234
compiler/src/main/pascal/blaise.codegen.toolkit.pas
Normal file
234
compiler/src/main/pascal/blaise.codegen.toolkit.pas
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
{
|
||||
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 blaise.codegen.toolkit;
|
||||
|
||||
{ Multi-target toolkit + registry (docs/native-target-architecture.adoc).
|
||||
|
||||
A TTargetToolkit is the Abstract Factory that produces the consistent
|
||||
*family* of per-target compiler-side objects: the native backend and the
|
||||
link target. Keeping them behind one factory guarantees the family is
|
||||
coherent — a FreeBSD backend paired with a Linux link target is a bug a
|
||||
registry-resolved toolkit cannot produce.
|
||||
|
||||
TTargetRegistry maps a TTargetDesc to its toolkit. Adding a target is a new
|
||||
TTargetToolkit subclass plus one registration call — no existing dispatch is
|
||||
edited (Open/Closed). --target resolution and "does this target have a
|
||||
native backend?" both go through the registry, replacing the previous
|
||||
hard-coded case statements in blaise.codegen.native and
|
||||
blaise.codegen.target.
|
||||
|
||||
This is the compiler-side half of the architecture. The runtime-side ports
|
||||
(TKernelABI / TPlatformLayout) live in the RTL and are introduced in later
|
||||
steps; they are not part of this unit. }
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
SysUtils, Generics.Collections,
|
||||
blaise.codegen.target, blaise.codegen.native.backend, blaise.linker.elf;
|
||||
|
||||
type
|
||||
{ Abstract Factory for one target's compiler-side object family. }
|
||||
TTargetToolkit = class
|
||||
public
|
||||
{ Canonical "<os>-<cpu>" name (matches blaise.codegen.target.TargetName). }
|
||||
function Name: string; virtual; abstract;
|
||||
{ Construct the native backend for this target. Caller owns the result. }
|
||||
function MakeBackend: TNativeBackend; virtual; abstract;
|
||||
{ Construct the link target (ELF header facts) for this target. Caller
|
||||
owns the result. }
|
||||
function MakeLinkTarget: TLinkTarget; virtual; abstract;
|
||||
end;
|
||||
|
||||
{ Linux x86_64 — the original (and, before FreeBSD, only) target. }
|
||||
TLinuxX86_64Toolkit = class(TTargetToolkit)
|
||||
public
|
||||
function Name: string; override;
|
||||
function MakeBackend: TNativeBackend; override;
|
||||
function MakeLinkTarget: TLinkTarget; override;
|
||||
end;
|
||||
|
||||
{ FreeBSD x86_64 — shares the System V AMD64 ABI and the x86_64 instruction
|
||||
selector with Linux, so MakeBackend reuses TX86_64Backend; only the link
|
||||
target's OS/ABI byte differs. The runtime/syscall divergence is handled
|
||||
in the RTL (TKernelABI / TPlatformLayout), not here. }
|
||||
TFreeBSDX86_64Toolkit = class(TTargetToolkit)
|
||||
public
|
||||
function Name: string; override;
|
||||
function MakeBackend: TNativeBackend; override;
|
||||
function MakeLinkTarget: TLinkTarget; override;
|
||||
end;
|
||||
|
||||
{ Toolkit registry. Owns the registered toolkit instances. A linear list
|
||||
is correct here: the target count is tiny (one per supported OS/CPU), so a
|
||||
name scan is cheaper than a hash map and avoids instantiating a generic
|
||||
map over a class type. }
|
||||
TTargetRegistry = class
|
||||
private
|
||||
FByName: TOrderedDictionary<string, TTargetToolkit>;
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
{ Register a toolkit. Registry takes ownership. }
|
||||
procedure Register(AToolkit: TTargetToolkit);
|
||||
{ Resolve the toolkit for ATarget, or nil if none is registered. }
|
||||
function Resolve(const ATarget: TTargetDesc): TTargetToolkit;
|
||||
end;
|
||||
|
||||
{ The process-wide registry, populated in this unit's initialization. }
|
||||
function GTargetRegistry: TTargetRegistry;
|
||||
|
||||
{ Resolve the toolkit for ATarget via the global registry (nil if unknown). }
|
||||
function ResolveToolkit(const ATarget: TTargetDesc): TTargetToolkit;
|
||||
|
||||
{ True when ATarget has a registered native backend toolkit. Replacement for
|
||||
blaise.codegen.target.TargetHasNativeBackend, driven by the registry. }
|
||||
function RegisteredHasNativeBackend(const ATarget: TTargetDesc): Boolean;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
blaise.codegen.native.x86_64;
|
||||
|
||||
{ ---- TLinuxX86_64Toolkit --------------------------------------------- }
|
||||
|
||||
function TLinuxX86_64Toolkit.Name: string;
|
||||
begin
|
||||
Result := 'linux-x86_64';
|
||||
end;
|
||||
|
||||
function TLinuxX86_64Toolkit.MakeBackend: TNativeBackend;
|
||||
var
|
||||
T: TTargetDesc;
|
||||
begin
|
||||
MakeTarget(osLinux, cpuX86_64, T);
|
||||
Result := TX86_64Backend.Create(T);
|
||||
end;
|
||||
|
||||
function TLinuxX86_64Toolkit.MakeLinkTarget: TLinkTarget;
|
||||
begin
|
||||
Result := LinuxX86_64Target();
|
||||
end;
|
||||
|
||||
{ ---- TFreeBSDX86_64Toolkit ------------------------------------------- }
|
||||
|
||||
function TFreeBSDX86_64Toolkit.Name: string;
|
||||
begin
|
||||
Result := 'freebsd-x86_64';
|
||||
end;
|
||||
|
||||
function TFreeBSDX86_64Toolkit.MakeBackend: TNativeBackend;
|
||||
var
|
||||
T: TTargetDesc;
|
||||
begin
|
||||
MakeTarget(osFreeBSD, cpuX86_64, T);
|
||||
Result := TX86_64Backend.Create(T);
|
||||
end;
|
||||
|
||||
function TFreeBSDX86_64Toolkit.MakeLinkTarget: TLinkTarget;
|
||||
begin
|
||||
Result := FreeBSDX86_64Target();
|
||||
end;
|
||||
|
||||
{ ---- TTargetRegistry ------------------------------------------------- }
|
||||
|
||||
constructor TTargetRegistry.Create;
|
||||
begin
|
||||
inherited Create();
|
||||
FByName := TOrderedDictionary<string, TTargetToolkit>.Create();
|
||||
end;
|
||||
|
||||
destructor TTargetRegistry.Destroy;
|
||||
var
|
||||
I: Integer;
|
||||
Tk: TTargetToolkit;
|
||||
begin
|
||||
if FByName <> nil then
|
||||
begin
|
||||
for I := 0 to FByName.Count - 1 do
|
||||
begin
|
||||
Tk := FByName.Values[I];
|
||||
Tk.Free();
|
||||
end;
|
||||
FByName.Free();
|
||||
end;
|
||||
inherited Destroy();
|
||||
end;
|
||||
|
||||
procedure TTargetRegistry.Register(AToolkit: TTargetToolkit);
|
||||
begin
|
||||
FByName.Items[AToolkit.Name()] := AToolkit;
|
||||
end;
|
||||
|
||||
function TTargetRegistry.Resolve(const ATarget: TTargetDesc): TTargetToolkit;
|
||||
var
|
||||
Key: string;
|
||||
Tk: TTargetToolkit;
|
||||
begin
|
||||
Key := TargetName(ATarget);
|
||||
if FByName.TryGetValue(Key, Tk) then
|
||||
Result := Tk
|
||||
else
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
{ ---- Global registry -------------------------------------------------- }
|
||||
|
||||
var
|
||||
GRegistry: TTargetRegistry;
|
||||
|
||||
{ Build the registry on first use. This unit's initialization section calls
|
||||
EnsureRegistry once at startup (single-threaded, before any worker thread is
|
||||
spawned), so the common path is a no-op nil check. The lazy guard also
|
||||
covers the case where a codegen unit's own initialization constructs a
|
||||
backend before this unit's initialization runs (link-order independence).
|
||||
|
||||
Thread-safety note: the incremental compile worker pool is THREADS, not
|
||||
processes. The registry must be FULLY built before it is published to the
|
||||
global, or a second thread could observe a non-nil GRegistry whose toolkit
|
||||
list is still empty and wrongly conclude a target has no backend. Build
|
||||
into a local and assign the global last (publish-after-populate). }
|
||||
procedure EnsureRegistry;
|
||||
var
|
||||
R: TTargetRegistry;
|
||||
begin
|
||||
if GRegistry = nil then
|
||||
begin
|
||||
R := TTargetRegistry.Create();
|
||||
R.Register(TLinuxX86_64Toolkit.Create());
|
||||
R.Register(TFreeBSDX86_64Toolkit.Create());
|
||||
GRegistry := R;
|
||||
end;
|
||||
end;
|
||||
|
||||
function GTargetRegistry: TTargetRegistry;
|
||||
begin
|
||||
EnsureRegistry();
|
||||
Result := GRegistry;
|
||||
end;
|
||||
|
||||
function ResolveToolkit(const ATarget: TTargetDesc): TTargetToolkit;
|
||||
begin
|
||||
EnsureRegistry();
|
||||
Result := GRegistry.Resolve(ATarget);
|
||||
end;
|
||||
|
||||
function RegisteredHasNativeBackend(const ATarget: TTargetDesc): Boolean;
|
||||
begin
|
||||
EnsureRegistry();
|
||||
Result := GRegistry.Resolve(ATarget) <> nil;
|
||||
end;
|
||||
|
||||
initialization
|
||||
EnsureRegistry();
|
||||
|
||||
finalization
|
||||
GRegistry.Free();
|
||||
|
||||
end.
|
||||
|
|
@ -84,6 +84,10 @@ type
|
|||
{ Linux x86-64 ELF, non-PIE ET_EXEC. Caller frees. }
|
||||
function LinuxX86_64Target: TLinkTarget;
|
||||
|
||||
{ FreeBSD x86-64 ELF. Same System V AMD64 machine/arch as Linux; differs
|
||||
only in EI_OSABI (9 = FreeBSD). Caller frees. }
|
||||
function FreeBSDX86_64Target: TLinkTarget;
|
||||
|
||||
type
|
||||
{ One output section accumulating contributions from input objects. }
|
||||
TMergedSection = class
|
||||
|
|
@ -611,6 +615,13 @@ begin
|
|||
{ defaults already describe Linux x86-64 }
|
||||
end;
|
||||
|
||||
function FreeBSDX86_64Target: TLinkTarget;
|
||||
begin
|
||||
Result := TLinkTarget.Create();
|
||||
{ Identical to Linux x86-64 except the OS/ABI byte in the ELF header. }
|
||||
Result.OSABI := ELFOSABI_FREEBSD;
|
||||
end;
|
||||
|
||||
{ ---- TLinker ----------------------------------------------------------- }
|
||||
|
||||
constructor TLinker.Create;
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ uses
|
|||
cp.test.strutils,
|
||||
cp.test.e2e.strutils,
|
||||
cp.test.toolchain,
|
||||
cp.test.targettoolkit,
|
||||
cp.test.dynarray,
|
||||
cp.test.math,
|
||||
cp.test.e2e.math,
|
||||
|
|
|
|||
141
compiler/src/test/pascal/cp.test.targettoolkit.pas
Normal file
141
compiler/src/test/pascal/cp.test.targettoolkit.pas
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
{
|
||||
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.targettoolkit;
|
||||
|
||||
{ Tests for the multi-target toolkit + registry (docs/native-target-
|
||||
architecture.adoc). A TTargetToolkit is the Abstract Factory that produces
|
||||
the consistent family of per-target objects (backend, link target); the
|
||||
registry resolves a toolkit by target.
|
||||
|
||||
These tests pin the behaviour-preserving Step 0a refactor: the existing
|
||||
Linux x86_64 target must resolve through the registry, and the FreeBSD
|
||||
x86_64 target must be registered (its codegen lands in later steps, so we
|
||||
only assert it is known to the registry here, not that it emits code). }
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
blaise.testing,
|
||||
blaise.codegen.target, blaise.codegen.toolkit,
|
||||
blaise.codegen.native.backend, blaise.linker.elf;
|
||||
|
||||
type
|
||||
TTargetToolkitTests = class(TTestCase)
|
||||
private
|
||||
function LinuxTarget: TTargetDesc;
|
||||
function FreeBSDTarget: TTargetDesc;
|
||||
published
|
||||
{ Linux x86_64 resolves to a toolkit, and that toolkit reports its name. }
|
||||
procedure TestResolve_LinuxX86_64_ReturnsToolkit;
|
||||
procedure TestResolve_LinuxX86_64_ToolkitName;
|
||||
{ The Linux toolkit makes a real backend (the existing TX86_64Backend). }
|
||||
procedure TestLinuxToolkit_MakeBackend_NotNil;
|
||||
{ The Linux toolkit makes a SysV/Linux link target (OSABI 0). }
|
||||
procedure TestLinuxToolkit_MakeLinkTarget_IsLinuxOSABI;
|
||||
{ RegisteredHasNativeBackend mirrors the old TargetHasNativeBackend for
|
||||
Linux x86_64. }
|
||||
procedure TestRegistered_LinuxX86_64_HasBackend;
|
||||
{ FreeBSD x86_64 is registered (resolves to a toolkit). }
|
||||
procedure TestResolve_FreeBSDX86_64_ReturnsToolkit;
|
||||
{ FreeBSD toolkit produces a FreeBSD-OSABI link target (OSABI 9). }
|
||||
procedure TestFreeBSDToolkit_MakeLinkTarget_IsFreeBSDOSABI;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
const
|
||||
ELFOSABI_SYSV = 0;
|
||||
ELFOSABI_FREEBSD = 9;
|
||||
|
||||
function TTargetToolkitTests.LinuxTarget: TTargetDesc;
|
||||
begin
|
||||
MakeTarget(osLinux, cpuX86_64, Result);
|
||||
end;
|
||||
|
||||
function TTargetToolkitTests.FreeBSDTarget: TTargetDesc;
|
||||
begin
|
||||
MakeTarget(osFreeBSD, cpuX86_64, Result);
|
||||
end;
|
||||
|
||||
procedure TTargetToolkitTests.TestResolve_LinuxX86_64_ReturnsToolkit;
|
||||
var
|
||||
Tk: TTargetToolkit;
|
||||
begin
|
||||
Tk := ResolveToolkit(Self.LinuxTarget());
|
||||
AssertTrue('linux-x86_64 must resolve to a toolkit', Tk <> nil);
|
||||
end;
|
||||
|
||||
procedure TTargetToolkitTests.TestResolve_LinuxX86_64_ToolkitName;
|
||||
var
|
||||
Tk: TTargetToolkit;
|
||||
begin
|
||||
Tk := ResolveToolkit(Self.LinuxTarget());
|
||||
AssertEquals('linux-x86_64', Tk.Name());
|
||||
end;
|
||||
|
||||
procedure TTargetToolkitTests.TestLinuxToolkit_MakeBackend_NotNil;
|
||||
var
|
||||
Tk: TTargetToolkit;
|
||||
B: TNativeBackend;
|
||||
begin
|
||||
Tk := ResolveToolkit(Self.LinuxTarget());
|
||||
B := Tk.MakeBackend();
|
||||
try
|
||||
AssertTrue('Linux toolkit must produce a backend', B <> nil);
|
||||
finally
|
||||
B.Free();
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TTargetToolkitTests.TestLinuxToolkit_MakeLinkTarget_IsLinuxOSABI;
|
||||
var
|
||||
Tk: TTargetToolkit;
|
||||
Lt: TLinkTarget;
|
||||
begin
|
||||
Tk := ResolveToolkit(Self.LinuxTarget());
|
||||
Lt := Tk.MakeLinkTarget();
|
||||
try
|
||||
AssertEquals(ELFOSABI_SYSV, Lt.OSABI);
|
||||
finally
|
||||
Lt.Free();
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TTargetToolkitTests.TestRegistered_LinuxX86_64_HasBackend;
|
||||
begin
|
||||
AssertTrue('linux-x86_64 must have a native backend',
|
||||
RegisteredHasNativeBackend(Self.LinuxTarget()));
|
||||
end;
|
||||
|
||||
procedure TTargetToolkitTests.TestResolve_FreeBSDX86_64_ReturnsToolkit;
|
||||
var
|
||||
Tk: TTargetToolkit;
|
||||
begin
|
||||
Tk := ResolveToolkit(Self.FreeBSDTarget());
|
||||
AssertTrue('freebsd-x86_64 must resolve to a toolkit', Tk <> nil);
|
||||
end;
|
||||
|
||||
procedure TTargetToolkitTests.TestFreeBSDToolkit_MakeLinkTarget_IsFreeBSDOSABI;
|
||||
var
|
||||
Tk: TTargetToolkit;
|
||||
Lt: TLinkTarget;
|
||||
begin
|
||||
Tk := ResolveToolkit(Self.FreeBSDTarget());
|
||||
Lt := Tk.MakeLinkTarget();
|
||||
try
|
||||
AssertEquals(ELFOSABI_FREEBSD, Lt.OSABI);
|
||||
finally
|
||||
Lt.Free();
|
||||
end;
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TTargetToolkitTests);
|
||||
|
||||
end.
|
||||
Loading…
Reference in a new issue