ww: rename toolchain to w-prefix + hare-style build/run/test driver

Plan 9-style w-prefix on the per-arch tools, disambiguating from the
real Plan 9 6c/6a/6l in ref/plan9front/:

    cmd/wwc/      → cmd/wcc/        libwwc.a → libwcc.a
    cmd/6{c,a,l}  → cmd/w6{c,a,l}   binary names too
    test/wwc/     → test/wcc/       6 test files w/ w6 prefix
    selfhost/cmd  mirror in lockstep
    bootstrap/amd64/{w6c,w6a,w6l}   snapshot binaries (gitignored)
    WW_6{C,A,L}   → WW_W6{C,A,L}    env-var overrides

Plan 9 source-tree refs ("Plan 9 6c shape", ref/plan9front/, etc.)
preserved. Hare-style driver, both C and ww sides:

    ww test [path]   discover *_test.ww in a directory module, run
                     each; single-file mode for `ww test foo.ww`
    Module-by-name   `ww build foo` resolves to foo.ww or foo/foo.ww
                     via search path (cwd : -I dirs : $WW_LIB)
    Default-to-cwd   `ww build` / `ww test` build the cwd module
    Run pass-through `ww run path arg1 arg2` reaches the program

lib/os: getcwd (79) and getdents64 (217) syscalls power `.` resolution
and directory enumeration on the ww side.

