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

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

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

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

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

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

View File

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