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

@@ -1975,103 +1975,63 @@ fn cglet(c: *cgen, n: *node) void = {
};
// fall through to the generic sret receive below.
};
// 32B tuple init for `let t: (scalar, str) = call()` /
// `let t: (str, scalar) = call()` (#105 / #164/#107). Each
// element rides its SysV class — a float its SSE cursor reg
// (X0,X1 = tupsse), an integer/ptr word its INTEGER cursor reg
// In-cap tuple initialiser (#105 / #164/#107): every in-cap
// tuple receive routes here, keyed on the DECLARED TYPE's
// register classify (sretretsize == 0, the shared SSoT) —
// mirror of cstage cgen.c N_LET tuple arm. Each element rides
// its SysV class — a float its SSE cursor reg (X0,X1 =
// tupsse), an integer/ptr word its INTEGER cursor reg
// (tupreg), a slice/str its 3-word {ptr,len,cap} header over
// consecutive INTEGER cursor regs — on INDEPENDENT counters.
// tupstore 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). str IS []u8 (24B) → 32B tuple
// (#1/Phase 3, task #5). Mirror of the cstage unified branch.
// slice/str takes its 24B header). Over-cap falls through to
// the sret receive below (#240 — an over-cap receive via the
// register cursor read garbage past R8).
//
// #240: cap-gate on sz (16/32 = in-cap, AX/DX/CX/R8). Without it
// an OVER-cap mixed-scalar tuple (e.g. (int,[]u8,str), 56B) was
// received here via the register cursor (4 GP regs + R8 fill)
// instead of from the sret dest the callee actually wrote — a
// silent cs!=ww divergence (cstage gates the twin branch on
// `sz == 16 || sz == 32`, cgen.c N_LET, and falls an over-cap
// tuple through to the sret receive below).
if (n.lhs != nil) {
if (n.lhs.kind == nkind.N_TTUPLE) {
let p0: *node = n.lhs.list;
let p1: *node = nil;
if (p0 != nil) { p1 = p0.next; };
let p0t: *node = nil;
let p1t: *node = nil;
if (p0 != nil) { p0t = p0.lhs; };
if (p1 != nil) { p1t = p1.lhs; };
let s0_is_str: bool = isstrtype(c, p0t)
|| isslicetype(c, p0t);
let s1_is_str: bool = isstrtype(c, p1t)
|| isslicetype(c, p1t);
if (p0 != nil) {
if (p1 != nil) {
if ((s0_is_str != s1_is_str) && (sz == 16 || sz == 32)) {
cgexpr(c, rhs);
let gpcur: i32 = 0;
let ssecur: i32 = 0;
let eoff: i32 = 0;
let q: *node = n.lhs.list;
for (q != nil) {
let qt: *node = q.lhs;
let isflt: bool = isfloattype(c, qt);
let wide: bool = isstrtype(c, qt)
|| isslicetype(c, qt);
tupstore(c, gpcur, ssecur,
off + eoff, wide, qt);
if (isflt) {
ssecur = ssecur + 1;
} else {
gpcur = gpcur + tupebytes(wide);
};
if (wide) {
eoff = eoff + (tyslicesize(): i32);
} else {
eoff = eoff + 8;
};
q = q.next;
};
c.lastwasreturn = 0;
return;
};
};
};
};
// C-t1 (#33): the old keys were producer-SHAPE — the mixed
// str/scalar arm required s0_is_str != s1_is_str (syntactic)
// AND sz==16/32, the rt16 arm required an N_CALL rhs
// (rettupleof) — so a scalar-scalar tuple LITERAL `(3, 4)`
// matched neither and fell to the generic single-word store,
// silently dropping word 1 (#209/#211-class syntactic-vs-type
// keying). Alias-peel mirrors cstage's type_chase_named; the
// unannotated `let t = f()` shape rides the inferletcalltype
// tn above.
let ttup: *node = tn;
for (ttup != nil && ttup.kind == nkind.N_TNAME) {
ttup = aliaslookup(c, ttup.str);
};
// 16B tuple init from a function call (#105 / #164/#107). Each
// eightbyte rides its SysV class: a float its SSE cursor reg
// (X0,X1 = tupsse), an integer/ptr word its INTEGER cursor reg
// (AX,DX = tupreg), INDEPENDENT counters — the RETURN leaves
// floats in X0/X1 and integer words in AX/DX, so a blanket MOVQ
// spill would store garbage where a float rode and the #103-
// FACE-Z field read (MOVSD-from-slot) would see it. tupstore
// routes each word from its real class; the same split drives
// the destructure / reassign sites. Without this branch a 16B
// tuple receive fell to the generic single-word store below and
// dropped word1 — silent loss of t.1 (#102).
let rt16: *node = rettupleof(c, rhs);
if (rt16 != nil && sz == 16) {
cgexpr(c, rhs);
let gpcur: i32 = 0;
let ssecur: i32 = 0;
let eoff: i32 = 0;
let q: *node = rt16.list;
for (q != nil) {
let qt: *node = q.lhs;
let isflt: bool = isfloattype(c, qt);
tupstore(c, gpcur, ssecur, off + eoff, false, qt);
if (isflt) {
ssecur = ssecur + 1;
} else {
gpcur = gpcur + 1;
if (ttup != nil) {
if (ttup.kind == nkind.N_TTUPLE
&& sretretsize(c, ttup) == 0) {
cgexpr(c, rhs);
let gpcur: i32 = 0;
let ssecur: i32 = 0;
let eoff: i32 = 0;
let q: *node = ttup.list;
for (q != nil) {
let qt: *node = q.lhs;
let isflt: bool = isfloattype(c, qt);
let wide: bool = isstrtype(c, qt)
|| isslicetype(c, qt);
tupstore(c, gpcur, ssecur,
off + eoff, wide, qt);
if (isflt) {
ssecur = ssecur + 1;
} else {
gpcur = gpcur + tupebytes(wide);
};
if (wide) {
eoff = eoff + (tyslicesize(): i32);
} else {
eoff = eoff + 8;
};
q = q.next;
};
eoff = eoff + 8;
q = q.next;
c.lastwasreturn = 0;
return;
};
c.lastwasreturn = 0;
return;
};
// Array literal init: `let xs: [N]T = [a, b, c];` (or [_]T).
// Walk elements in declaration order, store each at off + i*esz