A static FreeBSD link with --debug-opdf failed with 'absolute 32-bit
relocation unsupported in static mode': the OPDF emitter writes a
32-bit section-offset reference (.long label, R_X86_64_32) into the
.opdf section, and the internal linker's static mode rejected all
abs32 forms. The guard was over-conservative - in a non-PIE ET_EXEC
linked at base 0x400000 every mapped address has a final link-time
value that fits in 32 bits, so the relocation is directly resolvable
(this is what GNU ld does, erroring only on overflow).
Static mode now patches S+A with GNU-ld-style range checks:
R_X86_64_32 must fit zero-extended, R_X86_64_32S sign-extended.
Dynamic/PIE behaviour is unchanged (the patched value remains the
unslid link-time address; the OPDF reader applies the run-time slide).
Linux never hit this because Linux binaries link in PIE/dynamic mode;
the bug only surfaced on the first FreeBSD-hosted pdr smoke test.
Guard: TestReloc_Abs32_ResolvesInStaticMode links a .long target data
reference statically and asserts the slot is patched with the symbol's
absolute address.
Verified: all four fixpoints green (FIXPOINT_OK, NATIVE_FIXPOINT_OK,
NATIVE_INTERNAL_OK, WARMCACHE_FIXPOINT_OK); full suite OK (4035) under
both the QBE-fixpoint-built and native-built test runners; cross-
compiled freebsd-x86_64 --debug-opdf hello links and carries a
correctly patched .opdf section.
Complete the end-to-end FreeBSD cross-compile: `blaise --target freebsd-x86_64`
now emits a static, freestanding FreeBSD ET_EXEC on a Linux host with no
external tools.
Target selection through the linker:
* TargetIsFreestanding(target) — FreeBSD (Strategy B: direct syscalls, no
libc) is always a static ET_EXEC. LinkViaInternalLinker sets dynamic mode
to `not (AOpts.Static or freestanding)`, and EnsureRTLObjects selects the
freestanding kernel-leaf unit list the same way — so a FreeBSD target links
the syscall/libc-shim/thread leaves and no libc, regardless of --static.
Per-target platform layout (dependency injection off --target):
* The concrete TPlatformLayout adapter (rtl.platform.layout.<os>) is selected
and linked by the driver (BuildRTLUnitList), and the compiler injects a
direct call to that unit's initialiser — PlatformLayoutInitSym(target) =
rtl.platform.layout.<os>_init — into main, first, for the compile-time
--target. rtl.platform.posix no longer `uses` a concrete layout, so the
shared POSIX code is OS-agnostic and a FreeBSD build carries no Linux layout.
* Bootstrap fallback: each layout unit also defines a WEAK _BlaisePlatformInit
trampoline that _SetArgs calls, so a binary built by an older codegen (which
does not emit main's by-name call) still initialises GPlatformLayout. Weak
binding lets both layout units coexist in one link (e.g. the TestRunner,
which imports the FreeBSD layout via cp.test.platformlayout.freebsd) without
a duplicate-symbol error — the linker keeps the first-seen (host) copy.
FreeBSD libc-shaped leaf:
* New runtime.libc.freebsd (sibling of runtime.libc.linux): getcwd/getenv/
time/waitpid/execvp/sysconf on the FreeBSD syscall leaf. time() reads
CLOCK_REALTIME (no SYS_time on FreeBSD); sysconf(CPU count) uses
sysctl(hw.ncpu) — new SYS___sysctl=202 leaf, MIB {CTL_HW=6, HW_NCPU=3}.
Tests:
* TTargetToolkitTests.TestTargetIsFreestanding_{FreeBSD_True,Linux_False}.
* TInternalLinkerE2ETests.TestCompile_FreeBSDTarget_EmitsStaticFreeBSDExe —
a full compiler-CLI cross-compile asserting EI_OSABI=9, ET_EXEC, no
PT_INTERP (the binary cannot run on the Linux host, so shape only).
Verified: all four fixpoints green; full suite passes under QBE-built and
native-built test runner (4029 tests); the previously latent TestRunner
GPlatformLayout crash is resolved.
A nested function or procedure is lexically scoped to the routine that
contains it, so two sibling routines may each declare a nested helper of
the same name without conflict. Two independent gaps broke this:
* Nested-in-METHOD decls were still entered in the global overload index
(FProcIndex / FProcGroups). The registration guard in
AnalyseStandaloneDecls only excluded nested-in-standalone-proc decls
(FCurrentEnclosingDecl set); AnalyseMethodDecl never sets that field, so
a nested helper in a method body leaked into the global index and two
same-named siblings collided as "Ambiguous overload".
* Nested-in-PROC functions called as an expression could not be resolved.
AnalyseFuncCallExpr's tail consulted only FProcIndex.IndexOf and errored
with "Cannot find declaration for function 'X'" without ever checking the
scoped symbol it had already looked up. (The statement-position path,
AnalyseProcCall, already handled scoped nested procs, which is why only
the function-as-expression form failed.)
Fixes: also exclude a decl from global registration when FCurrentMethodOwner
is set (inside a method body); and in AnalyseFuncCallExpr, when the global
index misses but the scoped symbol carries a backing TMethodDecl, resolve the
call directly against that single decl.
Adds IR and e2e regression tests for both the proc and method enclosing forms,
and removes the MovRaxImm4b/4c rename workaround in cp.test.linker.pas — three
sibling test methods now legitimately share a nested MovRaxImm helper.
Verified: QBE, native, internal-asm and warm-cache fixpoints all green; full
suite passes under both the QBE-built and native-built test runner (4020 tests).
The last Step-4 slice: real POSIX threads for a static FreeBSD build,
plus the TLS setup Step 3 deferred (threads need per-thread %fs TLS, and
the main thread needs it too for threadvar access).
runtime.syscall.freebsd gains four FreeBSD-specific leaves (all numbers
verified against release/14.0.0 sys/sys/syscall.h): sysarch=165 (set the
%fs base for TLS via AMD64_SET_FSBASE=129, passing a POINTER to the base
value), _umtx_op=454 (the futex analogue), thr_new=455 (the clone
analogue), thr_exit=431 (terminate ONE thread — NOT exit(2)/SYS_exit,
which kills the process).
runtime.start.static.freebsd gains the full TLS machinery from its Linux
sibling: the auxv PT_TLS walk (AT_PHDR/PHENT/PHNUM and PT_TLS are the same
values on FreeBSD as Linux), the variant-II block build, BuildThreadTLS/
FreeThreadTLS, and _BlaiseStartC now calls SetupTLS(auxv) before main.
The one difference from Linux: the %fs base is set via
sysarch(AMD64_SET_FSBASE, @base) rather than arch_prctl(ARCH_SET_FS, base).
runtime.thread.static.freebsd (new) is the FreeBSD sibling of
runtime.thread.static.linux — it DEFINES the bare pthread_* names the RTL
imports. The 3-state Drepper mutex (CAS/Xchg) is target-invariant and
copied verbatim; only the wait/wake primitive is swapped
futex -> _umtx_op(UMTX_OP_WAIT_UINT_PRIVATE=15 / WAKE_PRIVATE=16). Thread
create fills a 104-byte struct thr_param at the amd64 offsets
(start_func@0 arg@8 stack_base@16 stack_size@24 tls_base@32 tls_size@40
child_tid@48 parent_tid@56 flags@64 rtp@72), memset-zeroed first so flags/
rtp/spare are 0, and calls thr_new. child_tid/parent_tid point at an
Int64 (FreeBSD long*, not Linux's 4-byte). FreeBSD has no
CLONE_CHILD_CLEARTID, so join uses a dedicated JoinWord: the child
trampoline runs StartRoutine, sets JoinWord:=1, _umtx_op-wakes it, then
thr_exit(nil); pthread_join _umtx_op-waits while JoinWord=0.
Both new/edited units are standalone (nothing in the Linux build graph
uses them), so the Linux self-host is untouched. Test
TestLink_FreeBSDThreadLeaf_SyscallNumbers asserts thr_new(455)/_umtx_op
(454)/thr_exit(431) bytes are present and Linux's clone(56)/futex(202)
are absent. Step 4 (the FreeBSD syscall leaf) is now complete.
The Step-4b RTL leaves for a static FreeBSD build. memcpy/memset/memcmp/
strlen are already target-invariant (runtime.cstub, pure Pascal — shared),
and the x86-64 jmp_buf is ABI-invariant (runtime.setjmp — shared), so this
step is small:
* runtime.syscall.freebsd gains getrandom (FreeBSD SYS 563, not Linux's
318) and an mremap stub. FreeBSD has no in-place-remap syscall, so
mremap is a plain function returning MAP_FAILED (-1); runtime.mem's
large-realloc path already treats an mremap failure as "grow the slow
way" (alloc a fresh region, memcpy, free the old), so exporting a failing
mremap gives the allocator its no-mremap fallback with zero allocator
changes.
* runtime.libc2.freebsd — the FreeBSD sibling of runtime.libc2.linux: the
atexit registry + `exit`, __cxa_atexit, abort, freestanding UTC
civil-calendar gmtime_r/localtime_r/timegm, mkstemp, and system. The
calendar math, atexit registry, and struct tm layout are target-invariant
(tm is identical on Linux/FreeBSD amd64), so the only differences from the
Linux unit are the syscall leaf it builds on (runtime.syscall.freebsd) and
the FreeBSD O_* flag values (O_CREAT=$0200, O_EXCL=$0800 — not Linux's
$40/$80).
Both units are standalone (nothing in the Linux build graph uses them), so
the Linux self-host is untouched — confirmed by the clean fixpoints/suite.
Test TestLink_FreeBSDSyscallLeaf_GetrandomAndMremapStub asserts getrandom
uses 563 (and NOT Linux's 318) and the mremap stub returns -1. Threads
(thr_new/_umtx_op) are Step 4c.
Grow runtime.syscall.freebsd.pas from the Step-3 minimum (_exit/write) to
the file/process syscall leaves, mirroring runtime.syscall.linux but with
FreeBSD syscall numbers and FreeBSD's carry-flag error convention.
Added leaves: open close read lseek fstat stat mkdir rmdir unlink rename
chdir chmod getpid dup2 pipe mmap munmap nanosleep clock_gettime fork
execve wait4 kill getcwd. Each emits the raw `syscall` (number in %rax,
args rdi/rsi/rdx/r10/r8/r9, %rcx->%r10 for >=4 args) and translates
FreeBSD's error convention (CF set, positive errno in %rax) into the
-errno the Pascal call sites in rtl.platform.posix expect:
movq $N, %rax ; syscall ; jae .Lok ; negq %rax ; .Lok: ret
Syscall numbers are the FreeBSD 14 ino64 ABI (mmap=477 lseek=478
fstat=551, NOT the freebsd6/11 compat 197/199/189). FreeBSD's ino64 ABI
has no plain `stat` syscall, so stat(path,buf) is a wrapper over
fstatat(AT_FDCWD,path,buf,0)=552; pipe(fds) wraps pipe2(fds,0)=542. All
numbers verified against release/14.0.0 sys/sys/syscall.h.
The unit is standalone (only runtime.start.static.freebsd references it,
itself outside the Linux build graph), so the Linux self-host is
untouched. Test TestLink_FreeBSDSyscallLeaf_SymbolsAndNumbers links a
fixture and asserts the bare POSIX symbols are defined and the FreeBSD
syscall-number bytes + negq (CF xlate) + mov %rcx,%r10 are present.
Scope: file/process only; memcpy/date/allocator/threads are Step 4b/4c.
Add the FreeBSD entry stub for a static, libc-free ET_EXEC:
- runtime.start.static.freebsd.pas — the FreeBSD sibling of
runtime.start.static.linux. _start captures %rsp, aligns the stack, and calls
the Pascal _BlaiseStartC, which parses argc/argv/envp off the kernel's initial
stack (same layout as Linux), captures environ, calls main(argc, argv), and
exits via the FreeBSD exit syscall. Deliberately minimal: no TLS/auxv-walk
(a trivial program does no threadvar access) — that lands with the threads
work in Step 4, mirroring how the Linux static start gained TLS.
- runtime.syscall.freebsd.pas — the minimum kernel leaf _start needs: _exit
(SYS_exit = 1), write (SYS_write = 4, aliased from _sys_write), and the
environ global. The full file/process/thread leaf grows here in Step 4. ABI
notes record the FreeBSD differences from Linux: different syscall numbers and
the carry-flag error convention (only relevant for the error-translating
wrappers that arrive in Step 4).
Both units are standalone — nothing in the default Linux build graph uses them,
so the Linux RTL and self-hosting are unaffected; the FreeBSD RTL composition
links them at Step 5.
TLinkerE2ETests.TestLink_FreeBSDStart_StaticExecShape links the _start fixture
with the FreeBSD target and asserts the Strategy-B shape: e_type = ET_EXEC,
entry == _start, no PT_INTERP. (Uses LinkToBytes, not ReadFile — the latter
truncates at the ELF header's first NUL byte.)
Step 3 of docs/freebsd-x86_64-backend-design.adoc. Full suite 3850 green;
FIXPOINT_OK.
First increment of the libc-free direct-syscall RTL (Linux as the proving
ground for FreeBSD; docs/linux-syscall-migration.adoc to follow). The default
build is unchanged — libc-backed dynamic ELF; everything here is behind the new
--static flag.
New kernel-leaf units (selected by EnsureRTLObjects when --static):
- runtime.syscall.linux — raw `syscall` stubs DEFINING the bare POSIX names the
RTL imports (open/read/write/lseek/close/fstat/stat/mkdir/rmdir/unlink/rename/
chdir/getpid/dup2/pipe/mmap/munmap/nanosleep/clock_gettime/exit/_exit).
`write`/`exit` are Pascal-reserved, so their Pascal names are _sys_* and the
asm body emits a bare label alias. 4th syscall arg moved %rcx->%r10.
- runtime.cstub — freestanding memcpy/memset/memcmp/strlen (no Char type; PChar
byte indexing).
- runtime.start.static.linux — freestanding _start (argc/argv off %rsp, call
main, exit_group) replacing the libc __libc_start_main entry.
Wiring: --static -> TBackendOpts.Static; EnsureRTLObjects swaps runtime.start
for runtime.start.static.linux and adds the syscall+cstub leaves; the native
internal linker calls SetDynamic(False) for a non-PIE ET_EXEC.
Internal linker static-mode fixes (these are correct in any ET_EXEC, not just
PIE — they were wrongly gated to dynamic-only):
- R_X86_64_64 against a defined symbol resolves to the symbol's absolute address
(no dynamic reloc); only PIE/dynamic mode also emits the .rela.dyn RELATIVE.
- R_X86_64_TPOFF32 (Local-Exec TLS) resolves at link time — it IS the static TLS
model. Updated TestReloc_Quad64_* to assert the now-correct resolution.
A --static link currently reaches symbol resolution and reports the remaining
libc leaves to implement (mremap/getcwd/fork/execvp/waitpid/time/getenv/sysconf/
gmtime_r/localtime_r/timegm/__cxa_atexit/abort/mkstemp/system/pthread_mutex_*) —
the next increments. Full suite green (3829); dynamic native/QBE unaffected.
Partial Stage-3 teardown — the safe removals that do not affect the bootstrap
chain (the runtime Makefile/ar stay until a release ships a source-build-capable
binary, since the v0.12.0-rooted rolling-bootstrap replay needs them for its
pre-source-build commits):
- Remove FindRTLArchive + TToolchain.RTLPath from uToolchain — dead since the
link paths source-build the RTL; the field was set but never read.
- TInternalLinkerE2ETests gated on blaise_rtl.a existing, so it silently skipped
once the archive was gone; gate on the RTL source instead. It now runs (5
tests) — the last automated test still tied to the archive.
- Refresh the runtime mem test/bench build-instruction comments to the
source-built, archive-free invocation (blaise --output, no qbe+gcc+.a).
Full suite green (3829 tests, 1 ignored) — TInternalLinkerE2ETests no longer
skipped.
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.
RTL-unification Stage 2 (docs/rtl-unification-plan.adoc): the native internal
linker no longer links a pre-built blaise_rtl.a. Instead it compiles the
implicit RTL units (runtime.* + rtl.platform.*) from source into an object cache
beside the compiler (compiler/target/rtl/*.o) and links those, recompiling a
unit only when its cached .o is missing or older than the source.
- EnsureRTLObjects (native driver) drives the per-unit compile via self-invoke
(blaise --no-incremental --assembler internal --source U.pas), the proven
per-unit path; LinkViaInternalLinker adds the cached .o via AddOwnedObject
instead of AddArchive.
- uToolchain.RTLSourceDir resolves the RTL source robustly: binary-relative
(compiler/target -> ../src/main/pascal), then CWD-relative (handles a binary
copied to /tmp but invoked from the project root, as the fixpoints do).
- New --rtl-src DIR flag (TBackendOpts.RTLSrcDir) + $BLAISE_RTL_SRC for a
relocated/release binary; the driver fails loudly when the RTL source is
unreachable rather than emitting a broken binary.
Performance: warm cache adds ~0ms (RTL skipped); a cold full RTL recompile is
~0.4s. Scope: this changes the NATIVE internal-linker path only — the QBE
backend and cc-link paths still use blaise_rtl.a, so the archive remains a build
artifact (the fixpoint scripts and QBE-based tests still build it).
TestLink_MissingRTL_FailsLoudly rewritten for the new contract (--rtl-src at a
nonexistent dir must fail naming the RTL source). All four fixpoints OK; full
suite 3829 green; cold-bootstrap from the existing release works.
LinkViaInternalLinker always built the linker with the default (Linux)
TLinkTarget, so --target freebsd-x86_64 still emitted a SYSV/Linux ELF. Resolve
the target's toolkit and build TLinker with its MakeLinkTarget, so the emitted
ELF carries the right EI_OSABI / machine / load base for the requested target.
--target freebsd-x86_64 now stamps EI_OSABI = 9 (FreeBSD); the default Linux
target is unchanged (EI_OSABI = 0, still compiles+runs). The FreeBSD binary is
not yet runnable (Strategy-B syscall _start stub lands later) — this is the
OSABI/link-target wiring (Step 1 of the FreeBSD design).
Adds TLinkerE2ETests.TestLink_FreeBSDTarget_StampsOSABI locking in that the
FreeBSD link target produces a static ET_EXEC with EI_OSABI=9.
All four fixpoints OK; full suite 3813 green.
The internal linker silently emitted a runtime-less binary when blaise_rtl.a
could not be found beside the compiler (RTLPath empty): every RTL symbol became
an undefined dynamic import, so the binary linked but died at runtime with
`undefined symbol: _SetArgs`.
scripts/rolling-bootstrap.sh hit exactly this: it carried each step's binary
forward as _carry_blaise with its RTL as _carry_rtl.a, but FindRTLArchive looks
for `blaise_rtl.a` beside the binary (CompilerBinDir) — a name it never used —
so the next step linked the compiler with no runtime archive and SMOKE_FAILED.
- Carry the binary + RTL into a dedicated _carry/ dir as blaise + blaise_rtl.a,
so FindRTLArchive resolves it. Rolling bootstrap reaches HEAD again.
- Make an empty RTLPath a hard error on both link paths (internal and external
cc) instead of producing a broken binary.
- Regression test TInternalLinkerE2ETests.TestLink_MissingRTL_FailsLoudly:
runs a symlinked compiler from an RTL-free dir and asserts a non-zero exit, a
message naming blaise_rtl.a, and no output binary.
The four fixpoint scripts missed this because fixpoint-native.sh links the .s
via gcc, never exercising the internal-linker + FindRTL path on a carried
binary layout.
Four tests exercise the full compiler-driver pipeline with
--backend native --assembler internal --linker internal:
HelloWorld, StringOps (Format/UpperCase/IntToStr), exception
handling (raise + try/except), and virtual method dispatch.
These catch regressions in TLS relocation, CRT discovery,
duplicate-symbol handling, and PIE emission.
Add --linker internal|external option to the native backend driver,
routing the link step through TLinker instead of shelling out to cc.
FindCrtObjects discovers CRT startup objects (Scrt1.o, crti.o, crtn.o,
crtbeginS.o, crtendS.o) on Debian and RedHat layouts.
Fix SHF_TLS constant from $200 to the correct ELF spec value $400 in
both the reader and writer — the wrong value caused .tbss sections to
be placed twice (once in the normal NOBITS loop, once in the TLS
section), producing doubled negative TPOFF32 offsets and segfaults.
Fix FTlsAddr to use the post-alignment address from PlaceSection rather
than the pre-alignment cursor, which caused off-by-padding TPOFF values.
Change duplicate global symbol handling from error to first-wins
semantics (matching ld behaviour), needed because the main program
object and archive members may both define system runtime functions.
Verified: FIXPOINT_OK, NATIVE_FIXPOINT_OK, NATIVE_INTERNAL_OK,
3532 tests pass on both QBE-built and native-built test runners.
Implement the Phase B internal-linker pipeline (TLinker in
blaise.linker.elf) on top of the Phase A section merger:
- Symbol resolution: build the global symbol table from merged
sections (STB_GLOBAL over STB_WEAK; two globals = duplicate error),
define the five linker-synthesised symbols (_GLOBAL_OFFSET_TABLE_,
__bss_start, _edata, _end, __TMC_END__), resolve weak-undefined
references to 0 and raise on strong-undefined.
- Static relocations: patch R_X86_64_PC32 / R_X86_64_PLT32 as S+A-P
(PLT32 against an internal symbol relaxes to the direct form, no
PLT). Reject the dynamic-only forms (R_X86_64_64, GOT/TLS) and
absolute 32-bit symbol references with a diagnostic — deferred to
Phase C.
- Layout + emission: assign virtual addresses at a fixed base
(0x400000) in an executable run (.text + .rodata, sharing the first
page with the headers) and a writable run (.data + .bss) on a fresh
page; write a non-PIE ET_EXEC with two PT_LOAD program headers, the
relocated section payloads, and a section-header table for tooling;
mark the file executable.
Platform/arch-variable values (Is64, OSABI, EMachine, BaseAddr,
PageSize) live in TLinkTarget rather than being hard-coded, so FreeBSD
and a future i386 ELF target are a new target record and a PE/Mach-O
container would be a sibling writer behind the same symbol/relocation
core.
The linker is not wired into the compiler driver — it is a standalone
unit exercised only by tests; all backends still link via cc. Driver
integration behind --linker internal is Phase D.
Tests: TLinkerTests (symbol resolution, PC32 cross-object displacement,
R_X86_64_64 rejection, ELF-header/entry-point structure) and
TLinkerE2ETests (hand-written write+exit syscall object linked
internally to an ET_EXEC, executed, asserted on stdout + exit code;
readelf -a reports a clean statically-linked x86-64 executable).
Two native-backend codegen constraints were worked around in this unit:
no string-element assignment through a var-string parameter (append-
style byte building + PChar-local in-place writes), and no capturing
nested functions (private methods instead).
TSectionMerger in the new blaise.linker.elf unit concatenates
like-named allocatable sections across input objects, padding each
contribution to its declared alignment, and records a placement
(merged section + offset) per input section — the basis for symbol
and relocation rebasing in Phase B.
SHT_NOBITS contributions advance the merged size without emitting
bytes; mixing NOBITS and PROGBITS under one name is an error.
Bookkeeping sections (symtab, strtab, rela, .note.GNU-stack,
.comment) are skipped — the linker rebuilds those itself — while
non-alloc .opdf.* debug sections are kept for the OPDF pass-through.
Tests cover text concatenation with placement offsets, alignment
padding between contributions, .bss size accumulation, and the
bookkeeping-section skip list.
First step of the internal-linker plan
(docs/internal-linker-design.adoc): blaise.elfreader parses ELF64
little-endian ET_REL x86-64 objects — section headers with contents,
the symbol table, and RELA relocation entries — and !<arch> static
archives with GNU long-name table support (blaise_rtl.a carries
member names beyond the 15-character ar limit).
Parsed entities are heap objects rather than records to sidestep the
known dynamic-array-of-record element-assignment hazard. The archive
API fills a caller-owned TList (generic function return types are not
supported yet).
TElfReaderTests covers: section bytes and flags from the internal
assembler's output, global function symbols, .quad relocations with
addends, NOBITS sections, bad-magic rejection for both formats, a
synthetic archive exercising the GNU long-name table and member
padding, and a sweep over every member of the real blaise_rtl.a.