Bug type: miscompilation
Component: ValueTracking
Bug location: llvm/lib/Analysis/ValueTracking.cpp: computeKnownFPClass
Issue title: [InstCombine] Wrong folding of is.fpclass + minnum
Labels: miscompilation
floating-point
llvm:analysis
generated by fuzzer
Reported by: dtcxzyw

Reproducer: https://alive2.llvm.org/ce/z/zTqnNp
```
; bin/opt -passes=instcombine test.ll -S
define i1 @src(i32 %x) {
entry:
  %or102.i.i = or i32 %x, -5938
  %0 = bitcast i32 %or102.i.i to float
  %1 = call float @llvm.minnum.f32(float %0, float 0.000000e+00)
  %tobool106.not2.i.i = call i1 @llvm.is.fpclass.f32(float %1, i32 64)
  ret i1 %tobool106.not2.i.i
}
```
```

----------------------------------------
define i1 @src(i32 %x) {
entry:
  %or102.i.i = or i32 %x, 4294961358
  %#0 = bitcast i32 %or102.i.i to float
  %#1 = fmin float %#0, 0.000000
  %tobool106.not2.i.i = is.fpclass float %#1, i32 64
  ret i1 %tobool106.not2.i.i
}
=>
define i1 @src(i32 %x) nofree noundef willreturn memory(none) {
entry:
  ret i1 0
}
Transformation doesn't verify!

ERROR: Value mismatch

Example:
i32 %x = #x00000000 (0)

Source:
i32 %or102.i.i = #xffffe8ce (4294961358, -5938)
float %#0 = #xffffe8ce (QNaN)
float %#1 = #x00000000 (+0.0)
i1 %tobool106.not2.i.i = #x1 (1)

Target:
Source value: #x1 (1)
Target value: #x0 (0)

Summary:
  0 correct transformations
  1 incorrect transformations
  0 failed-to-prove transformations
  0 Alive2 errors
```


Comments: author: dtcxzyw
body: I hit another case reported by riscv-tv: https://alive2.llvm.org/ce/z/DGiq7g
Perhaps this is an Alive2 bug.

author: dtcxzyw
body: > I hit another case reported by riscv-tv: https://alive2.llvm.org/ce/z/DGiq7g Perhaps this is an Alive2 bug.

They are different bugs.
author: nikic
body: > I hit another case reported by riscv-tv: https://alive2.llvm.org/ce/z/DGiq7g Perhaps this is an Alive2 bug.

I think this is because alive2 does not implement the "new" minnum semantics (see https://github.com/llvm/llvm-project/pull/138451). We should really update LangRef to specify nondet behavior for sNaN input.

Fix patch:
commit ea59be552f1db4f8f2e8a3edd909f17802339970
Author: Yingwei Zheng <dtcxzyw2333@gmail.com>
Date:   Tue Sep 16 20:59:42 2025 +0800

    [ValueTracking] Don't take sign bit from NaN operands (#157250)
    
    Closes https://github.com/llvm/llvm-project/issues/157238.

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 129823e0e98a..73192a75fa50 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -5070,6 +5070,11 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
                      KnownRHS.isKnownNeverPosZero()) &&
                     (KnownLHS.isKnownNeverPosZero() ||
                      KnownRHS.isKnownNeverNegZero()))) {
+          // Don't take sign bit from NaN operands.
+          if (!KnownLHS.isKnownNeverNaN())
+            KnownLHS.SignBit = std::nullopt;
+          if (!KnownRHS.isKnownNeverNaN())
+            KnownRHS.SignBit = std::nullopt;
           if ((IID == Intrinsic::maximum || IID == Intrinsic::maximumnum ||
                IID == Intrinsic::maxnum) &&
               (KnownLHS.SignBit == false || KnownRHS.SignBit == false))


Regression tests:
file: llvm/test/Transforms/InstCombine/is_fpclass.ll
commands: opt -S -passes=instcombine < %s
tests: test_name: minnum_qnan
test_body: define i1 @minnum_qnan(i32 %x) {
entry:
  %qnan_bits = or i32 %x, -5938
  %qnan = bitcast i32 %qnan_bits to float
  %min = call float @llvm.minnum.f32(float %qnan, float 0.000000e+00)
  %test = call i1 @llvm.is.fpclass.f32(float %min, i32 64)
  ret i1 %test
}

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare i1 @llvm.is.fpclass.f32(float, i32 immarg) #0

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare float @llvm.minnum.f32(float, float) #0

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }

test_name: test_class_is_not_psub_pnorm_pinf__dynamic
test_body: define i1 @test_class_is_not_psub_pnorm_pinf__dynamic(float %arg) #0 {
  %class = call i1 @llvm.is.fpclass.f32(float %arg, i32 127)
  ret i1 %class
}

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare i1 @llvm.is.fpclass.f32(float, i32 immarg) #1

attributes #0 = { "denormal-fp-math"="dynamic,dynamic" }
attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }

test_name: minnum_qnan_commuted
test_body: define i1 @minnum_qnan_commuted(i32 %x, float nofpclass(nan ninf nzero nsub nnorm) %y) {
entry:
  %qnan_bits = or i32 %x, -5938
  %qnan = bitcast i32 %qnan_bits to float
  %min = call float @llvm.minnum.f32(float %y, float %qnan)
  %test = call i1 @llvm.is.fpclass.f32(float %min, i32 64)
  ret i1 %test
}

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare i1 @llvm.is.fpclass.f32(float, i32 immarg) #0

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare float @llvm.minnum.f32(float, float) #0

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }