selfhost/cmd/wcc: collapse signedness predicates onto n.type_ (A.6.3a, #45)

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.
This commit is contained in:
2026-05-22 05:24:49 +09:00
parent 045c49e398
commit 03e4718199
5 changed files with 180 additions and 312 deletions

View File

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

View File

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

View File

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

View File

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

View File

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