diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index c47c29ef..b29ad34f 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -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) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 12b22d97..d3444a96 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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; diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index acdd42fa..5a384ef3 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -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; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 860fad16..8216ea7e 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -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); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index fea5a094..6aa82e64 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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; diff --git a/test/wcc/805_placeaddr_store.c b/test/wcc/805_placeaddr_store.c index ef3c69d9..9efc6a6c 100644 --- a/test/wcc/805_placeaddr_store.c +++ b/test/wcc/805_placeaddr_store.c @@ -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"