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

@@ -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 (istaggedtype(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 > 16) { emitline("\tPUSHQ\tCX\n"); };
if (isz > 8) { emitline("\tPUSHQ\tDX\n"); };
@@ -19033,6 +19042,23 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
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
// into the tagged-return ABI registers (AX=tag, DX=word0, CX=word1,
// 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-
// or DX-rooted callers must spill first.
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
emitline("\tMOVQ\t");
emitdispreg(foff: i64, basereg);
@@ -19291,6 +19327,29 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
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);
emitline("\tMOVQ\tAX, ");
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);
emitline("(BP)\n");
};
};
};
emitline("\tMOVQ\t$");
emitint(nested: i64);
@@ -19363,6 +19423,40 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
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);
emitline("\tMOVQ\tAX, ");
emitoff(slot_off: i64);
@@ -19384,6 +19478,23 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
};
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
// eightbyte — scalar/float a single 8B word, a slice/str its 3-word
// {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) {
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);
@@ -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);
let cl: str = mklabel(c, "tryunw_ok");
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.
nonident = true;
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;
@@ -21053,6 +21183,28 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
scrutt = matchscrutt(c, lhs);
let spillsz: i32 = matchspillsz(c, scrutt);
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);
emitline("\tMOVQ\tAX, ");
emitoff(scrutoff: i64);
@@ -21074,6 +21226,7 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
emitline("(BP)\n");
};
};
};
};
};
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
@@ -22150,6 +22303,13 @@ fn cgindex(c: *cgen, n: *node) void = {
return;
};
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) {
emitline("\tMOVQ\t24(BX), R8\n");
};
@@ -22209,6 +22369,13 @@ fn cgindex(c: *cgen, n: *node) void = {
return;
};
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) {
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
// under-read the cursor and the match spill stored stale R8.
// 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");
if (elem_slot_sz > 24) {
emitline("\tMOVQ\t24(BX), R8\n");
@@ -22653,7 +22825,47 @@ fn cgmatch(c: *cgen, n: *node) void = {
c.sretdestoff = scrutoff;
cgexpr(c, scrut);
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 {
// #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);
emitline("\tMOVQ\tAX, ");
emitoff(scrutoff: i64);
@@ -22678,7 +22890,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
emitline("(BP)\n");
};
};
};
}; };
};
};
let endl: str = mklabel(c, "match_end");
@@ -23170,19 +23382,18 @@ fn cgdot(c: *cgen, n: *node) void = {
// tagged arm.
if (istaggedtype(c, tpt)) {
let eslot: i32 = tupeslotn(tpt);
// #22b (rule 7): a >32B box
// overruns the 4-reg cursor —
// pre-bound tupreg clamped k>=3
// to R8 (silent payload drop)
// while cstage emitted invalid
// asm. Reachable only since the
// over-cap sret send unwired;
// the mem-based box read is the
// #37 family. Mirrors cstage.
// #37: a >32B box overruns the
// 4-reg cursor — leave its
// ADDRESS in AX (taggedmemread,
// the sret-receive convention);
// consumers copy from memory.
// Replaces the #22b loud bound.
// Mirrors cstage.
if (eslot > TUPLE_GPCAP * 8) {
let m37: str = "tagged tuple element read exceeds the AX/DX/CX/R8 box cursor (mem-based read is the #37 family; rule 7)\n";
os.write(2, m37.ptr, m37.len: u64);
os.exit(1);
emitline("\tLEAQ\t");
emitoff((lc.off + foff): i64);
emitline("(BP), AX\n");
return;
};
let k: i32 = 0;
for (k < eslot / 8) {
@@ -31860,16 +32071,11 @@ fn cgreturn(c: *cgen, n: *node) void = {
// cgwidentaggedstore's non-BP base.
if (sretretsize(c, c.fnret) > 0) {
let sa38v: i32 = localfind(c, "@sretarg");
if (forwardtagged) {
// exact type, but only an N_CALL source
// sret's into outer's dest; a cursor
// source (N_INDEX/N_DOT) can't carry
// >32B (rule 7, #38b residual).
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);
};
if (forwardtagged && rhs.kind == nkind.N_CALL) {
// exact-type N_CALL forward: inner sret's
// into outer's dest; an N_INDEX/N_DOT
// source routes through the widener's
// #37 mem-read arm below instead.
c.sretforward = 1;
cgexpr(c, rhs);
emitline("\tMOVQ\t");
@@ -31886,9 +32092,13 @@ fn cgreturn(c: *cgen, n: *node) void = {
ru38 = ru38.under;
};
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
&& 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";
os.write(2, m38e.ptr, m38e.len: u64);
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) {
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);
@@ -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);
let cl: str = mklabel(c, "tryunw_ok");
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.
nonident = true;
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;
@@ -586,6 +605,28 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
scrutt = matchscrutt(c, lhs);
let spillsz: i32 = matchspillsz(c, scrutt);
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);
emitline("\tMOVQ\tAX, ");
emitoff(scrutoff: i64);
@@ -607,6 +648,7 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
emitline("(BP)\n");
};
};
};
};
};
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
@@ -1683,6 +1725,13 @@ fn cgindex(c: *cgen, n: *node) void = {
return;
};
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) {
emitline("\tMOVQ\t24(BX), R8\n");
};
@@ -1742,6 +1791,13 @@ fn cgindex(c: *cgen, n: *node) void = {
return;
};
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) {
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
// under-read the cursor and the match spill stored stale R8.
// 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");
if (elem_slot_sz > 24) {
emitline("\tMOVQ\t24(BX), R8\n");
@@ -2186,7 +2247,47 @@ fn cgmatch(c: *cgen, n: *node) void = {
c.sretdestoff = scrutoff;
cgexpr(c, scrut);
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 {
// #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);
emitline("\tMOVQ\tAX, ");
emitoff(scrutoff: i64);
@@ -2211,7 +2312,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
emitline("(BP)\n");
};
};
};
}; };
};
};
let endl: str = mklabel(c, "match_end");
@@ -2703,19 +2804,18 @@ fn cgdot(c: *cgen, n: *node) void = {
// tagged arm.
if (istaggedtype(c, tpt)) {
let eslot: i32 = tupeslotn(tpt);
// #22b (rule 7): a >32B box
// overruns the 4-reg cursor —
// pre-bound tupreg clamped k>=3
// to R8 (silent payload drop)
// while cstage emitted invalid
// asm. Reachable only since the
// over-cap sret send unwired;
// the mem-based box read is the
// #37 family. Mirrors cstage.
// #37: a >32B box overruns the
// 4-reg cursor — leave its
// ADDRESS in AX (taggedmemread,
// the sret-receive convention);
// consumers copy from memory.
// Replaces the #22b loud bound.
// Mirrors cstage.
if (eslot > TUPLE_GPCAP * 8) {
let m37: str = "tagged tuple element read exceeds the AX/DX/CX/R8 box cursor (mem-based read is the #37 family; rule 7)\n";
os.write(2, m37.ptr, m37.len: u64);
os.exit(1);
emitline("\tLEAQ\t");
emitoff((lc.off + foff): i64);
emitline("(BP), AX\n");
return;
};
let k: i32 = 0;
for (k < eslot / 8) {

View File

@@ -851,16 +851,11 @@ fn cgreturn(c: *cgen, n: *node) void = {
// cgwidentaggedstore's non-BP base.
if (sretretsize(c, c.fnret) > 0) {
let sa38v: i32 = localfind(c, "@sretarg");
if (forwardtagged) {
// exact type, but only an N_CALL source
// sret's into outer's dest; a cursor
// source (N_INDEX/N_DOT) can't carry
// >32B (rule 7, #38b residual).
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);
};
if (forwardtagged && rhs.kind == nkind.N_CALL) {
// exact-type N_CALL forward: inner sret's
// into outer's dest; an N_INDEX/N_DOT
// source routes through the widener's
// #37 mem-read arm below instead.
c.sretforward = 1;
cgexpr(c, rhs);
emitline("\tMOVQ\t");
@@ -877,9 +872,13 @@ fn cgreturn(c: *cgen, n: *node) void = {
ru38 = ru38.under;
};
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
&& 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";
os.write(2, m38e.ptr, m38e.len: u64);
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 (istaggedtype(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 > 16) { emitline("\tPUSHQ\tCX\n"); };
if (isz > 8) { emitline("\tPUSHQ\tDX\n"); };
@@ -3023,6 +3032,23 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
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
// into the tagged-return ABI registers (AX=tag, DX=word0, CX=word1,
// 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-
// or DX-rooted callers must spill first.
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
emitline("\tMOVQ\t");
emitdispreg(foff: i64, basereg);
@@ -3281,6 +3317,29 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
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);
emitline("\tMOVQ\tAX, ");
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);
emitline("(BP)\n");
};
};
};
emitline("\tMOVQ\t$");
emitint(nested: i64);
@@ -3353,6 +3413,40 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
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);
emitline("\tMOVQ\tAX, ");
emitoff(slot_off: i64);
@@ -3374,6 +3468,23 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
};
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
// eightbyte — scalar/float a single 8B word, a slice/str its 3-word
// {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 (istaggedtype(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 > 16) { emitline("\tPUSHQ\tCX\n"); };
if (isz > 8) { emitline("\tPUSHQ\tDX\n"); };
@@ -19033,6 +19042,23 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
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
// into the tagged-return ABI registers (AX=tag, DX=word0, CX=word1,
// 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-
// or DX-rooted callers must spill first.
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
emitline("\tMOVQ\t");
emitdispreg(foff: i64, basereg);
@@ -19291,6 +19327,29 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
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);
emitline("\tMOVQ\tAX, ");
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);
emitline("(BP)\n");
};
};
};
emitline("\tMOVQ\t$");
emitint(nested: i64);
@@ -19363,6 +19423,40 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
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);
emitline("\tMOVQ\tAX, ");
emitoff(slot_off: i64);
@@ -19384,6 +19478,23 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
};
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
// eightbyte — scalar/float a single 8B word, a slice/str its 3-word
// {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) {
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);
@@ -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);
let cl: str = mklabel(c, "tryunw_ok");
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.
nonident = true;
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;
@@ -21053,6 +21183,28 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
scrutt = matchscrutt(c, lhs);
let spillsz: i32 = matchspillsz(c, scrutt);
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);
emitline("\tMOVQ\tAX, ");
emitoff(scrutoff: i64);
@@ -21074,6 +21226,7 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
emitline("(BP)\n");
};
};
};
};
};
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
@@ -22150,6 +22303,13 @@ fn cgindex(c: *cgen, n: *node) void = {
return;
};
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) {
emitline("\tMOVQ\t24(BX), R8\n");
};
@@ -22209,6 +22369,13 @@ fn cgindex(c: *cgen, n: *node) void = {
return;
};
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) {
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
// under-read the cursor and the match spill stored stale R8.
// 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");
if (elem_slot_sz > 24) {
emitline("\tMOVQ\t24(BX), R8\n");
@@ -22653,7 +22825,47 @@ fn cgmatch(c: *cgen, n: *node) void = {
c.sretdestoff = scrutoff;
cgexpr(c, scrut);
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 {
// #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);
emitline("\tMOVQ\tAX, ");
emitoff(scrutoff: i64);
@@ -22678,7 +22890,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
emitline("(BP)\n");
};
};
};
}; };
};
};
let endl: str = mklabel(c, "match_end");
@@ -23170,19 +23382,18 @@ fn cgdot(c: *cgen, n: *node) void = {
// tagged arm.
if (istaggedtype(c, tpt)) {
let eslot: i32 = tupeslotn(tpt);
// #22b (rule 7): a >32B box
// overruns the 4-reg cursor —
// pre-bound tupreg clamped k>=3
// to R8 (silent payload drop)
// while cstage emitted invalid
// asm. Reachable only since the
// over-cap sret send unwired;
// the mem-based box read is the
// #37 family. Mirrors cstage.
// #37: a >32B box overruns the
// 4-reg cursor — leave its
// ADDRESS in AX (taggedmemread,
// the sret-receive convention);
// consumers copy from memory.
// Replaces the #22b loud bound.
// Mirrors cstage.
if (eslot > TUPLE_GPCAP * 8) {
let m37: str = "tagged tuple element read exceeds the AX/DX/CX/R8 box cursor (mem-based read is the #37 family; rule 7)\n";
os.write(2, m37.ptr, m37.len: u64);
os.exit(1);
emitline("\tLEAQ\t");
emitoff((lc.off + foff): i64);
emitline("(BP), AX\n");
return;
};
let k: i32 = 0;
for (k < eslot / 8) {
@@ -31860,16 +32071,11 @@ fn cgreturn(c: *cgen, n: *node) void = {
// cgwidentaggedstore's non-BP base.
if (sretretsize(c, c.fnret) > 0) {
let sa38v: i32 = localfind(c, "@sretarg");
if (forwardtagged) {
// exact type, but only an N_CALL source
// sret's into outer's dest; a cursor
// source (N_INDEX/N_DOT) can't carry
// >32B (rule 7, #38b residual).
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);
};
if (forwardtagged && rhs.kind == nkind.N_CALL) {
// exact-type N_CALL forward: inner sret's
// into outer's dest; an N_INDEX/N_DOT
// source routes through the widener's
// #37 mem-read arm below instead.
c.sretforward = 1;
cgexpr(c, rhs);
emitline("\tMOVQ\t");
@@ -31886,9 +32092,13 @@ fn cgreturn(c: *cgen, n: *node) void = {
ru38 = ru38.under;
};
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
&& 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";
os.write(2, m38e.ptr, m38e.len: u64);
os.exit(1);