w6c: promote f32 arg to double in C-variadic tail (#14)

C99 6.5.2.2p6 requires float->double promotion for floats in the
variadic region of a call; w6c emitted MOVSS (4B), so a C variadic's
va_arg(double) read 8B of garbage. Promote at push (CVTSS2SD) so the
8B slot holds a real double, covering the XMM-reg and >8-float spill
cases uniformly; fi/AL XMM-count is unchanged.

cstage only; the wwstage twin rides C2 (#10), which adds wwstage's
C-variadic-call codegen.
This commit is contained in:
2026-06-21 11:14:14 +09:00
parent 294f4c93fd
commit 8f0ce09f2a
2 changed files with 78 additions and 5 deletions

View File

@@ -9395,6 +9395,23 @@ cgexpr(Cg *c, Node *n, Local *locals)
memarg[i] =
tagged_memarg_size(args[i]->type);
}
/* C default argument promotion (#14): an f32 passed in the
* variadic region of a C-variadic call must widen to f64 — the
* callee reads it with va_arg(double) (8B). The region is every
* arg past the fixed params (the C `...` adds no Tparam). The
* push promotes via CVTSS2SD and stores a full 8B double; the
* pop then reloads MOVSD and counts it as exactly one SSE reg.
* Fixed params keep their declared width. Ref SysV §3.5.7;
* C99 §6.5.2.2p6. */
int promote_f32[64] = {0};
if (cu && cu->kind == TY_FN && cu->variadic) {
int nfixed = 0;
for (Tparam *p = callee_params; p; p = p->next)
nfixed++;
for (int i = nfixed; i < argcount; i++)
if (args[i] && node_isf32(args[i]))
promote_f32[i] = 1;
}
/* #38b MEMORY-class pre-pass: stage every >48B tagged arg on
* the stack BELOW all register-class words (rightmost-first,
* so the leftmost mem arg lands at the lowest address = the
@@ -9791,8 +9808,16 @@ cgexpr(Cg *c, Node *n, Local *locals)
* float class drives the width per ref/qbe
* amd64/emit.c:524 (slot-copy single→movss). The
* slot is 8B either way; the pop reads the same
* width back. #143. */
int fmov = op_for(args[i], A_MOVSD, A_MOVSS);
* width back. #143.
*
* #14: a variadic-region f32 is promoted to f64
* here (CVTSS2SD in X0) and spilled MOVSD, so the
* slot holds a real 8B double for the pop. */
int fmov = promote_f32[i] ? A_MOVSD
: op_for(args[i], A_MOVSD, A_MOVSS);
if (promote_f32[i])
ins2(c, A_CVTSS2SD, areg(D_X0),
areg(D_X0));
ins2(c, A_SUBQ, aimm(8), areg(D_SP));
ins2(c, fmov, areg(D_X0), amem(D_SP, 0));
} else if (node_isstr(args[i])) {
@@ -10017,9 +10042,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
if (fi < 8) {
/* Reload the spilled f32/f64 at its class
* width — MOVSS for f32, MOVSD for f64 —
* matching the push above (#143). */
int fmov = op_for(args[i], A_MOVSD,
A_MOVSS);
* matching the push above (#143). A #14
* variadic-region f32 was promoted to f64
* at push, so its slot reloads MOVSD. */
int fmov = promote_f32[i] ? A_MOVSD
: op_for(args[i], A_MOVSD, A_MOVSS);
ins2(c, fmov, amem(D_SP, 0),
areg(sysv_fargregs[fi]));
ins2(c, A_ADDQ, aimm(8), areg(D_SP));