toolchain: banner purge + WHY-only comment sweep (rule 8)

selfhost/, cmd/, internal/ join the tree-wide sweep: every section
banner dies (91 selfhost + the cmd C-style dividers -> 0); narration
and stale contracts deleted (pre-#22 bundler notes, retired
single-PT_LOAD and no-archive claims, superseded ABI tables); every
ref/harec/qbe cite, task cite, encoding/ELF contract, and rule-10
twin pointer kept; lost lifetime/rationale lines restored where the
sweep over-cut (elf_globals ownership, kwtab linear-scan). Comment-
only proven: all five wwstage tool binaries byte-identical across
the sweep; test-commit, test-byteid (161+1399, 0 pinned-divergent),
and test-bootstrap (fixed point + 991-995 byte-id) all exit 0.
The read-through banked 66 latent-bug leads (checkpoint).
This commit is contained in:
2026-08-08 23:14:03 +09:00
parent 83f5956df2
commit 62b9d20383
60 changed files with 232 additions and 1045 deletions

View File

@@ -1,13 +1,5 @@
// selfhost/cmd/w6a/asm.ww — port of cmd/w6a/asm.c.
//
// Encode the parsed aprog list into amd64 machine bytes, appending to
// asm_.text. Relocations for CALL/branch targets that resolve to
// externals are queued in asm_.relocs.
//
// Encoding subset matches what w6c emits — see cmd/w6a/asm.c for the
// authoritative list. Helpers (rcode/rhi/modrm/emitrex etc.) are
// fully ported; encode itself is still a stub pending the full
// switch over A_*.
// Port of cmd/w6a/asm.c. Encoding subset matches what w6c emits — see
// cmd/w6a/asm.c for the authoritative list.
package w6a;
@@ -16,8 +8,6 @@ import rt;
import opcodes;
import strings;
// ---- text buffer growth ------------------------------------------------
export fn emitbyte(a: *asm_, b: u8) void = {
if (a.textlen + 1u64 > a.textcap) {
let nc: u64 = a.textcap;
@@ -45,16 +35,14 @@ 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.
// DATAR patches a 64-bit .data slot with a symbol's runtime VA;
// obj.ww separates section=1 relocs into .rela.data when emitting
// the .o.
export fn addrelocdata(a: *asm_, off: u64, kind: i32, s: *asym, add: i64) void = {
let r: *areloc = alloc(areloc { off = off, section = 1, kind = kind, asy = s, addend = add, 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;
@@ -70,8 +58,6 @@ export fn emitdatabyte(a: *asm_, b: u8) void = {
a.datalen += 1u64;
};
// ---- register codes ----------------------------------------------------
// Low 3 bits of register encoding.
fn rcode(r: i32) i32 = {
if (r == D_AX) { return 0; }; if (r == D_CX) { return 1; };
@@ -105,7 +91,6 @@ fn isxmm(r: i32) bool = {
return false;
};
// ModR/M byte builder.
fn modrmbyte(mod: i32, reg: i32, rm: i32) u8 = {
return (((mod & 3) << 6) | ((reg & 7) << 3) | (rm & 7)): u8;
};
@@ -210,8 +195,6 @@ fn sserrw(a: *asm_, prefix: u8, op2: u8, regop: i32, rmop: i32) void = {
emitbyte(a, modrmbyte(3, rcode(regop), rcode(rmop)));
};
// ---- label resolution / fixups ----------------------------------------
fn resolvelabel(a: *asm_, name: str) u64 = {
let s: *asym = a.syms;
for (s != nil) {
@@ -230,8 +213,6 @@ fn labeldefined(a: *asm_, name: str) bool = {
return false;
};
// ---- fixup helper -----------------------------------------------------
fn addfixup(a: *asm_, off: u64, label: str) void = {
let f: *afixup = alloc(afixup { off = off, label = label, fnext = a.fixups })!;
a.fixups = f;
@@ -242,15 +223,9 @@ fn isgpr(t: i32) bool = {
return false;
};
// `intern` lives in parse.ww — flat-scope concat lets us call it
// directly without an @symbol declaration here.
// ---- encode ----------------------------------------------------------
export fn encode(a: *asm_) i32 = {
let p: *aprog = a.head;
for (p != nil) {
// Define any pending label at the current PC.
if (p.label.len > 0) {
let s: *asym = intern(a, p.label);
s.defined = 1;
@@ -281,10 +256,9 @@ export fn encode(a: *asm_) i32 = {
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.
// obj.ww emits the .data 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;

View File

@@ -1,8 +1,4 @@
// selfhost/cmd/w6a/lex.ww — port of cmd/w6a/lex.c.
//
// Character-level helpers for w6a's line-oriented parser. The parser
// itself is in parse.ww; here we keep tokenisers for identifiers and
// numbers so parse.ww stays focused on syntax.
// Port of cmd/w6a/lex.c.
package w6a;
@@ -20,10 +16,6 @@ export fn isidcont(c: i32) bool = {
return false;
};
// parsenum — read a leading [+-]?[0x|0X|0]?digits from p[0..n-1].
// Returns (value, consumed). Stops at first non-digit.
// Plain Plan 9-style: $123 / $0x1f / $-7. Decimal default; 0x prefix
// for hex; 0 prefix for octal when followed by a digit (else just 0).
export fn parsenum(p: *u8, n: u64) (i64, u64) = {
// strtoll(s, end, 0) semantics, matching the C twin cmd/w6a/lex.c:30:
// skip leading whitespace, optional sign, base-0 prefix detection

View File

@@ -1,6 +1,4 @@
// selfhost/cmd/w6a/main.ww — port of cmd/w6a/main.c.
//
// w6a = amd64 assembler. Read .s, parse, encode, emit ELF .o.
// Port of cmd/w6a/main.c.
//
// w6a_ww -o file.o file.s
@@ -33,8 +31,7 @@ fn cstrlen(p: *u8) u64 = {
return n;
};
// pathstr — view a NUL-terminated *u8 as a str. Bridges argv-style
// callers to lib/os entrypoints (str post-task-#23).
// Bridges argv-style callers to lib/os entrypoints (str post-task-#23).
fn pathstr(p: *u8) str = {
let r: str;
r.ptr = p;
@@ -42,7 +39,6 @@ fn pathstr(p: *u8) str = {
return r;
};
// Slurp the whole file into a fresh buffer.
fn slurp(path: *u8) (*u8, u64) = {
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
if (fd < 0) { return nil, 0u64; };
@@ -122,7 +118,6 @@ export fn main(argc: i32, argv: **u8) i32 = {
if (parse(&s) != 0) { return 1; };
if (encode(&s) != 0) { return 1; };
// Open output for write.
let fd: i32 = os.open(pathstr(out), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
if (fd < 0) {
os.write(2, "w6a: cannot open output\n".ptr, 23u64);

View File

@@ -1,4 +1,4 @@
// selfhost/cmd/w6a/obj.ww — port of cmd/w6a/obj.c.
// Port of cmd/w6a/obj.c.
//
// Emit a tiny ELF64 relocatable object. Layout (in file order):
// [0] ELF header
@@ -36,7 +36,6 @@ fn wrdrop(fd: i32, p: *u8, n: u64) void = {
};
};
// ---- ELF constants ----------------------------------------------------
def ELFCLASS64: u8 = 2u8;
def ELFDATA2LSB: u8 = 1u8;
def EV_CURRENT_W: u32 = 1u32;
@@ -59,14 +58,11 @@ def STT_NOTYPE: u8 = 0u8;
def STT_OBJECT: u8 = 1u8;
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 wru8(p: *u8, off: u64, v: u8) void = { p[off] = v; };
fn wru16(p: *u8, off: u64, v: u16) void = {
p[off] = (v & 255u16): u8;
@@ -83,8 +79,6 @@ fn wru64(p: *u8, off: u64, v: u64) void = {
wru32(p, off + 4u64, ((v >> 32u64) & 4294967295u64): u32);
};
// ---- growable byte buffer ---------------------------------------------
type buf = struct {
p: *u8,
n: u64,
@@ -128,8 +122,6 @@ fn bufputcstr(b: *buf, s: str) u32 = {
return off;
};
// ---- emitelf ---------------------------------------------------------
export fn emitelf(a: *asm_, fd: i32) i32 = {
let shstr: buf; bufinit(&shstr);
let str_: buf; bufinit(&str_);
@@ -174,9 +166,9 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
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).
// Append .data / .rela.data names 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;
@@ -195,7 +187,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
for (zi < 24) { zsym[zi] = 0u8; zi += 1; };
bufputb(&sym, zsym.ptr, 24u64);
// Build symbols.
let idx: i32 = 1;
let s: *asym = a.syms;
for (s != nil) {
@@ -223,7 +214,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
s = s.snext;
};
// Build relocations — split into text vs data buffers.
let r: *areloc = a.relocs;
for (r != nil) {
let entry: [24]u8;
@@ -239,7 +229,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
r = r.rnext;
};
// File offsets.
let off: u64 = EHDR_SZ;
let offtext: u64 = off; off = off + a.textlen;
let offrela: u64 = off; off = off + rela.n;
@@ -258,7 +247,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
else { NSECT = 7u16; };
};
// ---- Ehdr ----
let eh: [64]u8;
let i: i32 = 0;
for (i < 64) { eh[i] = 0u8; i += 1; };
@@ -321,13 +309,11 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
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; };
wrdrop(fd, shbuf.ptr, 64u64);
// .text
sn = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
wru32(shbuf.ptr, 0u64, shntext);
@@ -337,7 +323,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
wru64(shbuf.ptr, 32u64, a.textlen);
wru64(shbuf.ptr, 48u64, 1u64); // sh_addralign
wrdrop(fd, shbuf.ptr, 64u64);
// .rela.text
sn = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
wru32(shbuf.ptr, 0u64, shnrela);
@@ -351,7 +336,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
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);
@@ -362,7 +346,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
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);
@@ -377,7 +360,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
wrdrop(fd, shbuf.ptr, 64u64);
};
};
// .symtab
sn = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
wru32(shbuf.ptr, 0u64, shnsymtab);
@@ -389,7 +371,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
wru64(shbuf.ptr, 48u64, 8u64);
wru64(shbuf.ptr, 56u64, SYM_SZ);
wrdrop(fd, shbuf.ptr, 64u64);
// .strtab
sn = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
wru32(shbuf.ptr, 0u64, shnstrtab);
@@ -398,7 +379,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
wru64(shbuf.ptr, 32u64, str_.n);
wru64(shbuf.ptr, 48u64, 1u64);
wrdrop(fd, shbuf.ptr, 64u64);
// .shstrtab
sn = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
wru32(shbuf.ptr, 0u64, shnshstrtab);

View File

@@ -1,12 +1,10 @@
// selfhost/cmd/w6a/opcodes.ww — types + constants shared across the
// w6a port. Mirrors cmd/w6a/a.h and cmd/w6c/6.out.h.
// Mirrors cmd/w6a/a.h and cmd/w6c/6.out.h.
package w6a;
// ---- registers + operand kinds (from 6.out.h) -------------------------
// These must stay numerically aligned with the C enum so that ww-cgen
// output (which reads them via `D_AX(SB)` etc.) lands on the same
// integers when read by ww-w6a.
// Registers + operand kinds must stay numerically aligned with the
// 6.out.h C enum so that ww-cgen output (which reads them via
// `D_AX(SB)` etc.) lands on the same integers when read by ww-w6a.
def D_NONE: i32 = 0;
def D_AX: i32 = 1;
@@ -52,7 +50,6 @@ def D_BRANCH: i32 = 37;
def D_EXTERN: i32 = 38;
def D_INDIR: i32 = 39;
// ---- opcodes ----------------------------------------------------------
def A_NOP: i32 = 0;
def A_TEXT: i32 = 1;
def A_DATA: i32 = 2;
@@ -145,8 +142,6 @@ def A_DATAR: i32 = 61;
// with DIVQ.
def A_CQO: i32 = 66;
// ---- structs (mirror cmd/w6a/a.h) --------------------------------------
type aoperand = struct {
atype: i32, // D_NONE / D_AX..D_R15 / D_CONST / D_INDIR / D_EXTERN / D_BRANCH
reg: i32,

View File

@@ -1,4 +1,4 @@
// selfhost/cmd/w6a/parse.ww — port of cmd/w6a/parse.c.
// Port of cmd/w6a/parse.c.
//
// Line-oriented parser for the asm subset emitted by w6c.
// Grammar:
@@ -28,8 +28,7 @@ fn streqlit(p: *u8, n: u64, lit: str) bool = {
return true;
};
// opcodelookup — name (length-bounded *u8) → A_*. Returns 0 (A_NOP)
// if not found.
// Returns 0 (A_NOP) if not found.
fn opcodelookup(p: *u8, n: u64) i32 = {
if (streqlit(p, n, "MOVQ")) { return A_MOVQ; };
if (streqlit(p, n, "MOVL")) { return A_MOVL; };
@@ -100,7 +99,7 @@ fn opcodelookup(p: *u8, n: u64) i32 = {
return A_NOP;
};
// reglookup — name → D_*. Returns D_NONE if not found.
// Returns D_NONE if not found.
fn reglookup(p: *u8, n: u64) i32 = {
if (streqlit(p, n, "AX")) { return D_AX; };
if (streqlit(p, n, "BX")) { return D_BX; };
@@ -180,7 +179,6 @@ fn perr(a: *asm_, msg: str) void = {
a.errs += 1;
};
// dupstr — copy n bytes from p into a fresh heap str.
fn dupstr(p: *u8, n: u64) str = {
let view: str;
view.ptr = p;
@@ -188,8 +186,6 @@ fn dupstr(p: *u8, n: u64) str = {
return strings.dup(view);
};
// ---- line iteration & whitespace --------------------------------------
// Read next line into a fresh heap buffer; returns (ptr, len) or (nil,0)
// at EOF. Advances a.pos past the newline.
fn nextline(a: *asm_) (*u8, u64) = {
@@ -204,7 +200,7 @@ fn nextline(a: *asm_) (*u8, u64) = {
let i: u64 = 0u64;
for (i < n) { buf[i] = a.src[start + i]; i += 1u64; };
buf[n] = 0u8;
a.pos += 1u64; // skip newline
a.pos += 1u64;
return buf.ptr, n;
};
// EOF without trailing newline
@@ -226,7 +222,6 @@ fn skipws(p: *u8, off: u64, n: u64) u64 = {
return i;
};
// parseoperand — parse one operand from p[off..n), populate out.
// Returns new offset (clamped to n on error).
fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = {
let off: u64 = skipws(p, offin, n);
@@ -239,7 +234,6 @@ fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = {
if (off >= n) { return off; };
let c0: u8 = p[off];
// $NUM
if (c0 == '$') {
off += 1u64;
let v: i64;
@@ -250,7 +244,6 @@ fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = {
return off + used;
};
// (REG)
if (c0 == '(') {
off += 1u64;
let rstart: u64 = off;
@@ -377,7 +370,6 @@ fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = {
return n;
};
// Append a fresh aprog to the list with given opcode and label.
fn addprog(a: *asm_, opc: i32, lbl: str) *aprog = {
let pr: *aprog = alloc(aprog { as_ = opc, line = a.line, label = lbl })!;
pr.from = alloc(aoperand { })!;
@@ -398,9 +390,7 @@ export fn parse(a: *asm_) i32 = {
line, n = nextline(a);
if (line == nil) { return a.errs; };
// skip leading ws
let i: u64 = skipws(line, 0u64, n);
// blank or //-comment
if (i >= n) { a.line += 1; continue; };
if (i + 1u64 < n) {
if (line[i] == '/') { if (line[i + 1u64] == '/') {
@@ -435,7 +425,6 @@ export fn parse(a: *asm_) i32 = {
};
};
// MNEMONIC at the start of the rest. Scan to first ws/EOL.
let mstart: u64 = i;
let m: u64 = mstart;
let scan: bool = true;
@@ -458,11 +447,9 @@ export fn parse(a: *asm_) i32 = {
let pr: *aprog = addprog(a, opc, pending);
pending.ptr = nil; pending.len = 0;
// Skip ws after mnemonic
let r0: u64 = skipws(line, m, n);
if (opc == A_TEXT) {
// TEXT name,$framesize — find first ',' as the end of name.
let q: u64 = r0;
let commapos: u64 = n;
let scant: bool = true;
@@ -502,7 +489,6 @@ export fn parse(a: *asm_) i32 = {
let toop: *aoperand = pr.to;
toop.atype = D_EXTERN;
toop.asym = dupstr(line + r0, lparen - r0);
// Skip past `(SB)` to land just after ')'.
let p2: u64 = lparen;
let scand2: bool = true;
for (scand2) {
@@ -510,7 +496,6 @@ export fn parse(a: *asm_) i32 = {
else { if (line[p2] == ')') { p2 += 1u64; scand2 = false; }
else { p2 += 1u64; }; };
};
// Skip ws / ',' / tab between `)` and the `"`.
let scand3: bool = true;
for (scand3) {
if (p2 >= n) { scand3 = false; }
@@ -521,8 +506,7 @@ export fn parse(a: *asm_) i32 = {
};
if (p2 >= n) { perr(a, "DATA missing payload"); a.line += 1; continue; };
if (line[p2] != '"') { perr(a, "DATA expects \"...\""); a.line += 1; continue; };
p2 += 1u64; // past opening "
// Parse escape sequence into a fresh growable buffer.
p2 += 1u64;
let cap: u64 = 32u64;
let blen: u64 = 0u64;
let dbuf: []u8 = alloc([], cap)!;
@@ -575,8 +559,6 @@ export fn parse(a: *asm_) i32 = {
a.line += 1; continue;
};
// Generic instruction: 0/1/2 operands separated by ','.
// Find top-level comma.
let comma: i64 = -1i64;
let q: u64 = r0;
for (q < n) {

View File

@@ -1,14 +1,4 @@
// selfhost/cmd/w6c/main.ww — port of cmd/w6c/main.c.
//
// w6c = amd64 compiler. Read .ww, parse, codegen, emit Plan 9 amd64
// asm to stdout (or the file given by -o).
//
// w6c_ww -o file.s file.ww
//
// The cgen routines in selfhost/cmd/wcc/cgen.ww write directly to
// fd 1 via os.write(1, ...). For -o, we open the output file and
// dup2 it onto fd 1 before invoking cgfile. This is the same trick
// the bootstrap uses with shell redirection, just in-process.
// Port of cmd/w6c/main.c.
package main;

View File

@@ -1,12 +1,8 @@
// selfhost/cmd/w6l/dyn.ww — port of cmd/w6l/dyn.c.
// Port of cmd/w6l/dyn.c.
//
// Load a shared object (ET_DYN) so the linker knows which symbols it
// exports and which DT_NEEDED entry to record. We do not pull bytes
// from the .so; the dynamic loader maps it at runtime.
//
// Each call appends one lso to lnk->sos. l_so_provides_v answers
// "does this .so export the named symbol, and at which version?" —
// l_resolve uses that to promote unresolved references to dynamic.
package w6l;
@@ -72,8 +68,6 @@ def VD_NEXT: u64 = 16u64;
def VA_NAME: u64 = 0u64;
def VA_NEXT: u64 = 4u64;
// ---- little-endian byte readers ---------------------------------------
fn du16(p: *u8, off: u64) u16 = {
let b0: u16 = p[off]: u16;
let b1: u16 = p[off + 1u64]: u16;
@@ -98,8 +92,6 @@ fn di64(p: *u8, off: u64) i64 = {
return du64(p, off): i64;
};
// ---- C-string helpers --------------------------------------------------
fn dcstrlen(p: *u8) u64 = {
let n: u64 = 0u64;
for (p[n] != 0u8) { n += 1u64; };
@@ -114,7 +106,6 @@ fn dcstrtostr(p: *u8) str = {
return strings.dup(view);
};
// basename: scan for last '/' and return pointer past it.
fn dbasename(p: *u8) *u8 = {
let n: u64 = dcstrlen(p);
let i: u64 = n;
@@ -127,8 +118,6 @@ fn dbasename(p: *u8) *u8 = {
return p;
};
// ---- file slurp --------------------------------------------------------
fn slurpso(path: *u8) (*u8, u64) = {
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
if (fd < 0) { return nil, 0u64; };
@@ -151,8 +140,6 @@ fn slurpso(path: *u8) (*u8, u64) = {
return buf.ptr, n: u64;
};
// ---- verdef helpers ----------------------------------------------------
// vdnameat — walk verdef records and return the name (as *u8 into
// the .so's verstr buffer) for the entry whose vd_ndx == ndx. The name
// is the first Verdaux's vda_name (subsequent auxes are predecessor
@@ -176,8 +163,6 @@ fn vdnameat(buf: *u8, verdefoff: u64, verdefsize: u64,
return nil;
};
// ---- entry points ------------------------------------------------------
export fn loadso(l: *lnk, path: *u8) i32 = {
let buf: *u8;
let blen: u64;
@@ -207,7 +192,6 @@ export fn loadso(l: *lnk, path: *u8) i32 = {
if (shoff == 0u64) { return soerr("stripped .so unsupported"); };
if (shnum == 0u32) { return soerr("stripped .so unsupported"); };
// Locate the four sections we care about.
let idxdynsym: i32 = -1;
let idxdynamic: i32 = -1;
let idxversym: i32 = -1;
@@ -292,8 +276,8 @@ export fn loadso(l: *lnk, path: *u8) i32 = {
verstr = buf + vstroff;
};
// Build the lso. Exports are appended in dynsym order so
// soprovides_v's first-match semantics match the C version.
// Exports are appended in dynsym order so soprovides_v's
// first-match semantics match the C version.
let so: *lso = alloc(lso { path = dcstrtostr(path), soname = dcstrtostr(sonamecs) })!;
let tail: *lexport = nil;
@@ -371,7 +355,6 @@ fn soerr(msg: str) i32 = {
return -1;
};
// soprovides — 1 if so exports name, 0 otherwise.
export fn soprovides(so: *lso, name: str) i32 = {
if (so == nil) { return 0; };
let e: *lexport = so.exports;

View File

@@ -1,4 +1,4 @@
// selfhost/cmd/w6l/dynout.ww — port of cmd/w6l/dynout.c.
// Port of cmd/w6l/dynout.c.
//
// Emit a dynamic-linked ELF executable. The shape is the simplest
// valid one: PT_INTERP + PT_DYNAMIC + DT_BIND_NOW so the loader
@@ -29,7 +29,6 @@ import rt;
import strings;
import sym;
// ELF constants
def ET_EXEC_D: u16 = 2u16;
def EM_X86_64_D: u16 = 62u16;
def EV_CURRENT_D: u32 = 1u32;
@@ -75,8 +74,6 @@ def PAGE: u64 = 4096u64;
def INTERP: str = "/lib64/ld-linux-x86-64.so.2";
// ---- byte writers ------------------------------------------------------
fn dwr8(buf: *u8, off: u64, v: u8) void = {
buf[off] = v;
};
@@ -106,8 +103,6 @@ fn dwri32(buf: *u8, off: u64, v: i32) void = {
dwr32(buf, off, v: u32);
};
// ---- byte readers ------------------------------------------------------
fn drdu16(p: *u8, off: u64) u16 = {
let b0: u16 = p[off]: u16;
let b1: u16 = p[off + 1u64]: u16;
@@ -153,8 +148,6 @@ fn alignup(off: u64, a: u64) u64 = {
return (off + a - 1u64) & ~(a - 1u64);
};
// ---- main entry --------------------------------------------------------
export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
// .data shares the R+W PT_LOAD with .got.plt and .dynamic.
// Placed after .dynamic so the segment is one contiguous run;
@@ -163,7 +156,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
let n: i32 = l.dynn;
let nu: u64 = n: u64;
// ---- collect dyn syms into a plt_idx-indexed array ----
let dynsyms: []*lsym = alloc([], nu)!;
let s: *lsym = l.syms;
for (s != nil) {
@@ -185,7 +177,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
i += 1;
};
// ---- collect used .so's (in l.sos order) ----
// Used .so's are collected in l.sos order.
let maxsos: i32 = 0;
let so: *lso = l.sos;
for (so != nil) { maxsos += 1; so = so.sonext; };
@@ -208,7 +200,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
so = so.sonext;
};
// ---- build flat version table grouped by vlib ----
// Flat version table grouped by vlib:
// vlib_sos_idx[k] = sos_used index for vlib k.
// vlib_first[k] = ver index of first version under vlib k.
// vlib_count[k] = number of versions under vlib k.
@@ -301,7 +293,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
vi += 1;
};
// ---- compute dynstr size ----
let dynstrsz: u64 = 1u64; // leading NUL
let pi: i32 = 0;
for (pi < nsos) {
@@ -325,7 +316,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
pi += 1;
};
// ---- fill dynstr ----
let dynstr: []u8 = alloc([], dynstrsz)!;
let dynstrpos: u64 = 1u64; // past leading NUL
@@ -365,7 +355,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
pi += 1;
};
// ---- per-dyn-sym versym index ----
let versymfor: []u8 = alloc([], nu * 2u64)!;
pi = 0;
for (pi < n) {
@@ -400,7 +389,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
pi += 1;
};
// ---- compute byte sizes ----
let ehdrsz: u64 = 64u64;
let nphdrs: u64 = 4u64;
let phdrsz: u64 = nphdrs * 56u64;
@@ -433,7 +421,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
let ndyn: u64 = (nsos: u64) + 11u64 + extra;
let dynamicsz: u64 = ndyn * 16u64;
// ---- compute file offsets ----
let off: u64 = ehdrsz + phdrsz;
let interpoff: u64 = off; off += interpsz;
off = alignup(off, 8u64);
@@ -486,7 +473,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
let datafilelen: u64 = l.datalen - bsslen;
let filedataend: u64 = dataoff + datafilelen;
// ---- build .dynsym ----
let dynsymbuf: []u8 = alloc([], dynsymsz)!;
pi = 0;
for (pi < n) {
@@ -500,7 +486,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
pi += 1;
};
// ---- build .hash (SysV, 1 bucket) ----
// .hash is SysV with a single bucket.
let hashbuf: []u8 = alloc([], hashsz)!;
dwr32(hashbuf.ptr, 0u64, nbuckets);
dwr32(hashbuf.ptr, 4u64, nchain);
@@ -515,7 +501,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
ci += 1u64;
};
// ---- build .rela.plt ----
let relapltbuf: []u8 = alloc([], relapltsz)!;
pi = 0;
for (pi < n) {
@@ -527,7 +512,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
pi += 1;
};
// ---- build .gnu.version (u16 per dynsym entry) ----
// .gnu.version is one u16 per dynsym entry.
let versymbuf: []u8 = alloc([], versymsz)!;
dwr16(versymbuf.ptr, 0u64, VER_NDX_LOCAL_D);
pi = 0;
@@ -536,7 +521,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
pi += 1;
};
// ---- build .gnu.version_r ----
let verneedbuf: []u8 = alloc([], verneedsz)!;
if (verneedsz > 0u64) {
let vnoff: u64 = 0u64;
@@ -575,7 +559,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
};
};
// ---- build .plt ----
let pltbuf: []u8 = alloc([], pltsz)!;
pi = 0;
for (pi < n) {
@@ -590,11 +573,9 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
pi += 1;
};
// ---- build .got.plt ----
let gotpltbuf: []u8 = alloc([], gotpltsz)!;
dwr64(gotpltbuf.ptr, 0u64, dynamicva);
// ---- build .dynamic ----
let dynamicbuf: []u8 = alloc([], dynamicsz)!;
let dk: u64 = 0u64;
pi = 0;
@@ -625,7 +606,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
return 1;
};
// ---- patch .text relocs targeting dynamic syms ----
let r: *lrel = l.rels;
for (r != nil) {
if (r.sym != nil) {
@@ -646,7 +626,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
r = r.rnext;
};
// ---- assemble file buffer ----
let filebuf: []u8 = alloc([], fileend)!;
filebuf.len = fileend: i32;
@@ -721,7 +700,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
dwr64(filebuf.ptr, p3 + 40u64, dynamicsz);
dwr64(filebuf.ptr, p3 + 48u64, 8u64);
// Sections.
dbcopy(filebuf.ptr, interpoff, INTERP.ptr, INTERP.len: u64);
dwr8(filebuf.ptr, interpoff + (INTERP.len: u64), 0u8);
dbcopy(filebuf.ptr, dynstroff, dynstr.ptr, dynstrsz);

View File

@@ -1,8 +1,4 @@
// selfhost/cmd/w6l/main.ww — port of cmd/w6l/main.c.
//
// w6l = amd64 linker. Reads relocatable ELF .o files, SysV `ar`
// archives, and shared objects (ET_DYN). Resolves symbols, applies
// relocations, writes a static or dynamic-linked ELF executable.
// Port of cmd/w6l/main.c.
//
// w6l_ww -o out [-L<dir>...] [-l<name>...] file1.o file2.o ...
@@ -25,10 +21,6 @@ fn mklnk() *lnk = {
return l;
};
// `cstreq` lives in obj.ww — same bundle, single definition.
// `cstrlen` lives in obj.ww — same bundle, single definition.
// Build "<dir>/lib<name>.<ext>" into dst (NUL-terminated). Returns total
// length excluding NUL. dst must be large enough.
fn buildpath(dst: *u8, dir: *u8, name: *u8, ext: str) u64 = {
@@ -274,8 +266,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
k += 1;
};
// Then resolve -l flags and load each. Archives append; shared
// objects register their exports.
// Archives append; shared objects register their exports.
let lf: i32 = 0;
for (lf < nlflags) {
let p: *u8 = resolvelib(lflags[lf], libdirs.ptr, nlibdirs);

View File

@@ -1,12 +1,4 @@
// selfhost/cmd/w6l/obj.ww — port of cmd/w6l/obj.c.
//
// Loads relocatable ELF64 .o files emitted by w6a, appends .text to
// the combined image, and pulls in symbols + relocations with
// offsets adjusted to the combined section.
//
// Also handles SysV `ar` archives (libwwrt.a). The two-pass loader
// indexes members on the first pass and iteratively pulls members
// that define currently-undefined symbols on subsequent passes.
// Port of cmd/w6l/obj.c.
package w6l;
@@ -22,7 +14,6 @@ def SHT_SYMTAB: i32 = 2;
def SHT_STRTAB: i32 = 3;
def SHT_RELA: i32 = 4;
// ---- little-endian byte readers ----------------------------------------
// w6a/w6l use straight LE on amd64. Reading via byte offsets keeps us off
// the cgen's u16 field-load story for now (MOVZBQ exists; MOVZWQ doesn't).
@@ -46,7 +37,6 @@ fn rdu64(p: *u8, off: u64) u64 = {
return lo | (hi << 32u64);
};
// ---- ELF64 section header offsets (40 bytes total) --------------------
def SHDR_SIZE: u64 = 64u64; // sizeof(Shdr) per ELF64 spec
def SHDR_NAME: u64 = 0u64;
def SHDR_TYPE: u64 = 4u64;
@@ -76,8 +66,6 @@ def RELA_OFFSET: u64 = 0u64;
def RELA_INFO: u64 = 8u64;
def RELA_ADDEND: u64 = 16u64;
// ---- file slurp --------------------------------------------------------
fn slurp(path: *u8) (*u8, u64) = {
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
if (fd < 0) { return nil, 0u64; };
@@ -100,8 +88,6 @@ fn slurp(path: *u8) (*u8, u64) = {
return buf.ptr, n: u64;
};
// ---- text buffer growth ------------------------------------------------
fn emittext(l: *lnk, src: *u8, n: u64) void = {
if (l.textlen + n > l.textcap) {
let nc: u64 = l.textcap;
@@ -149,17 +135,13 @@ fn emitdata(l: *lnk, src: *u8, n: u64) void = {
l.datalen += n;
};
// ---- C-string helpers --------------------------------------------------
fn cstrlen(p: *u8) u64 = {
let n: u64 = 0u64;
for (p[n] != 0u8) { n += 1u64; };
return n;
};
// pathstr — view a NUL-terminated *u8 as a str. Bridges argv-style
// callers to lib/os entrypoints (str post-task-#23). Shared with
// main.ww and dyn.ww via the w6l bundle.
// Bridges argv-style callers to lib/os entrypoints (str post-task-#23).
fn pathstr(p: *u8) str = {
let r: str;
r.ptr = p;
@@ -179,7 +161,6 @@ fn cstreq(p: *u8, lit: str) bool = {
return true;
};
// Build a ww str from a NUL-terminated *u8 (for passing to intern).
fn cstrtostr(p: *u8) str = {
let n: u64 = cstrlen(p);
let view: str;
@@ -188,8 +169,6 @@ fn cstrtostr(p: *u8) str = {
return strings.dup(view);
};
// ---- archive (SysV ar) types and helpers -------------------------------
//
// Each archive member starts with a 60-byte ar_hdr. The fields we care
// about are the first byte (member type) and the size at offset 48 (a
// 10-byte, space-padded decimal). Member bodies are 2-byte aligned.
@@ -231,9 +210,8 @@ fn arfield(p: *u8, n: u64) u64 = {
return v;
};
// elfglobals — return a linked list of names of globally-defined
// (STB_GLOBAL) symbols whose section is `.text`. Names are owned
// heap copies, so the source ELF buffer can be freed afterward.
// Names are owned heap copies, so the source ELF buffer can be freed
// afterward.
fn elfglobals(buf: *u8, len: u64) *defent = {
if (len < EHDR_SIZE) { return nil; };
if (buf[0u64] != 127u8) { return nil; };
@@ -310,9 +288,8 @@ fn elfglobals(buf: *u8, len: u64) *defent = {
return head;
};
// memberdefinesundef — true if any of m's defined globals matches a
// currently-undefined symbol in the linker's symbol table. Names not
// already interned are uninteresting (the link doesn't need them yet).
// Names not already interned are uninteresting (the link doesn't need
// them yet).
fn memberdefinesundef(l: *lnk, m: *armember) bool = {
let de: *defent = m.defs;
for (de != nil) {
@@ -406,8 +383,6 @@ fn loadarchive(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
return 0;
};
// ---- main loader -------------------------------------------------------
export fn load(l: *lnk, path: *u8) i32 = {
let bufp: *u8;
let buflen: u64;
@@ -441,7 +416,6 @@ 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, .data, .symtab, .rela.text, .rela.data
let idxtext: i32 = -1;
let idxdata: i32 = -1;
let idxsymtab: i32 = -1;
@@ -497,7 +471,6 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
datasize = rdu64(buf, datash + SHDR_SIZE_F);
};
// Track this object.
let ob: *lobj = alloc(lobj {
path = cstrtostr(path),
buf = buf,
@@ -510,18 +483,16 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
})!;
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
// than dancing around the cgen's u64-shift gaps.
// We don't keep a per-object map[] of *lsym. Instead the reloc
// loop re-walks symtab and re-interns by name. Simpler than
// dancing around the cgen's u64-shift gaps.
let si: u64 = 1u64; // skip index 0 (always undef sentinel)
for (si < nsyms) {
let symp: u64 = symoff + si * SYM_SIZE;
@@ -569,7 +540,6 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
si += 1u64;
};
// Per-object relocation collection.
if (idxrela >= 0) {
let relash: u64 = shoff + (idxrela: u64) * SHDR_SIZE;
let relaoff: u64 = rdu64(buf, relash + SHDR_OFFSET);
@@ -590,7 +560,6 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
addend = raddend: i64,
rnext = l.rels,
})!;
// Look up the referenced sym by name (re-walk symtab).
if ((rsymidx: u64) < nsyms) {
let sp: u64 = symoff + (rsymidx: u64) * SYM_SIZE;
let sname: u32 = rdu32(buf, sp + SYM_NAME);
@@ -605,8 +574,8 @@ 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.
// Offsets land in .data, shifted by this object's dataoff 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);

View File

@@ -1,11 +1,4 @@
// selfhost/cmd/w6l/out.ww — port of cmd/w6l/out.c.
//
// Emit a static ELF64 executable. File layout (per the C original):
// [0..64) Ehdr
// [64..120) Phdr (one PT_LOAD)
// [120..0x1000) zero pad
// [0x1000..) .text bytes
// Single PT_LOAD covers the whole file, R+X. No interpreter, no .bss.
// Port of cmd/w6l/out.c.
package w6l;
@@ -27,8 +20,6 @@ def PF_R: u32 = 4u32;
def TEXT_OFF: u64 = 4096u64; // 0x1000
def PAGE_SZ: u64 = 4096u64;
// ---- little-endian byte writers ----------------------------------------
fn wru16(buf: *u8, off: u64, v: u16) void = {
buf[off] = (v & 255u16): u8;
buf[off + 1u64] = ((v >> 8u16) & 255u16): u8;
@@ -46,8 +37,6 @@ fn wru64(buf: *u8, off: u64, v: u64) void = {
wru32(buf, off + 4u64, ((v >> 32u64) & 4294967295u64): u32);
};
// ---- emit ---------------------------------------------------------------
export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
// Dispatch: any loaded shared object plus any dynamic ref means
// we owe the loader a real PT_INTERP/PT_DYNAMIC binary.
@@ -92,7 +81,7 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
let hdr: []u8 = alloc([], TEXT_OFF)!;
hdr.len = TEXT_OFF: i32;
// --- Ehdr (64 bytes) ---
// Ehdr (64 bytes).
hdr[0u64] = 127u8; // 0x7f
hdr[1u64] = 'E';
hdr[2u64] = 'L';
@@ -115,7 +104,7 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
wru16(hdr.ptr, 60u64, 0u16); // e_shnum
wru16(hdr.ptr, 62u64, 0u16); // e_shstrndx
// --- Phdr #1 (R+X) at offset 64 ---
// Phdr #1 (R+X) at offset 64.
wru32(hdr.ptr, 64u64, PT_LOAD);
wru32(hdr.ptr, 68u64, PF_R | PF_X);
wru64(hdr.ptr, 72u64, 0u64); // p_offset
@@ -126,7 +115,7 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
wru64(hdr.ptr, 112u64, TEXT_OFF); // p_align
if (hasdata) {
// --- Phdr #2 (R+W) at offset 64+56=120 ---
// Phdr #2 (R+W) at offset 64+56=120.
wru32(hdr.ptr, 120u64, PT_LOAD);
wru32(hdr.ptr, 124u64, PF_R | PF_W);
wru64(hdr.ptr, 128u64, dataoff); // p_offset
@@ -137,7 +126,6 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
wru64(hdr.ptr, 168u64, PAGE_SZ); // p_align
};
// Write [0..0x1000) then .text.
let r1: (i64 | os.oserror) = os.writeall(fd, hdr.ptr, TEXT_OFF);
let n1: i64 = 0i64;
match (r1) {

View File

@@ -1,11 +1,4 @@
// selfhost/cmd/w6l/pass.ww — port of cmd/w6l/pass.c.
//
// Resolution + relocation. l_resolve flags every undefined symbol
// 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.
// Port of cmd/w6l/pass.c.
//
// Supported relocation kinds: PC32 (=2), PLT32 (=4); both are 32-bit
// PC-relative displacements (PLT32 == PC32 for static).

View File

@@ -1,7 +1,7 @@
// selfhost/cmd/w6l/sym.ww — port of cmd/w6l/sym.c.
// Port of cmd/w6l/sym.c.
//
// Linker symbol table. Singly-linked list, usually a few hundred
// entries; hashing isn't worth it yet.
// Singly-linked list, usually a few hundred entries; hashing isn't
// worth it yet.
package w6l;

View File

@@ -1,26 +1,4 @@
// selfhost/cmd/wcc/cgen.ww — port of cmd/w6c/cgen.c.
//
// Status: GROWING. Each subsystem we add is verified by `wwdump_ww -c`
// producing byte-identical output to C-side `w6c` for the same source,
// then by assembling + linking + running the result.
//
// Current coverage:
// - decls: nkind.N_FILE, nkind.N_FNDECL (params, frame for locals, prologue
// + dual-epilogue suppression; FFI body-less fn skipped)
// - stmts: nkind.N_BLOCK, nkind.N_RETURN, nkind.N_EXPRSTMT, nkind.N_LET (no init),
// nkind.N_LET (int-literal / ident / call / nkind.N_BIN init),
// nkind.N_IF (with optional else), nkind.N_FOR (cond-only and full
// init/cond/post), nkind.N_BREAK, nkind.N_CONTINUE
// - exprs: nkind.N_INTLIT, nkind.N_IDENT (local/param), nkind.N_BIN with full op
// coverage (+/-/*/// %, &/|/^, <</>>, comparisons with
// signed-vs-unsigned dispatch, &&/||), nkind.N_UN (- ! ~ &amp; *),
// nkind.N_CALL (recursive R-to-L push, pop into argregs L-to-R),
// nkind.N_ASSIGN to local idents (plain and compound +=/-=)
//
// Type info is shallow — frame slots are 8 bytes per local, all loads
// /stores are MOVQ. Programs that mix i8/i32/i64 locals work but spill
// 8 bytes per local. Float, str, slice, struct, match, defer, alloc,
// tagged-union return — none of those are wired yet.
// Port of cmd/w6c/cgen.c.
package wcc;
@@ -38,12 +16,8 @@ import cgenexpr;
import cgenstmt;
import cgendecl;
// ---- typedef alias registry -----------------------------------------
//
// `type error = str;` makes `error` a struct-shape alias. We track
// alias→target so isstrtype / isslicetype / structlookup can
// resolve through the chain. Only direct nkind.N_TNAME aliases are mapped;
// `type p = struct {...}` is handled by collectstructs.
// Only direct nkind.N_TNAME aliases are mapped; `type p = struct {...}`
// is handled by collectstructs.
type aliasent = struct {
aname: str,
@@ -156,12 +130,9 @@ fn aliassamemod(c: *cgen, name: str) *syntax.node = {
return nil;
};
// ---- enum registry --------------------------------------------------
//
// Mirrors cmd/wcc/check.c's enum resolution at collect time: walk
// every `type Foo = enum [storage] { ... }`, pre-compute each
// member's u64 value (supporting auto-increment and sibling refs),
// and stash them so cgdot can fold `Foo.MEMBER` → MOVQ $value, AX.
// Enum member values are pre-computed at collect time (auto-increment
// + sibling refs) so cgdot can fold `Foo.MEMBER` → MOVQ $value, AX.
// Mirrors cmd/wcc/check.c's enum resolution.
// foldintliteral — fold the literal subset usable for top-level
// constant slots: int/rune literal, true/false/nil, and a unary
@@ -376,13 +347,6 @@ fn resolvetype(c: *cgen, t: *syntax.node) *syntax.node = {
return cur;
};
// ---- struct registry ------------------------------------------------
//
// Per-file map from struct name → list of fields with computed offsets
// and sizes. Built when cgfile walks nkind.N_TYPEDECL with nkind.N_TSTRUCT lhs.
// nkind.N_DOT and nkind.N_ASSIGN consult this to resolve `s.field` for struct or
// *struct bases.
type fieldinfo = struct {
fname: str,
foff: i32,
@@ -399,8 +363,6 @@ type structinfo = struct {
sinext: *structinfo,
};
// ---- locals / frame --------------------------------------------------
type local = struct {
name: str,
off: i32,
@@ -764,8 +726,6 @@ fn localfind(c: *cgen, name: str) i32 = {
return 0;
};
// ---- emit helpers ---------------------------------------------------
// Cgfn defers its prologue (TEXT / SUBQ) until after the body so the
// frame size reflects every emit-time localadd — the scanlocals pre-
// pass that previously pre-computed it was dropped per #15/#26c. The
@@ -942,10 +902,6 @@ fn mkscratchname(c: *cgen, prefix: str) str = {
return r;
};
// ---- string interning ------------------------------------------------
//
// streq is provided by sym.ww and reused here.
// internstrlit — return a stable label for `bytes`. Dedups by content
// so identical literals share storage.
fn internstrlit(c: *cgen, bytes: str) str = {
@@ -1008,8 +964,6 @@ fn letscalarprim(nm: str) bool = {
return false;
};
// letfloatprim — float type-name keywords. f32 → 4B slot, f64 → 8B.
// Returns the slot size or 0 if not a float type.
fn letfloatprim(nm: str) i32 = {
if (syntax.streq(nm, "f32")) { return 4; };
if (syntax.streq(nm, "f64")) { return 8; };
@@ -1019,11 +973,6 @@ fn letfloatprim(nm: str) i32 = {
// letemitsize — slot size in bytes for a top-level `let`, or 0 if
// the type isn't yet supported as a writable global. Walks type
// aliases so byte output matches C cgen, which resolves Type kinds.
// 4 → f32 (literal init supported)
// 8 → scalar or f64 (literal init supported)
// 16 → str (only zero-init / nil / "" supported)
// 24 → slice (only zero-init supported)
// varies → struct (zero-init only; field reads/scalar-field writes)
fn letemitsize(c: *cgen, d: *syntax.node) i32 = {
if (d == nil) { return 0; };
let t: *syntax.node = d.lhs;
@@ -2024,7 +1973,6 @@ fn emitarraylitbytes(c: *cgen, arrt: *syntax.tinfo, rhs: *syntax.node,
};
if (eu != nil && eu.kind == syntax.tykind.TY_STRUCT) {
// Validate: every element must be N_STRUCTLIT (after N_CAST).
let idx: i32 = 0;
let last_ev: *syntax.node = nil;
let e: *syntax.node = rhs.list;
@@ -2113,7 +2061,6 @@ fn emitarraylitbytes(c: *cgen, arrt: *syntax.tinfo, rhs: *syntax.node,
if (syntax.typeisfloat(au.sub)) {
let isf32: bool = syntax.typeisf32(au.sub);
// Validate.
let idx: i32 = 0;
let e: *syntax.node = rhs.list;
for (e != nil && idx < alen) {
@@ -2211,7 +2158,6 @@ fn emitarraylitbytes(c: *cgen, arrt: *syntax.tinfo, rhs: *syntax.node,
let e: *syntax.node = rhs.list;
let last: u64 = 0u64;
let repeat: bool = false;
// Validate first.
for (e != nil && idx < alen) {
if (e.kind == syntax.nkind.N_FIELD) {
if (syntax.streq(e.str, "...")) { repeat = true; break; };
@@ -2551,7 +2497,6 @@ fn emitslicedata(c: *cgen, name: str, module: str, slt: *syntax.tinfo,
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
// Writable backing data.
emitline("DATAW ");
emitfnname(c, name, module);
emitline(".d(SB),\"");
@@ -3494,12 +3439,9 @@ fn emitdatasection(c: *cgen) void = {
};
};
// ---- fn return-type map ---------------------------------------------
//
// Per-file: ident → ret-type-node. Used to decide whether to shuffle
// (AX, DX) → (AX, BX) after a CALL — needed for str-returning fns so
// the value flows through cgen as the canonical (AX, BX) str pair.
// fnret decides whether to shuffle (AX, DX) → (AX, BX) after a CALL —
// needed for str-returning fns so the value flows through cgen as the
// canonical (AX, BX) str pair.
type fnret = struct {
fname: str,
fmod: str,
@@ -3670,12 +3612,8 @@ fn fnparamslookupmod(c: *cgen, name: str, mod: str) *syntax.node = {
return fnparamslookup(c, name);
};
// ---- def-constant registry ------------------------------------------
//
// `def NAME: T = LIT;` becomes a DATA symbol the C-side w6c emits; an
// ident reference loads it via `MOVQ NAME(SB), AX`. We collect them at
// file load and consult on nkind.N_IDENT lookup.
// ident reference loads it via `MOVQ NAME(SB), AX`.
type defent = struct {
dname: str,
dmod: str, // originating module (`// MODULE: foo`), or empty
@@ -3819,8 +3757,6 @@ fn defisaddressable(c: *cgen, opnd: *syntax.node) bool = {
return false;
};
// ---- module-private symbol map --------------------------------------
//
// Every non-FFI top-level fn decl lives in its module's namespace —
// cgen mangles the leaf to `<module>.<name>` at the def site (TEXT)
// and at every call/load site, so cross-module same-leaf fns (lib/os
@@ -4046,8 +3982,6 @@ fn emitfnname(c: *cgen, ident: str, hint: str) void = {
emitbytes( ident.ptr, ident.len: u64);
};
// ---- FFI map ---------------------------------------------------------
fn fficollect(c: *cgen, file: *syntax.node) void = {
c.ffis = nil;
if (file == nil) { return; };
@@ -4085,8 +4019,6 @@ fn ffiresolve(c: *cgen, ident: str) str = {
return ident;
};
// ---- ABI argreg helpers ---------------------------------------------
fn argregname(i: i32) str = {
if (i == 0) { return "DI"; };
if (i == 1) { return "SI"; };

View File

@@ -1,24 +1,9 @@
// selfhost/cmd/wcc/cgendecl.ww — split out of cgen.ww.
//
// Houses the top-level emission glue:
// - cgfnparams: parameter spilling per SysV
// - cgfn: fn body emit (TEXT/SUBQ patched after body), prologue
// deferred via cgen.ww's cgoutstate so the frame size
// reflects every emit-time localadd (#15/#26c)
// - cgfile: file-level entry (the exported driver)
//
// Bundler pulls this in transitively via cgen.ww; consumers don't
// need to `use cgendecl;` directly.
package wcc;
import os;
import syntax;
import strconv;
// ---- function-level cgen ---------------------------------------------
fn cgfnparams(c: *cgen, params: *syntax.node) void = {
let p: *syntax.node = params;
// sret (#23): RDI is consumed by the hidden dest pointer
@@ -580,8 +565,6 @@ fn cgfn(c: *cgen, fn_: *syntax.node) void = {
};
if (c.lastwasreturn == 0) {
// Run any registered defers in LIFO order before the
// implicit return.
rundefers(c);
// Zero AX before the fall-through return — matches cstage,
// which always emits this so void-returning fns don't leak
@@ -629,8 +612,6 @@ fn cgfn(c: *cgen, fn_: *syntax.node) void = {
cgout_flush();
};
// ---- file-level entry ------------------------------------------------
export fn cgfile(c: *cgen, file: *syntax.node) void = {
if (file == nil) { return; };
c.strlits = nil;

View File

@@ -1,10 +1,5 @@
// selfhost/cmd/wcc/cgenexpr.ww — split out of cgen.ww.
//
// cgexpr is a thin dispatcher over n.kind; each non-trivial branch
// lives in a per-kind helper (cgstrlit, cgident, cgindex, cgmatch,
// cgdot, cgun, cgbin, cgcall, cgassign). Trivial literal loads
// (nkind.N_INTLIT, nkind.N_RUNELIT, nkind.N_TRUE/FALSE/NIL, nkind.N_CAST) stay inline.
//
// The remainder of cgen lives in cgen.ww (foundation: types, emit
// primitives, the collect* tables, FFI/module maps) and cgenstmt.ww
// (cgstmt).
@@ -683,9 +678,6 @@ fn cgtryunw(c: *cgen, n: *syntax.node) void = {
};
fn cgtypetest(c: *cgen, n: *syntax.node) void = {
// `e is T` — load the lhs's tag, compare against T's variant
// index, set AX = (tag == idx). Result type is bool.
//
// Slot resolution is inlined (rather than factored into a helper
// with output parameters): wwstage cgen has a trap with i32
// stored via *i32 in this context — direct assignment of the
@@ -1077,9 +1069,6 @@ fn cgcast(c: *cgen, n: *syntax.node) void = {
else { if (lk == syntax.nkind.N_TENUM) { leaf_tn = leaf_tn.lhs; }
else { if (lk == syntax.nkind.N_TNAME) {
let lnm: str = leaf_tn.str;
// This is an alias chase loop
// — primsize is the leaf-primitive break test the loop
// wraps (aliaslookup advances the cursor on a miss).
if (primsize(lnm) > 0) { break; };
let lal: *syntax.node = aliaslookup(c, lnm);
if (lal == nil) { leaf_tn = nil; }
@@ -2062,9 +2051,6 @@ fn cgplaceaddr(c: *cgen, n: *syntax.node, dstreg: str) bool = {
};
fn cgindex(c: *cgen, n: *syntax.node) void = {
// Element-size-aware load: u8 → MOVZBQ, i32 → MOVSXD, u32 → MOVL,
// str → (ptr, len) into (AX, BX), everything else → MOVQ. Fast
// path when the base is a bare ident (mem.ww shape).
let base: *syntax.node = n.lhs;
let idx: *syntax.node = n.rhs;
// Direct non-ident index bases that match none of the typed arms
@@ -2512,7 +2498,6 @@ fn cgindex(c: *cgen, n: *syntax.node) void = {
emitline("\t(BX), AX\n");
return;
};
// Generic fallback when base isn't a plain ident.
// #135: N_DOT base on `[N]T` field needs the field's ADDRESS,
// not its value. cgexpr would auto-deref + load the 8-byte value
// as if it were a pointer. dotbaseaddr emits the address inline.
@@ -2866,7 +2851,6 @@ fn cgslice(c: *cgen, n: *syntax.node) void = {
let es60: *syntax.tinfo = tichase(bu60.sub);
if (es60 != nil) { esz = es60.size: i32; };
};
// base address
if (baselocal != nil) {
let tn: *syntax.node = baselocal.tnode;
let isarray: bool = false;
@@ -2936,11 +2920,9 @@ fn cgslice(c: *cgen, n: *syntax.node) void = {
};
};};};};
emitline("\tPUSHQ\tAX\n");
// lo (default 0)
if (lo != nil) { cgexpr(c, lo); }
else { emitline("\tMOVQ\t$0, AX\n"); };
emitline("\tPUSHQ\tAX\n");
// hi (default base length)
if (hi != nil) {
cgexpr(c, hi);
} else { if (baselocal != nil) {
@@ -3140,8 +3122,6 @@ fn matcharmwant(c: *cgen, scrutt: *syntax.node, pat: *syntax.node) i32 = {
};
fn cgmatch(c: *cgen, n: *syntax.node) void = {
// match (e) { case let v: T => stmt; ... }
//
// Read the tagged-union slot and dispatch by tag. Slot
// layout: [+0]=tag, [+8]=value0, [+16]=value1. Bindings
// (`case let v: T =>`) get a fresh local slot loaded from
@@ -3415,7 +3395,6 @@ fn cgmatch(c: *cgen, n: *syntax.node) void = {
};
};
};
// Bind `let v: T` from the slot, if requested.
let bn: str = cs.str;
if (bn.len > 0) {
if (pat != nil) {
@@ -3468,7 +3447,6 @@ fn cgmatch(c: *cgen, n: *syntax.node) void = {
};
};
};
// Body. Match arms are statements; we cgstmt them.
if (cs.body != nil) { cgstmt(c, cs.body); };
// Restore the locals head — pop everything the arm pushed
// so post-match code resolves names to their original (outer)
@@ -5299,10 +5277,6 @@ fn cgdot(c: *cgen, n: *syntax.node) void = {
};
fn cgun(c: *cgen, n: *syntax.node) void = {
// Match C cgen ordering: evaluate operand first (load into AX),
// then apply the unary op. AMP / STAR override AX with the
// address / deref. The wasted load before AMP keeps our asm
// byte-identical to the C version.
let fk: i32 = 0;
if (n.lhs != nil) {
let lt: *syntax.tinfo = n.lhs.type_: *syntax.tinfo;

View File

@@ -1,21 +1,9 @@
// selfhost/cmd/wcc/cgenstmt.ww — split out of cgen.ww.
//
// cgstmt is a thin dispatcher over n.kind; each branch defers to a
// per-kind helper: cgblock, cgreturn, cgexprstmt, cglet, cgif, cgfor,
// cgmassign, cgbreak, cgcontinue.
//
// The expression generator (cgexpr) lives in cgenexpr.ww; the
// foundation (types, emit primitives, collect* tables, FFI/module
// maps) lives in cgen.ww.
package wcc;
import os;
import syntax;
import strconv;
// ---- statement cgen --------------------------------------------------
fn cgstmt(c: *cgen, n: *syntax.node) void = {
if (n == nil) { return; };
let k: syntax.nkind = n.kind;
@@ -64,9 +52,8 @@ fn cgstmt(c: *cgen, n: *syntax.node) void = {
};
fn cgyield(c: *cgen, n: *syntax.node) void = {
// Evaluate the value into AX (and BX for str), then JMP to the
// enclosing match's end label. Falls through silently if there
// is no active match — should be a checker error eventually.
// Falls through silently if there is no active match — should be
// a checker error eventually.
if (n.lhs != nil) { cgexpr(c, n.lhs); };
if (c.yieldtop > 0) {
let tgt: str = c.yieldbuf[c.yieldtop - 1];
@@ -100,8 +87,6 @@ fn cgblock(c: *cgen, n: *syntax.node) void = {
return;
};
// rundefers — emit cgexpr for every queued defer in LIFO order.
// Called from cgreturn and the cgfn implicit-return path.
fn rundefers(c: *cgen) void = {
let i: i32 = c.defertop - 1;
for (i >= 0) {
@@ -3178,11 +3163,6 @@ fn cgfor(c: *cgen, n: *syntax.node) void = {
return;
};
// Tuple-destructure assign: `a, b = call();`. The call's tuple
// return lands in (AX, DX); push DX to free it, store AX into
// the first lvalue, then pop DX into the second. Mirrors
// cmd/w6c/cgen.c:2424-2440. Lvalues beyond two are dropped (same
// as C — no fixture uses >2 today).
fn cgmassign(c: *cgen, n: *syntax.node) void = {
// #83: positional per-element destructure REASSIGN. Same cursor as
// cgmlet (and cgreturn; harec create_unpack_bindings,
@@ -3394,17 +3374,6 @@ fn cgmassign(c: *cgen, n: *syntax.node) void = {
return;
};
// Multi-let from a tuple-returning call: `let n, s = call();` or
// `let (n, s) = call();`. wwstage has no checker, so each binding's
// type is taken from its explicit annotation (l.lhs) when present
// or inferred from the called fn's return-type tuple element.
//
// Per the AX:DX:CX:R8 return convention (mirrors C cgen nkind.N_MLET):
// (scalar, scalar) — AX → l0, DX → l1.
// (scalar, str) — AX → scalar slot, (DX, CX, R8) → str slot
// as (.ptr, .len, .cap). Position-agnostic — the
// regs are routed by element type, not by AX/DX.
// str IS []u8 (24B): cap rides R8 (#1/Phase 3, task #5).
fn cgmlet(c: *cgen, n: *syntax.node) void = {
let rhs: *syntax.node = n.rhs;
if (rhs == nil) { return; };
@@ -3958,7 +3927,6 @@ fn cgforrange(c: *cgen, n: *syntax.node) void = {
nbinds = 1;
};
// init: ioff(BP) = 0
emitline("\tMOVQ\t$0, ");
emitoff(ioff: i64);
emitline("(BP)\n");
@@ -4290,5 +4258,3 @@ fn cgcontinue(c: *cgen, n: *syntax.node) void = {
c.lastwasreturn = 0;
return;
};

View File

@@ -1,14 +1,3 @@
// selfhost/cmd/wcc/cgenutil.ww — split out of cgen.ww.
//
// General helpers used across cgenexpr / cgenstmt / cgendecl:
// - pushargsrev: per-call arg pushing
// - type predicates: isstr*/isslice*/istagged*/nodeis* families
// - field ops: fieldloadop, fieldstoreop
// - index helpers: elemsizeof, elemsizeofc
// - slot sizing: structlookup, primsize, slotsize, fieldsize,
// registerstruct, collectstructs
// - rhs helpers: taggedvariantindex
//
// Bundler pulls this in transitively via cgen.ww; consumers don't
// need to `use cgenutil;` directly.
@@ -18,8 +7,6 @@ import os;
import syntax;
import strconv;
// ---- variadic-call helpers (Hare-style `T...` param) -----------------
// slicewrap — synthesise an N_TSLICE node wrapping the given element
// type AST. Used by the Hare-style variadic path so the local entry
// for the param (callee side) and the call-site slice descriptor
@@ -263,8 +250,6 @@ fn calleecvariadic(c: *cgen, callee: *syntax.node, nfixed_out: *i32) bool = {
return false;
};
// ---- expression cgen -------------------------------------------------
// pushargsrev — recursively walks the arg list, evaluates rightmost
// first, and pushes. str args take two slots (ptr in AX, len in BX);
// the order on the stack so a left-to-right pop into argregs lands
@@ -787,7 +772,6 @@ fn pushargsrev(c: *cgen, arg: *syntax.node, param: *syntax.node, memphase: bool,
let es60: *syntax.tinfo = tichase(bu60.sub);
if (es60 != nil) { esz = es60.size: i32; };
};
// base address → push
if (baselocal != nil) {
let tn: *syntax.node = baselocal.tnode;
let isarr60: bool = false;
@@ -828,7 +812,6 @@ fn pushargsrev(c: *cgen, arg: *syntax.node, param: *syntax.node, memphase: bool,
cgexpr(c, base);
};};};
emitline("\tPUSHQ\tAX\n");
// hi (default base length) → push
if (hi != nil) {
cgexpr(c, hi);
} else { if (baselocal != nil) {
@@ -945,7 +928,6 @@ fn pushargsrev(c: *cgen, arg: *syntax.node, param: *syntax.node, memphase: bool,
emitline("\tMOVQ\t$0, AX\n");
};};};};};
emitline("\tPUSHQ\tAX\n");
// lo (default 0) → AX
if (lo != nil) { cgexpr(c, lo); }
else { emitline("\tMOVQ\t$0, AX\n"); };
emitline("\tPOPQ\tBX\n"); // hi
@@ -2098,14 +2080,9 @@ fn elemsizeofc(c: *cgen, t: *syntax.node) i32 = {
return slotsize(c, elem);
};
// nodeisunsigned — best-effort cgen-time inference from the AST. We
// walk surface nodes (N_DOT now reads n.type_ — #55 A.6.3g):
// nkind.N_INTLIT never marked unsigned (no tsuffix plumbing yet)
// nkind.N_IDENT — look up the local's declared type
// nkind.N_DOT — read the checker-stamped n.type_ (#55 A.6.3g)
// nkind.N_BIN / nkind.N_UN — recurse: unsigned if either operand is unsigned
// nkind.N_CAST — use the cast target type
//
// nodeisunsigned — best-effort cgen-time inference from the AST
// (N_DOT reads the checker-stamped n.type_ — #55 A.6.3g).
// nkind.N_INTLIT is never marked unsigned (no tsuffix plumbing yet).
// Conservative: if we can't tell, return false (signed). The cost of
// being wrong here is byte-different asm vs C, not bad runtime.
fn nodeisunsigned(c: *cgen, n: *syntax.node) bool = {
@@ -2191,8 +2168,6 @@ fn nodeprimwidth(c: *cgen, n: *syntax.node) i32 = {
return 0;
};
// ---- type-driven slot sizing ----------------------------------------
// structnaturalsize — type-natural size of `si`, i.e. max(foff +
// fsz) across declared fields, UNROUNDED. This is the memory-copy
// extent: cstage copies exactly these bytes for the >24B sret
@@ -2594,11 +2569,8 @@ fn structsamemod(c: *cgen, name: str) *structinfo = {
return nil;
};
// primsize — size in bytes of a primitive type name (or 0 if not
// recognised as a primitive — the caller falls back to other paths).
// fldnumidx — parse a tuple field name like "0" / "1" / "12" into an
// index, or -1 if not all-digits. Used by cgdot to dispatch
// `t.0` / `t.1` against an nkind.N_TTUPLE local without pulling in strconv.
// fldnumidx — used by cgdot to dispatch `t.0` / `t.1` against an
// nkind.N_TTUPLE local without pulling in strconv.
fn fldnumidx(s: str) i32 = {
if (s.len == 0) { return -1; };
let r: i32 = 0;
@@ -5141,7 +5113,6 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *syntax.tinfo, src: *syntax.node, slot_of
}; };
};
};
// Struct payload (literal or ident).
let sname: str = rhsstructpayload(c, src);
if (sname.len > 0) {
let si: *structinfo = structlookup(c, sname);

View File

@@ -1,21 +1,4 @@
// selfhost/cmd/wcc/check.ww — minimal port of cmd/wcc/check.c.
//
// Status: name-resolution + primitive-type seeding only. Full type
// inference, conversion rules, tagged-union dispatch typing, return-
// type checking, etc. all live in cmd/wcc/check.c (937 lines) and
// will land here in subsequent commits.
//
// What this version does:
// 1. Creates a top scope and seeds it with primitive type names so
// `i32`, `str`, `*u8` etc. resolve.
// 2. Walks the file's top-level decls (use/def/type/fn/let) and
// installs Sym entries for each.
// 3. Recursively walks fn bodies; for every nkind.N_IDENT used as an
// expression or as a type name, looks it up and counts the
// resolved vs. unresolved.
// 4. Returns a summary the caller (wwdump -r) prints; the test
// asserts unresolved == 0 on every selfhost fixture, which is
// the floor signal that the frontend can name-resolve real ww.
// Port of cmd/wcc/check.c.
package wcc;
@@ -91,8 +74,6 @@ fn circularnamed(c: *checker, t: *syntax.tinfo, n: *syntax.node) bool = {
os.exit(1);
};
// seedprimitives — install the built-in type names so `i32`, `str`,
// etc. can be looked up like ordinary symbols.
fn seedprimitives(c: *checker) void = {
syntax.scopedefine(c.top, "void", syntax.skind.SK_TYPE, c.tc.tyvoid, nil);
syntax.scopedefine(c.top, "bool", syntax.skind.SK_TYPE, c.tc.tybool, nil);
@@ -822,8 +803,7 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
// free identifier.
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
// A.6.0: branch returns early; stamp here so the post-walk
// dispatch below sees N_DOT covered. exprtype N_DOT arm is
// added in A.6.1; for now this is a no-op nil return.
// dispatch below sees N_DOT covered.
let _t: *syntax.node = exprtype(c, n, nil);
return;
};
@@ -957,15 +937,12 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
};
};
// ---- type-level helpers (AST-level, no resolved tinfo) --------------
//
// The selfhost check operates on AST type expressions rather than
// resolved Type structs. These helpers mirror what cmd/wcc/check.c
// does with tinfo, but only on the subset of cases this checker
// needs to enforce: tagged-union exhaustiveness, ? subset
// propagation, and !-flag semantics.
// unwrapbang — strip an nkind.N_TBANG wrapper; leaves other nodes alone.
fn unwrapbang(n: *syntax.node) *syntax.node = {
if (n == nil) { return nil; };
if (n.kind == syntax.nkind.N_TBANG) { return n.lhs; };
@@ -1050,10 +1027,6 @@ fn aliassym(c: *checker, n: *syntax.node) *syntax.sym = {
return s;
};
// resolvealias — if n is an nkind.N_TNAME pointing at a typedecl, return
// the typedecl's body (possibly recursively). Pass-through for any
// other node. The chain stops once we hit a non-nkind.N_TNAME node or a
// name we can't resolve.
fn resolvealias(c: *checker, n: *syntax.node) *syntax.node = {
let cur: *syntax.node = n;
for (cur != nil) {
@@ -1274,9 +1247,6 @@ fn scruttype(c: *checker, e: *syntax.node) *syntax.node = {
return nil;
};
// mktname — fabricate an nkind.N_TNAME node with str = `nm`. Used by
// exprtype to return primitive type nodes for literal
// expressions. The arena keeps them around as long as the checker.
fn mktname(c: *checker, nm: str) *syntax.node = {
let n: *syntax.node = syntax.newnode(syntax.nkind.N_TNAME, "", 0, 0);
n.str = nm;
@@ -4692,9 +4662,6 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
return nil;
};
// isuntypedint / is_str_like / is_bool_like — helpers used
// by the assignability check below to allow common AST shapes
// through without needing real type inference.
fn isuntypedint(t: *syntax.node) bool = {
if (t == nil) { return false; };
if (t.kind != syntax.nkind.N_TNAME) { return false; };
@@ -5386,14 +5353,10 @@ fn isassignable(c: *checker, dst: *syntax.node, src: *syntax.node, confident: *b
return true;
};
// ---- match exhaustiveness --------------------------------------------
//
// For every match arm, verify that every variant of the scrutinee's
// tagged-union type is handled by some case (or a default arm
// exists). Multi-pattern `case A | B =>` covers all alts.
// qualleaf — rightmost dotted segment of a (possibly module-qualified)
// type name; the whole name when unqualified.
fn qualleaf(nm: str) str = {
let dotidx: i32 = -1;
let i: i32 = 0;
@@ -5408,8 +5371,6 @@ fn qualleaf(nm: str) str = {
return leaf;
};
// qualmod — module qualifier of a type name (segment before the
// rightmost '.'), or `defmod` when unqualified.
fn qualmod(nm: str, defmod: str) str = {
let dotidx: i32 = -1;
let i: i32 = 0;
@@ -5646,8 +5607,6 @@ fn checkvariantcovered(c: *checker, n: *syntax.node, v: *syntax.node, unionmod:
if (!covered) { errmatchvariant(c, n, v); };
};
// ---- let init / return assignability --------------------------------
//
// AST-level approximation: when we can infer src's type and dst is
// explicitly declared, verify isassignable. We only emit an error
// when isassignable says "false with confidence." If we can't tell
@@ -6581,8 +6540,6 @@ fn checkretassign(c: *checker, n: *syntax.node) void = {
};
};
// ---- is / as validity ------------------------------------------------
//
// `e is T` and `e as T` require that e's declared type be a tagged
// union and that T name one of its variants. Operates on AST type
// expressions; falls back silently when we can't determine e's
@@ -6670,8 +6627,6 @@ fn checkisas(c: *checker, n: *syntax.node) void = {
c.errs += 1;
};
// ---- ? subset propagation --------------------------------------------
//
// For `expr?`, the operand's error subset must be a subset of the
// enclosing fn's return-type variants. Mirrors C check.c. Operand
// is nkind.N_TRYPROP or nkind.N_TRYUNW (the F8 cardinality gate covers
@@ -6836,9 +6791,6 @@ fn hascvariadic(params: *syntax.node) bool = {
return false;
};
// install_param — when entering a fn body, define its params in a
// fresh local scope.
//
// TODO(#11): cstage check.c (post-#32) errors `param '%s' redeclared`
// when two params share a name. The fn body's scope IS fresh here
// (resolvefnbody opens it before calling us), so guarding scopedefine's

View File

@@ -1,7 +1,6 @@
// selfhost/cmd/wcc/err.ww — port of cmd/wcc/err.c.
//
// Diagnostics. Plan 9 style: short, no levels beyond fatal/error/warn.
// Output goes through os.write so we don't pull in libc stdio.
// Port of cmd/wcc/err.c. Plan 9 style: short, no levels beyond
// fatal/error/warn. Output goes through os.write so we don't pull in
// libc stdio.
package wcc;

View File

@@ -16,17 +16,12 @@
// - A `.wwi` is ONE package's interface; the emit filters to PRIMARY
// decls (imported==0).
// Imports mirror check.ww (os/tok/strconv only): node/nkind/sym/skind/
// scopelookuptype/streq/checker/tkind resolve bare in the selfhost's flat
// bundled scope, exactly as check.ww references them.
package wcc;
import os;
import syntax;
import strconv;
// --- byte writers ------------------------------------------------------
fn wputs(fd: i32, s: str) void = {
os.write(fd, s.ptr, s.len: u64);
};
@@ -71,13 +66,11 @@ fn wquote(fd: i32, s: str) void = {
wputb(fd, '"');
};
// --- check_exported_type (drew) ---------------------------------------
//
// Resolve an N_TNAME to its type sym via the public sym helpers (mirror
// of cstage wwi_typesym / scope_lookup_type) WITHOUT the side effects of
// the checker's aliassym (no on-demand resolve, no double error). A
// primitive/keyword resolves to no SK_TYPE → leaf. By producer time
// checkfile has finished and c.cur == c.top.
// check_exported_type (drew): resolve an N_TNAME to its type sym via the
// public sym helpers (mirror of cstage wwi_typesym / scope_lookup_type)
// WITHOUT the side effects of the checker's aliassym (no on-demand
// resolve, no double error). A primitive/keyword resolves to no SK_TYPE
// → leaf. By producer time checkfile has finished and c.cur == c.top.
fn wwitypesym(c: *checker, nm: str) *syntax.sym = {
let empty: str;
@@ -188,7 +181,7 @@ fn wwicheckdecl(c: *checker, d: *syntax.node) i32 = {
bad = bad | wwichecktype(c, d, p.lhs);
p = p.next;
};
bad = bad | wwichecktype(c, d, d.lhs); // ret
bad = bad | wwichecktype(c, d, d.lhs);
} else { if (d.kind == syntax.nkind.N_TYPEDECL) {
bad = bad | wwichecktype(c, d, d.lhs);
} else { if (d.kind == syntax.nkind.N_DEF) {
@@ -200,10 +193,8 @@ fn wwicheckdecl(c: *checker, d: *syntax.node) i32 = {
return bad;
};
// --- type-expr + const-expr unparser (rob §2.2/§2.4) ------------------
// wwihexdigits — emit the low `n` hex digits of `v`, most-significant
// first, lowercase. Byte-identical to cstage's fprintf("%0Nx").
// Type-expr + const-expr unparser (rob §2.2/§2.4). wwihexdigits is
// byte-identical to cstage's fprintf("%0Nx").
fn wwihexdigits(fd: i32, v: u64, n: i32) void = {
let i: i32 = n - 1;
for (i >= 0) {
@@ -515,8 +506,6 @@ fn wwidecl(fd: i32, d: *syntax.node) void = {
};};};};
};
// --- deterministic ordering (rob §3) ----------------------------------
fn wwiprimary(n: *syntax.node) bool = {
// imported==1 marks a decl reached through a `//ww:module <path>`
// boundary (an imported module's concatenated section).
@@ -529,8 +518,9 @@ fn wwiisdecl(d: *syntax.node) bool = {
d.kind == syntax.nkind.N_DEF || d.kind == syntax.nkind.N_LET;
};
// strcmp — byte lexicographic, mirror C strcmp sign (<0/0/>0). Both
// stages key the sort identically, so the `.wwi` order is deterministic.
// Deterministic ordering (rob §3): byte-lexicographic, mirror C strcmp
// sign (<0/0/>0). Both stages key the sort identically, so the `.wwi`
// order is deterministic.
fn wwistrcmp(a: str, b: str) i32 = {
let i: i32 = 0;
for (i < a.len && i < b.len) {
@@ -542,8 +532,7 @@ fn wwistrcmp(a: str, b: str) i32 = {
return a.len - b.len;
};
// Selection sort over parallel (key, node) arrays. Total order keyed on
// the symbol name; ties broken by original index — so the result is
// Total order keyed on the symbol name; ties broken by original index —
// stable regardless of any same-name collision, matching cstage's qsort
// + idx tiebreak.
fn wwisortdecls(keys: []str, nodes: []*syntax.node, n: i32) void = {

View File

@@ -24,9 +24,8 @@ import strings;
// the cgen #127 mod-mangle attribution bug consumer per rule-7.
def CMD_MAX: u64 = 8192u64;
// cerr — bare stderr fragment writer for the driver's piecewise
// diagnostics. Tool-local (NOT a lib wrapper): messages are built from
// many fragments and we route through os.write to avoid libc stdio.
// Tool-local (NOT a lib wrapper): messages are built from many
// fragments and we route through os.write to avoid libc stdio.
// .len replaces the error-prone hand-counted byte literals these sites
// carried. Lives here (the selfhost ww-driver build is a single main.ww;
// err.c's bare-message path is not ported into this tree).
@@ -34,17 +33,14 @@ fn cerr(m: str) void = {
os.write(2, m.ptr, m.len: u64);
};
// ---- C-string helpers --------------------------------------------------
fn cstrlen(p: *u8) u64 = {
let n: u64 = 0u64;
for (p[n] != 0u8) { n += 1u64; };
return n;
};
// pathstr — view a NUL-terminated *u8 as a str. Bridges the
// driver's argv-style *u8 paths to lib/os entrypoints (str
// post-task-#23).
// Bridges the driver's argv-style *u8 paths to lib/os entrypoints
// (str post-task-#23).
fn pathstr(p: *u8) str = {
let r: str;
r.ptr = p;
@@ -56,12 +52,10 @@ fn cstreq(a: *u8, b: *u8) bool = {
return strings.compare(pathstr(a), pathstr(b)) == 0;
};
// cstreqlit — compare a NUL-terminated *u8 to a ww string literal.
fn cstreqlit(a: *u8, lit: str) bool = {
return strings.compare(pathstr(a), lit) == 0;
};
// memcpy
fn bytecpy(dst: *u8, src: *u8, n: u64) void = {
let i: u64 = 0u64;
for (i < n) {
@@ -70,8 +64,6 @@ fn bytecpy(dst: *u8, src: *u8, n: u64) void = {
};
};
// Copy a NUL-terminated *u8 into dst starting at off; return the new
// offset (without writing a NUL).
fn cstrinto(dst: *u8, off: u64, src: *u8) u64 = {
let i: u64 = 0u64;
for (src[i] != 0u8) {
@@ -81,7 +73,6 @@ fn cstrinto(dst: *u8, off: u64, src: *u8) u64 = {
return off + i;
};
// Same, but for a ww `str` (no NUL on the source side; we copy len bytes).
fn strinto(dst: *u8, off: u64, src: str) u64 = {
let n: i32 = src.len;
let i: i32 = 0;
@@ -94,22 +85,15 @@ fn strinto(dst: *u8, off: u64, src: str) u64 = {
return off + nu;
};
// Write a single byte, return new offset.
fn byteinto(dst: *u8, off: u64, c: u8) u64 = {
dst[off] = c;
return off + 1u64;
};
// NUL-terminate at off and return the same off (handy when passing the
// buffer to a syscall that expects a C-string).
fn cstrseal(dst: *u8, off: u64) void = {
dst[off] = 0u8;
};
// ---- Tool-path resolution ---------------------------------------------
// dirname-equivalent: copy argv[0] up to (but not including) the last
// '/' into dst, NUL-terminated. If no slash, write ".".
fn selfdirinto(dst: *u8, dstsz: u64, argv0: *u8) void = {
let n: u64 = cstrlen(argv0);
let cut: u64 = 0u64;
@@ -128,7 +112,6 @@ fn selfdirinto(dst: *u8, dstsz: u64, argv0: *u8) void = {
dst[cut] = 0u8;
};
// Build "$dir/$name" (NUL-terminated) into a fresh page-sized buffer.
fn joinpath(dir: *u8, name: *u8) *u8 = {
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
buf.len = os.PATH_MAX;
@@ -139,7 +122,6 @@ fn joinpath(dir: *u8, name: *u8) *u8 = {
return buf.ptr;
};
// Same, but the second component is a ww `str` literal.
fn joinpathlit(dir: *u8, name: str) *u8 = {
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
buf.len = os.PATH_MAX;
@@ -208,8 +190,6 @@ fn execpackagetests(selfdir: *u8, argv: **u8, argc: i32, start: i32,
return 1;
};
// ---- import resolution + visited-set -----------------------------------
//
// The separate-compilation producer scans each unit's top-of-file
// `import IDENT;` lines and resolves them via the colon-separated `dirs`
// search path. A per-scan visited set (linear; typical builds visit a
@@ -409,10 +389,9 @@ fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64) i32 = {
return 1;
};
// Byte-wise memcmp returning < 0, 0, > 0. Rule-10 byte-id requires
// cstage and wwstage sort the same way; memcmp is the
// locale-independent total order (mirrors ref/hare/sort/cmp/cmp.ha
// strs).
// Rule-10 byte-id requires cstage and wwstage sort the same way;
// memcmp is the locale-independent total order (mirrors
// ref/hare/sort/cmp/cmp.ha strs).
fn bytecmp(a: *u8, alen: u64, b: *u8, blen: u64) i32 = {
let n: u64 = alen;
if (blen < n) { n = blen; };
@@ -526,8 +505,6 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
return exact.ptr, n;
};
// ---- file slurp -------------------------------------------------------
fn slurp(pathcs: *u8) (*u8, u64) = {
let fd: i32 = os.open(pathstr(pathcs), os.flag.RDONLY, 0i32);
if (fd < 0) { return nil, 0u64; };
@@ -566,7 +543,6 @@ fn isidentbyte(c: u8) bool = {
// caller passes a slice of the source: src points at the line start.
fn scanuse(src: *u8, len: u64) (*u8, u64) = {
let i: u64 = 0u64;
// skip leading whitespace
for (i < len) {
if (src[i] != 32u8) { if (src[i] != 9u8) { break; }; };
i += 1u64;
@@ -595,11 +571,6 @@ fn scanuse(src: *u8, len: u64) (*u8, u64) = {
return src + idstart, idlen;
};
// ---- Build pipeline ---------------------------------------------------
// Strip the trailing ".ww" off `src` (a NUL-terminated path) into
// `stem`, NUL-terminated. If there's no .ww, the stem is the whole
// path.
fn makestem(stem: *u8, src: *u8) void = {
let n: u64 = cstrlen(src);
let stop: u64 = n;
@@ -617,7 +588,6 @@ fn makestem(stem: *u8, src: *u8) void = {
stem[stop] = 0u8;
};
// Append a literal suffix to `stem` (which already lives in a buffer).
fn appendlit(stem: *u8, suffix: str) *u8 = {
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
buf.len = os.PATH_MAX;
@@ -638,8 +608,6 @@ type lflags = struct {
nlibs: i32,
};
// ---- separate-compilation driver -------------------------------------
//
// Port of cmd/ww/main.c build_one_sep (task #46/c3). The build path
// materializes each imported package's `.wwi` interface and compiles
// every package on its own (`w6c -c`), then flat-links the `.o` set.
@@ -677,7 +645,6 @@ type sepgraph = struct {
n: i32,
};
// Find a package by dotted path, or add it. Returns index, -1 if full.
fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
let i: i32 = 0;
for (i < g.n) {
@@ -1060,8 +1027,6 @@ fn seploadpkg(g: *sepgraph, pi: i32, searchpath: *u8) i32 = {
return 0;
};
// Print one cycle-chain node: a package path, or "(root)" for the
// empty root path.
fn sepcyclenode(p: *u8) void = {
if (p[0] == 0u8) { cerr("(root)"); } else { cerr(pathstr(p)); };
};
@@ -1103,7 +1068,6 @@ fn septopovisit(g: *sepgraph, pi: i32, order: []i32, no: *i32,
return 0;
};
// Mark pi's transitive deps (excluding pi) in inset[].
fn sepmarkdeps(g: *sepgraph, pi: i32, inset: []u8) void = {
let k: i32 = 0;
for (k < g.pkg[pi].ndeps) {
@@ -1306,7 +1270,7 @@ fn archiveo(objpath: *u8, apath: *u8) i32 = {
// reverse-topo `w6l` of the root `.o` + dependency `.a` set + libwwrt.a.
// Side files land in a cold `<stem>.sepwork` scratch dir. Twin of cstage
// build_one_sep.
// ---- -w workdir freshness ----------------------------------------------
// A `-w DIR` workdir is a caller-owned persistent package-artifact tree
// that replaces the fresh `.sepwork` scratch. Staleness is pure content
// identity, never mtime: a package is reused only when its freshly
@@ -1478,7 +1442,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
let a6: *u8 = joinpathlit(selfdir, "w6a_ww");
let l6: *u8 = joinpathlit(selfdir, "w6l_ww");
// Default lib search path: <selfdir>/../../lib
let dotdotlib: []u8 = alloc([], (os.PATH_MAX: u64))!;
dotdotlib.len = os.PATH_MAX;
{
@@ -1487,7 +1450,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
cstrseal(dotdotlib.ptr, off);
};
// Source directory.
let srcd: []u8 = alloc([], (os.PATH_MAX: u64))!;
srcd.len = os.PATH_MAX;
if (entryisdir != 0) {
@@ -1532,7 +1494,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
cstrseal(searchpath.ptr, off);
};
// Stem for the scratch dir.
let stem: []u8 = alloc([], (os.PATH_MAX: u64))!;
stem.len = os.PATH_MAX;
if (entryisdir != 0) {
@@ -1606,7 +1567,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
};
};
// libwwrt.a path: <selfdir>/../lib/libwwrt.a
let libwwrt: []u8 = alloc([], (os.PATH_MAX: u64))!;
libwwrt.len = os.PATH_MAX;
{
@@ -1615,7 +1575,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
cstrseal(libwwrt.ptr, off);
};
// Discover.
let pkgslot: []seppkg = alloc([], SEP_MAXPKG: u64)!;
pkgslot.len = SEP_MAXPKG;
let g: *sepgraph = alloc(sepgraph{pkg = pkgslot, n = 0})!;
@@ -1649,7 +1608,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
};
if (seploadpkg(g, root, searchpath.ptr) < 0) { return 1; };
// Reset colors, reverse-topo.
let ci: i32 = 0;
for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; };
let order: []i32 = alloc([], g.n: u64)!;
@@ -1659,7 +1617,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
let norder: i32 = 0;
if (septopovisit(g, root, order, &norder, stack, 0) < 0) { return 1; };
// Producer loop — dep-first, one `w6c -c -I` per package.
let oi: i32 = 0;
for (oi < norder) {
let pi: i32 = order[oi];
@@ -1961,22 +1918,10 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
return r;
};
// ---- Module-by-name resolution ----------------------------------------
//
// Mirrors cmd/ww/main.c:resolvemodule. Maps a name like "foo", "lib/foo",
// "foo.ww", or "." to a concrete .ww file path:
// 1. literal <name>.ww that exists → use as-is
// 2. "." → <cwd>/<basename(cwd)>.ww → that, if it exists
// 3. <name>/<basename(name)>.ww → that, if it exists
// 4. walk search path (cwd:incs:<selfdir>/../../lib):
// <dir>/<name>.ww or <dir>/<name>/<name>.ww
fn cstrendswithlit(p: *u8, lit: str) bool = {
return strings.hassuffix(pathstr(p), lit);
};
// basenameoff — return the offset of the last path segment within `p`
// (i.e. one past the final '/'). Returns 0 if there's no slash.
fn basenameoff(p: *u8, plen: u64) u64 = {
let start: u64 = 0u64;
let i: u64 = 0u64;
@@ -1987,8 +1932,6 @@ fn basenameoff(p: *u8, plen: u64) u64 = {
return start;
};
// arenadupcstr — copy `plen` bytes from `src` into a fresh NUL-sealed
// heap buffer.
fn arenadupcstr(src: *u8, plen: u64) *u8 = {
let buf: []u8 = alloc([], plen + 1u64)!;
let i: u64 = 0u64;
@@ -1997,8 +1940,7 @@ fn arenadupcstr(src: *u8, plen: u64) *u8 = {
return buf.ptr;
};
// buildsearchpath — compose the colon-separated lookup path used by
// resolvemodule's case (4). Order: "." : <incs> : <selfdir>/../../lib
// resolvemodule search-path order: "." : <incs> : <selfdir>/../../lib
fn buildsearchpath(selfdir: *u8, incs: *u8) *u8 = {
let buf: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!;
let off: u64 = 0u64;
@@ -2016,13 +1958,11 @@ fn buildsearchpath(selfdir: *u8, incs: *u8) *u8 = {
return buf.ptr;
};
// resolvemodule — map a name like "foo", "lib/foo", "foo.ww", or
// "." to a concrete entry path. Sets *isdir when the entry is a
// Mirrors cmd/ww/main.c:resolvemodule. Sets *isdir when the entry is a
// module directory (caller will dir-enumerate).
fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
let nlen: u64 = cstrlen(name);
// (1) Literal file that exists → use as-is.
if (cstrendswithlit(name, ".ww")) {
if (os.access(pathstr(name), 0i32) == 0) {
*isdir = 0;
@@ -2030,7 +1970,6 @@ fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
};
};
// (2) Existing path → use as-is, dir vs file via stat.
let fi: os.filestat;
let sr: (void | os.oserror) = os.stat(&fi, pathstr(name));
let found: bool = false;
@@ -2048,13 +1987,10 @@ fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
return arenadupcstr(name, nlen);
};
// (3) Search-path lookup with dot-to-slash path translation.
let search: *u8 = buildsearchpath(selfdir, incs);
return locateimport(search, name, nlen, isdir);
};
// ---- Subcommand handlers ----------------------------------------------
fn writeusage(fd: i32) void = {
let s: str = "usage: ww [-V] <subcommand> [args...]\n -V print version and exit\n build [-S] [-w DIR] [-o FILE] [path] compile module; -S stops after package asm\n run [path] ... build then exec, passing extra args to the program\n test [-S -o STEM] [-w DIR] [options] [path] build/run tests; -S emits package asm\n version print version and exit\n\n path forms:\n foo.ww literal file\n foo search cwd, -I dirs, then $WW_LIB-equiv for foo.ww or foo/foo.ww\n lib/foo directory: build lib/foo/foo.ww\n lib/... every package under lib, recursively (test only)\n . build the cwd's <basename>.ww\n";
os.write(fd, s.ptr, s.len: u64);
@@ -2065,8 +2001,6 @@ fn doversion() i32 = {
return 0;
};
// Compute the basename of src (without trailing ".ww") into a fresh
// buffer. Used as the default output path for `ww build`.
fn defaultoutpath(src: *u8) *u8 = {
let n: u64 = cstrlen(src);
let start: u64 = 0u64;
@@ -2084,7 +2018,6 @@ fn defaultoutpath(src: *u8) *u8 = {
off += 1u64;
j += 1u64;
};
// Strip ".ww" if present.
if (off >= 3u64) {
if (out[off - 3u64] == 46u8) {
if (out[off - 2u64] == 119u8) {
@@ -2209,7 +2142,6 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
};
if (src == nil) {
// default to cwd module
let dot: [2]u8 = ['.': u8, 0u8];
src = &dot[0];
};
@@ -2264,7 +2196,6 @@ fn makedrivertmp(buf: *u8, prefix: str) void = {
pk += 1;
};
let pid: i32 = os.getpid();
// itoa for non-negative pid
let dig: [16]u8;
let n: i32 = 0;
if (pid <= 0) {
@@ -2383,7 +2314,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
src = p;
i += 1;
} else {
passstart = i; // remaining args go to the program
passstart = i;
};
};
};
@@ -2426,8 +2357,8 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
return 1;
};
// Execute with [tmp, argv[passstart..argc)). The standard process
// facility inherits stdio and waits only for this user program.
// The standard process facility inherits stdio and waits only for
// this user program.
let nextra: i32 = 0;
if (passstart >= 0) { nextra = argc - passstart; };
let execargv: []str = alloc([], (nextra + 1): u64)!;
@@ -2464,8 +2395,6 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
return rc;
};
// ---- ww test ----------------------------------------------------------
//
// Mirrors cmd/ww/main.c:dotest. Explicit regular files retain the bootstrap
// compatibility route; directory/default requests delegate to wwtest.
@@ -2762,15 +2691,12 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
replacement, targetindex < 0);
};
// ---- Entry -------------------------------------------------------------
export fn main(argc: i32, argv: **u8) i32 = {
if (argc < 1) {
writeusage(2);
return 2;
};
// selfdir = dirname(argv[0])
let selfdir: []u8 = alloc([], (os.PATH_MAX: u64))!;
selfdir.len = os.PATH_MAX;
selfdirinto(selfdir.ptr, (os.PATH_MAX: u64), argv[0]);

View File

@@ -1,13 +1,6 @@
// selfhost/cmd/wwdump/main.ww — ww-side port of cmd/wwdump/main.c.
//
// Reads a .ww file, runs the ww-side lexer, prints tokens through
// the ww-side tokprint. The 990_selfhost test diffs this output
// byte-for-byte against the C-side wwdump on the same file. Any
// Port of cmd/wwdump/main.c. The 990_selfhost test diffs token output
// byte-for-byte against the C-side wwdump on the same file; any
// divergence is a port bug in lex.ww or tok.ww.
//
// Modes:
// wwdump -t file.ww tokens (default)
// wwdump -a file.ww AST (not yet implemented; reserved)
package main;
@@ -17,10 +10,8 @@ import check;
import cgen;
import strconv;
// ---- argv helpers -----------------------------------------------------
// argstrlen — strlen on a NUL-terminated *u8. argv strings are always
// NUL-terminated (kernel-supplied) so this is safe.
// argv strings are always NUL-terminated (kernel-supplied), so the
// unbounded scan is safe.
fn argstrlen(s: *u8) i32 = {
let n: i32 = 0;
for (s[n] != 0u8) { n += 1; };
@@ -34,7 +25,6 @@ fn argstr(p: *u8) str = {
return s;
};
// streqlit — compare a NUL-terminated argv entry to a string literal.
fn streqlit(p: *u8, lit: str) bool = {
let i: i32 = 0;
for (i < lit.len) {
@@ -44,8 +34,6 @@ fn streqlit(p: *u8, lit: str) bool = {
return p[i] == 0u8;
};
// ---- main -------------------------------------------------------------
export fn main(argc: i32, argv: **u8) i32 = {
let mode: i32 = 116; // 't'
let path: *u8 = nil;
@@ -142,7 +130,6 @@ export fn main(argc: i32, argv: **u8) i32 = {
// Quiet by default; flip to 1 when debugging missing names.
ck.verbose = 0;
checkfile(&ck, f);
// (close out the if-else chain — we'll close all braces below)
// "<file>: <resolved>/<resolved+unresolved> resolved"
os.write(1, argstr(path).ptr, argstrlen(path): u64);
os.write(1, ": ".ptr, 2u64);