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:
@@ -15748,6 +15748,83 @@ fn structparamsize(c: *cgen, t: *node) i32 = {
|
||||
return si.totsize;
|
||||
};
|
||||
|
||||
// structfloatclass — SysV per-eightbyte classification for the #165
|
||||
// float-bearing-struct param case (param twin of #171's struct return;
|
||||
// classifies per-eightbyte, not #163's per-element). Returns 0 when the
|
||||
// struct does NOT qualify — the caller keeps the all-GP transport, which
|
||||
// is correct + byte-identical there — for: not a <=16B struct; an all-
|
||||
// integer layout (no float to route); an f32 field; >1 float packed in
|
||||
// one eightbyte; a float straddling the 8-byte SysV eightbyte boundary;
|
||||
// or an aggregate field (SysV would recurse, out of scope). Otherwise a
|
||||
// packed result whose low bits hold the eightbyte count nb (1|2) and bit
|
||||
// (4+e) marks eightbyte e SSE-class (a lone f64). Qualifies iff every
|
||||
// eightbyte is pure-INT or a lone f64 AND at least one is f64. f32 /
|
||||
// sub-eightbyte packing deferred (#165b). Mirrors cstage
|
||||
// struct_float_class (cmd/w6c/cgen.c).
|
||||
fn structfloatclass(c: *cgen, t: *node) i32 = {
|
||||
if (c == nil) { return 0; };
|
||||
let r: *node = resolvetype(c, t);
|
||||
if (r == nil) { return 0; };
|
||||
if (r.kind != nkind.N_TNAME) { return 0; };
|
||||
let nm: str = r.str;
|
||||
if (streq(nm, "str")) { return 0; };
|
||||
if (primsize(nm) > 0) { return 0; };
|
||||
let si: *structinfo = structlookup(c, nm);
|
||||
if (si == nil) { return 0; };
|
||||
if (si.totsize <= 0) { return 0; };
|
||||
if (si.totsize > 16) { return 0; };
|
||||
// SysV classifies aggregates in 8-byte eightbytes; 8 is the
|
||||
// eightbyte stride, not a type footprint.
|
||||
let nb: i32 = 1;
|
||||
if (si.totsize > 8) { nb = 2; };
|
||||
let nflt0: i32 = 0; let nflt1: i32 = 0;
|
||||
let nint0: i32 = 0; let nint1: i32 = 0;
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
let foff: i32 = fi.foff;
|
||||
let fsz: i32 = fi.fsz;
|
||||
let e: i32 = foff / 8;
|
||||
if (e < 0) { return 0; };
|
||||
if (e >= nb) { return 0; };
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
if (isf32type(c, fi.tnode)) { return 0; };
|
||||
if ((foff & 7) != 0) { return 0; };
|
||||
if (fsz != 8) { return 0; };
|
||||
if (e == 0) { nflt0 += 1; } else { nflt1 += 1; };
|
||||
} else {
|
||||
if (isslicetype(c, fi.tnode)) { return 0; };
|
||||
if (isstrtype(c, fi.tnode)) { return 0; };
|
||||
if (istaggedtype(c, fi.tnode)) { return 0; };
|
||||
if (structparamsize(c, fi.tnode) > 0) { return 0; };
|
||||
// Alias-aware array/tuple reject, mirroring cstage's
|
||||
// NAMED-peeled TY_ARRAY/TY_TUPLE (cgen.c struct_float_class).
|
||||
// A direct-AST-kind N_TARRAY test misses an aliased array
|
||||
// and every tuple field; the fsz>8 guard below also lets a
|
||||
// <=8B one slip, so such a struct would wrongly SSE-route on
|
||||
// this stage but stay GP on cstage (a #165b leak).
|
||||
let rf: *node = resolvetype(c, fi.tnode);
|
||||
if (rf != nil) {
|
||||
if (rf.kind == nkind.N_TARRAY) { return 0; };
|
||||
if (rf.kind == nkind.N_TTUPLE) { return 0; };
|
||||
};
|
||||
if (fsz > 8) { return 0; };
|
||||
if ((foff + fsz - 1) / 8 != e) { return 0; };
|
||||
if (e == 0) { nint0 += 1; } else { nint1 += 1; };
|
||||
};
|
||||
fi = fi.finext;
|
||||
};
|
||||
let enc: i32 = nb;
|
||||
let hasfloat: bool = false;
|
||||
if (nflt0 == 1 && nint0 == 0) { enc += 16; hasfloat = true; }
|
||||
else { if (nflt0 != 0) { return 0; }; };
|
||||
if (nb == 2) {
|
||||
if (nflt1 == 1 && nint1 == 0) { enc += 32; hasfloat = true; }
|
||||
else { if (nflt1 != 0) { return 0; }; };
|
||||
};
|
||||
if (!hasfloat) { return 0; };
|
||||
return enc;
|
||||
};
|
||||
|
||||
// istaggedtype — alias-aware. Reads stamped tinfo so `T`,
|
||||
// `type alias = (A|B)`, `type error = !(invalid|overflow)` all
|
||||
// resolve to TY_TAGGED — tinfofornode handles the N_TBANG unwrap
|
||||
@@ -21192,29 +21269,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;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -26361,6 +26485,52 @@ fn cgfnparams(c: *cgen, params: *node) void = {
|
||||
p = p.next;
|
||||
continue;
|
||||
};
|
||||
let sfc: i32 = structfloatclass(c, p.lhs);
|
||||
if (sfc != 0) {
|
||||
// #165: float-bearing struct PARAM receive (param
|
||||
// twin of #163's tuple). Classify each SysV
|
||||
// eightbyte; a lone-f64 eightbyte reads its XMM
|
||||
// (X0..X7), a pure-INT eightbyte its INTEGER arg reg
|
||||
// (DI/SI/..), stored into the param slot at the
|
||||
// 8-byte eightbyte stride. Gated to qualifying
|
||||
// structs by structfloatclass — all-int + f32-packed
|
||||
// fall through to the GP struct arm below (byte-id /
|
||||
// #165b). Reg overflow loud-stops (rule 7).
|
||||
let off: i32 = localadd(c, nm, structparamsize(c, p.lhs), p.lhs);
|
||||
let nb: i32 = sfc & 15;
|
||||
let e: i32 = 0;
|
||||
for (e < nb) {
|
||||
let issse: bool = (sfc & (16 << e)) != 0;
|
||||
if (issse) {
|
||||
if (fidx >= 8) {
|
||||
let msg: str = "float struct param 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");
|
||||
emitline(fargregname(fidx));
|
||||
emitline(", ");
|
||||
emitoff((off + e*8): i64);
|
||||
emitline("(BP)\n");
|
||||
fidx += 1;
|
||||
} else {
|
||||
if (idx >= 6) {
|
||||
let msg: str = "float struct param 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("\tMOVQ\t");
|
||||
emitline(argregname(idx));
|
||||
emitline(", ");
|
||||
emitoff((off + e*8): i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
};
|
||||
e += 1;
|
||||
};
|
||||
p = p.next;
|
||||
continue;
|
||||
};
|
||||
if (istaggedtype(c, p.lhs)) {
|
||||
let slot: i32 = slotsize(c, p.lhs);
|
||||
let nw: i32 = slot / 8;
|
||||
|
||||
Reference in New Issue
Block a user