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:
@@ -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 (- ! ~ & *),
|
||||
// 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"; };
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user