w6c_ww/cgen: size [N]enum element from tinfo not slotsize (fix #8)

wwstage sized a named-enum array element (`[N]tk`, tk = enum i32) as a
raw 8-byte slot instead of its i32 backing (4), via two sibling code
paths that both derived the element width structurally and missed the
enum's underlying size:

  - elemsizeofc (cgenutil.ww) was the odd-one-out among the elem*c
    helpers: elemissignedc/elemisfloatc already read the checker-stamped
    tinfo (t.type_.sub), but elemsizeofc went elemsizeof->primsize->
    slotsize, and primsize("tk")=0 fell through to 8. This drove the
    cgindex READ: `a[i]` strode by 8 (MOVQ) where cstage strode by 4
    (MOVSXD), reading the wrong/out-of-bounds element for i>=1.

  - the array-literal init STORE (cgenstmt.ww) computed its own esz the
    same way (primsize=0 -> stayed at the 8 sentinel, enum is not an
    aggregate), so a local `[N]enum` literal stored at stride 8 into a
    stride-4 frame slot, overrunning it and smashing the saved BP /
    return addr -> wwstage-built binary SEGFAULTED.

Both align UP to cstage, which reads the stamped element size uniformly
(N_INDEX idx_eff(bt)->sub->size; N_LET array-init lu->sub->size,
cgen.c:6387). The read fix brings all four elem*c helpers onto the same
tinfo SSoT; the store fix takes the stamped element size for a narrow
scalar. Closing both close-by-construction at the size source.

No in-tree [N]enum / aliased-narrow element existed before kwtab, so
this was byte-id-gate-blind until now. test/wcc/682_arr_enum_elem.c
pins it table-driven: global+local reads, local init-store, signed
sign-extend, and a frame-smash row, each run through both stages with
exit-code and cstage==wwstage asm-byte-id checks.
This commit is contained in:
2026-06-03 14:48:12 +09:00
parent 6b67655eca
commit d39691a3d7
6 changed files with 413 additions and 0 deletions

View File

@@ -16757,6 +16757,26 @@ fn elemsizeofc(c: *cgen, t: *node) i32 = {
};
let direct: i32 = elemsizeof(t);
if (direct != 8) { return direct; };
// #8: direct==8 is elemsizeof's "unresolved alias/aggregate" sentinel.
// Read the element width off the checker-stamped tinfo, mirroring the
// sibling elem*c helpers (elemissignedc :915, elemisfloatc :939, which
// already read t.type_.sub) and cstage idx_eff(bt)->sub->size
// (cmd/w6c/cgen.c N_INDEX). elemsizeofc was the odd-one-out among the
// elem*c family — it derived size purely structurally, so a named-narrow
// element (`[N]tkind`, tkind = enum i32) slipped through to a raw 8B slot
// instead of its i32 backing (4), wrong-striding both the cgindex READ
// and the local-array-init STORE (frame-smash). Peel TY_NAMED on the
// indexable and on its element, matching the #270-2 nested-array block
// above. Structural slotsize fallback stays for the t.type_==nil case.
let ti: *tinfo = t.type_: *tinfo;
if (ti != nil) {
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti != nil) {
let esub: *tinfo = ti.sub;
for (esub != nil && esub.kind == tykind.TY_NAMED) { esub = esub.under; };
if (esub != nil) { return esub.size: i32; };
};
};
let k: nkind = t.kind;
let elem: *node = nil;
if (k == nkind.N_TPTR) { elem = t.lhs; };
@@ -29491,6 +29511,20 @@ fn cglet(c: *cgen, n: *node) void = {
|| esubti.kind == tykind.TY_ARRAY
|| esubti.kind == tykind.TY_TUPLE);
if (isagg) { esz = esubti.size: i32; };
// #8: a named-narrow element (`[N]tk`, tk = enum i32) is
// neither a builtin prim (primsize=0 above, so esz stayed
// the 8 sentinel) nor an aggregate, so the scalar store kept
// an 8B stride/MOVQ and overran the stride-4 frame slot —
// smashing the saved BP / return addr (SEGFAULT). Mirror
// cstage's uniform lu->sub->size (cgen.c:6387) and the
// elemsizeofc read-side fix: take the stamped element tinfo's
// size for a narrow scalar (1/2/4). Wider non-prim elements
// (tagged/slice/str two-half) stay the documented follow-up
// at :1742-1744 — the single-MOVx store below is scalar-only.
if (!isstrel && !isagg && esz == 8 && esubti != nil) {
let es: i32 = esubti.size: i32;
if (es == 1 || es == 2 || es == 4) { esz = es; };
};
let mop: str = tnodestoreop(c, elemn, esz);
// float element → store FROM X0 (MOVSS/MOVSD): cgexpr
// leaves a float in X0 and for f32 the #104 CVTSD2SS

View File

