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;
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
static int
|
||||
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 "
|
||||
"tagged slot (mem-to-mem widen "
|
||||
"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);
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
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)
|
||||
ins2(c, A_MOVQ, areg(D_R8),
|
||||
amem(D_BP, write_off + 32));
|
||||
}
|
||||
}
|
||||
ins2(c, A_MOVQ, aimm(nested),
|
||||
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),
|
||||
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 {
|
||||
/* Tagged source returned via the tagged-return ABI
|
||||
* (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 "
|
||||
"cursor-widened into a tagged slot "
|
||||
"(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);
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, write_off + 0));
|
||||
@@ -8550,6 +8604,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
fatal("#38b: >32B tagged call result "
|
||||
"as a call argument unwired "
|
||||
"(#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);
|
||||
if (sz > 24)
|
||||
ins1(c, A_PUSHQ, areg(D_R8));
|
||||
@@ -9008,6 +9071,19 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* dispatch fired on a stale slot. */
|
||||
sl_off = localoff(c, &locals, "@match_spill",
|
||||
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);
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, sl_off + 0));
|
||||
@@ -9021,6 +9097,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins2(c, A_MOVQ, areg(D_R8),
|
||||
amem(D_BP, sl_off + 24));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* 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;
|
||||
cgexpr(c, s, locals);
|
||||
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 {
|
||||
/* #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);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sl_off + 0));
|
||||
if (!is_nullable) {
|
||||
@@ -9183,6 +9278,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
fatal("#38b: `?` on an sret-class call result "
|
||||
"unwired (mem-based unwrap is a #40-family "
|
||||
"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)
|
||||
fatal("#38b: `?` propagation into a >32B tagged "
|
||||
"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 "
|
||||
"unwired (mem-based unwrap is a #40-family "
|
||||
"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);
|
||||
Type *u = n->lhs ? n->lhs->type : NULL;
|
||||
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 "
|
||||
"unwired (#40-family follow-up)");
|
||||
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;
|
||||
if (u && u->kind == TY_NAMED) u = u->under;
|
||||
Type *vt = n->rhs ? n->rhs->type : NULL;
|
||||
@@ -9399,6 +9507,24 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (sl_off == 0) {
|
||||
sl_off = localoff(c, &locals, "@asrt_spill",
|
||||
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);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sl_off + 0));
|
||||
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),
|
||||
amem(D_BP, sl_off + 16));
|
||||
}
|
||||
}
|
||||
}
|
||||
char *ok = mklabel(c, "asrt_ok");
|
||||
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. */
|
||||
if (fu && fu->kind == TY_TAGGED) {
|
||||
int eslot = tuple_eslot(tp->type);
|
||||
/* #22b (rule 7): a >32B box overruns the
|
||||
* 4-reg cursor — pre-bound the walk
|
||||
* indexed past tuple_rseq (invalid asm)
|
||||
* while wwstage clamped to R8 (silent
|
||||
* payload drop). Reachable only since
|
||||
* the over-cap sret send unwired; the
|
||||
* mem-based box read is the #37 family. */
|
||||
if (eslot > TUPLE_GPCAP * 8)
|
||||
fatal("tagged tuple element read "
|
||||
"exceeds the AX/DX/CX/R8 box "
|
||||
"cursor (mem-based read is the "
|
||||
"#37 family; rule 7)");
|
||||
/* #37: a >32B box overruns the 4-reg
|
||||
* cursor — leave its ADDRESS in AX
|
||||
* (cg_tagged_memread, the sret-receive
|
||||
* convention); consumers copy from
|
||||
* memory. Replaces the #22b loud
|
||||
* bound (pre-bound: cstage indexed
|
||||
* past tuple_rseq = invalid asm,
|
||||
* wwstage clamped to R8 = silent
|
||||
* payload drop). */
|
||||
if (eslot > TUPLE_GPCAP * 8) {
|
||||
ins2(c, A_LEAQ,
|
||||
amem(base_reg,
|
||||
base_disp + foff),
|
||||
areg(D_AX));
|
||||
break;
|
||||
}
|
||||
for (int k = 0; k < eslot / 8; k++)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(base_reg,
|
||||
@@ -10074,6 +10205,17 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
? f->type->under : f->type;
|
||||
if (tag_fu && tag_fu->kind == TY_TAGGED) {
|
||||
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,
|
||||
amem(base_reg, fo + 0), areg(D_AX));
|
||||
ins2(c, A_MOVQ,
|
||||
@@ -10182,6 +10324,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
? f->type->under : f->type;
|
||||
if (ptag_fu && ptag_fu->kind == TY_TAGGED) {
|
||||
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,
|
||||
amem(D_BX, fo + 0),
|
||||
areg(D_AX));
|
||||
@@ -10613,6 +10764,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* pointer). */
|
||||
if (elem_tagged) {
|
||||
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)
|
||||
ins2(c, A_MOVQ, amem(D_BX, 24),
|
||||
areg(D_R8));
|
||||
@@ -10681,6 +10841,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* load slot words. */
|
||||
if (elem_tagged) {
|
||||
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));
|
||||
if (ssz > 24)
|
||||
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. */
|
||||
if (cg_sret_retsize(rt) > 0) {
|
||||
int sz = (int)rt->size;
|
||||
if (passthrough) {
|
||||
/* exact type but a cursor source
|
||||
* (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);
|
||||
if (passthrough
|
||||
&& n->lhs->kind == N_CALL) {
|
||||
cg_sret_forward = 1;
|
||||
cgexpr(c, n->lhs, *locals);
|
||||
ins2(c, A_MOVQ,
|
||||
@@ -11592,7 +11747,12 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
areg(D_AX));
|
||||
} else if (istagged
|
||||
&& 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 "
|
||||
"return-forward of a >32B "
|
||||
"source needs mem-to-mem "
|
||||
|
||||
Reference in New Issue
Block a user