w6c+w6c_ww: tuple by-value ARG send — every cursor-filling producer rides #163 (C-t2, #32)

node_tuplearg was N_CALL-scoped and its comment claimed non-call forms
"loud-stop" — they did NOT: a tuple ident/literal/unwrap arg fell to
the scalar single-PUSHQ default, skewing every later arg register so
the callee read garbage word 2 (byte-id both stages, the gate-blind
both-wrong class; packed shapes SIGSEGV'd pre-C-t0). The receive side
(cgfn #163 walk) was already correct.

cgexpr already fills the return-ABI cursor for every supported
producer (#241: ident via slot-to-cursor, literal via lit-to-cursor,
unwrap via payload shift; call via the return ABI) — the send now
admits exactly those into the existing @tupargscr restage + per-class
drain (node_tuplearg widened; wwstage gains nodetuplearg, mirroring it
over the local tnode / inferletcalltype; rettupleof stays N_CALL-scoped
for the destructure receives). Any OTHER tuple-typed source shape
loud-stops at the push site — the false comment's claim, now true
(rule 7). Literal tuple elements are stamped expr types, so the
restage/drain wide test goes type_isstr/type_isslice (TY_UNTYPED_STR-
aware) with the ty_str->size header stride; the wwstage twin walks a
literal's VALUE exprs the way cgtuplelittocursor classifies them.

Ken review demands folded in: (1) a NESTED composite element
(tuple/struct/array/tagged inside the tuple) occupies more than the
one GP word the restage walk counts — the checker accepted it and it
ran WRONG (inner words skewed, wwstage SIGSEGV); both stages' restage
walks now loud-stop the element kind (wiring is the filed follow-up,
task #65). (2) the variadic interaction probed: a tuple arg ahead of
a variadic tail rides the restage correctly (positive row);
variadic-of-tuples stays bounded-loud via the tuple-in-slice read
surface.

941 grows the t2 matrix: packed/16B params with branched callees,
mixed arg orders both ways, literal arg, (f64,i64) param, unwrap arg,
ken's >6-GP-pressure stress (4 leading scalars + tuple + a 7th
stack-class word), variadic-after-tuple, plus rule-7 reject rows
(chain-source arg, nested-element arg, variadic-of-tuples, over-cap
ident arg) and the fold-4 charset substrate pin ([](u32,u32) append
stays LOUD). At the C-t1 parent 18/73 checks fail: every runtime arg
row except the (f64,i64) anchor on BOTH stages (byte-identically — the
gate-blind both-wrong class) and the chain/nested args silently
accepted.
This commit is contained in:
2026-06-04 18:43:32 +09:00
parent 12af54f9f8
commit 6426fac6f2
7 changed files with 635 additions and 101 deletions

View File

@@ -238,16 +238,22 @@ node_isslice(Node *n)
}
/* node_tuplearg — the underlying TY_TUPLE Type of a tuple-typed argument
* VALUE, else NULL. #163: scoped to an N_CALL producer — the only form
* that leaves a tuple in the return-ABI cursor (AX/DX/CX/R8 + X0/X1, per
* #164). A tuple ident / literal as a first-class value is a separate
* unimplemented gap (`let t = (1,2)` does not materialise a slot today),
* so the SEND restricts to the call form and loud-stops the rest rather
* than push stale registers (rule 7, never a silent drop). */
* VALUE, else NULL. #163/#32 (C-t2): admits every producer whose cgexpr
* leaves the tuple in the return-ABI cursor (AX/DX/CX/R8 + X0/X1, per
* #164) — a CALL (return ABI), an IDENT (cg_tuple_slot_to_cursor, #241),
* a LITERAL (cg_tuple_lit_to_cursor, #241), a `?`/`!` unwrap
* (cg_tagged_tuple_payload_shift, #241). Pre-C-t2 this was N_CALL-scoped
* and the comment claimed the rest "loud-stop" — they did NOT: a tuple
* ident arg fell to the scalar single-PUSHQ default, skewing every later
* arg register (callee read garbage word 2). The cgcall push site now
* loud-stops any OTHER tuple-typed source shape (rule 7). */
static Type *
node_tuplearg(Node *n)
{
if (n == NULL || n->kind != N_CALL) return NULL;
if (n == NULL) return NULL;
if (n->kind != N_CALL && n->kind != N_IDENT && n->kind != N_TUPLE
&& n->kind != N_TRYUNW && n->kind != N_TRYPROP)
return NULL;
Type *t = n->type;
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
return (u && u->kind == TY_TUPLE) ? u : NULL;
@@ -8087,6 +8093,21 @@ cgexpr(Cg *c, Node *n, Local *locals)
}
cgexpr(c, args[i], locals);
Type *tuparg_push = 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 —
* pre-fix it fell to the scalar single-PUSHQ default
* and silently skewed every later arg register. */
if (tuparg_push == NULL) {
Type *targ = args[i]->type;
if (targ && targ->kind == TY_NAMED)
targ = targ->under;
if (targ && targ->kind == TY_TUPLE)
fatal("#32: tuple arg from unsupported "
"source shape %d (call/ident/"
"literal/unwrap only; rule 7)",
args[i]->kind);
}
if (node_isfloat(args[i])) {
/* f32 spills 4B (MOVSS), f64 8B (MOVSD): the SysV
* float class drives the width per ref/qbe
@@ -8146,11 +8167,36 @@ cgexpr(Cg *c, Node *n, Local *locals)
int gpcur = 0, ssecur = 0, eoff = 0, ef32;
int gptot = 0, sstot = 0, tsz = 0;
for (Tparam *p = tuparg_push->params; p; p = p->next) {
Type *pu = (p->type
&& p->type->kind == TY_NAMED)
? p->type->under : p->type;
int wide = pu && (pu->kind == TY_SLICE
|| pu->kind == TY_STR);
/* C-t2 (ken demand 1, rule 7): a
* COMPOSITE element (nested tuple /
* struct / array / tagged) occupies
* more than the one GP word this walk
* counts — the checker accepts the
* shape but the cursor transport
* cannot carry it; pre-guard it ran
* WRONG (inner words skewed). Loud
* until a consumer motivates wiring. */
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))
fatal("#32: tuple arg element "
"kind unsupported (nested "
"tuple/struct/array/tagged; "
"rule 7)");
/* C-t2: type_isstr/type_isslice, not a
* raw kind test — a LITERAL tuple's
* element types are the stamped expr
* types, so a strlit element is
* TY_UNTYPED_STR (size 0); the raw test
* under-classified it as 1 GP word
* against lit-to-cursor's node_isstr
* 3-word push. The wide stride reads
* ty_str->size (the header SSoT) for
* the same reason. */
int wide = type_isstr(p->type)
|| type_isslice(p->type);
if (fld_isfloat(p->type, &ef32))
sstot++;
else
@@ -8159,7 +8205,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
* size); matches the wwstage slotsize() walk so
* the @tupargscr width + reverse-push count agree
* byte-for-byte. */
tsz += wide ? (int)pu->size : 8;
tsz += wide ? (int)ty_str->size : 8;
}
/* The producing call already satisfied #164's
* return caps; guard anyway (tuple_store indexes
@@ -8177,11 +8223,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
"#163)", cg_tupargscr_sz, tsz);
}
for (Tparam *p = tuparg_push->params; p; p = p->next) {
Type *pu = (p->type
&& p->type->kind == TY_NAMED)
? p->type->under : p->type;
int wide = pu && (pu->kind == TY_SLICE
|| pu->kind == TY_STR);
int wide = type_isstr(p->type)
|| type_isslice(p->type);
int isflt = fld_isfloat(p->type, &ef32);
tuple_store(c, p->type, wide, gpcur, ssecur,
cg_tupargscr + eoff);
@@ -8189,7 +8232,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
ssecur++;
else
gpcur += tuple_ebytes(wide);
eoff += wide ? (int)pu->size : 8;
eoff += wide ? (int)ty_str->size : 8;
}
for (int w = tsz - 8; w >= 0; w -= 8) {
ins2(c, A_MOVQ,
@@ -8400,11 +8443,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
* scope (twin of #164's cap). */
int ef32;
for (Tparam *p = tu->params; p; p = p->next) {
Type *pu = (p->type
&& p->type->kind == TY_NAMED)
? p->type->under : p->type;
int wide = pu && (pu->kind == TY_SLICE
|| pu->kind == TY_STR);
/* C-t2: untyped-str-aware wide test —
* twin of the @tupargscr restage walk. */
int wide = type_isstr(p->type)
|| type_isslice(p->type);
if (fld_isfloat(p->type, &ef32)) {
if (fi >= 8)
fatal("tuple arg float "