@@ -1773,6 +1773,20 @@ fn cglet(c: *cgen, n: *node) void = {
|| esubti.kind == tykind.TY_ARRAY
|| esubti.kind == tykind.TY_TUPLE);
if (isagg) { esz = esubti.size: i32; };
// #8: a named-narrow element (`[N]tk`, tk = enum i32) is
// neither a builtin prim (primsize=0 above, so esz stayed
// the 8 sentinel) nor an aggregate, so the scalar store kept
// an 8B stride/MOVQ and overran the stride-4 frame slot —
// smashing the saved BP / return addr (SEGFAULT). Mirror
// cstage's uniform lu->sub->size (cgen.c:6387) and the
// elemsizeofc read-side fix: take the stamped element tinfo's
// size for a narrow scalar (1/2/4). Wider non-prim elements
// (tagged/slice/str two-half) stay the documented follow-up
// at :1742-1744 — the single-MOVx store below is scalar-only.
if (!isstrel && !isagg && esz == 8 && esubti != nil) {
let es: i32 = esubti.size: i32;
if (es == 1 || es == 2 || es == 4) { esz = es; };
};
let mop: str = tnodestoreop(c, elemn, esz);
// float element → store FROM X0 (MOVSS/MOVSD): cgexpr
// leaves a float in X0 and for f32 the #104 CVTSD2SS

View File

@@ -1166,6 +1166,26 @@ fn elemsizeofc(c: *cgen, t: *node) i32 = {
};
let direct: i32 = elemsizeof(t);
if (direct != 8) { return direct; };
// #8: direct==8 is elemsizeof's "unresolved alias/aggregate" sentinel.
// Read the element width off the checker-stamped tinfo, mirroring the
// sibling elem*c helpers (elemissignedc :915, elemisfloatc :939, which
// already read t.type_.sub) and cstage idx_eff(bt)->sub->size
// (cmd/w6c/cgen.c N_INDEX). elemsizeofc was the odd-one-out among the
// elem*c family — it derived size purely structurally, so a named-narrow
// element (`[N]tkind`, tkind = enum i32) slipped through to a raw 8B slot
// instead of its i32 backing (4), wrong-striding both the cgindex READ
// and the local-array-init STORE (frame-smash). Peel TY_NAMED on the
// indexable and on its element, matching the #270-2 nested-array block
// above. Structural slotsize fallback stays for the t.type_==nil case.
let ti: *tinfo = t.type_: *tinfo;
if (ti != nil) {
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti != nil) {
let esub: *tinfo = ti.sub;
for (esub != nil && esub.kind == tykind.TY_NAMED) { esub = esub.under; };
if (esub != nil) { return esub.size: i32; };
};
};
let k: nkind = t.kind;
let elem: *node = nil;
if (k == nkind.N_TPTR) { elem = t.lhs; };

View File

@@ -16757,6 +16757,26 @@ fn elemsizeofc(c: *cgen, t: *node) i32 = {
};
let direct: i32 = elemsizeof(t);
if (direct != 8) { return direct; };
// #8: direct==8 is elemsizeof's "unresolved alias/aggregate" sentinel.
// Read the element width off the checker-stamped tinfo, mirroring the
// sibling elem*c helpers (elemissignedc :915, elemisfloatc :939, which
// already read t.type_.sub) and cstage idx_eff(bt)->sub->size
// (cmd/w6c/cgen.c N_INDEX). elemsizeofc was the odd-one-out among the
// elem*c family — it derived size purely structurally, so a named-narrow
// element (`[N]tkind`, tkind = enum i32) slipped through to a raw 8B slot
// instead of its i32 backing (4), wrong-striding both the cgindex READ
// and the local-array-init STORE (frame-smash). Peel TY_NAMED on the
// indexable and on its element, matching the #270-2 nested-array block
// above. Structural slotsize fallback stays for the t.type_==nil case.
let ti: *tinfo = t.type_: *tinfo;
if (ti != nil) {
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti != nil) {
let esub: *tinfo = ti.sub;
for (esub != nil && esub.kind == tykind.TY_NAMED) { esub = esub.under; };
if (esub != nil) { return esub.size: i32; };
};
};
let k: nkind = t.kind;
let elem: *node = nil;
if (k == nkind.N_TPTR) { elem = t.lhs; };
@@ -29491,6 +29511,20 @@ fn cglet(c: *cgen, n: *node) void = {
|| esubti.kind == tykind.TY_ARRAY
|| esubti.kind == tykind.TY_TUPLE);
if (isagg) { esz = esubti.size: i32; };
// #8: a named-narrow element (`[N]tk`, tk = enum i32) is
// neither a builtin prim (primsize=0 above, so esz stayed
// the 8 sentinel) nor an aggregate, so the scalar store kept
// an 8B stride/MOVQ and overran the stride-4 frame slot —
// smashing the saved BP / return addr (SEGFAULT). Mirror
// cstage's uniform lu->sub->size (cgen.c:6387) and the
// elemsizeofc read-side fix: take the stamped element tinfo's
// size for a narrow scalar (1/2/4). Wider non-prim elements
// (tagged/slice/str two-half) stay the documented follow-up
// at :1742-1744 — the single-MOVx store below is scalar-only.
if (!isstrel && !isagg && esz == 8 && esubti != nil) {
let es: i32 = esubti.size: i32;
if (es == 1 || es == 2 || es == 4) { esz = es; };
};
let mop: str = tnodestoreop(c, elemn, esz);
// float element → store FROM X0 (MOVSS/MOVSD): cgexpr
// leaves a float in X0 and for f32 the #104 CVTSD2SS