wcc+w6c_ww: aggregate-field stores via cgplaceaddr (C1.25)

Wire struct/array/tuple field STORE through the C1 assign-resolver
(task #23): structlit rhs materialises into a FRESH-per-use @placescr
slot (the @slicescr discipline — a cached slot is the #31 multi-live
trap) then word-copies to the resolved address; addressable rhs
(ident/global/dot/deref) sources via aggarg_srcaddr with the dest
spilled around the dispatch (#270-1b order). Kept loud: compound on
aggregate, sret call rhs (#234-tail), <=24B call rhs (task #24),
unaddressable literal rhs, ww-only anonymous-struct structinfo miss.

test/wcc/805: +6 rows (40B structlit incl ... autofill, ident+deref
source, nested literal, [3]u8 MOVW/MOVB and [3]u32 MOVL tails,
two same-size stores in one fn pinning fresh-per-use) +3 exact-text
reject rows. Tuple-field row blocked by the pre-existing tuple
param/let-init word-2 drops (tasks #32/#33, documented in-row).
p7b_capstore_only graduates byte-id and runs; p7_composed builds and
runs on cstage, wwstage stays behind the pre-existing #29 asserttyped
bail (verified identical at master with w6c_ww).
This commit is contained in:
2026-06-04 10:00:16 +09:00
parent 32063d0da0
commit cfc2985c61
6 changed files with 789 additions and 23 deletions

View File

@@ -14,10 +14,15 @@
* selfhost/cmd/wcc/cgenexpr.ww); each call-site keeps its own
* load/store emission, and every N_DOT lvalue the resolver can't
* address now dies LOUD ("unsupported assign target shape") instead of
* silently dropping (rule 7). Field kinds the resolver arm does not
* wire yet (float / tagged / aggregate / str-slice compound) hard-stop
* with their own diagnostics — the reject rows pin the exact text on
* BOTH stages. Non-DOT lvalue tail residue is task #22.
* silently dropping (rule 7). C1.25 (#23) wires the aggregate field
* STORE on top of the resolver: literal rhs materialises into a FRESH
* per-use @placescr slot then word-copies to the resolved address;
* addressable rhs (ident/global/dot/deref) takes its source address
* straight from the #265/#268 dispatch. Field kinds the resolver arm
* does not wire yet (float / tagged / aggregate-compound / str-slice
* compound / call-rhs into aggregate) hard-stop with their own
* diagnostics — the reject rows pin the exact text on BOTH stages.
* Non-DOT lvalue tail residue is task #22.
*
* row | shape | want
* --------------------+----------------------------------------+------
@@ -36,7 +41,17 @@
* neutral_ident_bases | x.f / a[i].f / p.f = and += (untouched | 51
* | enumerated arms — runtime-pins the |
* | resolver's asm-neutrality claim) |
* reject_aggregate | struct-typed field store | BUILD_FAIL
* agg_structlit_40b | C1.25: 40B capture literal store via | 52
* | fresh @placescr + word-copy, incl ... |
* agg_from_ident_deref| C1.25: aggregate from ident + *p rhs | 53
* agg_nested_lit | C1.25: nested struct literal (#18) | 54
* agg_array_field_odd | C1.25: [3]u8 field, MOVW/MOVB tails | 55
* agg_fresh_two_stores| C1.25: two same-size structlit stores | 56
* | in ONE fn — distinct @placescr slots |
* agg_array_field_movl| C1.25: [3]u32 field, MOVQ+MOVL tail | 57
* reject_agg_compound | compound on aggregate field | BUILD_FAIL
* reject_agg_call_sret| >24B call rhs (#234-tail) | BUILD_FAIL
* reject_agg_call_reg | ≤24B call rhs (task #24) | BUILD_FAIL
* reject_float | f64 field store | BUILD_FAIL
* reject_tagged | tagged-union field store | BUILD_FAIL
* reject_str_compound | (*ts)[i].name += — str/slice compound | BUILD_FAIL
@@ -363,19 +378,245 @@ static const struct row rows[] = {
/* The fold-2b 40B capture store (p7b/p7_composed) — wired in a
* follow-up resolver commit; until then it must die LOUD, never
* the pre-C1 silent drop. */
{ "reject_aggregate",
/* C1.25 (#23): the run_thread 40B capture store, graduated from
* BUILD_FAIL (C1's loud boundary) to wired. Literal rhs goes
* through a FRESH per-use @placescr materialise + word-copy; the
* `...` setter pins the autofill zero loop; neighbour element +
* sibling field readbacks pin the stride and copy length. */
/* Readback is RAW bytes over ts.ptr (the 804 tagged_56b
* precedent): a depth-2 read behind an index (ts[1].cap.start)
* is the F4 walker gap (task #6) and link-fails today on the
* cgen.c:9038 global-leaf fallback — typed readbacks graduate
* with the C2 read-walker. Layout: t={pc@0,cap@8}, size 48;
* capture={content(str ptr@0,len@8,cap@16),start@24,end@32}.
* Elem1 base 48 → content.len byte 64, start 80, end 88;
* elem0 → 16/32/40. Stored values fit one byte. */
{ "agg_structlit_40b",
"package main;\n"
"type capture = struct { content: str, start: size, end: size };\n"
"type t = struct { pc: size, cap: capture };\n"
"fn setcap(ts: *[]t, i: size) void = {\n"
"\t(*ts)[i].cap = capture { content = \"x\", start = 1, end = 2 };\n"
"\t(*ts)[i].cap = capture { content = \"hit\", start = 2, end = 4 };\n"
"};\n"
"fn clearcap(ts: *[]t, i: size) void = {\n"
"\t(*ts)[i].cap = capture { content = \"z\", ... };\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet z: capture = capture { content = \"\", start = 0, end = 0 };\n"
"\tlet ts: []t = [];\n"
"\tappend(ts, t { pc = 7, cap = z });\n"
"\tappend(ts, t { pc = 8, cap = z });\n"
"\tsetcap(&ts, 1);\n"
"\tlet bp: *u8 = ts.ptr: *u8;\n"
"\tif (bp[64] != 3u8) { return 1; };\n"
"\tif (bp[80] != 2u8) { return 2; };\n"
"\tif (bp[88] != 4u8) { return 3; };\n"
"\tif (ts[1].pc != 8) { return 4; };\n"
"\tif (bp[16] != 0u8) { return 5; };\n"
"\tif (ts[0].pc != 7) { return 6; };\n"
"\tclearcap(&ts, 1);\n"
"\tif (bp[64] != 1u8) { return 7; };\n"
"\tif (bp[80] != 0u8) { return 8; };\n"
"\tif (bp[88] != 0u8) { return 9; };\n"
"\treturn 52;\n"
"};\n",
52, NULL },
/* Addressable aggregate sources via the #265/#268 dispatch:
* local ident (LEAQ slot) and deref (*pc). Raw-byte readback per
* the agg_structlit_40b layout note (F4 blocks typed depth-2). */
{ "agg_from_ident_deref",
"package main;\n"
"type capture = struct { content: str, start: size, end: size };\n"
"type t = struct { pc: size, cap: capture };\n"
"fn copycap(ts: *[]t, i: size, src: capture) void = {\n"
"\t(*ts)[i].cap = src;\n"
"};\n"
"fn derefcap(ts: *[]t, i: size, pc: *capture) void = {\n"
"\t(*ts)[i].cap = *pc;\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet z: capture = capture { content = \"\", start = 0, end = 0 };\n"
"\tlet ts: []t = [];\n"
"\tappend(ts, t { pc = 7, cap = z });\n"
"\tappend(ts, t { pc = 8, cap = z });\n"
"\tlet a: capture = capture { content = \"ab\", start = 1, end = 3 };\n"
"\tcopycap(&ts, 1, a);\n"
"\tlet bp: *u8 = ts.ptr: *u8;\n"
"\tif (bp[64] != 2u8) { return 1; };\n"
"\tif (bp[80] != 1u8) { return 2; };\n"
"\tif (bp[88] != 3u8) { return 3; };\n"
"\tlet b: capture = capture { content = \"wxyz\", start = 5, end = 9 };\n"
"\tderefcap(&ts, 0, &b);\n"
"\tif (bp[16] != 4u8) { return 4; };\n"
"\tif (bp[32] != 5u8) { return 5; };\n"
"\tif (bp[40] != 9u8) { return 6; };\n"
"\tif (bp[80] != 1u8) { return 7; };\n"
"\treturn 53;\n"
"};\n",
53, NULL },
/* Nested struct-typed literal field inside the stored literal —
* the #18 recursion through cg_structlit_fill must land the inner
* bytes in the @placescr image before the copy. */
{ "agg_nested_lit",
"package main;\n"
"type inner = struct { x: i64, y: i64 };\n"
"type big = struct { a: i64, ib: inner };\n"
"type t = struct { pc: size, bg: big };\n"
"fn setbg(ts: *[]t, i: size) void = {\n"
"\t(*ts)[i].bg = big { a = 1, ib = inner { x = 2, y = 3 } };\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet z: big = big { a = 0, ib = inner { x = 0, y = 0 } };\n"
"\tlet ts: []t = [];\n"
"\tappend(ts, t { pc = 7, bg = z });\n"
"\tsetbg(&ts, 0);\n"
"\tlet bp: *u8 = ts.ptr: *u8;\n"
"\tif (bp[8] != 1u8) { return 1; };\n"
"\tif (bp[16] != 2u8) { return 2; };\n"
"\tif (bp[24] != 3u8) { return 3; };\n"
"\tif (ts[0].pc != 7) { return 4; };\n"
"\treturn 54;\n"
"};\n",
54, NULL },
/* [3]u8 array field from an ident source — TY_ARRAY aggregate arm
* + the MOVW/MOVB copy tails (fsz=3). */
{ "agg_array_field_odd",
"package main;\n"
"type t = struct { pc: size, arr: [3]u8 };\n"
"fn setarr(ts: *[]t, i: size, src: [3]u8) void = {\n"
"\t(*ts)[i].arr = src;\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet a3: [3]u8 = [9u8, 8u8, 7u8];\n"
"\tlet z3: [3]u8 = [0u8, 0u8, 0u8];\n"
"\tlet ts: []t = [];\n"
"\tappend(ts, t { pc = 5, arr = z3 });\n"
"\tsetarr(&ts, 0, a3);\n"
"\tif (ts[0].arr[0] != 9u8) { return 1; };\n"
"\tif (ts[0].arr[1] != 8u8) { return 2; };\n"
"\tif (ts[0].arr[2] != 7u8) { return 3; };\n"
"\tif (ts[0].pc != 5) { return 4; };\n"
"\treturn 55;\n"
"};\n",
55, NULL },
/* Two same-size structlit stores in ONE fn — each must get its
* own FRESH @placescr slot and both must land (the #31 multi-
* live discipline pin). The stronger shape — a structlit whose
* FIELD expr recurses into a same-size aggregate assign, the
* fresh-per-use rationale proper — is not constructible today:
* match-expr (the only stmt-carrying expr) is a parse reject in
* field position; fresh-per-use stays defensive (rob ruling). */
{ "agg_fresh_two_stores",
"package main;\n"
"type capture = struct { content: str, start: size, end: size };\n"
"type t = struct { pc: size, cap: capture };\n"
"fn settwo(ts: *[]t) void = {\n"
"\t(*ts)[0].cap = capture { content = \"aa\", start = 1, end = 2 };\n"
"\t(*ts)[1].cap = capture { content = \"bbb\", start = 3, end = 4 };\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet z: capture = capture { content = \"\", start = 0, end = 0 };\n"
"\tlet ts: []t = [];\n"
"\tappend(ts, t { pc = 7, cap = z });\n"
"\tappend(ts, t { pc = 8, cap = z });\n"
"\tsettwo(&ts);\n"
"\tlet bp: *u8 = ts.ptr: *u8;\n"
"\tif (bp[16] != 2u8) { return 1; };\n"
"\tif (bp[32] != 1u8) { return 2; };\n"
"\tif (bp[40] != 2u8) { return 3; };\n"
"\tif (bp[64] != 3u8) { return 4; };\n"
"\tif (bp[80] != 3u8) { return 5; };\n"
"\tif (bp[88] != 4u8) { return 6; };\n"
"\treturn 56;\n"
"};\n",
56, NULL },
/* [3]u32 field (fsz=12) — the MOVL copy tail, untouched by the
* 40B (all-MOVQ) and [3]u8 (MOVW+MOVB) rows.
*
* Matrix honesty, TY_TUPLE: the arm wires tuple fields and a
* local-ident tuple source stores correctly (cstage-verified),
* but a dual-stage row is blocked by three PRE-EXISTING tuple
* gaps outside this arm: by-value tuple param drops word 2 at
* runtime (both stages, master too), wwstage tuple-let init
* `let a: (i64,i64) = (3,4)` drops the word-2 store (cs≠ww),
* and `a.0 =` element assign is unwired (loud). Tuple-field row
* graduates with that family (filed). */
{ "agg_array_field_movl",
"package main;\n"
"type t = struct { pc: size, arr: [3]u32 };\n"
"fn setarr(ts: *[]t, i: size, src: [3]u32) void = {\n"
"\t(*ts)[i].arr = src;\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet a3: [3]u32 = [9u32, 8u32, 7u32];\n"
"\tlet z3: [3]u32 = [0u32, 0u32, 0u32];\n"
"\tlet ts: []t = [];\n"
"\tappend(ts, t { pc = 5, arr = z3 });\n"
"\tsetarr(&ts, 0, a3);\n"
"\tif (ts[0].arr[0] != 9u32) { return 1; };\n"
"\tif (ts[0].arr[1] != 8u32) { return 2; };\n"
"\tif (ts[0].arr[2] != 7u32) { return 3; };\n"
"\tif (ts[0].pc != 5) { return 4; };\n"
"\treturn 57;\n"
"};\n",
57, NULL },
/* Compound on an aggregate field is meaningless — stays loud. */
{ "reject_agg_compound",
"package main;\n"
"type capture = struct { content: str, start: size, end: size };\n"
"type t = struct { pc: size, cap: capture };\n"
"fn addcap(ts: *[]t, i: size, src: capture) void = {\n"
"\t(*ts)[i].cap += src;\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet ts: []t = [];\n"
"\tlet a: capture = capture { content = \"\", start = 0, end = 0 };\n"
"\taddcap(&ts, 0, a);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL,
"assign-resolver: compound on aggregate field not wired (rule-7)" },
/* sret-class call rhs (>24B return) needs a runtime-RDI dest —
* the #234-tail deferral. */
{ "reject_agg_call_sret",
"package main;\n"
"type capture = struct { content: str, start: size, end: size };\n"
"type t = struct { pc: size, cap: capture };\n"
"fn mk() capture = {\n"
"\treturn capture { content = \"x\", start = 1, end = 2 };\n"
"};\n"
"fn setcap(ts: *[]t, i: size) void = { (*ts)[i].cap = mk(); };\n"
"export fn main() i32 = {\n"
"\tlet ts: []t = [];\n"
"\tsetcap(&ts, 0);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "assign-resolver: aggregate field not wired (rule-7)" },
BUILD_FAIL,
"assign-resolver: sret call into aggregate field unwired "
"(#234-tail/rule-7)" },
/* ≤24B reg-return call rhs — deferred, task #24. */
{ "reject_agg_call_reg",
"package main;\n"
"type pair = struct { a: i64, b: i64 };\n"
"type t = struct { pc: size, pr: pair };\n"
"fn mk() pair = { return pair { a = 1, b = 2 }; };\n"
"fn setpr(ts: *[]t, i: size) void = { (*ts)[i].pr = mk(); };\n"
"export fn main() i32 = {\n"
"\tlet ts: []t = [];\n"
"\tsetpr(&ts, 0);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL,
"assign-resolver: call result into aggregate field unwired "
"(task #24/rule-7)" },
{ "reject_float",
"package main;\n"