wcc: struct-param float fields via SSE arg regs (#165)

struct params were passed GP-only, so a struct{f64,f64} argument landed in
DI/SI instead of X0/X1 — value-correct for internal ww calls (the bits
round-trip) but not SysV register-class conformant. Add a per-eightbyte
classifier (struct_float_class) routing a qualifying struct's float eightbytes
through the SSE arg cursor, reusing #163's dual-cursor plumbing and #164's
field classification. A struct qualifies only when every eightbyte is
pure-integer or a lone f64 exactly filling it (and >=1 f64); anything else —
any f32, multiple floats per eightbyte, a straddling or aggregate field —
falls back to the unchanged GP path (f32 sub-eightbyte packing deferred #165b).
Both stages' predicates are alias-aware and identical in coverage.

Gate-blind and value-correct either way, so the discriminator is the callee's
receive instruction (MOVSD vs MOVQ), scoped per-function — covered by probe
946.
This commit is contained in:
2026-05-27 23:31:19 +09:00
parent 0465c423c1
commit a917533fbf
8 changed files with 1048 additions and 73 deletions

View File

@@ -3980,29 +3980,76 @@ fn cgcall(c: *cgen, n: *node) void = {
p = p.next;
};
} else {
let extra: i32 = 0;
// str IS []u8: 3-word arg, same as slice (#1/Phase 3).
if (nodeisstr(c, a)) { extra = 2; };
if (nodeisslice(c, a)) { extra = 2; };
// #21: tagged-CALL arg was pushed AX/DX/CX/R8 high→low
// by pushargsrev; size the per-arg pop to match so the
// next arg's POPQ doesn't land on residual tag/payload
// words and shift intidx out of sync.
let tcs: i32 = taggedcallslot(c, a);
if (tcs > 0) { extra = tcs / 8 - 1; };
let words: i32 = 1 + extra;
let w: i32 = 0;
for (w < words) {
if (intidx < 6) {
emitline("\tPOPQ\t");
emitline(argregname(intidx));
emitline("\n");
intidx += 1;
} else {
stackslots += 1;
let stfc: i32 = 0;
if (a.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, a.str);
if (lc != nil) { stfc = structfloatclass(c, lc.tnode); };
};
if (stfc != 0) {
// #165: float-bearing struct arg — drain by SysV
// eightbyte class: a lone-f64 eightbyte MOVSD off
// (SP) into the next XMM (X0..X7), a pure-INT
// eightbyte POPQ into the next INTEGER arg reg
// (DI/SI/..). The struct-ident push staged raw slot
// words (class-independent); only the drain differs.
// Gated to qualifying floats; all-int + f32-packed
// keep the generic pop below. Reg overflow loud-
// stops (rule 7), the partial-spill stitch out of
// scope (#163 twin).
let nb: i32 = stfc & 15;
let e: i32 = 0;
for (e < nb) {
let issse: bool = (stfc & (16 << e)) != 0;
if (issse) {
if (fpidx >= 8) {
let msg: str = "float struct arg eightbyte overflows SSE arg regs (X0..X7); stitch out of scope, see #165\n";
os.write(2, msg.ptr, msg.len: u64);
os.exit(1);
};
emitline("\tMOVSD\t(SP), ");
emitline(fargregname(fpidx));
emitline("\n");
emitline("\tADDQ\t$8, SP\n");
fpidx += 1;
} else {
if (intidx >= 6) {
let msg: str = "float struct arg eightbyte overflows integer arg regs (DI/SI/DX/CX/R8/R9); stitch out of scope, see #165\n";
os.write(2, msg.ptr, msg.len: u64);
os.exit(1);
};
emitline("\tPOPQ\t");
emitline(argregname(intidx));
emitline("\n");
intidx += 1;
};
popped += 1;
e += 1;
};
} else {
let extra: i32 = 0;
// str IS []u8: 3-word arg, same as slice (#1/Phase 3).
if (nodeisstr(c, a)) { extra = 2; };
if (nodeisslice(c, a)) { extra = 2; };
// #21: tagged-CALL arg was pushed AX/DX/CX/R8 high→low
// by pushargsrev; size the per-arg pop to match so the
// next arg's POPQ doesn't land on residual tag/payload
// words and shift intidx out of sync.
let tcs: i32 = taggedcallslot(c, a);
if (tcs > 0) { extra = tcs / 8 - 1; };
let words: i32 = 1 + extra;
let w: i32 = 0;
for (w < words) {
if (intidx < 6) {
emitline("\tPOPQ\t");
emitline(argregname(intidx));
emitline("\n");
intidx += 1;
} else {
stackslots += 1;
};
popped += 1;
w += 1;
};
popped += 1;
w += 1;
};
};
};