cgen: convert the struct-store loop arms to a tinfo-native fill (#31 c2)
The W1/W2/W5/W4b store / structlit-fill arms still resolved struct field LAYOUT by bare-leaf name (structlookupchain / structlookup / letvarstructinfo) inside the per-field store loop -- the write-side twin of commit-1's read fix. Under a cross-module same-leaf collision (two modules each exporting a `pair`, 16B vs 24B) the field is stored at the wrong offset/width; an interior 2-byte field store silently corrupted neighbours. cstage walks the resolved Type* (never a name) and is correct; this aligns wwstage UP (ww-only change). Convert each store loop to a tinfo-native fill. Two new helpers in cgenutil.ww: sretretsizetn (a verbatim port of cstage cg_sret_retsize -- 4 kind-branches, no TY_BANG, dropping the #129 module-juggling that was pure name-keying compensation, redundant once the lookup walks resolved tinfo) and cgstructlitfilltn (the tinfo-native twin of cgstructlitfill, recursing on tichase(tf.type_).fields with NO name lookup at any nesting level). Supporting twins storeopsz / tfieldlookup / cgdotfieldhardstoptn. Because the fill never bottoms out on a name, the W4b "decisive wall" (a structlit that needed a (sname,smod)-keyed structinfo the stamped leaf-only tinfo couldn't supply) dissolves by construction, with no module-aware tinfo->structinfo bridge. This CLOSES the nine in-loop nested sub-arms (N_CALL / N_STRUCTLIT / N_IDENT field receive) by construction -- the tf walk has no name lookup. The non-reddenable global arms (W5/W4b) are converted-for-construction: a qualified global decl resolves its bare leaf correctly today, so byte-id (cs.s==ww.s) is their net, not a value pin. `!T` is kind-preserving in both stages (resolve_type / tinfofornode), so a `!struct` sret sizes as the inner struct, never TAGGED -- the #129 drop changes no size. Completes the 2-commit #31 arc (RULING R2 / Opt-2, .ai/ken-31-spec.md): the #224 name-keyed receiver-layout cluster is now closed by construction. The residual nested-field surface (the shared cgstructlitfill kept for its non-#31 callers + the cgenstmt/cgenutil let-receive/fill family) is tracked as #32. Pins: test/wcc/797 value-asserts ptr-WRITE (W1) and val-WRITE (W2), each reddening under independent per-arm revert; nestfill_box drives cgstructlitfilltn's nested recursion (bootstrap-uncovered -- the corpus has no struct-literal field store -- so the value pin is its only net).
This commit is contained in:
@@ -41,12 +41,27 @@
|
||||
* letcopy_t16 | pair{u64,u16} 16B | pair 24B | C1 | 107 (value coincides; byte-id is the net)
|
||||
* addrptr_pq | pq{u64,u64} 16B | pq 24B | A1 | 10
|
||||
* addrval_pq | pq{u64,u64} 16B | pq 24B | A2 | 10
|
||||
* ptrwrite_t16 | pair{u64,u16} 16B | pair 24B | W1 | 109 (commit-2 STORE)
|
||||
* valwrite_t16 | pair{u64,u16} 16B | pair 24B | W2 | 109 (commit-2 STORE)
|
||||
* nestfill_box | box{tag,ir:nst} 32B | box 24B | W2 | 66 (commit-2 cgstructlitfilltn nested recursion)
|
||||
*
|
||||
* Commit-2 (RULING R2 / Opt-2) converts the W1/W2/W5/W4b store-loop arms
|
||||
* off the name-keyed receiver walk to a tinfo-native fill (cgstructlitfilltn
|
||||
* + sretretsizetn), closing the in-loop #32 nested struct-receive/structlit/
|
||||
* ident sub-arms by construction. The two scalar WRITE rows STORE through the
|
||||
* mis-resolved offset: at commit-1 (R1 read already fixed) wwstage stored at
|
||||
* the FOREIGN offset 16 while reading off 8 -> 100+7=107 WRONG vs cstage 109.
|
||||
* They reach only the scalar storeopsz arm, NOT cgstructlitfilltn; the
|
||||
* nestfill_box row is the cgstructlitfilltn fill + nested-recursion value pin
|
||||
* (a struct-literal stored into a struct-typed field, whose own field is a
|
||||
* nested struct literal) — the riskiest new helper, which no bootstrap
|
||||
* construct exercises.
|
||||
*
|
||||
* Each row reddens under an INDEPENDENT revert of its arm (verified
|
||||
* impl-side: R1 ww=101, C1 cs!=ww OOB, A1/A2 ww=8). cstage `ww build
|
||||
* --sep` + run pins runtime; the wwstage binary is run too (the bug WAS a
|
||||
* wrong wwstage runtime value / OOB); raw cs.s vs ww.s over the
|
||||
* driver-produced per-package asm pins rule-10 byte-id.
|
||||
* impl-side: R1 ww=101, C1 cs!=ww OOB, A1/A2 ww=8, W1/W2 ww=107 cs!=ww).
|
||||
* cstage `ww build --sep` + run pins runtime; the wwstage binary is run too
|
||||
* (the bug WAS a wrong wwstage runtime value / OOB); raw cs.s vs ww.s over
|
||||
* the driver-produced per-package asm pins rule-10 byte-id.
|
||||
*
|
||||
* NOTE the addr rows use an all-u64 `pq` (offset-only collision): the
|
||||
* canonical {u64,u16} field would, after `&p.lo`, force a `*q = v:u16`
|
||||
@@ -188,11 +203,101 @@ static const struct file addrval_files[] = {
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
/* ---- ptrwrite_t16 (W1, commit-2): inferred-let *struct field STORE.
|
||||
* `p.lo = 9: u16` resolved the field OFFSET via the name-keyed lookup ->
|
||||
* the FOREIGN 24B pair (lo @ off 16, u64) -> the store landed at off 16
|
||||
* (a u64 MOVQ into g's DATA neighbour) while the read (R1, fixed in
|
||||
* commit-1) reads off 8 -> p.lo stays 7 -> 100+7=107 WRONG (cstage 109,
|
||||
* cs!=ww). The {u64,u16} field stores MOVW in both stages (a direct
|
||||
* field store, NOT the addr-of `*q=v:u16` narrow-deref-store divergence
|
||||
* the A1/A2 pq rows dodge), so it isolates the #31 W1 offset/width fix. */
|
||||
static const struct file ptrwrite_files[] = {
|
||||
{ "m1.ww", m1_src },
|
||||
{ "m2.ww", m2_src },
|
||||
{ "main.ww",
|
||||
"package main;\n"
|
||||
"import m1;\n"
|
||||
"import m2;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let dummy: m2.pair;\n"
|
||||
" dummy.hi = 0: u64;\n"
|
||||
" let p = m1.mkp();\n"
|
||||
" p.lo = 9: u16;\n"
|
||||
" return (p.hi + (p.lo: u64)): i32;\n"
|
||||
"};\n" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
/* ---- valwrite_t16 (W2, commit-2): inferred-let value-struct field STORE.
|
||||
* `s.lo = 9: u16` resolved the OFFSET via the FOREIGN 24B pair -> stored
|
||||
* 8 bytes at off 16 (past s's 16B slot) while s.lo (off 8) stays 7 ->
|
||||
* 100+7=107 WRONG (cstage 109). The write-twin #21 missed. */
|
||||
static const struct file valwrite_files[] = {
|
||||
{ "m1.ww", m1_src },
|
||||
{ "m2.ww", m2_src },
|
||||
{ "main.ww",
|
||||
"package main;\n"
|
||||
"import m1;\n"
|
||||
"import m2;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let dummy: m2.pair;\n"
|
||||
" dummy.hi = 0: u64;\n"
|
||||
" let s = m1.mk();\n"
|
||||
" s.lo = 9: u16;\n"
|
||||
" return (s.hi + (s.lo: u64)): i32;\n"
|
||||
"};\n" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
/* ---- nestfill_box (W2 -> cgstructlitfilltn, commit-2): the nested-
|
||||
* recursion VALUE pin for the new fill helper. `s.ir = nst{...}` stores a
|
||||
* struct-LITERAL into a struct-typed field of a value-struct local,
|
||||
* driving cgstructlitfilltn (the W2 N_STRUCTLIT sub-arm, mode 0); the
|
||||
* literal's `dp = deep{...}` field is itself a struct literal, so
|
||||
* cgstructlitfilltn RECURSES — cgstructlitfilltn(tichase(tf.type_)) at
|
||||
* the inner offset (box.ir.dp @ +16). This is the riskiest new code and
|
||||
* is exercised by NO bootstrap construct (the corpus has zero `x.f =
|
||||
* Y{...}` struct-lit field stores) and by NO other 797 row (they all
|
||||
* store scalars/idents/calls, hitting only the storeopsz scalar arm).
|
||||
* Want = i0 + dp.d0 + dp.d1 = 11 + 22 + 33 = 66; a broken recursion
|
||||
* drops dp -> 11, a mis-offset -> a wrong sum.
|
||||
*
|
||||
* COLLISION-FREE by necessity (NOT an oversight): a cross-module
|
||||
* same-leaf box collision CANNOT drive this pin because TWO pre-existing
|
||||
* wwstage CHECKER gaps (orthogonal to #31, which is cgen-only) block the
|
||||
* wwstage readback under collision — (a) the checker mis-resolves a
|
||||
* nested value-struct field read to the FOREIGN field type (`u64 -> nst
|
||||
* not assignable`); (b) a chained `s.ir.dp.d0` read trips `asserttyped:
|
||||
* dot`. So the value is read back via single-dot-through-param helpers
|
||||
* (sumnst/sumdeep), the only nested-read shape wwstage accepts. The pin
|
||||
* still catches every cgstructlitfilltn fill/recursion miscompile (cs ==
|
||||
* ww runtime + cs.s == ww.s byte-id); the collision surface for the
|
||||
* SCALAR store stays covered by ptrwrite/valwrite above. */
|
||||
static const struct file nestfill_files[] = {
|
||||
{ "main.ww",
|
||||
"package main;\n"
|
||||
"type deep = struct { d0: u64, d1: u64 };\n"
|
||||
"type nst = struct { i0: u64, dp: deep };\n"
|
||||
"type box = struct { tag: u64, ir: nst };\n"
|
||||
"fn mkbox() box = { return box { tag = 0: u64, ir = nst { i0 = 0: u64, dp = deep { d0 = 0: u64, d1 = 0: u64 } } }; };\n"
|
||||
"fn sumdeep(d: deep) u64 = { return d.d0 + d.d1; };\n"
|
||||
"fn sumnst(n: nst) u64 = { return n.i0 + sumdeep(n.dp); };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let s = mkbox();\n"
|
||||
" s.ir = nst { i0 = 11: u64, dp = deep { d0 = 22: u64, d1 = 33: u64 } };\n"
|
||||
" return sumnst(s.ir): i32;\n"
|
||||
"};\n" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const struct scenario scenarios[] = {
|
||||
{ "ptrread_t16", ptrread_files, 107 },
|
||||
{ "letcopy_t16", letcopy_files, 107 },
|
||||
{ "addrptr_pq", addrptr_files, 10 },
|
||||
{ "addrval_pq", addrval_files, 10 },
|
||||
{ "ptrread_t16", ptrread_files, 107 },
|
||||
{ "letcopy_t16", letcopy_files, 107 },
|
||||
{ "addrptr_pq", addrptr_files, 10 },
|
||||
{ "addrval_pq", addrval_files, 10 },
|
||||
{ "ptrwrite_t16", ptrwrite_files, 109 },
|
||||
{ "valwrite_t16", valwrite_files, 109 },
|
||||
{ "nestfill_box", nestfill_files, 66 },
|
||||
};
|
||||
|
||||
static int
|
||||
|
||||
Reference in New Issue
Block a user