w6c+w6c_ww: over-cap (sret) tuple returns carry tagged elements — MEMORY-class box copy (#22b)
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<eslot/8 cursor walk: cstage OOB-indexed tuple_rseq[4] (invalid asm), wwstage tupreg-clamped 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.
This commit is contained in:
117
cmd/w6c/cgen.c
117
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)
|
||||
|
||||
Reference in New Issue
Block a user