wcc/cgen: #64+#68 tuple-literal cursor-fill decl-blind — massign + call-arg widen (both-stage)

A tuple LITERAL with a declared-tagged element reached the cursor-fill
helper (cg_tuple_lit_to_cursor) through the generic cgexpr(N_TUPLE) arm
with no declared type, so the element was stored stamped-keyed at its
constructed scalar width rather than widened into the declared tagged box.
Both consumers ran silent and wrong on both stages (#263 gate-blind:
cs==ww byte-identical, both wrong — runtime is the only net).

#64 massign: N_MASSIGN derives a declared tuple type from the lvalue
binding types and threads it into cg_tuple_lit_to_cursor + the receive
loop (mirror of the #57 N_LET wire); a `_` target falls back to the rhs
literal element type for cursor stride.

#68 call-arg: the send is made param-aware (fill over the PARAM tuple) and
the restage guard graduates a declared-tagged element to a real widen
(reusing cg_widen_tagged_store); nested tuple/struct/array elements and
tagged elements with no param decl stay rule-7 loud. The matching
pop/drain is made param-aware too so push count == pop count: a
param-aware send pushes the box's N words, so the drain must pop N or the
SysV arg sequence skews. This is a push/pop balance requirement of the
send change, not a separate latent under-drain (the standalone trailing-
arg drain is already correct at HEAD).

Closed by construction: the only remaining cg_tuple_lit_to_cursor caller
passing NULL/nil is the generic cgexpr(N_TUPLE) arm, provably non-widening
(constructed type == governing type). The four widening consumers — LET,
RETURN, MASSIGN, call-arg — are all decl-wired. Whole-tuple single-ident
reassign from a tuple literal is rule-7 loud (task #49), not a silent
widening consumer, so the residual NULL arm stays non-widening.

Pin: 945_tuple_lit_declblind_run — massign / call-arg / `_`-control /
call-arg-drain / nested-tuple-ERR rows, each base-fail at abd97e6 and
post-pass with cs==ww byte-id.
This commit is contained in:
2026-06-07 11:23:48 +09:00
parent abd97e6c57
commit 39432f717c
8 changed files with 825 additions and 141 deletions

View File

@@ -3171,11 +3171,37 @@ 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); };
// #64: a tuple-LITERAL rhs carries a DECLARED tuple type (built from
// the lvalue binding types) into the cursor fill, so a declared-tagged
// element's concrete rvalue widens into the box instead of riding the
// decl-less stamped-keyed route — the #57 decl wire extended past
// cgmlet/cgreturn to destructure-reassign. A `_` lvalue has no local
// (no declared type node); its decl element stays nil and the
// fill/receive fall back to the rhs literal element's own stamped type
// for the cursor stride (harec `_` advance; pinned by the R3 control).
let litrhs: bool = false;
if (n.rhs != nil) { if (n.rhs.kind == nkind.N_TUPLE) { litrhs = true; }; };
let synthdecl: *node = nil;
if (litrhs) {
synthdecl = newnode(nkind.N_TTUPLE, n.rhs.file, n.rhs.line, n.rhs.col);
let dtail: *node = nil;
let lb0: *node = n.list;
for (lb0 != nil) {
let w: *node = newnode(nkind.N_TUPLE, n.rhs.file, n.rhs.line, n.rhs.col);
w.lhs = nil;
if (lb0.kind == nkind.N_IDENT) {
let lc0: *local = localfindnode(c, lb0.str);
if (lc0 != nil) { w.lhs = lc0.tnode; };
};
w.next = nil;
if (dtail == nil) { synthdecl.list = w; } else { dtail.next = w; };
dtail = w;
lb0 = lb0.next;
};
cgtuplelittocursor(c, n.rhs, synthdecl);
} else {
if (n.rhs != nil) { cgexpr(c, n.rhs); };
};
if (sretrecv > 0) {
let scr: i32 = localfind(c, "@sretscr");
@@ -3254,9 +3280,20 @@ fn cgmassign(c: *cgen, n: *node) void = {
let l: *node = n.list;
let pt: *node = nil;
if (rettuple != nil) { pt = rettuple.list; };
// #64: a tuple-LITERAL rhs keys element WIDTH on the DECLARED lvalue
// type (synthdecl), not the rettuple (nil for a literal); a `_` slot
// (declared type nil) falls back to the rhs literal element's own
// stamped type for the cursor stride.
let dp: *node = nil;
let re: *node = nil;
if (litrhs) { dp = synthdecl.list; re = n.rhs.list; };
for (l != nil) {
let tn: *node = nil;
if (pt != nil) { tn = pt.lhs; };
if (litrhs) {
if (dp != nil && dp.lhs != nil) { tn = dp.lhs; } else { tn = re; };
} else {
if (pt != nil) { tn = pt.lhs; };
};
if (isfloattype(c, tn)) {
ssetotal = ssetotal + 1;
} else {
@@ -3264,6 +3301,8 @@ fn cgmassign(c: *cgen, n: *node) void = {
};
l = l.next;
if (pt != nil) { pt = pt.next; };
if (dp != nil) { dp = dp.next; };
if (re != nil) { re = re.next; };
};
if (gptotal > TUPLE_GPCAP) { // AX,DX,CX,R8 capacity
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
@@ -3283,9 +3322,16 @@ fn cgmassign(c: *cgen, n: *node) void = {
l = n.list;
pt = nil;
if (rettuple != nil) { pt = rettuple.list; };
dp = nil;
re = nil;
if (litrhs) { dp = synthdecl.list; re = n.rhs.list; };
for (l != nil) {
let tn: *node = nil;
if (pt != nil) { tn = pt.lhs; };
if (litrhs) {
if (dp != nil && dp.lhs != nil) { tn = dp.lhs; } else { tn = re; };
} else {
if (pt != nil) { tn = pt.lhs; };
};
let isflt: bool = isfloattype(c, tn);
let eslot: i32 = tupeslotn(tn);
let off: i32 = 0;
@@ -3302,6 +3348,8 @@ fn cgmassign(c: *cgen, n: *node) void = {
};
l = l.next;
if (pt != nil) { pt = pt.next; };
if (dp != nil) { dp = dp.next; };
if (re != nil) { re = re.next; };
};
c.lastwasreturn = 0;
return;