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:
208
cmd/w6c/cgen.c
208
cmd/w6c/cgen.c
@@ -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 "
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user