From 03e471819967956815ef53d736683bb6a0e516fe Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 22 May 2026 05:24:49 +0900 Subject: [PATCH] selfhost/cmd/wcc: collapse signedness predicates onto n.type_ (A.6.3a, #45) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The node-keyed signedness helpers (typenodeisunsigned, typenodeisunsignedc, elemissigned, elemissignedc, fieldissignedc) each re-walked TBANG / TENUM / TNAME chains and re-consulted alias / enum registries — duplicating cstage's type_isunsigned (cmd/wcc/type.c:178) and fld_issigned (cmd/w6c/cgen.c:240) at the AST level. After A.6.2 every type-AST kind we read here is tinfo-stamped at check.ww L426-436, so the predicates collapse to a single tinfo read. Two new arms close the wwstage divergence from cstage: typeisunsigned gains TY_RUNE and TY_ENUM (recurse on .sub), matching type.c:178 verbatim. typeissigned is added as the cgen-facing predicate per fld_issigned semantics (TY_BOOL excluded for sub-word storage — 0/1 → MOVZBQ — so it's not just !typeisunsigned). Rule 9 carve-out: the helper exists in cstage; harec keeps the same pair. elemissigned was fully dead (no callers); deleted. typenameissigned was internal-only and dead post-collapse; deleted. typenameisunsigned survives — two call sites (typenodeprimresolved, exprprimresolved) hold only a raw `str` (TNAME.str / INTLIT.tsuffix). paramissigned in cgenstmt.ww unchanged. Both deferrals close in A.6.3c (#47). Byte-identity (994/995) is the behavior gate for the alias/enum sites — full `make test` green at 133/133 confirms. --- lib/ww/typ.ww | 18 +++ selfhost/cmd/w6c/main.combined.ww | 164 ++++++++++----------------- selfhost/cmd/wcc/cgenexpr.ww | 14 ++- selfhost/cmd/wcc/cgenutil.ww | 132 +++++---------------- selfhost/cmd/wwdump/main.combined.ww | 164 ++++++++++----------------- 5 files changed, 180 insertions(+), 312 deletions(-) diff --git a/lib/ww/typ.ww b/lib/ww/typ.ww index ff6d4319..53876703 100644 --- a/lib/ww/typ.ww +++ b/lib/ww/typ.ww @@ -305,6 +305,10 @@ export fn typeisnum(t: *tinfo) bool = { return typeisfloat(t); }; +// TY_RUNE is unsigned: Unicode codepoint (0..0x10FFFF) zero-extends on +// sub-word load (MOVL, not MOVSXD). TY_ENUM recurses on .sub so a +// `type k = enum u32 {…}` reads as unsigned. Cite cstage type.c:178 +// `type_isunsigned`; rule 10 keeps wwstage aligned down to cstage. export fn typeisunsigned(t: *tinfo) bool = { if (t == nil) { return false; }; let k: tykind = t.kind; @@ -314,10 +318,24 @@ export fn typeisunsigned(t: *tinfo) bool = { if (k == tykind.TY_U64) { return true; }; if (k == tykind.TY_UINT){ return true; }; if (k == tykind.TY_UINTPTR) { return true; }; + if (k == tykind.TY_RUNE){ return true; }; if (k == tykind.TY_NAMED) { return typeisunsigned(t.under); }; + if (k == tykind.TY_ENUM) { return typeisunsigned(t.sub); }; return false; }; +// typeissigned — does this type need sign-extension on a sub-word +// (1/2/4B) load? Mirrors cstage cgen.c:240 `fld_issigned`. Cgen-facing +// predicate (TY_BOOL is unsigned for storage purposes — 0/1 → MOVZBQ), +// so it doesn't simply mirror `!typeisunsigned`. Pair-of-`is*` +// convention follows ref/hare/types/ helpers. +export fn typeissigned(t: *tinfo) bool = { + if (t == nil) { return false; }; + if (t.kind == tykind.TY_BOOL) { return false; }; + if (typeisunsigned(t)) { return false; }; + return typeisint(t); +}; + 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 23574d86..af3e0d4e 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -6635,6 +6635,10 @@ export fn typeisnum(t: *tinfo) bool = { return typeisfloat(t); }; +// TY_RUNE is unsigned: Unicode codepoint (0..0x10FFFF) zero-extends on +// sub-word load (MOVL, not MOVSXD). TY_ENUM recurses on .sub so a +// `type k = enum u32 {…}` reads as unsigned. Cite cstage type.c:178 +// `type_isunsigned`; rule 10 keeps wwstage aligned down to cstage. export fn typeisunsigned(t: *tinfo) bool = { if (t == nil) { return false; }; let k: tykind = t.kind; @@ -6644,10 +6648,24 @@ export fn typeisunsigned(t: *tinfo) bool = { if (k == tykind.TY_U64) { return true; }; if (k == tykind.TY_UINT){ return true; }; if (k == tykind.TY_UINTPTR) { return true; }; + if (k == tykind.TY_RUNE){ return true; }; if (k == tykind.TY_NAMED) { return typeisunsigned(t.under); }; + if (k == tykind.TY_ENUM) { return typeisunsigned(t.sub); }; return false; }; +// typeissigned — does this type need sign-extension on a sub-word +// (1/2/4B) load? Mirrors cstage cgen.c:240 `fld_issigned`. Cgen-facing +// predicate (TY_BOOL is unsigned for storage purposes — 0/1 → MOVZBQ), +// so it doesn't simply mirror `!typeisunsigned`. Pair-of-`is*` +// convention follows ref/hare/types/ helpers. +export fn typeissigned(t: *tinfo) bool = { + if (t == nil) { return false; }; + if (t.kind == tykind.TY_BOOL) { return false; }; + if (typeisunsigned(t)) { return false; }; + return typeisint(t); +}; + export fn typeisuntyped(t: *tinfo) bool = { if (t == nil) { return false; }; let k: tykind = t.kind; @@ -11082,10 +11100,12 @@ fn nodeisstr(c: *cgen, n: *node) bool = { return false; }; -// typenameisunsigned — true for u8/u16/u32/u64/uint/uintptr/rune. -// rune is a Unicode codepoint (0..0x10FFFF); cgen treats it as -// unsigned so narrow-cast / sub-word load paths zero-extend (MOVL, -// not MOVSXD). Mirrors cstage's type_isunsigned post task #5. +// typenameisunsigned — primitive-name unsigned check. Kept because +// two call sites (typenodeprimresolved L1729, exprprimresolved L1766) +// have only a raw `str` in scope (TNAME.str / INTLIT.tsuffix), not a +// stamped *node — the typed-AST path doesn't reach them yet (#47 +// A.6.3c). The node-keyed predicates collapsed onto +// typeisunsigned(n.type_: *tinfo) per A.6.3a (#45). fn typenameisunsigned(nm: str) bool = { if (streq(nm, "u8")) { return true; }; if (streq(nm, "u16")) { return true; }; @@ -11097,41 +11117,6 @@ fn typenameisunsigned(nm: str) bool = { return false; }; -// typenodeisunsigned — recurse through TNAME aliases / TBANG / TENUM -// to the resolved primitive. Mirrors cstage's type_isunsigned which -// recurses into TY_NAMED.under and TY_ENUM.sub. -fn typenodeisunsignedc(c: *cgen, t: *node) bool = { - if (t == nil) { return false; }; - let k: nkind = t.kind; - if (k == nkind.N_TBANG) { return typenodeisunsignedc(c, t.lhs); }; - if (k == nkind.N_TENUM) { return typenodeisunsignedc(c, t.lhs); }; - if (k == nkind.N_TNAME) { - let nm: str = t.str; - if (typenameisunsigned(nm)) { return true; }; - if (typenameissigned(nm)) { return false; }; - // Follow aliases / enum storage. - let al: *node = aliaslookup(c, nm); - if (al != nil) { return typenodeisunsignedc(c, al); }; - let en: *enumtype = enumlookup(c, nm); - if (en != nil) { - if (en.storage != nil) { - return typenodeisunsignedc(c, en.storage); - }; - return false; // default storage i32 is signed - }; - }; - return false; -}; - -// typenodeisunsigned — legacy callers without *cgen context. Only -// resolves primitive TNAMEs (no alias/enum recursion); use the -// _c variant where the cgen registry is in scope. -fn typenodeisunsigned(t: *node) bool = { - if (t == nil) { return false; }; - if (t.kind == nkind.N_TNAME) { return typenameisunsigned(t.str); }; - return false; -}; - // 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 @@ -11201,70 +11186,26 @@ fn typeis8byteprimitive(c: *cgen, t: *node) bool = { return false; }; -// elemissigned — given an indexable type (`*T`, `[]T`, `[N]T`), is -// its element a signed narrow primitive (i8/i16/i32)? Used by -// cgindex to pick MOVSXD vs MOVL at esz=4 (and MOVSBQ/MOVSWQ at -// esz=1/2). Mirrors cstage's `signed_elem`. Follows alias/enum -// chains so `[]Alias` arrays resolve to the underlying signedness. +// elemissignedc — given an indexable type-AST (`*T`, `[]T`, `[N]T`), +// is its element a signed narrow primitive? Used by cgindex to pick +// MOVSXD vs MOVL at esz=4 (and MOVSBQ/MOVSWQ at esz=1/2). Mirrors +// cstage's `signed_elem` (cmd/w6c/cgen.c idx_eff path). Reads through +// the stamped tinfo so alias/enum recursion lives in lib/ww/typ.ww. fn elemissignedc(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; - let elem: *node = nil; - let k: nkind = t.kind; - if (k == nkind.N_TPTR) { elem = t.lhs; }; - if (k == nkind.N_TSLICE) { elem = t.lhs; }; - if (k == nkind.N_TARRAY) { elem = t.lhs; }; - if (elem == nil) { return false; }; - return fieldissignedc(c, elem); + let ti: *tinfo = t.type_: *tinfo; + if (ti == nil) { return false; }; + return typeissigned(ti.sub); }; -fn elemissigned(t: *node) bool = { - if (t == nil) { return false; }; - let elem: *node = nil; - let k: nkind = t.kind; - if (k == nkind.N_TPTR) { elem = t.lhs; }; - if (k == nkind.N_TSLICE) { elem = t.lhs; }; - if (k == nkind.N_TARRAY) { elem = t.lhs; }; - if (elem == nil) { return false; }; - if (elem.kind != nkind.N_TNAME) { return false; }; - return typenameissigned(elem.str); -}; - -// typenameissigned — true for i8/i16/i32/i64/int. rune is excluded -// (it's a non-negative Unicode codepoint, treated as unsigned). -fn typenameissigned(nm: str) bool = { - if (streq(nm, "i8")) { return true; }; - if (streq(nm, "i16")) { return true; }; - if (streq(nm, "i32")) { return true; }; - if (streq(nm, "i64")) { return true; }; - if (streq(nm, "int")) { return true; }; - return false; -}; - -// fieldissignedc — does this field/element type need sign-extension -// on a sub-word load? Walks TBANG / TENUM / TNAME-aliases to the -// resolved primitive. Mirrors cstage's fld_issigned: bool is treated -// as unsigned (0/1 ⇒ MOVZBQ); rune is unsigned (codepoint ⇒ MOVL). +// fieldissignedc — does this field/element type-AST need sign- +// extension on a sub-word load? One-liner via typeissigned (cstage +// cgen.c:240 `fld_issigned` SSoT). t.type_ is stamped at check.ww +// L426-436 for every type-AST kind we see here (TNAME / TPTR / +// TBANG / TENUM / TARRAY / TSLICE — see resolvewalk). fn fieldissignedc(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; - let k: nkind = t.kind; - if (k == nkind.N_TBANG) { return fieldissignedc(c, t.lhs); }; - if (k == nkind.N_TENUM) { return fieldissignedc(c, t.lhs); }; - if (k == nkind.N_TNAME) { - let nm: str = t.str; - if (streq(nm, "bool")) { return false; }; - if (typenameisunsigned(nm)) { return false; }; - if (typenameissigned(nm)) { return true; }; - let al: *node = aliaslookup(c, nm); - if (al != nil) { return fieldissignedc(c, al); }; - let en: *enumtype = enumlookup(c, nm); - if (en != nil) { - if (en.storage != nil) { - return fieldissignedc(c, en.storage); - }; - return true; // default i32 storage is signed - }; - }; - return false; + return typeissigned(t.type_: *tinfo); }; // fieldloadop — pick the load instruction for a non-str struct @@ -11614,7 +11555,10 @@ fn nodeisunsigned(c: *cgen, n: *node) bool = { if (k == nkind.N_IDENT) { let nm: str = n.str; let lc: *local = localfindnode(c, nm); - if (lc != nil) { return typenodeisunsigned(lc.tnode); }; + if (lc != nil) { + if (lc.tnode == nil) { return false; }; + return typeisunsigned(lc.tnode.type_: *tinfo); + }; return false; }; if (k == nkind.N_DOT) { @@ -11644,7 +11588,8 @@ fn nodeisunsigned(c: *cgen, n: *node) bool = { for (fi != nil) { let fn_: str = fi.fname; if (streq(fn_, fld)) { - return typenodeisunsigned(fi.tnode); + if (fi.tnode == nil) { return false; }; + return typeisunsigned(fi.tnode.type_: *tinfo); }; fi = fi.finext; }; @@ -11655,7 +11600,10 @@ fn nodeisunsigned(c: *cgen, n: *node) bool = { }; return false; }; - if (k == nkind.N_CAST) { return typenodeisunsigned(n.rhs); }; + if (k == nkind.N_CAST) { + if (n.rhs == nil) { return false; }; + return typeisunsigned(n.rhs.type_: *tinfo); + }; if (k == nkind.N_BIN) { if (nodeisunsigned(c, n.lhs)) { return true; }; return nodeisunsigned(c, n.rhs); @@ -11679,7 +11627,7 @@ fn nodeisunsigned(c: *cgen, n: *node) bool = { if (tn.kind == nkind.N_TARRAY) { elem = tn.lhs; }; if (tn.kind == nkind.N_TSLICE) { elem = tn.lhs; }; if (elem != nil) { - return typenodeisunsigned(elem); + return typeisunsigned(elem.type_: *tinfo); }; }; }; @@ -17845,7 +17793,10 @@ fn cgassign(c: *cgen, n: *node) void = { // arm. Pre-fix the default branch silently stored // rhs into *p (combineop = MOVQ shape). if (n.op == tkind.TK_SLASHEQ || n.op == tkind.TK_PERCENTEQ) { - let unsignd: bool = typenodeisunsignedc(c, pe); + let unsignd: bool = false; + if (pe != nil) { + unsignd = typeisunsigned(pe.type_: *tinfo); + }; if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; @@ -19730,7 +19681,10 @@ fn cgassign(c: *cgen, n: *node) void = { // ferry AX or DX back to BX for the shared // store-BX tail below. else { if (n.op == tkind.TK_SLASHEQ || n.op == tkind.TK_PERCENTEQ) { - let unsignd: bool = typenodeisunsignedc(c, lvftn); + let unsignd: bool = false; + if (lvftn != nil) { + unsignd = typeisunsigned(lvftn.type_: *tinfo); + }; if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; @@ -20013,7 +19967,9 @@ fn cgassign(c: *cgen, n: *node) void = { if (n.op == tkind.TK_SLASHEQ || n.op == tkind.TK_PERCENTEQ) { let unsignd: bool = false; if (lcn != nil) { - unsignd = typenodeisunsignedc(c, lcn.tnode); + if (lcn.tnode != nil) { + unsignd = typeisunsigned(lcn.tnode.type_: *tinfo); + }; }; if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 21b83cdd..9c5b37ff 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -3615,7 +3615,10 @@ fn cgassign(c: *cgen, n: *node) void = { // arm. Pre-fix the default branch silently stored // rhs into *p (combineop = MOVQ shape). if (n.op == tkind.TK_SLASHEQ || n.op == tkind.TK_PERCENTEQ) { - let unsignd: bool = typenodeisunsignedc(c, pe); + let unsignd: bool = false; + if (pe != nil) { + unsignd = typeisunsigned(pe.type_: *tinfo); + }; if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; @@ -5500,7 +5503,10 @@ fn cgassign(c: *cgen, n: *node) void = { // ferry AX or DX back to BX for the shared // store-BX tail below. else { if (n.op == tkind.TK_SLASHEQ || n.op == tkind.TK_PERCENTEQ) { - let unsignd: bool = typenodeisunsignedc(c, lvftn); + let unsignd: bool = false; + if (lvftn != nil) { + unsignd = typeisunsigned(lvftn.type_: *tinfo); + }; if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; @@ -5783,7 +5789,9 @@ fn cgassign(c: *cgen, n: *node) void = { if (n.op == tkind.TK_SLASHEQ || n.op == tkind.TK_PERCENTEQ) { let unsignd: bool = false; if (lcn != nil) { - unsignd = typenodeisunsignedc(c, lcn.tnode); + if (lcn.tnode != nil) { + unsignd = typeisunsigned(lcn.tnode.type_: *tinfo); + }; }; if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 48e6e005..2b12575e 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -849,10 +849,12 @@ fn nodeisstr(c: *cgen, n: *node) bool = { return false; }; -// typenameisunsigned — true for u8/u16/u32/u64/uint/uintptr/rune. -// rune is a Unicode codepoint (0..0x10FFFF); cgen treats it as -// unsigned so narrow-cast / sub-word load paths zero-extend (MOVL, -// not MOVSXD). Mirrors cstage's type_isunsigned post task #5. +// typenameisunsigned — primitive-name unsigned check. Kept because +// two call sites (typenodeprimresolved L1729, exprprimresolved L1766) +// have only a raw `str` in scope (TNAME.str / INTLIT.tsuffix), not a +// stamped *node — the typed-AST path doesn't reach them yet (#47 +// A.6.3c). The node-keyed predicates collapsed onto +// typeisunsigned(n.type_: *tinfo) per A.6.3a (#45). fn typenameisunsigned(nm: str) bool = { if (streq(nm, "u8")) { return true; }; if (streq(nm, "u16")) { return true; }; @@ -864,41 +866,6 @@ fn typenameisunsigned(nm: str) bool = { return false; }; -// typenodeisunsigned — recurse through TNAME aliases / TBANG / TENUM -// to the resolved primitive. Mirrors cstage's type_isunsigned which -// recurses into TY_NAMED.under and TY_ENUM.sub. -fn typenodeisunsignedc(c: *cgen, t: *node) bool = { - if (t == nil) { return false; }; - let k: nkind = t.kind; - if (k == nkind.N_TBANG) { return typenodeisunsignedc(c, t.lhs); }; - if (k == nkind.N_TENUM) { return typenodeisunsignedc(c, t.lhs); }; - if (k == nkind.N_TNAME) { - let nm: str = t.str; - if (typenameisunsigned(nm)) { return true; }; - if (typenameissigned(nm)) { return false; }; - // Follow aliases / enum storage. - let al: *node = aliaslookup(c, nm); - if (al != nil) { return typenodeisunsignedc(c, al); }; - let en: *enumtype = enumlookup(c, nm); - if (en != nil) { - if (en.storage != nil) { - return typenodeisunsignedc(c, en.storage); - }; - return false; // default storage i32 is signed - }; - }; - return false; -}; - -// typenodeisunsigned — legacy callers without *cgen context. Only -// resolves primitive TNAMEs (no alias/enum recursion); use the -// _c variant where the cgen registry is in scope. -fn typenodeisunsigned(t: *node) bool = { - if (t == nil) { return false; }; - if (t.kind == nkind.N_TNAME) { return typenameisunsigned(t.str); }; - return false; -}; - // 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 @@ -968,70 +935,26 @@ fn typeis8byteprimitive(c: *cgen, t: *node) bool = { return false; }; -// elemissigned — given an indexable type (`*T`, `[]T`, `[N]T`), is -// its element a signed narrow primitive (i8/i16/i32)? Used by -// cgindex to pick MOVSXD vs MOVL at esz=4 (and MOVSBQ/MOVSWQ at -// esz=1/2). Mirrors cstage's `signed_elem`. Follows alias/enum -// chains so `[]Alias` arrays resolve to the underlying signedness. +// elemissignedc — given an indexable type-AST (`*T`, `[]T`, `[N]T`), +// is its element a signed narrow primitive? Used by cgindex to pick +// MOVSXD vs MOVL at esz=4 (and MOVSBQ/MOVSWQ at esz=1/2). Mirrors +// cstage's `signed_elem` (cmd/w6c/cgen.c idx_eff path). Reads through +// the stamped tinfo so alias/enum recursion lives in lib/ww/typ.ww. fn elemissignedc(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; - let elem: *node = nil; - let k: nkind = t.kind; - if (k == nkind.N_TPTR) { elem = t.lhs; }; - if (k == nkind.N_TSLICE) { elem = t.lhs; }; - if (k == nkind.N_TARRAY) { elem = t.lhs; }; - if (elem == nil) { return false; }; - return fieldissignedc(c, elem); + let ti: *tinfo = t.type_: *tinfo; + if (ti == nil) { return false; }; + return typeissigned(ti.sub); }; -fn elemissigned(t: *node) bool = { - if (t == nil) { return false; }; - let elem: *node = nil; - let k: nkind = t.kind; - if (k == nkind.N_TPTR) { elem = t.lhs; }; - if (k == nkind.N_TSLICE) { elem = t.lhs; }; - if (k == nkind.N_TARRAY) { elem = t.lhs; }; - if (elem == nil) { return false; }; - if (elem.kind != nkind.N_TNAME) { return false; }; - return typenameissigned(elem.str); -}; - -// typenameissigned — true for i8/i16/i32/i64/int. rune is excluded -// (it's a non-negative Unicode codepoint, treated as unsigned). -fn typenameissigned(nm: str) bool = { - if (streq(nm, "i8")) { return true; }; - if (streq(nm, "i16")) { return true; }; - if (streq(nm, "i32")) { return true; }; - if (streq(nm, "i64")) { return true; }; - if (streq(nm, "int")) { return true; }; - return false; -}; - -// fieldissignedc — does this field/element type need sign-extension -// on a sub-word load? Walks TBANG / TENUM / TNAME-aliases to the -// resolved primitive. Mirrors cstage's fld_issigned: bool is treated -// as unsigned (0/1 ⇒ MOVZBQ); rune is unsigned (codepoint ⇒ MOVL). +// fieldissignedc — does this field/element type-AST need sign- +// extension on a sub-word load? One-liner via typeissigned (cstage +// cgen.c:240 `fld_issigned` SSoT). t.type_ is stamped at check.ww +// L426-436 for every type-AST kind we see here (TNAME / TPTR / +// TBANG / TENUM / TARRAY / TSLICE — see resolvewalk). fn fieldissignedc(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; - let k: nkind = t.kind; - if (k == nkind.N_TBANG) { return fieldissignedc(c, t.lhs); }; - if (k == nkind.N_TENUM) { return fieldissignedc(c, t.lhs); }; - if (k == nkind.N_TNAME) { - let nm: str = t.str; - if (streq(nm, "bool")) { return false; }; - if (typenameisunsigned(nm)) { return false; }; - if (typenameissigned(nm)) { return true; }; - let al: *node = aliaslookup(c, nm); - if (al != nil) { return fieldissignedc(c, al); }; - let en: *enumtype = enumlookup(c, nm); - if (en != nil) { - if (en.storage != nil) { - return fieldissignedc(c, en.storage); - }; - return true; // default i32 storage is signed - }; - }; - return false; + return typeissigned(t.type_: *tinfo); }; // fieldloadop — pick the load instruction for a non-str struct @@ -1381,7 +1304,10 @@ fn nodeisunsigned(c: *cgen, n: *node) bool = { if (k == nkind.N_IDENT) { let nm: str = n.str; let lc: *local = localfindnode(c, nm); - if (lc != nil) { return typenodeisunsigned(lc.tnode); }; + if (lc != nil) { + if (lc.tnode == nil) { return false; }; + return typeisunsigned(lc.tnode.type_: *tinfo); + }; return false; }; if (k == nkind.N_DOT) { @@ -1411,7 +1337,8 @@ fn nodeisunsigned(c: *cgen, n: *node) bool = { for (fi != nil) { let fn_: str = fi.fname; if (streq(fn_, fld)) { - return typenodeisunsigned(fi.tnode); + if (fi.tnode == nil) { return false; }; + return typeisunsigned(fi.tnode.type_: *tinfo); }; fi = fi.finext; }; @@ -1422,7 +1349,10 @@ fn nodeisunsigned(c: *cgen, n: *node) bool = { }; return false; }; - if (k == nkind.N_CAST) { return typenodeisunsigned(n.rhs); }; + if (k == nkind.N_CAST) { + if (n.rhs == nil) { return false; }; + return typeisunsigned(n.rhs.type_: *tinfo); + }; if (k == nkind.N_BIN) { if (nodeisunsigned(c, n.lhs)) { return true; }; return nodeisunsigned(c, n.rhs); @@ -1446,7 +1376,7 @@ fn nodeisunsigned(c: *cgen, n: *node) bool = { if (tn.kind == nkind.N_TARRAY) { elem = tn.lhs; }; if (tn.kind == nkind.N_TSLICE) { elem = tn.lhs; }; if (elem != nil) { - return typenodeisunsigned(elem); + return typeisunsigned(elem.type_: *tinfo); }; }; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 3158ced0..2d232f9c 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -6635,6 +6635,10 @@ export fn typeisnum(t: *tinfo) bool = { return typeisfloat(t); }; +// TY_RUNE is unsigned: Unicode codepoint (0..0x10FFFF) zero-extends on +// sub-word load (MOVL, not MOVSXD). TY_ENUM recurses on .sub so a +// `type k = enum u32 {…}` reads as unsigned. Cite cstage type.c:178 +// `type_isunsigned`; rule 10 keeps wwstage aligned down to cstage. export fn typeisunsigned(t: *tinfo) bool = { if (t == nil) { return false; }; let k: tykind = t.kind; @@ -6644,10 +6648,24 @@ export fn typeisunsigned(t: *tinfo) bool = { if (k == tykind.TY_U64) { return true; }; if (k == tykind.TY_UINT){ return true; }; if (k == tykind.TY_UINTPTR) { return true; }; + if (k == tykind.TY_RUNE){ return true; }; if (k == tykind.TY_NAMED) { return typeisunsigned(t.under); }; + if (k == tykind.TY_ENUM) { return typeisunsigned(t.sub); }; return false; }; +// typeissigned — does this type need sign-extension on a sub-word +// (1/2/4B) load? Mirrors cstage cgen.c:240 `fld_issigned`. Cgen-facing +// predicate (TY_BOOL is unsigned for storage purposes — 0/1 → MOVZBQ), +// so it doesn't simply mirror `!typeisunsigned`. Pair-of-`is*` +// convention follows ref/hare/types/ helpers. +export fn typeissigned(t: *tinfo) bool = { + if (t == nil) { return false; }; + if (t.kind == tykind.TY_BOOL) { return false; }; + if (typeisunsigned(t)) { return false; }; + return typeisint(t); +}; + export fn typeisuntyped(t: *tinfo) bool = { if (t == nil) { return false; }; let k: tykind = t.kind; @@ -11082,10 +11100,12 @@ fn nodeisstr(c: *cgen, n: *node) bool = { return false; }; -// typenameisunsigned — true for u8/u16/u32/u64/uint/uintptr/rune. -// rune is a Unicode codepoint (0..0x10FFFF); cgen treats it as -// unsigned so narrow-cast / sub-word load paths zero-extend (MOVL, -// not MOVSXD). Mirrors cstage's type_isunsigned post task #5. +// typenameisunsigned — primitive-name unsigned check. Kept because +// two call sites (typenodeprimresolved L1729, exprprimresolved L1766) +// have only a raw `str` in scope (TNAME.str / INTLIT.tsuffix), not a +// stamped *node — the typed-AST path doesn't reach them yet (#47 +// A.6.3c). The node-keyed predicates collapsed onto +// typeisunsigned(n.type_: *tinfo) per A.6.3a (#45). fn typenameisunsigned(nm: str) bool = { if (streq(nm, "u8")) { return true; }; if (streq(nm, "u16")) { return true; }; @@ -11097,41 +11117,6 @@ fn typenameisunsigned(nm: str) bool = { return false; }; -// typenodeisunsigned — recurse through TNAME aliases / TBANG / TENUM -// to the resolved primitive. Mirrors cstage's type_isunsigned which -// recurses into TY_NAMED.under and TY_ENUM.sub. -fn typenodeisunsignedc(c: *cgen, t: *node) bool = { - if (t == nil) { return false; }; - let k: nkind = t.kind; - if (k == nkind.N_TBANG) { return typenodeisunsignedc(c, t.lhs); }; - if (k == nkind.N_TENUM) { return typenodeisunsignedc(c, t.lhs); }; - if (k == nkind.N_TNAME) { - let nm: str = t.str; - if (typenameisunsigned(nm)) { return true; }; - if (typenameissigned(nm)) { return false; }; - // Follow aliases / enum storage. - let al: *node = aliaslookup(c, nm); - if (al != nil) { return typenodeisunsignedc(c, al); }; - let en: *enumtype = enumlookup(c, nm); - if (en != nil) { - if (en.storage != nil) { - return typenodeisunsignedc(c, en.storage); - }; - return false; // default storage i32 is signed - }; - }; - return false; -}; - -// typenodeisunsigned — legacy callers without *cgen context. Only -// resolves primitive TNAMEs (no alias/enum recursion); use the -// _c variant where the cgen registry is in scope. -fn typenodeisunsigned(t: *node) bool = { - if (t == nil) { return false; }; - if (t.kind == nkind.N_TNAME) { return typenameisunsigned(t.str); }; - return false; -}; - // 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 @@ -11201,70 +11186,26 @@ fn typeis8byteprimitive(c: *cgen, t: *node) bool = { return false; }; -// elemissigned — given an indexable type (`*T`, `[]T`, `[N]T`), is -// its element a signed narrow primitive (i8/i16/i32)? Used by -// cgindex to pick MOVSXD vs MOVL at esz=4 (and MOVSBQ/MOVSWQ at -// esz=1/2). Mirrors cstage's `signed_elem`. Follows alias/enum -// chains so `[]Alias` arrays resolve to the underlying signedness. +// elemissignedc — given an indexable type-AST (`*T`, `[]T`, `[N]T`), +// is its element a signed narrow primitive? Used by cgindex to pick +// MOVSXD vs MOVL at esz=4 (and MOVSBQ/MOVSWQ at esz=1/2). Mirrors +// cstage's `signed_elem` (cmd/w6c/cgen.c idx_eff path). Reads through +// the stamped tinfo so alias/enum recursion lives in lib/ww/typ.ww. fn elemissignedc(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; - let elem: *node = nil; - let k: nkind = t.kind; - if (k == nkind.N_TPTR) { elem = t.lhs; }; - if (k == nkind.N_TSLICE) { elem = t.lhs; }; - if (k == nkind.N_TARRAY) { elem = t.lhs; }; - if (elem == nil) { return false; }; - return fieldissignedc(c, elem); + let ti: *tinfo = t.type_: *tinfo; + if (ti == nil) { return false; }; + return typeissigned(ti.sub); }; -fn elemissigned(t: *node) bool = { - if (t == nil) { return false; }; - let elem: *node = nil; - let k: nkind = t.kind; - if (k == nkind.N_TPTR) { elem = t.lhs; }; - if (k == nkind.N_TSLICE) { elem = t.lhs; }; - if (k == nkind.N_TARRAY) { elem = t.lhs; }; - if (elem == nil) { return false; }; - if (elem.kind != nkind.N_TNAME) { return false; }; - return typenameissigned(elem.str); -}; - -// typenameissigned — true for i8/i16/i32/i64/int. rune is excluded -// (it's a non-negative Unicode codepoint, treated as unsigned). -fn typenameissigned(nm: str) bool = { - if (streq(nm, "i8")) { return true; }; - if (streq(nm, "i16")) { return true; }; - if (streq(nm, "i32")) { return true; }; - if (streq(nm, "i64")) { return true; }; - if (streq(nm, "int")) { return true; }; - return false; -}; - -// fieldissignedc — does this field/element type need sign-extension -// on a sub-word load? Walks TBANG / TENUM / TNAME-aliases to the -// resolved primitive. Mirrors cstage's fld_issigned: bool is treated -// as unsigned (0/1 ⇒ MOVZBQ); rune is unsigned (codepoint ⇒ MOVL). +// fieldissignedc — does this field/element type-AST need sign- +// extension on a sub-word load? One-liner via typeissigned (cstage +// cgen.c:240 `fld_issigned` SSoT). t.type_ is stamped at check.ww +// L426-436 for every type-AST kind we see here (TNAME / TPTR / +// TBANG / TENUM / TARRAY / TSLICE — see resolvewalk). fn fieldissignedc(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; - let k: nkind = t.kind; - if (k == nkind.N_TBANG) { return fieldissignedc(c, t.lhs); }; - if (k == nkind.N_TENUM) { return fieldissignedc(c, t.lhs); }; - if (k == nkind.N_TNAME) { - let nm: str = t.str; - if (streq(nm, "bool")) { return false; }; - if (typenameisunsigned(nm)) { return false; }; - if (typenameissigned(nm)) { return true; }; - let al: *node = aliaslookup(c, nm); - if (al != nil) { return fieldissignedc(c, al); }; - let en: *enumtype = enumlookup(c, nm); - if (en != nil) { - if (en.storage != nil) { - return fieldissignedc(c, en.storage); - }; - return true; // default i32 storage is signed - }; - }; - return false; + return typeissigned(t.type_: *tinfo); }; // fieldloadop — pick the load instruction for a non-str struct @@ -11614,7 +11555,10 @@ fn nodeisunsigned(c: *cgen, n: *node) bool = { if (k == nkind.N_IDENT) { let nm: str = n.str; let lc: *local = localfindnode(c, nm); - if (lc != nil) { return typenodeisunsigned(lc.tnode); }; + if (lc != nil) { + if (lc.tnode == nil) { return false; }; + return typeisunsigned(lc.tnode.type_: *tinfo); + }; return false; }; if (k == nkind.N_DOT) { @@ -11644,7 +11588,8 @@ fn nodeisunsigned(c: *cgen, n: *node) bool = { for (fi != nil) { let fn_: str = fi.fname; if (streq(fn_, fld)) { - return typenodeisunsigned(fi.tnode); + if (fi.tnode == nil) { return false; }; + return typeisunsigned(fi.tnode.type_: *tinfo); }; fi = fi.finext; }; @@ -11655,7 +11600,10 @@ fn nodeisunsigned(c: *cgen, n: *node) bool = { }; return false; }; - if (k == nkind.N_CAST) { return typenodeisunsigned(n.rhs); }; + if (k == nkind.N_CAST) { + if (n.rhs == nil) { return false; }; + return typeisunsigned(n.rhs.type_: *tinfo); + }; if (k == nkind.N_BIN) { if (nodeisunsigned(c, n.lhs)) { return true; }; return nodeisunsigned(c, n.rhs); @@ -11679,7 +11627,7 @@ fn nodeisunsigned(c: *cgen, n: *node) bool = { if (tn.kind == nkind.N_TARRAY) { elem = tn.lhs; }; if (tn.kind == nkind.N_TSLICE) { elem = tn.lhs; }; if (elem != nil) { - return typenodeisunsigned(elem); + return typeisunsigned(elem.type_: *tinfo); }; }; }; @@ -17845,7 +17793,10 @@ fn cgassign(c: *cgen, n: *node) void = { // arm. Pre-fix the default branch silently stored // rhs into *p (combineop = MOVQ shape). if (n.op == tkind.TK_SLASHEQ || n.op == tkind.TK_PERCENTEQ) { - let unsignd: bool = typenodeisunsignedc(c, pe); + let unsignd: bool = false; + if (pe != nil) { + unsignd = typeisunsigned(pe.type_: *tinfo); + }; if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; @@ -19730,7 +19681,10 @@ fn cgassign(c: *cgen, n: *node) void = { // ferry AX or DX back to BX for the shared // store-BX tail below. else { if (n.op == tkind.TK_SLASHEQ || n.op == tkind.TK_PERCENTEQ) { - let unsignd: bool = typenodeisunsignedc(c, lvftn); + let unsignd: bool = false; + if (lvftn != nil) { + unsignd = typeisunsigned(lvftn.type_: *tinfo); + }; if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; @@ -20013,7 +19967,9 @@ fn cgassign(c: *cgen, n: *node) void = { if (n.op == tkind.TK_SLASHEQ || n.op == tkind.TK_PERCENTEQ) { let unsignd: bool = false; if (lcn != nil) { - unsignd = typenodeisunsignedc(c, lcn.tnode); + if (lcn.tnode != nil) { + unsignd = typeisunsigned(lcn.tnode.type_: *tinfo); + }; }; if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs);