From 1ac9980f7e9df23c522fbb6ee6f8c4fa596a2543 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 12 May 2026 13:03:03 +0900 Subject: [PATCH] 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. --- selfhost/cmd/w6a/asm.ww | 66 +++++++- selfhost/cmd/w6a/main.combined.ww | 228 +++++++++++++++++++++++-- selfhost/cmd/w6a/obj.ww | 121 +++++++++++-- selfhost/cmd/w6a/parse.ww | 22 ++- selfhost/cmd/w6a/types.ww | 19 ++- selfhost/cmd/w6c/main.combined.ww | 111 +++++++++--- selfhost/cmd/w6l/dynout.ww | 8 + selfhost/cmd/w6l/main.combined.ww | 244 ++++++++++++++++++++++++--- selfhost/cmd/w6l/main.ww | 15 +- selfhost/cmd/w6l/obj.ww | 117 ++++++++++++- selfhost/cmd/w6l/out.ww | 56 +++++- selfhost/cmd/w6l/pass.ww | 34 +++- selfhost/cmd/w6l/sym.ww | 14 +- selfhost/cmd/wcc/cgen.ww | 110 +++++++++--- selfhost/cmd/wcc/cgendecl.ww | 1 + selfhost/cmd/wwdump/main.combined.ww | 111 +++++++++--- 16 files changed, 1132 insertions(+), 145 deletions(-) diff --git a/selfhost/cmd/w6a/asm.ww b/selfhost/cmd/w6a/asm.ww index d0efb006..4dc95fe3 100644 --- a/selfhost/cmd/w6a/asm.ww +++ b/selfhost/cmd/w6a/asm.ww @@ -38,8 +38,9 @@ export fn emitu32(a: *asm_, v: u32) void = { }; export fn addreloc(a: *asm_, off: u64, kind: i32, s: *asym, add: i64) void = { - let r: *areloc = amalloc(a.a, 48u64): *areloc; + let r: *areloc = amalloc(a.a, 64u64): *areloc; r.off = off; + r.section = 0; // .text r.kind = kind; r.asy = s; r.addend = add; @@ -47,6 +48,37 @@ export fn addreloc(a: *asm_, off: u64, kind: i32, s: *asym, add: i64) void = { a.relocs = r; }; +// Record a relocation that lives in the .data section. Used by +// DATAR to patch a 64-bit slot with a symbol's runtime VA. obj.ww +// separates these into .rela.data when emitting the .o. +export fn addrelocdata(a: *asm_, off: u64, kind: i32, s: *asym, add: i64) void = { + let r: *areloc = amalloc(a.a, 64u64): *areloc; + r.off = off; + r.section = 1; // .data + r.kind = kind; + r.asy = s; + r.addend = add; + r.rnext = a.relocs; + a.relocs = r; +}; + +// Append one byte to the writable .data buffer. Mirrors emitbyte +// but targets a.data instead of a.text. +export fn emitdatabyte(a: *asm_, b: u8) void = { + if (a.datalen + 1u64 > a.datacap) { + let nc: u64 = a.datacap; + if (nc == 0u64) { nc = 256u64; }; + nc = nc * 2u64; + let nb: *u8 = os.alloc(nc): *u8; + let i: u64 = 0u64; + for (i < a.datalen) { nb[i] = a.data[i]; i += 1u64; }; + a.data = nb; + a.datacap = nc; + }; + a.data[a.datalen] = b; + a.datalen += 1u64; +}; + // ---- register codes ---------------------------------------------------- // Low 3 bits of register encoding. @@ -270,6 +302,38 @@ export fn encode(a: *asm_) i32 = { for (i < p.nbytes) { emitbyte(a, p.bytes[i]); i += 1u64; }; p = p.link; continue; }; + if (op == A_DATAW) { + // Writable variant: bytes go into .data instead of + // .text. obj.ww emits the extra section conditionally + // on datalen > 0 so .o output stays byte-identical + // for inputs that don't use DATAW. + let s: *asym = intern(a, p.to.asym); + s.defined = 1; + s.isdata = 1; + s.isglobal = 1; + s.addr = a.datalen; + let i: u64 = 0u64; + for (i < p.nbytes) { emitdatabyte(a, p.bytes[i]); i += 1u64; }; + p = p.link; continue; + }; + if (op == A_DATAR) { + // DATAR slot+off(SB), target(SB) — record an + // R_X86_64_64 relocation at slot+off in .data + // pointing at target. Slot must already be defined + // by a prior DATAW. + let holder: *asym = intern(a, p.from.asym); + if (holder.defined == 0) { + p = p.link; continue; + }; + if (holder.isdata == 0) { + p = p.link; continue; + }; + let target: *asym = intern(a, p.to.asym); + let reloff: u64 = holder.addr + p.from.offset: u64; + addrelocdata(a, reloff, 1 /* R_X86_64_64 */, + target, 0i64); + p = p.link; continue; + }; if (op == A_RET) { emitbyte(a, 195u8); // 0xC3 p = p.link; continue; diff --git a/selfhost/cmd/w6a/main.combined.ww b/selfhost/cmd/w6a/main.combined.ww index d8822050..6b054887 100644 --- a/selfhost/cmd/w6a/main.combined.ww +++ b/selfhost/cmd/w6a/main.combined.ww @@ -459,6 +459,13 @@ def A_JNZ: i32 = 58; def A_SYSCALL: i32 = 59; +// Writable data + reloc-only data. Mirror cmd/w6c/6.out.h. +// A_DATAW: bytes land in .data (RW) instead of .text. +// A_DATAR: record an R_X86_64_64 reloc at a .data slot, patched +// to a target symbol's runtime VA at link time. +def A_DATAW: i32 = 60; +def A_DATAR: i32 = 61; + // ---- structs (mirror cmd/w6a/a.h) -------------------------------------- type aoperand = struct { @@ -487,14 +494,16 @@ type asym = struct { name: str, defined: i32, istext: i32, + isdata: i32, // mutually exclusive with istext; DATAW symbols isglobal: i32, - addr: u64, + addr: u64, // offset within its section (.text or .data) idx: i32, snext: *asym, }; type areloc = struct { off: u64, + section: i32, // 0 = .text, 1 = .data kind: i32, asy: *asym, addend: i64, @@ -522,6 +531,14 @@ type asm_ = struct { textcap: u64, textlen: u64, + // Writable .data. Empty unless any DATAW directive was seen; + // obj.ww emits the extra section conditionally so .o output + // stays byte-identical for inputs that don't use DATAW (test + // 991 byte-diff invariant). + data: *u8, + datacap: u64, + datalen: u64, + syms: *asym, relocs: *areloc, fixups: *afixup, @@ -686,6 +703,8 @@ fn opcodelookup(p: *u8, n: u64) i32 = { if (streqlit(p, n, "SYSCALL")) { return A_SYSCALL; }; if (streqlit(p, n, "TEXT")) { return A_TEXT; }; if (streqlit(p, n, "DATA")) { return A_DATA; }; + if (streqlit(p, n, "DATAW")) { return A_DATAW; }; + if (streqlit(p, n, "DATAR")) { return A_DATAR; }; return A_NOP; }; @@ -891,13 +910,25 @@ fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = { return after; }; - // IDENT — register, symbol(SB), or branch label + // IDENT — register, symbol(SB), symbol+disp(SB), or branch label if (isidstart(c0: i32)) { let istart: u64 = off; for (off < n) { if (isidcont(p[off]: i32)) { off += 1u64; continue; }; off = off; off += 0u64; // loop break let in_: u64 = off - istart; + // Optional `+disp` between the ident and `(SB)`. Used + // by DATAR to address bytes within a previously-defined + // .data slot (e.g. `DATAR s+8(SB),...`). + let symdisp: i64 = 0i64; + if (off < n) { if (p[off] == 43u8) { // '+' + off += 1u64; + let v: i64; + let used: u64; + v, used = parsenum(p + off, n - off); + symdisp = v; + off += used; + };}; // IDENT(SB) — external if (off < n) { if (p[off] == 40u8) { // '(' let rstart: u64 = off + 1u64; @@ -910,6 +941,7 @@ fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = { if (r == D_PSB) { out.atype = D_EXTERN; out.asym = dupstr(a.a, p + istart, in_); + out.offset = symdisp; } else { out.atype = D_INDIR; out.reg = r; @@ -1059,8 +1091,9 @@ export fn parse(a: *asm_) i32 = { a.line += 1; continue; }; - if (opc == A_DATA) { - // DATA name(SB),"escaped bytes" — find first '(' as end of name. + if (opc == A_DATA || opc == A_DATAW) { + // DATA / DATAW name(SB),"escaped bytes" — same syntax, + // different destination section (.text vs .data). let q: u64 = r0; let lparen: u64 = n; let scand: bool = true; @@ -1209,8 +1242,9 @@ export fn emitu32(a: *asm_, v: u32) void = { }; export fn addreloc(a: *asm_, off: u64, kind: i32, s: *asym, add: i64) void = { - let r: *areloc = amalloc(a.a, 48u64): *areloc; + let r: *areloc = amalloc(a.a, 64u64): *areloc; r.off = off; + r.section = 0; // .text r.kind = kind; r.asy = s; r.addend = add; @@ -1218,6 +1252,37 @@ export fn addreloc(a: *asm_, off: u64, kind: i32, s: *asym, add: i64) void = { a.relocs = r; }; +// Record a relocation that lives in the .data section. Used by +// DATAR to patch a 64-bit slot with a symbol's runtime VA. obj.ww +// separates these into .rela.data when emitting the .o. +export fn addrelocdata(a: *asm_, off: u64, kind: i32, s: *asym, add: i64) void = { + let r: *areloc = amalloc(a.a, 64u64): *areloc; + r.off = off; + r.section = 1; // .data + r.kind = kind; + r.asy = s; + r.addend = add; + r.rnext = a.relocs; + a.relocs = r; +}; + +// Append one byte to the writable .data buffer. Mirrors emitbyte +// but targets a.data instead of a.text. +export fn emitdatabyte(a: *asm_, b: u8) void = { + if (a.datalen + 1u64 > a.datacap) { + let nc: u64 = a.datacap; + if (nc == 0u64) { nc = 256u64; }; + nc = nc * 2u64; + let nb: *u8 = os.alloc(nc): *u8; + let i: u64 = 0u64; + for (i < a.datalen) { nb[i] = a.data[i]; i += 1u64; }; + a.data = nb; + a.datacap = nc; + }; + a.data[a.datalen] = b; + a.datalen += 1u64; +}; + // ---- register codes ---------------------------------------------------- // Low 3 bits of register encoding. @@ -1441,6 +1506,38 @@ export fn encode(a: *asm_) i32 = { for (i < p.nbytes) { emitbyte(a, p.bytes[i]); i += 1u64; }; p = p.link; continue; }; + if (op == A_DATAW) { + // Writable variant: bytes go into .data instead of + // .text. obj.ww emits the extra section conditionally + // on datalen > 0 so .o output stays byte-identical + // for inputs that don't use DATAW. + let s: *asym = intern(a, p.to.asym); + s.defined = 1; + s.isdata = 1; + s.isglobal = 1; + s.addr = a.datalen; + let i: u64 = 0u64; + for (i < p.nbytes) { emitdatabyte(a, p.bytes[i]); i += 1u64; }; + p = p.link; continue; + }; + if (op == A_DATAR) { + // DATAR slot+off(SB), target(SB) — record an + // R_X86_64_64 relocation at slot+off in .data + // pointing at target. Slot must already be defined + // by a prior DATAW. + let holder: *asym = intern(a, p.from.asym); + if (holder.defined == 0) { + p = p.link; continue; + }; + if (holder.isdata == 0) { + p = p.link; continue; + }; + let target: *asym = intern(a, p.to.asym); + let reloff: u64 = holder.addr + p.from.offset: u64; + addrelocdata(a, reloff, 1 /* R_X86_64_64 */, + target, 0i64); + p = p.link; continue; + }; if (op == A_RET) { emitbyte(a, 195u8); // 0xC3 p = p.link; continue; @@ -1902,12 +1999,14 @@ def SHT_SYMTAB_C: u32 = 2u32; def SHT_STRTAB_C: u32 = 3u32; def SHT_RELA_C: u32 = 4u32; +def SHF_WRITE: u64 = 1u64; def SHF_ALLOC: u64 = 2u64; def SHF_EXECINSTR: u64 = 4u64; def SHF_INFO_LINK: u64 = 64u64; // 0x40 def STB_GLOBAL: u8 = 1u8; def STT_NOTYPE: u8 = 0u8; +def STT_OBJECT: u8 = 1u8; def STT_FUNC: u8 = 2u8; // Sizes of fixed structures. @@ -1987,15 +2086,56 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { let str_: buf; bufinit(&str_, a.a); let sym: buf; bufinit(&sym, a.a); let rela: buf; bufinit(&rela, a.a); + let relad: buf; bufinit(&relad, a.a); // Index 0 = empty. let zero: u8 = 0u8; bufputb(&shstr, &zero, 1u64); bufputb(&str_, &zero, 1u64); - // Section name offsets. + let hasdata: bool = a.datalen > 0u64; + let hasdatarelocs: bool = false; + let rscan: *areloc = a.relocs; + for (rscan != nil) { + if (rscan.section == 1) { hasdatarelocs = true; }; + rscan = rscan.rnext; + }; + + // Section indices (mirror cmd/w6a/obj.c): + // without data, without data-relocs: + // 1=.text 2=.rela.text 3=.symtab 4=.strtab 5=.shstrtab + // with data, no data-relocs: + // 1=.text 2=.rela.text 3=.data 4=.symtab 5=.strtab 6=.shstrtab + // with data + data-relocs: + // 1=.text 2=.rela.text 3=.data 4=.rela.data 5=.symtab + // 6=.strtab 7=.shstrtab + let SH_TEXT: u16 = 1u16; + let SH_DATA: u16 = 0u16; + let SH_RELAD: u16 = 0u16; + let SH_SYMTAB: u16 = 3u16; + if (hasdata) { + SH_DATA = 3u16; + if (hasdatarelocs) { + SH_RELAD = 4u16; + SH_SYMTAB = 5u16; + } else { + SH_SYMTAB = 4u16; + }; + }; + let SH_STRTAB: u16 = SH_SYMTAB + 1u16; + let SH_SHSTR: u16 = SH_STRTAB + 1u16; + + // Section name offsets. Append .data / .rela.data only when + // used so the .shstrtab buffer stays byte-identical for the + // no-DATAW case (test 991 byte-diff invariant). let shntext: u32 = bufputcstr(&shstr, ".text"); let shnrela: u32 = bufputcstr(&shstr, ".rela.text"); + let shndata: u32 = 0u32; + let shnrelad: u32 = 0u32; + if (hasdata) { shndata = bufputcstr(&shstr, ".data"); }; + if (hasdata) { if (hasdatarelocs) { + shnrelad = bufputcstr(&shstr, ".rela.data"); + };}; let shnsymtab: u32 = bufputcstr(&shstr, ".symtab"); let shnstrtab: u32 = bufputcstr(&shstr, ".strtab"); let shnshstrtab: u32 = bufputcstr(&shstr, ".shstrtab"); @@ -2006,8 +2146,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { for (zi < 24) { zsym[zi] = 0u8; zi += 1; }; bufputb(&sym, zsym.ptr, 24u64); - let SH_TEXT: u16 = 1u16; - // Build symbols. let idx: i32 = 1; let s: *asym = a.syms; @@ -2018,8 +2156,13 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { let stname: u32 = bufputcstr(&str_, s.name); wru32(entry.ptr, 0u64, stname); if (s.defined != 0) { - wru8(entry.ptr, 4u64, ((STB_GLOBAL << 4u8) | STT_FUNC)); - wru16(entry.ptr, 6u64, SH_TEXT); + if (s.isdata != 0) { + wru8(entry.ptr, 4u64, ((STB_GLOBAL << 4u8) | STT_OBJECT)); + wru16(entry.ptr, 6u64, SH_DATA); + } else { + wru8(entry.ptr, 4u64, ((STB_GLOBAL << 4u8) | STT_FUNC)); + wru16(entry.ptr, 6u64, SH_TEXT); + }; wru64(entry.ptr, 8u64, s.addr); } else { wru8(entry.ptr, 4u64, ((STB_GLOBAL << 4u8) | STT_NOTYPE)); @@ -2031,7 +2174,7 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { s = s.snext; }; - // Build relocations. + // Build relocations — split into text vs data buffers. let r: *areloc = a.relocs; for (r != nil) { let entry: [24]u8; @@ -2039,7 +2182,11 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { let rinfo: u64 = (r.asy.idx: u64 << 32u64) | (r.kind: u64 & 4294967295u64); wru64(entry.ptr, 8u64, rinfo); wru64(entry.ptr, 16u64, r.addend: u64); - bufputb(&rela, entry.ptr, 24u64); + if (r.section == 1) { + bufputb(&relad, entry.ptr, 24u64); + } else { + bufputb(&rela, entry.ptr, 24u64); + }; r = r.rnext; }; @@ -2047,12 +2194,20 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { let off: u64 = EHDR_SZ; let offtext: u64 = off; off = off + a.textlen; let offrela: u64 = off; off = off + rela.n; + let offdata: u64 = off; if (hasdata) { off = off + a.datalen; }; + let offrelad: u64 = off; if (hasdata) { if (hasdatarelocs) { + off = off + relad.n; + };}; let offsym: u64 = off; off = off + sym.n; let offstr: u64 = off; off = off + str_.n; let offshstr: u64 = off; off = off + shstr.n; for ((off & 7u64) != 0u64) { off += 1u64; }; let offshdr: u64 = off; let NSECT: u16 = 6u16; + if (hasdata) { + if (hasdatarelocs) { NSECT = 8u16; } + else { NSECT = 7u16; }; + }; // ---- Ehdr ---- let eh: [64]u8; @@ -2077,7 +2232,7 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { wru16(eh.ptr, 56u64, 0u16); // e_phnum wru16(eh.ptr, 58u64, 64u16); // e_shentsize wru16(eh.ptr, 60u64, NSECT); // e_shnum - wru16(eh.ptr, 62u64, 5u16); // e_shstrndx + wru16(eh.ptr, 62u64, SH_SHSTR); // e_shstrndx if (!wrn(fd, eh.ptr, 64u64, 64i64)) { return -1; }; if (a.textlen > 0u64) { @@ -2086,6 +2241,16 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { if (rela.n > 0u64) { if (!wrn(fd, rela.p, rela.n, rela.n: i64)) { return -1; }; }; + if (hasdata) { + if (a.datalen > 0u64) { + if (!wrn(fd, a.data, a.datalen, a.datalen: i64)) { return -1; }; + }; + if (hasdatarelocs) { + if (relad.n > 0u64) { + if (!wrn(fd, relad.p, relad.n, relad.n: i64)) { return -1; }; + }; + }; + }; if (sym.n > 0u64) { if (!wrn(fd, sym.p, sym.n, sym.n: i64)) { return -1; }; }; @@ -2098,6 +2263,10 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { // Pad to 8 before shdrs. let written: u64 = EHDR_SZ + a.textlen + rela.n + sym.n + str_.n + shstr.n; + if (hasdata) { + written += a.datalen; + if (hasdatarelocs) { written += relad.n; }; + }; for ((written & 7u64) != 0u64) { wrdrop(fd, &zero, 1u64); written += 1u64; @@ -2127,11 +2296,38 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { wru64(shbuf.ptr, 8u64, SHF_INFO_LINK); wru64(shbuf.ptr, 24u64, offrela); wru64(shbuf.ptr, 32u64, rela.n); - wru32(shbuf.ptr, 40u64, 3u32); // sh_link = symtab idx - wru32(shbuf.ptr, 44u64, 1u32); // sh_info = .text idx + wru32(shbuf.ptr, 40u64, SH_SYMTAB: u32); // sh_link + wru32(shbuf.ptr, 44u64, 1u32); // sh_info = .text idx wru64(shbuf.ptr, 48u64, 8u64); wru64(shbuf.ptr, 56u64, RELA_SZ); wrdrop(fd, shbuf.ptr, 64u64); + if (hasdata) { + // .data + sn = 0; + for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; + wru32(shbuf.ptr, 0u64, shndata); + wru32(shbuf.ptr, 4u64, SHT_PROGBITS_C); + wru64(shbuf.ptr, 8u64, SHF_ALLOC | SHF_WRITE); + wru64(shbuf.ptr, 24u64, offdata); + wru64(shbuf.ptr, 32u64, a.datalen); + wru64(shbuf.ptr, 48u64, 8u64); // sh_addralign + wrdrop(fd, shbuf.ptr, 64u64); + if (hasdatarelocs) { + // .rela.data + sn = 0; + for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; + wru32(shbuf.ptr, 0u64, shnrelad); + wru32(shbuf.ptr, 4u64, SHT_RELA_C); + wru64(shbuf.ptr, 8u64, SHF_INFO_LINK); + wru64(shbuf.ptr, 24u64, offrelad); + wru64(shbuf.ptr, 32u64, relad.n); + wru32(shbuf.ptr, 40u64, SH_SYMTAB: u32); + wru32(shbuf.ptr, 44u64, SH_DATA: u32); // applies to .data + wru64(shbuf.ptr, 48u64, 8u64); + wru64(shbuf.ptr, 56u64, RELA_SZ); + wrdrop(fd, shbuf.ptr, 64u64); + }; + }; // .symtab sn = 0; for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; @@ -2139,7 +2335,7 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { wru32(shbuf.ptr, 4u64, SHT_SYMTAB_C); wru64(shbuf.ptr, 24u64, offsym); wru64(shbuf.ptr, 32u64, sym.n); - wru32(shbuf.ptr, 40u64, 4u32); // sh_link = strtab + wru32(shbuf.ptr, 40u64, SH_STRTAB: u32); // sh_link wru32(shbuf.ptr, 44u64, 1u32); // sh_info = one local (STN_UNDEF) wru64(shbuf.ptr, 48u64, 8u64); wru64(shbuf.ptr, 56u64, SYM_SZ); diff --git a/selfhost/cmd/w6a/obj.ww b/selfhost/cmd/w6a/obj.ww index e8c7575e..e59d0ec2 100644 --- a/selfhost/cmd/w6a/obj.ww +++ b/selfhost/cmd/w6a/obj.ww @@ -48,12 +48,14 @@ def SHT_SYMTAB_C: u32 = 2u32; def SHT_STRTAB_C: u32 = 3u32; def SHT_RELA_C: u32 = 4u32; +def SHF_WRITE: u64 = 1u64; def SHF_ALLOC: u64 = 2u64; def SHF_EXECINSTR: u64 = 4u64; def SHF_INFO_LINK: u64 = 64u64; // 0x40 def STB_GLOBAL: u8 = 1u8; def STT_NOTYPE: u8 = 0u8; +def STT_OBJECT: u8 = 1u8; def STT_FUNC: u8 = 2u8; // Sizes of fixed structures. @@ -133,15 +135,56 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { let str_: buf; bufinit(&str_, a.a); let sym: buf; bufinit(&sym, a.a); let rela: buf; bufinit(&rela, a.a); + let relad: buf; bufinit(&relad, a.a); // Index 0 = empty. let zero: u8 = 0u8; bufputb(&shstr, &zero, 1u64); bufputb(&str_, &zero, 1u64); - // Section name offsets. + let hasdata: bool = a.datalen > 0u64; + let hasdatarelocs: bool = false; + let rscan: *areloc = a.relocs; + for (rscan != nil) { + if (rscan.section == 1) { hasdatarelocs = true; }; + rscan = rscan.rnext; + }; + + // Section indices (mirror cmd/w6a/obj.c): + // without data, without data-relocs: + // 1=.text 2=.rela.text 3=.symtab 4=.strtab 5=.shstrtab + // with data, no data-relocs: + // 1=.text 2=.rela.text 3=.data 4=.symtab 5=.strtab 6=.shstrtab + // with data + data-relocs: + // 1=.text 2=.rela.text 3=.data 4=.rela.data 5=.symtab + // 6=.strtab 7=.shstrtab + let SH_TEXT: u16 = 1u16; + let SH_DATA: u16 = 0u16; + let SH_RELAD: u16 = 0u16; + let SH_SYMTAB: u16 = 3u16; + if (hasdata) { + SH_DATA = 3u16; + if (hasdatarelocs) { + SH_RELAD = 4u16; + SH_SYMTAB = 5u16; + } else { + SH_SYMTAB = 4u16; + }; + }; + let SH_STRTAB: u16 = SH_SYMTAB + 1u16; + let SH_SHSTR: u16 = SH_STRTAB + 1u16; + + // Section name offsets. Append .data / .rela.data only when + // used so the .shstrtab buffer stays byte-identical for the + // no-DATAW case (test 991 byte-diff invariant). let shntext: u32 = bufputcstr(&shstr, ".text"); let shnrela: u32 = bufputcstr(&shstr, ".rela.text"); + let shndata: u32 = 0u32; + let shnrelad: u32 = 0u32; + if (hasdata) { shndata = bufputcstr(&shstr, ".data"); }; + if (hasdata) { if (hasdatarelocs) { + shnrelad = bufputcstr(&shstr, ".rela.data"); + };}; let shnsymtab: u32 = bufputcstr(&shstr, ".symtab"); let shnstrtab: u32 = bufputcstr(&shstr, ".strtab"); let shnshstrtab: u32 = bufputcstr(&shstr, ".shstrtab"); @@ -152,8 +195,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { for (zi < 24) { zsym[zi] = 0u8; zi += 1; }; bufputb(&sym, zsym.ptr, 24u64); - let SH_TEXT: u16 = 1u16; - // Build symbols. let idx: i32 = 1; let s: *asym = a.syms; @@ -164,8 +205,13 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { let stname: u32 = bufputcstr(&str_, s.name); wru32(entry.ptr, 0u64, stname); if (s.defined != 0) { - wru8(entry.ptr, 4u64, ((STB_GLOBAL << 4u8) | STT_FUNC)); - wru16(entry.ptr, 6u64, SH_TEXT); + if (s.isdata != 0) { + wru8(entry.ptr, 4u64, ((STB_GLOBAL << 4u8) | STT_OBJECT)); + wru16(entry.ptr, 6u64, SH_DATA); + } else { + wru8(entry.ptr, 4u64, ((STB_GLOBAL << 4u8) | STT_FUNC)); + wru16(entry.ptr, 6u64, SH_TEXT); + }; wru64(entry.ptr, 8u64, s.addr); } else { wru8(entry.ptr, 4u64, ((STB_GLOBAL << 4u8) | STT_NOTYPE)); @@ -177,7 +223,7 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { s = s.snext; }; - // Build relocations. + // Build relocations — split into text vs data buffers. let r: *areloc = a.relocs; for (r != nil) { let entry: [24]u8; @@ -185,7 +231,11 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { let rinfo: u64 = (r.asy.idx: u64 << 32u64) | (r.kind: u64 & 4294967295u64); wru64(entry.ptr, 8u64, rinfo); wru64(entry.ptr, 16u64, r.addend: u64); - bufputb(&rela, entry.ptr, 24u64); + if (r.section == 1) { + bufputb(&relad, entry.ptr, 24u64); + } else { + bufputb(&rela, entry.ptr, 24u64); + }; r = r.rnext; }; @@ -193,12 +243,20 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { let off: u64 = EHDR_SZ; let offtext: u64 = off; off = off + a.textlen; let offrela: u64 = off; off = off + rela.n; + let offdata: u64 = off; if (hasdata) { off = off + a.datalen; }; + let offrelad: u64 = off; if (hasdata) { if (hasdatarelocs) { + off = off + relad.n; + };}; let offsym: u64 = off; off = off + sym.n; let offstr: u64 = off; off = off + str_.n; let offshstr: u64 = off; off = off + shstr.n; for ((off & 7u64) != 0u64) { off += 1u64; }; let offshdr: u64 = off; let NSECT: u16 = 6u16; + if (hasdata) { + if (hasdatarelocs) { NSECT = 8u16; } + else { NSECT = 7u16; }; + }; // ---- Ehdr ---- let eh: [64]u8; @@ -223,7 +281,7 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { wru16(eh.ptr, 56u64, 0u16); // e_phnum wru16(eh.ptr, 58u64, 64u16); // e_shentsize wru16(eh.ptr, 60u64, NSECT); // e_shnum - wru16(eh.ptr, 62u64, 5u16); // e_shstrndx + wru16(eh.ptr, 62u64, SH_SHSTR); // e_shstrndx if (!wrn(fd, eh.ptr, 64u64, 64i64)) { return -1; }; if (a.textlen > 0u64) { @@ -232,6 +290,16 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { if (rela.n > 0u64) { if (!wrn(fd, rela.p, rela.n, rela.n: i64)) { return -1; }; }; + if (hasdata) { + if (a.datalen > 0u64) { + if (!wrn(fd, a.data, a.datalen, a.datalen: i64)) { return -1; }; + }; + if (hasdatarelocs) { + if (relad.n > 0u64) { + if (!wrn(fd, relad.p, relad.n, relad.n: i64)) { return -1; }; + }; + }; + }; if (sym.n > 0u64) { if (!wrn(fd, sym.p, sym.n, sym.n: i64)) { return -1; }; }; @@ -244,6 +312,10 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { // Pad to 8 before shdrs. let written: u64 = EHDR_SZ + a.textlen + rela.n + sym.n + str_.n + shstr.n; + if (hasdata) { + written += a.datalen; + if (hasdatarelocs) { written += relad.n; }; + }; for ((written & 7u64) != 0u64) { wrdrop(fd, &zero, 1u64); written += 1u64; @@ -273,11 +345,38 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { wru64(shbuf.ptr, 8u64, SHF_INFO_LINK); wru64(shbuf.ptr, 24u64, offrela); wru64(shbuf.ptr, 32u64, rela.n); - wru32(shbuf.ptr, 40u64, 3u32); // sh_link = symtab idx - wru32(shbuf.ptr, 44u64, 1u32); // sh_info = .text idx + wru32(shbuf.ptr, 40u64, SH_SYMTAB: u32); // sh_link + wru32(shbuf.ptr, 44u64, 1u32); // sh_info = .text idx wru64(shbuf.ptr, 48u64, 8u64); wru64(shbuf.ptr, 56u64, RELA_SZ); wrdrop(fd, shbuf.ptr, 64u64); + if (hasdata) { + // .data + sn = 0; + for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; + wru32(shbuf.ptr, 0u64, shndata); + wru32(shbuf.ptr, 4u64, SHT_PROGBITS_C); + wru64(shbuf.ptr, 8u64, SHF_ALLOC | SHF_WRITE); + wru64(shbuf.ptr, 24u64, offdata); + wru64(shbuf.ptr, 32u64, a.datalen); + wru64(shbuf.ptr, 48u64, 8u64); // sh_addralign + wrdrop(fd, shbuf.ptr, 64u64); + if (hasdatarelocs) { + // .rela.data + sn = 0; + for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; + wru32(shbuf.ptr, 0u64, shnrelad); + wru32(shbuf.ptr, 4u64, SHT_RELA_C); + wru64(shbuf.ptr, 8u64, SHF_INFO_LINK); + wru64(shbuf.ptr, 24u64, offrelad); + wru64(shbuf.ptr, 32u64, relad.n); + wru32(shbuf.ptr, 40u64, SH_SYMTAB: u32); + wru32(shbuf.ptr, 44u64, SH_DATA: u32); // applies to .data + wru64(shbuf.ptr, 48u64, 8u64); + wru64(shbuf.ptr, 56u64, RELA_SZ); + wrdrop(fd, shbuf.ptr, 64u64); + }; + }; // .symtab sn = 0; for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; @@ -285,7 +384,7 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { wru32(shbuf.ptr, 4u64, SHT_SYMTAB_C); wru64(shbuf.ptr, 24u64, offsym); wru64(shbuf.ptr, 32u64, sym.n); - wru32(shbuf.ptr, 40u64, 4u32); // sh_link = strtab + wru32(shbuf.ptr, 40u64, SH_STRTAB: u32); // sh_link wru32(shbuf.ptr, 44u64, 1u32); // sh_info = one local (STN_UNDEF) wru64(shbuf.ptr, 48u64, 8u64); wru64(shbuf.ptr, 56u64, SYM_SZ); diff --git a/selfhost/cmd/w6a/parse.ww b/selfhost/cmd/w6a/parse.ww index 1fb71a48..76b6bd78 100644 --- a/selfhost/cmd/w6a/parse.ww +++ b/selfhost/cmd/w6a/parse.ww @@ -86,6 +86,8 @@ fn opcodelookup(p: *u8, n: u64) i32 = { if (streqlit(p, n, "SYSCALL")) { return A_SYSCALL; }; if (streqlit(p, n, "TEXT")) { return A_TEXT; }; if (streqlit(p, n, "DATA")) { return A_DATA; }; + if (streqlit(p, n, "DATAW")) { return A_DATAW; }; + if (streqlit(p, n, "DATAR")) { return A_DATAR; }; return A_NOP; }; @@ -291,13 +293,25 @@ fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = { return after; }; - // IDENT — register, symbol(SB), or branch label + // IDENT — register, symbol(SB), symbol+disp(SB), or branch label if (isidstart(c0: i32)) { let istart: u64 = off; for (off < n) { if (isidcont(p[off]: i32)) { off += 1u64; continue; }; off = off; off += 0u64; // loop break let in_: u64 = off - istart; + // Optional `+disp` between the ident and `(SB)`. Used + // by DATAR to address bytes within a previously-defined + // .data slot (e.g. `DATAR s+8(SB),...`). + let symdisp: i64 = 0i64; + if (off < n) { if (p[off] == 43u8) { // '+' + off += 1u64; + let v: i64; + let used: u64; + v, used = parsenum(p + off, n - off); + symdisp = v; + off += used; + };}; // IDENT(SB) — external if (off < n) { if (p[off] == 40u8) { // '(' let rstart: u64 = off + 1u64; @@ -310,6 +324,7 @@ fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = { if (r == D_PSB) { out.atype = D_EXTERN; out.asym = dupstr(a.a, p + istart, in_); + out.offset = symdisp; } else { out.atype = D_INDIR; out.reg = r; @@ -459,8 +474,9 @@ export fn parse(a: *asm_) i32 = { a.line += 1; continue; }; - if (opc == A_DATA) { - // DATA name(SB),"escaped bytes" — find first '(' as end of name. + if (opc == A_DATA || opc == A_DATAW) { + // DATA / DATAW name(SB),"escaped bytes" — same syntax, + // different destination section (.text vs .data). let q: u64 = r0; let lparen: u64 = n; let scand: bool = true; diff --git a/selfhost/cmd/w6a/types.ww b/selfhost/cmd/w6a/types.ww index 4ff689b1..1787a755 100644 --- a/selfhost/cmd/w6a/types.ww +++ b/selfhost/cmd/w6a/types.ww @@ -121,6 +121,13 @@ def A_JNZ: i32 = 58; def A_SYSCALL: i32 = 59; +// Writable data + reloc-only data. Mirror cmd/w6c/6.out.h. +// A_DATAW: bytes land in .data (RW) instead of .text. +// A_DATAR: record an R_X86_64_64 reloc at a .data slot, patched +// to a target symbol's runtime VA at link time. +def A_DATAW: i32 = 60; +def A_DATAR: i32 = 61; + // ---- structs (mirror cmd/w6a/a.h) -------------------------------------- type aoperand = struct { @@ -149,14 +156,16 @@ type asym = struct { name: str, defined: i32, istext: i32, + isdata: i32, // mutually exclusive with istext; DATAW symbols isglobal: i32, - addr: u64, + addr: u64, // offset within its section (.text or .data) idx: i32, snext: *asym, }; type areloc = struct { off: u64, + section: i32, // 0 = .text, 1 = .data kind: i32, asy: *asym, addend: i64, @@ -184,6 +193,14 @@ type asm_ = struct { textcap: u64, textlen: u64, + // Writable .data. Empty unless any DATAW directive was seen; + // obj.ww emits the extra section conditionally so .o output + // stays byte-identical for inputs that don't use DATAW (test + // 991 byte-diff invariant). + data: *u8, + datacap: u64, + datalen: u64, + syms: *asym, relocs: *areloc, fixups: *afixup, diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 3f788510..6c49e1ea 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -8576,6 +8576,7 @@ export fn cgfile(c: *cgen, file: *node) void = { }; d = d.next; }; + letpreintern(c, file); emitdatasection(c); emitdefconstants(c, file); emitletdataw(c, file); @@ -9345,13 +9346,45 @@ fn emitdatawbyte(b: u8) void = { os.write(1, bb.ptr, 1u64); }; +// letpreintern — intern strlits referenced from top-level str-let +// initialisers BEFORE emitdatasection runs. Mirrors cmd/w6c/cgen.c +// let_pre_intern: emitletdataw later looks up the same label, and +// emitdatasection emits the DATA row in the same .s file. Running +// emitletdataw after emitdatasection would flip the (DATA strlits, +// DATAW lets) section order and break byte-identity. +export fn letpreintern(c: *cgen, file: *node) void = { + if (file == nil) { return; }; + let d: *node = file.list; + for (d != nil) { + if (d.kind == nkind.N_LET) { + let sz: i32 = letemitsize(c, d); + if (sz == 16) { + let r: *node = d.rhs; + for (r != nil) { + if (r.kind != nkind.N_CAST) { break; }; + r = r.lhs; + }; + if (r != nil) { + if (r.kind == nkind.N_STRLIT) { + if (r.str.len > 0) { + internstrlit(c, r.str); + }; + }; + }; + }; + }; + d = d.next; + }; +}; + // emitletdataw — DATAW directive per top-level `let` global. // 8B scalar with int/rune/bool/nil literal init (or no init). -// 16B str with no init (or `nil` / `""`) — zero header; the -// program must assign a real strlit at runtime before -// using .ptr / .len. -// Non-literal scalar inits and non-empty strlit inits are skipped -// so the link surfaces an undefined-symbol error. +// 16B str — no init / `nil` / `""` → 16 zero bytes; or non-empty +// strlit init → 8 zero placeholder + 8 LE len bytes plus a +// DATAR slot+0,strlit reloc that the linker patches at load. +// sz struct — zero only. +// Non-literal scalar inits and unsupported shapes are skipped so the +// link surfaces an undefined-symbol error if the binding is used. fn emitletdataw(c: *cgen, file: *node) void = { let d: *node = file.list; for (d != nil) { @@ -9394,31 +9427,67 @@ fn emitletdataw(c: *cgen, file: *node) void = { }; }; if (sz == 16 && !issg) { - let ok: bool = true; - if (d.rhs != nil) { - let r: *node = d.rhs; - for (r != nil) { - if (r.kind != nkind.N_CAST) { break; }; - r = r.lhs; - }; - ok = false; - if (r != nil) { - if (r.kind == nkind.N_NIL) { ok = true; }; - if (r.kind == nkind.N_STRLIT) { - if (r.str.len == 0) { ok = true; }; - }; + let r: *node = d.rhs; + for (r != nil) { + if (r.kind != nkind.N_CAST) { break; }; + r = r.lhs; + }; + // str-literal init (non-empty): emit + // the 16B payload as 8 placeholder zero + // bytes + 8 LE bytes of length, then a + // DATAR reloc to patch the ptr half with + // the strlit's runtime VA. + let strlitinit: bool = false; + if (r != nil) { + if (r.kind == nkind.N_STRLIT) { + if (r.str.len > 0) { strlitinit = true; }; }; }; - if (ok) { + if (strlitinit) { + let lab: str = internstrlit(c, r.str); + let v: u64 = r.str.len: u64; emitline("DATAW "); emitsymname(c, nm); emitline("(SB),\""); let i: i32 = 0; - for (i < 16) { - emitdatawbyte(0u8); + for (i < 8) { emitdatawbyte(0u8); i += 1; }; + i = 0; + let nv: u64 = v; + for (i < 8) { + emitdatawbyte((nv & 255u64): u8); + nv = nv >> 8u64; i += 1; }; emitline("\"\n"); + emitline("DATAR "); + emitsymname(c, nm); + emitline("+0(SB),"); + os.write(1, lab.ptr, lab.len: u64); + emitline("(SB)\n"); + } else { + // zero-init: accept no rhs, nil, + // or empty strlit. + let ok: bool = true; + if (d.rhs != nil) { + ok = false; + if (r != nil) { + if (r.kind == nkind.N_NIL) { ok = true; }; + if (r.kind == nkind.N_STRLIT) { + if (r.str.len == 0) { ok = true; }; + }; + }; + }; + if (ok) { + emitline("DATAW "); + emitsymname(c, nm); + emitline("(SB),\""); + let i: i32 = 0; + for (i < 16) { + emitdatawbyte(0u8); + i += 1; + }; + emitline("\"\n"); + }; }; }; if (sz == 24 && !issg) { diff --git a/selfhost/cmd/w6l/dynout.ww b/selfhost/cmd/w6l/dynout.ww index b0f6c311..5711ba23 100644 --- a/selfhost/cmd/w6l/dynout.ww +++ b/selfhost/cmd/w6l/dynout.ww @@ -163,6 +163,14 @@ fn streqd(a: str, b: str) bool = { // ---- main entry -------------------------------------------------------- export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { + // Writable globals on the dynamic-link path need their own R+W + // segment that the existing gotplt + .dynamic layout doesn't + // account for. Refuse rather than silently miscompile; folding + // .data into the existing R+W block is a follow-up. + if (l.datalen > 0u64) { + os.write(2, "w6l: top-level mutable globals (.data) not yet supported with -l/-L\n".ptr, 67u64); + return 1; + }; let a: *arena = l.a; let n: i32 = l.dynn; let nu: u64 = n: u64; diff --git a/selfhost/cmd/w6l/main.combined.ww b/selfhost/cmd/w6l/main.combined.ww index c921ae55..4f6a9d68 100644 --- a/selfhost/cmd/w6l/main.combined.ww +++ b/selfhost/cmd/w6l/main.combined.ww @@ -345,8 +345,10 @@ use mem; type lsym = struct { name: str, - val: u64, // offset within combined .text once linked + 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 @@ -361,7 +363,8 @@ type lsym = struct { }; type lrel = struct { - off: u64, // offset within combined .text + off: u64, // offset within the relocation's section + section: i32, // 0 = .text, 1 = .data kind: i32, // R_X86_64_* sym: *lsym, addend: i64, @@ -374,6 +377,8 @@ type lobj = struct { 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, }; @@ -402,6 +407,11 @@ type lnk = struct { 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 }; @@ -564,6 +574,28 @@ fn emittext(l: *lnk, src: *u8, n: u64) void = { l.textlen += n; }; +fn emitdata(l: *lnk, src: *u8, n: u64) void = { + if (l.datalen + n > l.datacap) { + let nc: u64 = l.datacap; + if (nc == 0u64) { nc = 256u64; }; + for (nc < l.datalen + n) { nc = nc * 2u64; }; + let nb: *u8 = os.alloc(nc): *u8; + let i: u64 = 0u64; + for (i < l.datalen) { + nb[i] = l.data[i]; + i += 1u64; + }; + l.data = nb; + l.datacap = nc; + }; + let i: u64 = 0u64; + for (i < n) { + l.data[l.datalen + i] = src[i]; + i += 1u64; + }; + l.datalen += n; +}; + // ---- C-string helpers -------------------------------------------------- fn cstrlen(p: *u8) u64 = { @@ -654,6 +686,7 @@ fn elfglobals(a: *arena, buf: *u8, len: u64) *defent = { let shstr: *u8 = buf + shstrshoff; let idxtext: i32 = -1; + let idxdata: i32 = -1; let idxsymtab: i32 = -1; let i: u32 = 0u32; for (i < shnum) { @@ -663,6 +696,7 @@ fn elfglobals(a: *arena, buf: *u8, len: u64) *defent = { let nm: *u8 = shstr + (shname: u64); if (shtype == SHT_PROGBITS: u32) { if (cstreq(nm, ".text")) { idxtext = i: i32; }; + if (cstreq(nm, ".data")) { idxdata = i: i32; }; }; if (shtype == SHT_SYMTAB: u32) { idxsymtab = i: i32; }; i += 1u32; @@ -688,10 +722,17 @@ fn elfglobals(a: *arena, buf: *u8, len: u64) *defent = { let stinfo: u8 = buf[symp + SYM_INFO]; let stshndx: u16 = rdu16(buf, symp + SYM_SHNDX); let bind: u32 = (stinfo: u32) >> 4u32; - // STB_GLOBAL = 1; defined in .text. + // STB_GLOBAL = 1; defined in .text or .data. Both are + // included so an archive member that owns a data global + // gets pulled in when something references it. if (bind == 1u32) { if (stshndx != 0u16) { - if ((stshndx: i32) == idxtext) { + let intext: bool = (stshndx: i32) == idxtext; + let indt: bool = false; + if (idxdata >= 0) { + indt = (stshndx: i32) == idxdata; + }; + if (intext || indt) { let nmp: *u8 = strtab + (stname: u64); if (nmp[0u64] != 0u8) { let nm: str = cstrtostr(a, nmp); @@ -813,10 +854,12 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { let shstrshoff: u64 = rdu64(buf, shoff + (shstrndx: u64) * SHDR_SIZE + SHDR_OFFSET); let shstr: *u8 = buf + shstrshoff; - // find .text, .symtab, .rela.text + // find .text, .data, .symtab, .rela.text, .rela.data let idxtext: i32 = -1; + let idxdata: i32 = -1; let idxsymtab: i32 = -1; let idxrela: i32 = -1; + let idxrelad: i32 = -1; let i: u32 = 0u32; for (i < shnum) { let secoff: u64 = shoff + (i: u64) * SHDR_SIZE; @@ -825,10 +868,12 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { let nm: *u8 = shstr + (shname: u64); if (shtype == SHT_PROGBITS: u32) { if (cstreq(nm, ".text")) { idxtext = i: i32; }; + if (cstreq(nm, ".data")) { idxdata = i: i32; }; }; if (shtype == SHT_SYMTAB: u32) { idxsymtab = i: i32; }; if (shtype == SHT_RELA: u32) { if (cstreq(nm, ".rela.text")) { idxrela = i: i32; }; + if (cstreq(nm, ".rela.data")) { idxrelad = i: i32; }; }; i += 1u32; }; @@ -855,18 +900,34 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { let stroff: u64 = rdu64(buf, strsh + SHDR_OFFSET); let strtab: *u8 = buf + stroff; + let datasize: u64 = 0u64; + let dataoff: u64 = 0u64; + if (idxdata >= 0) { + let datash: u64 = shoff + (idxdata: u64) * SHDR_SIZE; + dataoff = rdu64(buf, datash + SHDR_OFFSET); + datasize = rdu64(buf, datash + SHDR_SIZE_F); + }; + // Track this object. - let ob: *lobj = amalloc(l.a, 64u64): *lobj; + let ob: *lobj = amalloc(l.a, 96u64): *lobj; ob.path = cstrtostr(l.a, path); ob.buf = buf; ob.len = len; ob.textoff = l.textlen; ob.textsize = textsize; + ob.dataoff = l.datalen; + ob.datasize = datasize; ob.onext = l.objs; l.objs = ob; // Append .text bytes to the combined image. emittext(l, buf + textoff, textsize); + // Append .data bytes (if present) to the combined .data buffer. + if (idxdata >= 0) { + if (datasize > 0u64) { + emitdata(l, buf + dataoff, datasize); + }; + }; // Walk symbols. We don't keep a per-object map[] of *lsym. Instead // the reloc loop re-walks symtab and re-interns by name. Simpler @@ -882,7 +943,13 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { let nm: str = cstrtostr(l.a, nmp); let gs: *lsym = intern(l, nm); if (stshndx != 0u16) { - if ((stshndx: i32) == idxtext) { + let intext: bool = (stshndx: i32) == idxtext; + let indt: bool = false; + if (idxdata >= 0) { + indt = (stshndx: i32) == idxdata; + }; + if (intext) { if (indt) { indt = false; }; }; + if (intext) { if (gs.defined != 0) { os.write(2, "w6l: duplicate symbol\n".ptr, 21u64); l.errs += 1; @@ -893,6 +960,18 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { gs.val = ob.textoff + stvalue; }; }; + if (indt) { + if (gs.defined != 0) { + os.write(2, "w6l: duplicate symbol\n".ptr, 21u64); + l.errs += 1; + } else { + gs.defined = 1; + gs.indata = 1; + gs.owner = ob; + gs.idxinowner = si: i32; + gs.val = ob.dataoff + stvalue; + }; + }; }; }; si += 1u64; @@ -912,8 +991,9 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { let raddend: u64 = rdu64(buf, rp + RELA_ADDEND); let rsymidx: u32 = (rinfo >> 32u64): u32; let rkind: i32 = ((rinfo & 4294967295u64): u32): i32; - let nr: *lrel = amalloc(l.a, 48u64): *lrel; + let nr: *lrel = amalloc(l.a, 64u64): *lrel; nr.off = ob.textoff + roff; + nr.section = 0; nr.kind = rkind; nr.addend = raddend: i64; // Look up the referenced sym by name (re-walk symtab). @@ -932,6 +1012,41 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { }; }; + // Data-reloc collection. Offsets land in .data, shifted by + // this object's data_off so they index the combined buffer. + if (idxrelad >= 0) { + let relash: u64 = shoff + (idxrelad: u64) * SHDR_SIZE; + let relaoff: u64 = rdu64(buf, relash + SHDR_OFFSET); + let relasize: u64 = rdu64(buf, relash + SHDR_SIZE_F); + let nrel: u64 = relasize / RELA_SIZE; + let ri: u64 = 0u64; + for (ri < nrel) { + let rp: u64 = relaoff + ri * RELA_SIZE; + let roff: u64 = rdu64(buf, rp + RELA_OFFSET); + let rinfo: u64 = rdu64(buf, rp + RELA_INFO); + let raddend: u64 = rdu64(buf, rp + RELA_ADDEND); + let rsymidx: u32 = (rinfo >> 32u64): u32; + let rkind: i32 = ((rinfo & 4294967295u64): u32): i32; + let nr: *lrel = amalloc(l.a, 64u64): *lrel; + nr.off = ob.dataoff + roff; + nr.section = 1; + nr.kind = rkind; + nr.addend = raddend: i64; + if ((rsymidx: u64) < nsyms) { + let sp: u64 = symoff + (rsymidx: u64) * SYM_SIZE; + let sname: u32 = rdu32(buf, sp + SYM_NAME); + let snm: *u8 = strtab + (sname: u64); + if (snm[0u64] != 0u8) { + let nm: str = cstrtostr(l.a, snm); + nr.sym = intern(l, nm); + }; + }; + nr.rnext = l.rels; + l.rels = nr; + ri += 1u64; + }; + }; + return 0; }; @@ -1361,6 +1476,7 @@ use os; use sym; use dyn; +def R_X86_64_64: i32 = 1; def R_X86_64_PC32: i32 = 2; def R_X86_64_PLT32: i32 = 4; @@ -1427,7 +1543,15 @@ fn patchu32(p: *u8, v: u32) void = { p[3] = ((v >> 24u32) & 255u32): u8; }; -export fn relocate(l: *lnk, base: u64) i32 = { +fn patchu64(p: *u8, v: u64) void = { + let i: i32 = 0; + for (i < 8) { + p[i] = ((v >> (i: u64 * 8u64)) & 255u64): u8; + i += 1; + }; +}; + +export fn relocate(l: *lnk, textva: u64, datava: u64) i32 = { let r: *lrel = l.rels; for (r != nil) { if (r.sym != nil) { @@ -1438,21 +1562,30 @@ export fn relocate(l: *lnk, base: u64) i32 = { continue; }; if (r.sym.defined != 0) { + let symva: u64 = textva + r.sym.val; + if (r.sym.indata != 0) { symva = datava + r.sym.val; }; let k: i32 = r.kind; if (k == R_X86_64_PC32) { - let site: u64 = base + r.off; - let target: i64 = (base + r.sym.val): i64; - let rel: i64 = (target - site: i64) + r.addend; + let site: u64 = textva + r.off; + let rel: i64 = (symva: i64 - site: i64) + r.addend; patchu32(l.text + r.off, rel: u32); } else { if (k == R_X86_64_PLT32) { - let site: u64 = base + r.off; - let target: i64 = (base + r.sym.val): i64; - let rel: i64 = (target - site: i64) + r.addend; + let site: u64 = textva + r.off; + let rel: i64 = (symva: i64 - site: i64) + r.addend; patchu32(l.text + r.off, rel: u32); + } else { if (k == R_X86_64_64) { + // Absolute 64-bit. Currently used only + // for DATAR slots in .data. + let v: u64 = (symva: i64 + r.addend): u64; + if (r.section == 1) { + patchu64(l.data + r.off, v); + } else { + patchu64(l.text + r.off, v); + }; } else { os.write(2, "w6l: unsupported reloc kind\n".ptr, 27u64); l.errs += 1; - };}; + };};}; }; }; r = r.rnext; @@ -1626,6 +1759,14 @@ fn streqd(a: str, b: str) bool = { // ---- main entry -------------------------------------------------------- export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { + // Writable globals on the dynamic-link path need their own R+W + // segment that the existing gotplt + .dynamic layout doesn't + // account for. Refuse rather than silently miscompile; folding + // .data into the existing R+W block is a follow-up. + if (l.datalen > 0u64) { + os.write(2, "w6l: top-level mutable globals (.data) not yet supported with -l/-L\n".ptr, 67u64); + return 1; + }; let a: *arena = l.a; let n: i32 = l.dynn; let nu: u64 = n: u64; @@ -2212,9 +2353,11 @@ def ELFCLASS64: u8 = 2u8; def ELFDATA2LSB: u8 = 1u8; def PT_LOAD: u32 = 1u32; def PF_X: u32 = 1u32; +def PF_W: u32 = 2u32; def PF_R: u32 = 4u32; def TEXT_OFF: u64 = 4096u64; // 0x1000 +def PAGE_SZ: u64 = 4096u64; // ---- little-endian byte writers ---------------------------------------- @@ -2246,7 +2389,14 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { }; }; - let filesz: u64 = TEXT_OFF + l.textlen; + let hasdata: bool = l.datalen > 0u64; + let rxend: u64 = TEXT_OFF + l.textlen; + // .data lands at the next page boundary so the loader can give + // it fresh R+W permissions without overlapping the R+X mapping. + let dataoff: u64 = 0u64; + if (hasdata) { + dataoff = (rxend + PAGE_SZ - 1u64) & ~(PAGE_SZ - 1u64); + }; // One contiguous header buffer covering [0..0x1000), then .text. let hdr: *u8 = os.alloc(TEXT_OFF): *u8; // zero-initialised by mmap @@ -2268,21 +2418,34 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { wru32(hdr, 48u64, 0u32); // e_flags wru16(hdr, 52u64, 64u16); // e_ehsize wru16(hdr, 54u64, 56u16); // e_phentsize - wru16(hdr, 56u64, 1u16); // e_phnum + if (hasdata) { wru16(hdr, 56u64, 2u16); } + else { wru16(hdr, 56u64, 1u16); }; wru16(hdr, 58u64, 0u16); // e_shentsize wru16(hdr, 60u64, 0u16); // e_shnum wru16(hdr, 62u64, 0u16); // e_shstrndx - // --- Phdr (56 bytes) at offset 64 --- - wru32(hdr, 64u64, PT_LOAD); // p_type - wru32(hdr, 68u64, PF_R | PF_X); // p_flags + // --- Phdr #1 (R+X) at offset 64 --- + wru32(hdr, 64u64, PT_LOAD); + wru32(hdr, 68u64, PF_R | PF_X); wru64(hdr, 72u64, 0u64); // p_offset wru64(hdr, 80u64, base); // p_vaddr wru64(hdr, 88u64, base); // p_paddr - wru64(hdr, 96u64, filesz); // p_filesz - wru64(hdr, 104u64, filesz); // p_memsz + wru64(hdr, 96u64, rxend); // p_filesz + wru64(hdr, 104u64, rxend); // p_memsz wru64(hdr, 112u64, TEXT_OFF); // p_align + if (hasdata) { + // --- Phdr #2 (R+W) at offset 64+56=120 --- + wru32(hdr, 120u64, PT_LOAD); + wru32(hdr, 124u64, PF_R | PF_W); + wru64(hdr, 128u64, dataoff); // p_offset + wru64(hdr, 136u64, base + dataoff); // p_vaddr + wru64(hdr, 144u64, base + dataoff); // p_paddr + wru64(hdr, 152u64, l.datalen); // p_filesz + wru64(hdr, 160u64, l.datalen); // p_memsz + wru64(hdr, 168u64, PAGE_SZ); // p_align + }; + // Write [0..0x1000) then .text. let r1: (i64 | os.oserror) = os.writeall(fd, hdr, TEXT_OFF); let n1: i64 = 0i64; @@ -2300,6 +2463,26 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { }; if (n2 != l.textlen: i64) { return -1; }; }; + if (hasdata) { + // Pad to the page-aligned data offset, then write .data. + let here: u64 = TEXT_OFF + l.textlen; + let zero: u8 = 0u8; + for (here < dataoff) { + let r3: (i64 | os.oserror) = os.writeall(fd, &zero, 1u64); + match (r3) { + case let v: i64 => { }; + case let e: os.oserror => return -1; + }; + here += 1u64; + }; + let r4: (i64 | os.oserror) = os.writeall(fd, l.data, l.datalen); + let n4: i64 = 0i64; + match (r4) { + case let v: i64 => n4 = v; + case let e: os.oserror => return -1; + }; + if (n4 != l.datalen: i64) { return -1; }; + }; return 0; }; @@ -2324,7 +2507,7 @@ def BASE: u64 = 4194304u64; // 0x400000 def CODE_VA_OFF: u64 = 4096u64; // .text starts at base + 0x1000 fn mklnk(a: *arena) *lnk = { - let l: *lnk = amalloc(a, 112u64): *lnk; + let l: *lnk = amalloc(a, 144u64): *lnk; l.a = a; return l; }; @@ -2604,7 +2787,18 @@ export fn main(argc: i32, argv: **u8) i32 = { }; if (resolve(l) != 0) { return 1; }; - if (relocate(l, BASE + CODE_VA_OFF) != 0) { return 1; }; + // Static layout for relocation purposes. .data lands at the + // next page after .text; the dyn path overrides if its layout + // is different (text→text PC32 cancels the absolute VA so + // only the data delta matters here). + let textva: u64 = BASE + CODE_VA_OFF; + let datava: u64 = 0u64; + if (l.datalen > 0u64) { + let page: u64 = 4096u64; + let dataoff: u64 = (CODE_VA_OFF + l.textlen + page - 1u64) & ~(page - 1u64); + datava = BASE + dataoff; + }; + if (relocate(l, textva, datava) != 0) { return 1; }; let entrysym: *lsym = lookup(l, "_start"); if (entrysym == nil) { entrysym = lookup(l, "main"); } diff --git a/selfhost/cmd/w6l/main.ww b/selfhost/cmd/w6l/main.ww index db103b05..79b3242a 100644 --- a/selfhost/cmd/w6l/main.ww +++ b/selfhost/cmd/w6l/main.ww @@ -18,7 +18,7 @@ def BASE: u64 = 4194304u64; // 0x400000 def CODE_VA_OFF: u64 = 4096u64; // .text starts at base + 0x1000 fn mklnk(a: *arena) *lnk = { - let l: *lnk = amalloc(a, 112u64): *lnk; + let l: *lnk = amalloc(a, 144u64): *lnk; l.a = a; return l; }; @@ -298,7 +298,18 @@ export fn main(argc: i32, argv: **u8) i32 = { }; if (resolve(l) != 0) { return 1; }; - if (relocate(l, BASE + CODE_VA_OFF) != 0) { return 1; }; + // Static layout for relocation purposes. .data lands at the + // next page after .text; the dyn path overrides if its layout + // is different (text→text PC32 cancels the absolute VA so + // only the data delta matters here). + let textva: u64 = BASE + CODE_VA_OFF; + let datava: u64 = 0u64; + if (l.datalen > 0u64) { + let page: u64 = 4096u64; + let dataoff: u64 = (CODE_VA_OFF + l.textlen + page - 1u64) & ~(page - 1u64); + datava = BASE + dataoff; + }; + if (relocate(l, textva, datava) != 0) { return 1; }; let entrysym: *lsym = lookup(l, "_start"); if (entrysym == nil) { entrysym = lookup(l, "main"); } diff --git a/selfhost/cmd/w6l/obj.ww b/selfhost/cmd/w6l/obj.ww index f4a13a81..cfabdef4 100644 --- a/selfhost/cmd/w6l/obj.ww +++ b/selfhost/cmd/w6l/obj.ww @@ -123,6 +123,28 @@ fn emittext(l: *lnk, src: *u8, n: u64) void = { l.textlen += n; }; +fn emitdata(l: *lnk, src: *u8, n: u64) void = { + if (l.datalen + n > l.datacap) { + let nc: u64 = l.datacap; + if (nc == 0u64) { nc = 256u64; }; + for (nc < l.datalen + n) { nc = nc * 2u64; }; + let nb: *u8 = os.alloc(nc): *u8; + let i: u64 = 0u64; + for (i < l.datalen) { + nb[i] = l.data[i]; + i += 1u64; + }; + l.data = nb; + l.datacap = nc; + }; + let i: u64 = 0u64; + for (i < n) { + l.data[l.datalen + i] = src[i]; + i += 1u64; + }; + l.datalen += n; +}; + // ---- C-string helpers -------------------------------------------------- fn cstrlen(p: *u8) u64 = { @@ -213,6 +235,7 @@ fn elfglobals(a: *arena, buf: *u8, len: u64) *defent = { let shstr: *u8 = buf + shstrshoff; let idxtext: i32 = -1; + let idxdata: i32 = -1; let idxsymtab: i32 = -1; let i: u32 = 0u32; for (i < shnum) { @@ -222,6 +245,7 @@ fn elfglobals(a: *arena, buf: *u8, len: u64) *defent = { let nm: *u8 = shstr + (shname: u64); if (shtype == SHT_PROGBITS: u32) { if (cstreq(nm, ".text")) { idxtext = i: i32; }; + if (cstreq(nm, ".data")) { idxdata = i: i32; }; }; if (shtype == SHT_SYMTAB: u32) { idxsymtab = i: i32; }; i += 1u32; @@ -247,10 +271,17 @@ fn elfglobals(a: *arena, buf: *u8, len: u64) *defent = { let stinfo: u8 = buf[symp + SYM_INFO]; let stshndx: u16 = rdu16(buf, symp + SYM_SHNDX); let bind: u32 = (stinfo: u32) >> 4u32; - // STB_GLOBAL = 1; defined in .text. + // STB_GLOBAL = 1; defined in .text or .data. Both are + // included so an archive member that owns a data global + // gets pulled in when something references it. if (bind == 1u32) { if (stshndx != 0u16) { - if ((stshndx: i32) == idxtext) { + let intext: bool = (stshndx: i32) == idxtext; + let indt: bool = false; + if (idxdata >= 0) { + indt = (stshndx: i32) == idxdata; + }; + if (intext || indt) { let nmp: *u8 = strtab + (stname: u64); if (nmp[0u64] != 0u8) { let nm: str = cstrtostr(a, nmp); @@ -372,10 +403,12 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { let shstrshoff: u64 = rdu64(buf, shoff + (shstrndx: u64) * SHDR_SIZE + SHDR_OFFSET); let shstr: *u8 = buf + shstrshoff; - // find .text, .symtab, .rela.text + // find .text, .data, .symtab, .rela.text, .rela.data let idxtext: i32 = -1; + let idxdata: i32 = -1; let idxsymtab: i32 = -1; let idxrela: i32 = -1; + let idxrelad: i32 = -1; let i: u32 = 0u32; for (i < shnum) { let secoff: u64 = shoff + (i: u64) * SHDR_SIZE; @@ -384,10 +417,12 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { let nm: *u8 = shstr + (shname: u64); if (shtype == SHT_PROGBITS: u32) { if (cstreq(nm, ".text")) { idxtext = i: i32; }; + if (cstreq(nm, ".data")) { idxdata = i: i32; }; }; if (shtype == SHT_SYMTAB: u32) { idxsymtab = i: i32; }; if (shtype == SHT_RELA: u32) { if (cstreq(nm, ".rela.text")) { idxrela = i: i32; }; + if (cstreq(nm, ".rela.data")) { idxrelad = i: i32; }; }; i += 1u32; }; @@ -414,18 +449,34 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { let stroff: u64 = rdu64(buf, strsh + SHDR_OFFSET); let strtab: *u8 = buf + stroff; + let datasize: u64 = 0u64; + let dataoff: u64 = 0u64; + if (idxdata >= 0) { + let datash: u64 = shoff + (idxdata: u64) * SHDR_SIZE; + dataoff = rdu64(buf, datash + SHDR_OFFSET); + datasize = rdu64(buf, datash + SHDR_SIZE_F); + }; + // Track this object. - let ob: *lobj = amalloc(l.a, 64u64): *lobj; + let ob: *lobj = amalloc(l.a, 96u64): *lobj; ob.path = cstrtostr(l.a, path); ob.buf = buf; ob.len = len; ob.textoff = l.textlen; ob.textsize = textsize; + ob.dataoff = l.datalen; + ob.datasize = datasize; ob.onext = l.objs; l.objs = ob; // Append .text bytes to the combined image. emittext(l, buf + textoff, textsize); + // Append .data bytes (if present) to the combined .data buffer. + if (idxdata >= 0) { + if (datasize > 0u64) { + emitdata(l, buf + dataoff, datasize); + }; + }; // Walk symbols. We don't keep a per-object map[] of *lsym. Instead // the reloc loop re-walks symtab and re-interns by name. Simpler @@ -441,7 +492,13 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { let nm: str = cstrtostr(l.a, nmp); let gs: *lsym = intern(l, nm); if (stshndx != 0u16) { - if ((stshndx: i32) == idxtext) { + let intext: bool = (stshndx: i32) == idxtext; + let indt: bool = false; + if (idxdata >= 0) { + indt = (stshndx: i32) == idxdata; + }; + if (intext) { if (indt) { indt = false; }; }; + if (intext) { if (gs.defined != 0) { os.write(2, "w6l: duplicate symbol\n".ptr, 21u64); l.errs += 1; @@ -452,6 +509,18 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { gs.val = ob.textoff + stvalue; }; }; + if (indt) { + if (gs.defined != 0) { + os.write(2, "w6l: duplicate symbol\n".ptr, 21u64); + l.errs += 1; + } else { + gs.defined = 1; + gs.indata = 1; + gs.owner = ob; + gs.idxinowner = si: i32; + gs.val = ob.dataoff + stvalue; + }; + }; }; }; si += 1u64; @@ -471,8 +540,9 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { let raddend: u64 = rdu64(buf, rp + RELA_ADDEND); let rsymidx: u32 = (rinfo >> 32u64): u32; let rkind: i32 = ((rinfo & 4294967295u64): u32): i32; - let nr: *lrel = amalloc(l.a, 48u64): *lrel; + let nr: *lrel = amalloc(l.a, 64u64): *lrel; nr.off = ob.textoff + roff; + nr.section = 0; nr.kind = rkind; nr.addend = raddend: i64; // Look up the referenced sym by name (re-walk symtab). @@ -491,5 +561,40 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { }; }; + // Data-reloc collection. Offsets land in .data, shifted by + // this object's data_off so they index the combined buffer. + if (idxrelad >= 0) { + let relash: u64 = shoff + (idxrelad: u64) * SHDR_SIZE; + let relaoff: u64 = rdu64(buf, relash + SHDR_OFFSET); + let relasize: u64 = rdu64(buf, relash + SHDR_SIZE_F); + let nrel: u64 = relasize / RELA_SIZE; + let ri: u64 = 0u64; + for (ri < nrel) { + let rp: u64 = relaoff + ri * RELA_SIZE; + let roff: u64 = rdu64(buf, rp + RELA_OFFSET); + let rinfo: u64 = rdu64(buf, rp + RELA_INFO); + let raddend: u64 = rdu64(buf, rp + RELA_ADDEND); + let rsymidx: u32 = (rinfo >> 32u64): u32; + let rkind: i32 = ((rinfo & 4294967295u64): u32): i32; + let nr: *lrel = amalloc(l.a, 64u64): *lrel; + nr.off = ob.dataoff + roff; + nr.section = 1; + nr.kind = rkind; + nr.addend = raddend: i64; + if ((rsymidx: u64) < nsyms) { + let sp: u64 = symoff + (rsymidx: u64) * SYM_SIZE; + let sname: u32 = rdu32(buf, sp + SYM_NAME); + let snm: *u8 = strtab + (sname: u64); + if (snm[0u64] != 0u8) { + let nm: str = cstrtostr(l.a, snm); + nr.sym = intern(l, nm); + }; + }; + nr.rnext = l.rels; + l.rels = nr; + ri += 1u64; + }; + }; + return 0; }; diff --git a/selfhost/cmd/w6l/out.ww b/selfhost/cmd/w6l/out.ww index 5abef31f..88e3d55c 100644 --- a/selfhost/cmd/w6l/out.ww +++ b/selfhost/cmd/w6l/out.ww @@ -18,9 +18,11 @@ def ELFCLASS64: u8 = 2u8; def ELFDATA2LSB: u8 = 1u8; def PT_LOAD: u32 = 1u32; def PF_X: u32 = 1u32; +def PF_W: u32 = 2u32; def PF_R: u32 = 4u32; def TEXT_OFF: u64 = 4096u64; // 0x1000 +def PAGE_SZ: u64 = 4096u64; // ---- little-endian byte writers ---------------------------------------- @@ -52,7 +54,14 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { }; }; - let filesz: u64 = TEXT_OFF + l.textlen; + let hasdata: bool = l.datalen > 0u64; + let rxend: u64 = TEXT_OFF + l.textlen; + // .data lands at the next page boundary so the loader can give + // it fresh R+W permissions without overlapping the R+X mapping. + let dataoff: u64 = 0u64; + if (hasdata) { + dataoff = (rxend + PAGE_SZ - 1u64) & ~(PAGE_SZ - 1u64); + }; // One contiguous header buffer covering [0..0x1000), then .text. let hdr: *u8 = os.alloc(TEXT_OFF): *u8; // zero-initialised by mmap @@ -74,21 +83,34 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { wru32(hdr, 48u64, 0u32); // e_flags wru16(hdr, 52u64, 64u16); // e_ehsize wru16(hdr, 54u64, 56u16); // e_phentsize - wru16(hdr, 56u64, 1u16); // e_phnum + if (hasdata) { wru16(hdr, 56u64, 2u16); } + else { wru16(hdr, 56u64, 1u16); }; wru16(hdr, 58u64, 0u16); // e_shentsize wru16(hdr, 60u64, 0u16); // e_shnum wru16(hdr, 62u64, 0u16); // e_shstrndx - // --- Phdr (56 bytes) at offset 64 --- - wru32(hdr, 64u64, PT_LOAD); // p_type - wru32(hdr, 68u64, PF_R | PF_X); // p_flags + // --- Phdr #1 (R+X) at offset 64 --- + wru32(hdr, 64u64, PT_LOAD); + wru32(hdr, 68u64, PF_R | PF_X); wru64(hdr, 72u64, 0u64); // p_offset wru64(hdr, 80u64, base); // p_vaddr wru64(hdr, 88u64, base); // p_paddr - wru64(hdr, 96u64, filesz); // p_filesz - wru64(hdr, 104u64, filesz); // p_memsz + wru64(hdr, 96u64, rxend); // p_filesz + wru64(hdr, 104u64, rxend); // p_memsz wru64(hdr, 112u64, TEXT_OFF); // p_align + if (hasdata) { + // --- Phdr #2 (R+W) at offset 64+56=120 --- + wru32(hdr, 120u64, PT_LOAD); + wru32(hdr, 124u64, PF_R | PF_W); + wru64(hdr, 128u64, dataoff); // p_offset + wru64(hdr, 136u64, base + dataoff); // p_vaddr + wru64(hdr, 144u64, base + dataoff); // p_paddr + wru64(hdr, 152u64, l.datalen); // p_filesz + wru64(hdr, 160u64, l.datalen); // p_memsz + wru64(hdr, 168u64, PAGE_SZ); // p_align + }; + // Write [0..0x1000) then .text. let r1: (i64 | os.oserror) = os.writeall(fd, hdr, TEXT_OFF); let n1: i64 = 0i64; @@ -106,5 +128,25 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { }; if (n2 != l.textlen: i64) { return -1; }; }; + if (hasdata) { + // Pad to the page-aligned data offset, then write .data. + let here: u64 = TEXT_OFF + l.textlen; + let zero: u8 = 0u8; + for (here < dataoff) { + let r3: (i64 | os.oserror) = os.writeall(fd, &zero, 1u64); + match (r3) { + case let v: i64 => { }; + case let e: os.oserror => return -1; + }; + here += 1u64; + }; + let r4: (i64 | os.oserror) = os.writeall(fd, l.data, l.datalen); + let n4: i64 = 0i64; + match (r4) { + case let v: i64 => n4 = v; + case let e: os.oserror => return -1; + }; + if (n4 != l.datalen: i64) { return -1; }; + }; return 0; }; diff --git a/selfhost/cmd/w6l/pass.ww b/selfhost/cmd/w6l/pass.ww index edee3ed9..111ec400 100644 --- a/selfhost/cmd/w6l/pass.ww +++ b/selfhost/cmd/w6l/pass.ww @@ -14,6 +14,7 @@ use os; use sym; use dyn; +def R_X86_64_64: i32 = 1; def R_X86_64_PC32: i32 = 2; def R_X86_64_PLT32: i32 = 4; @@ -80,7 +81,15 @@ fn patchu32(p: *u8, v: u32) void = { p[3] = ((v >> 24u32) & 255u32): u8; }; -export fn relocate(l: *lnk, base: u64) i32 = { +fn patchu64(p: *u8, v: u64) void = { + let i: i32 = 0; + for (i < 8) { + p[i] = ((v >> (i: u64 * 8u64)) & 255u64): u8; + i += 1; + }; +}; + +export fn relocate(l: *lnk, textva: u64, datava: u64) i32 = { let r: *lrel = l.rels; for (r != nil) { if (r.sym != nil) { @@ -91,21 +100,30 @@ export fn relocate(l: *lnk, base: u64) i32 = { continue; }; if (r.sym.defined != 0) { + let symva: u64 = textva + r.sym.val; + if (r.sym.indata != 0) { symva = datava + r.sym.val; }; let k: i32 = r.kind; if (k == R_X86_64_PC32) { - let site: u64 = base + r.off; - let target: i64 = (base + r.sym.val): i64; - let rel: i64 = (target - site: i64) + r.addend; + let site: u64 = textva + r.off; + let rel: i64 = (symva: i64 - site: i64) + r.addend; patchu32(l.text + r.off, rel: u32); } else { if (k == R_X86_64_PLT32) { - let site: u64 = base + r.off; - let target: i64 = (base + r.sym.val): i64; - let rel: i64 = (target - site: i64) + r.addend; + let site: u64 = textva + r.off; + let rel: i64 = (symva: i64 - site: i64) + r.addend; patchu32(l.text + r.off, rel: u32); + } else { if (k == R_X86_64_64) { + // Absolute 64-bit. Currently used only + // for DATAR slots in .data. + let v: u64 = (symva: i64 + r.addend): u64; + if (r.section == 1) { + patchu64(l.data + r.off, v); + } else { + patchu64(l.text + r.off, v); + }; } else { os.write(2, "w6l: unsupported reloc kind\n".ptr, 27u64); l.errs += 1; - };}; + };};}; }; }; r = r.rnext; diff --git a/selfhost/cmd/w6l/sym.ww b/selfhost/cmd/w6l/sym.ww index d048297d..d2142f35 100644 --- a/selfhost/cmd/w6l/sym.ww +++ b/selfhost/cmd/w6l/sym.ww @@ -7,8 +7,10 @@ use mem; type lsym = struct { name: str, - val: u64, // offset within combined .text once linked + 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 @@ -23,7 +25,8 @@ type lsym = struct { }; type lrel = struct { - off: u64, // offset within combined .text + off: u64, // offset within the relocation's section + section: i32, // 0 = .text, 1 = .data kind: i32, // R_X86_64_* sym: *lsym, addend: i64, @@ -36,6 +39,8 @@ type lobj = struct { 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, }; @@ -64,6 +69,11 @@ type lnk = struct { 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 }; diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index f75bc8b0..ca96cd84 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -761,13 +761,45 @@ fn emitdatawbyte(b: u8) void = { os.write(1, bb.ptr, 1u64); }; +// letpreintern — intern strlits referenced from top-level str-let +// initialisers BEFORE emitdatasection runs. Mirrors cmd/w6c/cgen.c +// let_pre_intern: emitletdataw later looks up the same label, and +// emitdatasection emits the DATA row in the same .s file. Running +// emitletdataw after emitdatasection would flip the (DATA strlits, +// DATAW lets) section order and break byte-identity. +export fn letpreintern(c: *cgen, file: *node) void = { + if (file == nil) { return; }; + let d: *node = file.list; + for (d != nil) { + if (d.kind == nkind.N_LET) { + let sz: i32 = letemitsize(c, d); + if (sz == 16) { + let r: *node = d.rhs; + for (r != nil) { + if (r.kind != nkind.N_CAST) { break; }; + r = r.lhs; + }; + if (r != nil) { + if (r.kind == nkind.N_STRLIT) { + if (r.str.len > 0) { + internstrlit(c, r.str); + }; + }; + }; + }; + }; + d = d.next; + }; +}; + // emitletdataw — DATAW directive per top-level `let` global. // 8B scalar with int/rune/bool/nil literal init (or no init). -// 16B str with no init (or `nil` / `""`) — zero header; the -// program must assign a real strlit at runtime before -// using .ptr / .len. -// Non-literal scalar inits and non-empty strlit inits are skipped -// so the link surfaces an undefined-symbol error. +// 16B str — no init / `nil` / `""` → 16 zero bytes; or non-empty +// strlit init → 8 zero placeholder + 8 LE len bytes plus a +// DATAR slot+0,strlit reloc that the linker patches at load. +// sz struct — zero only. +// Non-literal scalar inits and unsupported shapes are skipped so the +// link surfaces an undefined-symbol error if the binding is used. fn emitletdataw(c: *cgen, file: *node) void = { let d: *node = file.list; for (d != nil) { @@ -810,31 +842,67 @@ fn emitletdataw(c: *cgen, file: *node) void = { }; }; if (sz == 16 && !issg) { - let ok: bool = true; - if (d.rhs != nil) { - let r: *node = d.rhs; - for (r != nil) { - if (r.kind != nkind.N_CAST) { break; }; - r = r.lhs; - }; - ok = false; - if (r != nil) { - if (r.kind == nkind.N_NIL) { ok = true; }; - if (r.kind == nkind.N_STRLIT) { - if (r.str.len == 0) { ok = true; }; - }; + let r: *node = d.rhs; + for (r != nil) { + if (r.kind != nkind.N_CAST) { break; }; + r = r.lhs; + }; + // str-literal init (non-empty): emit + // the 16B payload as 8 placeholder zero + // bytes + 8 LE bytes of length, then a + // DATAR reloc to patch the ptr half with + // the strlit's runtime VA. + let strlitinit: bool = false; + if (r != nil) { + if (r.kind == nkind.N_STRLIT) { + if (r.str.len > 0) { strlitinit = true; }; }; }; - if (ok) { + if (strlitinit) { + let lab: str = internstrlit(c, r.str); + let v: u64 = r.str.len: u64; emitline("DATAW "); emitsymname(c, nm); emitline("(SB),\""); let i: i32 = 0; - for (i < 16) { - emitdatawbyte(0u8); + for (i < 8) { emitdatawbyte(0u8); i += 1; }; + i = 0; + let nv: u64 = v; + for (i < 8) { + emitdatawbyte((nv & 255u64): u8); + nv = nv >> 8u64; i += 1; }; emitline("\"\n"); + emitline("DATAR "); + emitsymname(c, nm); + emitline("+0(SB),"); + os.write(1, lab.ptr, lab.len: u64); + emitline("(SB)\n"); + } else { + // zero-init: accept no rhs, nil, + // or empty strlit. + let ok: bool = true; + if (d.rhs != nil) { + ok = false; + if (r != nil) { + if (r.kind == nkind.N_NIL) { ok = true; }; + if (r.kind == nkind.N_STRLIT) { + if (r.str.len == 0) { ok = true; }; + }; + }; + }; + if (ok) { + emitline("DATAW "); + emitsymname(c, nm); + emitline("(SB),\""); + let i: i32 = 0; + for (i < 16) { + emitdatawbyte(0u8); + i += 1; + }; + emitline("\"\n"); + }; }; }; if (sz == 24 && !issg) { diff --git a/selfhost/cmd/wcc/cgendecl.ww b/selfhost/cmd/wcc/cgendecl.ww index 5723e859..d53c7ee1 100644 --- a/selfhost/cmd/wcc/cgendecl.ww +++ b/selfhost/cmd/wcc/cgendecl.ww @@ -297,6 +297,7 @@ export fn cgfile(c: *cgen, file: *node) void = { }; d = d.next; }; + letpreintern(c, file); emitdatasection(c); emitdefconstants(c, file); emitletdataw(c, file); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 056fef29..5f1a460a 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -8576,6 +8576,7 @@ export fn cgfile(c: *cgen, file: *node) void = { }; d = d.next; }; + letpreintern(c, file); emitdatasection(c); emitdefconstants(c, file); emitletdataw(c, file); @@ -9345,13 +9346,45 @@ fn emitdatawbyte(b: u8) void = { os.write(1, bb.ptr, 1u64); }; +// letpreintern — intern strlits referenced from top-level str-let +// initialisers BEFORE emitdatasection runs. Mirrors cmd/w6c/cgen.c +// let_pre_intern: emitletdataw later looks up the same label, and +// emitdatasection emits the DATA row in the same .s file. Running +// emitletdataw after emitdatasection would flip the (DATA strlits, +// DATAW lets) section order and break byte-identity. +export fn letpreintern(c: *cgen, file: *node) void = { + if (file == nil) { return; }; + let d: *node = file.list; + for (d != nil) { + if (d.kind == nkind.N_LET) { + let sz: i32 = letemitsize(c, d); + if (sz == 16) { + let r: *node = d.rhs; + for (r != nil) { + if (r.kind != nkind.N_CAST) { break; }; + r = r.lhs; + }; + if (r != nil) { + if (r.kind == nkind.N_STRLIT) { + if (r.str.len > 0) { + internstrlit(c, r.str); + }; + }; + }; + }; + }; + d = d.next; + }; +}; + // emitletdataw — DATAW directive per top-level `let` global. // 8B scalar with int/rune/bool/nil literal init (or no init). -// 16B str with no init (or `nil` / `""`) — zero header; the -// program must assign a real strlit at runtime before -// using .ptr / .len. -// Non-literal scalar inits and non-empty strlit inits are skipped -// so the link surfaces an undefined-symbol error. +// 16B str — no init / `nil` / `""` → 16 zero bytes; or non-empty +// strlit init → 8 zero placeholder + 8 LE len bytes plus a +// DATAR slot+0,strlit reloc that the linker patches at load. +// sz struct — zero only. +// Non-literal scalar inits and unsupported shapes are skipped so the +// link surfaces an undefined-symbol error if the binding is used. fn emitletdataw(c: *cgen, file: *node) void = { let d: *node = file.list; for (d != nil) { @@ -9394,31 +9427,67 @@ fn emitletdataw(c: *cgen, file: *node) void = { }; }; if (sz == 16 && !issg) { - let ok: bool = true; - if (d.rhs != nil) { - let r: *node = d.rhs; - for (r != nil) { - if (r.kind != nkind.N_CAST) { break; }; - r = r.lhs; - }; - ok = false; - if (r != nil) { - if (r.kind == nkind.N_NIL) { ok = true; }; - if (r.kind == nkind.N_STRLIT) { - if (r.str.len == 0) { ok = true; }; - }; + let r: *node = d.rhs; + for (r != nil) { + if (r.kind != nkind.N_CAST) { break; }; + r = r.lhs; + }; + // str-literal init (non-empty): emit + // the 16B payload as 8 placeholder zero + // bytes + 8 LE bytes of length, then a + // DATAR reloc to patch the ptr half with + // the strlit's runtime VA. + let strlitinit: bool = false; + if (r != nil) { + if (r.kind == nkind.N_STRLIT) { + if (r.str.len > 0) { strlitinit = true; }; }; }; - if (ok) { + if (strlitinit) { + let lab: str = internstrlit(c, r.str); + let v: u64 = r.str.len: u64; emitline("DATAW "); emitsymname(c, nm); emitline("(SB),\""); let i: i32 = 0; - for (i < 16) { - emitdatawbyte(0u8); + for (i < 8) { emitdatawbyte(0u8); i += 1; }; + i = 0; + let nv: u64 = v; + for (i < 8) { + emitdatawbyte((nv & 255u64): u8); + nv = nv >> 8u64; i += 1; }; emitline("\"\n"); + emitline("DATAR "); + emitsymname(c, nm); + emitline("+0(SB),"); + os.write(1, lab.ptr, lab.len: u64); + emitline("(SB)\n"); + } else { + // zero-init: accept no rhs, nil, + // or empty strlit. + let ok: bool = true; + if (d.rhs != nil) { + ok = false; + if (r != nil) { + if (r.kind == nkind.N_NIL) { ok = true; }; + if (r.kind == nkind.N_STRLIT) { + if (r.str.len == 0) { ok = true; }; + }; + }; + }; + if (ok) { + emitline("DATAW "); + emitsymname(c, nm); + emitline("(SB),\""); + let i: i32 = 0; + for (i < 16) { + emitdatawbyte(0u8); + i += 1; + }; + emitline("\"\n"); + }; }; }; if (sz == 24 && !issg) {