| Issue |
185183
|
| Summary |
[InstCombine] Fold of `minimumnum(x, fneg x)` to `fneg(fabs(x))` is incorrect for signaling NaNs
|
| Labels |
new issue
|
| Assignees |
|
| Reporter |
cardigan1008
|
Here is a miscompilation case when reviewing https://github.com/llvm/llvm-project/pull/180529:
```llvm
define i32 @test_minimumnum_x_negx(float %x) {
%negx = fneg float %x
%min = call float @llvm.minimumnum.f32(float %x, float %negx)
%r = bitcast float %min to i32
%res = lshr i32 %r, 24
ret i32 %res
}
declare float @llvm.minimumnum.f32(float, float)
define i32 @main(i32 %argc, ptr %argv) {
entry:
%r = call i32 @test_minimumnum_x_negx(float bitcast (i32 2139095041 to float))
ret i32 %r
}
```
With opt built on this patch, it's transformed into:
```llvm
define i32 @test_minimumnum_x_negx(float %x) {
%1 = call float @llvm.fabs.f32(float %x)
%min = fneg float %1
%r = bitcast float %min to i32
%res = lshr i32 %r, 24
ret i32 %res
}
; Function Attrs: nocallback nocreateundeforpoison nofree nosync nounwind speculatable willreturn memory(none)
declare float @llvm.minimumnum.f32(float, float) #0
; Function Attrs: nocallback nocreateundeforpoison nofree nosync nounwind speculatable willreturn memory(none)
declare float @llvm.fabs.f32(float) #0
attributes #0 = { nocallback nocreateundeforpoison nofree nosync nounwind speculatable willreturn memory(none) }
define i32 @main(i32 %argc, ptr %argv) {
entry:
%r = call i32 @test_minimumnum_x_negx(float bitcast (i32 2139095041 to float))
ret i32 %
```
Ran lli on them, we got different results:
```sh
$ lli src.ll
$ echo$?
127
$ lli tgt.ll
$ echo$?
255
```
> Note: This is a review assisted with a self-built agent. The reproducer was validated manually. Please let me know if anything is wrong.
**Bug Triggering Analysis:**
The provided test case triggers the bug by passing a signaling NaN (sNaN) to `minimumnum(X, -X)`.
In the original code, `minimumnum(sNaN, -sNaN)` is evaluated. According to the semantics of `minimumnum`, it quiets signaling NaNs. So the result is a quiet NaN (qNaN).
In the optimized code, the _expression_ is folded to `-(fabs X)`. `fabs` and `fneg` do not quiet signaling NaNs; they only manipulate the sign bit. So the result is a signaling NaN with its sign bit flipped.
This causes a semantic difference: the original code returns a quiet NaN, while the optimized code returns a signaling NaN. This can lead to unexpected traps or undefined behavior in subsequent operations that expect a quiet NaN.
**Fix Weakness Analysis:**
The fix added `minimumnum` and `maximumnum` to the `IsMinMaxOrXNegX` fold, which transforms `min(X, -X)` to `-(fabs X)` and `max(X, -X)` to `fabs X`.
However, the fix failed to account for the fact that `minimumnum` and `maximumnum` quiet signaling NaNs, whereas `fabs` and `fneg` do not.
By unconditionally applying this fold to `minimumnum` and `maximumnum`, the optimization pass incorrectly preserves signaling NaNs, violating the semantics of the intrinsics.
The test case reveals this weakness by explicitly checking the result of `minimumnum(X, -X)` when `X` is a signaling NaN, demonstrating the semantic mismatch.
Reference: http://llvm.org/docs/LangRef.html#id669
cc @nikic
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs