selfhost/cmd/wcc/cgenutil: collapse localloadop onto tinfo (#47, A.6.3c)

localloadop selected MOVBQSX/MOVBQZX/MOVSWQ/MOVL/MOVQ for scalar
local/let loads via an AST walk down TBANG/TENUM/TNAME-alias chains,
re-consulting aliaslookup and ending in fieldsize + fieldissignedc.
Three precursors retire the walk: #53 (e3237d1, scopelookupprefer
fixes flat-alias collision in resolvealias), #51 (1ce0f63, INTLIT
tsuffix stamp), #52 (747279c, typenameisunsigned collapse onto
typeisunsigned). Plus the A.6.2 type-AST stamp invariant at
check.ww L426-436.

Body collapses to two nil guards + ti.size + size cascade +
typeissigned(ti). cstage cmd/w6c/cgen.c:357-362 is the SSoT;
wwstage now reads tinfo.size directly the same way cstage reads
t->size, with TBANG / TENUM / TNAME-alias chains pre-folded by
tinfofornode (check.ww:1102-1153 TNAME, 1154-1161 TBANG,
1196-1208 TENUM).

Defensive nil branches mirror cstage's `(t && t->size > 0) ? sz : 8`
fall-through: when type info is missing, sz fails the != 1/2/4
discriminator and the helper returns MOVQ. Both nil paths are
reachable: cgenexpr.ww callers (4 sites: 643, 664, 5380, 5471)
populate the *node via a search loop that may exit with nil.

Signature unchanged — `c: *cgen` is retained unused for callsite
stability, mirroring A.6.3a fieldissignedc(c, t) which followed the
same precedent. 5 callsites in cgenexpr.ww untouched (body-only
collapse).

Net -33 LOC across cgenutil.ww + two .combined.ww bundler regens.
Byte-identity (994/995) is the behavior gate; full make test green
at 133/133 confirms.
This commit is contained in:
2026-05-23 00:11:05 +09:00
parent 747279c029
commit 68219a119c
3 changed files with 45 additions and 75 deletions

View File

@@ -941,35 +941,25 @@ fn loadopsz(sigd: bool, sz: i32) str = {
};
// localloadop — read instruction for a scalar local/let load. Same
// dispatch as fieldloadop, but keyed on the value's own tnode. Lets
// the caller emit MOVSXD/MOVSWQ/MOVSBQ on a signed-narrow slot instead
// of a raw MOVQ, so a slot that was last written by a narrow deref-
// store (`*p: *i32 = v` lowers to MOVL, only 4B) reads back as a
// properly-sign-extended i64. The natural N_ASSIGN / N_LET paths
// dispatch as fieldloadop, but keyed on the value's own tnode.type_.
// Lets the caller emit MOVSXD/MOVSWQ/MOVSBQ on a signed-narrow slot
// instead of a raw MOVQ, so a slot that was last written by a narrow
// deref-store (`*p: *i32 = v` lowers to MOVL, only 4B) reads back as
// a properly-sign-extended i64. The natural N_ASSIGN / N_LET paths
// store the rhs as a sign-extended 8B word, so MOVQ accidentally
// works; deref-stores are the only path that touches fewer bytes
// than MOVQ reads. Mirror of cstage's localloadop in cmd/w6c/cgen.c.
// Resolves TBANG / TENUM / TNAME-alias chains so `type err = !i32`
// picks up size 4 the same way the cstage checker pre-computes
// t->size — without this, aliased narrows fall through to MOVQ.
// than MOVQ reads. Mirror of cstage's localloadop in cmd/w6c/cgen.c
// — tinfo.size carries the same numeric width cstage's `t->size`
// reports, with TBANG / TENUM / TNAME-alias chains pre-folded by
// tinfofornode (check.ww:1102-1153 TNAME, 1154-1161 TBANG,
// 1196-1208 TENUM).
export fn localloadop(c: *cgen, tnode: *node) str = {
let t: *node = tnode;
for (t != nil) {
let k: nkind = t.kind;
if (k == nkind.N_TBANG) { t = t.lhs; }
else { if (k == nkind.N_TENUM) { t = t.lhs; }
else { if (k == nkind.N_TNAME) {
let nm: str = t.str;
if (primsize(nm) > 0) { break; };
let al: *node = aliaslookup(c, nm);
if (al == nil) { break; };
t = al;
}
else { break; }; }; };
};
let sz: i32 = fieldsize(c, t);
if (tnode == nil) { return "MOVQ"; };
let ti: *tinfo = tnode.type_: *tinfo;
if (ti == nil) { return "MOVQ"; };
let sz: i32 = ti.size: i32;
if (sz != 1) { if (sz != 2) { if (sz != 4) { return "MOVQ"; }; }; };
let sigd: bool = fieldissignedc(c, tnode);
let sigd: bool = typeissigned(ti);
return loadopsz(sigd, sz);
};