diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index c00ece01..13682e70 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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; }; }; }; diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index fccecad7..b25dba12 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -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; }; }; }; diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 06175ad1..a4534103 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -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 { diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index 8ac148b8..6b216bcd 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -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; }; }; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 98c66edd..c3c9181b 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -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; }; }; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 93a1134e..0ed4bc28 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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; }; }; }; diff --git a/test/wcc/944_alias_cgen_b6_run.c b/test/wcc/944_alias_cgen_b6_run.c index 50eeecf8..538fe088 100644 --- a/test/wcc/944_alias_cgen_b6_run.c +++ b/test/wcc/944_alias_cgen_b6_run.c @@ -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"