selfhost/cmd/wcc: collapse type-kind predicates onto n.type_ (A.6.3b, #46)

The node-keyed kind helpers (typeis8byteprimitive, isstrtype/raw,
isslicetype/raw, istaggedtype/raw, isfloattype, isf32type/raw,
isf64typeraw, isnullabletype) each re-walked TNAME aliases via
aliaslookup and peeled TBANG by hand — duplicating cstage's single-
peel kind predicates at the AST level. After A.6.2 every type-AST
kind these read is tinfo-stamped at check.ww L426-436, and
tinfofornode collapses N_TBANG (check.ww:1145-1152) and the TY_NAMED
chain, so each predicate folds to one tinfo read.

Six new tinfo helpers in lib/ww/typ.ww mirror their cstage SSoT
verbatim:

  typeisstr      — cstage cgen.c:159 `type_isstr`     (TY_STR / TY_UNTYPED_STR)
  typeisslice    — cstage cgen.c:174 `type_isslice`
  typeistagged   — cstage cgen.c:516 `type_istagged`
  typeisf32      — cstage cgen.c:188 `type_isf32`
  typeisnullable — cstage cgen.c:396 `type_isnullable` (reads tinfo.nullable
                                                       stamped at check.ww:1309-1318)
  typeis8byteprim — cstage cgen.c N_LET sz==8 ladder (slot-pad set)

Rule 9 carve-out per the A.6.3a precedent: each helper has a named
cstage counterpart; the wwstage shape mirrors it directly. The five
dead AST-walking variants (isstrtyperaw, isslicetyperaw,
istaggedtyperaw, isf32typeraw, isf64typeraw) are deleted; the five
remaining callsites (cgenstmt cglet / cgmlet str-routing, cgenexpr
cgdot tuple-field) graduate to the alias-aware isstrtype(c, t).

nullableptrtag stays AST-keyed for now — tinfofornode doesn't
populate TY_TAGGED.params (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. WHY
comment at the site cites #50 / A.6.3f as the graduation point,
alongside the variant-index work and the tparam-population glue.

Byte-identity (994/995) is the behavior gate; full `make test` green
at 133/133 confirms.
This commit is contained in:
2026-05-22 05:53:34 +09:00
parent 03e4718199
commit b8e5a921f8
6 changed files with 405 additions and 540 deletions

View File

@@ -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;