Makefile: wwstage tool deps now include lib/os/os.ww (+ lib/strconv
for wwdump_ww) so lib/* edits force their rebuild instead of leaving
stale binaries — surfaced when test 995 first failed against a stale
w6c_ww built before the lib/os additions.

Test 993 byte-identical parity gate (C-side ww vs ww-side ww_ww on a
build corpus) stays green; all 19 tests pass.
This commit is contained in:
2026-05-11 13:49:27 +09:00
parent e217cd32d1
commit 2c33228b7e
96 changed files with 2129 additions and 973 deletions

293
selfhost/cmd/w6a/obj.ww Normal file
View File

@@ -0,0 +1,293 @@
// selfhost/cmd/w6a/obj.ww — port of cmd/w6a/obj.c.
//
// Emit a tiny ELF64 relocatable object. Layout (in file order):
// [0] ELF header
// [1] Section .text (program bytes)
// [2] Section .rela.text (relocations)
// [3] Section .symtab
// [4] Section .strtab
// [5] Section .shstrtab
// [6] Section header table
//
// Symtab indices: 0 = STN_UNDEF, 1.. = our syms. Only GLOBAL symbols.
use os;
use mem;
use types;
// ---- ELF constants ----------------------------------------------------
def ELFCLASS64: u8 = 2u8;
def ELFDATA2LSB: u8 = 1u8;
def EV_CURRENT_W: u32 = 1u32;
def ET_REL_W: u16 = 1u16;
def EM_X86_64_W: u16 = 62u16;
def SHT_NULL_C: u32 = 0u32;
def SHT_PROGBITS_C: u32 = 1u32;
def SHT_SYMTAB_C: u32 = 2u32;
def SHT_STRTAB_C: u32 = 3u32;
def SHT_RELA_C: u32 = 4u32;
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_FUNC: u8 = 2u8;
// Sizes of fixed structures.
def EHDR_SZ: u64 = 64u64;
def SHDR_SZ: u64 = 64u64;
def SYM_SZ: u64 = 24u64;
def RELA_SZ: u64 = 24u64;
// ---- LE byte writers (own the bytes — write into a *u8 + offset) ----
fn wr_u8(p: *u8, off: u64, v: u8) void = { p[off] = v; };
fn wr_u16(p: *u8, off: u64, v: u16) void = {
p[off] = (v & 255u16): u8;
p[off + 1u64] = ((v >> 8u16) & 255u16): u8;
};
fn wr_u32(p: *u8, off: u64, v: u32) void = {
p[off] = (v & 255u32): u8;
p[off + 1u64] = ((v >> 8u32) & 255u32): u8;
p[off + 2u64] = ((v >> 16u32) & 255u32): u8;
p[off + 3u64] = ((v >> 24u32) & 255u32): u8;
};
fn wr_u64(p: *u8, off: u64, v: u64) void = {
wr_u32(p, off, (v & 4294967295u64): u32);
wr_u32(p, off + 4u64, ((v >> 32u64) & 4294967295u64): u32);
};
// ---- growable byte buffer ---------------------------------------------
type buf = struct {
a: *arena,
p: *u8,
n: u64,
cap: u64,
};
fn buf_init(b: *buf, a: *arena) void = {
b.a = a;
b.cap = 256u64;
b.n = 0u64;
b.p = amalloc(a, b.cap): *u8;
};
fn buf_grow(b: *buf, need: u64) void = {
if (b.n + need <= b.cap) { return; };
let nc: u64 = b.cap;
for (nc < b.n + need) { nc = nc * 2u64; };
let np: *u8 = amalloc(b.a, nc): *u8;
let i: u64 = 0u64;
for (i < b.n) { np[i] = b.p[i]; i += 1u64; };
b.p = np;
b.cap = nc;
};
fn buf_putb(b: *buf, src: *u8, n: u64) void = {
buf_grow(b, n);
let i: u64 = 0u64;
for (i < n) { b.p[b.n + i] = src[i]; i += 1u64; };
b.n += n;
};
// Write a NUL-terminated C-string copy of `s` into b. Returns offset
// where it started (suitable for st_name / sh_name fields).
fn buf_put_cstr(b: *buf, s: str) u32 = {
let off: u32 = b.n: u32;
buf_grow(b, s.len: u64 + 1u64);
let i: i32 = 0;
for (i < s.len) { b.p[b.n] = s[i]; b.n += 1u64; i += 1; };
b.p[b.n] = 0u8;
b.n += 1u64;
return off;
};
// ---- emit_elf ---------------------------------------------------------
export fn a_emit_elf(a: *asm_, fd: i32) i32 = {
let shstr: buf; buf_init(&shstr, a.a);
let str_: buf; buf_init(&str_, a.a);
let sym: buf; buf_init(&sym, a.a);
let rela: buf; buf_init(&rela, a.a);
// Index 0 = empty.
let zero: u8 = 0u8;
buf_putb(&shstr, &zero, 1u64);
buf_putb(&str_, &zero, 1u64);
// Section name offsets.
let shn_text: u32 = buf_put_cstr(&shstr, ".text");
let shn_rela: u32 = buf_put_cstr(&shstr, ".rela.text");
let shn_symtab: u32 = buf_put_cstr(&shstr, ".symtab");
let shn_strtab: u32 = buf_put_cstr(&shstr, ".strtab");
let shn_shstrtab: u32 = buf_put_cstr(&shstr, ".shstrtab");
// Symbol 0 — STN_UNDEF (24 zero bytes).
let zsym: [24]u8;
let zi: i32 = 0;
for (zi < 24) { zsym[zi] = 0u8; zi += 1; };
buf_putb(&sym, zsym.ptr, 24u64);
let SH_TEXT: u16 = 1u16;
// Build symbols.
let idx: i32 = 1;
let s: *asym = a.syms;
for (s != nil) {
let entry: [24]u8;
let ei: i32 = 0;
for (ei < 24) { entry[ei] = 0u8; ei += 1; };
let st_name: u32 = buf_put_cstr(&str_, s.name);
wr_u32(entry.ptr, 0u64, st_name);
if (s.defined != 0) {
wr_u8(entry.ptr, 4u64, ((STB_GLOBAL << 4u8) | STT_FUNC));
wr_u16(entry.ptr, 6u64, SH_TEXT);
wr_u64(entry.ptr, 8u64, s.addr);
} else {
wr_u8(entry.ptr, 4u64, ((STB_GLOBAL << 4u8) | STT_NOTYPE));
wr_u16(entry.ptr, 6u64, 0u16);
};
buf_putb(&sym, entry.ptr, 24u64);
s.idx = idx;
idx += 1;
s = s.snext;
};
// Build relocations.
let r: *areloc = a.relocs;
for (r != nil) {
let entry: [24]u8;
wr_u64(entry.ptr, 0u64, r.off);
let r_info: u64 = (r.asy.idx: u64 << 32u64) | (r.kind: u64 & 4294967295u64);
wr_u64(entry.ptr, 8u64, r_info);
wr_u64(entry.ptr, 16u64, r.addend: u64);
buf_putb(&rela, entry.ptr, 24u64);
r = r.rnext;
};
// File offsets.
let off: u64 = EHDR_SZ;
let off_text: u64 = off; off = off + a.textlen;
let off_rela: u64 = off; off = off + rela.n;
let off_sym: u64 = off; off = off + sym.n;
let off_str: u64 = off; off = off + str_.n;
let off_shstr: u64 = off; off = off + shstr.n;
for ((off & 7u64) != 0u64) { off += 1u64; };
let off_shdr: u64 = off;
let NSECT: u16 = 6u16;
// ---- Ehdr ----
let eh: [64]u8;
let i: i32 = 0;
for (i < 64) { eh[i] = 0u8; i += 1; };
eh[0] = 127u8; // 0x7f
eh[1] = 69u8; // 'E'
eh[2] = 76u8; // 'L'
eh[3] = 70u8; // 'F'
eh[4] = ELFCLASS64;
eh[5] = ELFDATA2LSB;
eh[6] = EV_CURRENT_W: u8;
wr_u16(eh.ptr, 16u64, ET_REL_W);
wr_u16(eh.ptr, 18u64, EM_X86_64_W);
wr_u32(eh.ptr, 20u64, EV_CURRENT_W);
wr_u64(eh.ptr, 24u64, 0u64); // e_entry
wr_u64(eh.ptr, 32u64, 0u64); // e_phoff
wr_u64(eh.ptr, 40u64, off_shdr); // e_shoff
wr_u32(eh.ptr, 48u64, 0u32); // e_flags
wr_u16(eh.ptr, 52u64, 64u16); // e_ehsize
wr_u16(eh.ptr, 54u64, 0u16); // e_phentsize
wr_u16(eh.ptr, 56u64, 0u16); // e_phnum
wr_u16(eh.ptr, 58u64, 64u16); // e_shentsize
wr_u16(eh.ptr, 60u64, NSECT); // e_shnum
wr_u16(eh.ptr, 62u64, 5u16); // e_shstrndx
if (os.writefull(fd, eh.ptr, 64u64) != 64i64) { return -1; };
if (a.textlen > 0u64) {
if (os.writefull(fd, a.text, a.textlen) != a.textlen: i64) { return -1; };
};
if (rela.n > 0u64) {
if (os.writefull(fd, rela.p, rela.n) != rela.n: i64) { return -1; };
};
if (sym.n > 0u64) {
if (os.writefull(fd, sym.p, sym.n) != sym.n: i64) { return -1; };
};
if (str_.n > 0u64) {
if (os.writefull(fd, str_.p, str_.n) != str_.n: i64) { return -1; };
};
if (shstr.n > 0u64) {
if (os.writefull(fd, shstr.p, shstr.n) != shstr.n: i64) { return -1; };
};
// Pad to 8 before shdrs.
let written: u64 = EHDR_SZ + a.textlen + rela.n + sym.n + str_.n + shstr.n;
for ((written & 7u64) != 0u64) {
os.writefull(fd, &zero, 1u64);
written += 1u64;
};
// Section header table — 6 headers of 64 bytes each = 384 bytes.
let shbuf: [64]u8;
// SHT_NULL
let sn: i32 = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
os.writefull(fd, shbuf.ptr, 64u64);
// .text
sn = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
wr_u32(shbuf.ptr, 0u64, shn_text);
wr_u32(shbuf.ptr, 4u64, SHT_PROGBITS_C);
wr_u64(shbuf.ptr, 8u64, SHF_ALLOC | SHF_EXECINSTR);
wr_u64(shbuf.ptr, 24u64, off_text);
wr_u64(shbuf.ptr, 32u64, a.textlen);
wr_u64(shbuf.ptr, 48u64, 1u64); // sh_addralign
os.writefull(fd, shbuf.ptr, 64u64);
// .rela.text
sn = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
wr_u32(shbuf.ptr, 0u64, shn_rela);
wr_u32(shbuf.ptr, 4u64, SHT_RELA_C);
wr_u64(shbuf.ptr, 8u64, SHF_INFO_LINK);
wr_u64(shbuf.ptr, 24u64, off_rela);
wr_u64(shbuf.ptr, 32u64, rela.n);
wr_u32(shbuf.ptr, 40u64, 3u32); // sh_link = symtab idx
wr_u32(shbuf.ptr, 44u64, 1u32); // sh_info = .text idx
wr_u64(shbuf.ptr, 48u64, 8u64);
wr_u64(shbuf.ptr, 56u64, RELA_SZ);
os.writefull(fd, shbuf.ptr, 64u64);
// .symtab
sn = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
wr_u32(shbuf.ptr, 0u64, shn_symtab);
wr_u32(shbuf.ptr, 4u64, SHT_SYMTAB_C);
wr_u64(shbuf.ptr, 24u64, off_sym);
wr_u64(shbuf.ptr, 32u64, sym.n);
wr_u32(shbuf.ptr, 40u64, 4u32); // sh_link = strtab
wr_u32(shbuf.ptr, 44u64, 1u32); // sh_info = one local (STN_UNDEF)
wr_u64(shbuf.ptr, 48u64, 8u64);
wr_u64(shbuf.ptr, 56u64, SYM_SZ);
os.writefull(fd, shbuf.ptr, 64u64);
// .strtab
sn = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
wr_u32(shbuf.ptr, 0u64, shn_strtab);
wr_u32(shbuf.ptr, 4u64, SHT_STRTAB_C);
wr_u64(shbuf.ptr, 24u64, off_str);
wr_u64(shbuf.ptr, 32u64, str_.n);
wr_u64(shbuf.ptr, 48u64, 1u64);
os.writefull(fd, shbuf.ptr, 64u64);
// .shstrtab
sn = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
wr_u32(shbuf.ptr, 0u64, shn_shstrtab);
wr_u32(shbuf.ptr, 4u64, SHT_STRTAB_C);
wr_u64(shbuf.ptr, 24u64, off_shstr);
wr_u64(shbuf.ptr, 32u64, shstr.n);
wr_u64(shbuf.ptr, 48u64, 1u64);
os.writefull(fd, shbuf.ptr, 64u64);
return 0;
};