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,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 = {