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

@@ -250,6 +250,23 @@ node_tuplearg(Node *n)
return (u && u->kind == TY_TUPLE) ? u : NULL;
}
/* node_tuplearg_decl — #68: like node_tuplearg, but an N_TUPLE LITERAL arg
* with a tuple PARAM type reports the DECLARED param tuple (element widths
* keyed on the param, so a declared-tagged element's box words drive the
* send/drain). The literal's own type is element-constructed (a concrete
* rvalue under a tagged slot counts ONE word, not the box) — see the cgcall
* paramtup send. Every other source falls back to node_tuplearg. */
static Type *
node_tuplearg_decl(Node *n, Type *param)
{
if (n && n->kind == N_TUPLE && param) {
Type *pu = type_chase_named(param);
if (pu && pu->kind == TY_TUPLE)
return pu;
}
return node_tuplearg(n);
}
/* #83: positional tuple register-return ABI. Tuple elements ride
* consecutive eightbytes over tuple_rseq[]; a slice/str rides its 3-word
* {ptr,len,cap} header (ref/hare/rt/ensure.ha:4-8, ty_str->size SSoT), a
@@ -8842,10 +8859,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
int widen_sz[64] = {0};
Type *widen_param[64] = {0};
int memarg[64] = {0};
Type *argparam[64] = {0}; /* #68: declared param type per arg */
{
Tparam *p = callee_params;
for (int i = 0; i < argcount; i++) {
if (p == NULL) break;
argparam[i] = p->type;
Type *at = args[i] ? args[i]->type : NULL;
int psz = tagged_arg_size(p->type);
if (psz > 0) {
@@ -9207,8 +9226,26 @@ cgexpr(Cg *c, Node *n, Local *locals)
args[i], widen_sz[i]);
continue;
}
cgexpr(c, args[i], locals);
Type *tuparg_push = node_tuplearg(args[i]);
/* #68: a tuple-LITERAL arg derives its DECLARED tuple
* type from the callee PARAM (the #57 decl wire,
* extended to call-arg send), so a declared-tagged
* element's concrete rvalue widens into the box cursor;
* the restage + push then key on the param tuple type,
* not the literal's element-constructed type. */
Type *paramtup = NULL;
if (args[i] && args[i]->kind == N_TUPLE) {
Type *pcu = argparam[i]
? type_chase_named(argparam[i]) : NULL;
if (pcu && pcu->kind == TY_TUPLE)
paramtup = pcu;
}
if (paramtup)
cg_tuple_lit_to_cursor(c, &locals, args[i],
paramtup);
else
cgexpr(c, args[i], locals);
Type *tuparg_push = paramtup
? paramtup : node_tuplearg(args[i]);
/* #32 (C-t2, rule 7): a tuple-typed arg from a source
* shape whose cgexpr does NOT fill the return cursor
* (chain reads, match exprs, ...) must die loud here —
@@ -9307,11 +9344,22 @@ cgexpr(Cg *c, Node *n, Local *locals)
* cannot carry it; pre-guard it ran
* WRONG (inner words skewed). Loud
* until a consumer motivates wiring. */
/* #68: a declared-tagged element graduates
* to a real widen — the decl-aware send
* (cg_tuple_lit_to_cursor over the param
* tuple type) already left the box words in
* the cursor, so the count + tuple_store
* below carry them. Only the param-decl
* literal path is wired; nested tuple/
* struct/array stay rule-7 loud, as does a
* tagged element with no param decl (the
* cursor was filled stamped-keyed). */
Type *cu = type_chase_named(p->type);
if (cu && (cu->kind == TY_TUPLE
|| cu->kind == TY_STRUCT
|| cu->kind == TY_ARRAY
|| cu->kind == TY_TAGGED))
|| (cu->kind == TY_TAGGED
&& !paramtup)))
fatal("#32: tuple arg element "
"kind unsupported (nested "
"tuple/struct/array/tagged; "
@@ -9549,7 +9597,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
else
stackslots++;
}
} else if ((tu = node_tuplearg(args[i])) != NULL) {
} else if ((tu = node_tuplearg_decl(args[i],
argparam[i])) != NULL) {
/* #163: drain the tuple's staged words (pushed
* slot+0 first) into the SysV arg cursor by SysV
* class — a float MOVSD/MOVSS off (SP) into the
@@ -9557,7 +9606,14 @@ cgexpr(Cg *c, Node *n, Local *locals)
* next INTEGER arg reg (DI/SI/..); a slice/str its
* 3-word {ptr,len,cap}. Reg overflow loud-stops
* (rule 7): the partial-spill stitch is out of
* scope (twin of #164's cap). */
* scope (twin of #164's cap).
*
* #68: a tuple-LITERAL arg drains over the PARAM
* tuple element widths (node_tuplearg_decl), so a
* declared-tagged element's box words pop into the
* arg cursor together with the i64 that follows —
* the literal's element-constructed types undercount
* a wide box. Mirrors the param-aware send. */
int ef32;
for (Tparam *p = tu->params; p; p = p->next) {
if (fld_isfloat(p->type, &ef32)) {
@@ -14084,15 +14140,42 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
* SAME producer source the SEND walks. */
int sret_recv = (n->rhs && n->rhs->kind == N_CALL)
? cg_sret_retsize(n->rhs->type) : 0;
/* #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. */
cgexpr(c, n->rhs, *locals);
Type *rt = n->rhs ? n->rhs->type : NULL;
Type *ru = type_chase_named(rt);
Tparam *tp0 = (ru && ru->kind == TY_TUPLE) ? ru->params : NULL;
int mf32;
/* #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 N_LET/N_RETURN to destructure-reassign.
* A `_` lvalue is never type-stamped (the checker skips it) —
* fall back to the rhs literal element type for its cursor
* stride so the next element stays aligned (harec `_` advance;
* pinned by the R3 control row). */
int litrhs = (n->rhs && n->rhs->kind == N_TUPLE);
Tparam *declp = NULL;
if (litrhs) {
Tparam **dpp = &declp;
Tparam *tpw = tp0;
for (Node *l = n->list; l; l = l->next) {
Tparam *dpn = amalloc(c->a, sizeof *dpn);
dpn->name = NULL;
dpn->type = l->type ? l->type
: (tpw ? tpw->type : NULL);
dpn->next = NULL;
dpn->variadic = 0;
*dpp = dpn;
dpp = &dpn->next;
if (tpw) tpw = tpw->next;
}
Type *decltt = newtype(c->a, TY_TUPLE);
decltt->params = declp;
cg_tuple_lit_to_cursor(c, locals, n->rhs, decltt);
} else {
cgexpr(c, n->rhs, *locals);
}
Tparam *recvp = litrhs ? declp : tp0;
if (sret_recv > 0) {
int scr = cg_sretscr_off;
int foff = 0;
@@ -14137,7 +14220,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
int gpcap = TUPLE_GPCAP;
int ssecap = TUPLE_SSECAP;
int gptotal = 0, ssetotal = 0;
for (Tparam *tp = tp0; tp; tp = tp->next) {
for (Tparam *tp = recvp; tp; tp = tp->next) {
if (fld_isfloat(tp->type, &mf32))
ssetotal++;
else
@@ -14152,7 +14235,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
"capacity (%d eightbytes: X0,X1); see return-ABI #10",
ssecap);
int gpcur = 0, ssecur = 0;
Tparam *tp = tp0;
Tparam *tp = recvp;
for (Node *l = n->list; l; l = l->next) {
Type *et = tp ? tp->type : NULL;
int isflt = fld_isfloat(et, &mf32);