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:
2026-06-05 00:32:35 +09:00
parent ed62e8199f
commit 66991585d6
12 changed files with 1449 additions and 523 deletions

View File

@@ -11864,6 +11864,13 @@ fn tupleelemslot(pt: *tinfo) u64 = {
if (pk == tykind.TY_VOID) { return 0u64; };
if (pk == tykind.TY_STR) { return t.size; };
if (pk == tykind.TY_SLICE) { return t.size; };
// #22 (user-ratified 2026-06-04): slot = roundup8(size(elem)) — 8B
// is a FLOOR, not a ceiling. (str,str)=48B predates this; tagged
// was the one truncated >8B kind (the #237 fieldslotsize-missing-
// TY_TUPLE precedent: fieldslotsize below already carried this
// arm). Cstage twin: check.c N_TTUPLE; cgen accessor: tuple_eslot
// / tupeslot.
if (pk == tykind.TY_TAGGED) { return (t.size + 7u64) & ~7u64; };
if (pk == tykind.TY_PTR || pk == tykind.TY_FN ||
pk == tykind.TY_CHAN || pk == tykind.TY_I64 ||
pk == tykind.TY_U64 || pk == tykind.TY_INT ||
@@ -16273,6 +16280,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);
@@ -16751,13 +16774,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;
@@ -16778,11 +16805,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;
@@ -16855,7 +16883,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"); };
@@ -17661,8 +17695,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;
};
@@ -19438,6 +19471,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; };
@@ -20570,7 +20617,22 @@ fn cgexpr(c: *cgen, n: *node) void = {
fn cgtagvariantidx(c: *cgen, tagged: *node, vt: *node) i32 = {
if (tagged == nil) { return -1; };
if (vt == nil) { return -1; };
if (tagged.kind != nkind.N_TTAGGED) { return -1; };
if (tagged.kind != nkind.N_TTAGGED) {
// #22a: a STAMPED-CARRIER scrutinee (the #67 matchscrutt
// shape — is/as on a tuple element t.N, a struct field, an
// indexed element) is not an N_TTAGGED type-AST node; its
// tagged type rides .type_. Resolve via the tinfo twin
// (flatvariantidxt), the same core the widen-store uses —
// cstage cg_tag_for_variant is type-based for every
// scrutinee shape, so the AST-keyed -1 here was a silent
// tag-0 clamp on wwstage (cs CMPQ $1 vs ww CMPQ $0).
let sti: *tinfo = tagged.type_: *tinfo;
for (sti != nil && sti.kind == tykind.TY_NAMED) { sti = sti.under; };
if (sti != nil && sti.kind == tykind.TY_TAGGED && vt.type_ != nil) {
return flatvariantidxt(sti, vt.type_: *tinfo);
};
return -1;
};
// `is []T` / `as []T` — slice-shape lookup routes through the
// element-aware helper, which carries the loose first-slice-shape
// fallback (cstage type_assignable stand-in) that flatvariantidx's
@@ -23080,7 +23142,9 @@ fn cgdot(c: *cgen, n: *node) void = {
for (i < idx) {
if (tp == nil) { i = idx; }
else {
foff += slotsize(c, tp.lhs);
// C-t0/#22: slot stride
// (tupeslot accessor).
foff += tupeslotn(tp.lhs);
tp = tp.next;
i += 1;
};
@@ -23114,6 +23178,25 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("(BP), X0\n");
return;
};
// #22a: tagged element — load the box
// into the tagged value regs (AX=tag,
// DX/CX/R8=payload), the cursor the
// is/as spill + match read. Byte-id
// twin of cstage's N_DOT TY_TUPLE
// tagged arm.
if (istaggedtype(c, tpt)) {
let eslot: i32 = tupeslotn(tpt);
let k: i32 = 0;
for (k < eslot / 8) {
emitline("\tMOVQ\t");
emitoff((lc.off + foff + k * 8): i64);
emitline("(BP), ");
emitline(tupreg(k));
emitline("\n");
k += 1;
};
return;
};
// C-t0: load at the element's NATURAL
// width (narrow MOVL/MOVSXD/... at the
// slot base), not the 8B slot width —
@@ -23284,7 +23367,9 @@ fn cgdot(c: *cgen, n: *node) void = {
for (gi < gidx) {
if (gtp == nil) { gi = gidx; }
else {
gfoff += slotsize(c, gtp.lhs);
// C-t0/#22: slot stride
// (tupeslot accessor).
gfoff += tupeslotn(gtp.lhs);
gtp = gtp.next;
gi += 1;
};
@@ -26474,7 +26559,9 @@ fn cgcall(c: *cgen, n: *node) void = {
for (i < idx) {
if (tp == nil) { i = idx; }
else {
foff += slotsize(c, tp.lhs);
// C-t0/#22: slot
// stride (tupeslot).
foff += tupeslotn(tp.lhs);
tp = tp.next;
i += 1;
};
@@ -26510,7 +26597,9 @@ fn cgcall(c: *cgen, n: *node) void = {
for (gi < gidx) {
if (gtp == nil) { gi = gidx; }
else {
gfoff += slotsize(c, gtp.lhs);
// C-t0/#22: slot
// stride (tupeslot).
gfoff += tupeslotn(gtp.lhs);
gtp = gtp.next;
gi += 1;
};
@@ -26963,10 +27052,14 @@ fn cgcall(c: *cgen, n: *node) void = {
fpidx += 1;
popped += 1;
} else {
// tagged is guarded loud at the restage, so
// wide-vs-scalar is the full slot split here
// (#22 accessor scale).
let wide: bool = false;
if (tuplit) { wide = nodeisstr(c, et) || nodeisslice(c, et); }
else { wide = isstrtype(c, et) || isslicetype(c, et); };
let eb: i32 = tupebytes(wide);
let eb: i32 = 1;
if (wide) { eb = (tyslicesize() / 8i64): i32; };
if (intidx + eb > 6) {
let msg: str = "tuple arg element overflows integer arg regs (DI/SI/DX/CX/R8/R9); stitch out of scope, see #163\n";
os.write(2, msg.ptr, msg.len: u64);
@@ -31050,9 +31143,36 @@ fn tupsse(i: i32) str = {
return "X1";
};
fn tupebytes(wide: bool) i32 = {
if (wide) { return (tyslicesize() / 8i64): i32; };
return 1;
// tupeslot — THE tuple element-stride accessor (#22): the slot a tuple
// element occupies, in bytes. slot = roundup8(size(elem)), 8B a FLOOR
// not a ceiling (user-ratified 2026-06-04): str/slice carry their 24B
// header, a tagged element its full tag+payload box ((str,str)=48B
// predates this; tagged was the one truncated >8B kind — the #237
// fieldslotsize precedent), narrow scalars pad UP to one 8B eightbyte.
// Every tuple walk (cursor send/receive, t.N read, destructure, sret
// classify, DATA emit) takes its stride and its eightbyte count
// (eslot/8) from here — the per-site wide=(STR||SLICE)-else-8
// predicates this absorbs were the #22 neighbor-slot/zeros miscompile.
// Checker twin: check.ww tupleelemslot / check.c N_TTUPLE; cstage twin:
// tuple_eslot (cmd/w6c/cgen.c).
export fn tupeslot(ti: *tinfo) i32 = {
let t: *tinfo = ti;
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
if (t == nil) { return 8; };
if (t.kind == tykind.TY_VOID) { return 0; };
// a literal tuple's stamped element can be untyped_str (size 0) —
// it occupies the str header slot (the C-t2 type_isstr lesson).
if (t.kind == tykind.TY_UNTYPED_STR) { return tyslicesize(): i32; };
if (t.kind == tykind.TY_STR || t.kind == tykind.TY_SLICE ||
t.kind == tykind.TY_TAGGED) {
return ((t.size + 7u64) & ~7u64): i32;
};
return 8;
};
export fn tupeslotn(n: *node) i32 = {
if (n == nil) { return 8; };
return tupeslot(n.type_: *tinfo);
};
// rettupleof — the N_TTUPLE return-type node of an N_CALL rhs (else nil).
@@ -31119,29 +31239,24 @@ fn nodetuplearg(c: *cgen, a: *node) *node = {
};
// tupstore — store the tuple element at register-cursor `cur` into the
// BP-relative slot at `off`. A slice/str stores its 3-word {ptr,len,cap}
// header (ref/hare/rt/ensure.ha:4-8) at off/+8/+16 from consecutive
// INTEGER cursor registers; a float rides the SSE cursor (X0,X1); a
// scalar stores 1 INTEGER word. The caller owns the dual cursor
// (validated + advanced). Byte-identical to the cstage tuple_store
// (cmd/w6c/cgen.c).
fn tupstore(c: *cgen, gpcur: i32, ssecur: i32, off: i32, wide: bool, tn: *node) void = {
if (wide) {
emitline("\tMOVQ\t");
emitline(tupreg(gpcur + 0));
emitline(", ");
emitoff(off: i64);
emitline("(BP)\n");
emitline("\tMOVQ\t");
emitline(tupreg(gpcur + 1));
emitline(", ");
emitoff((off + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\t");
emitline(tupreg(gpcur + 2));
emitline(", ");
emitoff((off + 16): i64);
emitline("(BP)\n");
// BP-relative slot at `off`. A >8B element (slice/str 3-word
// {ptr,len,cap} header, ref/hare/rt/ensure.ha:4-8; tagged tag+payload
// box, #22) stores its eslot/8 words from consecutive INTEGER cursor
// registers; a float rides the SSE cursor (X0,X1); a scalar stores 1
// INTEGER word. The caller owns the dual cursor (validated +
// advanced). Byte-identical to the cstage tuple_store (cmd/w6c/cgen.c).
fn tupstore(c: *cgen, gpcur: i32, ssecur: i32, off: i32, eslot: i32, tn: *node) void = {
if (eslot == 0) { return; }; // void element: the checker's 0-slot
if (eslot > 8) {
let k: i32 = 0;
for (k < eslot / 8) {
emitline("\tMOVQ\t");
emitline(tupreg(gpcur + k));
emitline(", ");
emitoff((off + k * 8): i64);
emitline("(BP)\n");
k += 1;
};
return;
};
// #105 / #164 (#107): an f64/f32 element rides the SSE cursor reg
@@ -31195,6 +31310,67 @@ fn tupstore(c: *cgen, gpcur: i32, ssecur: i32, off: i32, wide: bool, tn: *node)
emitline("(BP)\n");
};
// tuplitgpwords — INTEGER cursor words an N_TUPLE literal element
// occupies. MUST mirror the literal push arms (tuplitpushelem) exactly
// — the count drives the POP fill, so a count/push skew silently
// shifts every later element (#22 class). A float rides the SSE row
// (0 GP words); str/slice push their 3-word header; a tagged element
// its tupeslot/8 box words; a void element pushes nothing (the
// checker's 0-slot); a scalar 1. Mirror of cstage tuple_lit_gpwords.
fn tuplitgpwords(c: *cgen, e: *node) i32 = {
if (isfloattype(c, e)) { return 0; };
if (nodeisstr(c, e) || nodeisslice(c, e)) {
return (tyslicesize() / 8i64): i32;
};
let t: *tinfo = e.type_: *tinfo;
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
if (t != nil && (t.kind == tykind.TY_TAGGED ||
t.kind == tykind.TY_VOID)) {
return tupeslotn(e) / 8;
};
return 1;
};
// tuplitpushelem — evaluate one N_TUPLE literal element and push its
// INTEGER cursor words L->R (the pop side fills tupreg in reverse). A
// tagged element loads its box words straight from its local slot —
// cgexpr's ident load is word0-only for tagged (every tagged consumer
// reads memory), so the cursor fill must too; any other tagged source
// shape is loud (rule 7; the cursor-receive arm for call results is
// with #22b's tupstore work, widening literals with #23). Mirror of
// cstage tuple_lit_push_elem — count (tuplitgpwords) and push live or
// die together.
fn tuplitpushelem(c: *cgen, e: *node) void = {
let t: *tinfo = e.type_: *tinfo;
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
if (t != nil && t.kind == tykind.TY_TAGGED) {
let eslot: i32 = tupeslotn(e);
let eoff: i32 = 0;
if (e.kind == nkind.N_IDENT) { eoff = localfind(c, e.str); };
if (eoff == 0) {
let m22: str = "#22a: tagged tuple element from a non-local source shape unwired (ident locals only; rule 7, #22b/#23 follow-ups)\n";
os.write(2, m22.ptr, m22.len: u64);
os.exit(1);
};
let k: i32 = 0;
for (k < eslot / 8) {
emitline("\tMOVQ\t");
emitoff((eoff + k * 8): i64);
emitline("(BP), AX\n");
emitline("\tPUSHQ\tAX\n");
k += 1;
};
return;
};
cgexpr(c, e);
if (t != nil && t.kind == tykind.TY_VOID) { return; };
emitline("\tPUSHQ\tAX\n");
if (nodeisstr(c, e) || nodeisslice(c, e)) {
emitline("\tPUSHQ\tBX\n");
emitline("\tPUSHQ\tCX\n");
};
};
// cgtuplelittocursor — #241: materialise an N_TUPLE literal's elements into
// the SysV register-return cursor (integer words L->R over tupreg AX/DX/CX/
// R8, floats over tupsse X0/X1, a slice/str's {ptr,len,cap} over three
@@ -31214,8 +31390,7 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
if (isfloattype(c, e)) {
ssecount = ssecount + 1;
} else {
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
gptotal = gptotal + tupebytes(wide);
gptotal = gptotal + tuplitgpwords(c, e);
};
e = e.next;
};
@@ -31232,8 +31407,8 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
e = tuple.list;
for (e != nil) {
let isflt: bool = isfloattype(c, e);
cgexpr(c, e);
if (isflt) {
cgexpr(c, e);
let mov: str = "MOVSD";
if (isf32type(c, e)) { mov = "MOVSS"; };
emitline("\t"); emitline(mov); emitline("\tX0, ");
@@ -31241,11 +31416,7 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
emitline("(BP)\n");
sseidx = sseidx + 1;
} else {
emitline("\tPUSHQ\tAX\n");
if (nodeisstr(c, e) || nodeisslice(c, e)) {
emitline("\tPUSHQ\tBX\n");
emitline("\tPUSHQ\tCX\n");
};
tuplitpushelem(c, e);
};
e = e.next;
};
@@ -31292,8 +31463,7 @@ fn cgtupleslottocursor(c: *cgen, srcoff: i32, tu: *tinfo) void = {
if (et != nil && (et.kind == tykind.TY_F32 || et.kind == tykind.TY_F64)) {
ssecount = ssecount + 1;
} else {
let wide: bool = et != nil && (et.kind == tykind.TY_SLICE || et.kind == tykind.TY_STR);
gptotal = gptotal + tupebytes(wide);
gptotal = gptotal + tupeslot(el.type_) / 8;
};
el = el.tnext;
};
@@ -31310,7 +31480,7 @@ fn cgtupleslottocursor(c: *cgen, srcoff: i32, tu: *tinfo) void = {
let et: *tinfo = el.type_;
for (et != nil && et.kind == tykind.TY_NAMED) { et = et.under; };
let isflt: bool = et != nil && (et.kind == tykind.TY_F32 || et.kind == tykind.TY_F64);
let wide: bool = et != nil && (et.kind == tykind.TY_SLICE || et.kind == tykind.TY_STR);
let eslot: i32 = tupeslot(el.type_);
if (isflt) {
let mov: str = "MOVSD";
if (et.kind == tykind.TY_F32) { mov = "MOVSS"; };
@@ -31321,9 +31491,9 @@ fn cgtupleslottocursor(c: *cgen, srcoff: i32, tu: *tinfo) void = {
emitline("\n");
sse = sse + 1;
foff += 8;
} else { if (wide) {
} else {
let k: i32 = 0;
for (k < 3) {
for (k < eslot / 8) {
emitline("\tMOVQ\t");
emitoff((srcoff + foff + k * 8): i64);
emitline("(BP), ");
@@ -31331,17 +31501,9 @@ fn cgtupleslottocursor(c: *cgen, srcoff: i32, tu: *tinfo) void = {
emitline("\n");
k += 1;
};
gp += 3;
foff += et.size: i32;
} else {
emitline("\tMOVQ\t");
emitoff((srcoff + foff): i64);
emitline("(BP), ");
emitline(tupreg(gp));
emitline("\n");
gp += 1;
foff += 8;
}; };
gp += eslot / 8;
foff += eslot;
};
el = el.tnext;
};
};
@@ -31361,9 +31523,8 @@ fn cgtaggedtuplepayloadshift(c: *cgen, tup: *tinfo) void = {
let et: *tinfo = el.type_;
for (et != nil && et.kind == tykind.TY_NAMED) { et = et.under; };
let isflt: bool = et != nil && (et.kind == tykind.TY_F32 || et.kind == tykind.TY_F64);
let wide: bool = et != nil && (et.kind == tykind.TY_SLICE || et.kind == tykind.TY_STR);
if (isflt || wide) {
let msg: str = "tuple-in-union ? unwrap: float/slice/str payload element needs SysV per-eightbyte classification (see #243); only integer tuple payloads supported\n";
if (isflt || tupeslot(el.type_) != 8) {
let msg: str = "tuple-in-union ? unwrap: float/slice/str/tagged payload element needs SysV per-eightbyte classification (see #243); only integer tuple payloads supported\n";
os.write(2, msg.ptr, msg.len: u64);
os.exit(1);
};
@@ -31420,12 +31581,28 @@ fn cgreturn(c: *cgen, n: *node) void = {
if (isfloattype(c, e)) {
ssecount = ssecount + 1;
} else {
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
gptotal = gptotal + tupebytes(wide);
gptotal = gptotal + tuplitgpwords(c, e);
};
e = e.next;
};
if (gptotal > TUPLE_GPCAP || ssecount > ssecap) {
// #22a (rule 7): a tagged element's box can't
// ride the scalar/wide store arms below — the
// MEMORY-class tagged element store is #22b
// (task #28); pre-guard it silently stored
// word0 of the box.
let gq: *node = nil;
if (c.fnret != nil) { gq = c.fnret.list; };
for (gq != nil) {
let gqt: *tinfo = gq.lhs.type_: *tinfo;
for (gqt != nil && gqt.kind == tykind.TY_NAMED) { gqt = gqt.under; };
if (gqt != nil && gqt.kind == tykind.TY_TAGGED) {
let m22b: str = "#22b: tagged element in an over-cap (sret) tuple return unwired (rule 7)\n";
os.write(2, m22b.ptr, m22b.len: u64);
os.exit(1);
};
gq = gq.next;
};
// #10 Fold A: over-cap tuple returns via sret. The
// prologue wired @sretarg (sretretsize agrees on the
// caps — TUPLE_GPCAP/TUPLE_SSECAP, the shared SSoT),
@@ -31485,7 +31662,12 @@ fn cgreturn(c: *cgen, n: *node) void = {
emitline("\n");
};
};
foff += esz;
// C-t0: the sret buffer is slot-laid like
// every tuple home (checker size, t.N
// reader, mlet receive agree); esz keeps
// the store WIDTH natural. Mirrors cstage
// cgen.c N_RETURN over-cap arm.
if (wide) { foff += esz; } else { foff += 8; };
we = we.next;
if (pt != nil) { pt = pt.next; };
};
@@ -31506,8 +31688,8 @@ fn cgreturn(c: *cgen, n: *node) void = {
e = rhs.list;
for (e != nil) {
let isflt: bool = isfloattype(c, e);
cgexpr(c, e);
if (isflt) {
cgexpr(c, e);
let mov: str = "MOVSD";
if (isf32type(c, e)) { mov = "MOVSS"; };
emitline("\t");
@@ -31517,11 +31699,9 @@ fn cgreturn(c: *cgen, n: *node) void = {
emitline("(BP)\n");
sseidx = sseidx + 1;
} else {
emitline("\tPUSHQ\tAX\n"); // scalar / .ptr
if (nodeisstr(c, e) || nodeisslice(c, e)) {
emitline("\tPUSHQ\tBX\n"); // .len
emitline("\tPUSHQ\tCX\n"); // .cap
};
// scalar=AX; slice/str=AX,BX,CX; tagged
// box from its slot (tuplitpushelem)
tuplitpushelem(c, e);
};
e = e.next;
};
@@ -32962,20 +33142,15 @@ fn cglet(c: *cgen, n: *node) void = {
for (q != nil) {
let qt: *node = q.lhs;
let isflt: bool = isfloattype(c, qt);
let wide: bool = isstrtype(c, qt)
|| isslicetype(c, qt);
let eslot: i32 = tupeslotn(qt);
tupstore(c, gpcur, ssecur,
off + eoff, wide, qt);
off + eoff, eslot, qt);
if (isflt) {
ssecur = ssecur + 1;
} else {
gpcur = gpcur + tupebytes(wide);
};
if (wide) {
eoff = eoff + (tyslicesize(): i32);
} else {
eoff = eoff + 8;
gpcur = gpcur + eslot / 8;
};
eoff = eoff + eslot;
q = q.next;
};
c.lastwasreturn = 0;
@@ -33816,7 +33991,10 @@ fn cgmassign(c: *cgen, n: *node) void = {
};
};
};
foff += esz;
// C-t0: slot stride — must mirror the N_RETURN
// over-cap SEND's buffer layout (cstage N_MASSIGN
// twin strides tuple_eslot).
foff += tupeslotn(tn);
lb = lb.next;
if (pt2 != nil) { pt2 = pt2.next; };
};
@@ -33836,8 +34014,7 @@ fn cgmassign(c: *cgen, n: *node) void = {
if (isfloattype(c, tn)) {
ssetotal = ssetotal + 1;
} else {
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
gptotal = gptotal + tupebytes(wide);
gptotal = gptotal + tupeslotn(tn) / 8;
};
l = l.next;
if (pt != nil) { pt = pt.next; };
@@ -33864,18 +34041,18 @@ fn cgmassign(c: *cgen, n: *node) void = {
let tn: *node = nil;
if (pt != nil) { tn = pt.lhs; };
let isflt: bool = isfloattype(c, tn);
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
let eslot: i32 = tupeslotn(tn);
let off: i32 = 0;
if (l.kind == nkind.N_IDENT) { off = localfind(c, l.str); };
// harec `_` (off==0): skip the store but CONSUME the cursor
// slot so the next element stays aligned.
if (off != 0) {
tupstore(c, gpcur, ssecur, off, wide, tn);
tupstore(c, gpcur, ssecur, off, eslot, tn);
};
if (isflt) {
ssecur = ssecur + 1;
} else {
gpcur = gpcur + tupebytes(wide);
gpcur = gpcur + eslot / 8;
};
l = l.next;
if (pt != nil) { pt = pt.next; };
@@ -33938,13 +34115,13 @@ fn cgmlet(c: *cgen, n: *node) void = {
for (lb != nil) {
let tn: *node = lb.lhs;
let isflt: bool = isfloattype(c, tn);
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
let eslot: i32 = tupeslotn(tn);
let esz: i32 = 8;
let eti: *tinfo = nil;
if (tn != nil) { eti = tn.type_: *tinfo; };
if (eti != nil) { esz = eti.size: i32; };
let bsz: i32 = 8;
if (wide) { bsz = tyslicesize(): i32; };
if (eslot > 8) { bsz = eslot; };
let off: i32 = localadd(c, lb.str, bsz, tn);
if (isflt) {
let mov: str = "MOVSD";
@@ -33954,9 +34131,9 @@ fn cgmlet(c: *cgen, n: *node) void = {
emitline("(BP), X0\n");
emitline("\t"); emitline(mov); emitline("\tX0, ");
emitoff(off: i64); emitline("(BP)\n");
} else { if (wide) {
} else { if (eslot > 8) {
let k: i32 = 0;
for (k < esz) {
for (k < eslot) {
emitline("\tMOVQ\t");
emitoff((srcoff + foff + k): i64);
emitline("(BP), AX\n");
@@ -33974,7 +34151,7 @@ fn cgmlet(c: *cgen, n: *node) void = {
emitline("\t"); emitline(sop); emitline("\tAX, ");
emitoff(off: i64); emitline("(BP)\n");
}; };
if (wide) { foff += 24; } else { foff += 8; };
foff += eslot;
lb = lb.next;
};
c.lastwasreturn = 0;
@@ -33995,14 +34172,14 @@ fn cgmlet(c: *cgen, n: *node) void = {
let tn: *node = nil;
if (pt2 != nil) { tn = pt2.lhs; };
let isflt: bool = isfloattype(c, tn);
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
let eslot: i32 = tupeslotn(tn);
let esz: i32 = 8;
if (pt2 != nil) {
let eti: *tinfo = pt2.lhs.type_: *tinfo;
if (eti != nil) { esz = eti.size: i32; };
};
let bsz: i32 = 8;
if (wide) { bsz = tyslicesize(): i32; };
if (eslot > 8) { bsz = eslot; };
let off: i32 = localadd(c, lb.str, bsz, tn);
if (isflt) {
let mov: str = "MOVSD";
@@ -34013,9 +34190,9 @@ fn cgmlet(c: *cgen, n: *node) void = {
emitline("\t"); emitline(mov); emitline("\tX0, ");
emitoff(off: i64); emitline("(BP)\n");
} else {
if (wide) {
if (eslot > 8) {
let k: i32 = 0;
for (k < esz) {
for (k < eslot) {
emitline("\tMOVQ\t");
emitoff((scr + foff + k): i64);
emitline("(BP), AX\n");
@@ -34035,7 +34212,7 @@ fn cgmlet(c: *cgen, n: *node) void = {
emitoff(off: i64); emitline("(BP)\n");
};
};
foff += esz;
foff += eslot;
lb = lb.next;
if (pt2 != nil) { pt2 = pt2.next; };
};
@@ -34057,8 +34234,7 @@ fn cgmlet(c: *cgen, n: *node) void = {
if (isfloattype(c, tn)) {
ssetotal = ssetotal + 1;
} else {
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
gptotal = gptotal + tupebytes(wide);
gptotal = gptotal + tupeslotn(tn) / 8;
};
l = l.next;
if (pt != nil) { pt = pt.next; };
@@ -34087,15 +34263,15 @@ fn cgmlet(c: *cgen, n: *node) void = {
if (pt != nil) { tn = pt.lhs; };
};
let isflt: bool = isfloattype(c, tn);
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
let eslot: i32 = tupeslotn(tn);
let sz: i32 = 8;
if (wide) { sz = tyslicesize(): i32; };
if (eslot > 8) { sz = eslot; };
let off: i32 = localadd(c, l.str, sz, tn);
tupstore(c, gpcur, ssecur, off, wide, tn);
tupstore(c, gpcur, ssecur, off, eslot, tn);
if (isflt) {
ssecur = ssecur + 1;
} else {
gpcur = gpcur + tupebytes(wide);
gpcur = gpcur + eslot / 8;
};
l = l.next;
if (pt != nil) { pt = pt.next; };
@@ -34745,8 +34921,7 @@ fn cgfnparams(c: *cgen, params: *node) void = {
emitline("(BP)\n");
fidx += 1;
} else {
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
let eb: i32 = tupebytes(wide);
let eb: i32 = tupeslotn(et) / 8;
if (idx + eb > 6) {
let msg: str = "tuple param element overflows integer arg regs (DI/SI/DX/CX/R8/R9); stitch out of scope, see #163\n";
os.write(2, msg.ptr, msg.len: u64);
@@ -34763,7 +34938,7 @@ fn cgfnparams(c: *cgen, params: *node) void = {
k += 1;
};
};
eoff += slotsize(c, et);
eoff += tupeslotn(et);
te = te.next;
};
p = p.next;
@@ -37456,15 +37631,12 @@ fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo,
fn emittupledata(c: *cgen, name: str, module: str, tt: *node, rhs: *node) bool = {
if (tt == nil) { return false; };
if (rhs == nil) {
// #22: slot-sum via the accessor so the zero-fill matches
// the checker size (cstage zero-emits u->size).
let zsz: i32 = 0;
let p0: *node = tt.list;
for (p0 != nil) {
let et0: *node = p0.lhs;
if (isstrtype(c, et0) || isslicetype(c, et0)) {
zsz += (tyslicesize(): i32);
} else {
zsz += 8;
};
zsz += tupeslotn(p0.lhs);
p0 = p0.next;
};
emitline("DATAW ");
@@ -37486,6 +37658,21 @@ fn emittupledata(c: *cgen, name: str, module: str, tt: *node, rhs: *node) bool =
let ev: *node = e;
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
if (ev == nil) { return false; };
// #22a (rule 7): a tagged element slot has no static-init
// shape (tag word + payload widening) — reject so the caller
// loud-stops; pre-guard an int init would have emitted one
// 8B word into the 16B+ box (silent layout skew). Mirrors
// cstage emit_tuple_data.
{
let eti: *tinfo = nil;
if (et != nil) { eti = et.type_: *tinfo; };
for (eti != nil && eti.kind == tykind.TY_NAMED) {
eti = eti.under;
};
if (eti != nil && eti.kind == tykind.TY_TAGGED) {
return false;
};
};
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
if (wide) {
if (ev.kind != nkind.N_STRLIT) { return false; };
@@ -37557,7 +37744,10 @@ fn emittupledata(c: *cgen, name: str, module: str, tt: *node, rhs: *node) bool =
};
foff += (tyslicesize(): i32);
} else {
foff += 8;
// #22: slot stride via the accessor (tagged is
// rejected upstream; non-wide is 8 today — keeps the
// stride on the accessor scale).
foff += tupeslotn(et);
};
e = e.next;
if (tp != nil) { tp = tp.next; };