From a72e8158c7e740e098a009e8c4071e7a42acf6a8 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 5 Jun 2026 02:26:06 +0900 Subject: [PATCH] =?UTF-8?q?w6c+w6c=5Fww:=20over-cap=20(sret)=20tuple=20ret?= =?UTF-8?q?urns=20carry=20tagged=20elements=20=E2=80=94=20MEMORY-class=20b?= =?UTF-8?q?ox=20copy=20(#22b)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The over-cap SEND gains a per-element tagged arm: a declared-TAGGED element copies its whole box mem-to-mem through the sret pointer (@sretarg reloaded into DX, eslot/8 AX-bounced word pairs) from its ident-local slot — cgexpr can't source it (tagged ident load is word0-only; the AX/DX/CX/R8 box cursor would collide with the DX dest-base reload). Non-ident/widening sources stay loud (call-source is task #41 per rob's loud+filed ruling, widening #23/#40). The receive legs were already eslot-generic from #22a except the wwstage MASSIGN sret copy-out, whose wide=(str||slice) key dropped a tagged element to the scalar 8B branch — re-keyed to the accessor's eslot>8. The over-cap DECISION now rides cg_sret_retsize/sretretsize on the DECLARED return type — the same SSoT the prologue @sretarg wiring and the caller use. The old expr-shape count disagreed with the classifier for declared-tagged elements with unwidened-payload exprs: the emit took the register path against an sret-reading caller — built clean, ran garbage, BOTH stages, byte-id, gate-blind (941 b22_reject_widen_ skew). A rule-7 net behind the branch catches any future inverse skew. The same classify/emit class on the LET side: wwstage's cgexpr cursor materialiser counts a void literal elem as 0 words, so a void-bearing over-cap literal init slipped its loud and fell past every store arm to NOTHING (cs louded via the ken-R1 net; ww ran silent-wrong) — wwstage gains the same net. foff advances ride tuple_eslot/tupeslotn (THE accessor): ken's K4 proved the old wide?esz:8 void-elem 8-advance was a LIVE silent miscompile at base (declared-void-elem over-cap return, send wrote +8 where every receive walks the 0-slot). The unwiring also made >32B boxes transportable, exposing the t.N tagged read's unbounded k=3 to R8 (silent payload-word-3 drop). Bounded loud both stages; the mem-based box read is #37's fold (transport itself is size-generic and correct — 941 b22_big_box_mlet). One commit by the #61 route-sharing precedent: unwiring the send is the only thing that makes the ww massign arm, both skew nets, and the >32B read reachable — the one split order that avoids a transient silent-wrong build (send last) lands those arms DEAD, unexercisable by any test until the unwiring commit, so a defect in them would bisect to the wrong commit; fused, every arm lands with the 941 rows that pin it, preserving the #22a exit invariant (zero silent tagged-tuple paths: correct-via-accessor or LOUD with a task cite; proof-greps in-tree — tuple_ebytes/tupebytes 0 hits, all remaining tuple_rseq/tupreg walks cap-validated upstream). Driver: regex fold-5b parse_repetition ((void|size),(void|size),size) = 5 GP eightbytes > TUPLE_GPCAP — pinned end-to-end (construct, send, let/MLET/MASSIGN receive, t.N is/as, destructure) plus head/mid/tail, str+tagged and SSE-over-cap mixes, `_` skip, and the reject pins: 941 165/165 (15 new rows), test-unit 281/281. ken runtime-oracle PASS x2 (independent hand-math layout audit, K1-K5 probes); reviewer-22b amendments (test-only): ken-K5 32B-exact box-at-cap row, both-tagged 2-elem row, chained-sret row, #37-family cite pinned in the big-box experr. Residuals filed: #39 return-forward divergence, #41 call-source tripwire, #37 mem-based box read. --- cmd/w6c/cgen.c | 117 ++++++++-- selfhost/cmd/w6c/main.combined.ww | 158 ++++++++++--- selfhost/cmd/wcc/cgenexpr.ww | 14 ++ selfhost/cmd/wcc/cgenstmt.ww | 144 +++++++++--- selfhost/cmd/wwdump/main.combined.ww | 158 ++++++++++--- test/wcc/941_tuple_slot_layout_run.c | 324 ++++++++++++++++++++++++++- 6 files changed, 803 insertions(+), 112 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index b0da5e65..77f7a951 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -3114,8 +3114,8 @@ tuple_lit_gpwords(Node *e) * local slot — cgexpr's ident load is word0-only for tagged (every * tagged consumer reads memory), so the cursor fill must too; any * other tagged source shape is loud (rule 7; the cursor-receive arm - * for call results is with #22b's tupstore work, widening literals - * with #23). Shared by cg_tuple_lit_to_cursor and the cgreturn + * for call results rides the #35 non-ident-source family, widening + * literals #23). Shared by cg_tuple_lit_to_cursor and the cgreturn * N_TUPLE arm — count (tuple_lit_gpwords) and push live or die * together. */ static void @@ -3129,7 +3129,8 @@ tuple_lit_push_elem(Cg *c, Local *locals, Node *e) if (eoff == 0) fatal("#22a: tagged tuple element from a non-local " "source shape unwired (ident locals only; " - "rule 7, #22b/#23 follow-ups)"); + "rule 7; call-source is task #41, " + "widening #23, deref/cast #35)"); for (int k = 0; k < eslot / 8; k++) { ins2(c, A_MOVQ, amem(D_BP, eoff + k * 8), areg(D_AX)); @@ -10013,6 +10014,18 @@ cgexpr(Cg *c, Node *n, Local *locals) * the ascending walk can't clobber the base. */ if (fu && fu->kind == TY_TAGGED) { int eslot = tuple_eslot(tp->type); + /* #22b (rule 7): a >32B box overruns the + * 4-reg cursor — pre-bound the walk + * indexed past tuple_rseq (invalid asm) + * while wwstage clamped to R8 (silent + * payload drop). Reachable only since + * the over-cap sret send unwired; the + * mem-based box read is the #37 family. */ + if (eslot > TUPLE_GPCAP * 8) + fatal("tagged tuple element read " + "exceeds the AX/DX/CX/R8 box " + "cursor (mem-based read is the " + "#37 family; rule 7)"); for (int k = 0; k < eslot / 8; k++) ins2(c, A_MOVQ, amem(base_reg, @@ -12212,7 +12225,21 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) else gptotal += tuple_lit_gpwords(e); } - if (gptotal > TUPLE_GPCAP || ssecount > ssecap) { + /* #22b: classify and emit MUST agree (the #10 SSoT + * note at TUPLE_GPCAP). The over-cap DECISION rides + * cg_sret_retsize on the DECLARED return type — the + * same predicate the prologue (@sretarg) and the + * caller key on. The expr-shape count above only + * pairs the in-cap push/pop: a declared-tagged + * element whose expr is the unwidened payload counts + * 1 word here vs 2+ declared eightbytes, so the emit + * took the register path against an sret-classified + * caller — silent garbage, both stages, gate-blind + * (probe /tmp/i22b/p2). */ + int overcap = cg_ret_type != NULL + ? cg_sret_retsize(cg_ret_type) > 0 + : (gptotal > TUPLE_GPCAP || ssecount > ssecap); + if (overcap) { /* #10 Fold A: over-cap tuple returns via sret. The * prologue wired @sretarg (cg_sret_retsize agrees on * the caps — the shared SSoT), holding the caller- @@ -12237,20 +12264,56 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) Type *rtt = type_chase_named(cg_ret_type); Tparam *pp = (rtt && rtt->kind == TY_TUPLE) ? rtt->params : NULL; - /* #22a (rule 7): a tagged element's box can't - * ride the scalar/wide store arms below — the - * MEMORY-class tagged element store is #22b - * (task #28); pre-guard it silently stored - * word0 of the box. */ - for (Tparam *gp_ = pp; gp_; gp_ = gp_->next) { - Type *gu = type_chase_named(gp_->type); - if (gu && gu->kind == TY_TAGGED) - fatal("#22b: tagged element in " - "an over-cap (sret) tuple " - "return unwired (rule 7)"); - } int foff = 0; for (Node *e = n->lhs->list; e; e = e->next) { + Type *du = pp + ? type_chase_named(pp->type) : NULL; + if (du && du->kind == TY_TAGGED) { + /* #22b (task #28): MEMORY-class + * tagged element — the whole box + * copies through the sret pointer + * mem-to-mem from the element's + * local slot. cgexpr can't source + * it: the tagged ident load is + * word0-only (every tagged + * consumer reads memory) and the + * AX/DX/CX/R8 box cursor would + * collide with the DX dest-base + * reload. Ident-only, mirroring + * tuple_lit_push_elem; widening / + * non-ident sources stay loud + * (#23/#40 follow-ups). */ + int eslot = tuple_eslot(pp->type); + Type *eu = type_chase_named(e->type); + int eoff = 0; + if (e->kind == N_IDENT && e->str + && eu && eu->kind == TY_TAGGED + && tuple_eslot(e->type) == eslot) + eoff = localfind(*locals, + e->str); + if (eoff == 0) + fatal("#22b: tagged element " + "in an over-cap (sret) " + "tuple return from a " + "non-ident or widening " + "source unwired (ident " + "locals only; rule 7; " + "call-source is task " + "#41, widening #23/#40)"); + ins2(c, A_MOVQ, + amem(D_BP, cg_sret_arg_off), + areg(D_DX)); + for (int k = 0; k < eslot; k += 8) { + ins2(c, A_MOVQ, + amem(D_BP, eoff + k), + areg(D_AX)); + ins2(c, A_MOVQ, areg(D_AX), + amem(D_DX, foff + k)); + } + foff += eslot; + pp = pp->next; + continue; + } int isflt = fld_isfloat(e->type, &f32); int wide = node_isstr(e) || node_isslice(e); int esz = 8; @@ -12274,11 +12337,16 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) } else ins2(c, fldstoreop(e->type, esz), areg(D_AX), amem(D_DX, foff)); - /* C-t0: the sret buffer is slot-laid + /* C-t0/#22: the sret buffer is slot-laid * like every tuple home (checker size, - * t.N reader, mlet receive agree); esz - * keeps the store WIDTH natural. */ - foff += wide ? esz : 8; + * t.N reader, mlet receive agree) — the + * stride is THE accessor's (a declared + * void element's 0-slot included; the + * old wide?esz:8 advanced 8 where every + * receive walks 0). esz keeps the store + * WIDTH natural. */ + foff += tuple_eslot(pp ? pp->type + : e->type); if (pp) pp = pp->next; } ins2(c, A_MOVQ, amem(D_BP, cg_sret_arg_off), @@ -12288,6 +12356,15 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) ins0(c, A_RET); break; } + /* rule-7 net: register-classified by the declared type + * but the expr-shape count overflows the cursor — the + * pops below would index past tuple_rseq. Unreachable + * while expr counts never exceed declared counts; + * loud, not OOB, if a future shape breaks that. */ + if (gptotal > TUPLE_GPCAP || ssecount > ssecap) + fatal("register-classified tuple return " + "exceeds the cursor (classify/emit skew; " + "rule 7, #22b)"); int fscr = 0; if (ssecount > 0) { if (cg_tupfscr != 0) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 9c2d958d..77936ac5 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -23170,6 +23170,20 @@ fn cgdot(c: *cgen, n: *node) void = { // tagged arm. if (istaggedtype(c, tpt)) { let eslot: i32 = tupeslotn(tpt); + // #22b (rule 7): a >32B box + // overruns the 4-reg cursor — + // pre-bound tupreg clamped k>=3 + // to R8 (silent payload drop) + // while cstage emitted invalid + // asm. Reachable only since the + // over-cap sret send unwired; + // the mem-based box read is the + // #37 family. Mirrors cstage. + if (eslot > TUPLE_GPCAP * 8) { + let m37: str = "tagged tuple element read exceeds the AX/DX/CX/R8 box cursor (mem-based read is the #37 family; rule 7)\n"; + os.write(2, m37.ptr, m37.len: u64); + os.exit(1); + }; let k: i32 = 0; for (k < eslot / 8) { emitline("\tMOVQ\t"); @@ -31320,8 +31334,8 @@ fn tuplitgpwords(c: *cgen, e: *node) i32 = { // tagged element loads its box words straight from its local slot — // cgexpr's ident load is word0-only for tagged (every tagged consumer // reads memory), so the cursor fill must too; any other tagged source -// shape is loud (rule 7; the cursor-receive arm for call results is -// with #22b's tupstore work, widening literals with #23). Mirror of +// shape is loud (rule 7; the cursor-receive arm for call results +// rides the #35 non-ident-source family, widening literals #23). Mirror of // cstage tuple_lit_push_elem — count (tuplitgpwords) and push live or // die together. fn tuplitpushelem(c: *cgen, e: *node) void = { @@ -31332,7 +31346,7 @@ fn tuplitpushelem(c: *cgen, e: *node) void = { let eoff: i32 = 0; if (e.kind == nkind.N_IDENT) { eoff = localfind(c, e.str); }; if (eoff == 0) { - let m22: str = "#22a: tagged tuple element from a non-local source shape unwired (ident locals only; rule 7, #22b/#23 follow-ups)\n"; + let m22: str = "#22a: tagged tuple element from a non-local source shape unwired (ident locals only; rule 7; call-source is task #41, widening #23, deref/cast #35)\n"; os.write(2, m22.ptr, m22.len: u64); os.exit(1); }; @@ -31569,24 +31583,21 @@ fn cgreturn(c: *cgen, n: *node) void = { }; e = e.next; }; - if (gptotal > TUPLE_GPCAP || ssecount > ssecap) { - // #22a (rule 7): a tagged element's box can't - // ride the scalar/wide store arms below — the - // MEMORY-class tagged element store is #22b - // (task #28); pre-guard it silently stored - // word0 of the box. - let gq: *node = nil; - if (c.fnret != nil) { gq = c.fnret.list; }; - for (gq != nil) { - let gqt: *tinfo = gq.lhs.type_: *tinfo; - for (gqt != nil && gqt.kind == tykind.TY_NAMED) { gqt = gqt.under; }; - if (gqt != nil && gqt.kind == tykind.TY_TAGGED) { - let m22b: str = "#22b: tagged element in an over-cap (sret) tuple return unwired (rule 7)\n"; - os.write(2, m22b.ptr, m22b.len: u64); - os.exit(1); - }; - gq = gq.next; - }; + // #22b: classify and emit MUST agree (the #10 SSoT note + // at TUPLE_GPCAP). The over-cap DECISION rides + // sretretsize on the DECLARED return type — the same + // predicate the prologue (@sretarg) and the caller key + // on. The expr-shape count above only pairs the in-cap + // push/pop: a declared-tagged element whose expr is the + // unwidened payload counts 1 word here vs 2+ declared + // eightbytes, so the emit took the register path against + // an sret-classified caller — silent garbage, both + // stages, gate-blind (probe /tmp/i22b/p2). + let overcap: bool = gptotal > TUPLE_GPCAP || ssecount > ssecap; + if (c.fnret != nil) { + overcap = sretretsize(c, c.fnret) > 0; + }; + if (overcap) { // #10 Fold A: over-cap tuple returns via sret. The // prologue wired @sretarg (sretretsize agrees on the // caps — TUPLE_GPCAP/TUPLE_SSECAP, the shared SSoT), @@ -31607,6 +31618,55 @@ fn cgreturn(c: *cgen, n: *node) void = { let we: *node = rhs.list; let foff: i32 = 0; for (we != nil) { + let dt: *tinfo = nil; + if (pt != nil) { dt = pt.lhs.type_: *tinfo; }; + for (dt != nil && dt.kind == tykind.TY_NAMED) { dt = dt.under; }; + if (dt != nil && dt.kind == tykind.TY_TAGGED) { + // #22b (task #28): MEMORY-class tagged + // element — the whole box copies through + // the sret pointer mem-to-mem from the + // element's local slot. cgexpr can't + // source it: the tagged ident load is + // word0-only (every tagged consumer + // reads memory) and the AX/DX/CX/R8 box + // cursor would collide with the DX + // dest-base reload. Ident-only, + // mirroring tuplitpushelem; widening / + // non-ident sources stay loud (#23/#40 + // follow-ups). Mirror of cstage cgen.c + // N_RETURN over-cap tagged arm. + let eslot: i32 = tupeslotn(pt.lhs); + let eu: *tinfo = we.type_: *tinfo; + for (eu != nil && eu.kind == tykind.TY_NAMED) { eu = eu.under; }; + let eoff: i32 = 0; + if (we.kind == nkind.N_IDENT && eu != nil) { + if (eu.kind == tykind.TY_TAGGED && tupeslotn(we) == eslot) { + eoff = localfind(c, we.str); + }; + }; + if (eoff == 0) { + let m22b: str = "#22b: tagged element in an over-cap (sret) tuple return from a non-ident or widening source unwired (ident locals only; rule 7; call-source is task #41, widening #23/#40)\n"; + os.write(2, m22b.ptr, m22b.len: u64); + os.exit(1); + }; + emitline("\tMOVQ\t"); + emitoff(saoff: i64); + emitline("(BP), DX\n"); + let bk: i32 = 0; + for (bk < eslot) { + emitline("\tMOVQ\t"); + emitoff((eoff + bk): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg((foff + bk): i64, "DX"); + emitline("\n"); + bk += 8; + }; + foff += eslot; + we = we.next; + if (pt != nil) { pt = pt.next; }; + continue; + }; let isflt: bool = isfloattype(c, we); let wide: bool = nodeisstr(c, we) || nodeisslice(c, we); let esz: i32 = 8; @@ -31646,12 +31706,17 @@ fn cgreturn(c: *cgen, n: *node) void = { emitline("\n"); }; }; - // C-t0: the sret buffer is slot-laid like + // C-t0/#22: the sret buffer is slot-laid like // every tuple home (checker size, t.N - // reader, mlet receive agree); esz keeps - // the store WIDTH natural. Mirrors cstage - // cgen.c N_RETURN over-cap arm. - if (wide) { foff += esz; } else { foff += 8; }; + // reader, mlet receive agree) — the stride + // is THE accessor's (a declared void + // element's 0-slot included; the old + // wide?esz:8 advanced 8 where every receive + // walks 0). esz keeps the store WIDTH + // natural. Mirrors cstage cgen.c N_RETURN + // over-cap arm. + if (pt != nil) { foff += tupeslotn(pt.lhs); } + else { foff += tupeslotn(we); }; we = we.next; if (pt != nil) { pt = pt.next; }; }; @@ -31664,6 +31729,16 @@ fn cgreturn(c: *cgen, n: *node) void = { c.lastwasreturn = 1; return; }; + // rule-7 net: register-classified by the declared type + // but the expr-shape count overflows the cursor — the + // pops below would index past tupreg. Unreachable while + // expr counts never exceed declared counts; loud, not + // OOB, if a future shape breaks that. Mirrors cstage. + if (gptotal > TUPLE_GPCAP || ssecount > ssecap) { + let mskew: str = "register-classified tuple return exceeds the cursor (classify/emit skew; rule 7, #22b)\n"; + os.write(2, mskew.ptr, mskew.len: u64); + os.exit(1); + }; let fscr: i32 = 0; if (ssecount > 0) { fscr = localadd(c, "@tupfscr", ssecap * 8, nil); @@ -33140,6 +33215,24 @@ fn cglet(c: *cgen, n: *node) void = { c.lastwasreturn = 0; return; }; + // #22a (rule 7, ken R1) wwstage half: an OVER-CAP tuple + // init whose rhs is not a CALL has no store path — only + // the CALL shape rides the sret receive below; every + // other rhs fell past ALL the store arms to NOTHING + // (silent uninitialized-frame reads). cgexpr's cursor + // materialisers loud most shapes, but their EXPR-shape + // counts let a declared-tagged element's unwidened + // payload (or a void literal) slip through in-cap + // (probe /tmp/i22b/p7) — the let-twin of the #22b + // classify/emit skew. Mirrors cstage cgen.c N_LET net. + if (ttup.kind == nkind.N_TTUPLE + && rhs.kind != nkind.N_CALL + && sretretsize(c, ttup) > 0) { + cgexpr(c, rhs); + let mnet: str = "over-cap tuple initialiser from a non-call source unwired (see #10/#22b)\n"; + os.write(2, mnet.ptr, mnet.len: u64); + os.exit(1); + }; }; // Array literal init: `let xs: [N]T = [a, b, c];` (or [_]T). // Walk elements in declaration order, store each at off + i*esz @@ -33932,7 +34025,14 @@ fn cgmassign(c: *cgen, n: *node) void = { let tn: *node = nil; if (pt2 != nil) { tn = pt2.lhs; }; let isflt: bool = isfloattype(c, tn); - let wide: bool = isstrtype(c, tn) || isslicetype(c, tn); + // #22b: the >8B copy-out keys on the ACCESSOR's slot + // (str/slice header AND tagged box), not a str/slice + // kind test — the tagged element took the scalar arm + // (8B silent truncation; unreachable while the SEND + // louded, live once #22b unwires it). Byte-id for + // str/slice (esz == eslot == 24). Mirrors the cstage + // N_MASSIGN sret arm + the R-1 all-three-routings lesson. + let eslot: i32 = tupeslotn(tn); let esz: i32 = 8; if (pt2 != nil) { let eti: *tinfo = pt2.lhs.type_: *tinfo; @@ -33951,9 +34051,9 @@ fn cgmassign(c: *cgen, n: *node) void = { emitline("\tX0, "); emitoff(off: i64); emitline("(BP)\n"); } else { - if (wide) { + if (eslot > 8) { let k: i32 = 0; - for (k < esz) { + for (k < eslot) { emitline("\tMOVQ\t"); emitoff((scr + foff + k): i64); emitline("(BP), AX\n"); diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 9880db07..41d75b4e 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -2703,6 +2703,20 @@ fn cgdot(c: *cgen, n: *node) void = { // tagged arm. if (istaggedtype(c, tpt)) { let eslot: i32 = tupeslotn(tpt); + // #22b (rule 7): a >32B box + // overruns the 4-reg cursor — + // pre-bound tupreg clamped k>=3 + // to R8 (silent payload drop) + // while cstage emitted invalid + // asm. Reachable only since the + // over-cap sret send unwired; + // the mem-based box read is the + // #37 family. Mirrors cstage. + if (eslot > TUPLE_GPCAP * 8) { + let m37: str = "tagged tuple element read exceeds the AX/DX/CX/R8 box cursor (mem-based read is the #37 family; rule 7)\n"; + os.write(2, m37.ptr, m37.len: u64); + os.exit(1); + }; let k: i32 = 0; for (k < eslot / 8) { emitline("\tMOVQ\t"); diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index 1672160e..71fdda80 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -325,8 +325,8 @@ fn tuplitgpwords(c: *cgen, e: *node) i32 = { // tagged element loads its box words straight from its local slot — // cgexpr's ident load is word0-only for tagged (every tagged consumer // reads memory), so the cursor fill must too; any other tagged source -// shape is loud (rule 7; the cursor-receive arm for call results is -// with #22b's tupstore work, widening literals with #23). Mirror of +// shape is loud (rule 7; the cursor-receive arm for call results +// rides the #35 non-ident-source family, widening literals #23). Mirror of // cstage tuple_lit_push_elem — count (tuplitgpwords) and push live or // die together. fn tuplitpushelem(c: *cgen, e: *node) void = { @@ -337,7 +337,7 @@ fn tuplitpushelem(c: *cgen, e: *node) void = { let eoff: i32 = 0; if (e.kind == nkind.N_IDENT) { eoff = localfind(c, e.str); }; if (eoff == 0) { - let m22: str = "#22a: tagged tuple element from a non-local source shape unwired (ident locals only; rule 7, #22b/#23 follow-ups)\n"; + let m22: str = "#22a: tagged tuple element from a non-local source shape unwired (ident locals only; rule 7; call-source is task #41, widening #23, deref/cast #35)\n"; os.write(2, m22.ptr, m22.len: u64); os.exit(1); }; @@ -574,24 +574,21 @@ fn cgreturn(c: *cgen, n: *node) void = { }; e = e.next; }; - if (gptotal > TUPLE_GPCAP || ssecount > ssecap) { - // #22a (rule 7): a tagged element's box can't - // ride the scalar/wide store arms below — the - // MEMORY-class tagged element store is #22b - // (task #28); pre-guard it silently stored - // word0 of the box. - let gq: *node = nil; - if (c.fnret != nil) { gq = c.fnret.list; }; - for (gq != nil) { - let gqt: *tinfo = gq.lhs.type_: *tinfo; - for (gqt != nil && gqt.kind == tykind.TY_NAMED) { gqt = gqt.under; }; - if (gqt != nil && gqt.kind == tykind.TY_TAGGED) { - let m22b: str = "#22b: tagged element in an over-cap (sret) tuple return unwired (rule 7)\n"; - os.write(2, m22b.ptr, m22b.len: u64); - os.exit(1); - }; - gq = gq.next; - }; + // #22b: classify and emit MUST agree (the #10 SSoT note + // at TUPLE_GPCAP). The over-cap DECISION rides + // sretretsize on the DECLARED return type — the same + // predicate the prologue (@sretarg) and the caller key + // on. The expr-shape count above only pairs the in-cap + // push/pop: a declared-tagged element whose expr is the + // unwidened payload counts 1 word here vs 2+ declared + // eightbytes, so the emit took the register path against + // an sret-classified caller — silent garbage, both + // stages, gate-blind (probe /tmp/i22b/p2). + let overcap: bool = gptotal > TUPLE_GPCAP || ssecount > ssecap; + if (c.fnret != nil) { + overcap = sretretsize(c, c.fnret) > 0; + }; + if (overcap) { // #10 Fold A: over-cap tuple returns via sret. The // prologue wired @sretarg (sretretsize agrees on the // caps — TUPLE_GPCAP/TUPLE_SSECAP, the shared SSoT), @@ -612,6 +609,55 @@ fn cgreturn(c: *cgen, n: *node) void = { let we: *node = rhs.list; let foff: i32 = 0; for (we != nil) { + let dt: *tinfo = nil; + if (pt != nil) { dt = pt.lhs.type_: *tinfo; }; + for (dt != nil && dt.kind == tykind.TY_NAMED) { dt = dt.under; }; + if (dt != nil && dt.kind == tykind.TY_TAGGED) { + // #22b (task #28): MEMORY-class tagged + // element — the whole box copies through + // the sret pointer mem-to-mem from the + // element's local slot. cgexpr can't + // source it: the tagged ident load is + // word0-only (every tagged consumer + // reads memory) and the AX/DX/CX/R8 box + // cursor would collide with the DX + // dest-base reload. Ident-only, + // mirroring tuplitpushelem; widening / + // non-ident sources stay loud (#23/#40 + // follow-ups). Mirror of cstage cgen.c + // N_RETURN over-cap tagged arm. + let eslot: i32 = tupeslotn(pt.lhs); + let eu: *tinfo = we.type_: *tinfo; + for (eu != nil && eu.kind == tykind.TY_NAMED) { eu = eu.under; }; + let eoff: i32 = 0; + if (we.kind == nkind.N_IDENT && eu != nil) { + if (eu.kind == tykind.TY_TAGGED && tupeslotn(we) == eslot) { + eoff = localfind(c, we.str); + }; + }; + if (eoff == 0) { + let m22b: str = "#22b: tagged element in an over-cap (sret) tuple return from a non-ident or widening source unwired (ident locals only; rule 7; call-source is task #41, widening #23/#40)\n"; + os.write(2, m22b.ptr, m22b.len: u64); + os.exit(1); + }; + emitline("\tMOVQ\t"); + emitoff(saoff: i64); + emitline("(BP), DX\n"); + let bk: i32 = 0; + for (bk < eslot) { + emitline("\tMOVQ\t"); + emitoff((eoff + bk): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg((foff + bk): i64, "DX"); + emitline("\n"); + bk += 8; + }; + foff += eslot; + we = we.next; + if (pt != nil) { pt = pt.next; }; + continue; + }; let isflt: bool = isfloattype(c, we); let wide: bool = nodeisstr(c, we) || nodeisslice(c, we); let esz: i32 = 8; @@ -651,12 +697,17 @@ fn cgreturn(c: *cgen, n: *node) void = { emitline("\n"); }; }; - // C-t0: the sret buffer is slot-laid like + // C-t0/#22: the sret buffer is slot-laid like // every tuple home (checker size, t.N - // reader, mlet receive agree); esz keeps - // the store WIDTH natural. Mirrors cstage - // cgen.c N_RETURN over-cap arm. - if (wide) { foff += esz; } else { foff += 8; }; + // reader, mlet receive agree) — the stride + // is THE accessor's (a declared void + // element's 0-slot included; the old + // wide?esz:8 advanced 8 where every receive + // walks 0). esz keeps the store WIDTH + // natural. Mirrors cstage cgen.c N_RETURN + // over-cap arm. + if (pt != nil) { foff += tupeslotn(pt.lhs); } + else { foff += tupeslotn(we); }; we = we.next; if (pt != nil) { pt = pt.next; }; }; @@ -669,6 +720,16 @@ fn cgreturn(c: *cgen, n: *node) void = { c.lastwasreturn = 1; return; }; + // rule-7 net: register-classified by the declared type + // but the expr-shape count overflows the cursor — the + // pops below would index past tupreg. Unreachable while + // expr counts never exceed declared counts; loud, not + // OOB, if a future shape breaks that. Mirrors cstage. + if (gptotal > TUPLE_GPCAP || ssecount > ssecap) { + let mskew: str = "register-classified tuple return exceeds the cursor (classify/emit skew; rule 7, #22b)\n"; + os.write(2, mskew.ptr, mskew.len: u64); + os.exit(1); + }; let fscr: i32 = 0; if (ssecount > 0) { fscr = localadd(c, "@tupfscr", ssecap * 8, nil); @@ -2145,6 +2206,24 @@ fn cglet(c: *cgen, n: *node) void = { c.lastwasreturn = 0; return; }; + // #22a (rule 7, ken R1) wwstage half: an OVER-CAP tuple + // init whose rhs is not a CALL has no store path — only + // the CALL shape rides the sret receive below; every + // other rhs fell past ALL the store arms to NOTHING + // (silent uninitialized-frame reads). cgexpr's cursor + // materialisers loud most shapes, but their EXPR-shape + // counts let a declared-tagged element's unwidened + // payload (or a void literal) slip through in-cap + // (probe /tmp/i22b/p7) — the let-twin of the #22b + // classify/emit skew. Mirrors cstage cgen.c N_LET net. + if (ttup.kind == nkind.N_TTUPLE + && rhs.kind != nkind.N_CALL + && sretretsize(c, ttup) > 0) { + cgexpr(c, rhs); + let mnet: str = "over-cap tuple initialiser from a non-call source unwired (see #10/#22b)\n"; + os.write(2, mnet.ptr, mnet.len: u64); + os.exit(1); + }; }; // Array literal init: `let xs: [N]T = [a, b, c];` (or [_]T). // Walk elements in declaration order, store each at off + i*esz @@ -2937,7 +3016,14 @@ fn cgmassign(c: *cgen, n: *node) void = { let tn: *node = nil; if (pt2 != nil) { tn = pt2.lhs; }; let isflt: bool = isfloattype(c, tn); - let wide: bool = isstrtype(c, tn) || isslicetype(c, tn); + // #22b: the >8B copy-out keys on the ACCESSOR's slot + // (str/slice header AND tagged box), not a str/slice + // kind test — the tagged element took the scalar arm + // (8B silent truncation; unreachable while the SEND + // louded, live once #22b unwires it). Byte-id for + // str/slice (esz == eslot == 24). Mirrors the cstage + // N_MASSIGN sret arm + the R-1 all-three-routings lesson. + let eslot: i32 = tupeslotn(tn); let esz: i32 = 8; if (pt2 != nil) { let eti: *tinfo = pt2.lhs.type_: *tinfo; @@ -2956,9 +3042,9 @@ fn cgmassign(c: *cgen, n: *node) void = { emitline("\tX0, "); emitoff(off: i64); emitline("(BP)\n"); } else { - if (wide) { + if (eslot > 8) { let k: i32 = 0; - for (k < esz) { + for (k < eslot) { emitline("\tMOVQ\t"); emitoff((scr + foff + k): i64); emitline("(BP), AX\n"); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 071fb759..1dd79d90 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -23170,6 +23170,20 @@ fn cgdot(c: *cgen, n: *node) void = { // tagged arm. if (istaggedtype(c, tpt)) { let eslot: i32 = tupeslotn(tpt); + // #22b (rule 7): a >32B box + // overruns the 4-reg cursor — + // pre-bound tupreg clamped k>=3 + // to R8 (silent payload drop) + // while cstage emitted invalid + // asm. Reachable only since the + // over-cap sret send unwired; + // the mem-based box read is the + // #37 family. Mirrors cstage. + if (eslot > TUPLE_GPCAP * 8) { + let m37: str = "tagged tuple element read exceeds the AX/DX/CX/R8 box cursor (mem-based read is the #37 family; rule 7)\n"; + os.write(2, m37.ptr, m37.len: u64); + os.exit(1); + }; let k: i32 = 0; for (k < eslot / 8) { emitline("\tMOVQ\t"); @@ -31320,8 +31334,8 @@ fn tuplitgpwords(c: *cgen, e: *node) i32 = { // tagged element loads its box words straight from its local slot — // cgexpr's ident load is word0-only for tagged (every tagged consumer // reads memory), so the cursor fill must too; any other tagged source -// shape is loud (rule 7; the cursor-receive arm for call results is -// with #22b's tupstore work, widening literals with #23). Mirror of +// shape is loud (rule 7; the cursor-receive arm for call results +// rides the #35 non-ident-source family, widening literals #23). Mirror of // cstage tuple_lit_push_elem — count (tuplitgpwords) and push live or // die together. fn tuplitpushelem(c: *cgen, e: *node) void = { @@ -31332,7 +31346,7 @@ fn tuplitpushelem(c: *cgen, e: *node) void = { let eoff: i32 = 0; if (e.kind == nkind.N_IDENT) { eoff = localfind(c, e.str); }; if (eoff == 0) { - let m22: str = "#22a: tagged tuple element from a non-local source shape unwired (ident locals only; rule 7, #22b/#23 follow-ups)\n"; + let m22: str = "#22a: tagged tuple element from a non-local source shape unwired (ident locals only; rule 7; call-source is task #41, widening #23, deref/cast #35)\n"; os.write(2, m22.ptr, m22.len: u64); os.exit(1); }; @@ -31569,24 +31583,21 @@ fn cgreturn(c: *cgen, n: *node) void = { }; e = e.next; }; - if (gptotal > TUPLE_GPCAP || ssecount > ssecap) { - // #22a (rule 7): a tagged element's box can't - // ride the scalar/wide store arms below — the - // MEMORY-class tagged element store is #22b - // (task #28); pre-guard it silently stored - // word0 of the box. - let gq: *node = nil; - if (c.fnret != nil) { gq = c.fnret.list; }; - for (gq != nil) { - let gqt: *tinfo = gq.lhs.type_: *tinfo; - for (gqt != nil && gqt.kind == tykind.TY_NAMED) { gqt = gqt.under; }; - if (gqt != nil && gqt.kind == tykind.TY_TAGGED) { - let m22b: str = "#22b: tagged element in an over-cap (sret) tuple return unwired (rule 7)\n"; - os.write(2, m22b.ptr, m22b.len: u64); - os.exit(1); - }; - gq = gq.next; - }; + // #22b: classify and emit MUST agree (the #10 SSoT note + // at TUPLE_GPCAP). The over-cap DECISION rides + // sretretsize on the DECLARED return type — the same + // predicate the prologue (@sretarg) and the caller key + // on. The expr-shape count above only pairs the in-cap + // push/pop: a declared-tagged element whose expr is the + // unwidened payload counts 1 word here vs 2+ declared + // eightbytes, so the emit took the register path against + // an sret-classified caller — silent garbage, both + // stages, gate-blind (probe /tmp/i22b/p2). + let overcap: bool = gptotal > TUPLE_GPCAP || ssecount > ssecap; + if (c.fnret != nil) { + overcap = sretretsize(c, c.fnret) > 0; + }; + if (overcap) { // #10 Fold A: over-cap tuple returns via sret. The // prologue wired @sretarg (sretretsize agrees on the // caps — TUPLE_GPCAP/TUPLE_SSECAP, the shared SSoT), @@ -31607,6 +31618,55 @@ fn cgreturn(c: *cgen, n: *node) void = { let we: *node = rhs.list; let foff: i32 = 0; for (we != nil) { + let dt: *tinfo = nil; + if (pt != nil) { dt = pt.lhs.type_: *tinfo; }; + for (dt != nil && dt.kind == tykind.TY_NAMED) { dt = dt.under; }; + if (dt != nil && dt.kind == tykind.TY_TAGGED) { + // #22b (task #28): MEMORY-class tagged + // element — the whole box copies through + // the sret pointer mem-to-mem from the + // element's local slot. cgexpr can't + // source it: the tagged ident load is + // word0-only (every tagged consumer + // reads memory) and the AX/DX/CX/R8 box + // cursor would collide with the DX + // dest-base reload. Ident-only, + // mirroring tuplitpushelem; widening / + // non-ident sources stay loud (#23/#40 + // follow-ups). Mirror of cstage cgen.c + // N_RETURN over-cap tagged arm. + let eslot: i32 = tupeslotn(pt.lhs); + let eu: *tinfo = we.type_: *tinfo; + for (eu != nil && eu.kind == tykind.TY_NAMED) { eu = eu.under; }; + let eoff: i32 = 0; + if (we.kind == nkind.N_IDENT && eu != nil) { + if (eu.kind == tykind.TY_TAGGED && tupeslotn(we) == eslot) { + eoff = localfind(c, we.str); + }; + }; + if (eoff == 0) { + let m22b: str = "#22b: tagged element in an over-cap (sret) tuple return from a non-ident or widening source unwired (ident locals only; rule 7; call-source is task #41, widening #23/#40)\n"; + os.write(2, m22b.ptr, m22b.len: u64); + os.exit(1); + }; + emitline("\tMOVQ\t"); + emitoff(saoff: i64); + emitline("(BP), DX\n"); + let bk: i32 = 0; + for (bk < eslot) { + emitline("\tMOVQ\t"); + emitoff((eoff + bk): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg((foff + bk): i64, "DX"); + emitline("\n"); + bk += 8; + }; + foff += eslot; + we = we.next; + if (pt != nil) { pt = pt.next; }; + continue; + }; let isflt: bool = isfloattype(c, we); let wide: bool = nodeisstr(c, we) || nodeisslice(c, we); let esz: i32 = 8; @@ -31646,12 +31706,17 @@ fn cgreturn(c: *cgen, n: *node) void = { emitline("\n"); }; }; - // C-t0: the sret buffer is slot-laid like + // C-t0/#22: the sret buffer is slot-laid like // every tuple home (checker size, t.N - // reader, mlet receive agree); esz keeps - // the store WIDTH natural. Mirrors cstage - // cgen.c N_RETURN over-cap arm. - if (wide) { foff += esz; } else { foff += 8; }; + // reader, mlet receive agree) — the stride + // is THE accessor's (a declared void + // element's 0-slot included; the old + // wide?esz:8 advanced 8 where every receive + // walks 0). esz keeps the store WIDTH + // natural. Mirrors cstage cgen.c N_RETURN + // over-cap arm. + if (pt != nil) { foff += tupeslotn(pt.lhs); } + else { foff += tupeslotn(we); }; we = we.next; if (pt != nil) { pt = pt.next; }; }; @@ -31664,6 +31729,16 @@ fn cgreturn(c: *cgen, n: *node) void = { c.lastwasreturn = 1; return; }; + // rule-7 net: register-classified by the declared type + // but the expr-shape count overflows the cursor — the + // pops below would index past tupreg. Unreachable while + // expr counts never exceed declared counts; loud, not + // OOB, if a future shape breaks that. Mirrors cstage. + if (gptotal > TUPLE_GPCAP || ssecount > ssecap) { + let mskew: str = "register-classified tuple return exceeds the cursor (classify/emit skew; rule 7, #22b)\n"; + os.write(2, mskew.ptr, mskew.len: u64); + os.exit(1); + }; let fscr: i32 = 0; if (ssecount > 0) { fscr = localadd(c, "@tupfscr", ssecap * 8, nil); @@ -33140,6 +33215,24 @@ fn cglet(c: *cgen, n: *node) void = { c.lastwasreturn = 0; return; }; + // #22a (rule 7, ken R1) wwstage half: an OVER-CAP tuple + // init whose rhs is not a CALL has no store path — only + // the CALL shape rides the sret receive below; every + // other rhs fell past ALL the store arms to NOTHING + // (silent uninitialized-frame reads). cgexpr's cursor + // materialisers loud most shapes, but their EXPR-shape + // counts let a declared-tagged element's unwidened + // payload (or a void literal) slip through in-cap + // (probe /tmp/i22b/p7) — the let-twin of the #22b + // classify/emit skew. Mirrors cstage cgen.c N_LET net. + if (ttup.kind == nkind.N_TTUPLE + && rhs.kind != nkind.N_CALL + && sretretsize(c, ttup) > 0) { + cgexpr(c, rhs); + let mnet: str = "over-cap tuple initialiser from a non-call source unwired (see #10/#22b)\n"; + os.write(2, mnet.ptr, mnet.len: u64); + os.exit(1); + }; }; // Array literal init: `let xs: [N]T = [a, b, c];` (or [_]T). // Walk elements in declaration order, store each at off + i*esz @@ -33932,7 +34025,14 @@ fn cgmassign(c: *cgen, n: *node) void = { let tn: *node = nil; if (pt2 != nil) { tn = pt2.lhs; }; let isflt: bool = isfloattype(c, tn); - let wide: bool = isstrtype(c, tn) || isslicetype(c, tn); + // #22b: the >8B copy-out keys on the ACCESSOR's slot + // (str/slice header AND tagged box), not a str/slice + // kind test — the tagged element took the scalar arm + // (8B silent truncation; unreachable while the SEND + // louded, live once #22b unwires it). Byte-id for + // str/slice (esz == eslot == 24). Mirrors the cstage + // N_MASSIGN sret arm + the R-1 all-three-routings lesson. + let eslot: i32 = tupeslotn(tn); let esz: i32 = 8; if (pt2 != nil) { let eti: *tinfo = pt2.lhs.type_: *tinfo; @@ -33951,9 +34051,9 @@ fn cgmassign(c: *cgen, n: *node) void = { emitline("\tX0, "); emitoff(off: i64); emitline("(BP)\n"); } else { - if (wide) { + if (eslot > 8) { let k: i32 = 0; - for (k < esz) { + for (k < eslot) { emitline("\tMOVQ\t"); emitoff((scr + foff + k): i64); emitline("(BP), AX\n"); diff --git a/test/wcc/941_tuple_slot_layout_run.c b/test/wcc/941_tuple_slot_layout_run.c index ac5aec0a..5ef7283d 100644 --- a/test/wcc/941_tuple_slot_layout_run.c +++ b/test/wcc/941_tuple_slot_layout_run.c @@ -369,19 +369,333 @@ static const struct row rows[] = { " return send(t): i32;\n" "};\n", 0, K_BUILDERR, "tuple arg element kind unsupported" }, - { "t22_reject_overcap_return", + /* ---- #22b (task #28): over-cap (sret, MEMORY-class) tagged-tuple + * transport. Every K_RUN row here was a BUILD-FAIL at 22a's exit + * bound ("#22b: tagged element in an over-cap (sret) tuple return + * unwired") — the mutation story is build-fail→runtime, not + * wrong-value→right-value. The driver is regex fold-5b's + * parse_repetition: ((void|size),(void|size),size) = 5 GP + * eightbytes > TUPLE_GPCAP=4. ---- */ + { "b22_parse_repetition", "package main;\n" "fn pr() ((void | size), (void | size), size) = {\n" - " let a: (void | size) = 1: size;\n" - " let b: (void | size) = 2: size;\n" - " return (a, b, 7);\n" + " let mn: (void | size) = void;\n" + " let mx: (void | size) = 5: size;\n" + " mn = 3: size;\n" + " return (mn, mx, 9);\n" + "};\n" + "export fn main() i32 = {\n" + " let t = pr();\n" + " if (!(t.0 is size)) { return 1; };\n" + " if (t.0 as size != 3) { return 2; };\n" + " if (!(t.1 is size)) { return 3; };\n" + " if (t.1 as size != 5) { return 4; };\n" + " if (t.2 != 9) { return 5; };\n" + " let (a, b, n) = pr();\n" + " if (a as size != 3) { return 6; };\n" + " if (b as size != 5) { return 7; };\n" + " if (n != 9) { return 8; };\n" + " let a2: (void | size) = void;\n" + " let b2: (void | size) = void;\n" + " let n2: size = 0;\n" + " a2, b2, n2 = pr();\n" + " if (a2 as size != 3) { return 9; };\n" + " if (b2 as size != 5) { return 10; };\n" + " if (n2 != 9) { return 11; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* tagged at HEAD / MID / TAIL of over-cap tuples + void variants + + * destructure-from-IDENT of a received over-cap tuple (the memory- + * source MLET arm, no register cursor). */ + { "b22_head_mid_tail", + "package main;\n" + "fn ph() ((void | size), size, size, size, size) = {\n" + " let a: (void | size) = 11: size;\n" + " return (a, 1, 2, 3, 4);\n" + "};\n" + "fn pm() (size, size, (void | size), size, size) = {\n" + " let a: (void | size) = 12: size;\n" + " return (1, 2, a, 3, 4);\n" + "};\n" + "fn pt() (size, size, size, size, (void | size)) = {\n" + " let a: (void | size) = 13: size;\n" + " return (1, 2, 3, 4, a);\n" + "};\n" + "fn pv() ((void | size), (void | size), size) = {\n" + " let a: (void | size) = void;\n" + " let b: (void | size) = void;\n" + " return (a, b, 9);\n" + "};\n" + "export fn main() i32 = {\n" + " let h = ph();\n" + " if (h.0 as size != 11) { return 1; };\n" + " if (h.4 != 4) { return 2; };\n" + " let m = pm();\n" + " if (m.0 != 1) { return 3; };\n" + " if (m.2 as size != 12) { return 4; };\n" + " if (m.4 != 4) { return 5; };\n" + " let t = pt();\n" + " if (t.3 != 4) { return 6; };\n" + " if (t.4 as size != 13) { return 7; };\n" + " let v = pv();\n" + " if (!(v.0 is void)) { return 8; };\n" + " if (!(v.1 is void)) { return 9; };\n" + " if (v.2 != 9) { return 10; };\n" + " let (x, y, z) = v;\n" + " if (!(x is void)) { return 11; };\n" + " if (!(y is void)) { return 12; };\n" + " if (z != 9) { return 13; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* str + tagged mixed over-cap (3+2+1+1 = 7 GP words): the wide + * AX/BX/CX header store and the tagged mem-to-mem box copy + * interleave through the same sret pointer. */ + { "b22_str_tagged_mix", + "package main;\n" + "fn pr() (str, (void | size), size, size) = {\n" + " let mid: (void | size) = 4: size;\n" + " return (\"hello\", mid, 6, 7);\n" + "};\n" + "export fn main() i32 = {\n" + " let t = pr();\n" + " if (t.0.len != 5) { return 1; };\n" + " if (t.1 as size != 4) { return 2; };\n" + " if (t.2 != 6) { return 3; };\n" + " if (t.3 != 7) { return 4; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* SSE-over-cap (3 floats > TUPLE_SSECAP) with a tagged element: + * the X0 float store and the tagged box copy ride independent + * walks of the same slot-laid buffer. */ + { "b22_sse_mix", + "package main;\n" + "fn pr() ((void | size), f64, f64, f64) = {\n" + " let a: (void | size) = 6: size;\n" + " return (a, 1.5, 2.5, 3.5);\n" + "};\n" + "export fn main() i32 = {\n" + " let t = pr();\n" + " if (t.0 as size != 6) { return 1; };\n" + " if (t.1 != 1.5) { return 2; };\n" + " if (t.2 != 2.5) { return 3; };\n" + " if (t.3 != 3.5) { return 4; };\n" + " let (a, x, y, z) = pr();\n" + " if (a as size != 6) { return 5; };\n" + " if (x != 1.5) { return 6; };\n" + " if (y != 2.5) { return 7; };\n" + " if (z != 3.5) { return 8; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* harec `_` skip in over-cap MASSIGN + MLET: the skipped element + * still advances foff by its slot (a tagged 16B box, not 8). */ + { "b22_underscore_skip", + "package main;\n" + "fn pr() ((void | size), (void | size), size) = {\n" + " let mn: (void | size) = 3: size;\n" + " let mx: (void | size) = 5: size;\n" + " return (mn, mx, 9);\n" + "};\n" + "export fn main() i32 = {\n" + " let a: (void | size) = void;\n" + " let n: size = 0;\n" + " a, _, n = pr();\n" + " if (a as size != 3) { return 1; };\n" + " if (n != 9) { return 2; };\n" + " let (q, _, r) = pr();\n" + " if (q as size != 3) { return 3; };\n" + " if (r != 9) { return 4; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* residual loud (rule 7): tagged element from a NON-IDENT source + * in an over-cap return — the cursor can't carry the box past the + * DX dest-base reload; #23/#40 follow-ups. */ + { "b22_reject_nonident_source", + "package main;\n" + "fn g() (void | size) = { return 5: size; };\n" + "fn pr() ((void | size), (void | size), size) = {\n" + " let mx: (void | size) = 2: size;\n" + " return (g(), mx, 7);\n" "};\n" "export fn main() i32 = {\n" " let t = pr();\n" " return t.2: i32;\n" "};\n", 0, K_BUILDERR, "#22b: tagged element in an over-cap (sret) tuple " - "return unwired" }, + "return from a non-ident or widening source unwired" }, + /* classify/emit agreement: the declared type srets (5 eightbytes) + * but the UNWIDENED payload literal counted 4 expr-shape words — + * pre-#22b the emit took the register path against an sret + * caller: built clean, ran garbage, BOTH stages, byte-id + * (gate-blind). The decision now rides the declared classifier; + * the widening store itself is the #23/#40 follow-up. */ + { "b22_reject_widen_skew", + "package main;\n" + "fn pr() ((void | size), (void | size), size) = {\n" + " let mx: (void | size) = 5: size;\n" + " return (1: size, mx, 9);\n" + "};\n" + "export fn main() i32 = {\n" + " let t = pr();\n" + " return t.2: i32;\n" + "};\n", 0, + K_BUILDERR, "#22b: tagged element in an over-cap (sret) tuple " + "return from a non-ident or widening source unwired" }, + /* #37-class bound this arc newly exposed: a >32B box (40B = + * (void|4x-u64-struct)) became transportable through the sret + * routes, but the t.N READ rides the 4-reg AX/DX/CX/R8 cursor — + * unbounded, cstage emitted INVALID ASM (tuple_rseq[4] OOB → w6a + * "unsupported MOVQ shape") while wwstage clamped k>=3 to R8 and + * silently dropped payload word 3. Now loud both stages; the + * mem-based box read is #37's fold. */ + { "b22_reject_big_box_read", + "package main;\n" + "type four = struct { a: u64, b: u64, c: u64, d: u64, };\n" + "fn g() (void | four) = {\n" + " let v: four = four{a=1u64, b=2u64, c=3u64, d=4u64};\n" + " return v;\n" + "};\n" + "fn pr() ((void | four), size) = {\n" + " let e: (void | four) = g();\n" + " return (e, 7);\n" + "};\n" + "export fn main() i32 = {\n" + " let t = pr();\n" + " if (!(t.0 is four)) { return 1; };\n" + " return 0;\n" + "};\n", 0, + K_BUILDERR, "tagged tuple element read exceeds the AX/DX/CX/R8 " + "box cursor (mem-based read is the #37 family" }, + /* ...but the >32B TRANSPORT itself (sret send mem-to-mem + MLET + * copy-out) is size-generic and correct — only the cursor read is + * bounded. is-checks on the destructured local read the tag from + * its own slot. */ + { "b22_big_box_mlet", + "package main;\n" + "type four = struct { a: u64, b: u64, c: u64, d: u64, };\n" + "fn g() (void | four) = {\n" + " let v: four = four{a=1u64, b=2u64, c=3u64, d=4u64};\n" + " return v;\n" + "};\n" + "fn pr() ((void | four), size) = {\n" + " let e: (void | four) = g();\n" + " return (e, 7);\n" + "};\n" + "export fn main() i32 = {\n" + " let e2 = g();\n" + " if (!(e2 is four)) { return 1; };\n" + " let (e, n) = pr();\n" + " if (n != 7) { return 3; };\n" + " if (!(e is four)) { return 4; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* ken K4 (/tmp/ken_22b_validation.md): declared-VOID-elem over-cap + * return — NOT checker-unreachable; at base 6699158 it BUILT and + * ran silently WRONG (the send's old wide?esz:8 advanced 8 for the + * void elem while every receive walks its 0-slot — misaligned by + * one word). The tuple_eslot 0-advance fixes a LIVE base silent + * miscompile, not just a latent skew. */ + { "b22_void_elem_k4", + "package main;\n" + "fn mk() (void, size, size, size, size, size) = {\n" + " return (void, 1, 2, 3, 4, 5);\n" + "};\n" + "export fn main() i32 = {\n" + " let (v, a, b, c, d, e) = mk();\n" + " if (a != 1) { return 1; };\n" + " if (b != 2) { return 2; };\n" + " if (c != 3) { return 3; };\n" + " if (d != 4) { return 4; };\n" + " if (e != 5) { return 5; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* ken K5 (/tmp/ken_22b_validation.md delta re-OK, reviewer-22b + * amendment): a 32B-EXACT box — (void|str) = tag + 24B header = 4 + * words — puts the t.N read cursor exactly AT the AX/DX/CX/R8 cap. + * Pins the k < eslot/8 bound from below (no off-by-one: the str + * header's third word rides R8); base louds the #22b unwired text. */ + { "b22_k5_box_at_cap", + "package main;\n" + "fn mk() ((void | str), size, size) = {\n" + " let m: (void | str) = \"boundary\";\n" + " return (m, 5, 6);\n" + "};\n" + "export fn main() i32 = {\n" + " let t = mk();\n" + " if (!(t.0 is str)) { return 1; };\n" + " match (t.0) {\n" + " case let s: str => {\n" + " if (s.len != 8) { return 2; };\n" + " };\n" + " case void => { return 9; };\n" + " };\n" + " if (t.1 != 5) { return 3; };\n" + " if (t.2 != 6) { return 4; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* reviewer-22b gap probe: tagged in BOTH positions of a 2-elem + * over-cap tuple — a 32B box + a 16B box (6 GP words), no scalar + * spacer between the two mem-to-mem copies. Base louds. */ + { "b22_both_tagged_2elem", + "package main;\n" + "fn pr() ((void | str), (void | size)) = {\n" + " let a: (void | str) = \"boundary\";\n" + " let b: (void | size) = 7: size;\n" + " return (a, b);\n" + "};\n" + "export fn main() i32 = {\n" + " let t = pr();\n" + " if (!(t.0 is str)) { return 1; };\n" + " match (t.0) {\n" + " case let s: str => { if (s.len != 8) { return 2; }; };\n" + " case void => { return 9; };\n" + " };\n" + " if (!(t.1 is size)) { return 3; };\n" + " if (t.1 as size != 7) { return 4; };\n" + " let (x, y) = pr();\n" + " if (!(x is str)) { return 5; };\n" + " if (y as size != 7) { return 6; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* reviewer-22b gap probe: CHAINED sret — a fn that receives an + * over-cap tagged tuple via sret (destructure to ident locals) and + * returns one through its own sret arg; the received locals are + * exactly the ident-only shapes the send arm admits. (Direct + * `return t` forwarding is the #39 acceptance divergence.) */ + { "b22_chained_sret", + "package main;\n" + "fn g() ((void | size), (void | size), size) = {\n" + " let mn: (void | size) = 3: size;\n" + " let mx: (void | size) = 5: size;\n" + " return (mn, mx, 9);\n" + "};\n" + "fn h() ((void | size), (void | size), size) = {\n" + " let (a, b, n) = g();\n" + " return (a, b, n + 1);\n" + "};\n" + "export fn main() i32 = {\n" + " let t = h();\n" + " if (t.0 as size != 3) { return 1; };\n" + " if (t.1 as size != 5) { return 2; };\n" + " if (t.2 != 10) { return 3; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* the let-twin of the skew: a VOID-bearing over-cap literal init + * slipped wwstage's expr-count loud in-cap and fell past every + * store arm to NOTHING (cs louded via the ken-R1 net, ww ran + * silent-wrong — acceptance divergence). wwstage now carries the + * same net. */ + { "b22_reject_let_void_slip", + "package main;\n" + "fn pr() ((void | size), (void | size), size) = {\n" + " let t: ((void | size), (void | size), size) = (void, void, 1);\n" + " return t;\n" + "};\n" + "export fn main() i32 = {\n" + " let t = pr();\n" + " return t.2: i32;\n" + "};\n", 0, + K_BUILDERR, "over-cap tuple initialiser from a non-call source " + "unwired" }, /* ken R1: an OVER-CAP tuple literal init (a tagged box >16B pushes * the shape past TUPLE_GPCAP) fell past every cstage N_LET store * arm to NOTHING — silent uninitialized-frame reads — while