diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 4b0d943b..19b8ce5b 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -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)); diff --git a/test/wcc/989_ffivariadic_run.c b/test/wcc/989_ffivariadic_run.c index af0d6455..8e715936 100644 --- a/test/wcc/989_ffivariadic_run.c +++ b/test/wcc/989_ffivariadic_run.c @@ -79,6 +79,52 @@ static const struct row rows[] = { " return 1;\n" "};\n", 0 }, + + /* 3 f32 args (#14): C default arg promotion widens each to f64, so + * the callee's va_arg(double) reads 1.5+2.5+3.0 == 7.0. Values are + * exact in f32, so the f64 compare is exact. Unpromoted (MOVSS, 4B) + * the high 4B of each 8B slot is stale stack, so va_arg(double) + * pulls garbage and the sum misses 7.0 — this row FAILS pre-fix. */ + { "f32three", + "package main;\n" + "@symbol(\"fixture\") fn fixture(n: i64, ...) f64;\n" + "export fn main() int = {\n" + " let r: f64 = fixture(3, 1.5: f32, 2.5: f32, 3.0: f32);\n" + " if (r == 7.0) { return 0; };\n" + " return 1;\n" + "};\n", + 0 }, + + /* Sharper than f32three: 0.1 is INEXACT in f32, so the f32-rounded + * value differs from the f64 literal 0.1. The expected side `(x: f64)` + * is the SAME f32 var widened at runtime (CVTSS2SD), so equality holds + * iff the variadic arg carried the f32-rounded value promoted to f64 — + * NOT the original f64 literal and NOT stale-high-bit garbage. Both a + * no-promote (MOVSS) and a hypothetical direct-f64 pass would miss it. + * Single f32 arg also exercises the fi==1 boundary. */ + { "f32inexact", + "package main;\n" + "@symbol(\"fixture\") fn fixture(n: i64, ...) f64;\n" + "export fn main() int = {\n" + " let x: f32 = 0.1: f32;\n" + " let r: f64 = fixture(1, x);\n" + " if (r == (x: f64)) { return 0; };\n" + " return 1;\n" + "};\n", + 0 }, + + /* Mixed f32 + f64 in one variadic call: only the f32 args (1.5, 3.5) + * promote; the f64 arg (2.0) passes at its native width and must NOT be + * double-promoted or skewed. 1.5+2.0+3.5 == 7.0, exact in both widths. */ + { "f32mixed", + "package main;\n" + "@symbol(\"fixture\") fn fixture(n: i64, ...) f64;\n" + "export fn main() int = {\n" + " let r: f64 = fixture(3, 1.5: f32, 2.0, 3.5: f32);\n" + " if (r == 7.0) { return 0; };\n" + " return 1;\n" + "};\n", + 0 }, }; /* run_build — build+run `src` via cstage `driver`, linking libffifix.a from