clang -O2 for Hexagon lowers the IEEE-strict "pseudo-minimum" pattern
float pmin(float a, float b) { return (b < a) ? b : a; }
to sfmin(r1,r0), even though the LLVM IR is a plain fcmp olt + select with no fast-math flags. The C expression is fully defined for NaN (b < a is false when either operand is NaN, so the result must be a, i.e. pmin(NaN, x) == NaN), but sfmin/sfmax implement minNum-style NaN handling and return the non-NaN operand. The transform is only legal under nnan.
WASM spec suites simd_f32x4_pmin_pmax, simd_f64x2_pmin_pmax (and NaN-lane cases in simd_lane / simd_splat) fail on Hexagon: WAMR's fast interpreter executes f32x4.pmin etc. through SIMDe, whose portable implementation is exactly the pattern above. the Wasm spec mandates the (b < a) ? b : a semantics, so the miscompiled sfmin returns the wrong lane whenever the first operand is NaN.
/*
* Minimal reproducer: clang miscompiles the IEEE-strict float-min pattern
* on Hexagon by selecting sfmin/sfmax for it.
*
* `pmin` is the Wasm SIMD "pseudo-minimum" lane operation exactly as the
* spec defines it and exactly as SIMDe implements it (WAMR's fast
* interpreter uses SIMDe for v128 ops):
*
* pmin(a, b) = (b < a) ? b : a
*
* The C semantics are fully defined for NaN: `b < a` is false when either
* operand is NaN, so the result must be `a` — i.e. pmin(NaN, x) == NaN.
*
* At -O2/-O3 the Hexagon backend lowers the `fcmp olt` + `select` pair to
* `sfmin` even though the IR carries no fast-math flags. sfmin implements
* minNum-style NaN handling (returns the non-NaN operand), so
* pmin(NaN, x) comes back as x. The same happens for pmax and for the
* vectorized form (four independent sfmin after unrolling), which is how
* WAMR's f32x4.pmin/pmax and f64x2.pmin/pmax spec tests fail on Hexagon.
*
* `pmin_ref` computes the same expression with the comparison made opaque
* so the pattern cannot be matched; it is the ground truth.
*
* The scalar direction that *appears* to work (pmin(a=NaN passed via r0)
* in some operand orders) is luck: sfmin's NaN behavior is not the
* select's, and which operand survives depends on register assignment.
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
__attribute__((noinline)) static float
pmin(float a, float b)
{
return (b < a) ? b : a;
}
__attribute__((noinline)) static float
pmax(float a, float b)
{
return (a < b) ? b : a;
}
__attribute__((noinline)) static float
pmin_ref(float a, float b)
{
volatile int lt = (b < a);
return lt ? b : a;
}
__attribute__((noinline)) static float
pmax_ref(float a, float b)
{
volatile int lt = (a < b);
return lt ? b : a;
}
static uint32_t
bits(float f)
{
uint32_t u;
memcpy(&u, &f, 4);
return u;
}
int
main(void)
{
volatile uint32_t nan_bits = 0x7fc00000; /* quiet NaN */
volatile uint32_t one_bits = 0x3f800000; /* 1.0f */
float nanf, one;
memcpy(&nanf, (void *)&nan_bits, 4);
memcpy(&one, (void *)&one_bits, 4);
struct {
const char *name;
float got, want;
} cases[] = {
{ "pmin(nan, 1.0)", pmin(nanf, one), pmin_ref(nanf, one) },
{ "pmin(1.0, nan)", pmin(one, nanf), pmin_ref(one, nanf) },
{ "pmax(nan, 1.0)", pmax(nanf, one), pmax_ref(nanf, one) },
{ "pmax(1.0, nan)", pmax(one, nanf), pmax_ref(one, nanf) },
};
int fails = 0;
for (unsigned i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
uint32_t got = bits(cases[i].got), want = bits(cases[i].want);
printf("%s got=0x%08x want=0x%08x %s\n", cases[i].name, got, want,
got == want ? "ok" : "<<< MISCOMPILE");
if (got != want)
fails++;
}
printf("\n%d failure(s)\n", fails);
return fails ? 1 : 0;
}
clang -O2for Hexagon lowers the IEEE-strict "pseudo-minimum" patternto
sfmin(r1,r0), even though the LLVM IR is a plainfcmp olt+selectwith no fast-math flags. The C expression is fully defined for NaN (b < ais false when either operand is NaN, so the result must bea, i.e.pmin(NaN, x) == NaN), butsfmin/sfmaximplement minNum-style NaN handling and return the non-NaN operand. The transform is only legal undernnan.WASM spec suites
simd_f32x4_pmin_pmax,simd_f64x2_pmin_pmax(and NaN-lane cases insimd_lane/simd_splat) fail on Hexagon: WAMR's fast interpreter executesf32x4.pminetc. through SIMDe, whose portable implementation is exactly the pattern above. the Wasm spec mandates the(b < a) ? b : asemantics, so the miscompiledsfminreturns the wrong lane whenever the first operand is NaN.