wwstage: sign-extend signed-narrow struct-array-field element load via N_DOT base (#255)

The cgindex N_DOT-base arm set esz from the checker-stamped element
tinfo but skipped signedness, so loadopsz saw signed_elem=false and
emitted MOVL/MOVZ* (zero-extend) where cstage's fldloadop reads
signedness from the element type and emits MOVSXD/MOVSWQ/MOVSBQ. A
negative i8/i16/i32 read of `x.o[k]` (struct `[N]T` field) round-tripped
with the wrong upper bits — silent cs!=ww, byte-id-blind since bootstrap
never indexes signed-narrow struct array-fields.

Mirror the sibling N_INDEX-base arm: signed_elem = typeissigned(dt).
loadopsz already keys on (signed,sz), so this closes all three narrow
widths at once. Pure wwstage-up; cstage unchanged.

949 gains nload_i32/i16/i8 negative-read rows (run + cs==ww byte-id).
combined.ww regen'd for w6c + wwdump (the cgen embedders).
This commit is contained in:
2026-06-02 04:07:42 +09:00
parent 585ec50676
commit d8aaa54b41
4 changed files with 56 additions and 16 deletions

View File

@@ -20499,10 +20499,12 @@ fn cgindex(c: *cgen, n: *node) void = {
// checker-stamped element tinfo's natural size, the
// same idiom as the N_INDEX-base arm below (#60/#72).
// cstage idx_eff(base->type)->sub->size (cmd/w6c/
// cgen.c:3517-18). esz-only — N_DOT-base signedness
// stays unset, as before.
// cgen.c:3517-18). Signedness from the same element tinfo
// so a signed-narrow field element sign-extends on load
// (loadopsz keys on (signed,sz)); cstage's fldloadop reads
// it from the element type — align ww up (#255).
let dt: *tinfo = n.type_: *tinfo;
if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); elemisslice = typeisslice(dt); float_elem = typeisfloat(dt); f32_elem = typeisf32(dt); };
if (dt != nil) { esz = dt.size: i32; signed_elem = typeissigned(dt); elemisstr = typeisstr(dt); elemisslice = typeisslice(dt); float_elem = typeisfloat(dt); f32_elem = typeisf32(dt); };
elem_isarray = tinfoisarray(dt);
} else { if (base.kind == nkind.N_INDEX) {
// #60: chained `names[i][k]` — n.type_ is the checker-

View File

@@ -1201,10 +1201,12 @@ fn cgindex(c: *cgen, n: *node) void = {
// checker-stamped element tinfo's natural size, the
// same idiom as the N_INDEX-base arm below (#60/#72).
// cstage idx_eff(base->type)->sub->size (cmd/w6c/
// cgen.c:3517-18). esz-only — N_DOT-base signedness
// stays unset, as before.
// cgen.c:3517-18). Signedness from the same element tinfo
// so a signed-narrow field element sign-extends on load
// (loadopsz keys on (signed,sz)); cstage's fldloadop reads
// it from the element type — align ww up (#255).
let dt: *tinfo = n.type_: *tinfo;
if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); elemisslice = typeisslice(dt); float_elem = typeisfloat(dt); f32_elem = typeisf32(dt); };
if (dt != nil) { esz = dt.size: i32; signed_elem = typeissigned(dt); elemisstr = typeisstr(dt); elemisslice = typeisslice(dt); float_elem = typeisfloat(dt); f32_elem = typeisf32(dt); };
elem_isarray = tinfoisarray(dt);
} else { if (base.kind == nkind.N_INDEX) {
// #60: chained `names[i][k]` — n.type_ is the checker-

View File

@@ -20499,10 +20499,12 @@ fn cgindex(c: *cgen, n: *node) void = {
// checker-stamped element tinfo's natural size, the
// same idiom as the N_INDEX-base arm below (#60/#72).
// cstage idx_eff(base->type)->sub->size (cmd/w6c/
// cgen.c:3517-18). esz-only — N_DOT-base signedness
// stays unset, as before.
// cgen.c:3517-18). Signedness from the same element tinfo
// so a signed-narrow field element sign-extends on load
// (loadopsz keys on (signed,sz)); cstage's fldloadop reads
// it from the element type — align ww up (#255).
let dt: *tinfo = n.type_: *tinfo;
if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); elemisslice = typeisslice(dt); float_elem = typeisfloat(dt); f32_elem = typeisf32(dt); };
if (dt != nil) { esz = dt.size: i32; signed_elem = typeissigned(dt); elemisstr = typeisstr(dt); elemisslice = typeisslice(dt); float_elem = typeisfloat(dt); f32_elem = typeisf32(dt); };
elem_isarray = tinfoisarray(dt);
} else { if (base.kind == nkind.N_INDEX) {
// #60: chained `names[i][k]` — n.type_ is the checker-

View File

@@ -56,14 +56,14 @@
*
* Run-only rows (byteid=0): the chained VALUE-container arm (`o.i.m`,
* inner is a value nested struct). These exercise the same fixed helper
* and run correctly, but a value nested-struct instance trips THREE
* and run correctly, but a value nested-struct instance trips two
* orthogonal pre-existing cs!=ww divergences unrelated to #253 — bare-
* let zero-init policy (wwstage emits an extra `MOVQ $0,off(BP)`),
* global DATAW byte count (wwstage over-emits), and the i32 element-
* LOAD opcode in the index fallback (cstage MOVSXD vs wwstage MOVL) —
* so the rule-10 byte-id gate can't apply here until those are fixed
* (filed: wwstage value-nested-struct emission divergence). Run
* correctness alone proves the #253 segfault is gone for this cell.
* let zero-init policy (wwstage emits an extra `MOVQ $0,off(BP)`) and
* global DATAW byte count (wwstage over-emits) — so the rule-10 byte-id
* gate can't apply here until those are fixed (#254). (A third, the
* signed-narrow index-fallback element-LOAD opcode, was #255 — now
* fixed; these u8 rows never hit it anyway.) Run correctness alone
* proves the #253 segfault is gone for this cell.
* - chain_val_rd o.i.m[1] read (i value nested) → 66
* - chain_val_addr &o.i.m[2] then *q read → 55
* - chain_val_slice o.i.m[1:4], s[0] → 66
@@ -304,6 +304,40 @@ static const struct row rows[] = {
" let x: e; x.m[1] = 66u8;\n"
" return x.m[1]: i32;\n"
"};\n", 66, 1 },
/* #255 signed-narrow N_DOT-base index read. Reading x.o[k] of a
* [N]i32/i16/i8 struct field via the N_DOT-base index fallback must
* sign-extend the narrow element (loadopsz keys on (signed,sz) →
* MOVSXD/MOVSWQ/MOVSBQ); pre-fix wwstage left signedness unset and
* emitted MOVL/MOVZ* (zero-extend) where cstage emits MOVS* — a
* byte-id divergence bootstrap never indexes, so these rows ARE the
* net. Negative round-trip (-5 → exit 251 = 256-5). */
{ "nload_i32",
"package main;\n"
"type e = struct { o: [4]i32 };\n"
"export fn main() i32 = {\n"
" let x: e;\n"
" x.o[2] = -5;\n"
" let v: i32 = x.o[2];\n"
" return v;\n"
"};\n", 251, 1 },
{ "nload_i16",
"package main;\n"
"type e = struct { o: [4]i16 };\n"
"export fn main() i32 = {\n"
" let x: e;\n"
" x.o[2] = -5i16;\n"
" let v: i16 = x.o[2];\n"
" return v: i32;\n"
"};\n", 251, 1 },
{ "nload_i8",
"package main;\n"
"type e = struct { o: [4]i8 };\n"
"export fn main() i32 = {\n"
" let x: e;\n"
" x.o[2] = -5i8;\n"
" let v: i8 = x.o[2];\n"
" return v: i32;\n"
"};\n", 251, 1 },
/* #253 chained VALUE-container arm (o.i.m). Run-only (byteid=0):
* a value nested-struct instance trips orthogonal pre-existing
* cs!=ww emission divergences (see header). The fixed helper runs