[AArch64] Negated FMA has wrong signed zero
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
`fma(0.0, 0.0, -0.0)` is 0.0.
`-fma(0.0, 0.0, -0.0)` should be -0.0, but is 0.0 according to Clang/LLVM.
ARM and x86-64 both have FMA-adjacent instructions that are defined to effectively negate their inputs, but not their outputs. x86-64 code generation is correct, selecting the instruction based on negated inputs, then negating the output separately if needed. ARM code generation behaves really weirdly, never negating the output separately but uselessly negating inputs separately.
I made a [test case](https://godbolt.org/z/facM7Tvh4) on Compiler Explorer. One of the functions along with generated code and correct code:
```c
double not_fmsub(double x, double y, double z)
{
return -fma(x, y, -z);
}
```
```asm
not_fmsub:
fneg d2, d2
fnmadd d0, d0, d1, d2
ret
correct:
fnmsub d0, d0, d1, d2
fneg d0, d0
ret
expected_with_fast_math:
fmsub d0, d0, d1, d2
ret
```
Only `not_fnmadd` takes the literal bait and generates the incorrect `fnmadd`. The others aren’t even faster, `not_fmadd` adding _two_ `fneg`s!
The only thing that seems to convince Clang/LLVM is to store fma’s result in a `volatile` variable before negating it, as when uncommenting `//#define fma fmav`. Even `-O0` is incorrect. Interestingly, going the other way, `-ffast-math` doesn’t optimize much either. It simply doesn’t seem to know what it’s doing, but it can’t resist touching it.
Contributor guide
Assessment
This issue has not been assessed yet.