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

@@ -20015,7 +20015,8 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
};
// Thin wrapper preserving the BP-rel call shape used by cglet,
// cgreturn, and cgassign N_IDENT-lhs N_STRUCTLIT.
// cgreturn, cgassign N_IDENT-lhs N_STRUCTLIT, and the C1.25
// @placescr materialise (cg_structlit_fill_bp's named twin).
fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
if (si == nil) { return; };
cgstructlitfill(c, si, lit, 0, 0, "", bpoff);
@@ -29029,12 +29030,152 @@ fn cgassign(c: *cgen, n: *node) void = {
os.write(2, mt.ptr, mt.len: u64);
os.exit(1);
};
// C1.25 (#23): aggregate field STORE through the
// resolver — run_thread's 40B capture store
// `(*ts)[i].root_capture = capture{...}`. Dest
// address from cgplaceaddr (BX), source address
// in SI per rhs shape, then the #270-1b
// word-copy tail (SI)→(BX). Pre-C1 a SILENT
// no-op; C1 made it loud; this wires it
// (loud-first, wire-next). Compound on an
// aggregate is meaningless and stays loud.
// Mirror of the cstage cgen.c C1.25 arm.
if (fu.kind == tykind.TY_STRUCT
|| fu.kind == tykind.TY_ARRAY
|| fu.kind == tykind.TY_TUPLE) {
let ma: str = "assign-resolver: aggregate field not wired (rule-7)\n";
os.write(2, ma.ptr, ma.len: u64);
os.exit(1);
if (n.op != tkind.TK_ASSIGN) {
let mac: str = "assign-resolver: compound on aggregate field not wired (rule-7)\n";
os.write(2, mac.ptr, mac.len: u64);
os.exit(1);
};
if (n.rhs != nil) {
if (n.rhs.kind == nkind.N_CALL) {
// sret-class needs a runtime-RDI dest
// (the #234-tail deferral); the ≤24B
// reg-return receive is task #24. The
// callee return type equals the field
// type (checker-guaranteed), so
// callsretsize gives cstage's
// cg_sret_retsize(ft) verdict.
if (callsretsize(c, n.rhs) > 0) {
let mas: str = "assign-resolver: sret call into aggregate field unwired (#234-tail/rule-7)\n";
os.write(2, mas.ptr, mas.len: u64);
os.exit(1);
};
let ma24: str = "assign-resolver: call result into aggregate field unwired (task #24/rule-7)\n";
os.write(2, ma24.ptr, ma24.len: u64);
os.exit(1);
};
};
let placed: bool = false;
let isslit: bool = false;
if (n.rhs != nil) {
if (n.rhs.kind == nkind.N_STRUCTLIT
&& fu.kind == tykind.TY_STRUCT) {
isslit = true;
};
};
if (isslit) {
// @placescr — FRESH slot PER USE (the
// @slicescr discipline via localalloc,
// NOT the cached @tagscr table: a
// cached slot is the #31 multi-live
// corruption trap; rob ruling). Funnel
// contract, #44 discipline: this arm is
// the ONLY @placescr alloc site. Fill
// handles nested literals (#18), tagged
// fields, TK_ELLIPSIS autofill; the
// value sits in memory, so the resolver
// below may clobber AX/CX freely.
let sname: str;
sname.ptr = nil; sname.len = 0;
if (ft.kind == tykind.TY_NAMED) {
sname = ft.name;
};
let si: *structinfo = nil;
if (sname.len > 0) {
si = structlookup(c, sname);
};
if (si == nil) {
// wwstage-only bail: the fill is
// structinfo-keyed, so an anonymous-
// struct field type has no registry
// entry (cstage fills from Type
// directly). Loud, rule 7.
let man: str = "assign-resolver: structlit field layout unresolved (rule-7)\n";
os.write(2, man.ptr, man.len: u64);
os.exit(1);
};
let scr: i32 = localalloc(c, "@placescr", fsz, nil);
cgstructlitfillbp(c, si, n.rhs, scr);
placed = cgplaceaddr(c, lhs, "BX");
if (placed) {
emitline("\tLEAQ\t");
emitoff(scr: i64);
emitline("(BP), SI\n");
};
} else {
// Addressable source — ident / global /
// N_DOT chain / deref — via the closed
// #265/#268 dispatch. Its N_INDEX arm
// clobbers BX, so the dest spills around
// it (the #270-1b order). Literal
// arrays/tuples have no storage address
// and stay loud.
placed = cgplaceaddr(c, lhs, "BX");
if (placed) {
emitline("\tPUSHQ\tBX\n");
if (!aggargsrcaddr(c, n.rhs, "SI")) {
let mar: str = "assign-resolver: aggregate rhs shape unwired (rule-7)\n";
os.write(2, mar.ptr, mar.len: u64);
os.exit(1);
};
emitline("\tPOPQ\tBX\n");
};
};
if (!placed) {
let mau: str = "unsupported assign target shape\n";
os.write(2, mau.ptr, mau.len: u64);
os.exit(1);
};
let kc: i32 = 0;
for (kc + 8 <= fsz) {
emitline("\tMOVQ\t");
emitoff(kc: i64);
emitline("(SI), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff(kc: i64);
emitline("(BX)\n");
kc += 8;
};
if (kc + 4 <= fsz) {
emitline("\tMOVL\t");
emitoff(kc: i64);
emitline("(SI), AX\n");
emitline("\tMOVL\tAX, ");
emitoff(kc: i64);
emitline("(BX)\n");
kc += 4;
};
if (kc + 2 <= fsz) {
emitline("\tMOVW\t");
emitoff(kc: i64);
emitline("(SI), AX\n");
emitline("\tMOVW\tAX, ");
emitoff(kc: i64);
emitline("(BX)\n");
kc += 2;
};
if (kc + 1 <= fsz) {
emitline("\tMOVB\t");
emitoff(kc: i64);
emitline("(SI), AX\n");
emitline("\tMOVB\tAX, ");
emitoff(kc: i64);
emitline("(BX)\n");
kc += 1;
};
return;
};
};
let fstrsl: bool = false;