diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index e3904927..53f1fadc 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -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) { diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 0b11b874..511607da 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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 diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index b411b52a..7c62e8e4 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -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 diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 4dae206c..b64ed61f 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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 diff --git a/test/wcc/941_tuple_slot_layout_run.c b/test/wcc/941_tuple_slot_layout_run.c index ce0cde1c..05aeeda9 100644 --- a/test/wcc/941_tuple_slot_layout_run.c +++ b/test/wcc/941_tuple_slot_layout_run.c @@ -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. */