cgen: #57 in-cap tuple cursor fill keys on the DECLARED element type — tagged elems from concrete rvalues widen, both stages

The N_TUPLE literal's stamped type is CONSTRUCTED from its elements
(check.c N_TUPLE keeps untyped/concrete element types; assignability
is consumer-side), so the in-cap cursor fill — count
(tuple_lit_gpwords/tuplitgpwords) + push (tuple_lit_push_elem/
tuplitpushelem) — never saw the DECLARED tuple type. A declared-TAGGED
element whose expr is a concrete rvalue (`return (5: size, 9)` into
(un16, size)) counted ONE word and skipped the widen entirely: 2 words
sent against the receiver's declared 3-word walk, every later element
read garbage. Both stages, byte-identical, gate-blind (ken /tmp/ken57
p8/p9: t.1 read entry-junk). The let-literal twin
(`let t: (un16, size) = (5: size, 9)`) and the tagged-SECOND-elem
shift broke identically (probes q1/q2). The over-cap (sret) arm
already walks declared params (#240/#22b) — only the in-cap path was
declared-blind.

Fix threads the declared tuple type into the ONE shared helper pair
and its two loop sites:
  - tuple_lit_gpwords/tuplitpushelem take the declared elem type;
    declared-TAGGED + concrete rvalue widens into the shared tagged
    scratch (cg_tagscr_slot/tagscradd + cg_widen_tagged_store/
    cgwidentaggedstore, the cgreturn tagged-@retscr shape) and pushes
    the box words; declared-TAGGED gates the SSE row off (a (void|f64)
    box rides INTEGER eightbytes). Tagged->tagged subset (eslot
    mismatch) louds — the #23/#40 widening-remap family.
  - cg_tuple_lit_to_cursor/cgtuplelittocursor grow a decl param;
    cgreturn's in-cap N_TUPLE loops thread cg_ret_type/c.fnret.list
    (the same pp/pt walk its over-cap arm does); the N_LET in-cap
    tuple arm passes the declared type for an N_TUPLE rhs; the bare
    cgexpr route passes NULL/nil (emission unchanged).

Ident-elem sources keep the existing slot-load push byte-identically
(t57_ident_no_regress); the CALL-elem tripwire stays loud (#41,
t57_loud_call_elem). RESIDUAL FILED, not folded (rule 11): the
N_MASSIGN destructure-reassign literal rhs routes through the bare
cgexpr path (decl=NULL) and stays silent-wrong — probe q5_massign,
task #64, cited at the massign arm both stages. The annotated
multi-let spelling (`let (a, b): (un, size) = lit`) does not parse
(both stages), so N_MLET has no declared-literal route.

941 rows t57_*: return (named + inline union), let-literal, tagged
second elem, float payload, bare-untyped payload (rides the #33
chooser through the new wire), ident anchor, loud CALL tripwire;
ken's adversarial shapes (tagged-MID elem, two tagged rvalue elems
incl. void, plain-f64 SSE coexisting with a declared-tagged box), the
in-cap/over-cap boundary loud (k57d), and the NEW #57 tag-remap loud
pinned. Pre-fix at e8977a4: p8/p9 rows exit 1, q1_let exit 1,
q2_mixed exit 2.

Task #57.
This commit is contained in:
2026-06-05 09:05:01 +09:00
parent 80e7096f25
commit d14a23b85e
6 changed files with 796 additions and 105 deletions

View File

