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) {

View File

@@ -32169,103 +32169,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

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

View File

@@ -32169,103 +32169,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

View File

@@ -163,6 +163,75 @@ static const struct row rows[] = {
" if (b != 4) { return 2; };\n"
" return 0;\n"
"};\n", 0 },
/* ---- C-t1 (#33): the let RECEIVE re-keyed onto the declared
* type's register classify. Pre-C-t1 wwstage keyed on producer
* SHAPE (mixed-str syntactic / rettupleof N_CALL) so a
* scalar-scalar LITERAL matched neither and dropped word 1;
* cstage keyed on sz==16/32 so 24B 3-scalar shapes dropped words
* on BOTH sources. ---- */
{ "t1_lit_packed",
"package main;\n"
"fn second() u32 = {\n"
" let t: (u32, u32) = (3, 4);\n"
" if (t.0 != 3) { return 99; };\n"
" return t.1;\n"
"};\n"
"export fn main() i32 = {\n"
" if (second() != 4) { return 1; };\n"
" return 0;\n"
"};\n", 0 },
{ "t1_lit_16",
"package main;\n"
"export fn main() i32 = {\n"
" let t: (i64, i64) = (3, 4);\n"
" if (t.0 != 3) { return 1; };\n"
" if (t.1 != 4) { return 2; };\n"
" return 0;\n"
"};\n", 0 },
{ "t1_lit_mixed",
"package main;\n"
"export fn main() i32 = {\n"
" let t: (i64, str) = (12, \"wxyz\");\n"
" if (t.0 != 12) { return 1; };\n"
" if (len(t.1) != 4) { return 2; };\n"
" return 0;\n"
"};\n", 0 },
{ "t1_lit_3scalar",
"package main;\n"
"fn second() i64 = {\n"
" let t: (i64, i64, i64) = (3, 4, 5);\n"
" if (t.0 != 3) { return -1; };\n"
" if (t.1 != 4) { return -2; };\n"
" return t.2;\n"
"};\n"
"export fn main() i32 = {\n"
" if (second() != 5) { return 1; };\n"
" return 0;\n"
"};\n", 0 },
{ "t1_call_3scalar",
"package main;\n"
"fn f() (i64, i64, i64) = {\n"
" return (7, 8, 9);\n"
"};\n"
"export fn main() i32 = {\n"
" let t: (i64, i64, i64) = f();\n"
" if (t.0 != 7) { return 1; };\n"
" if (t.1 != 8) { return 2; };\n"
" if (t.2 != 9) { return 3; };\n"
" return 0;\n"
"};\n", 0 },
{ "t1_call_noannot_neutral",
"package main;\n"
"fn f() (i64, i64) = {\n"
" return (3, 4);\n"
"};\n"
"export fn main() i32 = {\n"
" let t = f();\n"
" if (t.0 != 3) { return 1; };\n"
" if (t.1 != 4) { return 2; };\n"
" return 0;\n"
"};\n", 0 },
};
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */