docs: kernel leaf is a link-time swap, not a runtime TKernelABI port
Records the Step 0c outcome: a runtime TKernelABI virtual port (30 abstract methods + a GKernel global, routing all rtl.platform.posix kernel calls through it with a libc-delegating Linux adapter) was implemented and reverted. It destabilised self-hosting — binaries built against the modified RTL segfaulted at startup with an unwindable/overflowed stack, and the failure compounded across bootstrap stages rather than converging. The Linux adapter was pure libc delegation, so it carried real self-hosting risk for zero present benefit. Replacement design: the kernel leaf is a LINK-TIME symbol swap (the pattern already used for blaise_setjmp/atomic .s objects). The RTL keeps its 'external name' bindings; the FreeBSD archive links a raw-syscall stub object exporting the same symbols. No runtime virtual dispatch, no new global, no change to the layout-sensitive RTL units. The kernel mechanism is a build-time property of a target (libc-linked vs freestanding, never switched at runtime), so a link-time swap models it exactly while a runtime port does not. Both docs updated throughout: ports table, units-that-change table, migration sequence (0a/0b done, kernel-leaf revised, ARC-walker lift deferred as arm64 groundwork), toolkit sketch, summaries. Adds a [#kernel-leaf] rationale note.
This commit is contained in:
parent
de27984647
commit
1000fe7686
|
|
@ -251,33 +251,34 @@ subclassing. This backend completes that idiom.
|
|||
The full structural design lives in
|
||||
`docs/native-target-architecture.adoc`. In brief, each target is a *family of
|
||||
adapter classes* produced by an Abstract Factory (`TTargetToolkit`) resolved by
|
||||
name from a `TTargetRegistry`, with two new runtime ports introduced for
|
||||
Strategy B:
|
||||
name from a `TTargetRegistry`. The two per-target seams Strategy B needs:
|
||||
|
||||
* `TKernelABI` — the raw syscall + `_start` entry leaf (adapters
|
||||
`TKernelLinuxX86_64`, `TKernelFreeBSDX86_64`).
|
||||
* `TPlatformLayout` — `struct stat`/`struct tm` layouts and the OS constants
|
||||
(`O_*`, signals, `CLOCK_*`, `WNOHANG`, `SEEK_*`), so the Linux values
|
||||
currently hard-coded inside `TRtlPlatformPosix` move behind a per-target
|
||||
port instead of leaking into a "shared" adapter.
|
||||
* *The kernel leaf* — the raw syscalls + `_start` entry stub. This is a
|
||||
*link-time symbol swap*, NOT a runtime port: the FreeBSD RTL archive links a
|
||||
syscall-stub object that resolves the same `external name` symbols (`open`,
|
||||
`read`, …) that libc resolves on Linux. (A runtime `TKernelABI` virtual port
|
||||
was tried and reverted — it destabilised self-hosting for zero present
|
||||
benefit; see the kernel-leaf note in the architecture doc.)
|
||||
* `TPlatformLayout` — `struct stat` layout and the OS constants (`O_*`,
|
||||
`CLOCK_*`, `WNOHANG`, `SEEK_*`), moved behind a per-target runtime port
|
||||
(Step 0b, done) instead of leaking into a "shared" adapter.
|
||||
|
||||
Target divergence therefore lives in *which adapter object is instantiated*,
|
||||
decided once at startup — and the test runner can select the FreeBSD adapter
|
||||
at run time to test it natively on a FreeBSD host, or structurally from Linux.
|
||||
The implementation steps below assume the structural refactor in the
|
||||
architecture doc (introduce toolkit/registry, extract `TPlatformLayout`,
|
||||
introduce `TKernelABI`) lands first as behaviour-preserving refactors against
|
||||
the existing Linux suite.
|
||||
Target divergence therefore lives in *which adapter object is instantiated and
|
||||
which RTL archive is linked* — and the test runner can select the FreeBSD
|
||||
adapter to test it natively on a FreeBSD host, or structurally from Linux.
|
||||
The implementation steps below assume the structural refactors in the
|
||||
architecture doc (toolkit/registry — Step 0a done; `TPlatformLayout` — Step 0b
|
||||
done) are already in place.
|
||||
|
||||
== Implementation plan
|
||||
|
||||
Each step states a verification check, per the project's goal-driven
|
||||
convention. Steps 0a–0c are the behaviour-preserving structural refactor from
|
||||
convention. Steps 0a–0b are the behaviour-preserving structural refactor from
|
||||
`docs/native-target-architecture.adoc` (no FreeBSD code, no behaviour change —
|
||||
verified against the existing Linux suite/fixpoints). Steps 1–9 add the
|
||||
FreeBSD target as a new adapter family on those clean seams. Strategy B
|
||||
(direct syscalls, static `ET_EXEC`) is assumed throughout; there is no
|
||||
dynamic-link / sysroot work on this path.
|
||||
verified against the existing Linux suite/fixpoints; *both are done*). Steps
|
||||
1–9 add the FreeBSD target as a new adapter family on those clean seams.
|
||||
Strategy B (direct syscalls, static `ET_EXEC`) is assumed throughout; there is
|
||||
no dynamic-link / sysroot work on this path.
|
||||
|
||||
=== Step 0a — Toolkit + registry (refactor, no behaviour change)
|
||||
|
||||
|
|
@ -296,15 +297,21 @@ dynamic-link / sysroot work on this path.
|
|||
No `{$IFDEF}`; the layout object is selected at startup.
|
||||
* *Verify:* full suite unchanged (the Linux layout reproduces current values).
|
||||
|
||||
=== Step 0c — Introduce `TKernelABI` (refactor, no behaviour change)
|
||||
=== Step 0c — Kernel leaf: link-time swap, not a runtime port (revised)
|
||||
|
||||
* Add the `TKernelABI` port (syscall + entry-stub leaf). The first Linux
|
||||
adapter may still *delegate to the existing libc bindings* so the seam lands
|
||||
with zero behaviour change before any syscall rewrite.
|
||||
* Lift the ARC field-kind walkers and record-return classifier into
|
||||
`TNativeBackend` as template methods with abstract per-CPU leaves, as the
|
||||
backend base class already plans.
|
||||
* *Verify:* full suite + native fixpoint unchanged.
|
||||
* *No refactor in this step.* A runtime `TKernelABI` virtual port was attempted
|
||||
and reverted: routing the RTL's kernel calls through a 30-method virtual class
|
||||
+ a new global destabilised self-hosting (segfaulting binaries, non-converging
|
||||
across bootstrap stages) for zero present benefit — the Linux adapter was pure
|
||||
libc delegation. See the kernel-leaf note in
|
||||
`docs/native-target-architecture.adoc`.
|
||||
* The RTL keeps its `external name 'open'`/`'read'`/… bindings unchanged. The
|
||||
FreeBSD kernel leaf is introduced as a *link-time symbol-stub object* when the
|
||||
FreeBSD RTL archive is built (Step 4), resolving those same symbols with raw
|
||||
syscalls. Nothing in the Linux RTL changes.
|
||||
* The ARC-walker lift into `TNativeBackend` template methods is *deferred* —
|
||||
it is arm64 groundwork (FreeBSD reuses the x86_64 backend unchanged) and does
|
||||
not block this target.
|
||||
|
||||
=== Step 1 — FreeBSD link target + toolkit skeleton
|
||||
|
||||
|
|
@ -330,11 +337,11 @@ dynamic-link / sysroot work on this path.
|
|||
|
||||
=== Step 3 — Freestanding `_start` entry stub
|
||||
|
||||
* The FreeBSD `TKernelABI` adapter's entry contract emits `_start` (not
|
||||
`__libc_start_main`): read `argc` at `[%rsp]`, `argv` at `[%rsp+8]`, align
|
||||
the stack, call `_SetArgs`, run the body, then `exit` via syscall. The
|
||||
linker's static `ET_EXEC` / `_start` path (Phase B) already supports this —
|
||||
no GOT/PLT, no interp, no CRT.
|
||||
* The FreeBSD kernel-stub object provides `_start` (not `__libc_start_main`):
|
||||
read `argc` at `[%rsp]`, `argv` at `[%rsp+8]`, align the stack, call
|
||||
`_SetArgs`, run the body, then `exit` via syscall. The linker's static
|
||||
`ET_EXEC` / `_start` path (Phase B) already supports this — no GOT/PLT, no
|
||||
interp, no CRT.
|
||||
* *Verify:* `freebsd-x86_64` link produces an `ET_EXEC` with entry `_start`
|
||||
and no `PT_INTERP`; linker unit test asserts the ELF shape.
|
||||
|
||||
|
|
@ -348,11 +355,12 @@ dynamic-link / sysroot work on this path.
|
|||
* Add a FreeBSD syscall-number table (e.g. open=5, read=3, write=4, close=6,
|
||||
exit=1, plus `fstat`/`lseek`/`mmap`/`munmap`/… — FreeBSD numbers, *not*
|
||||
Linux's).
|
||||
* Implement the `TKernelABI` leaves against the trampoline, then the harder
|
||||
leaves per the work breakdown above (`memcpy/memset/memcmp` freestanding;
|
||||
pure-Pascal `localtime_r/gmtime_r/timegm`; `mmap`-based allocator with a
|
||||
no-`mremap` grow fallback; `thr_new`/`_umtx_op` threads last; verify the
|
||||
FreeBSD `jmp_buf` for setjmp/longjmp; an `atexit` registry).
|
||||
* Implement the syscall-stub leaves (exporting `open`/`read`/`write`/… as raw
|
||||
syscalls) against the trampoline, then the harder leaves per the work
|
||||
breakdown above (`memcpy/memset/memcmp` freestanding; pure-Pascal
|
||||
`localtime_r/gmtime_r/timegm`; `mmap`-based allocator with a no-`mremap` grow
|
||||
fallback; `thr_new`/`_umtx_op` threads last; verify the FreeBSD `jmp_buf` for
|
||||
setjmp/longjmp; an `atexit` registry).
|
||||
* *Verify:* a FreeBSD hello-world + file-I/O smoke test produce correct output
|
||||
under emulation.
|
||||
|
||||
|
|
@ -418,8 +426,8 @@ here so the FreeBSD work is shaped to extend rather than be re-done:
|
|||
exists, but Windows is materially more backend work than FreeBSD.
|
||||
* *Syscall/startup abstraction.* Each OS gets its own entry stub and either
|
||||
a syscall leaf (Strategy B) or an import-library leaf (Win32: `kernel32`).
|
||||
Building the FreeBSD direct-syscall leaf as a clean, target-keyed
|
||||
`TKernelABI` adapter (Step 4) is the template every later OS follows.
|
||||
Building the FreeBSD direct-syscall leaf as a clean, link-time-swapped
|
||||
syscall-stub object (Step 4) is the template every later OS follows.
|
||||
* *Host independence.* Strategy B's "no host CRT, no host libc" property is
|
||||
what makes Linux-hosted cross-compilation tool-free. Every target that
|
||||
can be driven by direct syscalls (Linux, FreeBSD) should support it;
|
||||
|
|
@ -468,18 +476,19 @@ A `freebsd-x86_64` native backend is feasible and the codebase is already
|
|||
staged for it: the instruction selector, register allocator, System V ABI
|
||||
lowering, ELF relocation core, and OPDF debug sections are all reused
|
||||
unchanged. The entire FreeBSD-specific surface is (1) a FreeBSD `TLinkTarget`
|
||||
(OSABI, base, page size), (2) a FreeBSD `TPlatformLayout` (`struct stat`/
|
||||
`struct tm` layouts and OS constants), and (3) a FreeBSD `TKernelABI` (direct
|
||||
syscalls + `_start` entry stub).
|
||||
(OSABI, base, page size), (2) a FreeBSD `TPlatformLayout` (`struct stat` layout
|
||||
and OS constants), and (3) a FreeBSD kernel-stub object (raw syscalls + `_start`
|
||||
entry stub) that the FreeBSD RTL archive links in place of libc.
|
||||
|
||||
The chosen kernel-access strategy is *direct syscalls into a static `ET_EXEC`*
|
||||
(Strategy B): the linker's static path already supports it, and it is the only
|
||||
route that delivers the stated goal — cross-compiling a runnable FreeBSD binary
|
||||
from a Linux host with no external tools and nothing staged. Its cost is the
|
||||
libc-replacement leaves (threads, allocator, libc string ops, date math),
|
||||
which become methods on the FreeBSD `TKernelABI` adapter, done hardest-last.
|
||||
A conventional libc-linked path (Strategy A) is documented only as a fallback
|
||||
shape and is not on this implementation path.
|
||||
which are implemented in that per-target syscall-stub object (a link-time swap,
|
||||
not a runtime port), done hardest-last. A conventional libc-linked path
|
||||
(Strategy A) is documented only as a fallback shape and is not on this
|
||||
implementation path.
|
||||
|
||||
Structurally, FreeBSD is *one new adapter family* — composed by an Abstract
|
||||
Factory toolkit, resolved from a registry, with zero `{$IFDEF}` and no target
|
||||
|
|
|
|||
|
|
@ -90,29 +90,74 @@ startup. Callers depend on the port, never on a concrete target.
|
|||
| `TRtlPlatform` (exists)
|
||||
| High-level OS operations (file/dir/process/time/console). Unchanged.
|
||||
|
||||
| `TKernelABI` (new)
|
||||
| The *syscall + startup leaf*: raw `Open/Read/Write/Close/Mmap/Munmap/Exit/
|
||||
…` for the target kernel, and the entry-stub contract (how `_start` reads
|
||||
`argc/argv`, how `exit` is issued). Adapters: `TKernelLinuxX86_64`,
|
||||
`TKernelFreeBSDX86_64`. In Strategy B, `TRtlPlatformPosix` calls
|
||||
`GKernel.Open(...)` instead of `external name 'open'`.
|
||||
| Kernel leaf (NOT a runtime port — see below)
|
||||
| The *syscall + startup leaf*: `open/read/write/close/mmap/exit/…` and the
|
||||
`_start` entry stub. This is a *link-time symbol swap*, not a runtime
|
||||
virtual port: each target's RTL archive resolves the same `external name`
|
||||
symbols differently. See <<kernel-leaf>> for the rationale and the design
|
||||
that was tried and rejected.
|
||||
|
||||
| `TPlatformLayout` (new)
|
||||
| The target's *struct layouts and OS constants*: `struct stat` field
|
||||
offsets, `struct tm` layout, `O_*` flag bits, signal numbers, `SEEK_*`,
|
||||
`CLOCK_*`. This is where the Linux constants leaking into
|
||||
`TRtlPlatformPosix` go to live properly, per target.
|
||||
`TRtlPlatformPosix` go to live properly, per target. Implemented in Step 0b.
|
||||
|===
|
||||
|
||||
[#kernel-leaf]
|
||||
[NOTE]
|
||||
.The kernel leaf is a link-time swap, not a runtime port
|
||||
====
|
||||
`TKernelABI` and `TPlatformLayout` are kept *separate* deliberately. The
|
||||
syscall ABI and the struct/constant layout vary on *different axes*: a future
|
||||
`freebsd-arm64` may reuse FreeBSD's syscall numbers (`TKernelABI`) while
|
||||
needing different struct packing (`TPlatformLayout`), and vice-versa. Two
|
||||
ports let those vary independently instead of forcing a four-way adapter
|
||||
matrix. The Abstract Factory (below) composes the correct pair so callers
|
||||
never assemble an inconsistent combination by hand.
|
||||
An earlier revision of this design specified a runtime `TKernelABI` virtual
|
||||
class (≈30 abstract methods) with a `GKernel` global, through which every
|
||||
`TRtlPlatformPosix` kernel call was routed; the Linux adapter forwarded each
|
||||
method to the existing libc binding, and a FreeBSD adapter would later issue
|
||||
raw syscalls.
|
||||
|
||||
*That approach was implemented and reverted (2026-06-25).* Introducing the
|
||||
class and the third managed RTL global into `rtl.platform`/`rtl.platform.posix`
|
||||
— the most layout-sensitive units in the self-hosting RTL — destabilised the
|
||||
bootstrap. Binaries built against the modified RTL segfaulted at startup with
|
||||
a corrupted/overflowed stack that no debugger (gdb or pdr) could unwind. The
|
||||
failure was bootstrap-stage-dependent and non-converging: the release binary
|
||||
built a *working* hello-world from the modified RTL source, but the
|
||||
release-built stage-1 emitted a *crashing* one, and stage-2 was worse. The
|
||||
precise cause was not isolated (candidates: a `TimeNow` method-name collision
|
||||
with `TRtlPlatform.TimeNow` in the vtable, the compiler baking layout for a new
|
||||
managed global, or the large abstract vtable), but the verdict is independent
|
||||
of the cause: *the abstraction carried real self-hosting risk for zero present
|
||||
benefit* — the Linux adapter was pure libc delegation, so nothing functional
|
||||
changed until a FreeBSD syscall adapter existed.
|
||||
|
||||
The replacement design is a *link-time symbol swap*, the pattern Blaise already
|
||||
uses for `blaise_setjmp_x86_64.s` and `blaise_atomic_x86_64.s`:
|
||||
|
||||
* The RTL keeps its `external name 'open'` / `'read'` / `'write'` / … bindings
|
||||
exactly as today.
|
||||
* On Linux (and any libc-linked build), those symbols resolve to libc.
|
||||
* For FreeBSD Strategy B, the FreeBSD RTL archive instead links a small
|
||||
syscall-stub object that *exports the same symbol names* (`open`, `read`,
|
||||
…) implemented as raw `syscall` instructions with the FreeBSD numbers and
|
||||
carry-flag error translation, plus a `_start`.
|
||||
* The kernel leaf therefore varies by *which object the per-target archive
|
||||
composes* — selected at link time by the toolkit/RTL-archive choice — with
|
||||
*no new runtime virtual dispatch, no new global, and no change to the
|
||||
layout-sensitive RTL units*.
|
||||
|
||||
Why this is better here: the kernel mechanism is genuinely a build-time
|
||||
property of a target (a binary is libc-linked *or* freestanding; it never
|
||||
switches at run time), so a link-time swap models it exactly, while a runtime
|
||||
port adds vtable/global surface to the RTL for a choice that is never made at
|
||||
run time. `TPlatformLayout` stays a runtime object because its values
|
||||
(struct offsets, flag bits) are read by shared method bodies and genuinely
|
||||
differ per target instance; the kernel leaf does not need that — it needs a
|
||||
different *symbol implementation*, which the linker already selects.
|
||||
|
||||
`TPlatformLayout` and the kernel leaf still vary on *different axes* (a future
|
||||
`freebsd-arm64` may reuse FreeBSD syscall numbers but need different struct
|
||||
packing), so they remain separate concerns; they are simply realised by
|
||||
*different mechanisms* — a runtime object for layout, a link-time symbol set
|
||||
for the kernel leaf.
|
||||
====
|
||||
|
||||
Compiler-side ports (already present, kept as ports):
|
||||
|
|
@ -142,16 +187,19 @@ call vary by target"*:
|
|||
getenv/exit/nanosleep/pipe/dup2/fork/execvp/waitpid`
|
||||
| *Yes* — different syscall numbers; FreeBSD reports errors via the carry
|
||||
flag, not a negative return.
|
||||
| Route through `TKernelABI`.
|
||||
| Link-time symbol swap: the FreeBSD archive links a syscall-stub object
|
||||
exporting these names; the RTL's `external name` bindings are unchanged
|
||||
(see <<kernel-leaf>>).
|
||||
|
||||
| `mmap/munmap/mremap` (`blaise_mem.pas`)
|
||||
| *Yes* — and FreeBSD has no `mremap`.
|
||||
| Route through `TKernelABI` (memory leaf, with a grow-path fallback).
|
||||
| Link-time symbol swap (memory leaf, with a grow-path fallback for the
|
||||
missing `mremap`).
|
||||
|
||||
| `pthread_create/join/mutex_*` (`blaise_thread.pas`, and the mutexes used by
|
||||
`blaise_exc.pas`/`blaise_weak.pas`)
|
||||
| *Yes* — no pthreads without libc; FreeBSD uses `thr_new`/`_umtx_op`.
|
||||
| Route through `TKernelABI` (threads leaf — the hardest, done last).
|
||||
| Link-time symbol swap (threads leaf — the hardest, done last).
|
||||
|
||||
| `localtime_r/gmtime_r/timegm` (`rtl.platform.posix.pas`)
|
||||
| *Yes* — libc-dependent, no syscall equivalent.
|
||||
|
|
@ -176,27 +224,31 @@ call vary by target"*:
|
|||
| Leave as-is.
|
||||
|===
|
||||
|
||||
So the refactor is *narrow and funnelled*, not unit-by-unit:
|
||||
So the refactor is *narrow and link-time*, not unit-by-unit:
|
||||
|
||||
. *One unit changes shape* — `rtl.platform.posix.pas`: its `external name
|
||||
'open'` bindings become `GKernel.Open(...)` calls. This is the bulk of the
|
||||
kernel-facing surface and the natural caller of the port.
|
||||
. *A few units lose only their kernel-touching leaves* — `blaise_mem`,
|
||||
`blaise_thread`, `blaise_exc`, `blaise_weak` keep all their logic; only the
|
||||
handful of `mmap`/`pthread_*` lines move behind `TKernelABI`. The units are
|
||||
*not* re-architected.
|
||||
. *No RTL unit changes shape for the kernel leaf* — `rtl.platform.posix.pas`
|
||||
keeps its `external name 'open'` bindings verbatim. The FreeBSD difference is
|
||||
*which object resolves those symbols* (libc vs a raw-syscall stub), chosen
|
||||
when the per-target RTL archive is composed — not a code change in the unit.
|
||||
. *Units keep all their logic* — `blaise_mem`, `blaise_thread`, `blaise_exc`,
|
||||
`blaise_weak` are untouched; their `mmap`/`pthread_*` `external name`
|
||||
bindings are satisfied by the target's chosen kernel-stub object. The units
|
||||
are *not* re-architected.
|
||||
. *Most units are left alone* — pure-computation and CPU-keyed-asm units
|
||||
(`blaise_str`, `blaise_float`, the atomic/setjmp stubs) do not touch a port.
|
||||
(`blaise_str`, `blaise_float`, the atomic/setjmp stubs) need nothing.
|
||||
`localtime_r`/etc. become one target-independent Pascal unit.
|
||||
|
||||
The principle: *every kernel-varying leaf funnels into the single `TKernelABI`
|
||||
port; everything that does not vary by target stays where it is.* One port is
|
||||
the one place "what does FreeBSD do differently" lives — adding a port per unit
|
||||
would defeat that. Forcing target-invariant code (memcpy, string formatting,
|
||||
atomics) through a port would be abstraction for its own sake — the
|
||||
over-engineering this design explicitly rejects.
|
||||
The principle: *the kernel-varying leaves are satisfied by one per-target
|
||||
symbol set chosen at link time; everything that does not vary by target stays
|
||||
where it is.* That single stub object is the one place "what does FreeBSD do
|
||||
differently at the syscall level" lives. Routing these through a runtime
|
||||
virtual port instead was tried and reverted (<<kernel-leaf>>): it added
|
||||
vtable/global surface to the layout-sensitive RTL for a choice that is never
|
||||
made at run time. Forcing target-invariant code (memcpy, string formatting,
|
||||
atomics) through any indirection would likewise be abstraction for its own sake
|
||||
— the over-engineering this design rejects.
|
||||
|
||||
The migration sequence below exploits this: Step 0c introduces `TKernelABI`
|
||||
The migration sequence below exploits this: Step 0c introduces the kernel leaf
|
||||
with a Linux adapter that *still delegates to the existing libc bindings*, so
|
||||
every caller is repointed at the port with *zero behaviour change* (verified
|
||||
against the current Linux suite) before a single syscall is rewritten. No unit
|
||||
|
|
@ -212,18 +264,25 @@ and produces the *consistent family* of adapters for that target:
|
|||
type
|
||||
TTargetToolkit = class
|
||||
public
|
||||
function Name: string; virtual; abstract; { "linux-x86_64" }
|
||||
function MakeBackend: TNativeBackend; virtual; abstract;
|
||||
function MakeLinkTarget: TLinkTarget; virtual; abstract;
|
||||
function MakeKernelABI: TKernelABI; virtual; abstract; { runtime side }
|
||||
function MakeLayout: TPlatformLayout; virtual; abstract; { runtime side }
|
||||
function TargetName: string; virtual; abstract;
|
||||
end;
|
||||
----
|
||||
|
||||
The compiler-side toolkit (Step 0a, as built) makes the backend and the link
|
||||
target. It does *not* have a `MakeKernelABI` — the kernel leaf is a link-time
|
||||
symbol set in the per-target RTL archive, not a runtime object the compiler
|
||||
constructs (<<kernel-leaf>>). `TPlatformLayout` is also not made here: it is a
|
||||
runtime RTL object selected at the produced binary's startup, in that binary's
|
||||
own RTL archive, not in the compiler. (The method is named `Name`, not
|
||||
`TargetName`, to avoid colliding with the unit-level `TargetName` free
|
||||
function.)
|
||||
|
||||
Why Abstract Factory and not free functions: the produced objects must be
|
||||
*coherent as a set*. A FreeBSD backend paired with a Linux link target, or a
|
||||
FreeBSD syscall ABI paired with Linux struct offsets, is a silent bug. A
|
||||
factory guarantees the family is internally consistent — this is the textbook
|
||||
*coherent as a set*. A FreeBSD backend paired with a Linux link target is a
|
||||
silent bug. A factory guarantees the family is internally consistent — this is
|
||||
the textbook
|
||||
Abstract Factory motivation (families of related products that must be used
|
||||
together).
|
||||
|
||||
|
|
@ -255,9 +314,10 @@ hard-wiring with a lookup that the CLI *and* the test runner share.
|
|||
| GoF pattern | Applied to | Why it earns its place
|
||||
|
||||
| Strategy
|
||||
| `TNativeBackend`, `TKernelABI`, `TRtlPlatform`, `TPlatformLayout`
|
||||
| `TNativeBackend`, `TRtlPlatform`, `TPlatformLayout`
|
||||
| Interchangeable target behaviour chosen at run time. Already in use
|
||||
(`TProfilerStrategy`, `TNativeBackend`).
|
||||
(`TProfilerStrategy`, `TNativeBackend`). Note the kernel leaf is *not* a
|
||||
Strategy object — it is a link-time symbol swap (<<kernel-leaf>>).
|
||||
|
||||
| Abstract Factory
|
||||
| `TTargetToolkit`
|
||||
|
|
@ -299,11 +359,13 @@ target's runtime into every produced binary.
|
|||
resolves one toolkit from the registry.
|
||||
|
||||
| A *produced* binary (e.g. a FreeBSD app)
|
||||
| Must contain *only its own* runtime adapter (`TKernelABI` /
|
||||
`TPlatformLayout` / `TRtlPlatform`), selected at that binary's own startup.
|
||||
The unused targets' runtime code must NOT be linked in. This falls out of
|
||||
per-target RTL archives: a FreeBSD binary links
|
||||
`blaise_rtl-freebsd-x86_64.a`, which contains only the FreeBSD adapters.
|
||||
| Must contain *only its own* target's RTL: the `TRtlPlatform`/`TPlatformLayout`
|
||||
runtime objects and the kernel-leaf symbol implementation for that target.
|
||||
This falls out of per-target RTL archives — a FreeBSD binary links
|
||||
`blaise_rtl-freebsd-x86_64.a`, which contains the FreeBSD layout object and
|
||||
the FreeBSD syscall-stub object; the Linux archive contains the libc-backed
|
||||
equivalents. The kernel leaf in particular is fixed purely by *which archive
|
||||
is linked*, so no run-time selection is needed for it.
|
||||
|===
|
||||
|
||||
So: the compiler chooses a target *to emit for*; the produced binary chooses a
|
||||
|
|
@ -319,7 +381,7 @@ Because a toolkit is resolved from a `TTargetDesc` rather than frozen by
|
|||
|
||||
. *Native self-target.* On a FreeBSD host, `HostTarget()` resolves the
|
||||
FreeBSD toolkit, so running the compiler's own test suite there exercises
|
||||
the FreeBSD `TKernelABI`/`TPlatformLayout`/`TNativeBackend` adapters
|
||||
the FreeBSD kernel-stub object, `TPlatformLayout`, and `TNativeBackend`
|
||||
natively — no rebuild, no special flag.
|
||||
|
||||
. *Cross-target static coverage.* On any host, the e2e harness can loop over
|
||||
|
|
@ -341,37 +403,52 @@ This is the *structural* refactor; the FreeBSD-specific content (syscall
|
|||
numbers, struct layouts, `_start` shape) lives in
|
||||
`freebsd-x86_64-backend-design.adoc`.
|
||||
|
||||
. Introduce `TTargetToolkit` + `TTargetRegistry`; register the existing Linux
|
||||
target through them. Re-express `--target` resolution via the registry.
|
||||
*No behaviour change* — verify the full suite still passes (pure refactor).
|
||||
. Extract `TPlatformLayout` from `TRtlPlatformPosix`: move `TStatBuf`/`TTm`
|
||||
layouts and the `O_*`/`S_*`/`CLOCK_*`/`WNOHANG`/`SEEK_*` constants behind the
|
||||
port; the Linux adapter supplies today's values. Verify suite unchanged.
|
||||
. Introduce `TKernelABI`; for Linux, the first adapter may still delegate to
|
||||
the libc bindings (no behaviour change) so the seam lands before the
|
||||
syscall rewrite. Verify suite unchanged.
|
||||
. Lift the ARC field-kind walkers and record classifier into `TNativeBackend`
|
||||
as template methods with abstract per-CPU leaves (as the base class already
|
||||
plans), so a second CPU/OS adapter inherits them.
|
||||
. Only then add the `TFreeBSDX86_64Toolkit` adapters (per the FreeBSD design
|
||||
doc). Adding the target touches *no* existing target's code — it is new
|
||||
files plus one registry line.
|
||||
. *(DONE — Step 0a)* Introduce `TTargetToolkit` + `TTargetRegistry`; register
|
||||
the existing Linux target through them. Re-express `--target` resolution via
|
||||
the registry. *No behaviour change* — full suite + all fixpoints verified.
|
||||
. *(DONE — Step 0b)* Extract `TPlatformLayout` from `TRtlPlatformPosix`: move the
|
||||
`struct stat` layout (now an opaque buffer + offset accessors) and the
|
||||
`O_*`/`S_*`/`CLOCK_*`/`WNOHANG`/`SEEK_*` constants behind the port; the Linux
|
||||
adapter (`rtl.platform.layout.linux`) supplies today's values. Verified.
|
||||
. *(REVISED — kernel leaf)* The kernel leaf is a *link-time symbol swap*, NOT a
|
||||
runtime port. A runtime `TKernelABI` virtual port was attempted here and
|
||||
reverted because it destabilised self-hosting for zero present benefit
|
||||
(<<kernel-leaf>>). No refactor is needed in this step: the RTL keeps its
|
||||
`external name` bindings, and the FreeBSD syscall-stub object is introduced
|
||||
when the FreeBSD RTL archive is built (FreeBSD design doc, Step 4).
|
||||
. *(DEFERRED)* Lift the ARC field-kind walkers and record classifier into
|
||||
`TNativeBackend` as template methods with abstract per-CPU leaves. This is
|
||||
*arm64 groundwork* — FreeBSD reuses the x86_64 backend unchanged, so it does
|
||||
not block the FreeBSD target and is deferred until a second CPU lands.
|
||||
. Add the `TFreeBSDX86_64Toolkit` adapters + FreeBSD RTL archive (per the
|
||||
FreeBSD design doc). Adding the target touches *no* existing target's code —
|
||||
new files plus one registry line.
|
||||
|
||||
Steps 1–4 are behaviour-preserving refactors verifiable against the existing
|
||||
Linux suite and fixpoints; step 5 is the actual FreeBSD work. Sequencing the
|
||||
refactor first means the FreeBSD adapter slots into clean seams rather than
|
||||
forcing them open mid-implementation.
|
||||
The completed steps are behaviour-preserving refactors verified against the
|
||||
existing Linux suite and all four fixpoints; the last step is the actual
|
||||
FreeBSD work. Sequencing the refactors first means the FreeBSD adapter slots
|
||||
into clean seams rather than forcing them open mid-implementation.
|
||||
|
||||
== Summary
|
||||
|
||||
Hexagonal architecture is not just viable in Blaise — it is already the chosen
|
||||
idiom for the runtime platform layer and the codegen backend, explicitly
|
||||
`{$IFDEF}`-free. The path to many targets is to *complete* that idiom:
|
||||
promote the kernel leaf and the struct/constant layout to first-class ports
|
||||
(`TKernelABI`, `TPlatformLayout`), compose each target's adapter family with an
|
||||
Abstract Factory (`TTargetToolkit`), resolve toolkits by name through a
|
||||
Registry, keep the linker's container writer behind a Bridge, and reuse the
|
||||
Template-Method backend base. Target divergence then lives entirely in *which
|
||||
object is instantiated*, decided once at startup — the compiler picks a target
|
||||
to emit for, a produced binary picks a platform to run on — and the test runner
|
||||
can select adapters at run time to test any target, natively or structurally.
|
||||
`{$IFDEF}`-free. The path to many targets is to *complete* that idiom, using
|
||||
the right mechanism for each axis of divergence:
|
||||
|
||||
* the struct/constant layout becomes a runtime port (`TPlatformLayout`) —
|
||||
its per-target values are read by shared method bodies;
|
||||
* the kernel leaf becomes a *link-time symbol swap* — a per-target syscall/
|
||||
startup object resolving the RTL's `external name` bindings (a runtime
|
||||
`TKernelABI` port was tried and reverted, <<kernel-leaf>>);
|
||||
* each target's compiler-side adapter family (backend + link target) is
|
||||
composed by an Abstract Factory (`TTargetToolkit`) and resolved by name
|
||||
through a Registry;
|
||||
* the linker's container writer stays behind a Bridge and the backend reuses
|
||||
the Template-Method base.
|
||||
|
||||
Target divergence then lives in *which object is instantiated or which archive
|
||||
is linked*, decided once — the compiler picks a target to emit for, a produced
|
||||
binary picks a platform to run on — with no `{$IFDEF}` and no scattered
|
||||
per-target code. The test runner can select adapters at run time to test any
|
||||
target, natively or structurally.
|
||||
|
|
|
|||
Loading…
Reference in a new issue