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

@@ -6074,11 +6074,113 @@ cgexpr(Cg *c, Node *n, Local *locals)
if (fu && fu->kind == TY_TAGGED)
fatal("assign-resolver: tagged field not "
"wired (rule-7)");
/* 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 this was a SILENT no-op; C1 made it loud;
* this wires it (loud-first, wire-next). Compound on
* an aggregate is meaningless and stays loud. */
if (fu && (fu->kind == TY_STRUCT
|| fu->kind == TY_ARRAY
|| fu->kind == TY_TUPLE))
fatal("assign-resolver: aggregate field not "
"wired (rule-7)");
|| fu->kind == TY_TUPLE)) {
if (n->op != TK_ASSIGN)
fatal("assign-resolver: compound on "
"aggregate field not wired "
"(rule-7)");
if (n->rhs && n->rhs->kind == N_CALL) {
/* sret-class needs a runtime-RDI dest
* (the #234-tail deferral); the ≤24B
* reg-return receive is task #24. */
if (cg_sret_retsize(ft) > 0)
fatal("assign-resolver: sret "
"call into aggregate field "
"unwired (#234-tail/"
"rule-7)");
fatal("assign-resolver: call result "
"into aggregate field unwired "
"(task #24/rule-7)");
}
int placed = 0;
if (n->rhs && n->rhs->kind == N_STRUCTLIT
&& fu->kind == TY_STRUCT) {
/* @placescr — FRESH slot PER USE (the
* @slicescr discipline, 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. */
int scr = local_alloc(c, &locals,
"@placescr", fsz, cg_frame);
cg_structlit_fill_bp(c, &locals, fu,
n->rhs, scr);
placed = cgplaceaddr(c, n->lhs, D_BX,
locals);
if (placed)
ins2(c, A_LEAQ,
amem(D_BP, scr),
areg(D_SI));
} 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, n->lhs, D_BX,
locals);
if (placed) {
ins1(c, A_PUSHQ, areg(D_BX));
if (!aggarg_srcaddr(c, n->rhs,
D_SI, locals))
fatal("assign-resolver"
": aggregate rhs "
"shape unwired "
"(rule-7)");
ins1(c, A_POPQ, areg(D_BX));
}
}
if (!placed)
fatal("unsupported assign target "
"shape");
int k = 0;
for (; k + 8 <= fsz; k += 8) {
ins2(c, A_MOVQ, amem(D_SI, k),
areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BX, k));
}
if (k + 4 <= fsz) {
ins2(c, A_MOVL, amem(D_SI, k),
areg(D_AX));
ins2(c, A_MOVL, areg(D_AX),
amem(D_BX, k));
k += 4;
}
if (k + 2 <= fsz) {
ins2(c, A_MOVW, amem(D_SI, k),
areg(D_AX));
ins2(c, A_MOVW, areg(D_AX),
amem(D_BX, k));
k += 2;
}
if (k + 1 <= fsz) {
ins2(c, A_MOVB, amem(D_SI, k),
areg(D_AX));
ins2(c, A_MOVB, areg(D_AX),
amem(D_BX, k));
k += 1;
}
break;
}
if (fu && (fu->kind == TY_STR
|| fu->kind == TY_SLICE)) {
if (n->op != TK_ASSIGN)

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;

View File

@@ -9006,12 +9006,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;

View File

@@ -4283,7 +4283,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);

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;

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"