wcc+w6c+w6c_ww: tagged tuple elements take their full slot — tuple_eslot accessor (#22a)
slot = roundup8(size(elem)) — 8B is a FLOOR, not a ceiling (user- ratified 2026-06-04; the #237 fieldslotsize precedent; (str,str)=48B predates this — tagged was the one truncated >8B kind). Pre-fix the checkers truncated a tagged element to one 8B slot and every cgen transport walk strode wide=(STR||SLICE)-else-8: cstage read the NEIGHBOR slot, wwstage read ZEROS — both-wrong-differently, so the byte-id gates were blind (prober-9 PG1, /tmp/p9). One stride accessor per stage — tuple_eslot (cgen.c) / tupeslot (cgenstmt.ww) — now feeds every tuple walk; the per-site predicates are deleted as absorbed. Sizer: check.c N_TTUPLE + check.ww tupleelemslot gain the TY_TAGGED arm (astsize already rides the type table since commit 0). Routes flipped to the accessor, both stages symmetric: cursor producers (lit/slot-to-cursor; tagged ident elements load their box from the slot — cgexpr's tagged ident load is word0-only), let-receive (tuple_store/tupstore generalized to eslot/8 words), N_RETURN send, by-value param receive, arg restage/drain (tagged stays loud per C-t2), destructure (MLET/MASSIGN, ident + sret + in-cap), t.N read + len(t.N) + global-g.N offset walks (t.N gains the tagged box load: AX=tag, DX/CX/R8=payload — the is/as spill cursor), sret classifier, DATA emit. wwstage cgtagvariantidx gains the #67 stamped-carrier arm (flatvariantidxt on .type_) — its AST-only key silently clamped 't.0 is size' to tag 0; fused here because the tuple-element read this commit wires is its only exercisable consumer. Exit invariant: zero silent tagged-tuple paths — in-cap shapes (<=4 GP eightbytes) are correct end-to-end; everything else is LOUD: over-cap sret return (#22b, task #28), call-arg (C-t2 #32), non-local literal element sources (#22b/#23), tuple-in-union payload (#242/#22b), global static-init, element write (pre-existing). Closure proof-grep at HEAD: 'tuple_ebytes|tupebytes' -> 0 hits; 'wide.*=.*(TY_SLICE|TY_STR)' tuple-walk survivors are all behind tagged loud-guards (cgen.c:2535/2568/12013 widen-store + over-cap send; cgenutil.ww:3527 twin). Latent cross-checks closed by the accessor: wwstage MLET-sret strode esz (4 for i32) vs cstage 8; wwstage param-receive strode slotsize (composite slotsize) vs cstage 8; both now the accessor's floor-8. Tagged inits in pins use the CAST form (5: size) — the bare untyped-int widen-store mis-tag is pre-existing at master and filed (task #33). 941 gains 13 rows: t22_* size/align folds (+ void-elem 0-slot pin), the full runtime round-trip (read, is/as, both element orders, void variant, destructure, literal-let), two-tagged-elem in-cap and float+tagged in BOTH orders (ken k1/k2 regression-pinned as rows), t.N-as-call-arg, 5 loud pins (arg, over-cap return, call-source element, global init, element write), and the sret_narrow_mix_* triple below. Runtime rows exit-checked under both drivers + byte-id. 129/129; unit tests green; sizelint clean. ken R1 (re-validation fix): an OVER-CAP tuple init whose rhs is not a CALL fell past every cstage N_LET store arm to NOTHING — silent uninitialized-frame reads — while wwstage loud-rejected the same shape. Pre-existing for (str,str) literals; the #22a tagged slots routed >16B-box tagged shapes into it (k5b/k5c/k5d, base-correct because base truncated them in-cap). cstage now routes the rhs through cgexpr (the cursor materialisers carry the exact wwstage loud texts) with a trailing fatal as the net; 941 pins both the tagged and the (str,str) spellings of the hole. reviewer-22 (review fixes, folded): the MLET-receive slot flip above landed ALONE on the wwstage over-cap sret family — the ww RETURN-send and MASSIGN-receive still strode packed esz (4 for u32), so `let (a,b,c) = f()` over (str,u32,str) read c at slot offset 32 while ww's send wrote it packed at 28: a ww runtime REGRESSION vs base (base was packed-consistent on both sides — ran right, byte-diff). Both walks now stride the slot (send mirrors cstage's `wide ? esz : 8`; MASSIGN strides tupeslotn) — closing, in the same stroke, the pre-existing base skews: ww `let t = f()` over-cap narrow-mix read (reader slot-laid vs send packed, runtime-wrong at base) and the cs≠ww asm on all three routings. Second find: t.N tagged element as a CALL ARG — cstage rides its generic node_istaggedarg cursor push, but wwstage's kind-gated aistagged missed N_DOT and mis-routed the box into the widening branch (taggedvariantindex -1 clamped to 0: callee read variant 0, silent, ww-only). cgenutil.ww gains the N_DOT arm (aistagged + pushargsrev), the #67 stamped-carrier twin of the N_INDEX (#12) arm. The N_DOT arm was a LIVE wwstage stdlib miscompile: hextest's import graph carried two t.N tagged-arg sites (base .s diff = exactly the clamped-tag PUSHQ pair) — the 989 lib-byteid ratchet caught the convergence and #59.4 graduates to M_ID (runtime-correct per 979_hex_run). Probes: /tmp/r22. Residual filed: tagged arg from deref/cast/unwrap sources is a word0-only read on BOTH stages (pre-existing, base-confirmed) — generalizing wwstage's kind gate to cstage's type-keyed check rides that task.
This commit is contained in:
@@ -288,6 +288,22 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// #22a: t.N tuple-element read leaves the
|
||||
// same AX/DX/CX/R8 box cursor (this arc's
|
||||
// t.N box load) — without this gate the
|
||||
// widening scalar branch clamps the
|
||||
// unresolvable tag to 0 and the callee
|
||||
// reads variant 0. Stamped-carrier (#67)
|
||||
// twin of the N_INDEX arm above; cstage
|
||||
// needs no kind gate (its widen[i] `same`
|
||||
// check is type-keyed on args[i]->type).
|
||||
if (arg.kind == nkind.N_DOT) {
|
||||
if (istaggedtype(c, arg)) {
|
||||
if (slotsize(c, arg) == slotsize(c, ptype)) {
|
||||
aistagged = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (!aistagged) {
|
||||
widensz = slotsize(c, ptype);
|
||||
let tagged: *node = resolvetagged(c, ptype);
|
||||
@@ -766,13 +782,17 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
// tagged is guarded loud above, so wide-vs-scalar is
|
||||
// the full slot split here; eslot keeps the stride
|
||||
// arithmetic on the accessor scale (#22).
|
||||
let wide: bool = false;
|
||||
if (tuplit) { wide = nodeisstr(c, et) || nodeisslice(c, et); }
|
||||
else { wide = isstrtype(c, et) || isslicetype(c, et); };
|
||||
let eslot: i32 = 8;
|
||||
if (wide) { eslot = tyslicesize(): i32; };
|
||||
if (isfloattype(c, et)) { sstot += 1; }
|
||||
else { gptot += tupebytes(wide); };
|
||||
if (wide) { tsz += (tyslicesize(): i32); }
|
||||
else { tsz += 8; };
|
||||
else { gptot += eslot / 8; };
|
||||
tsz += eslot;
|
||||
p = p.next;
|
||||
};
|
||||
// The producing cursor fill already satisfied #164's caps;
|
||||
@@ -793,11 +813,12 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
let wide: bool = false;
|
||||
if (tuplit) { wide = nodeisstr(c, et) || nodeisslice(c, et); }
|
||||
else { wide = isstrtype(c, et) || isslicetype(c, et); };
|
||||
tupstore(c, gpcur, ssecur, scr + eoff, wide, et);
|
||||
let eslot: i32 = 8;
|
||||
if (wide) { eslot = tyslicesize(): i32; };
|
||||
tupstore(c, gpcur, ssecur, scr + eoff, eslot, et);
|
||||
if (isfloattype(c, et)) { ssecur += 1; }
|
||||
else { gpcur += tupebytes(wide); };
|
||||
if (wide) { eoff += (tyslicesize(): i32); }
|
||||
else { eoff += 8; };
|
||||
else { gpcur += eslot / 8; };
|
||||
eoff += eslot;
|
||||
p = p.next;
|
||||
};
|
||||
let w: i32 = tsz - 8;
|
||||
@@ -870,7 +891,13 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
// arg.type_ is the element tinfo (istaggedtype/slotsize read
|
||||
// .type_) — feed the N_INDEX node directly, dropping the
|
||||
// indexvaluetnode walk.
|
||||
if (arg.kind == nkind.N_INDEX) {
|
||||
// #22a: N_DOT rides the same arm — the t.N tuple-element box load
|
||||
// (this arc) fills the identical AX/DX/CX/R8 cursor. cstage's twin
|
||||
// is the generic node_istaggedarg push (cgen.c cgcall); wwstage
|
||||
// keeps the stamped-carrier kind gate (#67 pattern) — the
|
||||
// remaining kinds (deref/cast/unwrap) are word0-only reads today,
|
||||
// filed residual.
|
||||
if (arg.kind == nkind.N_INDEX || arg.kind == nkind.N_DOT) {
|
||||
if (istaggedtype(c, arg)) {
|
||||
let isz: i32 = slotsize(c, arg);
|
||||
if (isz > 24) { emitline("\tPUSHQ\tR8\n"); };
|
||||
@@ -1676,8 +1703,7 @@ export fn sretretsize(c: *cgen, t: *node) i32 = {
|
||||
if (isfloattype(c, et)) {
|
||||
ssecount = ssecount + 1;
|
||||
} else {
|
||||
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tupeslotn(et) / 8;
|
||||
};
|
||||
pt = pt.next;
|
||||
};
|
||||
@@ -3453,6 +3479,20 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
|
||||
let ttotal: i32 = 0;
|
||||
let ce: *node = tupsrc.list;
|
||||
for (ce != nil) {
|
||||
// #22a (rule 7): a tagged element's box can't
|
||||
// ride the scalar/wide store arms below —
|
||||
// pre-guard it silently stored word0. Nested
|
||||
// tagged-in-tuple-in-union packing is the
|
||||
// #242/#22b family. Mirrors cstage.
|
||||
let ceti: *tinfo = ce.type_: *tinfo;
|
||||
for (ceti != nil && ceti.kind == tykind.TY_NAMED) {
|
||||
ceti = ceti.under;
|
||||
};
|
||||
if (ceti != nil && ceti.kind == tykind.TY_TAGGED) {
|
||||
let m22: str = "cgwidentaggedstore: tagged element in a tuple-in-union payload unwired (see #242/#22b)\n";
|
||||
os.write(2, m22.ptr, m22.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (nodeisstr(c, ce) || nodeisslice(c, ce)) {
|
||||
ttotal += 24;
|
||||
} else { ttotal += 8; };
|
||||
|
||||
Reference in New Issue
Block a user