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

@@ -25061,15 +25061,57 @@ fn cgreturn(c: *cgen, n: *node) void = {
};
};
};
emitline("\tMOVQ\t");
emitoff(scroff: i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\t");
emitoff((scroff + 8): i64);
emitline("(BP), DX\n");
emitline("\tMOVQ\t");
emitoff((scroff + 16): i64);
emitline("(BP), CX\n");
// #171a: float-bearing struct RETURN (return
// twin of #165's param recv). A qualifying
// struct's float eightbytes ride the SSE return
// row (X0,X1 = tupsse), its INT eightbytes the
// INTEGER return row (AX,DX = tupreg), 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
// scratch is zero-padded to 24B so a full MOVQ
// on a trailing INT eightbyte reads no garbage
// (the #169 sized tail is a RECV concern).
// structfloatclass gates to qualifying structs;
// all-int + f32 keep the AX/DX/CX transport
// (byte-id / #171b).
let sfc: i32 = structfloatclass(c, c.fnret);
if (sfc != 0) {
let nb: i32 = sfc & 15;
let gpcur: i32 = 0;
let ssecur: i32 = 0;
let e: i32 = 0;
for (e < nb) {
let issse: bool = (sfc & (16 << e)) != 0;
if (issse) {
emitline("\tMOVSD\t");
emitoff((scroff + e*8): i64);
emitline("(BP), ");
emitline(tupsse(ssecur));
emitline("\n");
ssecur += 1;
} else {
emitline("\tMOVQ\t");
emitoff((scroff + e*8): i64);
emitline("(BP), ");
emitline(tupreg(gpcur));
emitline("\n");
gpcur += 1;
};
e += 1;
};
} else {
emitline("\tMOVQ\t");
emitoff(scroff: i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\t");
emitoff((scroff + 8): i64);
emitline("(BP), DX\n");
emitline("\tMOVQ\t");
emitoff((scroff + 16): i64);
emitline("(BP), CX\n");
};
emitline("\tMOVQ\tBP, SP\n");
emitline("\tPOPQ\tBP\n");
emitline("\tRET\n");
@@ -25539,6 +25581,49 @@ fn cglet(c: *cgen, n: *node) void = {
// stomping MOVQ tail. Sizes >24B also fall through (sret
// deferred, same constraint as #4). Mirrors the cstage
// cgen.c N_LET receive branch.
// #171a: float-bearing struct RECEIVE (return twin of #165's
// param recv). cgexpr leaves each float eightbyte in its SSE
// return reg (X0,X1 = tupsse) and each INT eightbyte in its
// INTEGER return reg (AX,DX = tupreg), 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
// abisize is maxalign-rounded to a multiple of 8 (an f64
// forces align 8), so every eightbyte is a full word — the
// #169 sized tail is unreachable here. structfloatclass gates
// to qualifying structs; all-int + f32 fall to the GP recv
// below (byte-id / #171b).
if (rhs.kind == nkind.N_CALL && tn != nil) {
let sfc: i32 = structfloatclass(c, tn);
if (sfc != 0) {
cgexpr(c, rhs);
let nb: i32 = sfc & 15;
let gpcur: i32 = 0;
let ssecur: i32 = 0;
let e: i32 = 0;
for (e < nb) {
let issse: bool = (sfc & (16 << e)) != 0;
if (issse) {
emitline("\tMOVSD\t");
emitline(tupsse(ssecur));
emitline(", ");
emitoff((off + e*8): i64);
emitline("(BP)\n");
ssecur += 1;
} else {
emitline("\tMOVQ\t");
emitline(tupreg(gpcur));
emitline(", ");
emitoff((off + e*8): i64);
emitline("(BP)\n");
gpcur += 1;
};
e += 1;
};
c.lastwasreturn = 0;
return;
};
};
if (rhs.kind == nkind.N_CALL) {
let sname: str;
sname.ptr = nil; sname.len = 0;

View File

@@ -728,15 +728,57 @@ fn cgreturn(c: *cgen, n: *node) void = {
};
};
};
emitline("\tMOVQ\t");
emitoff(scroff: i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\t");
emitoff((scroff + 8): i64);
emitline("(BP), DX\n");
emitline("\tMOVQ\t");
emitoff((scroff + 16): i64);
emitline("(BP), CX\n");
// #171a: float-bearing struct RETURN (return
// twin of #165's param recv). A qualifying
// struct's float eightbytes ride the SSE return
// row (X0,X1 = tupsse), its INT eightbytes the
// INTEGER return row (AX,DX = tupreg), 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
// scratch is zero-padded to 24B so a full MOVQ
// on a trailing INT eightbyte reads no garbage
// (the #169 sized tail is a RECV concern).
// structfloatclass gates to qualifying structs;
// all-int + f32 keep the AX/DX/CX transport
// (byte-id / #171b).
let sfc: i32 = structfloatclass(c, c.fnret);
if (sfc != 0) {
let nb: i32 = sfc & 15;
let gpcur: i32 = 0;
let ssecur: i32 = 0;
let e: i32 = 0;
for (e < nb) {
let issse: bool = (sfc & (16 << e)) != 0;
if (issse) {
emitline("\tMOVSD\t");
emitoff((scroff + e*8): i64);
emitline("(BP), ");
emitline(tupsse(ssecur));
emitline("\n");
ssecur += 1;
} else {
emitline("\tMOVQ\t");
emitoff((scroff + e*8): i64);
emitline("(BP), ");
emitline(tupreg(gpcur));
emitline("\n");
gpcur += 1;
};
e += 1;
};
} else {
emitline("\tMOVQ\t");
emitoff(scroff: i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\t");
emitoff((scroff + 8): i64);
emitline("(BP), DX\n");
emitline("\tMOVQ\t");
emitoff((scroff + 16): i64);
emitline("(BP), CX\n");
};
emitline("\tMOVQ\tBP, SP\n");
emitline("\tPOPQ\tBP\n");
emitline("\tRET\n");
@@ -1206,6 +1248,49 @@ fn cglet(c: *cgen, n: *node) void = {
// stomping MOVQ tail. Sizes >24B also fall through (sret
// deferred, same constraint as #4). Mirrors the cstage
// cgen.c N_LET receive branch.
// #171a: float-bearing struct RECEIVE (return twin of #165's
// param recv). cgexpr leaves each float eightbyte in its SSE
// return reg (X0,X1 = tupsse) and each INT eightbyte in its
// INTEGER return reg (AX,DX = tupreg), 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
// abisize is maxalign-rounded to a multiple of 8 (an f64
// forces align 8), so every eightbyte is a full word — the
// #169 sized tail is unreachable here. structfloatclass gates
// to qualifying structs; all-int + f32 fall to the GP recv
// below (byte-id / #171b).
if (rhs.kind == nkind.N_CALL && tn != nil) {
let sfc: i32 = structfloatclass(c, tn);
if (sfc != 0) {
cgexpr(c, rhs);
let nb: i32 = sfc & 15;
let gpcur: i32 = 0;
let ssecur: i32 = 0;
let e: i32 = 0;
for (e < nb) {
let issse: bool = (sfc & (16 << e)) != 0;
if (issse) {
emitline("\tMOVSD\t");
emitline(tupsse(ssecur));
emitline(", ");
emitoff((off + e*8): i64);
emitline("(BP)\n");
ssecur += 1;
} else {
emitline("\tMOVQ\t");
emitline(tupreg(gpcur));
emitline(", ");
emitoff((off + e*8): i64);
emitline("(BP)\n");
gpcur += 1;
};
e += 1;
};
c.lastwasreturn = 0;
return;
};
};
if (rhs.kind == nkind.N_CALL) {
let sname: str;
sname.ptr = nil; sname.len = 0;

View File

@@ -25061,15 +25061,57 @@ fn cgreturn(c: *cgen, n: *node) void = {
};
};
};
emitline("\tMOVQ\t");
emitoff(scroff: i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\t");
emitoff((scroff + 8): i64);
emitline("(BP), DX\n");
emitline("\tMOVQ\t");
emitoff((scroff + 16): i64);
emitline("(BP), CX\n");
// #171a: float-bearing struct RETURN (return
// twin of #165's param recv). A qualifying
// struct's float eightbytes ride the SSE return
// row (X0,X1 = tupsse), its INT eightbytes the
// INTEGER return row (AX,DX = tupreg), 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
// scratch is zero-padded to 24B so a full MOVQ
// on a trailing INT eightbyte reads no garbage
// (the #169 sized tail is a RECV concern).
// structfloatclass gates to qualifying structs;
// all-int + f32 keep the AX/DX/CX transport
// (byte-id / #171b).
let sfc: i32 = structfloatclass(c, c.fnret);
if (sfc != 0) {
let nb: i32 = sfc & 15;
let gpcur: i32 = 0;
let ssecur: i32 = 0;
let e: i32 = 0;
for (e < nb) {
let issse: bool = (sfc & (16 << e)) != 0;
if (issse) {
emitline("\tMOVSD\t");
emitoff((scroff + e*8): i64);
emitline("(BP), ");
emitline(tupsse(ssecur));
emitline("\n");
ssecur += 1;
} else {
emitline("\tMOVQ\t");
emitoff((scroff + e*8): i64);
emitline("(BP), ");
emitline(tupreg(gpcur));
emitline("\n");
gpcur += 1;
};
e += 1;
};
} else {
emitline("\tMOVQ\t");
emitoff(scroff: i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\t");
emitoff((scroff + 8): i64);
emitline("(BP), DX\n");
emitline("\tMOVQ\t");
emitoff((scroff + 16): i64);
emitline("(BP), CX\n");
};
emitline("\tMOVQ\tBP, SP\n");
emitline("\tPOPQ\tBP\n");
emitline("\tRET\n");
@@ -25539,6 +25581,49 @@ fn cglet(c: *cgen, n: *node) void = {
// stomping MOVQ tail. Sizes >24B also fall through (sret
// deferred, same constraint as #4). Mirrors the cstage
// cgen.c N_LET receive branch.
// #171a: float-bearing struct RECEIVE (return twin of #165's
// param recv). cgexpr leaves each float eightbyte in its SSE
// return reg (X0,X1 = tupsse) and each INT eightbyte in its
// INTEGER return reg (AX,DX = tupreg), 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
// abisize is maxalign-rounded to a multiple of 8 (an f64
// forces align 8), so every eightbyte is a full word — the
// #169 sized tail is unreachable here. structfloatclass gates
// to qualifying structs; all-int + f32 fall to the GP recv
// below (byte-id / #171b).
if (rhs.kind == nkind.N_CALL && tn != nil) {
let sfc: i32 = structfloatclass(c, tn);
if (sfc != 0) {
cgexpr(c, rhs);
let nb: i32 = sfc & 15;
let gpcur: i32 = 0;
let ssecur: i32 = 0;
let e: i32 = 0;
for (e < nb) {
let issse: bool = (sfc & (16 << e)) != 0;
if (issse) {
emitline("\tMOVSD\t");
emitline(tupsse(ssecur));
emitline(", ");
emitoff((off + e*8): i64);
emitline("(BP)\n");
ssecur += 1;
} else {
emitline("\tMOVQ\t");
emitline(tupreg(gpcur));
emitline(", ");
emitoff((off + e*8): i64);
emitline("(BP)\n");
gpcur += 1;
};
e += 1;
};
c.lastwasreturn = 0;
return;
};
};
if (rhs.kind == nkind.N_CALL) {
let sname: str;
sname.ptr = nil; sname.len = 0;