selfhost/cmd/wcc: extend tinfo coverage + graduate slotsize fast-path (Phase A.2)
tinfofornode (check.ww) covers six more kinds:
- N_TARRAY: typearray on recursed element, size = esz * elen.
- N_TFN: 8B/8B; recurse on ret.
- N_TENUM: storage size/align (default i32 → 4B). Mirrors cstage
check.c:531-542.
- N_TTUPLE: raw element sum + max-align. Mirrors check.c:329-345.
- N_TSTRUCT: per-field align, round total to maxalign. Mirrors
check.c:280-340 / :468-527.
- N_TTAGGED: 8B tag + (max(variant)+7)&~7, al ≥ 8. Mirrors
check.c:347-435.
Cycle-prone arms (TFN/TTUPLE/TSTRUCT/TTAGGED) pre-bind the in-progress
tinfo into the cache BEFORE recursing on subfields so self-referential
shapes (`type node = struct { next: *node, … }`) terminate. Pre-fix
wwdump_ww segfaulted on its own combined source.
More population sites in exprtype: every primitive literal arm
(N_FLOATLIT/N_STRLIT/N_RUNELIT/N_TRUE/N_FALSE/N_VOIDLIT/N_NIL —
A.1 only had N_INTLIT), N_IDENT (propagate from sym.decl.lhs.type_,
eagerly tinfofornode + cache if not yet visited), resolvewalk type-expr
stamping, and resolvefnbody now recurses into N_PARAM.lhs (pre-#61 the
param type-exprs were never walked — every param had nil type_).
slotsize (cgenutil.ww) gains a fast-path: when n.type_ is set AND the
kind is PTR / SLICE / CHAN / FN / STR, return ti.size: i32 directly.
The fallback walker stays alive for primitive scalars, enums, named
structs, inline composites, TARRAY — those need cstage's let_emit_size
slot-pad-to-8 contract (cmd/w6c/cgen.c:691-720) which tinfo doesn't
carry. A.3+ moves padding into the fast-path.
TY_TAGGED *not* in the fast-path (reviewer-61a2 caught this) —
tinfofornode's TTAGGED arm doesn't implement cstage's nullable-pointer
fold (check.c:412-426: `(*T | void) → 8B`). Self-host code happens not
to use that shape today, but the divergence would land latent. Pull
TAGGED until A.3 folds nullable into tinfofornode.
A.2 fallback-hit count under wwdump build: 1554 fast vs 2187 fallback —
partial graduation; expected. 131/131 + 994 + 995 + bootstrap
byte-identical (ww2==ww3==ww4).
This commit is contained in:
@@ -7442,6 +7442,24 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
};
|
||||
};
|
||||
|
||||
// #61 audit §1.8 — A.2 population: stamp tinfo onto type-expression
|
||||
// nodes once their children have been walked (sub-element TNAMEs
|
||||
// are now in scope so resolvealias inside tinfofornode can follow
|
||||
// user-defined aliases). Cgen's slotsize fast-path reads off
|
||||
// n.type_; uncovered shapes fall through to the cstage-mirror
|
||||
// walker until the next sub-commit graduates them.
|
||||
if (k == nkind.N_TNAME || k == nkind.N_TPTR ||
|
||||
k == nkind.N_TSLICE || k == nkind.N_TCHAN ||
|
||||
k == nkind.N_TBANG || k == nkind.N_TARRAY ||
|
||||
k == nkind.N_TFN || k == nkind.N_TSTRUCT ||
|
||||
k == nkind.N_TTUPLE || k == nkind.N_TTAGGED ||
|
||||
k == nkind.N_TENUM) {
|
||||
if (n.type_ == nil) {
|
||||
let ti: *tinfo = tinfofornode(c, n);
|
||||
if (ti != nil) { n.type_ = ti: *void; };
|
||||
};
|
||||
};
|
||||
|
||||
// #42: trigger the size/align/offset fold here so the mutation
|
||||
// fires regardless of context (if-conditions, expression statements,
|
||||
// etc.) — wwstage's exprtype is otherwise called only from
|
||||
@@ -7859,20 +7877,21 @@ 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.
|
||||
// #61 audit §1.8 — resolve a type-expression AST node to its *tinfo.
|
||||
// Mirrors cstage's resolve_type (cmd/wcc/check.c:286-565) 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
|
||||
// reads sizes from here starting with slotsize in A.2; subsequent
|
||||
// sub-commits graduate elemsize/fieldsize/letemitsize/etc. onto the
|
||||
// same pivot.
|
||||
//
|
||||
// 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.
|
||||
// A.2 coverage: primitive TNAME singletons, TNAME aliases (via
|
||||
// resolvealias), TBANG (inner unchanged — see iserror note), TPTR,
|
||||
// TSLICE, TCHAN, TARRAY, TFN, TENUM, TTUPLE, TSTRUCT, TTAGGED. Size
|
||||
// computation tracks cstage natural sizes; cgen's slot-padding
|
||||
// contract (cmd/w6c/cgen.c let_emit_size:691-720 pads narrow scalars
|
||||
// to 8B) stays in slotsize's fallback walker.
|
||||
fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
if (n == nil) { return nil; };
|
||||
let cached: *tinfo = tinfocachelookup(c.tc, n);
|
||||
@@ -7929,7 +7948,119 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
r = typeslice(c.a, tinfofornode(c, n.lhs));
|
||||
} else { if (k == nkind.N_TCHAN) {
|
||||
r = typechan(c.a, tinfofornode(c, n.lhs));
|
||||
};};};};};
|
||||
} else { if (k == nkind.N_TARRAY) {
|
||||
// Cstage cmd/wcc/check.c:314-326: length must be an integer
|
||||
// literal (`[_]T` keeps alen=0 as the inferred-length sentinel
|
||||
// patched at letslotsize-time).
|
||||
let elen: u64 = 0u64;
|
||||
if (n.rhs != nil) {
|
||||
if (n.rhs.kind == nkind.N_INTLIT) { elen = n.rhs.uval; };
|
||||
};
|
||||
r = typearray(c.a, tinfofornode(c, n.lhs), elen);
|
||||
} else { if (k == nkind.N_TFN) {
|
||||
// Cstage cmd/wcc/check.c:437-466: function types are 8B / 8B
|
||||
// (call-target pointer shape). Pre-bind before recursing into
|
||||
// the return type so a recursive `type F = fn() F` self-ref
|
||||
// doesn't spin (cycle-break mirror of the TSTRUCT/TTAGGED
|
||||
// pattern below).
|
||||
r = newtype(c.a, tykind.TY_FN);
|
||||
r.size = 8u64;
|
||||
r.align = 8u64;
|
||||
tinfocachebind(c.tc, n, r);
|
||||
r.ret = tinfofornode(c, n.lhs);
|
||||
} else { if (k == nkind.N_TENUM) {
|
||||
// Cstage cmd/wcc/check.c:529-542: storage type's size/align
|
||||
// (default i32 = 4B/4B). Cgen's slotsize-TENUM fallback pads
|
||||
// to 8B per its stack-slot contract; tinfo.size carries the
|
||||
// raw storage width so size(EnumT) folds to the correct value.
|
||||
r = newtype(c.a, tykind.TY_ENUM);
|
||||
let storage: *tinfo = nil;
|
||||
if (n.lhs != nil) { storage = tinfofornode(c, n.lhs); };
|
||||
if (storage == nil) { storage = c.tc.tyi32; };
|
||||
r.sub = storage;
|
||||
r.size = storage.size;
|
||||
r.align = storage.align;
|
||||
} else { if (k == nkind.N_TTUPLE) {
|
||||
// Cstage cmd/wcc/check.c:329-345: sum of element sizes with
|
||||
// per-element alignment NOT padded — cstage uses raw sums for
|
||||
// tuples and 8B-rounding lives at the call/return ABI layer.
|
||||
// Pre-bind for cycle protection (recursive tuple shapes).
|
||||
r = newtype(c.a, tykind.TY_TUPLE);
|
||||
tinfocachebind(c.tc, n, r);
|
||||
let total: u64 = 0u64;
|
||||
let maxal: u64 = 1u64;
|
||||
let p: *node = n.list;
|
||||
for (p != nil) {
|
||||
let pt: *tinfo = tinfofornode(c, p);
|
||||
if (pt != nil) {
|
||||
if (pt.align > maxal) { maxal = pt.align; };
|
||||
total += pt.size;
|
||||
};
|
||||
p = p.next;
|
||||
};
|
||||
r.size = total;
|
||||
r.align = maxal;
|
||||
} else { if (k == nkind.N_TSTRUCT) {
|
||||
// Cstage cmd/wcc/check.c:468-527: per-field alignment, max
|
||||
// align for the whole record, total rounded up to alignment.
|
||||
// Anonymous-embed promotion is deferred (#13).
|
||||
//
|
||||
// Pre-bind into the cache BEFORE walking fields so a
|
||||
// self-referential pointer field (e.g., `next: *node` inside
|
||||
// `type node = struct {..., next: *node, ...}`) terminates:
|
||||
// the inner tinfofornode(TNAME(node)) resolvealias-recurses
|
||||
// back to this same body node, hits the cache, and returns
|
||||
// the in-progress stub. r.size is filled in below; the stub's
|
||||
// only consumer during the recursion is typeptr (8B/8B
|
||||
// regardless of pointee size), so partial-fill is safe.
|
||||
r = newtype(c.a, tykind.TY_STRUCT);
|
||||
tinfocachebind(c.tc, n, r);
|
||||
let off: u64 = 0u64;
|
||||
let maxalign: u64 = 1u64;
|
||||
let f: *node = n.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == nkind.N_TFIELD) {
|
||||
let ft: *tinfo = tinfofornode(c, f.lhs);
|
||||
if (ft != nil) {
|
||||
if (ft.align > maxalign) { maxalign = ft.align; };
|
||||
if (ft.align > 0u64) {
|
||||
off = (off + ft.align - 1u64) & ~(ft.align - 1u64);
|
||||
};
|
||||
off += ft.size;
|
||||
};
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
if (maxalign > 0u64) {
|
||||
r.size = (off + maxalign - 1u64) & ~(maxalign - 1u64);
|
||||
};
|
||||
r.align = maxalign;
|
||||
} else { if (k == nkind.N_TTAGGED) {
|
||||
// Cstage cmd/wcc/check.c:347-435: 8B tag + max(variant)
|
||||
// rounded up to 8. Variant dedup / never-strip / nullable-fold
|
||||
// stay in cstage's check.c for now — wwstage cgen only reads
|
||||
// the size today, and the AST-level pre-fold (astsize's
|
||||
// TTAGGED arm) already matches the cstage layout numerically
|
||||
// for the shapes selfhost exercises. Pre-bind for cycle
|
||||
// protection (recursive sum-type shapes through NAMED
|
||||
// variants).
|
||||
r = newtype(c.a, tykind.TY_TAGGED);
|
||||
tinfocachebind(c.tc, n, r);
|
||||
let maxsz: u64 = 0u64;
|
||||
let al: u64 = 8u64;
|
||||
let v: *node = n.list;
|
||||
for (v != nil) {
|
||||
let vt: *tinfo = tinfofornode(c, v);
|
||||
if (vt != nil) {
|
||||
if (vt.size > maxsz) { maxsz = vt.size; };
|
||||
if (vt.align > al) { al = vt.align; };
|
||||
};
|
||||
v = v.next;
|
||||
};
|
||||
let pad: u64 = (maxsz + 7u64) & ~7u64;
|
||||
r.size = 8u64 + pad;
|
||||
r.align = al;
|
||||
};};};};};};};};};};};
|
||||
if (r != nil) { tinfocachebind(c.tc, n, r); };
|
||||
return r;
|
||||
};
|
||||
@@ -7941,28 +8072,70 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
fn exprtype(c: *checker, e: *node) *node = {
|
||||
if (e == nil) { return nil; };
|
||||
let k: nkind = e.kind;
|
||||
// #61 audit §1.8 — A.2 widens A.1's single N_INTLIT population to
|
||||
// every primitive literal arm + N_IDENT. Cgen size walkers
|
||||
// (slotsize first; elemsize/fieldsize/letemitsize follow) consult
|
||||
// node.type_ as the SSoT; populating literals + idents closes the
|
||||
// loop from the read side.
|
||||
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"); };
|
||||
if (k == nkind.N_TRUE) { return mktname(c, "bool"); };
|
||||
if (k == nkind.N_FALSE) { return mktname(c, "bool"); };
|
||||
if (k == nkind.N_VOIDLIT) { return mktname(c, "void"); };
|
||||
if (k == nkind.N_NIL) { return mktname(c, "untyped_nil"); };
|
||||
if (k == nkind.N_FLOATLIT) {
|
||||
let tn: *node = mktname(c, "untyped_float");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_STRLIT) {
|
||||
let tn: *node = mktname(c, "str");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_RUNELIT) {
|
||||
let tn: *node = mktname(c, "rune");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_TRUE) {
|
||||
let tn: *node = mktname(c, "bool");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_FALSE) {
|
||||
let tn: *node = mktname(c, "bool");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_VOIDLIT) {
|
||||
let tn: *node = mktname(c, "void");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_NIL) {
|
||||
let tn: *node = mktname(c, "untyped_nil");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_IDENT) {
|
||||
let s: *sym = scopelookup(c.cur, e.str);
|
||||
if (s == nil) { return nil; };
|
||||
if (s.decl == nil) { return nil; };
|
||||
return s.decl.lhs;
|
||||
let t: *node = s.decl.lhs;
|
||||
// Propagate the declared type's tinfo onto the use site so
|
||||
// downstream cgen walkers can read n.type_ off an ident.
|
||||
if (t != nil) {
|
||||
if (t.type_ != nil) {
|
||||
e.type_ = t.type_;
|
||||
} else {
|
||||
let ti: *tinfo = tinfofornode(c, t);
|
||||
if (ti != nil) {
|
||||
e.type_ = ti: *void;
|
||||
t.type_ = ti: *void;
|
||||
};
|
||||
};
|
||||
};
|
||||
return t;
|
||||
};
|
||||
if (k == nkind.N_CAST) {
|
||||
// `expr: T` — explicit cast; the type expr is e.rhs.
|
||||
@@ -8728,6 +8901,17 @@ fn resolvefnbody(c: *checker, fnnode: *node) void = {
|
||||
let outer: *scope = c.cur;
|
||||
c.cur = newscope(c.a, c.cur);
|
||||
installparams(c, fnnode.list);
|
||||
// #61 audit §1.8 — A.2: walk each param's declared type-expr so
|
||||
// tinfofornode stamps n.type_ on it. installparams binds the name
|
||||
// but never recurses into the type; without this, cgen's slotsize
|
||||
// fast-path hits the fallback for every param load/store.
|
||||
let p: *node = fnnode.list;
|
||||
for (p != nil) {
|
||||
if (p.kind == nkind.N_PARAM) {
|
||||
if (p.lhs != nil) { resolvewalk(c, p.lhs); };
|
||||
};
|
||||
p = p.next;
|
||||
};
|
||||
let prevret: *node = c.fnret;
|
||||
c.fnret = fnnode.lhs; // return type AST, used by `?` check
|
||||
if (fnnode.body != nil) {
|
||||
@@ -10735,6 +10919,31 @@ export fn letslotsize(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
|
||||
fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
// #61 audit §1.8 — A.2 graduation: read tinfo.size off the populated
|
||||
// type-expression node when its kind matches the cstage natural-size
|
||||
// SSoT. Filtered set covers shapes whose tinfo.size already encodes
|
||||
// the cgen slot-size contract: pointer-like (PTR/CHAN/FN), slice
|
||||
// (SLICE), and str (STR). Other kinds flow through the fallback
|
||||
// walker. TAGGED stays out because cstage's resolve_type folds
|
||||
// `(*T | void)` to a single 8B pointer (cmd/wcc/check.c:412-426)
|
||||
// but tinfofornode's TTAGGED arm doesn't yet — graduating TAGGED
|
||||
// would shrink that fold's slot from 16 to 8 on the wwstage side.
|
||||
// Primitive scalars + enums + inline TUPLE / TSTRUCT also keep
|
||||
// flowing through the fallback walker because cgen's slot-pad-to-8
|
||||
// contract (cmd/w6c/cgen.c let_emit_size:691-720 and the per-field
|
||||
// slot rounding in registerstruct/letslotsize) lives there, not in
|
||||
// tinfo.size. Subsequent sub-commits collapse the remaining shapes
|
||||
// onto the same pivot once cstage parity catches up (#61 Phase A —
|
||||
// see the audit doc for the staged plan).
|
||||
if (typn != nil && typn.type_ != nil) {
|
||||
let ti: *tinfo = typn.type_: *tinfo;
|
||||
let kk: tykind = ti.kind;
|
||||
if (kk == tykind.TY_PTR || kk == tykind.TY_SLICE ||
|
||||
kk == tykind.TY_CHAN || kk == tykind.TY_FN ||
|
||||
kk == tykind.TY_STR) {
|
||||
return ti.size: i32;
|
||||
};
|
||||
};
|
||||
if (typn == nil) { return 8; };
|
||||
let k: nkind = typn.kind;
|
||||
// `!T` carries T's memory layout; the error-tag bit lives in the
|
||||
|
||||
@@ -1941,6 +1941,31 @@ export fn letslotsize(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
|
||||
fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
// #61 audit §1.8 — A.2 graduation: read tinfo.size off the populated
|
||||
// type-expression node when its kind matches the cstage natural-size
|
||||
// SSoT. Filtered set covers shapes whose tinfo.size already encodes
|
||||
// the cgen slot-size contract: pointer-like (PTR/CHAN/FN), slice
|
||||
// (SLICE), and str (STR). Other kinds flow through the fallback
|
||||
// walker. TAGGED stays out because cstage's resolve_type folds
|
||||
// `(*T | void)` to a single 8B pointer (cmd/wcc/check.c:412-426)
|
||||
// but tinfofornode's TTAGGED arm doesn't yet — graduating TAGGED
|
||||
// would shrink that fold's slot from 16 to 8 on the wwstage side.
|
||||
// Primitive scalars + enums + inline TUPLE / TSTRUCT also keep
|
||||
// flowing through the fallback walker because cgen's slot-pad-to-8
|
||||
// contract (cmd/w6c/cgen.c let_emit_size:691-720 and the per-field
|
||||
// slot rounding in registerstruct/letslotsize) lives there, not in
|
||||
// tinfo.size. Subsequent sub-commits collapse the remaining shapes
|
||||
// onto the same pivot once cstage parity catches up (#61 Phase A —
|
||||
// see the audit doc for the staged plan).
|
||||
if (typn != nil && typn.type_ != nil) {
|
||||
let ti: *tinfo = typn.type_: *tinfo;
|
||||
let kk: tykind = ti.kind;
|
||||
if (kk == tykind.TY_PTR || kk == tykind.TY_SLICE ||
|
||||
kk == tykind.TY_CHAN || kk == tykind.TY_FN ||
|
||||
kk == tykind.TY_STR) {
|
||||
return ti.size: i32;
|
||||
};
|
||||
};
|
||||
if (typn == nil) { return 8; };
|
||||
let k: nkind = typn.kind;
|
||||
// `!T` carries T's memory layout; the error-tag bit lives in the
|
||||
|
||||
@@ -414,6 +414,24 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
};
|
||||
};
|
||||
|
||||
// #61 audit §1.8 — A.2 population: stamp tinfo onto type-expression
|
||||
// nodes once their children have been walked (sub-element TNAMEs
|
||||
// are now in scope so resolvealias inside tinfofornode can follow
|
||||
// user-defined aliases). Cgen's slotsize fast-path reads off
|
||||
// n.type_; uncovered shapes fall through to the cstage-mirror
|
||||
// walker until the next sub-commit graduates them.
|
||||
if (k == nkind.N_TNAME || k == nkind.N_TPTR ||
|
||||
k == nkind.N_TSLICE || k == nkind.N_TCHAN ||
|
||||
k == nkind.N_TBANG || k == nkind.N_TARRAY ||
|
||||
k == nkind.N_TFN || k == nkind.N_TSTRUCT ||
|
||||
k == nkind.N_TTUPLE || k == nkind.N_TTAGGED ||
|
||||
k == nkind.N_TENUM) {
|
||||
if (n.type_ == nil) {
|
||||
let ti: *tinfo = tinfofornode(c, n);
|
||||
if (ti != nil) { n.type_ = ti: *void; };
|
||||
};
|
||||
};
|
||||
|
||||
// #42: trigger the size/align/offset fold here so the mutation
|
||||
// fires regardless of context (if-conditions, expression statements,
|
||||
// etc.) — wwstage's exprtype is otherwise called only from
|
||||
@@ -831,20 +849,21 @@ 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.
|
||||
// #61 audit §1.8 — resolve a type-expression AST node to its *tinfo.
|
||||
// Mirrors cstage's resolve_type (cmd/wcc/check.c:286-565) 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
|
||||
// reads sizes from here starting with slotsize in A.2; subsequent
|
||||
// sub-commits graduate elemsize/fieldsize/letemitsize/etc. onto the
|
||||
// same pivot.
|
||||
//
|
||||
// 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.
|
||||
// A.2 coverage: primitive TNAME singletons, TNAME aliases (via
|
||||
// resolvealias), TBANG (inner unchanged — see iserror note), TPTR,
|
||||
// TSLICE, TCHAN, TARRAY, TFN, TENUM, TTUPLE, TSTRUCT, TTAGGED. Size
|
||||
// computation tracks cstage natural sizes; cgen's slot-padding
|
||||
// contract (cmd/w6c/cgen.c let_emit_size:691-720 pads narrow scalars
|
||||
// to 8B) stays in slotsize's fallback walker.
|
||||
fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
if (n == nil) { return nil; };
|
||||
let cached: *tinfo = tinfocachelookup(c.tc, n);
|
||||
@@ -901,7 +920,119 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
r = typeslice(c.a, tinfofornode(c, n.lhs));
|
||||
} else { if (k == nkind.N_TCHAN) {
|
||||
r = typechan(c.a, tinfofornode(c, n.lhs));
|
||||
};};};};};
|
||||
} else { if (k == nkind.N_TARRAY) {
|
||||
// Cstage cmd/wcc/check.c:314-326: length must be an integer
|
||||
// literal (`[_]T` keeps alen=0 as the inferred-length sentinel
|
||||
// patched at letslotsize-time).
|
||||
let elen: u64 = 0u64;
|
||||
if (n.rhs != nil) {
|
||||
if (n.rhs.kind == nkind.N_INTLIT) { elen = n.rhs.uval; };
|
||||
};
|
||||
r = typearray(c.a, tinfofornode(c, n.lhs), elen);
|
||||
} else { if (k == nkind.N_TFN) {
|
||||
// Cstage cmd/wcc/check.c:437-466: function types are 8B / 8B
|
||||
// (call-target pointer shape). Pre-bind before recursing into
|
||||
// the return type so a recursive `type F = fn() F` self-ref
|
||||
// doesn't spin (cycle-break mirror of the TSTRUCT/TTAGGED
|
||||
// pattern below).
|
||||
r = newtype(c.a, tykind.TY_FN);
|
||||
r.size = 8u64;
|
||||
r.align = 8u64;
|
||||
tinfocachebind(c.tc, n, r);
|
||||
r.ret = tinfofornode(c, n.lhs);
|
||||
} else { if (k == nkind.N_TENUM) {
|
||||
// Cstage cmd/wcc/check.c:529-542: storage type's size/align
|
||||
// (default i32 = 4B/4B). Cgen's slotsize-TENUM fallback pads
|
||||
// to 8B per its stack-slot contract; tinfo.size carries the
|
||||
// raw storage width so size(EnumT) folds to the correct value.
|
||||
r = newtype(c.a, tykind.TY_ENUM);
|
||||
let storage: *tinfo = nil;
|
||||
if (n.lhs != nil) { storage = tinfofornode(c, n.lhs); };
|
||||
if (storage == nil) { storage = c.tc.tyi32; };
|
||||
r.sub = storage;
|
||||
r.size = storage.size;
|
||||
r.align = storage.align;
|
||||
} else { if (k == nkind.N_TTUPLE) {
|
||||
// Cstage cmd/wcc/check.c:329-345: sum of element sizes with
|
||||
// per-element alignment NOT padded — cstage uses raw sums for
|
||||
// tuples and 8B-rounding lives at the call/return ABI layer.
|
||||
// Pre-bind for cycle protection (recursive tuple shapes).
|
||||
r = newtype(c.a, tykind.TY_TUPLE);
|
||||
tinfocachebind(c.tc, n, r);
|
||||
let total: u64 = 0u64;
|
||||
let maxal: u64 = 1u64;
|
||||
let p: *node = n.list;
|
||||
for (p != nil) {
|
||||
let pt: *tinfo = tinfofornode(c, p);
|
||||
if (pt != nil) {
|
||||
if (pt.align > maxal) { maxal = pt.align; };
|
||||
total += pt.size;
|
||||
};
|
||||
p = p.next;
|
||||
};
|
||||
r.size = total;
|
||||
r.align = maxal;
|
||||
} else { if (k == nkind.N_TSTRUCT) {
|
||||
// Cstage cmd/wcc/check.c:468-527: per-field alignment, max
|
||||
// align for the whole record, total rounded up to alignment.
|
||||
// Anonymous-embed promotion is deferred (#13).
|
||||
//
|
||||
// Pre-bind into the cache BEFORE walking fields so a
|
||||
// self-referential pointer field (e.g., `next: *node` inside
|
||||
// `type node = struct {..., next: *node, ...}`) terminates:
|
||||
// the inner tinfofornode(TNAME(node)) resolvealias-recurses
|
||||
// back to this same body node, hits the cache, and returns
|
||||
// the in-progress stub. r.size is filled in below; the stub's
|
||||
// only consumer during the recursion is typeptr (8B/8B
|
||||
// regardless of pointee size), so partial-fill is safe.
|
||||
r = newtype(c.a, tykind.TY_STRUCT);
|
||||
tinfocachebind(c.tc, n, r);
|
||||
let off: u64 = 0u64;
|
||||
let maxalign: u64 = 1u64;
|
||||
let f: *node = n.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == nkind.N_TFIELD) {
|
||||
let ft: *tinfo = tinfofornode(c, f.lhs);
|
||||
if (ft != nil) {
|
||||
if (ft.align > maxalign) { maxalign = ft.align; };
|
||||
if (ft.align > 0u64) {
|
||||
off = (off + ft.align - 1u64) & ~(ft.align - 1u64);
|
||||
};
|
||||
off += ft.size;
|
||||
};
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
if (maxalign > 0u64) {
|
||||
r.size = (off + maxalign - 1u64) & ~(maxalign - 1u64);
|
||||
};
|
||||
r.align = maxalign;
|
||||
} else { if (k == nkind.N_TTAGGED) {
|
||||
// Cstage cmd/wcc/check.c:347-435: 8B tag + max(variant)
|
||||
// rounded up to 8. Variant dedup / never-strip / nullable-fold
|
||||
// stay in cstage's check.c for now — wwstage cgen only reads
|
||||
// the size today, and the AST-level pre-fold (astsize's
|
||||
// TTAGGED arm) already matches the cstage layout numerically
|
||||
// for the shapes selfhost exercises. Pre-bind for cycle
|
||||
// protection (recursive sum-type shapes through NAMED
|
||||
// variants).
|
||||
r = newtype(c.a, tykind.TY_TAGGED);
|
||||
tinfocachebind(c.tc, n, r);
|
||||
let maxsz: u64 = 0u64;
|
||||
let al: u64 = 8u64;
|
||||
let v: *node = n.list;
|
||||
for (v != nil) {
|
||||
let vt: *tinfo = tinfofornode(c, v);
|
||||
if (vt != nil) {
|
||||
if (vt.size > maxsz) { maxsz = vt.size; };
|
||||
if (vt.align > al) { al = vt.align; };
|
||||
};
|
||||
v = v.next;
|
||||
};
|
||||
let pad: u64 = (maxsz + 7u64) & ~7u64;
|
||||
r.size = 8u64 + pad;
|
||||
r.align = al;
|
||||
};};};};};};};};};};};
|
||||
if (r != nil) { tinfocachebind(c.tc, n, r); };
|
||||
return r;
|
||||
};
|
||||
@@ -913,28 +1044,70 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
fn exprtype(c: *checker, e: *node) *node = {
|
||||
if (e == nil) { return nil; };
|
||||
let k: nkind = e.kind;
|
||||
// #61 audit §1.8 — A.2 widens A.1's single N_INTLIT population to
|
||||
// every primitive literal arm + N_IDENT. Cgen size walkers
|
||||
// (slotsize first; elemsize/fieldsize/letemitsize follow) consult
|
||||
// node.type_ as the SSoT; populating literals + idents closes the
|
||||
// loop from the read side.
|
||||
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"); };
|
||||
if (k == nkind.N_TRUE) { return mktname(c, "bool"); };
|
||||
if (k == nkind.N_FALSE) { return mktname(c, "bool"); };
|
||||
if (k == nkind.N_VOIDLIT) { return mktname(c, "void"); };
|
||||
if (k == nkind.N_NIL) { return mktname(c, "untyped_nil"); };
|
||||
if (k == nkind.N_FLOATLIT) {
|
||||
let tn: *node = mktname(c, "untyped_float");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_STRLIT) {
|
||||
let tn: *node = mktname(c, "str");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_RUNELIT) {
|
||||
let tn: *node = mktname(c, "rune");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_TRUE) {
|
||||
let tn: *node = mktname(c, "bool");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_FALSE) {
|
||||
let tn: *node = mktname(c, "bool");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_VOIDLIT) {
|
||||
let tn: *node = mktname(c, "void");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_NIL) {
|
||||
let tn: *node = mktname(c, "untyped_nil");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_IDENT) {
|
||||
let s: *sym = scopelookup(c.cur, e.str);
|
||||
if (s == nil) { return nil; };
|
||||
if (s.decl == nil) { return nil; };
|
||||
return s.decl.lhs;
|
||||
let t: *node = s.decl.lhs;
|
||||
// Propagate the declared type's tinfo onto the use site so
|
||||
// downstream cgen walkers can read n.type_ off an ident.
|
||||
if (t != nil) {
|
||||
if (t.type_ != nil) {
|
||||
e.type_ = t.type_;
|
||||
} else {
|
||||
let ti: *tinfo = tinfofornode(c, t);
|
||||
if (ti != nil) {
|
||||
e.type_ = ti: *void;
|
||||
t.type_ = ti: *void;
|
||||
};
|
||||
};
|
||||
};
|
||||
return t;
|
||||
};
|
||||
if (k == nkind.N_CAST) {
|
||||
// `expr: T` — explicit cast; the type expr is e.rhs.
|
||||
@@ -1700,6 +1873,17 @@ fn resolvefnbody(c: *checker, fnnode: *node) void = {
|
||||
let outer: *scope = c.cur;
|
||||
c.cur = newscope(c.a, c.cur);
|
||||
installparams(c, fnnode.list);
|
||||
// #61 audit §1.8 — A.2: walk each param's declared type-expr so
|
||||
// tinfofornode stamps n.type_ on it. installparams binds the name
|
||||
// but never recurses into the type; without this, cgen's slotsize
|
||||
// fast-path hits the fallback for every param load/store.
|
||||
let p: *node = fnnode.list;
|
||||
for (p != nil) {
|
||||
if (p.kind == nkind.N_PARAM) {
|
||||
if (p.lhs != nil) { resolvewalk(c, p.lhs); };
|
||||
};
|
||||
p = p.next;
|
||||
};
|
||||
let prevret: *node = c.fnret;
|
||||
c.fnret = fnnode.lhs; // return type AST, used by `?` check
|
||||
if (fnnode.body != nil) {
|
||||
|
||||
@@ -7442,6 +7442,24 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
};
|
||||
};
|
||||
|
||||
// #61 audit §1.8 — A.2 population: stamp tinfo onto type-expression
|
||||
// nodes once their children have been walked (sub-element TNAMEs
|
||||
// are now in scope so resolvealias inside tinfofornode can follow
|
||||
// user-defined aliases). Cgen's slotsize fast-path reads off
|
||||
// n.type_; uncovered shapes fall through to the cstage-mirror
|
||||
// walker until the next sub-commit graduates them.
|
||||
if (k == nkind.N_TNAME || k == nkind.N_TPTR ||
|
||||
k == nkind.N_TSLICE || k == nkind.N_TCHAN ||
|
||||
k == nkind.N_TBANG || k == nkind.N_TARRAY ||
|
||||
k == nkind.N_TFN || k == nkind.N_TSTRUCT ||
|
||||
k == nkind.N_TTUPLE || k == nkind.N_TTAGGED ||
|
||||
k == nkind.N_TENUM) {
|
||||
if (n.type_ == nil) {
|
||||
let ti: *tinfo = tinfofornode(c, n);
|
||||
if (ti != nil) { n.type_ = ti: *void; };
|
||||
};
|
||||
};
|
||||
|
||||
// #42: trigger the size/align/offset fold here so the mutation
|
||||
// fires regardless of context (if-conditions, expression statements,
|
||||
// etc.) — wwstage's exprtype is otherwise called only from
|
||||
@@ -7859,20 +7877,21 @@ 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.
|
||||
// #61 audit §1.8 — resolve a type-expression AST node to its *tinfo.
|
||||
// Mirrors cstage's resolve_type (cmd/wcc/check.c:286-565) 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
|
||||
// reads sizes from here starting with slotsize in A.2; subsequent
|
||||
// sub-commits graduate elemsize/fieldsize/letemitsize/etc. onto the
|
||||
// same pivot.
|
||||
//
|
||||
// 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.
|
||||
// A.2 coverage: primitive TNAME singletons, TNAME aliases (via
|
||||
// resolvealias), TBANG (inner unchanged — see iserror note), TPTR,
|
||||
// TSLICE, TCHAN, TARRAY, TFN, TENUM, TTUPLE, TSTRUCT, TTAGGED. Size
|
||||
// computation tracks cstage natural sizes; cgen's slot-padding
|
||||
// contract (cmd/w6c/cgen.c let_emit_size:691-720 pads narrow scalars
|
||||
// to 8B) stays in slotsize's fallback walker.
|
||||
fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
if (n == nil) { return nil; };
|
||||
let cached: *tinfo = tinfocachelookup(c.tc, n);
|
||||
@@ -7929,7 +7948,119 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
r = typeslice(c.a, tinfofornode(c, n.lhs));
|
||||
} else { if (k == nkind.N_TCHAN) {
|
||||
r = typechan(c.a, tinfofornode(c, n.lhs));
|
||||
};};};};};
|
||||
} else { if (k == nkind.N_TARRAY) {
|
||||
// Cstage cmd/wcc/check.c:314-326: length must be an integer
|
||||
// literal (`[_]T` keeps alen=0 as the inferred-length sentinel
|
||||
// patched at letslotsize-time).
|
||||
let elen: u64 = 0u64;
|
||||
if (n.rhs != nil) {
|
||||
if (n.rhs.kind == nkind.N_INTLIT) { elen = n.rhs.uval; };
|
||||
};
|
||||
r = typearray(c.a, tinfofornode(c, n.lhs), elen);
|
||||
} else { if (k == nkind.N_TFN) {
|
||||
// Cstage cmd/wcc/check.c:437-466: function types are 8B / 8B
|
||||
// (call-target pointer shape). Pre-bind before recursing into
|
||||
// the return type so a recursive `type F = fn() F` self-ref
|
||||
// doesn't spin (cycle-break mirror of the TSTRUCT/TTAGGED
|
||||
// pattern below).
|
||||
r = newtype(c.a, tykind.TY_FN);
|
||||
r.size = 8u64;
|
||||
r.align = 8u64;
|
||||
tinfocachebind(c.tc, n, r);
|
||||
r.ret = tinfofornode(c, n.lhs);
|
||||
} else { if (k == nkind.N_TENUM) {
|
||||
// Cstage cmd/wcc/check.c:529-542: storage type's size/align
|
||||
// (default i32 = 4B/4B). Cgen's slotsize-TENUM fallback pads
|
||||
// to 8B per its stack-slot contract; tinfo.size carries the
|
||||
// raw storage width so size(EnumT) folds to the correct value.
|
||||
r = newtype(c.a, tykind.TY_ENUM);
|
||||
let storage: *tinfo = nil;
|
||||
if (n.lhs != nil) { storage = tinfofornode(c, n.lhs); };
|
||||
if (storage == nil) { storage = c.tc.tyi32; };
|
||||
r.sub = storage;
|
||||
r.size = storage.size;
|
||||
r.align = storage.align;
|
||||
} else { if (k == nkind.N_TTUPLE) {
|
||||
// Cstage cmd/wcc/check.c:329-345: sum of element sizes with
|
||||
// per-element alignment NOT padded — cstage uses raw sums for
|
||||
// tuples and 8B-rounding lives at the call/return ABI layer.
|
||||
// Pre-bind for cycle protection (recursive tuple shapes).
|
||||
r = newtype(c.a, tykind.TY_TUPLE);
|
||||
tinfocachebind(c.tc, n, r);
|
||||
let total: u64 = 0u64;
|
||||
let maxal: u64 = 1u64;
|
||||
let p: *node = n.list;
|
||||
for (p != nil) {
|
||||
let pt: *tinfo = tinfofornode(c, p);
|
||||
if (pt != nil) {
|
||||
if (pt.align > maxal) { maxal = pt.align; };
|
||||
total += pt.size;
|
||||
};
|
||||
p = p.next;
|
||||
};
|
||||
r.size = total;
|
||||
r.align = maxal;
|
||||
} else { if (k == nkind.N_TSTRUCT) {
|
||||
// Cstage cmd/wcc/check.c:468-527: per-field alignment, max
|
||||
// align for the whole record, total rounded up to alignment.
|
||||
// Anonymous-embed promotion is deferred (#13).
|
||||
//
|
||||
// Pre-bind into the cache BEFORE walking fields so a
|
||||
// self-referential pointer field (e.g., `next: *node` inside
|
||||
// `type node = struct {..., next: *node, ...}`) terminates:
|
||||
// the inner tinfofornode(TNAME(node)) resolvealias-recurses
|
||||
// back to this same body node, hits the cache, and returns
|
||||
// the in-progress stub. r.size is filled in below; the stub's
|
||||
// only consumer during the recursion is typeptr (8B/8B
|
||||
// regardless of pointee size), so partial-fill is safe.
|
||||
r = newtype(c.a, tykind.TY_STRUCT);
|
||||
tinfocachebind(c.tc, n, r);
|
||||
let off: u64 = 0u64;
|
||||
let maxalign: u64 = 1u64;
|
||||
let f: *node = n.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == nkind.N_TFIELD) {
|
||||
let ft: *tinfo = tinfofornode(c, f.lhs);
|
||||
if (ft != nil) {
|
||||
if (ft.align > maxalign) { maxalign = ft.align; };
|
||||
if (ft.align > 0u64) {
|
||||
off = (off + ft.align - 1u64) & ~(ft.align - 1u64);
|
||||
};
|
||||
off += ft.size;
|
||||
};
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
if (maxalign > 0u64) {
|
||||
r.size = (off + maxalign - 1u64) & ~(maxalign - 1u64);
|
||||
};
|
||||
r.align = maxalign;
|
||||
} else { if (k == nkind.N_TTAGGED) {
|
||||
// Cstage cmd/wcc/check.c:347-435: 8B tag + max(variant)
|
||||
// rounded up to 8. Variant dedup / never-strip / nullable-fold
|
||||
// stay in cstage's check.c for now — wwstage cgen only reads
|
||||
// the size today, and the AST-level pre-fold (astsize's
|
||||
// TTAGGED arm) already matches the cstage layout numerically
|
||||
// for the shapes selfhost exercises. Pre-bind for cycle
|
||||
// protection (recursive sum-type shapes through NAMED
|
||||
// variants).
|
||||
r = newtype(c.a, tykind.TY_TAGGED);
|
||||
tinfocachebind(c.tc, n, r);
|
||||
let maxsz: u64 = 0u64;
|
||||
let al: u64 = 8u64;
|
||||
let v: *node = n.list;
|
||||
for (v != nil) {
|
||||
let vt: *tinfo = tinfofornode(c, v);
|
||||
if (vt != nil) {
|
||||
if (vt.size > maxsz) { maxsz = vt.size; };
|
||||
if (vt.align > al) { al = vt.align; };
|
||||
};
|
||||
v = v.next;
|
||||
};
|
||||
let pad: u64 = (maxsz + 7u64) & ~7u64;
|
||||
r.size = 8u64 + pad;
|
||||
r.align = al;
|
||||
};};};};};};};};};};};
|
||||
if (r != nil) { tinfocachebind(c.tc, n, r); };
|
||||
return r;
|
||||
};
|
||||
@@ -7941,28 +8072,70 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
fn exprtype(c: *checker, e: *node) *node = {
|
||||
if (e == nil) { return nil; };
|
||||
let k: nkind = e.kind;
|
||||
// #61 audit §1.8 — A.2 widens A.1's single N_INTLIT population to
|
||||
// every primitive literal arm + N_IDENT. Cgen size walkers
|
||||
// (slotsize first; elemsize/fieldsize/letemitsize follow) consult
|
||||
// node.type_ as the SSoT; populating literals + idents closes the
|
||||
// loop from the read side.
|
||||
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"); };
|
||||
if (k == nkind.N_TRUE) { return mktname(c, "bool"); };
|
||||
if (k == nkind.N_FALSE) { return mktname(c, "bool"); };
|
||||
if (k == nkind.N_VOIDLIT) { return mktname(c, "void"); };
|
||||
if (k == nkind.N_NIL) { return mktname(c, "untyped_nil"); };
|
||||
if (k == nkind.N_FLOATLIT) {
|
||||
let tn: *node = mktname(c, "untyped_float");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_STRLIT) {
|
||||
let tn: *node = mktname(c, "str");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_RUNELIT) {
|
||||
let tn: *node = mktname(c, "rune");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_TRUE) {
|
||||
let tn: *node = mktname(c, "bool");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_FALSE) {
|
||||
let tn: *node = mktname(c, "bool");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_VOIDLIT) {
|
||||
let tn: *node = mktname(c, "void");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_NIL) {
|
||||
let tn: *node = mktname(c, "untyped_nil");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
if (k == nkind.N_IDENT) {
|
||||
let s: *sym = scopelookup(c.cur, e.str);
|
||||
if (s == nil) { return nil; };
|
||||
if (s.decl == nil) { return nil; };
|
||||
return s.decl.lhs;
|
||||
let t: *node = s.decl.lhs;
|
||||
// Propagate the declared type's tinfo onto the use site so
|
||||
// downstream cgen walkers can read n.type_ off an ident.
|
||||
if (t != nil) {
|
||||
if (t.type_ != nil) {
|
||||
e.type_ = t.type_;
|
||||
} else {
|
||||
let ti: *tinfo = tinfofornode(c, t);
|
||||
if (ti != nil) {
|
||||
e.type_ = ti: *void;
|
||||
t.type_ = ti: *void;
|
||||
};
|
||||
};
|
||||
};
|
||||
return t;
|
||||
};
|
||||
if (k == nkind.N_CAST) {
|
||||
// `expr: T` — explicit cast; the type expr is e.rhs.
|
||||
@@ -8728,6 +8901,17 @@ fn resolvefnbody(c: *checker, fnnode: *node) void = {
|
||||
let outer: *scope = c.cur;
|
||||
c.cur = newscope(c.a, c.cur);
|
||||
installparams(c, fnnode.list);
|
||||
// #61 audit §1.8 — A.2: walk each param's declared type-expr so
|
||||
// tinfofornode stamps n.type_ on it. installparams binds the name
|
||||
// but never recurses into the type; without this, cgen's slotsize
|
||||
// fast-path hits the fallback for every param load/store.
|
||||
let p: *node = fnnode.list;
|
||||
for (p != nil) {
|
||||
if (p.kind == nkind.N_PARAM) {
|
||||
if (p.lhs != nil) { resolvewalk(c, p.lhs); };
|
||||
};
|
||||
p = p.next;
|
||||
};
|
||||
let prevret: *node = c.fnret;
|
||||
c.fnret = fnnode.lhs; // return type AST, used by `?` check
|
||||
if (fnnode.body != nil) {
|
||||
@@ -10735,6 +10919,31 @@ export fn letslotsize(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
|
||||
fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
// #61 audit §1.8 — A.2 graduation: read tinfo.size off the populated
|
||||
// type-expression node when its kind matches the cstage natural-size
|
||||
// SSoT. Filtered set covers shapes whose tinfo.size already encodes
|
||||
// the cgen slot-size contract: pointer-like (PTR/CHAN/FN), slice
|
||||
// (SLICE), and str (STR). Other kinds flow through the fallback
|
||||
// walker. TAGGED stays out because cstage's resolve_type folds
|
||||
// `(*T | void)` to a single 8B pointer (cmd/wcc/check.c:412-426)
|
||||
// but tinfofornode's TTAGGED arm doesn't yet — graduating TAGGED
|
||||
// would shrink that fold's slot from 16 to 8 on the wwstage side.
|
||||
// Primitive scalars + enums + inline TUPLE / TSTRUCT also keep
|
||||
// flowing through the fallback walker because cgen's slot-pad-to-8
|
||||
// contract (cmd/w6c/cgen.c let_emit_size:691-720 and the per-field
|
||||
// slot rounding in registerstruct/letslotsize) lives there, not in
|
||||
// tinfo.size. Subsequent sub-commits collapse the remaining shapes
|
||||
// onto the same pivot once cstage parity catches up (#61 Phase A —
|
||||
// see the audit doc for the staged plan).
|
||||
if (typn != nil && typn.type_ != nil) {
|
||||
let ti: *tinfo = typn.type_: *tinfo;
|
||||
let kk: tykind = ti.kind;
|
||||
if (kk == tykind.TY_PTR || kk == tykind.TY_SLICE ||
|
||||
kk == tykind.TY_CHAN || kk == tykind.TY_FN ||
|
||||
kk == tykind.TY_STR) {
|
||||
return ti.size: i32;
|
||||
};
|
||||
};
|
||||
if (typn == nil) { return 8; };
|
||||
let k: nkind = typn.kind;
|
||||
// `!T` carries T's memory layout; the error-tag bit lives in the
|
||||
|
||||
Reference in New Issue
Block a user