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:
165
cmd/w6c/cgen.c
165
cmd/w6c/cgen.c
@@ -432,6 +432,68 @@ struct_arg_size(Type *t)
|
||||
return (int)t->size;
|
||||
}
|
||||
|
||||
/* struct_float_class — SysV per-eightbyte classification for the #165
|
||||
* float-bearing-struct param case (the param twin of #171's struct
|
||||
* return, classifying per-eightbyte rather than #163's per-element).
|
||||
* Fills cls[e] = 1 (SSE) / 0 (INTEGER) for each of the struct's 1-2
|
||||
* eightbytes and returns the eightbyte count, but ONLY for a qualifying
|
||||
* struct: every eightbyte is either pure-INTEGER or a lone f64 exactly
|
||||
* filling it, AND at least one is f64. Returns 0 (caller keeps the all-
|
||||
* GP transport, which is correct + byte-identical for those) when the
|
||||
* type is not a <=16B struct, has an all-integer layout (no float to
|
||||
* route), carries an f32 field, packs >1 float into an eightbyte, has a
|
||||
* float straddling the 8-byte SysV eightbyte boundary, or holds an
|
||||
* aggregate field (SysV would recurse — out of scope here). f32 / sub-
|
||||
* eightbyte packing is deferred (#165b). */
|
||||
static int
|
||||
struct_float_class(Type *t, int *cls)
|
||||
{
|
||||
if (t == NULL) return 0;
|
||||
if (t->kind == TY_NAMED) t = t->under;
|
||||
if (t == NULL || t->kind != TY_STRUCT) return 0;
|
||||
int sz = (int)t->size;
|
||||
if (sz <= 0 || sz > 16) return 0;
|
||||
/* SysV classifies aggregates in 8-byte eightbytes (§3.2.3); 8 is
|
||||
* the eightbyte stride, not a type footprint. */
|
||||
int nb = (sz > 8) ? 2 : 1;
|
||||
int nflt[2], nint[2];
|
||||
nflt[0] = nflt[1] = nint[0] = nint[1] = 0;
|
||||
for (Tfield *f = t->fields; f; f = f->next) {
|
||||
Type *fu = (f->type && f->type->kind == TY_NAMED)
|
||||
? f->type->under : f->type;
|
||||
if (fu == NULL) return 0;
|
||||
int foff = (int)f->offset;
|
||||
int fsz = (int)fu->size;
|
||||
int e = foff / 8;
|
||||
if (e < 0 || e >= nb) return 0;
|
||||
int f32;
|
||||
if (fld_isfloat(f->type, &f32)) {
|
||||
if (f32) return 0;
|
||||
if (foff % 8 != 0 || fsz != 8) return 0;
|
||||
nflt[e]++;
|
||||
} else {
|
||||
if (fu->kind == TY_STRUCT || fu->kind == TY_ARRAY
|
||||
|| fu->kind == TY_SLICE || fu->kind == TY_STR
|
||||
|| fu->kind == TY_TAGGED || fu->kind == TY_TUPLE)
|
||||
return 0;
|
||||
if (fsz > 8 || (foff + fsz - 1) / 8 != e) return 0;
|
||||
nint[e]++;
|
||||
}
|
||||
}
|
||||
int hasfloat = 0;
|
||||
for (int e = 0; e < nb; e++) {
|
||||
if (nflt[e] == 1 && nint[e] == 0) {
|
||||
cls[e] = 1;
|
||||
hasfloat = 1;
|
||||
} else if (nflt[e] == 0) {
|
||||
cls[e] = 0;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return hasfloat ? nb : 0;
|
||||
}
|
||||
|
||||
/* Tagged-union arg byte size: 16 (8B variants) or 24 (16B variants).
|
||||
* Nullable-folded `(*T | void)` collapses to 8 bytes (just the
|
||||
* pointer). Returns 0 if not a tagged union or too large to pass
|
||||
@@ -5645,13 +5707,59 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
stackslots++;
|
||||
}
|
||||
} else if (node_isstructarg(args[i])) {
|
||||
int sz = struct_arg_size(args[i]->type);
|
||||
int eb = (sz > 8) ? 2 : 1;
|
||||
for (int k = 0; k < eb; k++) {
|
||||
if (ii < 6)
|
||||
ins1(c, A_POPQ, areg(sysv_argregs[ii++]));
|
||||
else
|
||||
stackslots++;
|
||||
int sclass[2], snb;
|
||||
/* SSE-drain only for an ident arg: the struct push
|
||||
* stages raw slot words for an N_IDENT only (non-
|
||||
* ident struct args are a pre-existing >8B-push gap,
|
||||
* out of scope). Gating here keeps cstage byte-id
|
||||
* with wwstage, whose type lookup is ident-keyed. */
|
||||
if (args[i]->kind == N_IDENT
|
||||
&& (snb = struct_float_class(args[i]->type,
|
||||
sclass)) > 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 words (class-independent); only the
|
||||
* drain differs. Gated to qualifying floats;
|
||||
* all-int + f32-packed keep the all-GP pop
|
||||
* below. Reg overflow loud-stops (rule 7), the
|
||||
* partial-spill stitch out of scope (#163 twin). */
|
||||
for (int e = 0; e < snb; e++) {
|
||||
if (sclass[e]) {
|
||||
if (fi >= 8)
|
||||
fatal("float struct arg "
|
||||
"eightbyte overflows SSE "
|
||||
"arg regs (X0..X7); stitch "
|
||||
"out of scope, see #165");
|
||||
ins2(c, A_MOVSD,
|
||||
amem(D_SP, 0),
|
||||
areg(sysv_fargregs[fi]));
|
||||
ins2(c, A_ADDQ, aimm(8),
|
||||
areg(D_SP));
|
||||
fi++;
|
||||
} else {
|
||||
if (ii >= 6)
|
||||
fatal("float struct arg "
|
||||
"eightbyte overflows "
|
||||
"integer arg regs (DI/SI/"
|
||||
"DX/CX/R8/R9); stitch out "
|
||||
"of scope, see #165");
|
||||
ins1(c, A_POPQ,
|
||||
areg(sysv_argregs[ii++]));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int sz = struct_arg_size(args[i]->type);
|
||||
int eb = (sz > 8) ? 2 : 1;
|
||||
for (int k = 0; k < eb; k++) {
|
||||
if (ii < 6)
|
||||
ins1(c, A_POPQ,
|
||||
areg(sysv_argregs[ii++]));
|
||||
else
|
||||
stackslots++;
|
||||
}
|
||||
}
|
||||
} else if (node_istaggedarg(args[i])) {
|
||||
int sz = tagged_arg_size(args[i]->type);
|
||||
@@ -8762,6 +8870,49 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* #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's frame
|
||||
* slot at the 8-byte eightbyte stride. Gated to qualifying
|
||||
* structs by struct_float_class — all-int + f32-packed keep
|
||||
* the GP transport below (byte-id / #165b). Placed before the
|
||||
* single-class eightbyte logic, which can't model a mixed
|
||||
* GP/SSE aggregate. Reg overflow loud-stops (rule 7). */
|
||||
if (is_struct) {
|
||||
int sclass[2], snb;
|
||||
if ((snb = struct_float_class(pt, sclass)) > 0) {
|
||||
int sz = (int)pu->size;
|
||||
int off = localoff(c, &locals, p->str, sz, &frame);
|
||||
for (int e = 0; e < snb; e++) {
|
||||
if (sclass[e]) {
|
||||
if (fargi >= 8)
|
||||
fatal("float struct param "
|
||||
"eightbyte overflows SSE "
|
||||
"arg regs (X0..X7); stitch "
|
||||
"out of scope, see #165");
|
||||
ins2(c, A_MOVSD,
|
||||
areg(sysv_fargregs[fargi]),
|
||||
amem(D_BP, off + e * 8));
|
||||
fargi++;
|
||||
} else {
|
||||
if (argi >= 6)
|
||||
fatal("float struct param "
|
||||
"eightbyte overflows "
|
||||
"integer arg regs (DI/SI/"
|
||||
"DX/CX/R8/R9); stitch out "
|
||||
"of scope, see #165");
|
||||
ins2(c, A_MOVQ,
|
||||
areg(sysv_argregs[argi]),
|
||||
amem(D_BP, off + e * 8));
|
||||
argi++;
|
||||
}
|
||||
}
|
||||
if (tp) tp = tp->next;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Args overflowing register classes live at positive offsets
|
||||
* from BP (16 + i*8). We register them as Locals at those
|
||||
* offsets, no spill needed. */
|
||||
|
||||
Reference in New Issue
Block a user