w6c+w6c_ww: (*p)[i] deref base materializes the array ADDRESS (#61 C)

Both stages SEGV'd identically (byte-id-blind): cgun's TK_STAR emitted
a scalar MOVQ (AX),AX for an array pointee, so the index consumed
a[0]'s VALUE as its base — a wild deref. An array value IS its address
everywhere in this cgen (#270-1a), so the ARRAY pointee now takes the
same skip as the #185 *fn deref in both stages: `*p` leaves AX = p's
value, and every consumer that materializes a complex index base via
cgexpr(base) — N_INDEX read fallback, cgassign store/compound, TK_AMP,
N_SLICE — gets the array address from the one deref choke-point.

wwstage additionally joins the N_UN-TK_STAR base to the stamped-tinfo
esz arms (cgindex / cgassign store + compound / TK_AMP &(*p)[i]) where
cstage reads base->type uniformly: without it, esz fell to the 8B
default and a narrow element would mis-stride the moment the base
started materializing (cs!=ww only reachable post-choke-point-fix,
which is why it rides this commit).

949_ptrarr_index_run grows the deref_* rows: read (8B/4B/param-base),
write (8B / 1B+neighbor-guards), compound — runtime + byte-id, the only
nets that can see a both-stages-identical miscompile.
This commit is contained in:
2026-06-04 22:08:11 +09:00
parent eea3e197c2
commit 33ec0fb1ac
5 changed files with 169 additions and 20 deletions

View File

@@ -21930,7 +21930,8 @@ fn cgindex(c: *cgen, n: *node) void = {
};
};
};
} else { if (base.kind == nkind.N_DOT) {
} else { if (base.kind == nkind.N_DOT
|| (base.kind == nkind.N_UN && base.op == tkind.TK_STAR)) {
// `s.ptr[i]` / struct-field index: stride is the
// checker-stamped element tinfo's natural size, the
// same idiom as the N_INDEX-base arm below (#60/#72).
@@ -21939,6 +21940,10 @@ fn cgindex(c: *cgen, n: *node) void = {
// 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).
// N_UN deref base (`(*p)[i]`, #61 C): same stamped-tinfo
// source — cstage reads base->type uniformly; without
// this arm esz fell to the 8B default (wrong stride for
// narrow elements once the deref base materializes).
let dt: *tinfo = n.type_: *tinfo;
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);
@@ -21997,7 +22002,9 @@ fn cgindex(c: *cgen, n: *node) void = {
// cstage's fallback `MOVQ AX,BX; MOVQ (BX),AX`). #261: without
// this an N_DOT-base tagged element fell to the scalar
// loadopsz path and silently dropped the tag/payload-high word.
if (base.kind == nkind.N_DOT || base.kind == nkind.N_INDEX) {
// N_UN deref base joins for the same reason (#61 C).
if (base.kind == nkind.N_DOT || base.kind == nkind.N_INDEX
|| (base.kind == nkind.N_UN && base.op == tkind.TK_STAR)) {
let dt: *tinfo = n.type_: *tinfo;
if (dt != nil) {
if (typeistagged(dt)) {
@@ -24489,12 +24496,17 @@ fn cgun(c: *cgen, n: *node) void = {
};
};
};
} else { if (base.kind == nkind.N_DOT) {
} else { if (base.kind == nkind.N_DOT
|| (base.kind == nkind.N_UN
&& base.op == tkind.TK_STAR)) {
// `&p.ptr[i]`: stride is the checker-stamped
// element tinfo's natural size, mirroring
// cgindex's N_DOT arm so &p.ptr[i] and
// p.ptr[i] agree. cstage idx_eff(base->type)
// ->sub->size (cmd/w6c/cgen.c:3517-18). #72.
// N_UN deref base (`&(*p)[i]`, #61 C): same
// stamped source; cstage reads base->type
// uniformly.
let dt: *tinfo = opnd.type_: *tinfo;
if (dt != nil) { esz = dt.size: i32; };
};};
@@ -24583,9 +24595,15 @@ fn cgun(c: *cgen, n: *node) void = {
// would load the first instruction word and a subsequent
// CALL would segfault. Mirror ref/harec/src/check.c
// expr_call's STORAGE_POINTER→STORAGE_FUNCTION skip.
// #61 C: same skip for an ARRAY pointee — an array value IS
// its address everywhere in this cgen (#270-1a), so `*p` on
// `*[N]T` leaves AX = p's value. The scalar load below
// pulled a[0]'s VALUE and `(*p)[i]` then dereferenced it as
// the index base — a wild pointer, SIGSEGV on both stages.
let rti: *tinfo = n.type_: *tinfo;
for (rti != nil && rti.kind == tykind.TY_NAMED) { rti = rti.under; };
if (rti != nil && rti.kind == tykind.TY_FN) { return; };
if (rti != nil && rti.kind == tykind.TY_ARRAY) { return; };
// f64/f32 result rides X0 (SSE), not AX — an integer MOVQ
// strands the value off the float ABI and the caller's
// MOVSD X0 reads stale bits (#96). Mirrors the float
@@ -27232,13 +27250,16 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
} else { if (base.kind == nkind.N_DOT) {
} else { if (base.kind == nkind.N_DOT
|| (base.kind == nkind.N_UN
&& base.op == tkind.TK_STAR)) {
// lhs.type_ is the checker-stamped element tinfo
// of the N_INDEX: esz is its natural size and the
// tagged-element gate (below) reads the same
// .type_ — same idiom as cgindex's n.type_ read
// (#60/#72). cstage idx_eff(base->type)->sub->size
// (cmd/w6c/cgen.c:3517-18).
// (cmd/w6c/cgen.c:3517-18). N_UN deref base
// (`(*p)[i] = v`, #61 C): same stamped source.
let dt: *tinfo = lhs.type_: *tinfo;
if (dt != nil) { esz = dt.size: i32; elemtn = lhs; };
} else { if (base.kind == nkind.N_INDEX) {
@@ -27677,7 +27698,11 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
} else { if (base.kind == nkind.N_DOT) {
} else { if (base.kind == nkind.N_DOT
|| (base.kind == nkind.N_UN
&& base.op == tkind.TK_STAR)) {
// N_UN deref base (`(*p)[i] OP= v`, #61 C):
// same stamped source as the store arm.
let dt: *tinfo = lhs.type_: *tinfo;
if (dt != nil) { esz = dt.size: i32; elemtn = lhs; };
} else { if (base.kind == nkind.N_INDEX) {

View File

@@ -1558,7 +1558,8 @@ fn cgindex(c: *cgen, n: *node) void = {
};
};
};
} else { if (base.kind == nkind.N_DOT) {
} else { if (base.kind == nkind.N_DOT
|| (base.kind == nkind.N_UN && base.op == tkind.TK_STAR)) {
// `s.ptr[i]` / struct-field index: stride is the
// checker-stamped element tinfo's natural size, the
// same idiom as the N_INDEX-base arm below (#60/#72).
@@ -1567,6 +1568,10 @@ fn cgindex(c: *cgen, n: *node) void = {
// 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).
// N_UN deref base (`(*p)[i]`, #61 C): same stamped-tinfo
// source — cstage reads base->type uniformly; without
// this arm esz fell to the 8B default (wrong stride for
// narrow elements once the deref base materializes).
let dt: *tinfo = n.type_: *tinfo;
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);
@@ -1625,7 +1630,9 @@ fn cgindex(c: *cgen, n: *node) void = {
// cstage's fallback `MOVQ AX,BX; MOVQ (BX),AX`). #261: without
// this an N_DOT-base tagged element fell to the scalar
// loadopsz path and silently dropped the tag/payload-high word.
if (base.kind == nkind.N_DOT || base.kind == nkind.N_INDEX) {
// N_UN deref base joins for the same reason (#61 C).
if (base.kind == nkind.N_DOT || base.kind == nkind.N_INDEX
|| (base.kind == nkind.N_UN && base.op == tkind.TK_STAR)) {
let dt: *tinfo = n.type_: *tinfo;
if (dt != nil) {
if (typeistagged(dt)) {
@@ -4117,12 +4124,17 @@ fn cgun(c: *cgen, n: *node) void = {
};
};
};
} else { if (base.kind == nkind.N_DOT) {
} else { if (base.kind == nkind.N_DOT
|| (base.kind == nkind.N_UN
&& base.op == tkind.TK_STAR)) {
// `&p.ptr[i]`: stride is the checker-stamped
// element tinfo's natural size, mirroring
// cgindex's N_DOT arm so &p.ptr[i] and
// p.ptr[i] agree. cstage idx_eff(base->type)
// ->sub->size (cmd/w6c/cgen.c:3517-18). #72.
// N_UN deref base (`&(*p)[i]`, #61 C): same
// stamped source; cstage reads base->type
// uniformly.
let dt: *tinfo = opnd.type_: *tinfo;
if (dt != nil) { esz = dt.size: i32; };
};};
@@ -4211,9 +4223,15 @@ fn cgun(c: *cgen, n: *node) void = {
// would load the first instruction word and a subsequent
// CALL would segfault. Mirror ref/harec/src/check.c
// expr_call's STORAGE_POINTER→STORAGE_FUNCTION skip.
// #61 C: same skip for an ARRAY pointee — an array value IS
// its address everywhere in this cgen (#270-1a), so `*p` on
// `*[N]T` leaves AX = p's value. The scalar load below
// pulled a[0]'s VALUE and `(*p)[i]` then dereferenced it as
// the index base — a wild pointer, SIGSEGV on both stages.
let rti: *tinfo = n.type_: *tinfo;
for (rti != nil && rti.kind == tykind.TY_NAMED) { rti = rti.under; };
if (rti != nil && rti.kind == tykind.TY_FN) { return; };
if (rti != nil && rti.kind == tykind.TY_ARRAY) { return; };
// f64/f32 result rides X0 (SSE), not AX — an integer MOVQ
// strands the value off the float ABI and the caller's
// MOVSD X0 reads stale bits (#96). Mirrors the float
@@ -6860,13 +6878,16 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
} else { if (base.kind == nkind.N_DOT) {
} else { if (base.kind == nkind.N_DOT
|| (base.kind == nkind.N_UN
&& base.op == tkind.TK_STAR)) {
// lhs.type_ is the checker-stamped element tinfo
// of the N_INDEX: esz is its natural size and the
// tagged-element gate (below) reads the same
// .type_ — same idiom as cgindex's n.type_ read
// (#60/#72). cstage idx_eff(base->type)->sub->size
// (cmd/w6c/cgen.c:3517-18).
// (cmd/w6c/cgen.c:3517-18). N_UN deref base
// (`(*p)[i] = v`, #61 C): same stamped source.
let dt: *tinfo = lhs.type_: *tinfo;
if (dt != nil) { esz = dt.size: i32; elemtn = lhs; };
} else { if (base.kind == nkind.N_INDEX) {
@@ -7305,7 +7326,11 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
} else { if (base.kind == nkind.N_DOT) {
} else { if (base.kind == nkind.N_DOT
|| (base.kind == nkind.N_UN
&& base.op == tkind.TK_STAR)) {
// N_UN deref base (`(*p)[i] OP= v`, #61 C):
// same stamped source as the store arm.
let dt: *tinfo = lhs.type_: *tinfo;
if (dt != nil) { esz = dt.size: i32; elemtn = lhs; };
} else { if (base.kind == nkind.N_INDEX) {

View File

@@ -21930,7 +21930,8 @@ fn cgindex(c: *cgen, n: *node) void = {
};
};
};
} else { if (base.kind == nkind.N_DOT) {
} else { if (base.kind == nkind.N_DOT
|| (base.kind == nkind.N_UN && base.op == tkind.TK_STAR)) {
// `s.ptr[i]` / struct-field index: stride is the
// checker-stamped element tinfo's natural size, the
// same idiom as the N_INDEX-base arm below (#60/#72).
@@ -21939,6 +21940,10 @@ fn cgindex(c: *cgen, n: *node) void = {
// 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).
// N_UN deref base (`(*p)[i]`, #61 C): same stamped-tinfo
// source — cstage reads base->type uniformly; without
// this arm esz fell to the 8B default (wrong stride for
// narrow elements once the deref base materializes).
let dt: *tinfo = n.type_: *tinfo;
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);
@@ -21997,7 +22002,9 @@ fn cgindex(c: *cgen, n: *node) void = {
// cstage's fallback `MOVQ AX,BX; MOVQ (BX),AX`). #261: without
// this an N_DOT-base tagged element fell to the scalar
// loadopsz path and silently dropped the tag/payload-high word.
if (base.kind == nkind.N_DOT || base.kind == nkind.N_INDEX) {
// N_UN deref base joins for the same reason (#61 C).
if (base.kind == nkind.N_DOT || base.kind == nkind.N_INDEX
|| (base.kind == nkind.N_UN && base.op == tkind.TK_STAR)) {
let dt: *tinfo = n.type_: *tinfo;
if (dt != nil) {
if (typeistagged(dt)) {
@@ -24489,12 +24496,17 @@ fn cgun(c: *cgen, n: *node) void = {
};
};
};
} else { if (base.kind == nkind.N_DOT) {
} else { if (base.kind == nkind.N_DOT
|| (base.kind == nkind.N_UN
&& base.op == tkind.TK_STAR)) {
// `&p.ptr[i]`: stride is the checker-stamped
// element tinfo's natural size, mirroring
// cgindex's N_DOT arm so &p.ptr[i] and
// p.ptr[i] agree. cstage idx_eff(base->type)
// ->sub->size (cmd/w6c/cgen.c:3517-18). #72.
// N_UN deref base (`&(*p)[i]`, #61 C): same
// stamped source; cstage reads base->type
// uniformly.
let dt: *tinfo = opnd.type_: *tinfo;
if (dt != nil) { esz = dt.size: i32; };
};};
@@ -24583,9 +24595,15 @@ fn cgun(c: *cgen, n: *node) void = {
// would load the first instruction word and a subsequent
// CALL would segfault. Mirror ref/harec/src/check.c
// expr_call's STORAGE_POINTER→STORAGE_FUNCTION skip.
// #61 C: same skip for an ARRAY pointee — an array value IS
// its address everywhere in this cgen (#270-1a), so `*p` on
// `*[N]T` leaves AX = p's value. The scalar load below
// pulled a[0]'s VALUE and `(*p)[i]` then dereferenced it as
// the index base — a wild pointer, SIGSEGV on both stages.
let rti: *tinfo = n.type_: *tinfo;
for (rti != nil && rti.kind == tykind.TY_NAMED) { rti = rti.under; };
if (rti != nil && rti.kind == tykind.TY_FN) { return; };
if (rti != nil && rti.kind == tykind.TY_ARRAY) { return; };
// f64/f32 result rides X0 (SSE), not AX — an integer MOVQ
// strands the value off the float ABI and the caller's
// MOVSD X0 reads stale bits (#96). Mirrors the float
@@ -27232,13 +27250,16 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
} else { if (base.kind == nkind.N_DOT) {
} else { if (base.kind == nkind.N_DOT
|| (base.kind == nkind.N_UN
&& base.op == tkind.TK_STAR)) {
// lhs.type_ is the checker-stamped element tinfo
// of the N_INDEX: esz is its natural size and the
// tagged-element gate (below) reads the same
// .type_ — same idiom as cgindex's n.type_ read
// (#60/#72). cstage idx_eff(base->type)->sub->size
// (cmd/w6c/cgen.c:3517-18).
// (cmd/w6c/cgen.c:3517-18). N_UN deref base
// (`(*p)[i] = v`, #61 C): same stamped source.
let dt: *tinfo = lhs.type_: *tinfo;
if (dt != nil) { esz = dt.size: i32; elemtn = lhs; };
} else { if (base.kind == nkind.N_INDEX) {
@@ -27677,7 +27698,11 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
} else { if (base.kind == nkind.N_DOT) {
} else { if (base.kind == nkind.N_DOT
|| (base.kind == nkind.N_UN
&& base.op == tkind.TK_STAR)) {
// N_UN deref base (`(*p)[i] OP= v`, #61 C):
// same stamped source as the store arm.
let dt: *tinfo = lhs.type_: *tinfo;
if (dt != nil) { esz = dt.size: i32; elemtn = lhs; };
} else { if (base.kind == nkind.N_INDEX) {