w6c+w6c_ww: >32B tagged INDEX/DOT reads go mem-based — box address in AX (#37)

A tagged box wider than the AX/DX/CX/R8 cursor (size > TUPLE_GPCAP*8)
read via N_INDEX or N_DOT now leaves its ADDRESS in AX — joining the
sret-call mem-based class the #38b gates already speak — and every
cursor consumer branches on one shared predicate (cg_tagged_memread /
taggedmemread) before reading AX as the tag. <=32B keeps the cursor
byte-for-byte (32B-at-cap asm proven identical to base on both the
t.N and INDEX routes).

Emitters: N_INDEX ident+fallback arms, N_DOT tuple-element (flips the
#22b loud bound), N_DOT struct-field + ptr-chained-field (were silent
clamps at 32B); wwstage twins gate cgloadtaggedfield at the helper
choke-point. Consumers: match spill x2 and the widen-store subset +
nested arms (the let/assign/return-widen/arg-widen/vararg choke-point)
copy the box from memory, then share the existing zero-pad + tag-remap
tail; `is` loads the tag through the address; `as` spills mem-based.
Rule-7 loud bounds replace silent cursor garbage for the non-mem-based
>32B kinds, `?`/`!`, and the 33-48B in-reg tagged arg push (mem-based
push stays the #35 family); the exact-type >32B return passthrough
from INDEX/DOT flips from its #38b loud into the widener route. The
pre-existing >48B memarg stack blit (cgplaceaddr) never used the
cursor and is pinned unchanged.

Reviewer-37 amendment: the non-mem-based >32B loud was ONE-SIDED on
two wwstage routes — cgwidentaggedstorebp had no fall-through guard
at all (`let w = *p` on a 56B box: cstage loud, wwstage silent word0
truncation), and cgmatch's guard keyed on matchscrutt-resolved
spillsz, which defaults under cap for kinds matchscrutt can't resolve
(N_UN deref et al), so `match (*p)` slipped it the same way. Both now
loud off the stamped src/scrut type_ (the kind-blind key cstage
already uses), restoring the rule-10 symmetry the body claims.

Emitters and consumers ship as ONE commit: they share the memread
contract, and splitting would open a transient window where a wired
emitter hands an address to an unwired consumer (silent garbage) —
the #61-precedent route-sharing fuse. The CX-global-tuple-base LEAQ
arm is TRIPWIRE wiring: a >32B tagged global-tuple element is
unreachable today (module-level tuple inits are int/str-literal-only;
tagged elements loud at the DATA emit), and the LEAQ keeps the same
base_reg generality as the cursor walk it replaces (ken note, task
record).

This was the last 5b compiler gate: `match insts[pc]` on the regex
inst union (inst_lit|inst_repeat, 56B) was silent-wrong gate-blind
byte-id on both stages (payload words 3+ dropped past the R8 clamp).
test 941 grows 165->200 checks: the #22b BUILDERR pin flips to a
runtime row, plus the 56B driver match, str+nested-tagged payload,
let/is/assign, indexed return, widening (identity and reversed-order
remap), 56B memarg, ken's X1 composition row, 32B-at-cap INDEX
boundary, the ptr-chained p.f match (BX-arm) and (*p)[i] fallback-arm
rows, and the two deref loud-symmetry BUILDERR pins. At base f272068
the 11 bug rows fail (2 BUILDERR flips + 7 silent-wrong + 2 missing-
loud pins, both drivers); the anchors pass. Oracle: ken PASS at
bf21964b pre-amendment; re-bind on the amended tree pending (source
bytes changed: cgenutil.ww/cgenexpr.ww louds + combined.ww regen).
This commit is contained in:
2026-06-05 03:41:34 +09:00
parent f272068940
commit 26d375410e
7 changed files with 1271 additions and 107 deletions

View File

@@ -648,6 +648,22 @@ tagged_memarg_size(Type *t)
return (int)t->size; return (int)t->size;
} }
/* cg_tagged_memread — #37: does cgexpr leave this tagged expr's box in
* MEMORY (AX = box address) instead of the AX/DX/CX/R8 cursor? True
* for an N_INDEX/N_DOT read whose box exceeds the 4-reg cursor — the
* same mem-based class as an sret-classified call (which the #38b
* gates key separately on cg_sret_retsize). Every cursor-spill
* consumer must branch on this before reading AX as the tag. */
static int
cg_tagged_memread(Node *e)
{
Type *u;
if (e == NULL || (e->kind != N_INDEX && e->kind != N_DOT)) return 0;
u = type_chase_named(e->type);
if (u == NULL || u->kind != TY_TAGGED) return 0;
return (int)u->size > TUPLE_GPCAP * 8;
}
/* type_isnullable — TY_TAGGED with the (*T | void) one-word fold. */ /* type_isnullable — TY_TAGGED with the (*T | void) one-word fold. */
static int static int
type_isnullable(Type *t) type_isnullable(Type *t)
@@ -2408,6 +2424,26 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
"cannot be cursor-widened into a " "cannot be cursor-widened into a "
"tagged slot (mem-to-mem widen " "tagged slot (mem-to-mem widen "
"unwired)"); "unwired)");
if (cg_tagged_memread(src)) {
/* #37: >32B box read — ADDRESS in
* AX; copy the inner box from memory
* into the payload area. */
cgexpr(c, src, *locals_p);
for (int k = 0; k < ssz; k += 8) {
ins2(c, A_MOVQ,
amem(D_AX, k),
areg(D_DX));
ins2(c, A_MOVQ, areg(D_DX),
amem(D_BP,
write_off + 8 + k));
}
} else {
/* #37 (rule 7): >32B from a non-mem-based
* kind would spill an unfilled cursor. */
if (ssz > TUPLE_GPCAP * 8)
fatal("#37: >32B tagged payload from "
"a non-mem-based source (kind %d) "
"unwired (rule 7)", src->kind);
cgexpr(c, src, *locals_p); cgexpr(c, src, *locals_p);
ins2(c, A_MOVQ, areg(D_AX), ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, write_off + 8)); amem(D_BP, write_off + 8));
@@ -2420,6 +2456,7 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
if (ssz > 24) if (ssz > 24)
ins2(c, A_MOVQ, areg(D_R8), ins2(c, A_MOVQ, areg(D_R8),
amem(D_BP, write_off + 32)); amem(D_BP, write_off + 32));
}
} }
ins2(c, A_MOVQ, aimm(nested), ins2(c, A_MOVQ, aimm(nested),
amem(D_BP, write_off + 0)); amem(D_BP, write_off + 0));
@@ -2434,6 +2471,17 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
ins2(c, A_MOVQ, areg(D_AX), ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, write_off + k)); amem(D_BP, write_off + k));
} }
} else if (cg_tagged_memread(src)) {
/* #37: >32B box read (insts[pc], t.N, s.f) —
* cgexpr left its ADDRESS in AX; copy the whole
* box from memory. Pad + remap below are shared
* with the ident path (both mem-based). */
cgexpr(c, src, *locals_p);
for (int k = 0; k < ssz; k += 8) {
ins2(c, A_MOVQ, amem(D_AX, k), areg(D_DX));
ins2(c, A_MOVQ, areg(D_DX),
amem(D_BP, write_off + k));
}
} else { } else {
/* Tagged source returned via the tagged-return ABI /* Tagged source returned via the tagged-return ABI
* (AX=tag, DX=word0, CX=word1, R8=word2). The unused * (AX=tag, DX=word0, CX=word1, R8=word2). The unused
@@ -2447,6 +2495,12 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
fatal("#40: sret-class call result cannot be " fatal("#40: sret-class call result cannot be "
"cursor-widened into a tagged slot " "cursor-widened into a tagged slot "
"(mem-to-mem widen unwired)"); "(mem-to-mem widen unwired)");
/* #37 (rule 7): >32B from a non-mem-based kind
* would spill an unfilled cursor. */
if (ssz > TUPLE_GPCAP * 8)
fatal("#37: >32B tagged source of a non-mem-"
"based kind (%d) unwired (rule 7)",
src->kind);
cgexpr(c, src, *locals_p); cgexpr(c, src, *locals_p);
ins2(c, A_MOVQ, areg(D_AX), ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, write_off + 0)); amem(D_BP, write_off + 0));
@@ -8550,6 +8604,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
fatal("#38b: >32B tagged call result " fatal("#38b: >32B tagged call result "
"as a call argument unwired " "as a call argument unwired "
"(#40-family follow-up)"); "(#40-family follow-up)");
/* #37 (rule 7): a 33-48B box from an
* INDEX/DOT read is mem-based (AX = addr,
* no cursor to push) — was silent cursor
* truncation pre-#37; the mem-based push
* is the #35 family. */
if (cg_tagged_memread(args[i]))
fatal("#37: >32B tagged arg from a "
"mem-based read unwired (#35/"
"#40-family follow-up)");
int sz = tagged_arg_size(args[i]->type); int sz = tagged_arg_size(args[i]->type);
if (sz > 24) if (sz > 24)
ins1(c, A_PUSHQ, areg(D_R8)); ins1(c, A_PUSHQ, areg(D_R8));
@@ -9008,6 +9071,19 @@ cgexpr(Cg *c, Node *n, Local *locals)
* dispatch fired on a stale slot. */ * dispatch fired on a stale slot. */
sl_off = localoff(c, &locals, "@match_spill", sl_off = localoff(c, &locals, "@match_spill",
slot_size, cg_frame); slot_size, cg_frame);
if (cg_tagged_memread(s)) {
/* #37: >32B box — cgexpr left its
* ADDRESS in AX; copy the whole box
* from memory (the cursor can't
* carry it). */
cgexpr(c, s, locals);
for (int k = 0; k < slot_size; k += 8) {
ins2(c, A_MOVQ, amem(D_AX, k),
areg(D_DX));
ins2(c, A_MOVQ, areg(D_DX),
amem(D_BP, sl_off + k));
}
} else {
cgexpr(c, s, locals); cgexpr(c, s, locals);
ins2(c, A_MOVQ, areg(D_AX), ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, sl_off + 0)); amem(D_BP, sl_off + 0));
@@ -9021,6 +9097,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_MOVQ, areg(D_R8), ins2(c, A_MOVQ, areg(D_R8),
amem(D_BP, sl_off + 24)); amem(D_BP, sl_off + 24));
} }
}
} }
} else { } else {
/* Spill non-ident scrutinees (e.g. `match (foo()?)`) into /* Spill non-ident scrutinees (e.g. `match (foo()?)`) into
@@ -9040,7 +9117,25 @@ cgexpr(Cg *c, Node *n, Local *locals)
cg_sret_dest_off = sl_off; cg_sret_dest_off = sl_off;
cgexpr(c, s, locals); cgexpr(c, s, locals);
cg_sret_dest_off = 0; cg_sret_dest_off = 0;
} else if (cg_tagged_memread(s)) {
/* #37: >32B box read (insts[pc], t.N) —
* cgexpr left its ADDRESS in AX; copy the
* whole box from memory. */
cgexpr(c, s, locals);
for (int k = 0; k < slot_size; k += 8) {
ins2(c, A_MOVQ, amem(D_AX, k),
areg(D_DX));
ins2(c, A_MOVQ, areg(D_DX),
amem(D_BP, sl_off + k));
}
} else { } else {
/* #37 (rule 7): a >32B box from a kind with no
* mem-read convention (cast, ...) would spill the
* cursor it never filled — loud, not garbage. */
if (!is_nullable && slot_size > TUPLE_GPCAP * 8)
fatal("#37: >32B tagged match scrutinee from "
"a non-mem-based source (kind %d) unwired "
"(rule 7)", s->kind);
cgexpr(c, s, locals); cgexpr(c, s, locals);
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sl_off + 0)); ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sl_off + 0));
if (!is_nullable) { if (!is_nullable) {
@@ -9183,6 +9278,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
fatal("#38b: `?` on an sret-class call result " fatal("#38b: `?` on an sret-class call result "
"unwired (mem-based unwrap is a #40-family " "unwired (mem-based unwrap is a #40-family "
"follow-up)"); "follow-up)");
/* #37 (rule 7): a >32B box read leaves AX = address, not
* the tag the unwrap below compares. */
if (cg_tagged_memread(n->lhs))
fatal("#37: `?` on a >32B mem-based tagged read "
"unwired (#40-family follow-up)");
if (cg_sret_retsize(cg_ret_type) > 0) if (cg_sret_retsize(cg_ret_type) > 0)
fatal("#38b: `?` propagation into a >32B tagged " fatal("#38b: `?` propagation into a >32B tagged "
"return unwired (sret error-propagate is a " "return unwired (sret error-propagate is a "
@@ -9273,6 +9373,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
fatal("#38b: `!` on an sret-class call result " fatal("#38b: `!` on an sret-class call result "
"unwired (mem-based unwrap is a #40-family " "unwired (mem-based unwrap is a #40-family "
"follow-up)"); "follow-up)");
/* #37 (rule 7): see the N_TRYPROP twin. */
if (cg_tagged_memread(n->lhs))
fatal("#37: `!` on a >32B mem-based tagged read "
"unwired (#40-family follow-up)");
cgexpr(c, n->lhs, locals); cgexpr(c, n->lhs, locals);
Type *u = n->lhs ? n->lhs->type : NULL; Type *u = n->lhs ? n->lhs->type : NULL;
if (u && u->kind == TY_NAMED) u = u->under; if (u && u->kind == TY_NAMED) u = u->under;
@@ -9332,6 +9436,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
fatal("#38b: `is` on an sret-class call result " fatal("#38b: `is` on an sret-class call result "
"unwired (#40-family follow-up)"); "unwired (#40-family follow-up)");
cgexpr(c, n->lhs, locals); cgexpr(c, n->lhs, locals);
/* #37: a >32B box read leaves its ADDRESS in AX — load
* the tag word from memory before the compare. */
if (cg_tagged_memread(n->lhs))
ins2(c, A_MOVQ, amem(D_AX, 0), areg(D_AX));
Type *u = n->lhs ? n->lhs->type : NULL; Type *u = n->lhs ? n->lhs->type : NULL;
if (u && u->kind == TY_NAMED) u = u->under; if (u && u->kind == TY_NAMED) u = u->under;
Type *vt = n->rhs ? n->rhs->type : NULL; Type *vt = n->rhs ? n->rhs->type : NULL;
@@ -9399,6 +9507,24 @@ cgexpr(Cg *c, Node *n, Local *locals)
if (sl_off == 0) { if (sl_off == 0) {
sl_off = localoff(c, &locals, "@asrt_spill", sl_off = localoff(c, &locals, "@asrt_spill",
slot_size, cg_frame); slot_size, cg_frame);
if (cg_tagged_memread(s)) {
/* #37: >32B box read — ADDRESS in AX; copy
* the whole box from memory. */
cgexpr(c, s, locals);
for (int k = 0; k < slot_size; k += 8) {
ins2(c, A_MOVQ, amem(D_AX, k),
areg(D_DX));
ins2(c, A_MOVQ, areg(D_DX),
amem(D_BP, sl_off + k));
}
} else {
/* #37 (rule 7): >32B from a non-mem-based kind
* would spill an unfilled cursor. */
if (!(u && u->kind == TY_TAGGED && u->nullable)
&& slot_size > TUPLE_GPCAP * 8)
fatal("#37: `as` on a >32B tagged value from "
"a non-mem-based source (kind %d) unwired "
"(rule 7)", s->kind);
cgexpr(c, s, locals); cgexpr(c, s, locals);
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sl_off + 0)); ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sl_off + 0));
if (!(u && u->kind == TY_TAGGED && u->nullable)) { if (!(u && u->kind == TY_TAGGED && u->nullable)) {
@@ -9408,6 +9534,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_MOVQ, areg(D_CX), ins2(c, A_MOVQ, areg(D_CX),
amem(D_BP, sl_off + 16)); amem(D_BP, sl_off + 16));
} }
}
} }
char *ok = mklabel(c, "asrt_ok"); char *ok = mklabel(c, "asrt_ok");
if (u && u->kind == TY_TAGGED && u->nullable) { if (u && u->kind == TY_TAGGED && u->nullable) {
@@ -10014,18 +10141,22 @@ cgexpr(Cg *c, Node *n, Local *locals)
* the ascending walk can't clobber the base. */ * the ascending walk can't clobber the base. */
if (fu && fu->kind == TY_TAGGED) { if (fu && fu->kind == TY_TAGGED) {
int eslot = tuple_eslot(tp->type); int eslot = tuple_eslot(tp->type);
/* #22b (rule 7): a >32B box overruns the /* #37: a >32B box overruns the 4-reg
* 4-reg cursor — pre-bound the walk * cursor — leave its ADDRESS in AX
* indexed past tuple_rseq (invalid asm) * (cg_tagged_memread, the sret-receive
* while wwstage clamped to R8 (silent * convention); consumers copy from
* payload drop). Reachable only since * memory. Replaces the #22b loud
* the over-cap sret send unwired; the * bound (pre-bound: cstage indexed
* mem-based box read is the #37 family. */ * past tuple_rseq = invalid asm,
if (eslot > TUPLE_GPCAP * 8) * wwstage clamped to R8 = silent
fatal("tagged tuple element read " * payload drop). */
"exceeds the AX/DX/CX/R8 box " if (eslot > TUPLE_GPCAP * 8) {
"cursor (mem-based read is the " ins2(c, A_LEAQ,
"#37 family; rule 7)"); amem(base_reg,
base_disp + foff),
areg(D_AX));
break;
}
for (int k = 0; k < eslot / 8; k++) for (int k = 0; k < eslot / 8; k++)
ins2(c, A_MOVQ, ins2(c, A_MOVQ,
amem(base_reg, amem(base_reg,
@@ -10074,6 +10205,17 @@ cgexpr(Cg *c, Node *n, Local *locals)
? f->type->under : f->type; ? f->type->under : f->type;
if (tag_fu && tag_fu->kind == TY_TAGGED) { if (tag_fu && tag_fu->kind == TY_TAGGED) {
int fo = base_disp + (int)f->offset; int fo = base_disp + (int)f->offset;
/* #37: >32B box — ADDRESS in AX (the
* cg_tagged_memread convention), not
* the cursor. */
if ((int)tag_fu->size
> TUPLE_GPCAP * 8) {
ins2(c, A_LEAQ,
amem(base_reg, fo),
areg(D_AX));
(void)is_global;
break;
}
ins2(c, A_MOVQ, ins2(c, A_MOVQ,
amem(base_reg, fo + 0), areg(D_AX)); amem(base_reg, fo + 0), areg(D_AX));
ins2(c, A_MOVQ, ins2(c, A_MOVQ,
@@ -10182,6 +10324,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
? f->type->under : f->type; ? f->type->under : f->type;
if (ptag_fu && ptag_fu->kind == TY_TAGGED) { if (ptag_fu && ptag_fu->kind == TY_TAGGED) {
int fo = (int)f->offset; int fo = (int)f->offset;
/* #37: >32B box — ADDRESS in
* AX, not the cursor. */
if ((int)ptag_fu->size
> TUPLE_GPCAP * 8) {
ins2(c, A_LEAQ,
amem(D_BX, fo),
areg(D_AX));
break;
}
ins2(c, A_MOVQ, ins2(c, A_MOVQ,
amem(D_BX, fo + 0), amem(D_BX, fo + 0),
areg(D_AX)); areg(D_AX));
@@ -10613,6 +10764,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
* pointer). */ * pointer). */
if (elem_tagged) { if (elem_tagged) {
int ssz = (int)esubu->size; int ssz = (int)esubu->size;
/* #37: >32B box — ADDRESS in AX (the
* cg_tagged_memread convention); the
* 4-reg cursor walk below would
* truncate past payload word 2. */
if (ssz > TUPLE_GPCAP * 8) {
ins2(c, A_MOVQ, areg(D_BX),
areg(D_AX));
break;
}
if (ssz > 24) if (ssz > 24)
ins2(c, A_MOVQ, amem(D_BX, 24), ins2(c, A_MOVQ, amem(D_BX, 24),
areg(D_R8)); areg(D_R8));
@@ -10681,6 +10841,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
* load slot words. */ * load slot words. */
if (elem_tagged) { if (elem_tagged) {
int ssz = (int)esubu->size; int ssz = (int)esubu->size;
/* #37: >32B box — AX already holds the element
* address; leave it (cg_tagged_memread). */
if (ssz > TUPLE_GPCAP * 8)
break;
ins2(c, A_MOVQ, areg(D_AX), areg(D_BX)); ins2(c, A_MOVQ, areg(D_AX), areg(D_BX));
if (ssz > 24) if (ssz > 24)
ins2(c, A_MOVQ, amem(D_BX, 24), areg(D_R8)); ins2(c, A_MOVQ, amem(D_BX, 24), areg(D_R8));
@@ -11574,17 +11738,8 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
* then return the dest pointer. */ * then return the dest pointer. */
if (cg_sret_retsize(rt) > 0) { if (cg_sret_retsize(rt) > 0) {
int sz = (int)rt->size; int sz = (int)rt->size;
if (passthrough) { if (passthrough
/* exact type but a cursor source && n->lhs->kind == N_CALL) {
* (N_INDEX/N_DOT) can't carry
* >32B — loud-stop (rule 7,
* #38b residual). */
if (n->lhs->kind != N_CALL)
fatal("#38b: >32B tagged "
"return from a cursor "
"source (kind %d) "
"unsupported",
n->lhs->kind);
cg_sret_forward = 1; cg_sret_forward = 1;
cgexpr(c, n->lhs, *locals); cgexpr(c, n->lhs, *locals);
ins2(c, A_MOVQ, ins2(c, A_MOVQ,
@@ -11592,7 +11747,12 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
areg(D_AX)); areg(D_AX));
} else if (istagged } else if (istagged
&& n->lhs->kind != N_IDENT && n->lhs->kind != N_IDENT
&& (int)vu->size > TUPLE_GPCAP * 8) { && (int)vu->size > TUPLE_GPCAP * 8
&& !cg_tagged_memread(n->lhs)) {
/* #37 wired the N_INDEX/N_DOT
* mem-read into the widener;
* the remaining >32B kinds
* stay loud. */
fatal("#40: widening tagged " fatal("#40: widening tagged "
"return-forward of a >32B " "return-forward of a >32B "
"source needs mem-to-mem " "source needs mem-to-mem "

View File

@@ -16910,6 +16910,15 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
if (arg.kind == nkind.N_INDEX || arg.kind == nkind.N_DOT) { if (arg.kind == nkind.N_INDEX || arg.kind == nkind.N_DOT) {
if (istaggedtype(c, arg)) { if (istaggedtype(c, arg)) {
let isz: i32 = slotsize(c, arg); let isz: i32 = slotsize(c, arg);
// #37 (rule 7): a 33-48B box from an INDEX/DOT read
// is mem-based (AX = addr, no cursor to push) — was
// silent cursor truncation pre-#37; the mem-based
// push is the #35 family. Mirrors cstage.
if (taggedmemread(c, arg)) {
let m37g: str = "#37: >32B tagged arg from a mem-based read unwired (#35/#40-family follow-up)\n";
os.write(2, m37g.ptr, m37g.len: u64);
os.exit(1);
};
if (isz > 24) { emitline("\tPUSHQ\tR8\n"); }; if (isz > 24) { emitline("\tPUSHQ\tR8\n"); };
if (isz > 16) { emitline("\tPUSHQ\tCX\n"); }; if (isz > 16) { emitline("\tPUSHQ\tCX\n"); };
if (isz > 8) { emitline("\tPUSHQ\tDX\n"); }; if (isz > 8) { emitline("\tPUSHQ\tDX\n"); };
@@ -19033,6 +19042,23 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
return false; return false;
}; };
// taggedmemread — #37: does cgexpr leave this tagged expr's box in
// MEMORY (AX = box address) instead of the AX/DX/CX/R8 cursor? True
// for an N_INDEX/N_DOT read whose box exceeds the 4-reg cursor — the
// same mem-based class as an sret-classified call (which the #38b
// gates key separately on callsretsize). Every cursor-spill consumer
// must branch on this before reading AX as the tag. Mirrors cstage
// cg_tagged_memread.
fn taggedmemread(c: *cgen, e: *node) bool = {
if (e == nil) { return false; };
if (e.kind != nkind.N_INDEX && e.kind != nkind.N_DOT) { return false; };
let u: *tinfo = e.type_: *tinfo;
for (u != nil && u.kind == tykind.TY_NAMED) { u = u.under; };
if (u == nil) { return false; };
if (u.kind != tykind.TY_TAGGED) { return false; };
return u.size: i32 > TUPLE_GPCAP * 8;
};
// cgloadtaggedfield — load a tagged-union slot at `basereg`+foff // cgloadtaggedfield — load a tagged-union slot at `basereg`+foff
// into the tagged-return ABI registers (AX=tag, DX=word0, CX=word1, // into the tagged-return ABI registers (AX=tag, DX=word0, CX=word1,
// R8=word2). Slot sizes: 16B = (tag, word0), 24B = + word1, 32B // R8=word2). Slot sizes: 16B = (tag, word0), 24B = + word1, 32B
@@ -19048,6 +19074,16 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
// only register loaded into that is NOT a target is BX, so AX- // only register loaded into that is NOT a target is BX, so AX-
// or DX-rooted callers must spill first. // or DX-rooted callers must spill first.
fn cgloadtaggedfield(c: *cgen, basereg: str, foff: i32, slot_sz: i32) void = { fn cgloadtaggedfield(c: *cgen, basereg: str, foff: i32, slot_sz: i32) void = {
// #37: >32B box — leave its ADDRESS in AX (taggedmemread, the
// sret-receive convention); the 4-reg cursor walk below would
// truncate past payload word 2. Mirrors cstage's N_DOT
// TY_STRUCT/TY_PTR tagged arms.
if (slot_sz > TUPLE_GPCAP * 8) {
emitline("\tLEAQ\t");
emitdispreg(foff: i64, basereg);
emitline(", AX\n");
return;
};
// tag → AX // tag → AX
emitline("\tMOVQ\t"); emitline("\tMOVQ\t");
emitdispreg(foff: i64, basereg); emitdispreg(foff: i64, basereg);
@@ -19291,6 +19327,29 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
os.exit(1); os.exit(1);
}; };
}; };
if (taggedmemread(c, src)) {
// #37: >32B box read — ADDRESS in AX;
// copy the inner box from memory into
// the payload area. Mirrors cstage.
cgexpr(c, src);
let mk: i32 = 0;
for (mk < ssz) {
emitline("\tMOVQ\t");
emitdispreg(mk: i64, "AX");
emitline(", DX\n");
emitline("\tMOVQ\tDX, ");
emitoff((slot_off + 8 + mk): i64);
emitline("(BP)\n");
mk += 8;
};
} else {
// #37 (rule 7): >32B from a non-mem-based
// kind would spill an unfilled cursor.
if (ssz > TUPLE_GPCAP * 8) {
let m37a: str = "#37: >32B tagged payload from a non-mem-based source unwired (rule 7)\n";
os.write(2, m37a.ptr, m37a.len: u64);
os.exit(1);
};
cgexpr(c, src); cgexpr(c, src);
emitline("\tMOVQ\tAX, "); emitline("\tMOVQ\tAX, ");
emitoff((slot_off + 8): i64); emitoff((slot_off + 8): i64);
@@ -19310,6 +19369,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
emitoff((slot_off + 32): i64); emitoff((slot_off + 32): i64);
emitline("(BP)\n"); emitline("(BP)\n");
}; };
};
}; };
emitline("\tMOVQ\t$"); emitline("\tMOVQ\t$");
emitint(nested: i64); emitint(nested: i64);
@@ -19363,6 +19423,40 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
os.exit(1); os.exit(1);
}; };
}; };
// #37: >32B box read (insts[pc], t.N, s.f) — cgexpr left
// its ADDRESS in AX; copy the whole box from memory, pad,
// tag-remap — the mem-based twin of the ident arm above.
// Mirrors cstage cg_widen_tagged_store's memread arm.
if (taggedmemread(c, src)) {
let su37: *tinfo = src.type_: *tinfo;
for (su37 != nil && su37.kind == tykind.TY_NAMED) {
su37 = su37.under;
};
let ssz37: i32 = su37.size: i32;
cgexpr(c, src);
let mk37: i32 = 0;
for (mk37 < ssz37) {
emitline("\tMOVQ\t");
emitdispreg(mk37: i64, "AX");
emitline(", DX\n");
emitline("\tMOVQ\tDX, ");
emitoff((slot_off + mk37): i64);
emitline("(BP)\n");
mk37 += 8;
};
if (ssz37 < slot_sz) {
emitline("\tXORQ\tAX, AX\n");
let pp37: i32 = ssz37;
for (pp37 < slot_sz) {
emitline("\tMOVQ\tAX, ");
emitoff((slot_off + pp37): i64);
emitline("(BP)\n");
pp37 += 8;
};
};
cgwidentagremap(c, dt, src.type_: *tinfo, slot_off);
return;
};
cgexpr(c, src); cgexpr(c, src);
emitline("\tMOVQ\tAX, "); emitline("\tMOVQ\tAX, ");
emitoff(slot_off: i64); emitoff(slot_off: i64);
@@ -19384,6 +19478,23 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
}; };
return; return;
}; };
// #37 (rule 7): a >32B TAGGED source of a kind the resolver arms
// above don't carry (deref/cast/unwrap/...) would fall to the
// scalar word0 arm below and silently truncate — keyed on the
// stamped src.type_ (kind-blind), the twin of cstage
// cg_widen_tagged_store's generic-else bound. Surfaced by
// reviewer-37's `let w = *p` probe on a 56B box: cstage loud,
// wwstage silent (rule-10 break).
let sf37: *tinfo = src.type_: *tinfo;
for (sf37 != nil && sf37.kind == tykind.TY_NAMED) {
sf37 = sf37.under;
};
if (sf37 != nil && sf37.kind == tykind.TY_TAGGED
&& sf37.size: i32 > TUPLE_GPCAP * 8) {
let m37f: str = "#37: >32B tagged source of a non-mem-based kind unwired (rule 7)\n";
os.write(2, m37f.ptr, m37f.len: u64);
os.exit(1);
};
// #242: tuple payload. Each element rides ONE register-ABI // #242: tuple payload. Each element rides ONE register-ABI
// eightbyte — scalar/float a single 8B word, a slice/str its 3-word // eightbyte — scalar/float a single 8B word, a slice/str its 3-word
// {ptr,len,cap} header (24B) — matching the tagged-return load // {ptr,len,cap} header (24B) — matching the tagged-return load
@@ -20668,6 +20779,13 @@ fn cgtryprop(c: *cgen, n: *node) void = {
}; };
}; };
}; };
// #37 (rule 7): a >32B box read leaves AX = address, not the
// tag the unwrap below compares. Mirrors cstage.
if (taggedmemread(c, n.lhs)) {
let m37p: str = "#37: `?` on a >32B mem-based tagged read unwired (#40-family follow-up)\n";
os.write(2, m37p.ptr, m37p.len: u64);
os.exit(1);
};
if (sretretsize(c, c.fnret) > 0) { if (sretretsize(c, c.fnret) > 0) {
let m38q: str = "#38b: `?` propagation into a >32B tagged return unwired (sret error-propagate is a #40-family follow-up)\n"; let m38q: str = "#38b: `?` propagation into a >32B tagged return unwired (sret error-propagate is a #40-family follow-up)\n";
os.write(2, m38q.ptr, m38q.len: u64); os.write(2, m38q.ptr, m38q.len: u64);
@@ -20801,6 +20919,12 @@ fn cgtryunw(c: *cgen, n: *node) void = {
}; };
}; };
}; };
// #37 (rule 7): see the cgtryprop twin.
if (taggedmemread(c, n.lhs)) {
let m37u: str = "#37: `!` on a >32B mem-based tagged read unwired (#40-family follow-up)\n";
os.write(2, m37u.ptr, m37u.len: u64);
os.exit(1);
};
cgexpr(c, n.lhs); cgexpr(c, n.lhs);
let cl: str = mklabel(c, "tryunw_ok"); let cl: str = mklabel(c, "tryunw_ok");
emitline("\tCMPQ\t$0, AX\n"); emitline("\tCMPQ\t$0, AX\n");
@@ -20905,6 +21029,12 @@ fn cgtypetest(c: *cgen, n: *node) void = {
// tag read landed on (BP) — the saved-BP word. // tag read landed on (BP) — the saved-BP word.
nonident = true; nonident = true;
cgexpr(c, lhs); cgexpr(c, lhs);
// #37: a >32B box read leaves its ADDRESS in AX —
// load the tag word from memory before the compare.
// Mirrors cstage N_TYPETEST.
if (taggedmemread(c, lhs)) {
emitline("\tMOVQ\t(AX), AX\n");
};
}; };
}; };
let want: i32 = 0; let want: i32 = 0;
@@ -21053,6 +21183,28 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
scrutt = matchscrutt(c, lhs); scrutt = matchscrutt(c, lhs);
let spillsz: i32 = matchspillsz(c, scrutt); let spillsz: i32 = matchspillsz(c, scrutt);
scrutoff = localalloc(c, "@asrt_spill", spillsz, nil); scrutoff = localalloc(c, "@asrt_spill", spillsz, nil);
if (taggedmemread(c, lhs)) {
// #37: >32B box read — ADDRESS in AX; copy the
// whole box from memory. Mirrors cstage.
cgexpr(c, lhs);
let ak37: i32 = 0;
for (ak37 < spillsz) {
emitline("\tMOVQ\t");
emitdispreg(ak37: i64, "AX");
emitline(", DX\n");
emitline("\tMOVQ\tDX, ");
emitoff((scrutoff + ak37): i64);
emitline("(BP)\n");
ak37 += 8;
};
} else {
// #37 (rule 7): >32B from a non-mem-based kind would
// spill an unfilled cursor. Mirrors cstage.
if (!isnullabletype(scrutt) && spillsz > TUPLE_GPCAP * 8) {
let m37s: str = "#37: `as` on a >32B tagged value from a non-mem-based source unwired (rule 7)\n";
os.write(2, m37s.ptr, m37s.len: u64);
os.exit(1);
};
cgexpr(c, lhs); cgexpr(c, lhs);
emitline("\tMOVQ\tAX, "); emitline("\tMOVQ\tAX, ");
emitoff(scrutoff: i64); emitoff(scrutoff: i64);
@@ -21074,6 +21226,7 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
emitline("(BP)\n"); emitline("(BP)\n");
}; };
}; };
};
}; };
}; };
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs); let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
@@ -22150,6 +22303,13 @@ fn cgindex(c: *cgen, n: *node) void = {
return; return;
}; };
if (elem_tagged) { if (elem_tagged) {
// #37: >32B box — ADDRESS in AX (the taggedmemread
// convention); the 4-reg cursor walk below would
// truncate past payload word 2. Mirrors cstage.
if (elem_slot_sz > TUPLE_GPCAP * 8) {
emitline("\tMOVQ\tBX, AX\n");
return;
};
if (elem_slot_sz > 24) { if (elem_slot_sz > 24) {
emitline("\tMOVQ\t24(BX), R8\n"); emitline("\tMOVQ\t24(BX), R8\n");
}; };
@@ -22209,6 +22369,13 @@ fn cgindex(c: *cgen, n: *node) void = {
return; return;
}; };
if (elem_tagged) { if (elem_tagged) {
// #37: >32B box — ADDRESS in AX (the taggedmemread
// convention); the 4-reg cursor walk below would
// truncate past payload word 2. Mirrors cstage.
if (elem_slot_sz > TUPLE_GPCAP * 8) {
emitline("\tMOVQ\tBX, AX\n");
return;
};
if (elem_slot_sz > 24) { if (elem_slot_sz > 24) {
emitline("\tMOVQ\t24(BX), R8\n"); emitline("\tMOVQ\t24(BX), R8\n");
}; };
@@ -22264,6 +22431,11 @@ fn cgindex(c: *cgen, n: *node) void = {
// arms have it) — a >24B-slot element via a non-ident base // arms have it) — a >24B-slot element via a non-ident base
// under-read the cursor and the match spill stored stale R8. // under-read the cursor and the match spill stored stale R8.
// Mirrors cstage cgen.c:9106-9117. // Mirrors cstage cgen.c:9106-9117.
// #37: >32B box — AX already holds the element address;
// leave it (taggedmemread). Mirrors cstage.
if (elem_slot_sz > TUPLE_GPCAP * 8) {
return;
};
emitline("\tMOVQ\tAX, BX\n"); emitline("\tMOVQ\tAX, BX\n");
if (elem_slot_sz > 24) { if (elem_slot_sz > 24) {
emitline("\tMOVQ\t24(BX), R8\n"); emitline("\tMOVQ\t24(BX), R8\n");
@@ -22653,7 +22825,47 @@ fn cgmatch(c: *cgen, n: *node) void = {
c.sretdestoff = scrutoff; c.sretdestoff = scrutoff;
cgexpr(c, scrut); cgexpr(c, scrut);
c.sretdestoff = 0; c.sretdestoff = 0;
} else { if (taggedmemread(c, scrut)) {
// #37: >32B box read (insts[pc], t.N) — cgexpr left
// its ADDRESS in AX; copy the whole box from memory.
// Mirrors cstage cgmatch.
cgexpr(c, scrut);
let mk37: i32 = 0;
for (mk37 < spillsz) {
emitline("\tMOVQ\t");
emitdispreg(mk37: i64, "AX");
emitline(", DX\n");
emitline("\tMOVQ\tDX, ");
emitoff((scrutoff + mk37): i64);
emitline("(BP)\n");
mk37 += 8;
};
} else { } else {
// #37 (rule 7): a >32B box from a kind with no mem-read
// convention would spill the cursor it never filled —
// loud, not garbage. Mirrors cstage.
if (!isnullabletype(scrutt) && spillsz > TUPLE_GPCAP * 8) {
let m37m: str = "#37: >32B tagged match scrutinee from a non-mem-based source unwired (rule 7)\n";
os.write(2, m37m.ptr, m37m.len: u64);
os.exit(1);
};
// #37 (rule 7) stamped twin: matchscrutt returns nil
// for kinds it can't resolve (deref/cast/...), so
// spillsz defaults under cap and the guard above is
// blind there. cstage sizes the spill from the
// stamped s->type, so it louds — key on scrut.type_
// to match. Surfaced by reviewer-37's `match (*p)`
// probe on a 56B box.
let ms37: *tinfo = scrut.type_: *tinfo;
for (ms37 != nil && ms37.kind == tykind.TY_NAMED) {
ms37 = ms37.under;
};
if (ms37 != nil && ms37.kind == tykind.TY_TAGGED
&& ms37.size: i32 > TUPLE_GPCAP * 8) {
let m37n: str = "#37: >32B tagged match scrutinee from a non-mem-based source unwired (rule 7)\n";
os.write(2, m37n.ptr, m37n.len: u64);
os.exit(1);
};
cgexpr(c, scrut); cgexpr(c, scrut);
emitline("\tMOVQ\tAX, "); emitline("\tMOVQ\tAX, ");
emitoff(scrutoff: i64); emitoff(scrutoff: i64);
@@ -22678,7 +22890,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
emitline("(BP)\n"); emitline("(BP)\n");
}; };
}; };
}; }; };
}; };
}; };
let endl: str = mklabel(c, "match_end"); let endl: str = mklabel(c, "match_end");
@@ -23170,19 +23382,18 @@ fn cgdot(c: *cgen, n: *node) void = {
// tagged arm. // tagged arm.
if (istaggedtype(c, tpt)) { if (istaggedtype(c, tpt)) {
let eslot: i32 = tupeslotn(tpt); let eslot: i32 = tupeslotn(tpt);
// #22b (rule 7): a >32B box // #37: a >32B box overruns the
// overruns the 4-reg cursor — // 4-reg cursor — leave its
// pre-bound tupreg clamped k>=3 // ADDRESS in AX (taggedmemread,
// to R8 (silent payload drop) // the sret-receive convention);
// while cstage emitted invalid // consumers copy from memory.
// asm. Reachable only since the // Replaces the #22b loud bound.
// over-cap sret send unwired; // Mirrors cstage.
// the mem-based box read is the
// #37 family. Mirrors cstage.
if (eslot > TUPLE_GPCAP * 8) { 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"; emitline("\tLEAQ\t");
os.write(2, m37.ptr, m37.len: u64); emitoff((lc.off + foff): i64);
os.exit(1); emitline("(BP), AX\n");
return;
}; };
let k: i32 = 0; let k: i32 = 0;
for (k < eslot / 8) { for (k < eslot / 8) {
@@ -31860,16 +32071,11 @@ fn cgreturn(c: *cgen, n: *node) void = {
// cgwidentaggedstore's non-BP base. // cgwidentaggedstore's non-BP base.
if (sretretsize(c, c.fnret) > 0) { if (sretretsize(c, c.fnret) > 0) {
let sa38v: i32 = localfind(c, "@sretarg"); let sa38v: i32 = localfind(c, "@sretarg");
if (forwardtagged) { if (forwardtagged && rhs.kind == nkind.N_CALL) {
// exact type, but only an N_CALL source // exact-type N_CALL forward: inner sret's
// sret's into outer's dest; a cursor // into outer's dest; an N_INDEX/N_DOT
// source (N_INDEX/N_DOT) can't carry // source routes through the widener's
// >32B (rule 7, #38b residual). // #37 mem-read arm below instead.
if (rhs.kind != nkind.N_CALL) {
let m38d: str = "#38b: >32B tagged return from a cursor source (N_INDEX/N_DOT) unsupported\n";
os.write(2, m38d.ptr, m38d.len: u64);
os.exit(1);
};
c.sretforward = 1; c.sretforward = 1;
cgexpr(c, rhs); cgexpr(c, rhs);
emitline("\tMOVQ\t"); emitline("\tMOVQ\t");
@@ -31886,9 +32092,13 @@ fn cgreturn(c: *cgen, n: *node) void = {
ru38 = ru38.under; ru38 = ru38.under;
}; };
if (ru38 != nil) { if (ru38 != nil) {
// #37 wired the N_INDEX/N_DOT mem-read into
// the widener; the remaining >32B kinds stay
// loud.
if (ru38.kind == tykind.TY_TAGGED if (ru38.kind == tykind.TY_TAGGED
&& rhs.kind != nkind.N_IDENT && rhs.kind != nkind.N_IDENT
&& ru38.size: i32 > TUPLE_GPCAP * 8) { && ru38.size: i32 > TUPLE_GPCAP * 8
&& !taggedmemread(c, rhs)) {
let m38e: str = "#40: widening tagged return-forward of a >32B source needs mem-to-mem tag-remap (unwired)\n"; let m38e: str = "#40: widening tagged return-forward of a >32B source needs mem-to-mem tag-remap (unwired)\n";
os.write(2, m38e.ptr, m38e.len: u64); os.write(2, m38e.ptr, m38e.len: u64);
os.exit(1); os.exit(1);

View File

@@ -201,6 +201,13 @@ fn cgtryprop(c: *cgen, n: *node) void = {
}; };
}; };
}; };
// #37 (rule 7): a >32B box read leaves AX = address, not the
// tag the unwrap below compares. Mirrors cstage.
if (taggedmemread(c, n.lhs)) {
let m37p: str = "#37: `?` on a >32B mem-based tagged read unwired (#40-family follow-up)\n";
os.write(2, m37p.ptr, m37p.len: u64);
os.exit(1);
};
if (sretretsize(c, c.fnret) > 0) { if (sretretsize(c, c.fnret) > 0) {
let m38q: str = "#38b: `?` propagation into a >32B tagged return unwired (sret error-propagate is a #40-family follow-up)\n"; let m38q: str = "#38b: `?` propagation into a >32B tagged return unwired (sret error-propagate is a #40-family follow-up)\n";
os.write(2, m38q.ptr, m38q.len: u64); os.write(2, m38q.ptr, m38q.len: u64);
@@ -334,6 +341,12 @@ fn cgtryunw(c: *cgen, n: *node) void = {
}; };
}; };
}; };
// #37 (rule 7): see the cgtryprop twin.
if (taggedmemread(c, n.lhs)) {
let m37u: str = "#37: `!` on a >32B mem-based tagged read unwired (#40-family follow-up)\n";
os.write(2, m37u.ptr, m37u.len: u64);
os.exit(1);
};
cgexpr(c, n.lhs); cgexpr(c, n.lhs);
let cl: str = mklabel(c, "tryunw_ok"); let cl: str = mklabel(c, "tryunw_ok");
emitline("\tCMPQ\t$0, AX\n"); emitline("\tCMPQ\t$0, AX\n");
@@ -438,6 +451,12 @@ fn cgtypetest(c: *cgen, n: *node) void = {
// tag read landed on (BP) — the saved-BP word. // tag read landed on (BP) — the saved-BP word.
nonident = true; nonident = true;
cgexpr(c, lhs); cgexpr(c, lhs);
// #37: a >32B box read leaves its ADDRESS in AX —
// load the tag word from memory before the compare.
// Mirrors cstage N_TYPETEST.
if (taggedmemread(c, lhs)) {
emitline("\tMOVQ\t(AX), AX\n");
};
}; };
}; };
let want: i32 = 0; let want: i32 = 0;
@@ -586,6 +605,28 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
scrutt = matchscrutt(c, lhs); scrutt = matchscrutt(c, lhs);
let spillsz: i32 = matchspillsz(c, scrutt); let spillsz: i32 = matchspillsz(c, scrutt);
scrutoff = localalloc(c, "@asrt_spill", spillsz, nil); scrutoff = localalloc(c, "@asrt_spill", spillsz, nil);
if (taggedmemread(c, lhs)) {
// #37: >32B box read — ADDRESS in AX; copy the
// whole box from memory. Mirrors cstage.
cgexpr(c, lhs);
let ak37: i32 = 0;
for (ak37 < spillsz) {
emitline("\tMOVQ\t");
emitdispreg(ak37: i64, "AX");
emitline(", DX\n");
emitline("\tMOVQ\tDX, ");
emitoff((scrutoff + ak37): i64);
emitline("(BP)\n");
ak37 += 8;
};
} else {
// #37 (rule 7): >32B from a non-mem-based kind would
// spill an unfilled cursor. Mirrors cstage.
if (!isnullabletype(scrutt) && spillsz > TUPLE_GPCAP * 8) {
let m37s: str = "#37: `as` on a >32B tagged value from a non-mem-based source unwired (rule 7)\n";
os.write(2, m37s.ptr, m37s.len: u64);
os.exit(1);
};
cgexpr(c, lhs); cgexpr(c, lhs);
emitline("\tMOVQ\tAX, "); emitline("\tMOVQ\tAX, ");
emitoff(scrutoff: i64); emitoff(scrutoff: i64);
@@ -607,6 +648,7 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
emitline("(BP)\n"); emitline("(BP)\n");
}; };
}; };
};
}; };
}; };
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs); let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
@@ -1683,6 +1725,13 @@ fn cgindex(c: *cgen, n: *node) void = {
return; return;
}; };
if (elem_tagged) { if (elem_tagged) {
// #37: >32B box — ADDRESS in AX (the taggedmemread
// convention); the 4-reg cursor walk below would
// truncate past payload word 2. Mirrors cstage.
if (elem_slot_sz > TUPLE_GPCAP * 8) {
emitline("\tMOVQ\tBX, AX\n");
return;
};
if (elem_slot_sz > 24) { if (elem_slot_sz > 24) {
emitline("\tMOVQ\t24(BX), R8\n"); emitline("\tMOVQ\t24(BX), R8\n");
}; };
@@ -1742,6 +1791,13 @@ fn cgindex(c: *cgen, n: *node) void = {
return; return;
}; };
if (elem_tagged) { if (elem_tagged) {
// #37: >32B box — ADDRESS in AX (the taggedmemread
// convention); the 4-reg cursor walk below would
// truncate past payload word 2. Mirrors cstage.
if (elem_slot_sz > TUPLE_GPCAP * 8) {
emitline("\tMOVQ\tBX, AX\n");
return;
};
if (elem_slot_sz > 24) { if (elem_slot_sz > 24) {
emitline("\tMOVQ\t24(BX), R8\n"); emitline("\tMOVQ\t24(BX), R8\n");
}; };
@@ -1797,6 +1853,11 @@ fn cgindex(c: *cgen, n: *node) void = {
// arms have it) — a >24B-slot element via a non-ident base // arms have it) — a >24B-slot element via a non-ident base
// under-read the cursor and the match spill stored stale R8. // under-read the cursor and the match spill stored stale R8.
// Mirrors cstage cgen.c:9106-9117. // Mirrors cstage cgen.c:9106-9117.
// #37: >32B box — AX already holds the element address;
// leave it (taggedmemread). Mirrors cstage.
if (elem_slot_sz > TUPLE_GPCAP * 8) {
return;
};
emitline("\tMOVQ\tAX, BX\n"); emitline("\tMOVQ\tAX, BX\n");
if (elem_slot_sz > 24) { if (elem_slot_sz > 24) {
emitline("\tMOVQ\t24(BX), R8\n"); emitline("\tMOVQ\t24(BX), R8\n");
@@ -2186,7 +2247,47 @@ fn cgmatch(c: *cgen, n: *node) void = {
c.sretdestoff = scrutoff; c.sretdestoff = scrutoff;
cgexpr(c, scrut); cgexpr(c, scrut);
c.sretdestoff = 0; c.sretdestoff = 0;
} else { if (taggedmemread(c, scrut)) {
// #37: >32B box read (insts[pc], t.N) — cgexpr left
// its ADDRESS in AX; copy the whole box from memory.
// Mirrors cstage cgmatch.
cgexpr(c, scrut);
let mk37: i32 = 0;
for (mk37 < spillsz) {
emitline("\tMOVQ\t");
emitdispreg(mk37: i64, "AX");
emitline(", DX\n");
emitline("\tMOVQ\tDX, ");
emitoff((scrutoff + mk37): i64);
emitline("(BP)\n");
mk37 += 8;
};
} else { } else {
// #37 (rule 7): a >32B box from a kind with no mem-read
// convention would spill the cursor it never filled —
// loud, not garbage. Mirrors cstage.
if (!isnullabletype(scrutt) && spillsz > TUPLE_GPCAP * 8) {
let m37m: str = "#37: >32B tagged match scrutinee from a non-mem-based source unwired (rule 7)\n";
os.write(2, m37m.ptr, m37m.len: u64);
os.exit(1);
};
// #37 (rule 7) stamped twin: matchscrutt returns nil
// for kinds it can't resolve (deref/cast/...), so
// spillsz defaults under cap and the guard above is
// blind there. cstage sizes the spill from the
// stamped s->type, so it louds — key on scrut.type_
// to match. Surfaced by reviewer-37's `match (*p)`
// probe on a 56B box.
let ms37: *tinfo = scrut.type_: *tinfo;
for (ms37 != nil && ms37.kind == tykind.TY_NAMED) {
ms37 = ms37.under;
};
if (ms37 != nil && ms37.kind == tykind.TY_TAGGED
&& ms37.size: i32 > TUPLE_GPCAP * 8) {
let m37n: str = "#37: >32B tagged match scrutinee from a non-mem-based source unwired (rule 7)\n";
os.write(2, m37n.ptr, m37n.len: u64);
os.exit(1);
};
cgexpr(c, scrut); cgexpr(c, scrut);
emitline("\tMOVQ\tAX, "); emitline("\tMOVQ\tAX, ");
emitoff(scrutoff: i64); emitoff(scrutoff: i64);
@@ -2211,7 +2312,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
emitline("(BP)\n"); emitline("(BP)\n");
}; };
}; };
}; }; };
}; };
}; };
let endl: str = mklabel(c, "match_end"); let endl: str = mklabel(c, "match_end");
@@ -2703,19 +2804,18 @@ fn cgdot(c: *cgen, n: *node) void = {
// tagged arm. // tagged arm.
if (istaggedtype(c, tpt)) { if (istaggedtype(c, tpt)) {
let eslot: i32 = tupeslotn(tpt); let eslot: i32 = tupeslotn(tpt);
// #22b (rule 7): a >32B box // #37: a >32B box overruns the
// overruns the 4-reg cursor — // 4-reg cursor — leave its
// pre-bound tupreg clamped k>=3 // ADDRESS in AX (taggedmemread,
// to R8 (silent payload drop) // the sret-receive convention);
// while cstage emitted invalid // consumers copy from memory.
// asm. Reachable only since the // Replaces the #22b loud bound.
// over-cap sret send unwired; // Mirrors cstage.
// the mem-based box read is the
// #37 family. Mirrors cstage.
if (eslot > TUPLE_GPCAP * 8) { 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"; emitline("\tLEAQ\t");
os.write(2, m37.ptr, m37.len: u64); emitoff((lc.off + foff): i64);
os.exit(1); emitline("(BP), AX\n");
return;
}; };
let k: i32 = 0; let k: i32 = 0;
for (k < eslot / 8) { for (k < eslot / 8) {

View File

@@ -851,16 +851,11 @@ fn cgreturn(c: *cgen, n: *node) void = {
// cgwidentaggedstore's non-BP base. // cgwidentaggedstore's non-BP base.
if (sretretsize(c, c.fnret) > 0) { if (sretretsize(c, c.fnret) > 0) {
let sa38v: i32 = localfind(c, "@sretarg"); let sa38v: i32 = localfind(c, "@sretarg");
if (forwardtagged) { if (forwardtagged && rhs.kind == nkind.N_CALL) {
// exact type, but only an N_CALL source // exact-type N_CALL forward: inner sret's
// sret's into outer's dest; a cursor // into outer's dest; an N_INDEX/N_DOT
// source (N_INDEX/N_DOT) can't carry // source routes through the widener's
// >32B (rule 7, #38b residual). // #37 mem-read arm below instead.
if (rhs.kind != nkind.N_CALL) {
let m38d: str = "#38b: >32B tagged return from a cursor source (N_INDEX/N_DOT) unsupported\n";
os.write(2, m38d.ptr, m38d.len: u64);
os.exit(1);
};
c.sretforward = 1; c.sretforward = 1;
cgexpr(c, rhs); cgexpr(c, rhs);
emitline("\tMOVQ\t"); emitline("\tMOVQ\t");
@@ -877,9 +872,13 @@ fn cgreturn(c: *cgen, n: *node) void = {
ru38 = ru38.under; ru38 = ru38.under;
}; };
if (ru38 != nil) { if (ru38 != nil) {
// #37 wired the N_INDEX/N_DOT mem-read into
// the widener; the remaining >32B kinds stay
// loud.
if (ru38.kind == tykind.TY_TAGGED if (ru38.kind == tykind.TY_TAGGED
&& rhs.kind != nkind.N_IDENT && rhs.kind != nkind.N_IDENT
&& ru38.size: i32 > TUPLE_GPCAP * 8) { && ru38.size: i32 > TUPLE_GPCAP * 8
&& !taggedmemread(c, rhs)) {
let m38e: str = "#40: widening tagged return-forward of a >32B source needs mem-to-mem tag-remap (unwired)\n"; let m38e: str = "#40: widening tagged return-forward of a >32B source needs mem-to-mem tag-remap (unwired)\n";
os.write(2, m38e.ptr, m38e.len: u64); os.write(2, m38e.ptr, m38e.len: u64);
os.exit(1); os.exit(1);

View File

@@ -900,6 +900,15 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
if (arg.kind == nkind.N_INDEX || arg.kind == nkind.N_DOT) { if (arg.kind == nkind.N_INDEX || arg.kind == nkind.N_DOT) {
if (istaggedtype(c, arg)) { if (istaggedtype(c, arg)) {
let isz: i32 = slotsize(c, arg); let isz: i32 = slotsize(c, arg);
// #37 (rule 7): a 33-48B box from an INDEX/DOT read
// is mem-based (AX = addr, no cursor to push) — was
// silent cursor truncation pre-#37; the mem-based
// push is the #35 family. Mirrors cstage.
if (taggedmemread(c, arg)) {
let m37g: str = "#37: >32B tagged arg from a mem-based read unwired (#35/#40-family follow-up)\n";
os.write(2, m37g.ptr, m37g.len: u64);
os.exit(1);
};
if (isz > 24) { emitline("\tPUSHQ\tR8\n"); }; if (isz > 24) { emitline("\tPUSHQ\tR8\n"); };
if (isz > 16) { emitline("\tPUSHQ\tCX\n"); }; if (isz > 16) { emitline("\tPUSHQ\tCX\n"); };
if (isz > 8) { emitline("\tPUSHQ\tDX\n"); }; if (isz > 8) { emitline("\tPUSHQ\tDX\n"); };
@@ -3023,6 +3032,23 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
return false; return false;
}; };
// taggedmemread — #37: does cgexpr leave this tagged expr's box in
// MEMORY (AX = box address) instead of the AX/DX/CX/R8 cursor? True
// for an N_INDEX/N_DOT read whose box exceeds the 4-reg cursor — the
// same mem-based class as an sret-classified call (which the #38b
// gates key separately on callsretsize). Every cursor-spill consumer
// must branch on this before reading AX as the tag. Mirrors cstage
// cg_tagged_memread.
fn taggedmemread(c: *cgen, e: *node) bool = {
if (e == nil) { return false; };
if (e.kind != nkind.N_INDEX && e.kind != nkind.N_DOT) { return false; };
let u: *tinfo = e.type_: *tinfo;
for (u != nil && u.kind == tykind.TY_NAMED) { u = u.under; };
if (u == nil) { return false; };
if (u.kind != tykind.TY_TAGGED) { return false; };
return u.size: i32 > TUPLE_GPCAP * 8;
};
// cgloadtaggedfield — load a tagged-union slot at `basereg`+foff // cgloadtaggedfield — load a tagged-union slot at `basereg`+foff
// into the tagged-return ABI registers (AX=tag, DX=word0, CX=word1, // into the tagged-return ABI registers (AX=tag, DX=word0, CX=word1,
// R8=word2). Slot sizes: 16B = (tag, word0), 24B = + word1, 32B // R8=word2). Slot sizes: 16B = (tag, word0), 24B = + word1, 32B
@@ -3038,6 +3064,16 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
// only register loaded into that is NOT a target is BX, so AX- // only register loaded into that is NOT a target is BX, so AX-
// or DX-rooted callers must spill first. // or DX-rooted callers must spill first.
fn cgloadtaggedfield(c: *cgen, basereg: str, foff: i32, slot_sz: i32) void = { fn cgloadtaggedfield(c: *cgen, basereg: str, foff: i32, slot_sz: i32) void = {
// #37: >32B box — leave its ADDRESS in AX (taggedmemread, the
// sret-receive convention); the 4-reg cursor walk below would
// truncate past payload word 2. Mirrors cstage's N_DOT
// TY_STRUCT/TY_PTR tagged arms.
if (slot_sz > TUPLE_GPCAP * 8) {
emitline("\tLEAQ\t");
emitdispreg(foff: i64, basereg);
emitline(", AX\n");
return;
};
// tag → AX // tag → AX
emitline("\tMOVQ\t"); emitline("\tMOVQ\t");
emitdispreg(foff: i64, basereg); emitdispreg(foff: i64, basereg);
@@ -3281,6 +3317,29 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
os.exit(1); os.exit(1);
}; };
}; };
if (taggedmemread(c, src)) {
// #37: >32B box read — ADDRESS in AX;
// copy the inner box from memory into
// the payload area. Mirrors cstage.
cgexpr(c, src);
let mk: i32 = 0;
for (mk < ssz) {
emitline("\tMOVQ\t");
emitdispreg(mk: i64, "AX");
emitline(", DX\n");
emitline("\tMOVQ\tDX, ");
emitoff((slot_off + 8 + mk): i64);
emitline("(BP)\n");
mk += 8;
};
} else {
// #37 (rule 7): >32B from a non-mem-based
// kind would spill an unfilled cursor.
if (ssz > TUPLE_GPCAP * 8) {
let m37a: str = "#37: >32B tagged payload from a non-mem-based source unwired (rule 7)\n";
os.write(2, m37a.ptr, m37a.len: u64);
os.exit(1);
};
cgexpr(c, src); cgexpr(c, src);
emitline("\tMOVQ\tAX, "); emitline("\tMOVQ\tAX, ");
emitoff((slot_off + 8): i64); emitoff((slot_off + 8): i64);
@@ -3300,6 +3359,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
emitoff((slot_off + 32): i64); emitoff((slot_off + 32): i64);
emitline("(BP)\n"); emitline("(BP)\n");
}; };
};
}; };
emitline("\tMOVQ\t$"); emitline("\tMOVQ\t$");
emitint(nested: i64); emitint(nested: i64);
@@ -3353,6 +3413,40 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
os.exit(1); os.exit(1);
}; };
}; };
// #37: >32B box read (insts[pc], t.N, s.f) — cgexpr left
// its ADDRESS in AX; copy the whole box from memory, pad,
// tag-remap — the mem-based twin of the ident arm above.
// Mirrors cstage cg_widen_tagged_store's memread arm.
if (taggedmemread(c, src)) {
let su37: *tinfo = src.type_: *tinfo;
for (su37 != nil && su37.kind == tykind.TY_NAMED) {
su37 = su37.under;
};
let ssz37: i32 = su37.size: i32;
cgexpr(c, src);
let mk37: i32 = 0;
for (mk37 < ssz37) {
emitline("\tMOVQ\t");
emitdispreg(mk37: i64, "AX");
emitline(", DX\n");
emitline("\tMOVQ\tDX, ");
emitoff((slot_off + mk37): i64);
emitline("(BP)\n");
mk37 += 8;
};
if (ssz37 < slot_sz) {
emitline("\tXORQ\tAX, AX\n");
let pp37: i32 = ssz37;
for (pp37 < slot_sz) {
emitline("\tMOVQ\tAX, ");
emitoff((slot_off + pp37): i64);
emitline("(BP)\n");
pp37 += 8;
};
};
cgwidentagremap(c, dt, src.type_: *tinfo, slot_off);
return;
};
cgexpr(c, src); cgexpr(c, src);
emitline("\tMOVQ\tAX, "); emitline("\tMOVQ\tAX, ");
emitoff(slot_off: i64); emitoff(slot_off: i64);
@@ -3374,6 +3468,23 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
}; };
return; return;
}; };
// #37 (rule 7): a >32B TAGGED source of a kind the resolver arms
// above don't carry (deref/cast/unwrap/...) would fall to the
// scalar word0 arm below and silently truncate — keyed on the
// stamped src.type_ (kind-blind), the twin of cstage
// cg_widen_tagged_store's generic-else bound. Surfaced by
// reviewer-37's `let w = *p` probe on a 56B box: cstage loud,
// wwstage silent (rule-10 break).
let sf37: *tinfo = src.type_: *tinfo;
for (sf37 != nil && sf37.kind == tykind.TY_NAMED) {
sf37 = sf37.under;
};
if (sf37 != nil && sf37.kind == tykind.TY_TAGGED
&& sf37.size: i32 > TUPLE_GPCAP * 8) {
let m37f: str = "#37: >32B tagged source of a non-mem-based kind unwired (rule 7)\n";
os.write(2, m37f.ptr, m37f.len: u64);
os.exit(1);
};
// #242: tuple payload. Each element rides ONE register-ABI // #242: tuple payload. Each element rides ONE register-ABI
// eightbyte — scalar/float a single 8B word, a slice/str its 3-word // eightbyte — scalar/float a single 8B word, a slice/str its 3-word
// {ptr,len,cap} header (24B) — matching the tagged-return load // {ptr,len,cap} header (24B) — matching the tagged-return load

