wcc_ww/cgen: #101 narrow-alias fill-stride via aliasprimsize SSoT

A struct-literal array fill sized a narrow-alias element off a bare
primsize(name): `type my32 = u32` gave primsize("my32")=0, so the
element width defaulted to 8 and a [3]my32 strode MOVQ -24/-16/-8 —
field n collided with arr[2] (kw1_101 run exit 1). cstage chases
my32->u32->4 (MOVL stride-4) at the twin sites and is runtime-correct;
this is a ww-only align-up, cs untouched.

Fix: a new aliasprimsize(c, nm) SSoT helper — primsize(nm), else an
aliaslookup-chase N_TNAME loop then primsize — and route the SIZE-use
primsize() family through it. The 7 c-bearing bare-no-chase size-use
sites are routed: cgen:993 (letemitsize), cgenstmt:1896 (cgarrlitfillbp),
cgenutil:1579 (elemsizeofc fallback)/1657+1665 (nodeprimwidth)/4791
(cgstructlitfill = the kw1_101 site), cgenexpr:6902 (cgcall vararg esz).
This is the rule-13 close-by-construction shape (one accessor for
"resolved primitive size"), not a per-site patch.

kw1_101 is the SOLE asm mover (byte-id NO->YES, run 1->0, MOVL
stride-4); every other routed site is latent/byte-neutral. Bootstrap:
all 5 combined units stay w6c==w6c_ww byte-identical. sizelint 0,
peellint 0, test-unit 296/296.

