cgen: wwstage cglet spills both eightbytes of a 16B tuple-from-call receive (#102)

wwstage-only. cglet had no 16B whole-tuple-from-call receive branch, so
`let t = call()` whose callee returns a 2-eightbyte (16B) tuple fell
through to the generic single-word store (MOVQ AX, off(BP)) and never
spilled word1 (the DX eightbyte) — silent loss of t.1. Align to cstage
cgen.c:6652, which spills both AX->off+0 and DX->off+8.

Not a tupstore cursor off-by-one and not f64-specific: the destructure
form `let (a,b) = call()` (cgmlet + tupstore cursor) was already byte-id;
only the whole-tuple N_LET receive dropped word1, for any element mix
incl. all-integer (i64,i64). An f64 element surfaced it first. The f64
element rides its eightbyte in AX/DX at receive and is re-read from
X0/XMM at field-read (already byte-id), so no SSE cursor is needed.
This commit is contained in:
2026-05-25 14:29:34 +09:00
parent 60c3e51dc5
commit 6a586cf792

View File

@@ -864,6 +864,26 @@ fn cglet(c: *cgen, n: *node) void = {
};
};
};
// 16B tuple init from a function call: SysV returns a
// 16-byte aggregate across the integer cursor (AX, DX). Spill
// BOTH eightbytes over the integer registers — an f64 element
// also rides its eightbyte in AX/DX here and is re-read from
// X0/XMM at field-read time, so no SSE cursor is needed (the
// f64 read is already byte-id). Mirror of cstage cgen.c:6652.
// Without this branch a 16B tuple receive (any element mix,
// incl. all-integer) fell to the generic single-word store
// below and dropped word1 — silent loss of t.1 (#102).
if (rettupleof(c, rhs) != nil && sz == 16) {
cgexpr(c, rhs);
emitline("\tMOVQ\tAX, ");
emitoff(off: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tDX, ");
emitoff((off + 8): i64);
emitline("(BP)\n");
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
// using the right width for the element type. Trailing `...`