wcc: struct-return float fields via SSE return regs (#171a)

The RETURN twin of #165: a qualifying float-struct was returned GP-only
(struct{f64,f64} in AX/DX instead of X0/X1) — value-correct via GP transport
but not SysV register-class conformant. Route each float eightbyte through the
SSE return cursor (X0/X1) and each integer eightbyte through GP (AX/DX) via
independent cursors, at the struct-return SEND and RECV, both stages, reusing
struct_float_class verbatim. Closes the temporary tuple-SSE/struct-GP
divergence opened across #164/#165.

A qualifying struct has >=1 lone f64 so maxalign is 8 and the ABI slot is an
8-multiple — no sub-8 tail — so #169's sized tail is unreachable here and the
integer eightbyte uses a full MOVQ (cstage agrees, proven by the f64i32
cs==ww byte-id). f32 / multi-float-per-eightbyte stays GP (deferred #171b);
>16B stays sret.

Gate-blind and value-correct, so the discriminator is the SEND/RECV register
class (MOVSD X0/X1 vs MOVQ AX/DX) — covered by probe 946_structret_run.
This commit is contained in:
2026-05-28 01:50:28 +09:00
parent d4e500f61c
commit c882bcf27c
6 changed files with 679 additions and 33 deletions

View File

@@ -7717,6 +7717,40 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
* excludes them so they fall through to the existing scalar
* path rather than emit a stomping MOVQ tail. Sizes >24B also
* fall through (sret deferred, same constraint as #4). */
/* #171a: float-bearing struct RECEIVE (the return twin of
* #165's param recv). cgexpr leaves each float eightbyte in
* its SSE return reg (X0,X1 = tuple_sse_seq) and each INT
* eightbyte in its INTEGER return reg (AX,DX = tuple_rseq),
* on INDEPENDENT cursors per SysV (ref/qbe/amd64/sysv.c retr)
* — so a float is read from the next XMM regardless of its
* positional eightbyte (struct{f64,i32}: e0←X0, e1←AX). A
* qualifying struct's size is maxalign-rounded to a multiple
* of 8 (an f64 forces align 8), so every eightbyte is a full
* word — the #169 sized tail (MOVL/MOVB) is unreachable here.
* struct_float_class gates to qualifying structs; all-int +
* f32 fall through to the GP recv below (byte-id / #171b). */
if (n->rhs && n->rhs->kind == N_CALL && lu
&& lu->kind == TY_STRUCT) {
int sclass[2], snb;
if ((snb = struct_float_class(lu, sclass)) > 0) {
cgexpr(c, n->rhs, *locals);
int gpcur = 0, ssecur = 0;
for (int e = 0; e < snb; e++) {
if (sclass[e]) {
ins2(c, A_MOVSD,
areg(tuple_sse_seq[ssecur]),
amem(D_BP, off + e * 8));
ssecur++;
} else {
ins2(c, A_MOVQ,
areg(tuple_rseq[gpcur]),
amem(D_BP, off + e * 8));
gpcur++;
}
}
break;
}
}
if (n->rhs && n->rhs->kind == N_CALL && lu
&& lu->kind == TY_STRUCT && sz <= 24
&& (sz % 8 == 0 || sz % 8 == 1
@@ -8290,12 +8324,44 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
k += 1;
}
}
ins2(c, A_MOVQ, amem(D_BP, scr + 0),
areg(D_AX));
ins2(c, A_MOVQ, amem(D_BP, scr + 8),
areg(D_DX));
ins2(c, A_MOVQ, amem(D_BP, scr + 16),
areg(D_CX));
/* #171a: float-bearing struct RETURN (the return
* twin of #165's param recv). A qualifying struct's
* float eightbytes ride the SSE return row (X0,X1 =
* tuple_sse_seq), its INT eightbytes the INTEGER
* return row (AX,DX = tuple_rseq), on INDEPENDENT
* cursors per SysV (ref/qbe/amd64/sysv.c retr) — so a
* float lands in the next XMM regardless of its
* positional eightbyte (struct{f64,i32}: e0→X0, e1→AX,
* NOT DX). The scr slot is zero-padded to 24B, so a
* full MOVQ on a trailing INT eightbyte reads no
* garbage — the #169 sized tail is a RECV-only concern.
* struct_float_class gates to qualifying structs (>=1
* f64, every eightbyte lone-f64 or pure-INT); all-int +
* f32 keep the AX/DX/CX transport (byte-id / #171b). */
int sclass[2], snb;
if ((snb = struct_float_class(rt, sclass)) > 0) {
int gpcur = 0, ssecur = 0;
for (int e = 0; e < snb; e++) {
if (sclass[e]) {
ins2(c, A_MOVSD,
amem(D_BP, scr + e * 8),
areg(tuple_sse_seq[ssecur]));
ssecur++;
} else {
ins2(c, A_MOVQ,
amem(D_BP, scr + e * 8),
areg(tuple_rseq[gpcur]));
gpcur++;
}
}
} else {
ins2(c, A_MOVQ, amem(D_BP, scr + 0),
areg(D_AX));
ins2(c, A_MOVQ, amem(D_BP, scr + 8),
areg(D_DX));
ins2(c, A_MOVQ, amem(D_BP, scr + 16),
areg(D_CX));
}
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
ins1(c, A_POPQ, areg(D_BP));
ins0(c, A_RET);