diff --git a/lib/ww/typ.ww b/lib/ww/typ.ww index 53876703..f426503d 100644 --- a/lib/ww/typ.ww +++ b/lib/ww/typ.ww @@ -336,6 +336,94 @@ export fn typeissigned(t: *tinfo) bool = { return typeisint(t); }; +// typeisstr — TY_STR (and TY_UNTYPED_STR for literals pre-default). +// Cite cstage cgen.c:159 `type_isstr` — single TY_NAMED peel, accepts +// the same untyped form. ww walks the .under chain so alias-of-alias +// (`type s2 = s1; type s1 = str;`) lands the same way. +export fn typeisstr(t: *tinfo) bool = { + if (t == nil) { return false; }; + let k: tykind = t.kind; + if (k == tykind.TY_STR) { return true; }; + if (k == tykind.TY_UNTYPED_STR) { return true; }; + if (k == tykind.TY_NAMED) { return typeisstr(t.under); }; + return false; +}; + +// typeisslice — TY_SLICE. Cite cstage cgen.c:174 `type_isslice`. +export fn typeisslice(t: *tinfo) bool = { + if (t == nil) { return false; }; + let k: tykind = t.kind; + if (k == tykind.TY_SLICE) { return true; }; + if (k == tykind.TY_NAMED) { return typeisslice(t.under); }; + return false; +}; + +// typeistagged — TY_TAGGED (alias-aware). Cite cstage cgen.c:516 +// `type_istagged` — same single-peel shape. The node-keyed wwstage +// helper this replaces also unwrapped a leading N_TBANG so +// `type error = !(invalid | overflow);` registered as tagged. Post- +// A.6.2 the TBANG unwrap is handled by tinfofornode (check.ww:1145- +// 1152 returns the inner tinfo unchanged) so we recover the cstage +// semantics with the bare kind check + NAMED chase. +export fn typeistagged(t: *tinfo) bool = { + if (t == nil) { return false; }; + let k: tykind = t.kind; + if (k == tykind.TY_TAGGED) { return true; }; + if (k == tykind.TY_NAMED) { return typeistagged(t.under); }; + return false; +}; + +// typeisf32 — narrower-than-typeisfloat: only TY_F32 (after alias +// chase). Cite cstage cgen.c:188 `type_isf32`. Used to pick MOVSS vs +// MOVSD and the SS-variant arithmetic / cast opcodes. +export fn typeisf32(t: *tinfo) bool = { + if (t == nil) { return false; }; + let k: tykind = t.kind; + if (k == tykind.TY_F32) { return true; }; + if (k == tykind.TY_NAMED) { return typeisf32(t.under); }; + return false; +}; + +// typeisnullable — TY_TAGGED with the `(*T | void)` one-word fold. +// Cite cstage cgen.c:396 `type_isnullable`. The .nullable flag is +// stamped by tinfofornode (check.ww:1309-1318) when the two-variant +// shape matches. +export fn typeisnullable(t: *tinfo) bool = { + if (t == nil) { return false; }; + let k: tykind = t.kind; + if (k == tykind.TY_TAGGED) { return t.nullable != 0; }; + if (k == tykind.TY_NAMED) { return typeisnullable(t.under); }; + return false; +}; + +// typeis8byteprim — does this type take exactly one 8-byte stack +// slot (ptr / fn / chan / 64-bit int / scalar primitive padded up to +// 8 / `[N]T` whose natural width is 8) rather than a wider aggregate? +// Mirrors the ladder cstage's cgen.c N_LET zero-init takes on `sz==8` +// (cmd/wcc/check.c sizing + cgen.c N_LET). The node-keyed wwstage +// helper this replaces predates tinfo and AST-walked TBANG / TNAME +// alias chains; tinfofornode now collapses TBANG (check.ww:1145) and +// TY_NAMED.under carries the chain, so the tinfo walk handles every +// shape the AST walker did. +export fn typeis8byteprim(t: *tinfo) bool = { + if (t == nil) { return false; }; + let k: tykind = t.kind; + if (k == tykind.TY_PTR) { return true; }; + if (k == tykind.TY_FN) { return true; }; + if (k == tykind.TY_CHAN) { return true; }; + if (k == tykind.TY_SLICE) { return false; }; + if (k == tykind.TY_TUPLE) { return false; }; + if (k == tykind.TY_TAGGED) { return false; }; + if (k == tykind.TY_STR) { return false; }; + if (k == tykind.TY_STRUCT) { return false; }; + if (k == tykind.TY_ARRAY) { return t.size == 8u64; }; + if (k == tykind.TY_NAMED) { return typeis8byteprim(t.under); }; + // Remaining: primitives (i8/u8/.../i64/u64/bool/rune/f32/f64/ + // int/uint/uintptr) and TY_VOID. All slot-pad to 8 and zero-init + // in cstage's `sz==8` branch. + return true; +}; + export fn typeisuntyped(t: *tinfo) bool = { if (t == nil) { return false; }; let k: tykind = t.kind; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index af3e0d4e..bb00d4a9 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -6666,6 +6666,94 @@ export fn typeissigned(t: *tinfo) bool = { return typeisint(t); }; +// typeisstr — TY_STR (and TY_UNTYPED_STR for literals pre-default). +// Cite cstage cgen.c:159 `type_isstr` — single TY_NAMED peel, accepts +// the same untyped form. ww walks the .under chain so alias-of-alias +// (`type s2 = s1; type s1 = str;`) lands the same way. +export fn typeisstr(t: *tinfo) bool = { + if (t == nil) { return false; }; + let k: tykind = t.kind; + if (k == tykind.TY_STR) { return true; }; + if (k == tykind.TY_UNTYPED_STR) { return true; }; + if (k == tykind.TY_NAMED) { return typeisstr(t.under); }; + return false; +}; + +// typeisslice — TY_SLICE. Cite cstage cgen.c:174 `type_isslice`. +export fn typeisslice(t: *tinfo) bool = { + if (t == nil) { return false; }; + let k: tykind = t.kind; + if (k == tykind.TY_SLICE) { return true; }; + if (k == tykind.TY_NAMED) { return typeisslice(t.under); }; + return false; +}; + +// typeistagged — TY_TAGGED (alias-aware). Cite cstage cgen.c:516 +// `type_istagged` — same single-peel shape. The node-keyed wwstage +// helper this replaces also unwrapped a leading N_TBANG so +// `type error = !(invalid | overflow);` registered as tagged. Post- +// A.6.2 the TBANG unwrap is handled by tinfofornode (check.ww:1145- +// 1152 returns the inner tinfo unchanged) so we recover the cstage +// semantics with the bare kind check + NAMED chase. +export fn typeistagged(t: *tinfo) bool = { + if (t == nil) { return false; }; + let k: tykind = t.kind; + if (k == tykind.TY_TAGGED) { return true; }; + if (k == tykind.TY_NAMED) { return typeistagged(t.under); }; + return false; +}; + +// typeisf32 — narrower-than-typeisfloat: only TY_F32 (after alias +// chase). Cite cstage cgen.c:188 `type_isf32`. Used to pick MOVSS vs +// MOVSD and the SS-variant arithmetic / cast opcodes. +export fn typeisf32(t: *tinfo) bool = { + if (t == nil) { return false; }; + let k: tykind = t.kind; + if (k == tykind.TY_F32) { return true; }; + if (k == tykind.TY_NAMED) { return typeisf32(t.under); }; + return false; +}; + +// typeisnullable — TY_TAGGED with the `(*T | void)` one-word fold. +// Cite cstage cgen.c:396 `type_isnullable`. The .nullable flag is +// stamped by tinfofornode (check.ww:1309-1318) when the two-variant +// shape matches. +export fn typeisnullable(t: *tinfo) bool = { + if (t == nil) { return false; }; + let k: tykind = t.kind; + if (k == tykind.TY_TAGGED) { return t.nullable != 0; }; + if (k == tykind.TY_NAMED) { return typeisnullable(t.under); }; + return false; +}; + +// typeis8byteprim — does this type take exactly one 8-byte stack +// slot (ptr / fn / chan / 64-bit int / scalar primitive padded up to +// 8 / `[N]T` whose natural width is 8) rather than a wider aggregate? +// Mirrors the ladder cstage's cgen.c N_LET zero-init takes on `sz==8` +// (cmd/wcc/check.c sizing + cgen.c N_LET). The node-keyed wwstage +// helper this replaces predates tinfo and AST-walked TBANG / TNAME +// alias chains; tinfofornode now collapses TBANG (check.ww:1145) and +// TY_NAMED.under carries the chain, so the tinfo walk handles every +// shape the AST walker did. +export fn typeis8byteprim(t: *tinfo) bool = { + if (t == nil) { return false; }; + let k: tykind = t.kind; + if (k == tykind.TY_PTR) { return true; }; + if (k == tykind.TY_FN) { return true; }; + if (k == tykind.TY_CHAN) { return true; }; + if (k == tykind.TY_SLICE) { return false; }; + if (k == tykind.TY_TUPLE) { return false; }; + if (k == tykind.TY_TAGGED) { return false; }; + if (k == tykind.TY_STR) { return false; }; + if (k == tykind.TY_STRUCT) { return false; }; + if (k == tykind.TY_ARRAY) { return t.size == 8u64; }; + if (k == tykind.TY_NAMED) { return typeis8byteprim(t.under); }; + // Remaining: primitives (i8/u8/.../i64/u64/bool/rune/f32/f64/ + // int/uint/uintptr) and TY_VOID. All slot-pad to 8 and zero-init + // in cstage's `sz==8` branch. + return true; +}; + export fn typeisuntyped(t: *tinfo) bool = { if (t == nil) { return false; }; let k: tykind = t.kind; @@ -11118,72 +11206,13 @@ fn typenameisunsigned(nm: str) bool = { }; // typeis8byteprimitive — does this type take exactly one 8-byte -// slot (pointer / fn-ptr / 64-bit int / chan / scalar primitive -// padded up to 8) rather than a wider aggregate? Used by nkind.N_LET -// zero-init to mirror C cgen's "only zero if sz == 8 at the type -// level" rule. Strings (16), slices (24), tagged unions (>=16), -// tuples (16), structs (varies), arrays — all fall through to -// false here even when their *slot* rounds up to 8. +// slot rather than a wider aggregate? One-liner via typeis8byteprim +// (cstage N_LET sz==8 ladder SSoT). t.type_ is stamped at check.ww +// L426-436 for every type-AST kind callers reach. Collapsed onto the +// tinfo helper per A.6.3b (#46). fn typeis8byteprimitive(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; - let k: nkind = t.kind; - if (k == nkind.N_TPTR) { return true; }; - if (k == nkind.N_TFN) { return true; }; - if (k == nkind.N_TCHAN) { return true; }; - if (k == nkind.N_TSLICE) { return false; }; - if (k == nkind.N_TARRAY) { - // C cgen (cmd/w6c/cgen.c:3317) zero-inits TY_ARRAY whenever - // its raw byte size is 8 — e.g. `[8]bool`, `[2]i32`, `[4]i16`, - // `[1]i64`. Mirror that here so the wwstage matches. - let lenn: *node = t.rhs; - let elemn: *node = t.lhs; - if (lenn == nil) { return false; }; - if (lenn.kind != nkind.N_INTLIT) { return false; }; - let elen: i64 = lenn.uval: i64; - let esz: i32 = 8; - if (elemn != nil) { - if (elemn.kind == nkind.N_TNAME) { - let ps: i32 = primsize(elemn.str); - if (ps > 0) { esz = ps; }; - }; - }; - return (esz: i64 * elen) == 8i64; - }; - if (k == nkind.N_TTUPLE) { return false; }; - if (k == nkind.N_TTAGGED){ return false; }; - // STATUS-3 #22: `!T` carries the error flag on T's underlying - // shape (cmd/wcc/check.c:290 resolve_type N_TBANG copies T's - // kind, just sets iserror). cstage's N_LET sizes off lu->kind, - // so `!void`/`!i32` land in the sz=8 default and `!str`/`!slice` - // keep their composite slot. Defer to the inner type so - // `let e: !void;` mirrors cstage's MOVQ $0 while `!str` falls - // through to the multi-word fill. - if (k == nkind.N_TBANG) { return typeis8byteprimitive(c, t.lhs); }; - if (k == nkind.N_TNAME) { - let nm: str = t.str; - if (streq(nm, "str")) { return false; }; - // Plain `void` slot: cstage sz=8 default → MOVQ $0. The let- - // decl is a phantom (a tagged-union variant tag carrier), but - // the slot is still 8B and zero-inits like any other prim. - if (streq(nm, "void")) { return true; }; - // Struct alias: not a primitive even if the slot is 8B. - if (structlookup(c, nm) != nil) { return false; }; - // Primitive (i8/u8/.../i64/u64/bool/rune/f32/f64/int/...). - // All of these get slot-padded to 8 and zero-init in C. - if (primsize(nm) > 0) { return true; }; - // STATUS-3 #22: alias to `!T` or to `void` (Hare-style error - // type / phantom variant). cstage resolves the alias and - // lands on sz=8 default. Follow through aliaslookup so - // `type invalid = !void;` and `type done = void;` zero-init. - if (c != nil) { - let aliased: *node = aliaslookup(c, nm); - if (aliased != nil) { - return typeis8byteprimitive(c, aliased); - }; - }; - return false; - }; - return false; + return typeis8byteprim(t.type_: *tinfo); }; // elemissignedc — given an indexable type-AST (`*T`, `[]T`, `[N]T`), @@ -12419,54 +12448,20 @@ fn collectstructs(c: *cgen, file: *node) void = { }; }; -// `type X = str;` aliases) to `str`. Takes *cgen so it can walk the -// alias chain registered at file load. -fn isstrtyperaw(t: *node) bool = { - if (t == nil) { return false; }; - if (t.kind == nkind.N_TNAME) { - let nm: str = t.str; - if (streq(nm, "str")) { return true; }; - }; - return false; -}; - +// isstrtype — alias-aware. Reads the stamped tinfo so `str`, +// `type alias = str`, `!str`, and chained aliases all route to the +// str-shaped slot. Cite cstage cgen.c:159 `type_isstr` SSoT. +// Collapsed onto typeisstr per A.6.3b (#46); the prior AST walker is +// reconstituted by typeisstr's TY_NAMED chase + tinfofornode's +// TBANG-unwrap (check.ww:1145). fn isstrtype(c: *cgen, t: *node) bool = { - if (isstrtyperaw(t)) { return true; }; - if (c == nil) { return false; }; - let r: *node = resolvetype(c, t); - if (isstrtyperaw(r)) { return true; }; - // `parserr = !str` — `!T` aliases shouldn't hide their - // underlying type from str-routing. Unwrap and re-check. - if (r != nil) { - if (r.kind == nkind.N_TBANG) { - let inner: *node = r.lhs; - if (isstrtyperaw(inner)) { return true; }; - if (inner != nil) { - let r2: *node = resolvetype(c, inner); - if (isstrtyperaw(r2)) { return true; }; - }; - }; - }; - return false; -}; - -fn isslicetyperaw(t: *node) bool = { if (t == nil) { return false; }; - if (t.kind == nkind.N_TSLICE) { return true; }; - return false; + return typeisstr(t.type_: *tinfo); }; fn isslicetype(c: *cgen, t: *node) bool = { - if (isslicetyperaw(t)) { return true; }; - if (c == nil) { return false; }; - let r: *node = resolvetype(c, t); - return isslicetyperaw(r); -}; - -fn istaggedtyperaw(t: *node) bool = { if (t == nil) { return false; }; - if (t.kind == nkind.N_TTAGGED) { return true; }; - return false; + return typeisslice(t.type_: *tinfo); }; // resolvetagged — return the underlying N_TTAGGED node for `t`, or nil @@ -12587,64 +12582,31 @@ fn structparamsize(c: *cgen, t: *node) i32 = { return si.totsize; }; -// istaggedtype — alias-aware. Mirrors isstrtype: follow N_TNAME to its -// underlying decl, then unwrap a leading N_TBANG so `type error = -// !(invalid | overflow);` is still recognised as tagged. Without the -// bang unwrap the prologue treats the param as scalar (8B), spilling -// only DI and losing the value-word SI; the match read of slot+8 then -// trails into saved BP. +// istaggedtype — alias-aware. Reads stamped tinfo so `T`, +// `type alias = (A|B)`, `type error = !(invalid|overflow)` all +// resolve to TY_TAGGED — tinfofornode handles the N_TBANG unwrap +// (check.ww:1145) so we don't re-walk it here. Cite cstage cgen.c +// (`type_chase_named` + TY_TAGGED). Collapsed per A.6.3b (#46). fn istaggedtype(c: *cgen, t: *node) bool = { - if (istaggedtyperaw(t)) { return true; }; - if (c == nil) { return false; }; - let r: *node = resolvetype(c, t); - if (istaggedtyperaw(r)) { return true; }; - if (r != nil) { - if (r.kind == nkind.N_TBANG) { - let inner: *node = r.lhs; - if (istaggedtyperaw(inner)) { return true; }; - if (inner != nil) { - let r2: *node = resolvetype(c, inner); - if (istaggedtyperaw(r2)) { return true; }; - }; - }; - }; - return false; -}; - -// isf32typeraw / isf64typeraw — bare TNAME check, no alias resolution. -fn isf32typeraw(t: *node) bool = { if (t == nil) { return false; }; - if (t.kind != nkind.N_TNAME) { return false; }; - return streq(t.str, "f32"); + return typeistagged(t.type_: *tinfo); }; -fn isf64typeraw(t: *node) bool = { - if (t == nil) { return false; }; - if (t.kind != nkind.N_TNAME) { return false; }; - return streq(t.str, "f64"); -}; - -// isfloattype — f32 / f64 (and aliases of those). Used by cglet, -// cgident, cgassign, cgbin, cgcast, cgcall, cgreturn, fn-prologue to -// dispatch the MOVSS/MOVSD-shaped paths. +// isfloattype — f32 / f64 / untyped_float (alias-aware). Cite cstage +// cgen.c:117 `cg_isfloat`. Dispatches MOVSS/MOVSD-shaped paths across +// cglet, cgident, cgassign, cgbin, cgcast, cgcall, cgreturn, fn- +// prologue. Collapsed per A.6.3b (#46). export fn isfloattype(c: *cgen, t: *node) bool = { - if (isf32typeraw(t)) { return true; }; - if (isf64typeraw(t)) { return true; }; - if (c == nil) { return false; }; - let r: *node = resolvetype(c, t); - if (isf32typeraw(r)) { return true; }; - if (isf64typeraw(r)) { return true; }; - return false; + if (t == nil) { return false; }; + return typeisfloat(t.type_: *tinfo); }; -// isf32type — narrower predicate: true only for f32 (after alias -// resolution). f64 returns false. Used to pick MOVSS vs MOVSD and -// the SS-variant arithmetic / cast opcodes. +// isf32type — narrower: true only for f32 (after alias chase). Cite +// cstage cgen.c:188 `type_isf32`. Picks MOVSS vs MOVSD and the SS- +// variant arithmetic / cast opcodes. Collapsed per A.6.3b (#46). export fn isf32type(c: *cgen, t: *node) bool = { - if (isf32typeraw(t)) { return true; }; - if (c == nil) { return false; }; - let r: *node = resolvetype(c, t); - return isf32typeraw(r); + if (t == nil) { return false; }; + return typeisf32(t.type_: *tinfo); }; // exprfloatkind — classify an expression's value-class so callers can @@ -12765,30 +12727,23 @@ export fn exprfloatkind(c: *cgen, n: *node) i32 = { return 0; }; -// isnullabletype — nkind.N_TTAGGED with exactly two children, one *T and -// one `void`. Folds to a single 8-byte pointer slot per Hare's -// `(*T | null)` semantics. Mirrors check.c's resolve_type detection. +// isnullabletype — `(*T | void)` one-word fold per Hare's +// `(*T | null)` semantics. Cite cstage cgen.c:396 `type_isnullable`; +// the .nullable flag lands on tinfo at check.ww:1309-1318 when the +// two-variant shape matches. Collapsed per A.6.3b (#46). export fn isnullabletype(t: *node) bool = { if (t == nil) { return false; }; - if (t.kind != nkind.N_TTAGGED) { return false; }; - let a: *node = t.list; - if (a == nil) { return false; }; - let b: *node = a.next; - if (b == nil) { return false; }; - if (b.next != nil) { return false; }; - let aptr: bool = (a.kind == nkind.N_TPTR); - let bptr: bool = (b.kind == nkind.N_TPTR); - let avoid: bool = (a.kind == nkind.N_TNAME); - if (avoid) { avoid = streq(a.str, "void"); }; - let bvoid: bool = (b.kind == nkind.N_TNAME); - if (bvoid) { bvoid = streq(b.str, "void"); }; - if (aptr) { if (bvoid) { return true; }; }; - if (avoid) { if (bptr) { return true; }; }; - return false; + return typeisnullable(t.type_: *tinfo); }; // nullableptrtag — 0-based index of the *T variant in a nullable -// union. The void variant takes the other slot (0 or 1). +// union. The void variant takes the other slot (0 or 1). AST-keyed +// for now: tinfofornode doesn't populate TY_TAGGED.params yet +// (check.ww:1287-1337 sets size/align/nullable but not the variant +// chain), so the tinfo equivalent of cstage cgen.c:405 +// `nullable_ptr_tag` can't read params today. Graduates to a pure +// tinfo helper in A.6.3f (#50) alongside the variant-index work and +// the tparam-population glue. export fn nullableptrtag(t: *node) i32 = { if (t == nil) { return 0; }; if (t.kind != nkind.N_TTAGGED) { return 0; }; @@ -15663,7 +15618,7 @@ fn cgdot(c: *cgen, n: *node) void = { }; if (tp != nil) { let tpt: *node = tp.lhs; - if (isstrtyperaw(tpt)) { + if (isstrtype(c, tpt)) { emitline("\tMOVQ\t"); emitoff((lc.off + foff + 0): i64); emitline("(BP), AX\n"); @@ -20714,8 +20669,8 @@ fn cglet(c: *cgen, n: *node) void = { let p1t: *node = nil; if (p0 != nil) { p0t = p0.lhs; }; if (p1 != nil) { p1t = p1.lhs; }; - let s0_is_str: bool = isstrtyperaw(p0t); - let s1_is_str: bool = isstrtyperaw(p1t); + let s0_is_str: bool = isstrtype(c, p0t); + let s1_is_str: bool = isstrtype(c, p1t); if (p0 != nil) { if (p1 != nil) { if (s0_is_str != s1_is_str) { @@ -21257,8 +21212,8 @@ fn cgmlet(c: *cgen, n: *node) void = { if (t0 == nil) { t0 = p0t; }; if (t1 == nil) { t1 = p1t; }; - let s0_is_str: bool = isstrtyperaw(t0); - let s1_is_str: bool = isstrtyperaw(t1); + let s0_is_str: bool = isstrtype(c, t0); + let s1_is_str: bool = isstrtype(c, t1); cgexpr(c, rhs); diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 9c5b37ff..dcd508c2 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -1485,7 +1485,7 @@ fn cgdot(c: *cgen, n: *node) void = { }; if (tp != nil) { let tpt: *node = tp.lhs; - if (isstrtyperaw(tpt)) { + if (isstrtype(c, tpt)) { emitline("\tMOVQ\t"); emitoff((lc.off + foff + 0): i64); emitline("(BP), AX\n"); diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index 6a6600de..8714edfe 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -712,8 +712,8 @@ fn cglet(c: *cgen, n: *node) void = { let p1t: *node = nil; if (p0 != nil) { p0t = p0.lhs; }; if (p1 != nil) { p1t = p1.lhs; }; - let s0_is_str: bool = isstrtyperaw(p0t); - let s1_is_str: bool = isstrtyperaw(p1t); + let s0_is_str: bool = isstrtype(c, p0t); + let s1_is_str: bool = isstrtype(c, p1t); if (p0 != nil) { if (p1 != nil) { if (s0_is_str != s1_is_str) { @@ -1255,8 +1255,8 @@ fn cgmlet(c: *cgen, n: *node) void = { if (t0 == nil) { t0 = p0t; }; if (t1 == nil) { t1 = p1t; }; - let s0_is_str: bool = isstrtyperaw(t0); - let s1_is_str: bool = isstrtyperaw(t1); + let s0_is_str: bool = isstrtype(c, t0); + let s1_is_str: bool = isstrtype(c, t1); cgexpr(c, rhs); diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 2b12575e..470b8333 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -867,72 +867,13 @@ fn typenameisunsigned(nm: str) bool = { }; // typeis8byteprimitive — does this type take exactly one 8-byte -// slot (pointer / fn-ptr / 64-bit int / chan / scalar primitive -// padded up to 8) rather than a wider aggregate? Used by nkind.N_LET -// zero-init to mirror C cgen's "only zero if sz == 8 at the type -// level" rule. Strings (16), slices (24), tagged unions (>=16), -// tuples (16), structs (varies), arrays — all fall through to -// false here even when their *slot* rounds up to 8. +// slot rather than a wider aggregate? One-liner via typeis8byteprim +// (cstage N_LET sz==8 ladder SSoT). t.type_ is stamped at check.ww +// L426-436 for every type-AST kind callers reach. Collapsed onto the +// tinfo helper per A.6.3b (#46). fn typeis8byteprimitive(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; - let k: nkind = t.kind; - if (k == nkind.N_TPTR) { return true; }; - if (k == nkind.N_TFN) { return true; }; - if (k == nkind.N_TCHAN) { return true; }; - if (k == nkind.N_TSLICE) { return false; }; - if (k == nkind.N_TARRAY) { - // C cgen (cmd/w6c/cgen.c:3317) zero-inits TY_ARRAY whenever - // its raw byte size is 8 — e.g. `[8]bool`, `[2]i32`, `[4]i16`, - // `[1]i64`. Mirror that here so the wwstage matches. - let lenn: *node = t.rhs; - let elemn: *node = t.lhs; - if (lenn == nil) { return false; }; - if (lenn.kind != nkind.N_INTLIT) { return false; }; - let elen: i64 = lenn.uval: i64; - let esz: i32 = 8; - if (elemn != nil) { - if (elemn.kind == nkind.N_TNAME) { - let ps: i32 = primsize(elemn.str); - if (ps > 0) { esz = ps; }; - }; - }; - return (esz: i64 * elen) == 8i64; - }; - if (k == nkind.N_TTUPLE) { return false; }; - if (k == nkind.N_TTAGGED){ return false; }; - // STATUS-3 #22: `!T` carries the error flag on T's underlying - // shape (cmd/wcc/check.c:290 resolve_type N_TBANG copies T's - // kind, just sets iserror). cstage's N_LET sizes off lu->kind, - // so `!void`/`!i32` land in the sz=8 default and `!str`/`!slice` - // keep their composite slot. Defer to the inner type so - // `let e: !void;` mirrors cstage's MOVQ $0 while `!str` falls - // through to the multi-word fill. - if (k == nkind.N_TBANG) { return typeis8byteprimitive(c, t.lhs); }; - if (k == nkind.N_TNAME) { - let nm: str = t.str; - if (streq(nm, "str")) { return false; }; - // Plain `void` slot: cstage sz=8 default → MOVQ $0. The let- - // decl is a phantom (a tagged-union variant tag carrier), but - // the slot is still 8B and zero-inits like any other prim. - if (streq(nm, "void")) { return true; }; - // Struct alias: not a primitive even if the slot is 8B. - if (structlookup(c, nm) != nil) { return false; }; - // Primitive (i8/u8/.../i64/u64/bool/rune/f32/f64/int/...). - // All of these get slot-padded to 8 and zero-init in C. - if (primsize(nm) > 0) { return true; }; - // STATUS-3 #22: alias to `!T` or to `void` (Hare-style error - // type / phantom variant). cstage resolves the alias and - // lands on sz=8 default. Follow through aliaslookup so - // `type invalid = !void;` and `type done = void;` zero-init. - if (c != nil) { - let aliased: *node = aliaslookup(c, nm); - if (aliased != nil) { - return typeis8byteprimitive(c, aliased); - }; - }; - return false; - }; - return false; + return typeis8byteprim(t.type_: *tinfo); }; // elemissignedc — given an indexable type-AST (`*T`, `[]T`, `[N]T`), @@ -2168,54 +2109,20 @@ fn collectstructs(c: *cgen, file: *node) void = { }; }; -// `type X = str;` aliases) to `str`. Takes *cgen so it can walk the -// alias chain registered at file load. -fn isstrtyperaw(t: *node) bool = { - if (t == nil) { return false; }; - if (t.kind == nkind.N_TNAME) { - let nm: str = t.str; - if (streq(nm, "str")) { return true; }; - }; - return false; -}; - +// isstrtype — alias-aware. Reads the stamped tinfo so `str`, +// `type alias = str`, `!str`, and chained aliases all route to the +// str-shaped slot. Cite cstage cgen.c:159 `type_isstr` SSoT. +// Collapsed onto typeisstr per A.6.3b (#46); the prior AST walker is +// reconstituted by typeisstr's TY_NAMED chase + tinfofornode's +// TBANG-unwrap (check.ww:1145). fn isstrtype(c: *cgen, t: *node) bool = { - if (isstrtyperaw(t)) { return true; }; - if (c == nil) { return false; }; - let r: *node = resolvetype(c, t); - if (isstrtyperaw(r)) { return true; }; - // `parserr = !str` — `!T` aliases shouldn't hide their - // underlying type from str-routing. Unwrap and re-check. - if (r != nil) { - if (r.kind == nkind.N_TBANG) { - let inner: *node = r.lhs; - if (isstrtyperaw(inner)) { return true; }; - if (inner != nil) { - let r2: *node = resolvetype(c, inner); - if (isstrtyperaw(r2)) { return true; }; - }; - }; - }; - return false; -}; - -fn isslicetyperaw(t: *node) bool = { if (t == nil) { return false; }; - if (t.kind == nkind.N_TSLICE) { return true; }; - return false; + return typeisstr(t.type_: *tinfo); }; fn isslicetype(c: *cgen, t: *node) bool = { - if (isslicetyperaw(t)) { return true; }; - if (c == nil) { return false; }; - let r: *node = resolvetype(c, t); - return isslicetyperaw(r); -}; - -fn istaggedtyperaw(t: *node) bool = { if (t == nil) { return false; }; - if (t.kind == nkind.N_TTAGGED) { return true; }; - return false; + return typeisslice(t.type_: *tinfo); }; // resolvetagged — return the underlying N_TTAGGED node for `t`, or nil @@ -2336,64 +2243,31 @@ fn structparamsize(c: *cgen, t: *node) i32 = { return si.totsize; }; -// istaggedtype — alias-aware. Mirrors isstrtype: follow N_TNAME to its -// underlying decl, then unwrap a leading N_TBANG so `type error = -// !(invalid | overflow);` is still recognised as tagged. Without the -// bang unwrap the prologue treats the param as scalar (8B), spilling -// only DI and losing the value-word SI; the match read of slot+8 then -// trails into saved BP. +// istaggedtype — alias-aware. Reads stamped tinfo so `T`, +// `type alias = (A|B)`, `type error = !(invalid|overflow)` all +// resolve to TY_TAGGED — tinfofornode handles the N_TBANG unwrap +// (check.ww:1145) so we don't re-walk it here. Cite cstage cgen.c +// (`type_chase_named` + TY_TAGGED). Collapsed per A.6.3b (#46). fn istaggedtype(c: *cgen, t: *node) bool = { - if (istaggedtyperaw(t)) { return true; }; - if (c == nil) { return false; }; - let r: *node = resolvetype(c, t); - if (istaggedtyperaw(r)) { return true; }; - if (r != nil) { - if (r.kind == nkind.N_TBANG) { - let inner: *node = r.lhs; - if (istaggedtyperaw(inner)) { return true; }; - if (inner != nil) { - let r2: *node = resolvetype(c, inner); - if (istaggedtyperaw(r2)) { return true; }; - }; - }; - }; - return false; -}; - -// isf32typeraw / isf64typeraw — bare TNAME check, no alias resolution. -fn isf32typeraw(t: *node) bool = { if (t == nil) { return false; }; - if (t.kind != nkind.N_TNAME) { return false; }; - return streq(t.str, "f32"); + return typeistagged(t.type_: *tinfo); }; -fn isf64typeraw(t: *node) bool = { - if (t == nil) { return false; }; - if (t.kind != nkind.N_TNAME) { return false; }; - return streq(t.str, "f64"); -}; - -// isfloattype — f32 / f64 (and aliases of those). Used by cglet, -// cgident, cgassign, cgbin, cgcast, cgcall, cgreturn, fn-prologue to -// dispatch the MOVSS/MOVSD-shaped paths. +// isfloattype — f32 / f64 / untyped_float (alias-aware). Cite cstage +// cgen.c:117 `cg_isfloat`. Dispatches MOVSS/MOVSD-shaped paths across +// cglet, cgident, cgassign, cgbin, cgcast, cgcall, cgreturn, fn- +// prologue. Collapsed per A.6.3b (#46). export fn isfloattype(c: *cgen, t: *node) bool = { - if (isf32typeraw(t)) { return true; }; - if (isf64typeraw(t)) { return true; }; - if (c == nil) { return false; }; - let r: *node = resolvetype(c, t); - if (isf32typeraw(r)) { return true; }; - if (isf64typeraw(r)) { return true; }; - return false; + if (t == nil) { return false; }; + return typeisfloat(t.type_: *tinfo); }; -// isf32type — narrower predicate: true only for f32 (after alias -// resolution). f64 returns false. Used to pick MOVSS vs MOVSD and -// the SS-variant arithmetic / cast opcodes. +// isf32type — narrower: true only for f32 (after alias chase). Cite +// cstage cgen.c:188 `type_isf32`. Picks MOVSS vs MOVSD and the SS- +// variant arithmetic / cast opcodes. Collapsed per A.6.3b (#46). export fn isf32type(c: *cgen, t: *node) bool = { - if (isf32typeraw(t)) { return true; }; - if (c == nil) { return false; }; - let r: *node = resolvetype(c, t); - return isf32typeraw(r); + if (t == nil) { return false; }; + return typeisf32(t.type_: *tinfo); }; // exprfloatkind — classify an expression's value-class so callers can @@ -2514,30 +2388,23 @@ export fn exprfloatkind(c: *cgen, n: *node) i32 = { return 0; }; -// isnullabletype — nkind.N_TTAGGED with exactly two children, one *T and -// one `void`. Folds to a single 8-byte pointer slot per Hare's -// `(*T | null)` semantics. Mirrors check.c's resolve_type detection. +// isnullabletype — `(*T | void)` one-word fold per Hare's +// `(*T | null)` semantics. Cite cstage cgen.c:396 `type_isnullable`; +// the .nullable flag lands on tinfo at check.ww:1309-1318 when the +// two-variant shape matches. Collapsed per A.6.3b (#46). export fn isnullabletype(t: *node) bool = { if (t == nil) { return false; }; - if (t.kind != nkind.N_TTAGGED) { return false; }; - let a: *node = t.list; - if (a == nil) { return false; }; - let b: *node = a.next; - if (b == nil) { return false; }; - if (b.next != nil) { return false; }; - let aptr: bool = (a.kind == nkind.N_TPTR); - let bptr: bool = (b.kind == nkind.N_TPTR); - let avoid: bool = (a.kind == nkind.N_TNAME); - if (avoid) { avoid = streq(a.str, "void"); }; - let bvoid: bool = (b.kind == nkind.N_TNAME); - if (bvoid) { bvoid = streq(b.str, "void"); }; - if (aptr) { if (bvoid) { return true; }; }; - if (avoid) { if (bptr) { return true; }; }; - return false; + return typeisnullable(t.type_: *tinfo); }; // nullableptrtag — 0-based index of the *T variant in a nullable -// union. The void variant takes the other slot (0 or 1). +// union. The void variant takes the other slot (0 or 1). AST-keyed +// for now: tinfofornode doesn't populate TY_TAGGED.params yet +// (check.ww:1287-1337 sets size/align/nullable but not the variant +// chain), so the tinfo equivalent of cstage cgen.c:405 +// `nullable_ptr_tag` can't read params today. Graduates to a pure +// tinfo helper in A.6.3f (#50) alongside the variant-index work and +// the tparam-population glue. export fn nullableptrtag(t: *node) i32 = { if (t == nil) { return 0; }; if (t.kind != nkind.N_TTAGGED) { return 0; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 2d232f9c..87dcf619 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -6666,6 +6666,94 @@ export fn typeissigned(t: *tinfo) bool = { return typeisint(t); }; +// typeisstr — TY_STR (and TY_UNTYPED_STR for literals pre-default). +// Cite cstage cgen.c:159 `type_isstr` — single TY_NAMED peel, accepts +// the same untyped form. ww walks the .under chain so alias-of-alias +// (`type s2 = s1; type s1 = str;`) lands the same way. +export fn typeisstr(t: *tinfo) bool = { + if (t == nil) { return false; }; + let k: tykind = t.kind; + if (k == tykind.TY_STR) { return true; }; + if (k == tykind.TY_UNTYPED_STR) { return true; }; + if (k == tykind.TY_NAMED) { return typeisstr(t.under); }; + return false; +}; + +// typeisslice — TY_SLICE. Cite cstage cgen.c:174 `type_isslice`. +export fn typeisslice(t: *tinfo) bool = { + if (t == nil) { return false; }; + let k: tykind = t.kind; + if (k == tykind.TY_SLICE) { return true; }; + if (k == tykind.TY_NAMED) { return typeisslice(t.under); }; + return false; +}; + +// typeistagged — TY_TAGGED (alias-aware). Cite cstage cgen.c:516 +// `type_istagged` — same single-peel shape. The node-keyed wwstage +// helper this replaces also unwrapped a leading N_TBANG so +// `type error = !(invalid | overflow);` registered as tagged. Post- +// A.6.2 the TBANG unwrap is handled by tinfofornode (check.ww:1145- +// 1152 returns the inner tinfo unchanged) so we recover the cstage +// semantics with the bare kind check + NAMED chase. +export fn typeistagged(t: *tinfo) bool = { + if (t == nil) { return false; }; + let k: tykind = t.kind; + if (k == tykind.TY_TAGGED) { return true; }; + if (k == tykind.TY_NAMED) { return typeistagged(t.under); }; + return false; +}; + +// typeisf32 — narrower-than-typeisfloat: only TY_F32 (after alias +// chase). Cite cstage cgen.c:188 `type_isf32`. Used to pick MOVSS vs +// MOVSD and the SS-variant arithmetic / cast opcodes. +export fn typeisf32(t: *tinfo) bool = { + if (t == nil) { return false; }; + let k: tykind = t.kind; + if (k == tykind.TY_F32) { return true; }; + if (k == tykind.TY_NAMED) { return typeisf32(t.under); }; + return false; +}; + +// typeisnullable — TY_TAGGED with the `(*T | void)` one-word fold. +// Cite cstage cgen.c:396 `type_isnullable`. The .nullable flag is +// stamped by tinfofornode (check.ww:1309-1318) when the two-variant +// shape matches. +export fn typeisnullable(t: *tinfo) bool = { + if (t == nil) { return false; }; + let k: tykind = t.kind; + if (k == tykind.TY_TAGGED) { return t.nullable != 0; }; + if (k == tykind.TY_NAMED) { return typeisnullable(t.under); }; + return false; +}; + +// typeis8byteprim — does this type take exactly one 8-byte stack +// slot (ptr / fn / chan / 64-bit int / scalar primitive padded up to +// 8 / `[N]T` whose natural width is 8) rather than a wider aggregate? +// Mirrors the ladder cstage's cgen.c N_LET zero-init takes on `sz==8` +// (cmd/wcc/check.c sizing + cgen.c N_LET). The node-keyed wwstage +// helper this replaces predates tinfo and AST-walked TBANG / TNAME +// alias chains; tinfofornode now collapses TBANG (check.ww:1145) and +// TY_NAMED.under carries the chain, so the tinfo walk handles every +// shape the AST walker did. +export fn typeis8byteprim(t: *tinfo) bool = { + if (t == nil) { return false; }; + let k: tykind = t.kind; + if (k == tykind.TY_PTR) { return true; }; + if (k == tykind.TY_FN) { return true; }; + if (k == tykind.TY_CHAN) { return true; }; + if (k == tykind.TY_SLICE) { return false; }; + if (k == tykind.TY_TUPLE) { return false; }; + if (k == tykind.TY_TAGGED) { return false; }; + if (k == tykind.TY_STR) { return false; }; + if (k == tykind.TY_STRUCT) { return false; }; + if (k == tykind.TY_ARRAY) { return t.size == 8u64; }; + if (k == tykind.TY_NAMED) { return typeis8byteprim(t.under); }; + // Remaining: primitives (i8/u8/.../i64/u64/bool/rune/f32/f64/ + // int/uint/uintptr) and TY_VOID. All slot-pad to 8 and zero-init + // in cstage's `sz==8` branch. + return true; +}; + export fn typeisuntyped(t: *tinfo) bool = { if (t == nil) { return false; }; let k: tykind = t.kind; @@ -11118,72 +11206,13 @@ fn typenameisunsigned(nm: str) bool = { }; // typeis8byteprimitive — does this type take exactly one 8-byte -// slot (pointer / fn-ptr / 64-bit int / chan / scalar primitive -// padded up to 8) rather than a wider aggregate? Used by nkind.N_LET -// zero-init to mirror C cgen's "only zero if sz == 8 at the type -// level" rule. Strings (16), slices (24), tagged unions (>=16), -// tuples (16), structs (varies), arrays — all fall through to -// false here even when their *slot* rounds up to 8. +// slot rather than a wider aggregate? One-liner via typeis8byteprim +// (cstage N_LET sz==8 ladder SSoT). t.type_ is stamped at check.ww +// L426-436 for every type-AST kind callers reach. Collapsed onto the +// tinfo helper per A.6.3b (#46). fn typeis8byteprimitive(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; - let k: nkind = t.kind; - if (k == nkind.N_TPTR) { return true; }; - if (k == nkind.N_TFN) { return true; }; - if (k == nkind.N_TCHAN) { return true; }; - if (k == nkind.N_TSLICE) { return false; }; - if (k == nkind.N_TARRAY) { - // C cgen (cmd/w6c/cgen.c:3317) zero-inits TY_ARRAY whenever - // its raw byte size is 8 — e.g. `[8]bool`, `[2]i32`, `[4]i16`, - // `[1]i64`. Mirror that here so the wwstage matches. - let lenn: *node = t.rhs; - let elemn: *node = t.lhs; - if (lenn == nil) { return false; }; - if (lenn.kind != nkind.N_INTLIT) { return false; }; - let elen: i64 = lenn.uval: i64; - let esz: i32 = 8; - if (elemn != nil) { - if (elemn.kind == nkind.N_TNAME) { - let ps: i32 = primsize(elemn.str); - if (ps > 0) { esz = ps; }; - }; - }; - return (esz: i64 * elen) == 8i64; - }; - if (k == nkind.N_TTUPLE) { return false; }; - if (k == nkind.N_TTAGGED){ return false; }; - // STATUS-3 #22: `!T` carries the error flag on T's underlying - // shape (cmd/wcc/check.c:290 resolve_type N_TBANG copies T's - // kind, just sets iserror). cstage's N_LET sizes off lu->kind, - // so `!void`/`!i32` land in the sz=8 default and `!str`/`!slice` - // keep their composite slot. Defer to the inner type so - // `let e: !void;` mirrors cstage's MOVQ $0 while `!str` falls - // through to the multi-word fill. - if (k == nkind.N_TBANG) { return typeis8byteprimitive(c, t.lhs); }; - if (k == nkind.N_TNAME) { - let nm: str = t.str; - if (streq(nm, "str")) { return false; }; - // Plain `void` slot: cstage sz=8 default → MOVQ $0. The let- - // decl is a phantom (a tagged-union variant tag carrier), but - // the slot is still 8B and zero-inits like any other prim. - if (streq(nm, "void")) { return true; }; - // Struct alias: not a primitive even if the slot is 8B. - if (structlookup(c, nm) != nil) { return false; }; - // Primitive (i8/u8/.../i64/u64/bool/rune/f32/f64/int/...). - // All of these get slot-padded to 8 and zero-init in C. - if (primsize(nm) > 0) { return true; }; - // STATUS-3 #22: alias to `!T` or to `void` (Hare-style error - // type / phantom variant). cstage resolves the alias and - // lands on sz=8 default. Follow through aliaslookup so - // `type invalid = !void;` and `type done = void;` zero-init. - if (c != nil) { - let aliased: *node = aliaslookup(c, nm); - if (aliased != nil) { - return typeis8byteprimitive(c, aliased); - }; - }; - return false; - }; - return false; + return typeis8byteprim(t.type_: *tinfo); }; // elemissignedc — given an indexable type-AST (`*T`, `[]T`, `[N]T`), @@ -12419,54 +12448,20 @@ fn collectstructs(c: *cgen, file: *node) void = { }; }; -// `type X = str;` aliases) to `str`. Takes *cgen so it can walk the -// alias chain registered at file load. -fn isstrtyperaw(t: *node) bool = { - if (t == nil) { return false; }; - if (t.kind == nkind.N_TNAME) { - let nm: str = t.str; - if (streq(nm, "str")) { return true; }; - }; - return false; -}; - +// isstrtype — alias-aware. Reads the stamped tinfo so `str`, +// `type alias = str`, `!str`, and chained aliases all route to the +// str-shaped slot. Cite cstage cgen.c:159 `type_isstr` SSoT. +// Collapsed onto typeisstr per A.6.3b (#46); the prior AST walker is +// reconstituted by typeisstr's TY_NAMED chase + tinfofornode's +// TBANG-unwrap (check.ww:1145). fn isstrtype(c: *cgen, t: *node) bool = { - if (isstrtyperaw(t)) { return true; }; - if (c == nil) { return false; }; - let r: *node = resolvetype(c, t); - if (isstrtyperaw(r)) { return true; }; - // `parserr = !str` — `!T` aliases shouldn't hide their - // underlying type from str-routing. Unwrap and re-check. - if (r != nil) { - if (r.kind == nkind.N_TBANG) { - let inner: *node = r.lhs; - if (isstrtyperaw(inner)) { return true; }; - if (inner != nil) { - let r2: *node = resolvetype(c, inner); - if (isstrtyperaw(r2)) { return true; }; - }; - }; - }; - return false; -}; - -fn isslicetyperaw(t: *node) bool = { if (t == nil) { return false; }; - if (t.kind == nkind.N_TSLICE) { return true; }; - return false; + return typeisstr(t.type_: *tinfo); }; fn isslicetype(c: *cgen, t: *node) bool = { - if (isslicetyperaw(t)) { return true; }; - if (c == nil) { return false; }; - let r: *node = resolvetype(c, t); - return isslicetyperaw(r); -}; - -fn istaggedtyperaw(t: *node) bool = { if (t == nil) { return false; }; - if (t.kind == nkind.N_TTAGGED) { return true; }; - return false; + return typeisslice(t.type_: *tinfo); }; // resolvetagged — return the underlying N_TTAGGED node for `t`, or nil @@ -12587,64 +12582,31 @@ fn structparamsize(c: *cgen, t: *node) i32 = { return si.totsize; }; -// istaggedtype — alias-aware. Mirrors isstrtype: follow N_TNAME to its -// underlying decl, then unwrap a leading N_TBANG so `type error = -// !(invalid | overflow);` is still recognised as tagged. Without the -// bang unwrap the prologue treats the param as scalar (8B), spilling -// only DI and losing the value-word SI; the match read of slot+8 then -// trails into saved BP. +// istaggedtype — alias-aware. Reads stamped tinfo so `T`, +// `type alias = (A|B)`, `type error = !(invalid|overflow)` all +// resolve to TY_TAGGED — tinfofornode handles the N_TBANG unwrap +// (check.ww:1145) so we don't re-walk it here. Cite cstage cgen.c +// (`type_chase_named` + TY_TAGGED). Collapsed per A.6.3b (#46). fn istaggedtype(c: *cgen, t: *node) bool = { - if (istaggedtyperaw(t)) { return true; }; - if (c == nil) { return false; }; - let r: *node = resolvetype(c, t); - if (istaggedtyperaw(r)) { return true; }; - if (r != nil) { - if (r.kind == nkind.N_TBANG) { - let inner: *node = r.lhs; - if (istaggedtyperaw(inner)) { return true; }; - if (inner != nil) { - let r2: *node = resolvetype(c, inner); - if (istaggedtyperaw(r2)) { return true; }; - }; - }; - }; - return false; -}; - -// isf32typeraw / isf64typeraw — bare TNAME check, no alias resolution. -fn isf32typeraw(t: *node) bool = { if (t == nil) { return false; }; - if (t.kind != nkind.N_TNAME) { return false; }; - return streq(t.str, "f32"); + return typeistagged(t.type_: *tinfo); }; -fn isf64typeraw(t: *node) bool = { - if (t == nil) { return false; }; - if (t.kind != nkind.N_TNAME) { return false; }; - return streq(t.str, "f64"); -}; - -// isfloattype — f32 / f64 (and aliases of those). Used by cglet, -// cgident, cgassign, cgbin, cgcast, cgcall, cgreturn, fn-prologue to -// dispatch the MOVSS/MOVSD-shaped paths. +// isfloattype — f32 / f64 / untyped_float (alias-aware). Cite cstage +// cgen.c:117 `cg_isfloat`. Dispatches MOVSS/MOVSD-shaped paths across +// cglet, cgident, cgassign, cgbin, cgcast, cgcall, cgreturn, fn- +// prologue. Collapsed per A.6.3b (#46). export fn isfloattype(c: *cgen, t: *node) bool = { - if (isf32typeraw(t)) { return true; }; - if (isf64typeraw(t)) { return true; }; - if (c == nil) { return false; }; - let r: *node = resolvetype(c, t); - if (isf32typeraw(r)) { return true; }; - if (isf64typeraw(r)) { return true; }; - return false; + if (t == nil) { return false; }; + return typeisfloat(t.type_: *tinfo); }; -// isf32type — narrower predicate: true only for f32 (after alias -// resolution). f64 returns false. Used to pick MOVSS vs MOVSD and -// the SS-variant arithmetic / cast opcodes. +// isf32type — narrower: true only for f32 (after alias chase). Cite +// cstage cgen.c:188 `type_isf32`. Picks MOVSS vs MOVSD and the SS- +// variant arithmetic / cast opcodes. Collapsed per A.6.3b (#46). export fn isf32type(c: *cgen, t: *node) bool = { - if (isf32typeraw(t)) { return true; }; - if (c == nil) { return false; }; - let r: *node = resolvetype(c, t); - return isf32typeraw(r); + if (t == nil) { return false; }; + return typeisf32(t.type_: *tinfo); }; // exprfloatkind — classify an expression's value-class so callers can @@ -12765,30 +12727,23 @@ export fn exprfloatkind(c: *cgen, n: *node) i32 = { return 0; }; -// isnullabletype — nkind.N_TTAGGED with exactly two children, one *T and -// one `void`. Folds to a single 8-byte pointer slot per Hare's -// `(*T | null)` semantics. Mirrors check.c's resolve_type detection. +// isnullabletype — `(*T | void)` one-word fold per Hare's +// `(*T | null)` semantics. Cite cstage cgen.c:396 `type_isnullable`; +// the .nullable flag lands on tinfo at check.ww:1309-1318 when the +// two-variant shape matches. Collapsed per A.6.3b (#46). export fn isnullabletype(t: *node) bool = { if (t == nil) { return false; }; - if (t.kind != nkind.N_TTAGGED) { return false; }; - let a: *node = t.list; - if (a == nil) { return false; }; - let b: *node = a.next; - if (b == nil) { return false; }; - if (b.next != nil) { return false; }; - let aptr: bool = (a.kind == nkind.N_TPTR); - let bptr: bool = (b.kind == nkind.N_TPTR); - let avoid: bool = (a.kind == nkind.N_TNAME); - if (avoid) { avoid = streq(a.str, "void"); }; - let bvoid: bool = (b.kind == nkind.N_TNAME); - if (bvoid) { bvoid = streq(b.str, "void"); }; - if (aptr) { if (bvoid) { return true; }; }; - if (avoid) { if (bptr) { return true; }; }; - return false; + return typeisnullable(t.type_: *tinfo); }; // nullableptrtag — 0-based index of the *T variant in a nullable -// union. The void variant takes the other slot (0 or 1). +// union. The void variant takes the other slot (0 or 1). AST-keyed +// for now: tinfofornode doesn't populate TY_TAGGED.params yet +// (check.ww:1287-1337 sets size/align/nullable but not the variant +// chain), so the tinfo equivalent of cstage cgen.c:405 +// `nullable_ptr_tag` can't read params today. Graduates to a pure +// tinfo helper in A.6.3f (#50) alongside the variant-index work and +// the tparam-population glue. export fn nullableptrtag(t: *node) i32 = { if (t == nil) { return 0; }; if (t.kind != nkind.N_TTAGGED) { return 0; }; @@ -15663,7 +15618,7 @@ fn cgdot(c: *cgen, n: *node) void = { }; if (tp != nil) { let tpt: *node = tp.lhs; - if (isstrtyperaw(tpt)) { + if (isstrtype(c, tpt)) { emitline("\tMOVQ\t"); emitoff((lc.off + foff + 0): i64); emitline("(BP), AX\n"); @@ -20714,8 +20669,8 @@ fn cglet(c: *cgen, n: *node) void = { let p1t: *node = nil; if (p0 != nil) { p0t = p0.lhs; }; if (p1 != nil) { p1t = p1.lhs; }; - let s0_is_str: bool = isstrtyperaw(p0t); - let s1_is_str: bool = isstrtyperaw(p1t); + let s0_is_str: bool = isstrtype(c, p0t); + let s1_is_str: bool = isstrtype(c, p1t); if (p0 != nil) { if (p1 != nil) { if (s0_is_str != s1_is_str) { @@ -21257,8 +21212,8 @@ fn cgmlet(c: *cgen, n: *node) void = { if (t0 == nil) { t0 = p0t; }; if (t1 == nil) { t1 = p1t; }; - let s0_is_str: bool = isstrtyperaw(t0); - let s1_is_str: bool = isstrtyperaw(t1); + let s0_is_str: bool = isstrtype(c, t0); + let s1_is_str: bool = isstrtype(c, t1); cgexpr(c, rhs);