w6c+w6c_ww: tuple-let receive keyed on type classify, not producer shape (C-t1, #33)

wwstage cglet's tuple receive was producer-SHAPE-keyed: the mixed
str/scalar arm required s0_is_str != s1_is_str (syntactic) and the
rt16 arm required an N_CALL rhs (rettupleof), so a scalar-scalar tuple
LITERAL `let t: (u32,u32) = (3,4)` matched neither and fell to the
generic single-word store — word 1 silently dropped (#209/#211-class
syntactic-vs-type keying). cstage's twin arm was sz==16/32 magic-size
keyed, so 24B 3-scalar tuples dropped words 2+ on BOTH sources.

Both stages now key the same way: declared-type TY_TUPLE + in-cap
register classify (cg_sret_retsize / sretretsize == 0, the shared
SSoT), alias-peeled; the two wwstage shape arms collapse into one
type-keyed arm walking the declared element list (the #240 lesson —
never the producer's). Over-cap falls through to the sret receive
exactly as before; unannotated `let t = f()` rides inferletcalltype.

941 grows the t1 rows: lit packed/16B/3-scalar + call 3-scalar fail at
the C-t0 parent (10/39 checks — wwstage lit halves AND both-stage
24B halves), mixed-lit + unannotated-call anchor the untouched paths.

Filed while probing: cstage silently accepts an over-cap tuple-LITERAL
let where wwstage loud-stops (pre-existing at master, task #64).
This commit is contained in:
2026-06-04 18:28:30 +09:00
parent fdfc2ce318
commit 12af54f9f8
5 changed files with 237 additions and 281 deletions

View File

@@ -10499,21 +10499,28 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off + 16));
break;
}
/* Tuple initialiser from a function call (#105 / #164/#107),
* 16B (two eightbytes) or 32B (scalar/float + slice/str header).
* Each element rides its SysV class: a float its SSE cursor reg
* (X0,X1 = tuple_sse_seq), an integer/ptr word its INTEGER cursor
* reg (tuple_rseq), a slice/str its 3-word {ptr,len,cap} header
* over consecutive INTEGER cursor regs — INDEPENDENT counters,
* so the RETURN leaves floats in X0/X1 and integer words in
* AX/DX/CX/R8. A blanket MOVQ spill would store garbage where a
* float rode and the #103-FACE-Z field read (MOVSD-from-slot)
* would see it. tuple_store routes each element from its real
* class into its positional slot (eoff steps by the element's
* slot size: a slice/str takes its 24B header); the same split
* drives the destructure / reassign sites. */
/* Tuple initialiser (#105 / #164/#107): every IN-CAP tuple
* receive routes here. Each element rides its SysV class: a
* float its SSE cursor reg (X0,X1 = tuple_sse_seq), an
* integer/ptr word its INTEGER cursor reg (tuple_rseq), a
* slice/str its 3-word {ptr,len,cap} header over consecutive
* INTEGER cursor regs — INDEPENDENT counters, so the RETURN
* leaves floats in X0/X1 and integer words in AX/DX/CX/R8. A
* blanket MOVQ spill would store garbage where a float rode
* and the #103-FACE-Z field read (MOVSD-from-slot) would see
* it. tuple_store routes each element from its real class
* into its positional slot (eoff steps by the element's slot
* size: a slice/str takes its 24B header); the same split
* drives the destructure / reassign sites.
*
* C-t1 (#33 family): keyed on the TYPE's register classify
* (cg_sret_retsize == 0, the shared SSoT), not the sz==16/32
* magic — that key missed sz==24/40/48 in-cap shapes (3-scalar
* tuples dropped words 2+ silently) and pre-C-t0 missed the
* packed sz==8 entirely. Over-cap falls through to the sret
* receive below, exactly as before. */
if (n->rhs && lu && lu->kind == TY_TUPLE
&& (sz == 16 || sz == 32)) {
&& cg_sret_retsize(lt) == 0) {
cgexpr(c, n->rhs, *locals);
int gpcur = 0, ssecur = 0, eoff = 0, ef32;
for (Tparam *p = lu->params; p; p = p->next) {