for int foo(float x) { return __builtin_isnan(x) ? 0 : 1; } int bar(float x) { return x!=x ? 0 : 1; } i get foo: vcmpe.f32 s0, s0 vmrs APSR_nzcv, fpscr mov r0, #0 movvc r0, #1 bx lr bar: vcmpe.f32 s0, s0 vmrs APSR_nzcv, fpscr mov r0, #0 movvc r0, #1 bx lr in standard conform compilation mode, i expected vcmp.f32 instead of vcmpe.f32. in principle the difference is not observable if FENV_ACCESS is off, but since clang does not support toggling that switch it must be assumed to be always on for conforming behaviour and then != and is* operations must be non-signaling (iso c annex f requirement).
Adding Kristof and Peter, since this seems ARM-specific. https://github.com/ARM-software/optimized-routines/issues/16 is where this was reported (and failing tests).
The C99 standard states in section 7.12.14: """ The relational and equality operators support the usual mathematical relationships between numeric values. For any ordered pair of numeric values exactly one of the relationships — less, greater, and equal — is true. Relational operators may raise the ‘‘invalid’’ floating-point exception when argument values are NaNs. """ My interpretation of that paragraph is that it's OK for <, <=, > and >= to raise an exception when argument values are NaNs. It is not OK for == an != to raise an exception when argument values are NaNs. Therefore, int bar(float x) { return x!=x ? 0 : 1; } should not produce an exception when x is NaN, and hence vcmp rather than vcmpe should be produced when generating Arm code for this. Nonetheless, http://llvm.org/viewvc/llvm-project?rev=294945&view=rev introduced support for generating vcmp instead of vcmpe for equality comparisons. How come vcmpe is generated (x!=x)? The answer is that InstCombine transform the equality comparison into an "ordered comparison": *** IR Dump Before Combine redundant instructions *** define dso_local i32 @foo(float %x) local_unnamed_addr { entry: %cmp = fcmp une float %x, %x %cond = select i1 %cmp, i32 0, i32 1 ret i32 %cond } INSTCOMBINE ITERATION #1 on foo IC: ADDING: 3 instrs to worklist IC: Visiting: %cmp = fcmp une float %x, %x IC: Mod = %cmp = fcmp une float %x, %x New = %cmp = fcmp uno float %x, 0.000000e+00 IC: ADD: %cmp = fcmp uno float %x, 0.000000e+00 IC: Visiting: %cmp = fcmp uno float %x, 0.000000e+00 IC: Visiting: %cond = select i1 %cmp, i32 0, i32 1 IC: ADD: %not.cmp = xor i1 %cmp, true IC: Old = %cond = select i1 %cmp, i32 0, i32 1 New = <badref> = zext i1 %not.cmp to i32 IC: ADD: %cond = zext i1 %not.cmp to i32 IC: ERASE %0 = select i1 %cmp, i32 0, i32 1 IC: ADD: %cmp = fcmp uno float %x, 0.000000e+00 IC: Visiting: %cmp = fcmp uno float %x, 0.000000e+00 IC: Visiting: %cond = zext i1 %not.cmp to i32 IC: Visiting: %not.cmp = xor i1 %cmp, true IC: ADD: %cond = zext i1 %not.cmp to i32 IC: Replacing %not.cmp = xor i1 %cmp, true with %cmp = fcmp ord float %x, 0.000000e+00 IC: Mod = %not.cmp = xor i1 %cmp, true New = %not.cmp = xor i1 %cmp, true IC: ERASE %not.cmp = xor i1 %cmp, true IC: ADD: %cmp = fcmp ord float %x, 0.000000e+00 IC: Visiting: %cmp = fcmp ord float %x, 0.000000e+00 IC: Visiting: %cond = zext i1 %cmp to i32 IC: Visiting: ret i32 %cond ; Function Attrs: norecurse nounwind readnone define dso_local i32 @foo(float %x) local_unnamed_addr #0 { entry: %cmp = fcmp ord float %x, 0.000000e+00 %cond = zext i1 %cmp to i32 ret i32 %cond } Seemingly, instcombine does a transform that illegally transforms a "fcmp une" into an "fcmp ord". How come this issue isn't seen on x86_64 or AArch64 backends? Those backends seem to simply not produce code that generates floating point exceptions for any floating point relationships when argument values are NaNs. In summary, so far, it looks to me like this is a bug in InstCombine.
Should be fixed in r374025, by never generating vcmpe, but always vcmp for floating point comparisons. This makes the ARM backend behave similar to the other backends with respect to signalling quiet NaNs in compare operations. Also see LLVMdev thread http://lists.llvm.org/pipermail/llvm-dev/2019-October/135574.html for more background.