w6c+selfhost: localloadop helper for sign-aware ident loads (closes #19)

Read-side fix dual to fldloadop: signed-narrow local/global ident loads
now MOVSXD/MOVSWQ/MOVSBQ from the slot instead of raw MOVQ. Deref-stores
(MOVL/MOVW/MOVB) no longer corrupt downstream i64 widens. Compound RMW
restructured to gate direct-mem ADDQ/SUBQ on load_op == MOVQ. Top-level
lets use LEAQ+indirect (w6a doesn't expose MOVSXD/MOVSWQ/MOVSBQ for
D_EXTERN).

dotchainresolve out-params restored to natural *i32 (workaround retired).
selfhost/CLAUDE.md graduated.
This commit is contained in:
2026-05-14 01:58:52 +09:00
parent 5f87c60e6c
commit d2f4659305
7 changed files with 728 additions and 281 deletions

View File

@@ -724,6 +724,39 @@ fn loadopsz(sigd: bool, sz: i32) str = {
return "MOVQ";
};
// 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
// 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.
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 (sz != 1) { if (sz != 2) { if (sz != 4) { return "MOVQ"; }; }; };
let sigd: bool = fieldissignedc(c, tnode);
return loadopsz(sigd, sz);
};
// indexbaseesz — element size for `arr[i]` where the base is a
// chained-dot pseudo-field `s.ptr` (s being str/*str/slice/*slice).
// For str the element is one byte; for `[]T` / `*[]T` we drill into
@@ -2325,22 +2358,19 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz:
// (*outisglobal false, base = *outrootoff(BP)) or top-level let
// (*outisglobal true, base reached via LEAQ *outrootname(SB), CX).
//
// Numeric out-params are i64 so the deref-stores stay 8-byte (MOVQ).
// `*p: *i32 = v` writes only 4 bytes via MOVL, leaving the caller's
// 8-byte slot's upper half stale from its zero-init — and a later MOVQ
// read sees the zero-extended low half, so a negative i32 root offset
// would come back as a huge positive i64. Tracked as task #19; until
// it lands, callers cast to i32 at the assign sites.
// Numeric out-params are i32 — offsets fit naturally and the post-#19
// localloadop sign-extends i32 deref-stored slots on read, so negative
// frame offsets round-trip intact.
export fn dotchainresolve(c: *cgen, n: *node,
outrootname: *str, outrootoff: *i64, outtotaloff: *i64,
outleaffi: **fieldinfo, outslicedelta: *i64,
outrootname: *str, outrootoff: *i32, outtotaloff: *i32,
outleaffi: **fieldinfo, outslicedelta: *i32,
outisglobal: *bool) bool = {
*outrootname = "";
*outrootoff = 0i64;
*outrootoff = 0;
*outisglobal = false;
*outtotaloff = 0i64;
*outtotaloff = 0;
*outleaffi = nil;
*outslicedelta = -1i64;
*outslicedelta = -1;
if (n == nil) { return false; };
if (n.kind != nkind.N_DOT) { return false; };
let stk: [16]*node;
@@ -2363,7 +2393,7 @@ export fn dotchainresolve(c: *cgen, n: *node,
if (lc.tnode != nil) {
if (lc.tnode.kind == nkind.N_TNAME) {
rootstruct = lc.tnode.str;
*outrootoff = lc.off: i64;
*outrootoff = lc.off;
};
};
};
@@ -2390,7 +2420,7 @@ export fn dotchainresolve(c: *cgen, n: *node,
};
if (found == nil) { return false; };
if (i == 0) {
*outtotaloff = *outtotaloff + (found.foff: i64);
*outtotaloff = *outtotaloff + found.foff;
*outleaffi = found;
return true;
};
@@ -2400,27 +2430,27 @@ export fn dotchainresolve(c: *cgen, n: *node,
if (streq(ft.str, "str")) {
if (i != 1) { return false; };
let pseudo: str = stk[0].str;
let delta: i64 = -1i64;
if (streq(pseudo, "ptr")) { delta = 0i64; }
else { if (streq(pseudo, "len")) { delta = 8i64; }; };
if (delta < 0i64) { return false; };
*outtotaloff = *outtotaloff + (found.foff: i64);
let delta: i32 = -1;
if (streq(pseudo, "ptr")) { delta = 0; }
else { if (streq(pseudo, "len")) { delta = 8; }; };
if (delta < 0) { return false; };
*outtotaloff = *outtotaloff + found.foff;
*outslicedelta = delta;
return true;
};
if (primsize(ft.str) != 0) { return false; };
*outtotaloff = *outtotaloff + (found.foff: i64);
*outtotaloff = *outtotaloff + found.foff;
curstruct = ft.str;
i -= 1;
} else { if (ft.kind == nkind.N_TSLICE) {
if (i != 1) { return false; };
let pseudo: str = stk[0].str;
let delta: i64 = -1i64;
if (streq(pseudo, "ptr")) { delta = 0i64; }
else { if (streq(pseudo, "len")) { delta = 8i64; }
else { if (streq(pseudo, "cap")) { delta = 16i64; }; }; };
if (delta < 0i64) { return false; };
*outtotaloff = *outtotaloff + (found.foff: i64);
let delta: i32 = -1;
if (streq(pseudo, "ptr")) { delta = 0; }
else { if (streq(pseudo, "len")) { delta = 8; }
else { if (streq(pseudo, "cap")) { delta = 16; }; }; };
if (delta < 0) { return false; };
*outtotaloff = *outtotaloff + found.foff;
*outslicedelta = delta;
return true;
} else {