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

@@ -7363,19 +7363,38 @@ fn cgcall(c: *cgen, n: *node) void = {
stackslots += 1;
};
popped += 1;
} else { let tuparg: *node = nodetuplearg(c, a);
} else {
// #68: drain over the PARAM tuple element widths for a
// tuple LITERAL arg (the declared-tagged box words pop
// together with the i64 that follows), mirroring the
// param-aware send; else the source tuple type.
let dptt: *node = nil;
if (a.kind == nkind.N_TUPLE) { if (dparam != nil) {
if (dparam.kind == nkind.N_PARAM && dparam.lhs != nil) {
let dtn2: *node = dparam.lhs;
for (dtn2 != nil && dtn2.kind == nkind.N_TNAME) {
dtn2 = aliaslookup(c, dtn2.str);
};
if (dtn2 != nil) { if (dtn2.kind == nkind.N_TTUPLE) { dptt = dtn2; }; };
};
}; };
let tuparg: *node = dptt;
if (tuparg == nil) { tuparg = nodetuplearg(c, a); };
if (tuparg != nil) {
// #163/#32: drain the tuple's staged words (slot+0
// pushed first) into the SysV arg cursor by SysV class
// — a float MOVSD/MOVSS off (SP) into the next XMM,
// else POPQ into the next INTEGER arg reg; a slice/str
// its 3 words. Reg overflow loud-stops (rule 7); the
// its 3 words, a declared-tagged box its eslot words
// (#68). Reg overflow loud-stops (rule 7); the
// partial-spill stitch is out of scope (twin of #164).
// C-t2: nodetuplearg admits ident/literal/unwrap
// sources; a literal's elements are VALUE exprs,
// classified the way the @tupargscr restage classified
// them (kind-discriminated twin walk).
let tuplit: bool = tuparg.kind == nkind.N_TUPLE;
// them (kind-discriminated twin walk). The param-aware
// path (dptt) walks declared element TYPE nodes.
let tuplit: bool = false;
if (dptt == nil) { if (tuparg.kind == nkind.N_TUPLE) { tuplit = true; }; };
let p: *node = tuparg.list;
for (p != nil) {
let et: *node = p.lhs;
@@ -7397,14 +7416,17 @@ 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); };
// eslot — full slot split; on the declared
// path tupeslotn reads the element TYPE node
// directly (#68 box-aware), else wide-vs-scalar
// off the literal VALUE node (#22 accessor scale).
let eb: i32 = 1;
if (wide) { eb = (tyslicesize() / 8i64): i32; };
if (tuplit) {
let wide: bool = nodeisstr(c, et) || nodeisslice(c, et);
if (wide) { eb = (tyslicesize() / 8i64): i32; };
} else {
eb = tupeslotn(et) / 8;
};
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);