[FMF] Missed optimization: Ordered FP clamp only lowers to fmaxnm/fminnm when no-NaN is explicit
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
AArch64 can lower an ordered floating-point clamp idiom to `fmaxnm` / `fminnm` when the clamp input has an explicit no-NaN fact, but the same source-shaped clamp remains `fcmp` / `fcsel` when that fact is not exposed.
Illustrative examples:
```llvm ir
define i1 @llvm_knows_no_nan_from_sitofp_fdiv(i32 %rate) {
entry:
%x = sitofp i32 %rate to float
%d = fdiv float %x, 1.000000e+01
%isnan = call i1 @llvm.is.fpclass.f32(float %d, i32 3) ; qnan|snan
ret i1 %isnan
}
```
Observed results after `opt -O3`:
```llvm ir
define noundef i1 @llvm_knows_no_nan_from_sitofp_fdiv(i32 %rate) {
entry:
ret i1 false
}
```
Above case is correctly folded into a constant return, showing LLVM knows the `%d` is `nnan`. However, the below clamp case, when lowering to AArch64 assembly by `-O3 -mtriple=aarch64-linux-gnu`, is not folded to `fmaxnm` / `fminnm`, though it is essentially still ordered (https://godbolt.org/z/vY5M4fbET):
```llvm ir
define float @missed_clamp_from_sitofp_fdiv(i32 %rate) {
entry:
%x = sitofp i32 %rate to float
%d = fdiv float %x, 1.000000e+01
%gt0 = fcmp ogt float %d, 0.000000e+00
%max = select i1 %gt0, float %d, float 0.000000e+00
%lt100 = fcmp olt float %max, 1.000000e+02
%min = select i1 %lt100, float %max, float 1.000000e+02
ret float %min
}
```
```assembly
// without explict nnan
missed_clamp_from_sitofp_fdiv: // @missed_clamp_from_sitofp_fdiv
scvtf s0, w0
fmov s1, #10.00000000
mov w8, #1120403456 // =0x42c80000
fdiv s0, s0, s1
movi d1, #0000000000000000
fcmp s0, #0.0
fcsel s0, s0, s1, gt
fmov s1, w8
fcmp s0, s1
fcsel s0, s0, s1, mi
ret
```
After explicitly adding `nnan`:
```llvm ir
define float @clamp_from_sitofp_fdiv_with_nnan(i32 %rate) {
entry:
%x = sitofp i32 %rate to float
%d = fdiv nnan float %x, 1.000000e+01
%gt0 = fcmp ogt float %d, 0.000000e+00
%max = select i1 %gt0, float %d, float 0.000000e+00
%lt100 = fcmp olt float %max, 1.000000e+02
%min = select i1 %lt100, float %max, float 1.000000e+02
ret float %min
}
```
The assembly correctly folds:
```assembly
clamp_from_sitofp_fdiv_with_nnan: // @clamp_from_sitofp_fdiv_with_nnan
scvtf s0, w0
fmov s1, #10.00000000
mov w8, #1120403456 // =0x42c80000
fdiv s0, s0, s1
movi d1, #0000000000000000
fmaxnm s0, s0, s1
fmov s1, w8
fminnm s0, s0, s1
ret
```
While X86 backends handles both case (with/without explicit nnan) flawlessly:
```assembly
missed_clamp_from_sitofp_fdiv: # @missed_clamp_from_sitofp_fdiv
cvtsi2ss xmm0, edi
divss xmm0, dword ptr [rip + .LCPI0_0]
xorps xmm1, xmm1
maxss xmm0, xmm1
minss xmm0, dword ptr [rip + .LCPI0_1]
ret
```
This pattern is seen from function `UCTSearch::dump_analysis` in SPEC CPU 2017 `541.leela_r `/ `641.leela_s` . It has a pattern like:
```c++
int rate = ...;
float winrate = rate / 10.0f;
winrate = std::max(0.0f, winrate);
winrate = std::min(100.0f, winrate);
```
And AArch64 backend will exactly produce the above sub-optimal assembly on that SPEC case.
Contributor guide
Assessment
This issue has not been assessed yet.