6l: port ET_DYN dynamic linking to the ww side

Ports cmd/6l/{dyn,dynout}.c into selfhost/cmd/6l/{dyn,dynout}.ww:
ET_DYN .so loading + PT_INTERP/PT_DYNAMIC ELF emission with .rela.plt,
.gnu.version_r, BIND_NOW. lsym grows dyn fields; pass.ww promotes
undefs to dyn; out.ww dispatches; main.ww takes -L/-l. The ww driver
forwards -L/-l to 6l_ww so 'ww_ww build snake.ww -L /usr/lib -l ncurses
-l c' runs without cc.

Test 996 pins byte-identical output to C-6l on snake.

'make bootstrap' gains a fourth stage with cmp ww3 == ww4, proving
ww3 is byte-stable when used as a compiler — not just a coincidental
two-stage equilibrium.

Four wwstage 6c cgen quirks surfaced and are documented in dynout.ww's
header (two-level field-write through a pointer field, (scalar, str)
tuple returns, def : str, ≤6 arg calling convention).
This commit is contained in:
2026-05-11 12:47:36 +09:00
parent edfc4273f6
commit e217cd32d1
13 changed files with 3361 additions and 111 deletions

View File

@@ -1,31 +1,76 @@
// selfhost/cmd/6l/pass.ww — port of cmd/6l/pass.c.
//
// Resolution + relocation. l_resolve flags every undefined symbol
// referenced by a relocation. l_relocate walks the rel list and
// patches the .text bytes in place once the final virtual base is
// known. Supported relocation kinds: PC32 (=2), PLT32 (=4); both
// are 32-bit PC-relative displacements (PLT32 == PC32 for static).
// referenced by a relocation, and promotes those provided by some
// loaded .so to "dynamic" with a freshly-assigned PLT slot.
// l_relocate walks the rel list and patches the .text bytes in place
// once the final virtual base is known. Dynamic refs are deferred:
// their site is patched later in dynout, once the PLT vaddr is known.
//
// Supported relocation kinds: PC32 (=2), PLT32 (=4); both are 32-bit
// PC-relative displacements (PLT32 == PC32 for static).
use os;
use sym;
use dyn;
def R_X86_64_PC32: i32 = 2;
def R_X86_64_PLT32: i32 = 4;
export fn l_resolve(l: *lnk) i32 = {
// Initialise dynamic-linking sentinels. amalloc zeroes, so
// is_dyn/dyn_lib start clean — but plt_idx and dynsym_idx
// must be -1, not 0.
let si: *lsym = l.syms;
for (si != nil) {
si.plt_idx = -1;
si.dynsym_idx = -1;
si = si.snext;
};
// Promote each undefined sym that some lso exports to dynamic
// and hand it a PLT slot. Iteration order over the relocation
// list determines slot numbering and is stable across runs.
let r: *lrel = l.rels;
for (r != nil) {
if (r.sym != nil) {
if (r.sym.defined == 0) {
os.write(2, "6l: undefined reference to '".ptr, 28u64);
let nm: str = r.sym.name;
os.write(2, nm.ptr, nm.len: u64);
os.write(2, "'\n".ptr, 2u64);
l.errs += 1;
let sym: *lsym = r.sym;
if (sym.is_dyn == 0) {
let so: *lso = l.sos;
for (so != nil) {
if (l_so_provides(so, sym.name) != 0) {
sym.is_dyn = 1;
sym.dyn_lib = so;
sym.plt_idx = l.dyn_n;
l.dyn_n += 1;
so = nil; // break
} else {
so = so.sonext;
};
};
};
};
};
r = r.rnext;
};
// What remains undefined truly is undefined.
let r2: *lrel = l.rels;
for (r2 != nil) {
if (r2.sym != nil) {
if (r2.sym.defined == 0) {
if (r2.sym.is_dyn == 0) {
os.write(2, "6l: undefined reference to '".ptr, 28u64);
let nm: str = r2.sym.name;
os.write(2, nm.ptr, nm.len: u64);
os.write(2, "'\n".ptr, 2u64);
l.errs += 1;
};
};
};
r2 = r2.rnext;
};
return l.errs;
};
@@ -40,6 +85,12 @@ export fn l_relocate(l: *lnk, base: u64) i32 = {
let r: *lrel = l.rels;
for (r != nil) {
if (r.sym != nil) {
// Dynamic refs are patched later in dynout once the
// PLT vaddr is known.
if (r.sym.is_dyn != 0) {
r = r.rnext;
continue;
};
if (r.sym.defined != 0) {
let k: i32 = r.kind;
if (k == R_X86_64_PC32) {