Scope fence (rob route-7-decline-6 ruling): three DESIGNED-exemption
sites carry inline primsize-ok annotations — elemsizeof :1475/:1499 and
paramfieldsize :3541 are structural (no-`c`, non-chasing) BY DESIGN;
their alias-chasing twin elemsizeofc is the routed :1579 leg. These are
the #109 peellint-whitelist seeds. Three further declines are already
correct chasing paths, not bare-no-chase bug shapes (typenodeprimresolved
:2026 / exprprimresolved :2063 are the chase machinery itself; cgassign
:7631 already chases via typenodeprimresolved, #11). The ~17 GUARD sites
(is-primitive dispatch) + the peellint finale are the committed #109
follow-on. Threading `c` into the structural sizers is dormant #110.

#101
This commit is contained in:
2026-06-06 10:04:37 +09:00
parent c9cfa52624
commit 45f5415209
7 changed files with 159 additions and 21 deletions

View File

@@ -17613,6 +17613,10 @@ fn elemsizeof(t: *node) i32 = {
// reads eff->sub->size (cmd/w6c/cgen.c N_INDEX).
if (streq(nm, "str")) { return primtypesize("u8"): i32; };
// Indexing a primitive name (rare): element size = the prim.
// primsize-ok (#101/#109): elemsizeof is the STRUCTURAL (non-
// chasing) sizer by design — its alias-resolving twin elemsizeofc
// owns the chase (routed through aliasprimsize at the :1579 leg).
// A bare primsize here is correct, not the #101 bug shape.
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
return 1;
@@ -17637,6 +17641,8 @@ fn elemsizeof(t: *node) i32 = {
let nm: str = elem.str;
// str element is 16B (ptr+len). primsize returns 0 for it.
if (streq(nm, "str")) { return primtypesize("str"): i32; };
// primsize-ok (#101/#109): structural sizer — the chase lives
// in elemsizeofc (:1579), not here. See the :1475 leg.
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
};
@@ -17717,7 +17723,7 @@ fn elemsizeofc(c: *cgen, t: *node) i32 = {
let elem: *node = idxelemtn(t);
if (elem == nil) { return direct; };
if (elem.kind == nkind.N_TNAME) {
let ps: i32 = primsize(elem.str);
let ps: i32 = aliasprimsize(c, elem.str);
if (ps > 0) { return ps; };
};
return slotsize(c, elem);
@@ -17795,7 +17801,7 @@ fn nodeprimwidth(c: *cgen, n: *node) i32 = {
if (lc != nil) {
let tn: *node = lc.tnode;
if (tn != nil) {
if (tn.kind == nkind.N_TNAME) { return primsize(tn.str); };
if (tn.kind == nkind.N_TNAME) { return aliasprimsize(c, tn.str); };
};
};
return 0;
@@ -17803,7 +17809,7 @@ fn nodeprimwidth(c: *cgen, n: *node) i32 = {
if (k == nkind.N_CAST) {
let tn: *node = n.rhs;
if (tn != nil) {
if (tn.kind == nkind.N_TNAME) { return primsize(tn.str); };
if (tn.kind == nkind.N_TNAME) { return aliasprimsize(c, tn.str); };
};
return 0;
};
@@ -18136,6 +18142,31 @@ fn primsize(name: str) i32 = {
return 0;
};
// aliasprimsize — resolved primitive byte width for a type NAME: the
// prim width if `nm` is itself a primitive, else chase the alias chain
// (aliaslookup) to its bottom and take that prim's width. Returns 0
// when the name doesn't reduce to a width-known primitive (struct /
// tagged / `!`/enum-bottom / unresolved). SSoT for the size-use
// primsize() family: a bare primsize(name) is alias-blind — a narrow
// alias (`type my32 = u32`) returns 0, defaulting the stride/width to
// 8 (the #101 struct-fill miscompile: [3]my32 strode 8 not 4, field n
// collided with arr[2]). cstage chases my32→u32→4 via type_chase_named
// at the twin sites; this is the ww align-up. The bare-primsize GUARD
// family (is-primitive dispatch) is the #109 follow-on, NOT routed
// here. #101.
fn aliasprimsize(c: *cgen, nm: str) i32 = {
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
let cur: *node = aliaslookup(c, nm);
for (cur != nil) {
if (cur.kind != nkind.N_TNAME) { return 0; };
let p: i32 = primsize(cur.str);
if (p > 0) { return p; };
cur = aliaslookup(c, cur.str);
};
return 0;
};
// typenodeprimresolved — walk N_TBANG / N_TENUM / N_TNAME alias
// chains to the underlying primitive, returning its byte size and
// signedness. Sets *sz_out = 0 when the type doesn't reduce to a
@@ -20929,7 +20960,7 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
let esz: i32 = 8;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
let ps: i32 = primsize(elemn.str);
let ps: i32 = aliasprimsize(c, elemn.str);
if (ps > 0) { esz = ps; };
};
};
@@ -28044,7 +28075,7 @@ fn cgcall(c: *cgen, n: *node) void = {
let esz: i32 = 8;
if (velem != nil) {
if (velem.kind == nkind.N_TNAME) {
let ps: i32 = primsize(velem.str);
let ps: i32 = aliasprimsize(c, velem.str);
if (ps > 0) { esz = ps; }
else { esz = slotsize(c, velem); };
} else {
@@ -34352,7 +34383,7 @@ fn cgarrlitfillbp(c: *cgen, arrtn: *node, rhs: *node, off: i32) void = {
esz = primtypesize("str"): i32;
isstrel = true;
} else {
let ps: i32 = primsize(elemn.str);
let ps: i32 = aliasprimsize(c, elemn.str);
if (ps > 0) { esz = ps; };
};
};
@@ -35997,6 +36028,11 @@ fn paramfieldsize(t: *node) i32 = {
if (k == nkind.N_TNAME) {
let nm: str = t.str;
if (streq(nm, "str")) { return primtypesize("str"): i32; };
// primsize-ok (#101/#109): paramfieldsize is a STRUCTURAL
// (no-`c`, no-chase) sizer by design — it takes a *node, not a
// *cgen, so it cannot run aliasprimsize's aliaslookup chase
// (threading c is the dormant #110). A bare primsize is correct
// here, not the #101 narrow-alias bug shape.
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
};
@@ -38157,7 +38193,7 @@ fn letemitsize(c: *cgen, d: *node) i32 = {
let esz: i32 = 8;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
let ps: i32 = primsize(elemn.str);
let ps: i32 = aliasprimsize(c, elemn.str);
if (ps > 0) { esz = ps; };
};
};

View File

@@ -990,7 +990,7 @@ fn letemitsize(c: *cgen, d: *node) i32 = {
let esz: i32 = 8;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
let ps: i32 = primsize(elemn.str);
let ps: i32 = aliasprimsize(c, elemn.str);
if (ps > 0) { esz = ps; };
};
};

View File

@@ -6899,7 +6899,7 @@ fn cgcall(c: *cgen, n: *node) void = {
let esz: i32 = 8;
if (velem != nil) {
if (velem.kind == nkind.N_TNAME) {
let ps: i32 = primsize(velem.str);
let ps: i32 = aliasprimsize(c, velem.str);
if (ps > 0) { esz = ps; }
else { esz = slotsize(c, velem); };
} else {

View File

@@ -1893,7 +1893,7 @@ fn cgarrlitfillbp(c: *cgen, arrtn: *node, rhs: *node, off: i32) void = {
esz = primtypesize("str"): i32;
isstrel = true;
} else {
let ps: i32 = primsize(elemn.str);
let ps: i32 = aliasprimsize(c, elemn.str);
if (ps > 0) { esz = ps; };
};
};
@@ -3538,6 +3538,11 @@ fn paramfieldsize(t: *node) i32 = {
if (k == nkind.N_TNAME) {
let nm: str = t.str;
if (streq(nm, "str")) { return primtypesize("str"): i32; };
// primsize-ok (#101/#109): paramfieldsize is a STRUCTURAL
// (no-`c`, no-chase) sizer by design — it takes a *node, not a
// *cgen, so it cannot run aliasprimsize's aliaslookup chase
// (threading c is the dormant #110). A bare primsize is correct
// here, not the #101 narrow-alias bug shape.
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
};

View File

@@ -1472,6 +1472,10 @@ fn elemsizeof(t: *node) i32 = {
// reads eff->sub->size (cmd/w6c/cgen.c N_INDEX).
if (streq(nm, "str")) { return primtypesize("u8"): i32; };
// Indexing a primitive name (rare): element size = the prim.
// primsize-ok (#101/#109): elemsizeof is the STRUCTURAL (non-
// chasing) sizer by design — its alias-resolving twin elemsizeofc
// owns the chase (routed through aliasprimsize at the :1579 leg).
// A bare primsize here is correct, not the #101 bug shape.
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
return 1;
@@ -1496,6 +1500,8 @@ fn elemsizeof(t: *node) i32 = {
let nm: str = elem.str;
// str element is 16B (ptr+len). primsize returns 0 for it.
if (streq(nm, "str")) { return primtypesize("str"): i32; };
// primsize-ok (#101/#109): structural sizer — the chase lives
// in elemsizeofc (:1579), not here. See the :1475 leg.
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
};
@@ -1576,7 +1582,7 @@ fn elemsizeofc(c: *cgen, t: *node) i32 = {
let elem: *node = idxelemtn(t);
if (elem == nil) { return direct; };
if (elem.kind == nkind.N_TNAME) {
let ps: i32 = primsize(elem.str);
let ps: i32 = aliasprimsize(c, elem.str);
if (ps > 0) { return ps; };
};
return slotsize(c, elem);
@@ -1654,7 +1660,7 @@ fn nodeprimwidth(c: *cgen, n: *node) i32 = {
if (lc != nil) {
let tn: *node = lc.tnode;
if (tn != nil) {
if (tn.kind == nkind.N_TNAME) { return primsize(tn.str); };
if (tn.kind == nkind.N_TNAME) { return aliasprimsize(c, tn.str); };
};
};
return 0;
@@ -1662,7 +1668,7 @@ fn nodeprimwidth(c: *cgen, n: *node) i32 = {
if (k == nkind.N_CAST) {
let tn: *node = n.rhs;
if (tn != nil) {
if (tn.kind == nkind.N_TNAME) { return primsize(tn.str); };
if (tn.kind == nkind.N_TNAME) { return aliasprimsize(c, tn.str); };
};
return 0;
};
@@ -1995,6 +2001,31 @@ fn primsize(name: str) i32 = {
return 0;
};
// aliasprimsize — resolved primitive byte width for a type NAME: the
// prim width if `nm` is itself a primitive, else chase the alias chain
// (aliaslookup) to its bottom and take that prim's width. Returns 0
// when the name doesn't reduce to a width-known primitive (struct /
// tagged / `!`/enum-bottom / unresolved). SSoT for the size-use
// primsize() family: a bare primsize(name) is alias-blind — a narrow
// alias (`type my32 = u32`) returns 0, defaulting the stride/width to
// 8 (the #101 struct-fill miscompile: [3]my32 strode 8 not 4, field n
// collided with arr[2]). cstage chases my32→u32→4 via type_chase_named
// at the twin sites; this is the ww align-up. The bare-primsize GUARD
// family (is-primitive dispatch) is the #109 follow-on, NOT routed
// here. #101.
fn aliasprimsize(c: *cgen, nm: str) i32 = {
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
let cur: *node = aliaslookup(c, nm);
for (cur != nil) {
if (cur.kind != nkind.N_TNAME) { return 0; };
let p: i32 = primsize(cur.str);
if (p > 0) { return p; };
cur = aliaslookup(c, cur.str);
};
return 0;
};
// typenodeprimresolved — walk N_TBANG / N_TENUM / N_TNAME alias
// chains to the underlying primitive, returning its byte size and
// signedness. Sets *sz_out = 0 when the type doesn't reduce to a
@@ -4788,7 +4819,7 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
let esz: i32 = 8;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
let ps: i32 = primsize(elemn.str);
let ps: i32 = aliasprimsize(c, elemn.str);
if (ps > 0) { esz = ps; };
};
};

View File

@@ -17613,6 +17613,10 @@ fn elemsizeof(t: *node) i32 = {
// reads eff->sub->size (cmd/w6c/cgen.c N_INDEX).
if (streq(nm, "str")) { return primtypesize("u8"): i32; };
// Indexing a primitive name (rare): element size = the prim.
// primsize-ok (#101/#109): elemsizeof is the STRUCTURAL (non-
// chasing) sizer by design — its alias-resolving twin elemsizeofc
// owns the chase (routed through aliasprimsize at the :1579 leg).
// A bare primsize here is correct, not the #101 bug shape.
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
return 1;
@@ -17637,6 +17641,8 @@ fn elemsizeof(t: *node) i32 = {
let nm: str = elem.str;
// str element is 16B (ptr+len). primsize returns 0 for it.
if (streq(nm, "str")) { return primtypesize("str"): i32; };
// primsize-ok (#101/#109): structural sizer — the chase lives
// in elemsizeofc (:1579), not here. See the :1475 leg.
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
};
@@ -17717,7 +17723,7 @@ fn elemsizeofc(c: *cgen, t: *node) i32 = {
let elem: *node = idxelemtn(t);
if (elem == nil) { return direct; };
if (elem.kind == nkind.N_TNAME) {
let ps: i32 = primsize(elem.str);
let ps: i32 = aliasprimsize(c, elem.str);
if (ps > 0) { return ps; };
};
return slotsize(c, elem);
@@ -17795,7 +17801,7 @@ fn nodeprimwidth(c: *cgen, n: *node) i32 = {
if (lc != nil) {
let tn: *node = lc.tnode;
if (tn != nil) {
if (tn.kind == nkind.N_TNAME) { return primsize(tn.str); };
if (tn.kind == nkind.N_TNAME) { return aliasprimsize(c, tn.str); };
};
};
return 0;
@@ -17803,7 +17809,7 @@ fn nodeprimwidth(c: *cgen, n: *node) i32 = {
if (k == nkind.N_CAST) {
let tn: *node = n.rhs;
if (tn != nil) {
if (tn.kind == nkind.N_TNAME) { return primsize(tn.str); };
if (tn.kind == nkind.N_TNAME) { return aliasprimsize(c, tn.str); };
};
return 0;
};
@@ -18136,6 +18142,31 @@ fn primsize(name: str) i32 = {
return 0;
};
// aliasprimsize — resolved primitive byte width for a type NAME: the
// prim width if `nm` is itself a primitive, else chase the alias chain
// (aliaslookup) to its bottom and take that prim's width. Returns 0
// when the name doesn't reduce to a width-known primitive (struct /
// tagged / `!`/enum-bottom / unresolved). SSoT for the size-use
// primsize() family: a bare primsize(name) is alias-blind — a narrow
// alias (`type my32 = u32`) returns 0, defaulting the stride/width to
// 8 (the #101 struct-fill miscompile: [3]my32 strode 8 not 4, field n
// collided with arr[2]). cstage chases my32→u32→4 via type_chase_named
// at the twin sites; this is the ww align-up. The bare-primsize GUARD
// family (is-primitive dispatch) is the #109 follow-on, NOT routed
// here. #101.
fn aliasprimsize(c: *cgen, nm: str) i32 = {
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
let cur: *node = aliaslookup(c, nm);
for (cur != nil) {
if (cur.kind != nkind.N_TNAME) { return 0; };
let p: i32 = primsize(cur.str);
if (p > 0) { return p; };
cur = aliaslookup(c, cur.str);
};
return 0;
};
// typenodeprimresolved — walk N_TBANG / N_TENUM / N_TNAME alias
// chains to the underlying primitive, returning its byte size and
// signedness. Sets *sz_out = 0 when the type doesn't reduce to a
@@ -20929,7 +20960,7 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
let esz: i32 = 8;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
let ps: i32 = primsize(elemn.str);
let ps: i32 = aliasprimsize(c, elemn.str);
if (ps > 0) { esz = ps; };
};
};
@@ -28044,7 +28075,7 @@ fn cgcall(c: *cgen, n: *node) void = {
let esz: i32 = 8;
if (velem != nil) {
if (velem.kind == nkind.N_TNAME) {
let ps: i32 = primsize(velem.str);
let ps: i32 = aliasprimsize(c, velem.str);
if (ps > 0) { esz = ps; }
else { esz = slotsize(c, velem); };
} else {
@@ -34352,7 +34383,7 @@ fn cgarrlitfillbp(c: *cgen, arrtn: *node, rhs: *node, off: i32) void = {
esz = primtypesize("str"): i32;
isstrel = true;
} else {
let ps: i32 = primsize(elemn.str);
let ps: i32 = aliasprimsize(c, elemn.str);
if (ps > 0) { esz = ps; };
};
};
@@ -35997,6 +36028,11 @@ fn paramfieldsize(t: *node) i32 = {
if (k == nkind.N_TNAME) {
let nm: str = t.str;
if (streq(nm, "str")) { return primtypesize("str"): i32; };
// primsize-ok (#101/#109): paramfieldsize is a STRUCTURAL
// (no-`c`, no-chase) sizer by design — it takes a *node, not a
// *cgen, so it cannot run aliasprimsize's aliaslookup chase
// (threading c is the dormant #110). A bare primsize is correct
// here, not the #101 narrow-alias bug shape.
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
};
@@ -38157,7 +38193,7 @@ fn letemitsize(c: *cgen, d: *node) i32 = {
let esz: i32 = 8;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
let ps: i32 = primsize(elemn.str);
let ps: i32 = aliasprimsize(c, elemn.str);
if (ps > 0) { esz = ps; };
};
};

View File

@@ -402,6 +402,36 @@ static const struct row rows[] = {
" if (xs[0]: i64 + xs[1]: i64 + xs[2]: i64 != 18) { return 1; };\n"
" return 0;\n"
"};\n", 0, 0, K_RUN, NULL, NULL },
/* #101: NARROW-alias struct-FILL stride (the cgstructlitfill
* :4791 leg, twin of esub_2lvl's INDEX leg). Pre-fold ww sized
* the [3]my32 element off primsize("my32")=0 → 8B-default stride,
* so the fill stored at -24/-16/-8 and field n collided with
* arr[2] (run exit 1); cs chased my32→u32→4 (MOVL stride-4). The
* aliasprimsize route aligns ww UP — kw1_101 was the SOLE corpus
* mover (cs0/ww1 byteid-NO → byteid-YES + run 0). */
{ "kw1_101",
"package main;\n"
"type my32 = u32;\n"
"type S = struct { arr: [3]my32, n: int };\n"
"export fn main() i32 = {\n"
" let s = S { arr = [10u32, 20u32, 30u32], n = 4 };\n"
" if (s.arr[0] + s.arr[1] + s.arr[2] != 60u32) { return 1; };\n"
" if (s.n != 4) { return 1; };\n"
" return 0;\n"
"};\n", 0, 0, K_RUN, NULL, NULL },
/* #101 control: a DIFFERENT narrow width (u16, stride-2 MOVW)
* proves the routed esz is width-general, not a 4-only patch.
* fld n must survive the fill untouched. */
{ "kw1_ctl16",
"package main;\n"
"type my16 = u16;\n"
"type S2 = struct { arr: [4]my16, n: int };\n"
"export fn main() i32 = {\n"
" let s = S2 { arr = [1u16, 2u16, 3u16, 4u16], n = 9 };\n"
" if (s.arr[0] + s.arr[1] + s.arr[2] + s.arr[3] != 10u16) { return 1; };\n"
" if (s.n != 9) { return 1; };\n"
" return 0;\n"
"};\n", 0, 0, K_RUN, NULL, NULL },
/* ---- c4: cast/is/try family */
{ "strcast_2lvl",
"package main;\n"