w6c_ww/cgen: uniform tinfo esz for global str/slice addr-of + store (fix #11)

Three sibling arms of the #10 global-str/slice INDEX miscompile (23670d7,
the READ path) shared the identical N_TARRAY/N_TPTR tnode-KIND whitelist in
their global-ident resolution arm and were still LIVE and silently cs!=ww:

  - cgun  `&s[1]` / `&g[1]` (cgenexpr.ww N_INDEX addr-of) — a global str
    (tnode N_TNAME) / slice (N_TSLICE) matched neither arm, so esz stayed at
    the default 8 and the base fell to the complex-base fallback: a wide
    {ptr,len,cap} header + 8-byte stride instead of MOVQ name(SB) (.ptr) +
    ADDQ.
  - cgassign `g[1] = v` store AND `g[1] OP= v` compound (two arms) — same
    whitelist; a global slice store emitted a full-word MOVQ at an 8-byte
    stride: an 8-BYTE OUT-OF-BOUNDS WRITE past a 1-byte element (memory
    corruption) instead of MOVB at .ptr+1.

cstage (cmd/w6c/cgen.c) is the runtime-correct reference and was already
uniform across all three: esz off idx_eff(base->type)->sub->size and the
base load gated by is_arr (TY_ARRAY -> LEAQ name(SB), every other -> MOVQ
name(SB), since a str/slice's .ptr IS the symbol's first word). Align the
wwstage UP to that, mirroring the just-landed cgindex template (#10): resolve
esz via elemsizeofc with no kind gate, dispatch the base by N_TARRAY ? LEAQ :
MOVQ name(SB). The store/compound arms also resolve elemtn exactly like their
local branch (element node for ARRAY/SLICE/PTR; nil for str so tnodestoreop
picks MOVB) so a global []str store routes to the 3-word header store and the
compound arm's str/slice hard-error still fires.

Close-by-construction: the global element base/stride is now computed off the
resolved type at every wwstage index site — read (cgindex, #10), addr-of
(cgun), store + compound (cgassign) — with no remaining tnode-kind whitelist.
cgslice/cgbaselen already resolved via elemsizeofc.

803_globalidx_run extends from 9 to 18 rows: global str/slice addr-of (read
back through the pointer), global slice store AND compound store `g[i] OP= v`
(the distinct third fixed arm, with adjacent-element addends as the OOB-write
guard on both), a WIDTH>1 signed variant of each (esz=4 stride/store-width pin),
and local addr-of/store regression pins. Runtime (cstage build+run) + cs==ww
byte-id per row. combined.ww embeds (w6c + wwdump) regenerate.
This commit is contained in:
2026-06-03 15:50:54 +09:00
parent 23670d7c4e
commit a9228dabb3
4 changed files with 305 additions and 69 deletions

View File

@@ -23170,17 +23170,26 @@ fn cgun(c: *cgen, n: *node) void = {
if (tn.kind == nkind.N_TARRAY) { isarr = true; };
};
} else {
// #11: addr-of twin of the #10 cgindex read
// fix. Dispatch esz + base load off the
// global's RESOLVED type, NOT an N_TARRAY/
// N_TPTR kind whitelist — a global str (tnode
// N_TNAME) / slice (N_TSLICE) matched NEITHER
// old arm, so esz stayed at the default 8 and
// the base fell to the complex-base fallback,
// yielding a wide-stride &s[i]. cstage's
// TK_AMP N_INDEX (cmd/w6c/cgen.c) is uniform:
// esz=bu->sub->size, base is_arr?LEAQ:MOVQ
// name(SB) (a str/slice's .ptr IS the symbol's
// first word). Align UP, mirroring cgindex.
let tn: *node = letvartnode(c, base.str);
if (tn != nil) {
globalname = base.str;
esz = elemsizeofc(c, tn);
if (tn.kind == nkind.N_TARRAY) {
isglobalarr = true;
globalname = base.str;
esz = elemsizeofc(c, tn);
};
if (tn.kind == nkind.N_TPTR) {
} else {
isglobalptr = true;
globalname = base.str;
esz = elemsizeofc(c, tn);
};
};
};
@@ -24797,19 +24806,34 @@ fn cgassign(c: *cgen, n: *node) void = {
if (bk == nkind.N_TPTR) { elemtn = btn.lhs; };
};
} else {
// #11: store/compound twin of the #10 cgindex
// read fix. A global str/slice element store hit
// the same kind whitelist — N_TNAME (str) /
// N_TSLICE matched NEITHER arm, so esz stayed 8
// and the store emitted a full-word MOVQ — an
// 8-byte OUT-OF-BOUNDS write past a 1-byte
// element — instead of MOVB. cstage
// (cmd/w6c/cgen.c N_INDEX store) dispatches esz
// off idx_eff->sub->size + the elem-kind flags
// off eff->sub uniformly, base is_arr?LEAQ:MOVQ
// name(SB). Align UP and resolve elemtn exactly
// like the local branch above (element node for
// ARRAY/SLICE/PTR; nil for str so tnodestoreop
// picks MOVB on the store arm, and the compound
// arm's str/slice hard-error still fires on a
// []str element).
let tn: *node = letvartnode(c, bn);
if (tn != nil) {
if (tn.kind == nkind.N_TARRAY) {
globalname = bn;
esz = elemsizeofc(c, tn);
let bk: nkind = tn.kind;
if (bk == nkind.N_TARRAY) {
isglobalarr = true;
globalname = bn;
esz = elemsizeofc(c, tn);
elemtn = tn.lhs;
};
if (tn.kind == nkind.N_TPTR) {
} else {
isglobalptr = true;
globalname = bn;
esz = elemsizeofc(c, tn);
elemtn = tn.lhs;
if (bk == nkind.N_TSLICE) { elemtn = tn.lhs; };
if (bk == nkind.N_TPTR) { elemtn = tn.lhs; };
};
};
};
@@ -25229,19 +25253,34 @@ fn cgassign(c: *cgen, n: *node) void = {
if (bk == nkind.N_TPTR) { elemtn = btn.lhs; };
};
} else {
// #11: store/compound twin of the #10 cgindex
// read fix. A global str/slice element store hit
// the same kind whitelist — N_TNAME (str) /
// N_TSLICE matched NEITHER arm, so esz stayed 8
// and the store emitted a full-word MOVQ — an
// 8-byte OUT-OF-BOUNDS write past a 1-byte
// element — instead of MOVB. cstage
// (cmd/w6c/cgen.c N_INDEX store) dispatches esz
// off idx_eff->sub->size + the elem-kind flags
// off eff->sub uniformly, base is_arr?LEAQ:MOVQ
// name(SB). Align UP and resolve elemtn exactly
// like the local branch above (element node for
// ARRAY/SLICE/PTR; nil for str so tnodestoreop
// picks MOVB on the store arm, and the compound
// arm's str/slice hard-error still fires on a
// []str element).
let tn: *node = letvartnode(c, bn);
if (tn != nil) {
if (tn.kind == nkind.N_TARRAY) {
globalname = bn;
esz = elemsizeofc(c, tn);
let bk: nkind = tn.kind;
if (bk == nkind.N_TARRAY) {
isglobalarr = true;
globalname = bn;
esz = elemsizeofc(c, tn);
elemtn = tn.lhs;
};
if (tn.kind == nkind.N_TPTR) {
} else {
isglobalptr = true;
globalname = bn;
esz = elemsizeofc(c, tn);
elemtn = tn.lhs;
if (bk == nkind.N_TSLICE) { elemtn = tn.lhs; };
if (bk == nkind.N_TPTR) { elemtn = tn.lhs; };
};
};
};

View File

@@ -3446,17 +3446,26 @@ fn cgun(c: *cgen, n: *node) void = {
if (tn.kind == nkind.N_TARRAY) { isarr = true; };
};
} else {
// #11: addr-of twin of the #10 cgindex read
// fix. Dispatch esz + base load off the
// global's RESOLVED type, NOT an N_TARRAY/
// N_TPTR kind whitelist — a global str (tnode
// N_TNAME) / slice (N_TSLICE) matched NEITHER
// old arm, so esz stayed at the default 8 and
// the base fell to the complex-base fallback,
// yielding a wide-stride &s[i]. cstage's
// TK_AMP N_INDEX (cmd/w6c/cgen.c) is uniform:
// esz=bu->sub->size, base is_arr?LEAQ:MOVQ
// name(SB) (a str/slice's .ptr IS the symbol's
// first word). Align UP, mirroring cgindex.
let tn: *node = letvartnode(c, base.str);
if (tn != nil) {
globalname = base.str;
esz = elemsizeofc(c, tn);
if (tn.kind == nkind.N_TARRAY) {
isglobalarr = true;
globalname = base.str;
esz = elemsizeofc(c, tn);
};
if (tn.kind == nkind.N_TPTR) {
} else {
isglobalptr = true;
globalname = base.str;
esz = elemsizeofc(c, tn);
};
};
};
@@ -5073,19 +5082,34 @@ fn cgassign(c: *cgen, n: *node) void = {
if (bk == nkind.N_TPTR) { elemtn = btn.lhs; };
};
} else {
// #11: store/compound twin of the #10 cgindex
// read fix. A global str/slice element store hit
// the same kind whitelist — N_TNAME (str) /
// N_TSLICE matched NEITHER arm, so esz stayed 8
// and the store emitted a full-word MOVQ — an
// 8-byte OUT-OF-BOUNDS write past a 1-byte
// element — instead of MOVB. cstage
// (cmd/w6c/cgen.c N_INDEX store) dispatches esz
// off idx_eff->sub->size + the elem-kind flags
// off eff->sub uniformly, base is_arr?LEAQ:MOVQ
// name(SB). Align UP and resolve elemtn exactly
// like the local branch above (element node for
// ARRAY/SLICE/PTR; nil for str so tnodestoreop
// picks MOVB on the store arm, and the compound
// arm's str/slice hard-error still fires on a
// []str element).
let tn: *node = letvartnode(c, bn);
if (tn != nil) {
if (tn.kind == nkind.N_TARRAY) {
globalname = bn;
esz = elemsizeofc(c, tn);
let bk: nkind = tn.kind;
if (bk == nkind.N_TARRAY) {
isglobalarr = true;
globalname = bn;
esz = elemsizeofc(c, tn);
elemtn = tn.lhs;
};
if (tn.kind == nkind.N_TPTR) {
} else {
isglobalptr = true;
globalname = bn;
esz = elemsizeofc(c, tn);
elemtn = tn.lhs;
if (bk == nkind.N_TSLICE) { elemtn = tn.lhs; };
if (bk == nkind.N_TPTR) { elemtn = tn.lhs; };
};
};
};
@@ -5505,19 +5529,34 @@ fn cgassign(c: *cgen, n: *node) void = {
if (bk == nkind.N_TPTR) { elemtn = btn.lhs; };
};
} else {
// #11: store/compound twin of the #10 cgindex
// read fix. A global str/slice element store hit
// the same kind whitelist — N_TNAME (str) /
// N_TSLICE matched NEITHER arm, so esz stayed 8
// and the store emitted a full-word MOVQ — an
// 8-byte OUT-OF-BOUNDS write past a 1-byte
// element — instead of MOVB. cstage
// (cmd/w6c/cgen.c N_INDEX store) dispatches esz
// off idx_eff->sub->size + the elem-kind flags
// off eff->sub uniformly, base is_arr?LEAQ:MOVQ
// name(SB). Align UP and resolve elemtn exactly
// like the local branch above (element node for
// ARRAY/SLICE/PTR; nil for str so tnodestoreop
// picks MOVB on the store arm, and the compound
// arm's str/slice hard-error still fires on a
// []str element).
let tn: *node = letvartnode(c, bn);
if (tn != nil) {
if (tn.kind == nkind.N_TARRAY) {
globalname = bn;
esz = elemsizeofc(c, tn);
let bk: nkind = tn.kind;
if (bk == nkind.N_TARRAY) {
isglobalarr = true;
globalname = bn;
esz = elemsizeofc(c, tn);
elemtn = tn.lhs;
};
if (tn.kind == nkind.N_TPTR) {
} else {
isglobalptr = true;
globalname = bn;
esz = elemsizeofc(c, tn);
elemtn = tn.lhs;
if (bk == nkind.N_TSLICE) { elemtn = tn.lhs; };
if (bk == nkind.N_TPTR) { elemtn = tn.lhs; };
};
};
};

View File

@@ -23170,17 +23170,26 @@ fn cgun(c: *cgen, n: *node) void = {
if (tn.kind == nkind.N_TARRAY) { isarr = true; };
};
} else {
// #11: addr-of twin of the #10 cgindex read
// fix. Dispatch esz + base load off the
// global's RESOLVED type, NOT an N_TARRAY/
// N_TPTR kind whitelist — a global str (tnode
// N_TNAME) / slice (N_TSLICE) matched NEITHER
// old arm, so esz stayed at the default 8 and
// the base fell to the complex-base fallback,
// yielding a wide-stride &s[i]. cstage's
// TK_AMP N_INDEX (cmd/w6c/cgen.c) is uniform:
// esz=bu->sub->size, base is_arr?LEAQ:MOVQ
// name(SB) (a str/slice's .ptr IS the symbol's
// first word). Align UP, mirroring cgindex.
let tn: *node = letvartnode(c, base.str);
if (tn != nil) {
globalname = base.str;
esz = elemsizeofc(c, tn);
if (tn.kind == nkind.N_TARRAY) {
isglobalarr = true;
globalname = base.str;
esz = elemsizeofc(c, tn);
};
if (tn.kind == nkind.N_TPTR) {
} else {
isglobalptr = true;
globalname = base.str;
esz = elemsizeofc(c, tn);
};
};
};
@@ -24797,19 +24806,34 @@ fn cgassign(c: *cgen, n: *node) void = {
if (bk == nkind.N_TPTR) { elemtn = btn.lhs; };
};
} else {
// #11: store/compound twin of the #10 cgindex
// read fix. A global str/slice element store hit
// the same kind whitelist — N_TNAME (str) /
// N_TSLICE matched NEITHER arm, so esz stayed 8
// and the store emitted a full-word MOVQ — an
// 8-byte OUT-OF-BOUNDS write past a 1-byte
// element — instead of MOVB. cstage
// (cmd/w6c/cgen.c N_INDEX store) dispatches esz
// off idx_eff->sub->size + the elem-kind flags
// off eff->sub uniformly, base is_arr?LEAQ:MOVQ
// name(SB). Align UP and resolve elemtn exactly
// like the local branch above (element node for
// ARRAY/SLICE/PTR; nil for str so tnodestoreop
// picks MOVB on the store arm, and the compound
// arm's str/slice hard-error still fires on a
// []str element).
let tn: *node = letvartnode(c, bn);
if (tn != nil) {
if (tn.kind == nkind.N_TARRAY) {
globalname = bn;
esz = elemsizeofc(c, tn);
let bk: nkind = tn.kind;
if (bk == nkind.N_TARRAY) {
isglobalarr = true;
globalname = bn;
esz = elemsizeofc(c, tn);
elemtn = tn.lhs;
};
if (tn.kind == nkind.N_TPTR) {
} else {
isglobalptr = true;
globalname = bn;
esz = elemsizeofc(c, tn);
elemtn = tn.lhs;
if (bk == nkind.N_TSLICE) { elemtn = tn.lhs; };
if (bk == nkind.N_TPTR) { elemtn = tn.lhs; };
};
};
};
@@ -25229,19 +25253,34 @@ fn cgassign(c: *cgen, n: *node) void = {
if (bk == nkind.N_TPTR) { elemtn = btn.lhs; };
};
} else {
// #11: store/compound twin of the #10 cgindex
// read fix. A global str/slice element store hit
// the same kind whitelist — N_TNAME (str) /
// N_TSLICE matched NEITHER arm, so esz stayed 8
// and the store emitted a full-word MOVQ — an
// 8-byte OUT-OF-BOUNDS write past a 1-byte
// element — instead of MOVB. cstage
// (cmd/w6c/cgen.c N_INDEX store) dispatches esz
// off idx_eff->sub->size + the elem-kind flags
// off eff->sub uniformly, base is_arr?LEAQ:MOVQ
// name(SB). Align UP and resolve elemtn exactly
// like the local branch above (element node for
// ARRAY/SLICE/PTR; nil for str so tnodestoreop
// picks MOVB on the store arm, and the compound
// arm's str/slice hard-error still fires on a
// []str element).
let tn: *node = letvartnode(c, bn);
if (tn != nil) {
if (tn.kind == nkind.N_TARRAY) {
globalname = bn;
esz = elemsizeofc(c, tn);
let bk: nkind = tn.kind;
if (bk == nkind.N_TARRAY) {
isglobalarr = true;
globalname = bn;
esz = elemsizeofc(c, tn);
elemtn = tn.lhs;
};
if (tn.kind == nkind.N_TPTR) {
} else {
isglobalptr = true;
globalname = bn;
esz = elemsizeofc(c, tn);
elemtn = tn.lhs;
if (bk == nkind.N_TSLICE) { elemtn = tn.lhs; };
if (bk == nkind.N_TPTR) { elemtn = tn.lhs; };
};
};
};