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