@@ -20959,7 +20959,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
// elements into the register cursor (mirror cgreturn's N_TUPLE
// arm) so a let-bind / destructure consumer reads every element,
// not just AX = 0 from the default arm below.
cgtuplelittocursor(c, n);
cgtuplelittocursor(c, n, nil);
return;
case:
// Default fallback: produce a deterministic AX = 0. Mirrors
@@ -32211,7 +32211,17 @@ fn tupstore(c: *cgen, gpcur: i32, ssecur: i32, off: i32, eslot: i32, tn: *node)
// (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 = {
//
// #57: `dtn` is the DECLARED tuple element TYPE node (nil when the
// consumer has none). The N_TUPLE literal's stamped type is
// CONSTRUCTED from its elements, so a concrete rvalue under a
// declared-TAGGED slot counted ONE word here while the receive walks
// the declared eslot — the cursor shifted and every later element
// read garbage. Declared-tagged keys the count on the DECLARED box.
fn tuplitgpwords(c: *cgen, e: *node, dtn: *node) i32 = {
if (dtn != nil) {
if (istaggedtype(c, dtn)) { return tupeslotn(dtn) / 8; };
};
if (isfloattype(c, e)) { return 0; };
if (nodeisstr(c, e) || nodeisslice(c, e)) {
return (tyslicesize() / 8i64): i32;
@@ -32229,15 +32239,56 @@ fn tuplitgpwords(c: *cgen, e: *node) i32 = {
// 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
// rides the #35 non-ident-source family, widening literals #23). Mirror of
// cstage tuple_lit_push_elem — count (tuplitgpwords) and push live or
// die together.
fn tuplitpushelem(c: *cgen, e: *node) void = {
// reads memory), so the cursor fill must too. Mirror of cstage
// tuple_lit_push_elem — count (tuplitgpwords) and push live or die
// together.
//
// #57: a DECLARED-tagged element whose expr is a concrete rvalue
// (`return (5: size, 9)` — cast, literal, call) skipped the widen
// entirely: the stamped-keyed arm below saw a scalar and pushed ONE
// word, the receiver read the declared box words — silent shift, both
// stages, gate-blind (ken /tmp/ken57). Such an element now widens
// into the shared tagged scratch (cgwidentaggedstore, the cgreturn
// tagged-@retscr shape) and pushes the box words. A tagged->tagged
// SUBSET element (eslot mismatch) needs a tag remap on the way into
// the slot — loud (rule 7, the #23/#40 widening family).
fn tuplitpushelem(c: *cgen, e: *node, dtn: *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 etagged: bool = false;
if (t != nil) { if (t.kind == tykind.TY_TAGGED) { etagged = true; }; };
if (dtn != nil) {
if (istaggedtype(c, dtn) && !etagged) {
let eslot: i32 = tupeslotn(dtn);
let scr: i32 = tagscradd(c, eslot);
emitline("\tXORQ\tAX, AX\n");
let z: i32 = 0;
for (z < eslot) {
emitline("\tMOVQ\tAX, ");
emitoff((scr + z): i64);
emitline("(BP)\n");
z += 8;
};
cgwidentaggedstore(c, dtn.type_: *tinfo, e,
"BP", scr, eslot);
let pk: i32 = 0;
for (pk < eslot / 8) {
emitline("\tMOVQ\t");
emitoff((scr + pk * 8): i64);
emitline("(BP), AX\n");
emitline("\tPUSHQ\tAX\n");
pk += 1;
};
return;
};
if (istaggedtype(c, dtn) && etagged
&& tupeslotn(dtn) != tupeslotn(e)) {
let m57: str = "#57: tagged tuple element widening into a wider declared union slot needs a tag remap (rule 7; the #23/#40 widening family)\n";
os.write(2, m57.ptr, m57.len: u64);
os.exit(1);
};
};
if (etagged) {
let eslot: i32 = tupeslotn(e);
let eoff: i32 = 0;
if (e.kind == nkind.N_IDENT) { eoff = localfind(c, e.str); };
@@ -32275,17 +32326,35 @@ fn tuplitpushelem(c: *cgen, e: *node) void = {
// extraction of cgreturn's in-register N_TUPLE arm (cgenstmt.ww), now shared
// with cgexpr. Over-cap loud-stops (rule 7); a bare expression value can't
// sret, so the >cap rvalue-tuple materialisation is the #10 follow-up.
fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
//
// #57: `decl` is the consumer's DECLARED tuple TYPE node (N_TTUPLE,
// nil when it has none — the bare cgexpr route). A declared-TAGGED
// element gates the SSE row off (its payload may be float-stamped but
// the BOX rides INTEGER eightbytes) and keys count + push on the
// declared eslot — see tuplitgpwords / tuplitpushelem. Mirrors cstage
// cg_tuple_lit_to_cursor's decl walk; decl.list nodes wrap the elem
// type in .lhs (the c.fnret.list shape the over-cap arm walks).
fn cgtuplelittocursor(c: *cgen, tuple: *node, decl: *node) void = {
let dp0: *node = nil;
if (decl != nil) {
if (decl.kind == nkind.N_TTUPLE) { dp0 = decl.list; };
};
let ssecap: i32 = TUPLE_SSECAP;
let gptotal: i32 = 0;
let ssecount: i32 = 0;
let dp: *node = dp0;
let e: *node = tuple.list;
for (e != nil) {
if (isfloattype(c, e)) {
let dtn: *node = nil;
if (dp != nil) { dtn = dp.lhs; };
let dtagged: bool = false;
if (dtn != nil) { dtagged = istaggedtype(c, dtn); };
if (!dtagged && isfloattype(c, e)) {
ssecount = ssecount + 1;
} else {
gptotal = gptotal + tuplitgpwords(c, e);
gptotal = gptotal + tuplitgpwords(c, e, dtn);
};
if (dp != nil) { dp = dp.next; };
e = e.next;
};
if (gptotal > TUPLE_GPCAP || ssecount > ssecap) {
@@ -32298,9 +32367,14 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
fscr = localadd(c, "@tupfscr", ssecap * 8, nil);
};
let sseidx: i32 = 0;
dp = dp0;
e = tuple.list;
for (e != nil) {
let isflt: bool = isfloattype(c, e);
let dtn: *node = nil;
if (dp != nil) { dtn = dp.lhs; };
let dtagged: bool = false;
if (dtn != nil) { dtagged = istaggedtype(c, dtn); };
let isflt: bool = !dtagged && isfloattype(c, e);
if (isflt) {
cgexpr(c, e);
let mov: str = "MOVSD";
@@ -32310,8 +32384,9 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
emitline("(BP)\n");
sseidx = sseidx + 1;
} else {
tuplitpushelem(c, e);
tuplitpushelem(c, e, dtn);
};
if (dp != nil) { dp = dp.next; };
e = e.next;
};
let i: i32 = gptotal - 1;
@@ -32322,9 +32397,14 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
i = i - 1;
};
let j: i32 = 0;
dp = dp0;
e = tuple.list;
for (e != nil) {
if (isfloattype(c, e)) {
let dtn: *node = nil;
if (dp != nil) { dtn = dp.lhs; };
let dtagged: bool = false;
if (dtn != nil) { dtagged = istaggedtype(c, dtn); };
if (!dtagged && isfloattype(c, e)) {
let mov: str = "MOVSD";
if (isf32type(c, e)) { mov = "MOVSS"; };
emitline("\t"); emitline(mov); emitline("\t");
@@ -32334,6 +32414,7 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
emitline("\n");
j = j + 1;
};
if (dp != nil) { dp = dp.next; };
e = e.next;
};
};
@@ -32470,13 +32551,33 @@ fn cgreturn(c: *cgen, n: *node) void = {
let ssecap: i32 = TUPLE_SSECAP; // X0,X1 per SysV
let gptotal: i32 = 0;
let ssecount: i32 = 0;
// #57: count + push key on the DECLARED return-type
// element (c.fnret.list) — the literal's stamped type
// is element-constructed, so a declared-TAGGED
// element's concrete rvalue counted 1 word and skipped
// the widen while the caller's receive walks the
// declared eslot (2 words sent for a 3-word shape;
// ken /tmp/ken57 p8/p9). Same pt walk the over-cap arm
// already does (#240/#22b). Mirrors cstage cgreturn.
let rp0: *node = nil;
if (c.fnret != nil) {
if (c.fnret.kind == nkind.N_TTUPLE) {
rp0 = c.fnret.list;
};
};
let rp: *node = rp0;
let e: *node = rhs.list;
for (e != nil) {
if (isfloattype(c, e)) {
let rdtn: *node = nil;
if (rp != nil) { rdtn = rp.lhs; };
let rdtag: bool = false;
if (rdtn != nil) { rdtag = istaggedtype(c, rdtn); };
if (!rdtag && isfloattype(c, e)) {
ssecount = ssecount + 1;
} else {
gptotal = gptotal + tuplitgpwords(c, e);
gptotal = gptotal + tuplitgpwords(c, e, rdtn);
};
if (rp != nil) { rp = rp.next; };
e = e.next;
};
// #22b: classify and emit MUST agree (the #10 SSoT note
@@ -32640,9 +32741,14 @@ fn cgreturn(c: *cgen, n: *node) void = {
fscr = localadd(c, "@tupfscr", ssecap * 8, nil);
};
let sseidx: i32 = 0;
rp = rp0;
e = rhs.list;
for (e != nil) {
let isflt: bool = isfloattype(c, e);
let rdtn: *node = nil;
if (rp != nil) { rdtn = rp.lhs; };
let rdtag: bool = false;
if (rdtn != nil) { rdtag = istaggedtype(c, rdtn); };
let isflt: bool = !rdtag && isfloattype(c, e);
if (isflt) {
cgexpr(c, e);
let mov: str = "MOVSD";
@@ -32655,9 +32761,11 @@ fn cgreturn(c: *cgen, n: *node) void = {
sseidx = sseidx + 1;
} else {
// scalar=AX; slice/str=AX,BX,CX; tagged
// box from its slot (tuplitpushelem)
tuplitpushelem(c, e);
// box from its slot or widened scratch
// (tuplitpushelem)
tuplitpushelem(c, e, rdtn);
};
if (rp != nil) { rp = rp.next; };
e = e.next;
};
let i: i32 = gptotal - 1;
@@ -32668,9 +32776,14 @@ fn cgreturn(c: *cgen, n: *node) void = {
i = i - 1;
};
let j: i32 = 0;
rp = rp0;
e = rhs.list;
for (e != nil) {
if (isfloattype(c, e)) {
let rdtn: *node = nil;
if (rp != nil) { rdtn = rp.lhs; };
let rdtag: bool = false;
if (rdtn != nil) { rdtag = istaggedtype(c, rdtn); };
if (!rdtag && isfloattype(c, e)) {
let mov: str = "MOVSD";
if (isf32type(c, e)) { mov = "MOVSS"; };
emitline("\t");
@@ -32682,6 +32795,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
emitline("\n");
j = j + 1;
};
if (rp != nil) { rp = rp.next; };
e = e.next;
};
emitline("\tMOVQ\tBP, SP\n");
@@ -34098,7 +34212,19 @@ fn cglet(c: *cgen, n: *node) void = {
if (ttup != nil) {
if (ttup.kind == nkind.N_TTUPLE
&& sretretsize(c, ttup) == 0) {
cgexpr(c, rhs);
// #57: a tuple LITERAL rhs carries the DECLARED
// type into the cursor fill — its stamped type
// is element-constructed, so a declared-tagged
// element's concrete rvalue skipped the widen
// and the fill/receive cursor walks skewed
// (let-twin of the return-position bug; probe
// /tmp/p57/q1_let). Same emission as the cgexpr
// route for every declared-tagged-free literal.
if (rhs.kind == nkind.N_TUPLE) {
cgtuplelittocursor(c, rhs, ttup);
} else {
cgexpr(c, rhs);
};
let gpcur: i32 = 0;
let ssecur: i32 = 0;
let eoff: i32 = 0;
@@ -34918,6 +35044,10 @@ fn cgmassign(c: *cgen, n: *node) void = {
};
};
// #64 (filed, rule 7): an N_TUPLE literal rhs rides this decl-less
// cgexpr route, so a declared-TAGGED element's concrete rvalue
// still fills the cursor stamped-keyed (silent skew) — the #57
// decl wire stops at return/let.
if (n.rhs != nil) { cgexpr(c, n.rhs); };
if (sretrecv > 0) {

View File

@@ -115,7 +115,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
// elements into the register cursor (mirror cgreturn's N_TUPLE
// arm) so a let-bind / destructure consumer reads every element,
// not just AX = 0 from the default arm below.
cgtuplelittocursor(c, n);
cgtuplelittocursor(c, n, nil);
return;
case:
// Default fallback: produce a deterministic AX = 0. Mirrors

View File

@@ -306,7 +306,17 @@ fn tupstore(c: *cgen, gpcur: i32, ssecur: i32, off: i32, eslot: i32, tn: *node)
// (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 = {
//
// #57: `dtn` is the DECLARED tuple element TYPE node (nil when the
// consumer has none). The N_TUPLE literal's stamped type is
// CONSTRUCTED from its elements, so a concrete rvalue under a
// declared-TAGGED slot counted ONE word here while the receive walks
// the declared eslot — the cursor shifted and every later element
// read garbage. Declared-tagged keys the count on the DECLARED box.
fn tuplitgpwords(c: *cgen, e: *node, dtn: *node) i32 = {
if (dtn != nil) {
if (istaggedtype(c, dtn)) { return tupeslotn(dtn) / 8; };
};
if (isfloattype(c, e)) { return 0; };
if (nodeisstr(c, e) || nodeisslice(c, e)) {
return (tyslicesize() / 8i64): i32;
@@ -324,15 +334,56 @@ fn tuplitgpwords(c: *cgen, e: *node) i32 = {
// 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
// rides the #35 non-ident-source family, widening literals #23). Mirror of
// cstage tuple_lit_push_elem — count (tuplitgpwords) and push live or
// die together.
fn tuplitpushelem(c: *cgen, e: *node) void = {
// reads memory), so the cursor fill must too. Mirror of cstage
// tuple_lit_push_elem — count (tuplitgpwords) and push live or die
// together.
//
// #57: a DECLARED-tagged element whose expr is a concrete rvalue
// (`return (5: size, 9)` — cast, literal, call) skipped the widen
// entirely: the stamped-keyed arm below saw a scalar and pushed ONE
// word, the receiver read the declared box words — silent shift, both
// stages, gate-blind (ken /tmp/ken57). Such an element now widens
// into the shared tagged scratch (cgwidentaggedstore, the cgreturn
// tagged-@retscr shape) and pushes the box words. A tagged->tagged
// SUBSET element (eslot mismatch) needs a tag remap on the way into
// the slot — loud (rule 7, the #23/#40 widening family).
fn tuplitpushelem(c: *cgen, e: *node, dtn: *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 etagged: bool = false;
if (t != nil) { if (t.kind == tykind.TY_TAGGED) { etagged = true; }; };
if (dtn != nil) {
if (istaggedtype(c, dtn) && !etagged) {
let eslot: i32 = tupeslotn(dtn);
let scr: i32 = tagscradd(c, eslot);
emitline("\tXORQ\tAX, AX\n");
let z: i32 = 0;
for (z < eslot) {
emitline("\tMOVQ\tAX, ");
emitoff((scr + z): i64);
emitline("(BP)\n");
z += 8;
};
cgwidentaggedstore(c, dtn.type_: *tinfo, e,
"BP", scr, eslot);
let pk: i32 = 0;
for (pk < eslot / 8) {
emitline("\tMOVQ\t");
emitoff((scr + pk * 8): i64);
emitline("(BP), AX\n");
emitline("\tPUSHQ\tAX\n");
pk += 1;
};
return;
};
if (istaggedtype(c, dtn) && etagged
&& tupeslotn(dtn) != tupeslotn(e)) {
let m57: str = "#57: tagged tuple element widening into a wider declared union slot needs a tag remap (rule 7; the #23/#40 widening family)\n";
os.write(2, m57.ptr, m57.len: u64);
os.exit(1);
};
};
if (etagged) {
let eslot: i32 = tupeslotn(e);
let eoff: i32 = 0;
if (e.kind == nkind.N_IDENT) { eoff = localfind(c, e.str); };
@@ -370,17 +421,35 @@ fn tuplitpushelem(c: *cgen, e: *node) void = {
// extraction of cgreturn's in-register N_TUPLE arm (cgenstmt.ww), now shared
// with cgexpr. Over-cap loud-stops (rule 7); a bare expression value can't
// sret, so the >cap rvalue-tuple materialisation is the #10 follow-up.
fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
//
// #57: `decl` is the consumer's DECLARED tuple TYPE node (N_TTUPLE,
// nil when it has none — the bare cgexpr route). A declared-TAGGED
// element gates the SSE row off (its payload may be float-stamped but
// the BOX rides INTEGER eightbytes) and keys count + push on the
// declared eslot — see tuplitgpwords / tuplitpushelem. Mirrors cstage
// cg_tuple_lit_to_cursor's decl walk; decl.list nodes wrap the elem
// type in .lhs (the c.fnret.list shape the over-cap arm walks).
fn cgtuplelittocursor(c: *cgen, tuple: *node, decl: *node) void = {
let dp0: *node = nil;
if (decl != nil) {
if (decl.kind == nkind.N_TTUPLE) { dp0 = decl.list; };
};
let ssecap: i32 = TUPLE_SSECAP;
let gptotal: i32 = 0;
let ssecount: i32 = 0;
let dp: *node = dp0;
let e: *node = tuple.list;
for (e != nil) {
if (isfloattype(c, e)) {
let dtn: *node = nil;
if (dp != nil) { dtn = dp.lhs; };
let dtagged: bool = false;
if (dtn != nil) { dtagged = istaggedtype(c, dtn); };
if (!dtagged && isfloattype(c, e)) {
ssecount = ssecount + 1;
} else {
gptotal = gptotal + tuplitgpwords(c, e);
gptotal = gptotal + tuplitgpwords(c, e, dtn);
};
if (dp != nil) { dp = dp.next; };
e = e.next;
};
if (gptotal > TUPLE_GPCAP || ssecount > ssecap) {
@@ -393,9 +462,14 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
fscr = localadd(c, "@tupfscr", ssecap * 8, nil);
};
let sseidx: i32 = 0;
dp = dp0;
e = tuple.list;
for (e != nil) {
let isflt: bool = isfloattype(c, e);
let dtn: *node = nil;
if (dp != nil) { dtn = dp.lhs; };
let dtagged: bool = false;
if (dtn != nil) { dtagged = istaggedtype(c, dtn); };
let isflt: bool = !dtagged && isfloattype(c, e);
if (isflt) {
cgexpr(c, e);
let mov: str = "MOVSD";
@@ -405,8 +479,9 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
emitline("(BP)\n");
sseidx = sseidx + 1;
} else {
tuplitpushelem(c, e);
tuplitpushelem(c, e, dtn);
};
if (dp != nil) { dp = dp.next; };
e = e.next;
};
let i: i32 = gptotal - 1;
@@ -417,9 +492,14 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
i = i - 1;
};
let j: i32 = 0;
dp = dp0;
e = tuple.list;
for (e != nil) {
if (isfloattype(c, e)) {
let dtn: *node = nil;
if (dp != nil) { dtn = dp.lhs; };
let dtagged: bool = false;
if (dtn != nil) { dtagged = istaggedtype(c, dtn); };
if (!dtagged && isfloattype(c, e)) {
let mov: str = "MOVSD";
if (isf32type(c, e)) { mov = "MOVSS"; };
emitline("\t"); emitline(mov); emitline("\t");
@@ -429,6 +509,7 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
emitline("\n");
j = j + 1;
};
if (dp != nil) { dp = dp.next; };
e = e.next;
};
};
@@ -565,13 +646,33 @@ fn cgreturn(c: *cgen, n: *node) void = {
let ssecap: i32 = TUPLE_SSECAP; // X0,X1 per SysV
let gptotal: i32 = 0;
let ssecount: i32 = 0;
// #57: count + push key on the DECLARED return-type
// element (c.fnret.list) — the literal's stamped type
// is element-constructed, so a declared-TAGGED
// element's concrete rvalue counted 1 word and skipped
// the widen while the caller's receive walks the
// declared eslot (2 words sent for a 3-word shape;
// ken /tmp/ken57 p8/p9). Same pt walk the over-cap arm
// already does (#240/#22b). Mirrors cstage cgreturn.
let rp0: *node = nil;
if (c.fnret != nil) {
if (c.fnret.kind == nkind.N_TTUPLE) {
rp0 = c.fnret.list;
};
};
let rp: *node = rp0;
let e: *node = rhs.list;
for (e != nil) {
if (isfloattype(c, e)) {
let rdtn: *node = nil;
if (rp != nil) { rdtn = rp.lhs; };
let rdtag: bool = false;
if (rdtn != nil) { rdtag = istaggedtype(c, rdtn); };
if (!rdtag && isfloattype(c, e)) {
ssecount = ssecount + 1;
} else {
gptotal = gptotal + tuplitgpwords(c, e);
gptotal = gptotal + tuplitgpwords(c, e, rdtn);
};
if (rp != nil) { rp = rp.next; };
e = e.next;
};
// #22b: classify and emit MUST agree (the #10 SSoT note
@@ -735,9 +836,14 @@ fn cgreturn(c: *cgen, n: *node) void = {
fscr = localadd(c, "@tupfscr", ssecap * 8, nil);
};
let sseidx: i32 = 0;
rp = rp0;
e = rhs.list;
for (e != nil) {
let isflt: bool = isfloattype(c, e);
let rdtn: *node = nil;
if (rp != nil) { rdtn = rp.lhs; };
let rdtag: bool = false;
if (rdtn != nil) { rdtag = istaggedtype(c, rdtn); };
let isflt: bool = !rdtag && isfloattype(c, e);
if (isflt) {
cgexpr(c, e);
let mov: str = "MOVSD";
@@ -750,9 +856,11 @@ fn cgreturn(c: *cgen, n: *node) void = {
sseidx = sseidx + 1;
} else {
// scalar=AX; slice/str=AX,BX,CX; tagged
// box from its slot (tuplitpushelem)
tuplitpushelem(c, e);
// box from its slot or widened scratch
// (tuplitpushelem)
tuplitpushelem(c, e, rdtn);
};
if (rp != nil) { rp = rp.next; };
e = e.next;
};
let i: i32 = gptotal - 1;
@@ -763,9 +871,14 @@ fn cgreturn(c: *cgen, n: *node) void = {
i = i - 1;
};
let j: i32 = 0;
rp = rp0;
e = rhs.list;
for (e != nil) {
if (isfloattype(c, e)) {
let rdtn: *node = nil;
if (rp != nil) { rdtn = rp.lhs; };
let rdtag: bool = false;
if (rdtn != nil) { rdtag = istaggedtype(c, rdtn); };
if (!rdtag && isfloattype(c, e)) {
let mov: str = "MOVSD";
if (isf32type(c, e)) { mov = "MOVSS"; };
emitline("\t");
@@ -777,6 +890,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
emitline("\n");
j = j + 1;
};
if (rp != nil) { rp = rp.next; };
e = e.next;
};
emitline("\tMOVQ\tBP, SP\n");
@@ -2193,7 +2307,19 @@ fn cglet(c: *cgen, n: *node) void = {
if (ttup != nil) {
if (ttup.kind == nkind.N_TTUPLE
&& sretretsize(c, ttup) == 0) {
cgexpr(c, rhs);
// #57: a tuple LITERAL rhs carries the DECLARED
// type into the cursor fill — its stamped type
// is element-constructed, so a declared-tagged
// element's concrete rvalue skipped the widen
// and the fill/receive cursor walks skewed
// (let-twin of the return-position bug; probe
// /tmp/p57/q1_let). Same emission as the cgexpr
// route for every declared-tagged-free literal.
if (rhs.kind == nkind.N_TUPLE) {
cgtuplelittocursor(c, rhs, ttup);
} else {
cgexpr(c, rhs);
};
let gpcur: i32 = 0;
let ssecur: i32 = 0;
let eoff: i32 = 0;
@@ -3013,6 +3139,10 @@ fn cgmassign(c: *cgen, n: *node) void = {
};
};
// #64 (filed, rule 7): an N_TUPLE literal rhs rides this decl-less
// cgexpr route, so a declared-TAGGED element's concrete rvalue
// still fills the cursor stamped-keyed (silent skew) — the #57
// decl wire stops at return/let.
if (n.rhs != nil) { cgexpr(c, n.rhs); };
if (sretrecv > 0) {

View File

@@ -20959,7 +20959,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
// elements into the register cursor (mirror cgreturn's N_TUPLE
// arm) so a let-bind / destructure consumer reads every element,
// not just AX = 0 from the default arm below.
cgtuplelittocursor(c, n);
cgtuplelittocursor(c, n, nil);
return;
case:
// Default fallback: produce a deterministic AX = 0. Mirrors
@@ -32211,7 +32211,17 @@ fn tupstore(c: *cgen, gpcur: i32, ssecur: i32, off: i32, eslot: i32, tn: *node)
// (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 = {
//
// #57: `dtn` is the DECLARED tuple element TYPE node (nil when the
// consumer has none). The N_TUPLE literal's stamped type is
// CONSTRUCTED from its elements, so a concrete rvalue under a
// declared-TAGGED slot counted ONE word here while the receive walks
// the declared eslot — the cursor shifted and every later element
// read garbage. Declared-tagged keys the count on the DECLARED box.
fn tuplitgpwords(c: *cgen, e: *node, dtn: *node) i32 = {
if (dtn != nil) {
if (istaggedtype(c, dtn)) { return tupeslotn(dtn) / 8; };
};
if (isfloattype(c, e)) { return 0; };
if (nodeisstr(c, e) || nodeisslice(c, e)) {
return (tyslicesize() / 8i64): i32;
@@ -32229,15 +32239,56 @@ fn tuplitgpwords(c: *cgen, e: *node) i32 = {
// 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
// rides the #35 non-ident-source family, widening literals #23). Mirror of
// cstage tuple_lit_push_elem — count (tuplitgpwords) and push live or
// die together.
fn tuplitpushelem(c: *cgen, e: *node) void = {
// reads memory), so the cursor fill must too. Mirror of cstage
// tuple_lit_push_elem — count (tuplitgpwords) and push live or die
// together.
//
// #57: a DECLARED-tagged element whose expr is a concrete rvalue
// (`return (5: size, 9)` — cast, literal, call) skipped the widen
// entirely: the stamped-keyed arm below saw a scalar and pushed ONE
// word, the receiver read the declared box words — silent shift, both
// stages, gate-blind (ken /tmp/ken57). Such an element now widens
// into the shared tagged scratch (cgwidentaggedstore, the cgreturn
// tagged-@retscr shape) and pushes the box words. A tagged->tagged
// SUBSET element (eslot mismatch) needs a tag remap on the way into
// the slot — loud (rule 7, the #23/#40 widening family).
fn tuplitpushelem(c: *cgen, e: *node, dtn: *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 etagged: bool = false;
if (t != nil) { if (t.kind == tykind.TY_TAGGED) { etagged = true; }; };
if (dtn != nil) {
if (istaggedtype(c, dtn) && !etagged) {
let eslot: i32 = tupeslotn(dtn);
let scr: i32 = tagscradd(c, eslot);
emitline("\tXORQ\tAX, AX\n");
let z: i32 = 0;
for (z < eslot) {
emitline("\tMOVQ\tAX, ");
emitoff((scr + z): i64);
emitline("(BP)\n");
z += 8;
};
cgwidentaggedstore(c, dtn.type_: *tinfo, e,
"BP", scr, eslot);
let pk: i32 = 0;
for (pk < eslot / 8) {
emitline("\tMOVQ\t");
emitoff((scr + pk * 8): i64);
emitline("(BP), AX\n");
emitline("\tPUSHQ\tAX\n");
pk += 1;
};
return;
};
if (istaggedtype(c, dtn) && etagged
&& tupeslotn(dtn) != tupeslotn(e)) {
let m57: str = "#57: tagged tuple element widening into a wider declared union slot needs a tag remap (rule 7; the #23/#40 widening family)\n";
os.write(2, m57.ptr, m57.len: u64);
os.exit(1);
};
};
if (etagged) {
let eslot: i32 = tupeslotn(e);
let eoff: i32 = 0;
if (e.kind == nkind.N_IDENT) { eoff = localfind(c, e.str); };
@@ -32275,17 +32326,35 @@ fn tuplitpushelem(c: *cgen, e: *node) void = {
// extraction of cgreturn's in-register N_TUPLE arm (cgenstmt.ww), now shared
// with cgexpr. Over-cap loud-stops (rule 7); a bare expression value can't
// sret, so the >cap rvalue-tuple materialisation is the #10 follow-up.
fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
//
// #57: `decl` is the consumer's DECLARED tuple TYPE node (N_TTUPLE,
// nil when it has none — the bare cgexpr route). A declared-TAGGED
// element gates the SSE row off (its payload may be float-stamped but
// the BOX rides INTEGER eightbytes) and keys count + push on the
// declared eslot — see tuplitgpwords / tuplitpushelem. Mirrors cstage
// cg_tuple_lit_to_cursor's decl walk; decl.list nodes wrap the elem
// type in .lhs (the c.fnret.list shape the over-cap arm walks).
fn cgtuplelittocursor(c: *cgen, tuple: *node, decl: *node) void = {
let dp0: *node = nil;
if (decl != nil) {
if (decl.kind == nkind.N_TTUPLE) { dp0 = decl.list; };
};
let ssecap: i32 = TUPLE_SSECAP;
let gptotal: i32 = 0;
let ssecount: i32 = 0;
let dp: *node = dp0;
let e: *node = tuple.list;
for (e != nil) {
if (isfloattype(c, e)) {
let dtn: *node = nil;
if (dp != nil) { dtn = dp.lhs; };
let dtagged: bool = false;
if (dtn != nil) { dtagged = istaggedtype(c, dtn); };
if (!dtagged && isfloattype(c, e)) {
ssecount = ssecount + 1;
} else {
gptotal = gptotal + tuplitgpwords(c, e);
gptotal = gptotal + tuplitgpwords(c, e, dtn);
};
if (dp != nil) { dp = dp.next; };
e = e.next;
};
if (gptotal > TUPLE_GPCAP || ssecount > ssecap) {
@@ -32298,9 +32367,14 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
fscr = localadd(c, "@tupfscr", ssecap * 8, nil);
};
let sseidx: i32 = 0;
dp = dp0;
e = tuple.list;
for (e != nil) {
let isflt: bool = isfloattype(c, e);
let dtn: *node = nil;
if (dp != nil) { dtn = dp.lhs; };
let dtagged: bool = false;
if (dtn != nil) { dtagged = istaggedtype(c, dtn); };
let isflt: bool = !dtagged && isfloattype(c, e);
if (isflt) {
cgexpr(c, e);
let mov: str = "MOVSD";
@@ -32310,8 +32384,9 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
emitline("(BP)\n");
sseidx = sseidx + 1;
} else {
tuplitpushelem(c, e);
tuplitpushelem(c, e, dtn);
};
if (dp != nil) { dp = dp.next; };
e = e.next;
};
let i: i32 = gptotal - 1;
@@ -32322,9 +32397,14 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
i = i - 1;
};
let j: i32 = 0;
dp = dp0;
e = tuple.list;
for (e != nil) {
if (isfloattype(c, e)) {
let dtn: *node = nil;
if (dp != nil) { dtn = dp.lhs; };
let dtagged: bool = false;
if (dtn != nil) { dtagged = istaggedtype(c, dtn); };
if (!dtagged && isfloattype(c, e)) {
let mov: str = "MOVSD";
if (isf32type(c, e)) { mov = "MOVSS"; };
emitline("\t"); emitline(mov); emitline("\t");
@@ -32334,6 +32414,7 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
emitline("\n");
j = j + 1;
};
if (dp != nil) { dp = dp.next; };
e = e.next;
};
};
@@ -32470,13 +32551,33 @@ fn cgreturn(c: *cgen, n: *node) void = {
let ssecap: i32 = TUPLE_SSECAP; // X0,X1 per SysV
let gptotal: i32 = 0;
let ssecount: i32 = 0;
// #57: count + push key on the DECLARED return-type
// element (c.fnret.list) — the literal's stamped type
// is element-constructed, so a declared-TAGGED
// element's concrete rvalue counted 1 word and skipped
// the widen while the caller's receive walks the
// declared eslot (2 words sent for a 3-word shape;
// ken /tmp/ken57 p8/p9). Same pt walk the over-cap arm
// already does (#240/#22b). Mirrors cstage cgreturn.
let rp0: *node = nil;
if (c.fnret != nil) {
if (c.fnret.kind == nkind.N_TTUPLE) {
rp0 = c.fnret.list;
};
};
let rp: *node = rp0;
let e: *node = rhs.list;
for (e != nil) {
if (isfloattype(c, e)) {
let rdtn: *node = nil;
if (rp != nil) { rdtn = rp.lhs; };
let rdtag: bool = false;
if (rdtn != nil) { rdtag = istaggedtype(c, rdtn); };
if (!rdtag && isfloattype(c, e)) {
ssecount = ssecount + 1;
} else {
gptotal = gptotal + tuplitgpwords(c, e);
gptotal = gptotal + tuplitgpwords(c, e, rdtn);
};
if (rp != nil) { rp = rp.next; };
e = e.next;
};
// #22b: classify and emit MUST agree (the #10 SSoT note
@@ -32640,9 +32741,14 @@ fn cgreturn(c: *cgen, n: *node) void = {
fscr = localadd(c, "@tupfscr", ssecap * 8, nil);
};
let sseidx: i32 = 0;
rp = rp0;
e = rhs.list;
for (e != nil) {
let isflt: bool = isfloattype(c, e);
let rdtn: *node = nil;
if (rp != nil) { rdtn = rp.lhs; };
let rdtag: bool = false;
if (rdtn != nil) { rdtag = istaggedtype(c, rdtn); };
let isflt: bool = !rdtag && isfloattype(c, e);
if (isflt) {
cgexpr(c, e);
let mov: str = "MOVSD";
@@ -32655,9 +32761,11 @@ fn cgreturn(c: *cgen, n: *node) void = {
sseidx = sseidx + 1;
} else {
// scalar=AX; slice/str=AX,BX,CX; tagged
// box from its slot (tuplitpushelem)
tuplitpushelem(c, e);
// box from its slot or widened scratch
// (tuplitpushelem)
tuplitpushelem(c, e, rdtn);
};
if (rp != nil) { rp = rp.next; };
e = e.next;
};
let i: i32 = gptotal - 1;
@@ -32668,9 +32776,14 @@ fn cgreturn(c: *cgen, n: *node) void = {
i = i - 1;
};
let j: i32 = 0;
rp = rp0;
e = rhs.list;
for (e != nil) {
if (isfloattype(c, e)) {
let rdtn: *node = nil;
if (rp != nil) { rdtn = rp.lhs; };
let rdtag: bool = false;
if (rdtn != nil) { rdtag = istaggedtype(c, rdtn); };
if (!rdtag && isfloattype(c, e)) {
let mov: str = "MOVSD";
if (isf32type(c, e)) { mov = "MOVSS"; };
emitline("\t");
@@ -32682,6 +32795,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
emitline("\n");
j = j + 1;
};
if (rp != nil) { rp = rp.next; };
e = e.next;
};
emitline("\tMOVQ\tBP, SP\n");
@@ -34098,7 +34212,19 @@ fn cglet(c: *cgen, n: *node) void = {
if (ttup != nil) {
if (ttup.kind == nkind.N_TTUPLE
&& sretretsize(c, ttup) == 0) {
cgexpr(c, rhs);
// #57: a tuple LITERAL rhs carries the DECLARED
// type into the cursor fill — its stamped type
// is element-constructed, so a declared-tagged
// element's concrete rvalue skipped the widen
// and the fill/receive cursor walks skewed
// (let-twin of the return-position bug; probe
// /tmp/p57/q1_let). Same emission as the cgexpr
// route for every declared-tagged-free literal.
if (rhs.kind == nkind.N_TUPLE) {
cgtuplelittocursor(c, rhs, ttup);
} else {
cgexpr(c, rhs);
};
let gpcur: i32 = 0;
let ssecur: i32 = 0;
let eoff: i32 = 0;
@@ -34918,6 +35044,10 @@ fn cgmassign(c: *cgen, n: *node) void = {
};
};
// #64 (filed, rule 7): an N_TUPLE literal rhs rides this decl-less
// cgexpr route, so a declared-TAGGED element's concrete rvalue
// still fills the cursor stamped-keyed (silent skew) — the #57
// decl wire stops at return/let.
if (n.rhs != nil) { cgexpr(c, n.rhs); };
if (sretrecv > 0) {