View File

@@ -16910,6 +16910,15 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
if (arg.kind == nkind.N_INDEX || arg.kind == nkind.N_DOT) { if (arg.kind == nkind.N_INDEX || arg.kind == nkind.N_DOT) {
if (istaggedtype(c, arg)) { if (istaggedtype(c, arg)) {
let isz: i32 = slotsize(c, arg); let isz: i32 = slotsize(c, arg);
// #37 (rule 7): a 33-48B box from an INDEX/DOT read
// is mem-based (AX = addr, no cursor to push) — was
// silent cursor truncation pre-#37; the mem-based
// push is the #35 family. Mirrors cstage.
if (taggedmemread(c, arg)) {
let m37g: str = "#37: >32B tagged arg from a mem-based read unwired (#35/#40-family follow-up)\n";
os.write(2, m37g.ptr, m37g.len: u64);
os.exit(1);
};
if (isz > 24) { emitline("\tPUSHQ\tR8\n"); }; if (isz > 24) { emitline("\tPUSHQ\tR8\n"); };
if (isz > 16) { emitline("\tPUSHQ\tCX\n"); }; if (isz > 16) { emitline("\tPUSHQ\tCX\n"); };
if (isz > 8) { emitline("\tPUSHQ\tDX\n"); }; if (isz > 8) { emitline("\tPUSHQ\tDX\n"); };
@@ -19033,6 +19042,23 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
return false; return false;
}; };
// taggedmemread — #37: does cgexpr leave this tagged expr's box in
// MEMORY (AX = box address) instead of the AX/DX/CX/R8 cursor? True
// for an N_INDEX/N_DOT read whose box exceeds the 4-reg cursor — the
// same mem-based class as an sret-classified call (which the #38b
// gates key separately on callsretsize). Every cursor-spill consumer
// must branch on this before reading AX as the tag. Mirrors cstage
// cg_tagged_memread.
fn taggedmemread(c: *cgen, e: *node) bool = {
if (e == nil) { return false; };
if (e.kind != nkind.N_INDEX && e.kind != nkind.N_DOT) { return false; };
let u: *tinfo = e.type_: *tinfo;
for (u != nil && u.kind == tykind.TY_NAMED) { u = u.under; };
if (u == nil) { return false; };
if (u.kind != tykind.TY_TAGGED) { return false; };
return u.size: i32 > TUPLE_GPCAP * 8;
};
// cgloadtaggedfield — load a tagged-union slot at `basereg`+foff // cgloadtaggedfield — load a tagged-union slot at `basereg`+foff
// into the tagged-return ABI registers (AX=tag, DX=word0, CX=word1, // into the tagged-return ABI registers (AX=tag, DX=word0, CX=word1,
// R8=word2). Slot sizes: 16B = (tag, word0), 24B = + word1, 32B // R8=word2). Slot sizes: 16B = (tag, word0), 24B = + word1, 32B
@@ -19048,6 +19074,16 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
// only register loaded into that is NOT a target is BX, so AX- // only register loaded into that is NOT a target is BX, so AX-
// or DX-rooted callers must spill first. // or DX-rooted callers must spill first.
fn cgloadtaggedfield(c: *cgen, basereg: str, foff: i32, slot_sz: i32) void = { fn cgloadtaggedfield(c: *cgen, basereg: str, foff: i32, slot_sz: i32) void = {
// #37: >32B box — leave its ADDRESS in AX (taggedmemread, the
// sret-receive convention); the 4-reg cursor walk below would
// truncate past payload word 2. Mirrors cstage's N_DOT
// TY_STRUCT/TY_PTR tagged arms.
if (slot_sz > TUPLE_GPCAP * 8) {
emitline("\tLEAQ\t");
emitdispreg(foff: i64, basereg);
emitline(", AX\n");
return;
};
// tag → AX // tag → AX
emitline("\tMOVQ\t"); emitline("\tMOVQ\t");
emitdispreg(foff: i64, basereg); emitdispreg(foff: i64, basereg);
@@ -19291,6 +19327,29 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
os.exit(1); os.exit(1);
}; };
}; };
if (taggedmemread(c, src)) {
// #37: >32B box read — ADDRESS in AX;
// copy the inner box from memory into
// the payload area. Mirrors cstage.
cgexpr(c, src);
let mk: i32 = 0;
for (mk < ssz) {
emitline("\tMOVQ\t");
emitdispreg(mk: i64, "AX");
emitline(", DX\n");
emitline("\tMOVQ\tDX, ");
emitoff((slot_off + 8 + mk): i64);
emitline("(BP)\n");
mk += 8;
};
} else {
// #37 (rule 7): >32B from a non-mem-based
// kind would spill an unfilled cursor.
if (ssz > TUPLE_GPCAP * 8) {
let m37a: str = "#37: >32B tagged payload from a non-mem-based source unwired (rule 7)\n";
os.write(2, m37a.ptr, m37a.len: u64);
os.exit(1);
};
cgexpr(c, src); cgexpr(c, src);
emitline("\tMOVQ\tAX, "); emitline("\tMOVQ\tAX, ");
emitoff((slot_off + 8): i64); emitoff((slot_off + 8): i64);
@@ -19310,6 +19369,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
emitoff((slot_off + 32): i64); emitoff((slot_off + 32): i64);
emitline("(BP)\n"); emitline("(BP)\n");
}; };
};
}; };
emitline("\tMOVQ\t$"); emitline("\tMOVQ\t$");
emitint(nested: i64); emitint(nested: i64);
@@ -19363,6 +19423,40 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
os.exit(1); os.exit(1);
}; };
}; };
// #37: >32B box read (insts[pc], t.N, s.f) — cgexpr left
// its ADDRESS in AX; copy the whole box from memory, pad,
// tag-remap — the mem-based twin of the ident arm above.
// Mirrors cstage cg_widen_tagged_store's memread arm.
if (taggedmemread(c, src)) {
let su37: *tinfo = src.type_: *tinfo;
for (su37 != nil && su37.kind == tykind.TY_NAMED) {
su37 = su37.under;
};
let ssz37: i32 = su37.size: i32;
cgexpr(c, src);
let mk37: i32 = 0;
for (mk37 < ssz37) {
emitline("\tMOVQ\t");
emitdispreg(mk37: i64, "AX");
emitline(", DX\n");
emitline("\tMOVQ\tDX, ");
emitoff((slot_off + mk37): i64);
emitline("(BP)\n");
mk37 += 8;
};
if (ssz37 < slot_sz) {
emitline("\tXORQ\tAX, AX\n");
let pp37: i32 = ssz37;
for (pp37 < slot_sz) {
emitline("\tMOVQ\tAX, ");
emitoff((slot_off + pp37): i64);
emitline("(BP)\n");
pp37 += 8;
};
};
cgwidentagremap(c, dt, src.type_: *tinfo, slot_off);
return;
};
cgexpr(c, src); cgexpr(c, src);
emitline("\tMOVQ\tAX, "); emitline("\tMOVQ\tAX, ");
emitoff(slot_off: i64); emitoff(slot_off: i64);
@@ -19384,6 +19478,23 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
}; };
return; return;
}; };
// #37 (rule 7): a >32B TAGGED source of a kind the resolver arms
// above don't carry (deref/cast/unwrap/...) would fall to the
// scalar word0 arm below and silently truncate — keyed on the
// stamped src.type_ (kind-blind), the twin of cstage
// cg_widen_tagged_store's generic-else bound. Surfaced by
// reviewer-37's `let w = *p` probe on a 56B box: cstage loud,
// wwstage silent (rule-10 break).
let sf37: *tinfo = src.type_: *tinfo;
for (sf37 != nil && sf37.kind == tykind.TY_NAMED) {
sf37 = sf37.under;
};
if (sf37 != nil && sf37.kind == tykind.TY_TAGGED
&& sf37.size: i32 > TUPLE_GPCAP * 8) {
let m37f: str = "#37: >32B tagged source of a non-mem-based kind unwired (rule 7)\n";
os.write(2, m37f.ptr, m37f.len: u64);
os.exit(1);
};
// #242: tuple payload. Each element rides ONE register-ABI // #242: tuple payload. Each element rides ONE register-ABI
// eightbyte — scalar/float a single 8B word, a slice/str its 3-word // eightbyte — scalar/float a single 8B word, a slice/str its 3-word
// {ptr,len,cap} header (24B) — matching the tagged-return load // {ptr,len,cap} header (24B) — matching the tagged-return load
@@ -20668,6 +20779,13 @@ fn cgtryprop(c: *cgen, n: *node) void = {
}; };
}; };
}; };
// #37 (rule 7): a >32B box read leaves AX = address, not the
// tag the unwrap below compares. Mirrors cstage.
if (taggedmemread(c, n.lhs)) {
let m37p: str = "#37: `?` on a >32B mem-based tagged read unwired (#40-family follow-up)\n";
os.write(2, m37p.ptr, m37p.len: u64);
os.exit(1);
};
if (sretretsize(c, c.fnret) > 0) { if (sretretsize(c, c.fnret) > 0) {
let m38q: str = "#38b: `?` propagation into a >32B tagged return unwired (sret error-propagate is a #40-family follow-up)\n"; let m38q: str = "#38b: `?` propagation into a >32B tagged return unwired (sret error-propagate is a #40-family follow-up)\n";
os.write(2, m38q.ptr, m38q.len: u64); os.write(2, m38q.ptr, m38q.len: u64);
@@ -20801,6 +20919,12 @@ fn cgtryunw(c: *cgen, n: *node) void = {
}; };
}; };
}; };
// #37 (rule 7): see the cgtryprop twin.
if (taggedmemread(c, n.lhs)) {
let m37u: str = "#37: `!` on a >32B mem-based tagged read unwired (#40-family follow-up)\n";
os.write(2, m37u.ptr, m37u.len: u64);
os.exit(1);
};
cgexpr(c, n.lhs); cgexpr(c, n.lhs);
let cl: str = mklabel(c, "tryunw_ok"); let cl: str = mklabel(c, "tryunw_ok");
emitline("\tCMPQ\t$0, AX\n"); emitline("\tCMPQ\t$0, AX\n");
@@ -20905,6 +21029,12 @@ fn cgtypetest(c: *cgen, n: *node) void = {
// tag read landed on (BP) — the saved-BP word. // tag read landed on (BP) — the saved-BP word.
nonident = true; nonident = true;
cgexpr(c, lhs); cgexpr(c, lhs);
// #37: a >32B box read leaves its ADDRESS in AX —
// load the tag word from memory before the compare.
// Mirrors cstage N_TYPETEST.
if (taggedmemread(c, lhs)) {
emitline("\tMOVQ\t(AX), AX\n");
};
}; };
}; };
let want: i32 = 0; let want: i32 = 0;
@@ -21053,6 +21183,28 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
scrutt = matchscrutt(c, lhs); scrutt = matchscrutt(c, lhs);
let spillsz: i32 = matchspillsz(c, scrutt); let spillsz: i32 = matchspillsz(c, scrutt);
scrutoff = localalloc(c, "@asrt_spill", spillsz, nil); scrutoff = localalloc(c, "@asrt_spill", spillsz, nil);
if (taggedmemread(c, lhs)) {
// #37: >32B box read — ADDRESS in AX; copy the
// whole box from memory. Mirrors cstage.
cgexpr(c, lhs);
let ak37: i32 = 0;
for (ak37 < spillsz) {
emitline("\tMOVQ\t");
emitdispreg(ak37: i64, "AX");
emitline(", DX\n");
emitline("\tMOVQ\tDX, ");
emitoff((scrutoff + ak37): i64);
emitline("(BP)\n");
ak37 += 8;
};
} else {
// #37 (rule 7): >32B from a non-mem-based kind would
// spill an unfilled cursor. Mirrors cstage.
if (!isnullabletype(scrutt) && spillsz > TUPLE_GPCAP * 8) {
let m37s: str = "#37: `as` on a >32B tagged value from a non-mem-based source unwired (rule 7)\n";
os.write(2, m37s.ptr, m37s.len: u64);
os.exit(1);
};
cgexpr(c, lhs); cgexpr(c, lhs);
emitline("\tMOVQ\tAX, "); emitline("\tMOVQ\tAX, ");
emitoff(scrutoff: i64); emitoff(scrutoff: i64);
@@ -21074,6 +21226,7 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
emitline("(BP)\n"); emitline("(BP)\n");
}; };
}; };
};
}; };
}; };
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs); let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
@@ -22150,6 +22303,13 @@ fn cgindex(c: *cgen, n: *node) void = {
return; return;
}; };
if (elem_tagged) { if (elem_tagged) {
// #37: >32B box — ADDRESS in AX (the taggedmemread
// convention); the 4-reg cursor walk below would
// truncate past payload word 2. Mirrors cstage.
if (elem_slot_sz > TUPLE_GPCAP * 8) {
emitline("\tMOVQ\tBX, AX\n");
return;
};
if (elem_slot_sz > 24) { if (elem_slot_sz > 24) {
emitline("\tMOVQ\t24(BX), R8\n"); emitline("\tMOVQ\t24(BX), R8\n");
}; };
@@ -22209,6 +22369,13 @@ fn cgindex(c: *cgen, n: *node) void = {
return; return;
}; };
if (elem_tagged) { if (elem_tagged) {
// #37: >32B box — ADDRESS in AX (the taggedmemread
// convention); the 4-reg cursor walk below would
// truncate past payload word 2. Mirrors cstage.
if (elem_slot_sz > TUPLE_GPCAP * 8) {
emitline("\tMOVQ\tBX, AX\n");
return;
};
if (elem_slot_sz > 24) { if (elem_slot_sz > 24) {
emitline("\tMOVQ\t24(BX), R8\n"); emitline("\tMOVQ\t24(BX), R8\n");
}; };
@@ -22264,6 +22431,11 @@ fn cgindex(c: *cgen, n: *node) void = {
// arms have it) — a >24B-slot element via a non-ident base // arms have it) — a >24B-slot element via a non-ident base
// under-read the cursor and the match spill stored stale R8. // under-read the cursor and the match spill stored stale R8.
// Mirrors cstage cgen.c:9106-9117. // Mirrors cstage cgen.c:9106-9117.
// #37: >32B box — AX already holds the element address;
// leave it (taggedmemread). Mirrors cstage.
if (elem_slot_sz > TUPLE_GPCAP * 8) {
return;
};
emitline("\tMOVQ\tAX, BX\n"); emitline("\tMOVQ\tAX, BX\n");
if (elem_slot_sz > 24) { if (elem_slot_sz > 24) {
emitline("\tMOVQ\t24(BX), R8\n"); emitline("\tMOVQ\t24(BX), R8\n");
@@ -22653,7 +22825,47 @@ fn cgmatch(c: *cgen, n: *node) void = {
c.sretdestoff = scrutoff; c.sretdestoff = scrutoff;
cgexpr(c, scrut); cgexpr(c, scrut);
c.sretdestoff = 0; c.sretdestoff = 0;
} else { if (taggedmemread(c, scrut)) {
// #37: >32B box read (insts[pc], t.N) — cgexpr left
// its ADDRESS in AX; copy the whole box from memory.
// Mirrors cstage cgmatch.
cgexpr(c, scrut);
let mk37: i32 = 0;
for (mk37 < spillsz) {
emitline("\tMOVQ\t");
emitdispreg(mk37: i64, "AX");
emitline(", DX\n");
emitline("\tMOVQ\tDX, ");
emitoff((scrutoff + mk37): i64);
emitline("(BP)\n");
mk37 += 8;
};
} else { } else {
// #37 (rule 7): a >32B box from a kind with no mem-read
// convention would spill the cursor it never filled —
// loud, not garbage. Mirrors cstage.
if (!isnullabletype(scrutt) && spillsz > TUPLE_GPCAP * 8) {
let m37m: str = "#37: >32B tagged match scrutinee from a non-mem-based source unwired (rule 7)\n";
os.write(2, m37m.ptr, m37m.len: u64);
os.exit(1);
};
// #37 (rule 7) stamped twin: matchscrutt returns nil
// for kinds it can't resolve (deref/cast/...), so
// spillsz defaults under cap and the guard above is
// blind there. cstage sizes the spill from the
// stamped s->type, so it louds — key on scrut.type_
// to match. Surfaced by reviewer-37's `match (*p)`
// probe on a 56B box.
let ms37: *tinfo = scrut.type_: *tinfo;
for (ms37 != nil && ms37.kind == tykind.TY_NAMED) {
ms37 = ms37.under;
};
if (ms37 != nil && ms37.kind == tykind.TY_TAGGED
&& ms37.size: i32 > TUPLE_GPCAP * 8) {
let m37n: str = "#37: >32B tagged match scrutinee from a non-mem-based source unwired (rule 7)\n";
os.write(2, m37n.ptr, m37n.len: u64);
os.exit(1);
};
cgexpr(c, scrut); cgexpr(c, scrut);
emitline("\tMOVQ\tAX, "); emitline("\tMOVQ\tAX, ");
emitoff(scrutoff: i64); emitoff(scrutoff: i64);
@@ -22678,7 +22890,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
emitline("(BP)\n"); emitline("(BP)\n");
}; };
}; };
}; }; };
}; };
}; };
let endl: str = mklabel(c, "match_end"); let endl: str = mklabel(c, "match_end");
@@ -23170,19 +23382,18 @@ fn cgdot(c: *cgen, n: *node) void = {
// tagged arm. // tagged arm.
if (istaggedtype(c, tpt)) { if (istaggedtype(c, tpt)) {
let eslot: i32 = tupeslotn(tpt); let eslot: i32 = tupeslotn(tpt);
// #22b (rule 7): a >32B box // #37: a >32B box overruns the
// overruns the 4-reg cursor — // 4-reg cursor — leave its
// pre-bound tupreg clamped k>=3 // ADDRESS in AX (taggedmemread,
// to R8 (silent payload drop) // the sret-receive convention);
// while cstage emitted invalid // consumers copy from memory.
// asm. Reachable only since the // Replaces the #22b loud bound.
// over-cap sret send unwired; // Mirrors cstage.
// the mem-based box read is the
// #37 family. Mirrors cstage.
if (eslot > TUPLE_GPCAP * 8) { 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"; emitline("\tLEAQ\t");
os.write(2, m37.ptr, m37.len: u64); emitoff((lc.off + foff): i64);
os.exit(1); emitline("(BP), AX\n");
return;
}; };
let k: i32 = 0; let k: i32 = 0;
for (k < eslot / 8) { for (k < eslot / 8) {
@@ -31860,16 +32071,11 @@ fn cgreturn(c: *cgen, n: *node) void = {
// cgwidentaggedstore's non-BP base. // cgwidentaggedstore's non-BP base.
if (sretretsize(c, c.fnret) > 0) { if (sretretsize(c, c.fnret) > 0) {
let sa38v: i32 = localfind(c, "@sretarg"); let sa38v: i32 = localfind(c, "@sretarg");
if (forwardtagged) { if (forwardtagged && rhs.kind == nkind.N_CALL) {
// exact type, but only an N_CALL source // exact-type N_CALL forward: inner sret's
// sret's into outer's dest; a cursor // into outer's dest; an N_INDEX/N_DOT
// source (N_INDEX/N_DOT) can't carry // source routes through the widener's
// >32B (rule 7, #38b residual). // #37 mem-read arm below instead.
if (rhs.kind != nkind.N_CALL) {
let m38d: str = "#38b: >32B tagged return from a cursor source (N_INDEX/N_DOT) unsupported\n";
os.write(2, m38d.ptr, m38d.len: u64);
os.exit(1);
};
c.sretforward = 1; c.sretforward = 1;
cgexpr(c, rhs); cgexpr(c, rhs);
emitline("\tMOVQ\t"); emitline("\tMOVQ\t");
@@ -31886,9 +32092,13 @@ fn cgreturn(c: *cgen, n: *node) void = {
ru38 = ru38.under; ru38 = ru38.under;
}; };
if (ru38 != nil) { if (ru38 != nil) {
// #37 wired the N_INDEX/N_DOT mem-read into
// the widener; the remaining >32B kinds stay
// loud.
if (ru38.kind == tykind.TY_TAGGED if (ru38.kind == tykind.TY_TAGGED
&& rhs.kind != nkind.N_IDENT && rhs.kind != nkind.N_IDENT
&& ru38.size: i32 > TUPLE_GPCAP * 8) { && ru38.size: i32 > TUPLE_GPCAP * 8
&& !taggedmemread(c, rhs)) {
let m38e: str = "#40: widening tagged return-forward of a >32B source needs mem-to-mem tag-remap (unwired)\n"; let m38e: str = "#40: widening tagged return-forward of a >32B source needs mem-to-mem tag-remap (unwired)\n";
os.write(2, m38e.ptr, m38e.len: u64); os.write(2, m38e.ptr, m38e.len: u64);
os.exit(1); os.exit(1);

View File

@@ -540,14 +540,16 @@ static const struct row rows[] = {
"};\n", 0, "};\n", 0,
K_BUILDERR, "#22b: tagged element in an over-cap (sret) tuple " K_BUILDERR, "#22b: tagged element in an over-cap (sret) tuple "
"return from a non-ident or widening source unwired" }, "return from a non-ident or widening source unwired" },
/* #37-class bound this arc newly exposed: a >32B box (40B = /* #37 FLIP (was the #22b loud bound; BUILDERR at master a72e815
* (void|4x-u64-struct)) became transportable through the sret * with "tagged tuple element read exceeds the AX/DX/CX/R8 box
* routes, but the t.N READ rides the 4-reg AX/DX/CX/R8 cursor — * cursor"): a >32B box t.N READ now leaves the box ADDRESS in AX
* unbounded, cstage emitted INVALID ASM (tuple_rseq[4] OOB → w6a * (cg_tagged_memread, the sret-receive convention) and every
* "unsupported MOVQ shape") while wwstage clamped k>=3 to R8 and * cursor consumer copies from memory. The pre-#22b history: cstage
* silently dropped payload word 3. Now loud both stages; the * emitted INVALID ASM (tuple_rseq[4] OOB), wwstage clamped k>=3 to
* mem-based box read is #37's fold. */ * R8 and silently dropped payload word 3. Readback covers the
{ "b22_reject_big_box_read", * is-test (N_TYPETEST mem tag load) AND the full payload through a
* direct match on t.N (match-spill mem copy). */
{ "c37_big_box_t0_read",
"package main;\n" "package main;\n"
"type four = struct { a: u64, b: u64, c: u64, d: u64, };\n" "type four = struct { a: u64, b: u64, c: u64, d: u64, };\n"
"fn g() (void | four) = {\n" "fn g() (void | four) = {\n"
@@ -560,11 +562,383 @@ static const struct row rows[] = {
"};\n" "};\n"
"export fn main() i32 = {\n" "export fn main() i32 = {\n"
" let t = pr();\n" " let t = pr();\n"
" if (!(t.0 is four)) { return 1; };\n" " if (t.1 != 7) { return 1; };\n"
" if (!(t.0 is four)) { return 2; };\n"
" match (t.0) {\n"
" case let f: four => {\n"
" if (f.a != 1) { return 3; };\n"
" if (f.b != 2) { return 4; };\n"
" if (f.c != 3) { return 5; };\n"
" if (f.d != 4) { return 6; };\n"
" };\n"
" case void => { return 7; };\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* #37 THE 5b DRIVER SHAPE: `match insts[pc]` where inst =
* (inst_lit | inst_repeat) = 56B (8 tag + 48 payload: 2x size +
* 2x 16B nested tagged). SILENT-WRONG at master a72e815 (gate-
* blind, byte-id both stages): the N_INDEX cursor load clamped at
* R8, dropping payload words 3+ — min's payload and max's whole
* box read stack garbage. Matches BOTH variants, reads every
* nested-tagged field back. */
{ "c37_idx_match_56",
"package main;\n"
"type inst_lit = rune;\n"
"type inst_repeat = struct {\n"
" id: size,\n"
" origin: size,\n"
" min: (void | size),\n"
" max: (void | size),\n"
"};\n"
"type inst = (inst_lit | inst_repeat);\n"
"export fn main() i32 = {\n"
" let insts: []inst = [];\n"
" let r = inst_repeat{id=7: size, origin=3: size, min=(11: size), max=(22: size)};\n"
" append(insts, r);\n"
" append(insts, ('A': inst_lit));\n"
" let pc: size = 0;\n"
" match (insts[pc]) {\n"
" case let ir: inst_repeat => {\n"
" if (ir.id != 7) { return 1; };\n"
" if (ir.origin != 3) { return 2; };\n"
" if (!(ir.min is size)) { return 3; };\n"
" if (ir.min as size != 11) { return 4; };\n"
" if (!(ir.max is size)) { return 5; };\n"
" if (ir.max as size != 22) { return 6; };\n"
" };\n"
" case let l: inst_lit => { return 7; };\n"
" };\n"
" match (insts[1]) {\n"
" case let l: inst_lit => {\n"
" if (l != 'A') { return 8; };\n"
" };\n"
" case let ir: inst_repeat => { return 9; };\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* #37: str + nested-tagged payload variant — the big variant
* carries a 24B str header AND a nested 16B box (64B union);
* readback checks the str CONTENT survives the mem copy, not just
* the tag. SILENT-WRONG at master (words 3+ dropped). */
{ "c37_idx_match_str_nested",
"package main;\n"
"type lit2 = rune;\n"
"type rep2 = struct {\n"
" name: str,\n"
" min: (void | size),\n"
" max: (void | size),\n"
"};\n"
"type in2 = (lit2 | rep2);\n"
"export fn main() i32 = {\n"
" let xs: []in2 = [];\n"
" let r = rep2{name=\"deadbeef\", min=(2: size), max=(5: size)};\n"
" append(xs, r);\n"
" match (xs[0]) {\n"
" case let v: rep2 => {\n"
" if (v.name.len != 8) { return 1; };\n"
" if (v.name[0] != 'd') { return 2; };\n"
" if (v.name[7] != 'f') { return 3; };\n"
" if (!(v.min is size)) { return 4; };\n"
" if (v.min as size != 2) { return 5; };\n"
" if (v.max as size != 5) { return 6; };\n"
" };\n"
" case let l: lit2 => { return 7; };\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* #37: let-bind from a >32B indexed read (widener subset arm
* mem-copy), then is-test directly on the indexed read (N_TYPETEST
* mem tag load), then reassign (cgassign route). SILENT-WRONG at
* master. */
{ "c37_idx_let_is_assign",
"package main;\n"
"type inst_lit = rune;\n"
"type inst_repeat = struct {\n"
" id: size,\n"
" origin: size,\n"
" min: (void | size),\n"
" max: (void | size),\n"
"};\n"
"type inst = (inst_lit | inst_repeat);\n"
"export fn main() i32 = {\n"
" let insts: []inst = [];\n"
" let r = inst_repeat{id=7: size, origin=3: size, min=(11: size), max=(22: size)};\n"
" append(insts, r);\n"
" append(insts, ('A': inst_lit));\n"
" if (!(insts[0] is inst_repeat)) { return 1; };\n"
" if (insts[0] is inst_lit) { return 2; };\n"
" let w = insts[0];\n"
" match (w) {\n"
" case let ir: inst_repeat => {\n"
" if (ir.max as size != 22) { return 3; };\n"
" };\n"
" case let l: inst_lit => { return 4; };\n"
" };\n"
" w = insts[1];\n"
" if (!(w is inst_lit)) { return 5; };\n"
" w = insts[0];\n"
" match (w) {\n"
" case let ir: inst_repeat => {\n"
" if (ir.min as size != 11) { return 6; };\n"
" };\n"
" case let l: inst_lit => { return 7; };\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* #37: exact-type >32B tagged RETURN from an indexed source — was
* the "#38b: >32B tagged return from a cursor source" LOUD bound;
* now routes through the widener's mem-read arm into the sret
* dest. BUILDERR at master a72e815. */
{ "c37_idx_return",
"package main;\n"
"type inst_lit = rune;\n"
"type inst_repeat = struct {\n"
" id: size,\n"
" origin: size,\n"
" min: (void | size),\n"
" max: (void | size),\n"
"};\n"
"type inst = (inst_lit | inst_repeat);\n"
"fn pick(insts: []inst, pc: size) inst = {\n"
" return insts[pc];\n"
"};\n"
"export fn main() i32 = {\n"
" let insts: []inst = [];\n"
" let r = inst_repeat{id=7: size, origin=3: size, min=(11: size), max=(22: size)};\n"
" append(insts, r);\n"
" let w = pick(insts, 0);\n"
" match (w) {\n"
" case let ir: inst_repeat => {\n"
" if (ir.id != 7) { return 1; };\n"
" if (ir.min as size != 11) { return 2; };\n"
" };\n"
" case let l: inst_lit => { return 3; };\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* #37: WIDENING let from a >32B indexed read — subset mem-copy +
* zero-pad + (identity) tag remap; and the 56B (>48B MEMORY-class)
* exact-type ARG from an indexed source (the pre-existing #38b
* memarg stack-blit path, kept working alongside the new emitters:
* cgplaceaddr never sees the cursor). */
{ "c37_widen_and_memarg",
"package main;\n"
"type inst_lit = rune;\n"
"type inst_repeat = struct {\n"
" id: size,\n"
" origin: size,\n"
" min: (void | size),\n"
" max: (void | size),\n"
"};\n"
"type inst = (inst_lit | inst_repeat);\n"
"type wide = (inst_lit | inst_repeat | str);\n"
"fn idof(i: inst) size = {\n"
" match (i) {\n"
" case let ir: inst_repeat => { return ir.id; };\n"
" case let l: inst_lit => { return 999; };\n"
" };\n"
" return 998;\n"
"};\n"
"export fn main() i32 = {\n"
" let insts: []inst = [];\n"
" let r = inst_repeat{id=7: size, origin=3: size, min=(11: size), max=(22: size)};\n"
" append(insts, r);\n"
" let w: wide = insts[0];\n"
" match (w) {\n"
" case let ir: inst_repeat => {\n"
" if (ir.max as size != 22) { return 1; };\n"
" };\n"
" case let l: inst_lit => { return 2; };\n"
" case let s2: str => { return 3; };\n"
" };\n"
" if (idof(insts[0]) != 7) { return 4; };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* #37: NON-identity tag remap on a >32B mem-read widen — source
* union declares its variants in REVERSED order vs dst, so the
* widener's remap chain must fire (tag 0 -> 1) after the mem copy
* + zero-pad. SILENT-WRONG at master a72e815. */
{ "c37_widen_remap",
"package main;\n"
"type inst_lit = rune;\n"
"type inst_repeat = struct {\n"
" id: size,\n"
" origin: size,\n"
" min: (void | size),\n"
" max: (void | size),\n"
"};\n"
"type rev = (inst_repeat | inst_lit);\n"
"type wide = (inst_lit | inst_repeat | str);\n"
"export fn main() i32 = {\n"
" let xs: []rev = [];\n"
" let r = inst_repeat{id=7: size, origin=3: size, min=(11: size), max=(22: size)};\n"
" append(xs, r);\n"
" let w: wide = xs[0];\n"
" match (w) {\n"
" case let ir: inst_repeat => {\n"
" if (ir.id != 7) { return 1; };\n"
" if (!(ir.max is size)) { return 2; };\n"
" if (ir.max as size != 22) { return 3; };\n"
" };\n"
" case let l: inst_lit => { return 4; };\n"
" case let s2: str => { return 5; };\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* ken X1 (/tmp/ken_22b_validation.md, impl-22b flag: ROW-WORTHY):
* the #22a/#23/#22b/#37 composition — a #23-constructed box
* ((void|st), st from an inline struct-lit with a NESTED TAGGED
* FIELD, box exactly 32B = cursor AT cap) transported through the
* #22b over-cap sret return, every field read back. */
{ "c37_x1_compose",
"package main;\n"
"type st = struct { a: size, m: (void | size), };\n"
"fn pr() ((void | st), size, size) = {\n"
" let e: (void | st) = st{a=4: size, m=(2: size)};\n"
" return (e, 5, 6);\n"
"};\n"
"export fn main() i32 = {\n"
" let t = pr();\n"
" if (!(t.0 is st)) { return 1; };\n"
" match (t.0) {\n"
" case let v: st => {\n"
" if (v.a != 4) { return 2; };\n"
" if (!(v.m is size)) { return 3; };\n"
" if (v.m as size != 2) { return 4; };\n"
" };\n"
" case void => { return 5; };\n"
" };\n"
" if (t.1 != 5) { return 6; };\n"
" if (t.2 != 6) { return 7; };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* #37 boundary neighbor: a 32B-EXACT (cursor AT cap) tagged
* element INDEX read keeps the AX/DX/CX/R8 cursor — asm byte-
* identical to master (probed: p6 asm unchanged vs a72e815).
* Sister of b22_k5_box_at_cap (the t.N boundary). */
{ "c37_idx_at_cap",
"package main;\n"
"type box = (void | str);\n"
"export fn main() i32 = {\n"
" let bs: []box = [];\n"
" let s: str = \"abcdefgh\";\n"
" append(bs, s);\n"
" match (bs[0]) {\n"
" case let v: str => {\n"
" if (v.len != 8) { return 1; };\n"
" };\n"
" case void => { return 2; };\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* #37 ptr-chained route: match on p.f through a *struct base —
* the cstage D_BX LEAQ arm / wwstage cgloadtaggedfield gate. The
* only #37 emitter pair otherwise covered by probes alone (ken
* M4); unlike the local-field shape (#42 cs!=ww divergence,
* disclosed), the ptr-base spelling is byte-id. SILENT-WRONG at
* master f272068 (exit 1, truncated payload). */
{ "c37_ptr_field_match",
"package main;\n"
"type inst_lit = rune;\n"
"type inst_repeat = struct {\n"
" id: size,\n"
" origin: size,\n"
" min: (void | size),\n"
" max: (void | size),\n"
"};\n"
"type inst = (inst_lit | inst_repeat);\n"
"type holder = struct { pre: size, f: inst, };\n"
"export fn main() i32 = {\n"
" let g = holder{pre=9: size, f=inst_repeat{id=7: size, origin=3: size, min=(11: size), max=(55: size)}};\n"
" let p = &g;\n"
" if (!(p.f is inst_repeat)) { return 1; };\n"
" match (p.f) {\n"
" case let ir: inst_repeat => {\n"
" if (ir.id != 7) { return 2; };\n"
" if (ir.max as size != 55) { return 3; };\n"
" };\n"
" case let l: inst_lit => { return 4; };\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* #37 non-ident INDEX base: (*p)[i] rides the cgindex fallback
* arm (AX already the element address — the emitter that LEAVES
* AX rather than LEAQing). SILENT-WRONG at master f272068. */
{ "c37_deref_idx_match",
"package main;\n"
"type inst_lit = rune;\n"
"type inst_repeat = struct {\n"
" id: size,\n"
" origin: size,\n"
" min: (void | size),\n"
" max: (void | size),\n"
"};\n"
"type inst = (inst_lit | inst_repeat);\n"
"export fn main() i32 = {\n"
" let insts: []inst = [];\n"
" let r = inst_repeat{id=7: size, origin=3: size, min=(11: size), max=(22: size)};\n"
" append(insts, r);\n"
" let p = &insts;\n"
" match ((*p)[0]) {\n"
" case let ir: inst_repeat => {\n"
" if (ir.id != 7) { return 1; };\n"
" if (ir.max as size != 22) { return 2; };\n"
" };\n"
" case let l: inst_lit => { return 3; };\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* #37 loud-symmetry pin (reviewer-37): a >32B tagged WIDEN/let
* source of a non-mem-based kind (deref) louds on BOTH stages.
* Pre-amendment wwstage fell to the scalar word0 arm and ran
* silent-wrong while cstage loud'ed (rule-10 break). */
{ "c37_reject_deref_widen",
"package main;\n"
"type inst_lit = rune;\n"
"type inst_repeat = struct {\n"
" id: size,\n"
" origin: size,\n"
" min: (void | size),\n"
" max: (void | size),\n"
"};\n"
"type inst = (inst_lit | inst_repeat);\n"
"export fn main() i32 = {\n"
" let insts: []inst = [];\n"
" let r = inst_repeat{id=7: size, origin=3: size, min=(11: size), max=(22: size)};\n"
" append(insts, r);\n"
" let p = &insts[0];\n"
" let w = *p;\n"
" if (!(w is inst_repeat)) { return 1; };\n"
" return 0;\n" " return 0;\n"
"};\n", 0, "};\n", 0,
K_BUILDERR, "tagged tuple element read exceeds the AX/DX/CX/R8 " K_BUILDERR, "#37: >32B tagged source of a non-mem-based kind" },
"box cursor (mem-based read is the #37 family" }, /* #37 loud-symmetry pin (reviewer-37): match on a >32B deref
* scrutinee louds on BOTH stages. Pre-amendment wwstage's
* matchscrutt returned nil for N_UN, spillsz defaulted under
* cap, and the guard was blind — silent-wrong vs cstage loud. */
{ "c37_reject_deref_match",
"package main;\n"
"type inst_lit = rune;\n"
"type inst_repeat = struct {\n"
" id: size,\n"
" origin: size,\n"
" min: (void | size),\n"
" max: (void | size),\n"
"};\n"
"type inst = (inst_lit | inst_repeat);\n"
"export fn main() i32 = {\n"
" let insts: []inst = [];\n"
" let r = inst_repeat{id=7: size, origin=3: size, min=(11: size), max=(22: size)};\n"
" append(insts, r);\n"
" let p = &insts[0];\n"
" match (*p) {\n"
" case let ir: inst_repeat => { return 1; };\n"
" case let l: inst_lit => { return 2; };\n"
" };\n"
" return 0;\n"
"};\n", 0,
K_BUILDERR, "#37: >32B tagged match scrutinee from a non-mem-based source" },
/* ...but the >32B TRANSPORT itself (sret send mem-to-mem + MLET /* ...but the >32B TRANSPORT itself (sret send mem-to-mem + MLET
* copy-out) is size-generic and correct — only the cursor read is * copy-out) is size-generic and correct — only the cursor read is
* bounded. is-checks on the destructured local read the tag from * bounded. is-checks on the destructured local read the tag from