diff --git a/lib/ww/typ.ww b/lib/ww/typ.ww index 9fc37fb4..5881839d 100644 --- a/lib/ww/typ.ww +++ b/lib/ww/typ.ww @@ -89,6 +89,17 @@ type tinfo = struct { under: *tinfo, }; +// #61 audit §1.8 / Rob+Drew convergence 2026-05-20: memoizes +// tinfofornode lookups keyed by AST pointer. Linked-list shape mirrors +// other wwstage-side caches (cgen.aliases, cgen.structs) — sea-of-stars +// over hash-table cleverness, and Sym/Scope already pay the FNV cost +// for the resolver pass. +type tinfocacheent = struct { + key: *node, + val: *tinfo, + cnext: *tinfocacheent, +}; + // ---- tctx — the box of primitive types ------------------------------- type tctx = struct { @@ -118,6 +129,7 @@ type tctx = struct { tyuntypedrune: *tinfo, tyuntypedbool: *tinfo, tyuntypednil: *tinfo, + tinfocache: *tinfocacheent, }; // ---- constructors ----------------------------------------------------- @@ -214,6 +226,28 @@ export fn typenamed(a: *arena, name: str, under: *tinfo) *tinfo = { return t; }; +// #61 audit §1.8 — A.1 infrastructure: tinfocache lookup/bind. Keyed +// by AST node-pointer so two different N_TNAME("i32") nodes get +// independent entries that both resolve to c.tyi32. Used by +// tinfofornode in check.ww; cgen still reads sizes via primtypesize +// until A.2+ graduates each walker family. +export fn tinfocachelookup(c: *tctx, key: *node) *tinfo = { + let e: *tinfocacheent = c.tinfocache; + for (e != nil) { + if (e.key == key) { return e.val; }; + e = e.cnext; + }; + return nil; +}; + +export fn tinfocachebind(c: *tctx, key: *node, val: *tinfo) void = { + let e: *tinfocacheent = amalloc(c.a, 32u64): *tinfocacheent; + e.key = key; + e.val = val; + e.cnext = c.tinfocache; + c.tinfocache = e; +}; + // ---- predicates ------------------------------------------------------- export fn typeisint(t: *tinfo) bool = { diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 2bd57849..f98736d7 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -6515,6 +6515,17 @@ type tinfo = struct { under: *tinfo, }; +// #61 audit §1.8 / Rob+Drew convergence 2026-05-20: memoizes +// tinfofornode lookups keyed by AST pointer. Linked-list shape mirrors +// other wwstage-side caches (cgen.aliases, cgen.structs) — sea-of-stars +// over hash-table cleverness, and Sym/Scope already pay the FNV cost +// for the resolver pass. +type tinfocacheent = struct { + key: *node, + val: *tinfo, + cnext: *tinfocacheent, +}; + // ---- tctx — the box of primitive types ------------------------------- type tctx = struct { @@ -6544,6 +6555,7 @@ type tctx = struct { tyuntypedrune: *tinfo, tyuntypedbool: *tinfo, tyuntypednil: *tinfo, + tinfocache: *tinfocacheent, }; // ---- constructors ----------------------------------------------------- @@ -6640,6 +6652,28 @@ export fn typenamed(a: *arena, name: str, under: *tinfo) *tinfo = { return t; }; +// #61 audit §1.8 — A.1 infrastructure: tinfocache lookup/bind. Keyed +// by AST node-pointer so two different N_TNAME("i32") nodes get +// independent entries that both resolve to c.tyi32. Used by +// tinfofornode in check.ww; cgen still reads sizes via primtypesize +// until A.2+ graduates each walker family. +export fn tinfocachelookup(c: *tctx, key: *node) *tinfo = { + let e: *tinfocacheent = c.tinfocache; + for (e != nil) { + if (e.key == key) { return e.val; }; + e = e.cnext; + }; + return nil; +}; + +export fn tinfocachebind(c: *tctx, key: *node, val: *tinfo) void = { + let e: *tinfocacheent = amalloc(c.a, 32u64): *tinfocacheent; + e.key = key; + e.val = val; + e.cnext = c.tinfocache; + c.tinfocache = e; +}; + // ---- predicates ------------------------------------------------------- export fn typeisint(t: *tinfo) bool = { @@ -7825,6 +7859,81 @@ fn foldtointlit(c: *checker, n: *node, v: i64) void = { n.tsuffix = empty; }; +// #61 audit §1.8 — A.1 infrastructure: resolve a type-expression AST +// node to its *tinfo. Mirrors cstage's resolve_type (cmd/wcc/check.c) +// which produces ty_* singletons / arena-allocated composites from a +// Node*. Cache lives in c.tc (typ.ww) so the same shape can be reused +// across modules within one check pass. Rob+Drew convergence +// 2026-05-20: cgen will graduate to read sizes from here in A.2+; A.1 +// just lays the helper and exercises it on N_INTLIT. +// +// Coverage today (A.1): +// - N_TNAME primitive (i32, str, untyped_int, ...) -> tctx singleton +// - N_TNAME alias -> recurse through resolvealias +// - N_TPTR / N_TSLICE / N_TCHAN / N_TBANG -> structural recurse +// Other shapes (TARRAY/TSTRUCT/TFN/TTAGGED/TTUPLE/TENUM) return nil +// for now; A.2+ extends as cgen-site graduation demands them. +fn tinfofornode(c: *checker, n: *node) *tinfo = { + if (n == nil) { return nil; }; + let cached: *tinfo = tinfocachelookup(c.tc, n); + if (cached != nil) { return cached; }; + let r: *tinfo = nil; + let k: nkind = n.kind; + if (k == nkind.N_TNAME) { + let nm: str = n.str; + if (streq(nm, "void")) { r = c.tc.tyvoid; }; + if (streq(nm, "bool")) { r = c.tc.tybool; }; + if (streq(nm, "rune")) { r = c.tc.tyrune; }; + if (streq(nm, "i8")) { r = c.tc.tyi8; }; + if (streq(nm, "i16")) { r = c.tc.tyi16; }; + if (streq(nm, "i32")) { r = c.tc.tyi32; }; + if (streq(nm, "i64")) { r = c.tc.tyi64; }; + if (streq(nm, "u8")) { r = c.tc.tyu8; }; + if (streq(nm, "u16")) { r = c.tc.tyu16; }; + if (streq(nm, "u32")) { r = c.tc.tyu32; }; + if (streq(nm, "u64")) { r = c.tc.tyu64; }; + if (streq(nm, "int")) { r = c.tc.tyint; }; + if (streq(nm, "uint")) { r = c.tc.tyuint; }; + if (streq(nm, "uintptr")) { r = c.tc.tyuintptr; }; + if (streq(nm, "f32")) { r = c.tc.tyf32; }; + if (streq(nm, "f64")) { r = c.tc.tyf64; }; + if (streq(nm, "str")) { r = c.tc.tystr; }; + if (streq(nm, "never")) { r = c.tc.tynever; }; + if (streq(nm, "untyped_int")) { r = c.tc.tyuntypedint; }; + if (streq(nm, "untyped_float")) { r = c.tc.tyuntypedfloat; }; + if (streq(nm, "untyped_str")) { r = c.tc.tyuntypedstr; }; + if (streq(nm, "untyped_rune")) { r = c.tc.tyuntypedrune; }; + if (streq(nm, "untyped_bool")) { r = c.tc.tyuntypedbool; }; + if (streq(nm, "untyped_nil")) { r = c.tc.tyuntypednil; }; + if (r == nil) { + // Alias / user-defined name: resolve via scope and recurse. + // Mirrors astsize's TNAME fallback so the helpers stay in + // lockstep until A.2 collapses each cgen size-walker onto + // tinfo.size directly. + let body: *node = resolvealias(c, n); + if (body != nil && body != n) { + r = tinfofornode(c, body); + }; + }; + } else { if (k == nkind.N_TBANG) { + // #61 audit §1.8: `!T` propagates the inner shape; cstage's + // resolve_type sets ty->iserror on the wrapper but no wwstage + // cgen reader consumes it yet, so A.1 drops the flag and + // returns the inner tinfo unchanged. Mirrors typeeqast's + // unwrapbang pre-walk; graduate alongside the first cgen + // site that needs iserror discrimination. + r = tinfofornode(c, n.lhs); + } else { if (k == nkind.N_TPTR) { + r = typeptr(c.a, tinfofornode(c, n.lhs)); + } else { if (k == nkind.N_TSLICE) { + r = typeslice(c.a, tinfofornode(c, n.lhs)); + } else { if (k == nkind.N_TCHAN) { + r = typechan(c.a, tinfofornode(c, n.lhs)); + };};};};}; + if (r != nil) { tinfocachebind(c.tc, n, r); }; + return r; +}; + // exprtype — best-effort type-AST inference for an expression // node. Handles literals, identifiers, calls, and casts; returns // nil for shapes we don't statically know (binary ops, struct @@ -7832,7 +7941,16 @@ fn foldtointlit(c: *checker, n: *node, v: i64) void = { fn exprtype(c: *checker, e: *node) *node = { if (e == nil) { return nil; }; let k: nkind = e.kind; - if (k == nkind.N_INTLIT) { return mktname(c, "untyped_int"); }; + if (k == nkind.N_INTLIT) { + let tn: *node = mktname(c, "untyped_int"); + // #61 audit §1.8 — A.1 single population site. Cgen still + // reads sizes through primtypesize/slotsize today; A.2+ + // graduates each walker family to read e.type_ instead, + // ending the size-walker cascade (Rob+Drew convergence + // 2026-05-20). Don't extend population to other arms in A.1. + e.type_ = tinfofornode(c, tn): *void; + return tn; + }; if (k == nkind.N_FLOATLIT) { return mktname(c, "untyped_float"); }; if (k == nkind.N_STRLIT) { return mktname(c, "str"); }; if (k == nkind.N_RUNELIT) { return mktname(c, "rune"); }; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 2700c151..019b2fbc 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -831,6 +831,81 @@ fn foldtointlit(c: *checker, n: *node, v: i64) void = { n.tsuffix = empty; }; +// #61 audit §1.8 — A.1 infrastructure: resolve a type-expression AST +// node to its *tinfo. Mirrors cstage's resolve_type (cmd/wcc/check.c) +// which produces ty_* singletons / arena-allocated composites from a +// Node*. Cache lives in c.tc (typ.ww) so the same shape can be reused +// across modules within one check pass. Rob+Drew convergence +// 2026-05-20: cgen will graduate to read sizes from here in A.2+; A.1 +// just lays the helper and exercises it on N_INTLIT. +// +// Coverage today (A.1): +// - N_TNAME primitive (i32, str, untyped_int, ...) -> tctx singleton +// - N_TNAME alias -> recurse through resolvealias +// - N_TPTR / N_TSLICE / N_TCHAN / N_TBANG -> structural recurse +// Other shapes (TARRAY/TSTRUCT/TFN/TTAGGED/TTUPLE/TENUM) return nil +// for now; A.2+ extends as cgen-site graduation demands them. +fn tinfofornode(c: *checker, n: *node) *tinfo = { + if (n == nil) { return nil; }; + let cached: *tinfo = tinfocachelookup(c.tc, n); + if (cached != nil) { return cached; }; + let r: *tinfo = nil; + let k: nkind = n.kind; + if (k == nkind.N_TNAME) { + let nm: str = n.str; + if (streq(nm, "void")) { r = c.tc.tyvoid; }; + if (streq(nm, "bool")) { r = c.tc.tybool; }; + if (streq(nm, "rune")) { r = c.tc.tyrune; }; + if (streq(nm, "i8")) { r = c.tc.tyi8; }; + if (streq(nm, "i16")) { r = c.tc.tyi16; }; + if (streq(nm, "i32")) { r = c.tc.tyi32; }; + if (streq(nm, "i64")) { r = c.tc.tyi64; }; + if (streq(nm, "u8")) { r = c.tc.tyu8; }; + if (streq(nm, "u16")) { r = c.tc.tyu16; }; + if (streq(nm, "u32")) { r = c.tc.tyu32; }; + if (streq(nm, "u64")) { r = c.tc.tyu64; }; + if (streq(nm, "int")) { r = c.tc.tyint; }; + if (streq(nm, "uint")) { r = c.tc.tyuint; }; + if (streq(nm, "uintptr")) { r = c.tc.tyuintptr; }; + if (streq(nm, "f32")) { r = c.tc.tyf32; }; + if (streq(nm, "f64")) { r = c.tc.tyf64; }; + if (streq(nm, "str")) { r = c.tc.tystr; }; + if (streq(nm, "never")) { r = c.tc.tynever; }; + if (streq(nm, "untyped_int")) { r = c.tc.tyuntypedint; }; + if (streq(nm, "untyped_float")) { r = c.tc.tyuntypedfloat; }; + if (streq(nm, "untyped_str")) { r = c.tc.tyuntypedstr; }; + if (streq(nm, "untyped_rune")) { r = c.tc.tyuntypedrune; }; + if (streq(nm, "untyped_bool")) { r = c.tc.tyuntypedbool; }; + if (streq(nm, "untyped_nil")) { r = c.tc.tyuntypednil; }; + if (r == nil) { + // Alias / user-defined name: resolve via scope and recurse. + // Mirrors astsize's TNAME fallback so the helpers stay in + // lockstep until A.2 collapses each cgen size-walker onto + // tinfo.size directly. + let body: *node = resolvealias(c, n); + if (body != nil && body != n) { + r = tinfofornode(c, body); + }; + }; + } else { if (k == nkind.N_TBANG) { + // #61 audit §1.8: `!T` propagates the inner shape; cstage's + // resolve_type sets ty->iserror on the wrapper but no wwstage + // cgen reader consumes it yet, so A.1 drops the flag and + // returns the inner tinfo unchanged. Mirrors typeeqast's + // unwrapbang pre-walk; graduate alongside the first cgen + // site that needs iserror discrimination. + r = tinfofornode(c, n.lhs); + } else { if (k == nkind.N_TPTR) { + r = typeptr(c.a, tinfofornode(c, n.lhs)); + } else { if (k == nkind.N_TSLICE) { + r = typeslice(c.a, tinfofornode(c, n.lhs)); + } else { if (k == nkind.N_TCHAN) { + r = typechan(c.a, tinfofornode(c, n.lhs)); + };};};};}; + if (r != nil) { tinfocachebind(c.tc, n, r); }; + return r; +}; + // exprtype — best-effort type-AST inference for an expression // node. Handles literals, identifiers, calls, and casts; returns // nil for shapes we don't statically know (binary ops, struct @@ -838,7 +913,16 @@ fn foldtointlit(c: *checker, n: *node, v: i64) void = { fn exprtype(c: *checker, e: *node) *node = { if (e == nil) { return nil; }; let k: nkind = e.kind; - if (k == nkind.N_INTLIT) { return mktname(c, "untyped_int"); }; + if (k == nkind.N_INTLIT) { + let tn: *node = mktname(c, "untyped_int"); + // #61 audit §1.8 — A.1 single population site. Cgen still + // reads sizes through primtypesize/slotsize today; A.2+ + // graduates each walker family to read e.type_ instead, + // ending the size-walker cascade (Rob+Drew convergence + // 2026-05-20). Don't extend population to other arms in A.1. + e.type_ = tinfofornode(c, tn): *void; + return tn; + }; if (k == nkind.N_FLOATLIT) { return mktname(c, "untyped_float"); }; if (k == nkind.N_STRLIT) { return mktname(c, "str"); }; if (k == nkind.N_RUNELIT) { return mktname(c, "rune"); }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 3ab6431d..926d31d8 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -6515,6 +6515,17 @@ type tinfo = struct { under: *tinfo, }; +// #61 audit §1.8 / Rob+Drew convergence 2026-05-20: memoizes +// tinfofornode lookups keyed by AST pointer. Linked-list shape mirrors +// other wwstage-side caches (cgen.aliases, cgen.structs) — sea-of-stars +// over hash-table cleverness, and Sym/Scope already pay the FNV cost +// for the resolver pass. +type tinfocacheent = struct { + key: *node, + val: *tinfo, + cnext: *tinfocacheent, +}; + // ---- tctx — the box of primitive types ------------------------------- type tctx = struct { @@ -6544,6 +6555,7 @@ type tctx = struct { tyuntypedrune: *tinfo, tyuntypedbool: *tinfo, tyuntypednil: *tinfo, + tinfocache: *tinfocacheent, }; // ---- constructors ----------------------------------------------------- @@ -6640,6 +6652,28 @@ export fn typenamed(a: *arena, name: str, under: *tinfo) *tinfo = { return t; }; +// #61 audit §1.8 — A.1 infrastructure: tinfocache lookup/bind. Keyed +// by AST node-pointer so two different N_TNAME("i32") nodes get +// independent entries that both resolve to c.tyi32. Used by +// tinfofornode in check.ww; cgen still reads sizes via primtypesize +// until A.2+ graduates each walker family. +export fn tinfocachelookup(c: *tctx, key: *node) *tinfo = { + let e: *tinfocacheent = c.tinfocache; + for (e != nil) { + if (e.key == key) { return e.val; }; + e = e.cnext; + }; + return nil; +}; + +export fn tinfocachebind(c: *tctx, key: *node, val: *tinfo) void = { + let e: *tinfocacheent = amalloc(c.a, 32u64): *tinfocacheent; + e.key = key; + e.val = val; + e.cnext = c.tinfocache; + c.tinfocache = e; +}; + // ---- predicates ------------------------------------------------------- export fn typeisint(t: *tinfo) bool = { @@ -7825,6 +7859,81 @@ fn foldtointlit(c: *checker, n: *node, v: i64) void = { n.tsuffix = empty; }; +// #61 audit §1.8 — A.1 infrastructure: resolve a type-expression AST +// node to its *tinfo. Mirrors cstage's resolve_type (cmd/wcc/check.c) +// which produces ty_* singletons / arena-allocated composites from a +// Node*. Cache lives in c.tc (typ.ww) so the same shape can be reused +// across modules within one check pass. Rob+Drew convergence +// 2026-05-20: cgen will graduate to read sizes from here in A.2+; A.1 +// just lays the helper and exercises it on N_INTLIT. +// +// Coverage today (A.1): +// - N_TNAME primitive (i32, str, untyped_int, ...) -> tctx singleton +// - N_TNAME alias -> recurse through resolvealias +// - N_TPTR / N_TSLICE / N_TCHAN / N_TBANG -> structural recurse +// Other shapes (TARRAY/TSTRUCT/TFN/TTAGGED/TTUPLE/TENUM) return nil +// for now; A.2+ extends as cgen-site graduation demands them. +fn tinfofornode(c: *checker, n: *node) *tinfo = { + if (n == nil) { return nil; }; + let cached: *tinfo = tinfocachelookup(c.tc, n); + if (cached != nil) { return cached; }; + let r: *tinfo = nil; + let k: nkind = n.kind; + if (k == nkind.N_TNAME) { + let nm: str = n.str; + if (streq(nm, "void")) { r = c.tc.tyvoid; }; + if (streq(nm, "bool")) { r = c.tc.tybool; }; + if (streq(nm, "rune")) { r = c.tc.tyrune; }; + if (streq(nm, "i8")) { r = c.tc.tyi8; }; + if (streq(nm, "i16")) { r = c.tc.tyi16; }; + if (streq(nm, "i32")) { r = c.tc.tyi32; }; + if (streq(nm, "i64")) { r = c.tc.tyi64; }; + if (streq(nm, "u8")) { r = c.tc.tyu8; }; + if (streq(nm, "u16")) { r = c.tc.tyu16; }; + if (streq(nm, "u32")) { r = c.tc.tyu32; }; + if (streq(nm, "u64")) { r = c.tc.tyu64; }; + if (streq(nm, "int")) { r = c.tc.tyint; }; + if (streq(nm, "uint")) { r = c.tc.tyuint; }; + if (streq(nm, "uintptr")) { r = c.tc.tyuintptr; }; + if (streq(nm, "f32")) { r = c.tc.tyf32; }; + if (streq(nm, "f64")) { r = c.tc.tyf64; }; + if (streq(nm, "str")) { r = c.tc.tystr; }; + if (streq(nm, "never")) { r = c.tc.tynever; }; + if (streq(nm, "untyped_int")) { r = c.tc.tyuntypedint; }; + if (streq(nm, "untyped_float")) { r = c.tc.tyuntypedfloat; }; + if (streq(nm, "untyped_str")) { r = c.tc.tyuntypedstr; }; + if (streq(nm, "untyped_rune")) { r = c.tc.tyuntypedrune; }; + if (streq(nm, "untyped_bool")) { r = c.tc.tyuntypedbool; }; + if (streq(nm, "untyped_nil")) { r = c.tc.tyuntypednil; }; + if (r == nil) { + // Alias / user-defined name: resolve via scope and recurse. + // Mirrors astsize's TNAME fallback so the helpers stay in + // lockstep until A.2 collapses each cgen size-walker onto + // tinfo.size directly. + let body: *node = resolvealias(c, n); + if (body != nil && body != n) { + r = tinfofornode(c, body); + }; + }; + } else { if (k == nkind.N_TBANG) { + // #61 audit §1.8: `!T` propagates the inner shape; cstage's + // resolve_type sets ty->iserror on the wrapper but no wwstage + // cgen reader consumes it yet, so A.1 drops the flag and + // returns the inner tinfo unchanged. Mirrors typeeqast's + // unwrapbang pre-walk; graduate alongside the first cgen + // site that needs iserror discrimination. + r = tinfofornode(c, n.lhs); + } else { if (k == nkind.N_TPTR) { + r = typeptr(c.a, tinfofornode(c, n.lhs)); + } else { if (k == nkind.N_TSLICE) { + r = typeslice(c.a, tinfofornode(c, n.lhs)); + } else { if (k == nkind.N_TCHAN) { + r = typechan(c.a, tinfofornode(c, n.lhs)); + };};};};}; + if (r != nil) { tinfocachebind(c.tc, n, r); }; + return r; +}; + // exprtype — best-effort type-AST inference for an expression // node. Handles literals, identifiers, calls, and casts; returns // nil for shapes we don't statically know (binary ops, struct @@ -7832,7 +7941,16 @@ fn foldtointlit(c: *checker, n: *node, v: i64) void = { fn exprtype(c: *checker, e: *node) *node = { if (e == nil) { return nil; }; let k: nkind = e.kind; - if (k == nkind.N_INTLIT) { return mktname(c, "untyped_int"); }; + if (k == nkind.N_INTLIT) { + let tn: *node = mktname(c, "untyped_int"); + // #61 audit §1.8 — A.1 single population site. Cgen still + // reads sizes through primtypesize/slotsize today; A.2+ + // graduates each walker family to read e.type_ instead, + // ending the size-walker cascade (Rob+Drew convergence + // 2026-05-20). Don't extend population to other arms in A.1. + e.type_ = tinfofornode(c, tn): *void; + return tn; + }; if (k == nkind.N_FLOATLIT) { return mktname(c, "untyped_float"); }; if (k == nkind.N_STRLIT) { return mktname(c, "str"); }; if (k == nkind.N_RUNELIT) { return mktname(c, "rune"); };