Files
ww/selfhost/cmd/w6l/sym.ww
Hojun-Cho 1ac9980f7e selfhost: mirror writable .data + R_X86_64_64 across the wwstage
Bring the wwstage toolchain to parity with C-side DATAW / DATAR /
.data / .rela.data support. With this, w6c_ww + w6a_ww + w6l_ww can
compile, assemble and link `let g: str = "lit";` (and the scalar /
str / slice / struct globals that landed earlier) end-to-end, with
output that's byte-identical to the C-side pipeline.

w6a (types.ww / parse.ww / asm.ww / obj.ww):
  - A_DATAW + A_DATAR opcodes; parser learns `name+disp(SB)`;
    A_DATAR records an R_X86_64_64 reloc in .data via the new
    addrelocdata helper; areloc gains a `section` flag and asym
    an `isdata` flag; obj.ww splits relocs into .rela.text /
    .rela.data, emits .data PROGBITS + .rela.data conditionally,
    and shuffles section indices the same way cmd/w6a/obj.c does
    so byte output stays identical when no DATAW/DATAR are used.

w6l (sym.ww / obj.ww / pass.ww / out.ww / dynout.ww / main.ww):
  - lrel grows `section`; lsym grows `indata`; lobj tracks
    dataoff / datasize; lnk grows combined .data buffer;
  - obj.ww loads .data and .rela.data, registers data symbols
    with indata=1 and val shifted by the input's data_off, and
    the archive scanner includes both .text and .data globals;
  - pass.ww adds R_X86_64_64 (patch 8 bytes in .text or .data
    with sym_va + addend); relocate's signature becomes
    (textva, datava);
  - out.ww emits a second PT_LOAD (R+W) when datalen > 0, with
    .data at the page-aligned offset after .text;
  - dynout.ww refuses .data + -l/-L cleanly (matches the C-side
    error message);
  - main.ww computes text_va / data_va and passes both to
    relocate.

wcc cgen (cgen.ww / cgendecl.ww):
  - letpreintern walks top-level str-lets and interns the strlit
    BEFORE emitdatasection emits its DATA row, so emitletdataw
    can later look up the same label;
  - emitletdataw's 16B branch detects non-empty strlit init and
    emits the 8-zero + 8-LE-len DATAW plus a DATAR slot+0,strlit
    reloc, mirroring cmd/w6c/cgen.c.

Verified: `wwdump_ww -c` byte-matches `w6c` on a `let g: str =
"hello world\n";` fixture; `w6a_ww` and `w6l_ww` produce a
binary byte-identical to the C-side pipeline that runs and
prints "hello world". Bootstrap fixed-point holds (ww2 == ww3 ==
ww4), 26/26 tests green.
2026-05-12 13:03:03 +09:00

112 lines
2.8 KiB
Plaintext

// selfhost/cmd/w6l/sym.ww — port of cmd/w6l/sym.c.
//
// Linker symbol table. Singly-linked list, usually a few hundred
// entries; hashing isn't worth it yet.
use mem;
type lsym = struct {
name: str,
val: u64, // offset within combined .text (or .data when
// indata=1) once linked
defined: i32, // 1 if some lobj defines this symbol
indata: i32, // 1 if defined in .data (writable globals)
owner: *lobj,
idxinowner: i32,
// Dynamic-linking fields. Set by resolve when an undefined sym
// is provided by some loaded lso. pltidx and dynsymidx default
// to -1 (set explicitly by resolve, not by amalloc-zeroing).
isdyn: i32,
dynlib: *lso,
dynversion: str, // matched export's version; len 0 if none
pltidx: i32,
dynsymidx: i32,
snext: *lsym,
};
type lrel = struct {
off: u64, // offset within the relocation's section
section: i32, // 0 = .text, 1 = .data
kind: i32, // R_X86_64_*
sym: *lsym,
addend: i64,
rnext: *lrel,
};
type lobj = struct {
path: str,
buf: *u8, // object bytes
len: u64,
textoff: u64, // offset of .text in combined output
textsize: u64,
dataoff: u64, // offset of .data in combined output
datasize: u64, // bytes contributed to combined .data (0 if none)
onext: *lobj,
};
// lexport — one entry per GLOBAL/WEAK symbol exported by a loaded .so.
// Stored as a chain in the order the .so's dynsym presents them, so
// soprovides_v's first-match semantics agree with the C version.
type lexport = struct {
name: str,
version: str, // len 0 for unversioned globals
enext: *lexport,
};
type lso = struct {
path: str, // full filesystem path used to load
soname: str, // DT_SONAME, or basename if missing
exports: *lexport, // dynsym-order chain of exported names
sonext: *lso,
};
type lnk = struct {
a: *arena,
objs: *lobj,
sos: *lso,
syms: *lsym,
rels: *lrel,
text: *u8, // combined .text
textcap: u64,
textlen: u64,
// Combined .data (writable). Empty unless any input .o has a
// .data PROGBITS section.
data: *u8,
datacap: u64,
datalen: u64,
errs: i32,
dynn: i32, // number of syms routed through PLT
};
fn streq(a: str, b: str) bool = {
if (a.len != b.len) { return false; };
let i: i32 = 0;
for (i < a.len) {
if (a[i] != b[i]) { return false; };
i += 1;
};
return true;
};
export fn intern(l: *lnk, name: str) *lsym = {
let s: *lsym = l.syms;
for (s != nil) {
if (streq(s.name, name)) { return s; };
s = s.snext;
};
let n: *lsym = amalloc(l.a, 96u64): *lsym;
n.name = name;
n.snext = l.syms;
l.syms = n;
return n;
};
export fn lookup(l: *lnk, name: str) *lsym = {
let s: *lsym = l.syms;
for (s != nil) {
if (streq(s.name, name)) { return s; };
s = s.snext;
};
return nil;
};