Bug type: miscompilation
Bug location: llvm/include/llvm/IR/GenericFloatingPointPredicateUtils.h: fcmpImpliesClass
Issue title: [llvm] Incorrect simplification of fabs intrinsic
Labels: miscompilation
floating-point
llvm:ir
Reported by: ojhunt

The below test case fails at optimization levels greater than -O1
```cpp
#include <math.h>
#include <stdio.h>

__attribute__((noinline))
int test(double a, double b)
{
  int maxIdx = 0;
  double maxDot = 0.0;
  for (int i = 0; i < 2; ++i) {
    double dot = a * b;
    if (fabs(dot) > fabs(maxDot)) {
      maxIdx = i;
      maxDot = dot;
    }
  }
  return maxIdx;
}

int main(int argc, char *argv[])
{

  int i = test(1.0, -1.0);
  int failed = (i != 0);

  printf("Returned %d, should be 0: %s\n", i, (failed ? "failed" : "success"));

  return failed;
}
```

This is a regression introduced in #76360

~The bug is due to https://github.com/llvm/llvm-project/pull/76360/files#diff-532fd830a7b080a7121645e6c8dccb2f94a7e25450c74aa496b8600f8f4481ccR6220~

~comparing a std::optional<bool> to false rather than dereferencing it (maybe we need a way to warn about this? it's a super easy error)~

[Implicit conversion means the comparison I thought was wrong, was correct, and my PoC fix worked by functionally disabling signedness propagation entirely]

Comments: author: nikic
body: Seems to go wrong during unroling: https://alive2.llvm.org/ce/z/sxJ5eo
author: nikic
body: Or rather in simplification during unrolling (https://alive2.llvm.org/ce/z/66EoES):
```llvm
define half @src(half noundef %mul) {
entry:
  %abs = call half @llvm.fabs.f16(half %mul)
  %cmp1 = fcmp ogt half %abs, 0.000000e+00
  %maxDot.1 = select i1 %cmp1, half %mul, half 0.000000e+00
  %abs2 = call half @llvm.fabs.f16(half %maxDot.1)
  ret half %abs2
}

define half @tgt(half noundef %mul) {
entry:
  %abs = call half @llvm.fabs.f16(half %mul)
  %cmp1 = fcmp ogt half %abs, 0xH0000
  %maxDot.1 = select i1 %cmp1, half %mul, half 0xH0000
  ret half %maxDot.1
}
```

The second abs should not be eliminated.
author: nikic
body: Presumably something is wrong in https://github.com/llvm/llvm-project/blob/fc44a4fcd3c54be927c15ddd9211aca1501633e7/llvm/lib/Analysis/ValueTracking.cpp#L4852-L4860 or the fcmpImpliesClass utility it calls.
author: dtcxzyw
body: @nikic @ojhunt Are you working on this issue?

author: nikic
body: I'm not working on it.
author: ojhunt
body: > [@nikic](https://github.com/nikic) [@ojhunt](https://github.com/ojhunt) Are you working on this issue?

I am not - I don't know anything like enough about the IR pipelines :D
author: dtcxzyw
body: Okay. I will post a fix tomorrow.
author: ojhunt
body: > Okay. I will post a fix tomorrow.

cool, I'd love to see how this kind of issue comes about in IR land (obviously everything looks quite different from in clang)

Fix patch:
commit d8b1b46cd39c91830bcf49ed91d80f38f78c2168
Author: Yingwei Zheng <dtcxzyw2333@gmail.com>
Date:   Sun Aug 10 22:55:04 2025 +0800

    [IR] Handle fabs LHS in `fcmpImpliesClass` (#152913)
    
    Closes https://github.com/llvm/llvm-project/issues/152824.

diff --git a/llvm/include/llvm/IR/GenericFloatingPointPredicateUtils.h b/llvm/include/llvm/IR/GenericFloatingPointPredicateUtils.h
index 8aac9d5b49db..448a6e913eb8 100644
--- a/llvm/include/llvm/IR/GenericFloatingPointPredicateUtils.h
+++ b/llvm/include/llvm/IR/GenericFloatingPointPredicateUtils.h
@@ -135,6 +135,12 @@ public:
       if (Mode.Input != DenormalMode::IEEE)
         return {Invalid, fcAllFlags, fcAllFlags};
 
+      auto ExactClass = [IsFabs, Src](FPClassTest Mask) {
+        if (IsFabs)
+          Mask = llvm::inverse_fabs(Mask);
+        return exactClass(Src, Mask);
+      };
+
       switch (Pred) {
       case FCmpInst::FCMP_OEQ: // Match x == 0.0
         return exactClass(Src, fcZero);
@@ -151,26 +157,24 @@ public:
       case FCmpInst::FCMP_UNO:
         return exactClass(Src, fcNan);
       case FCmpInst::FCMP_OGT: // x > 0
-        return exactClass(Src, fcPosSubnormal | fcPosNormal | fcPosInf);
+        return ExactClass(fcPosSubnormal | fcPosNormal | fcPosInf);
       case FCmpInst::FCMP_UGT: // isnan(x) || x > 0
-        return exactClass(Src, fcPosSubnormal | fcPosNormal | fcPosInf | fcNan);
+        return ExactClass(fcPosSubnormal | fcPosNormal | fcPosInf | fcNan);
       case FCmpInst::FCMP_OGE: // x >= 0
-        return exactClass(Src, fcPositive | fcNegZero);
+        return ExactClass(fcPositive | fcNegZero);
       case FCmpInst::FCMP_UGE: // isnan(x) || x >= 0
-        return exactClass(Src, fcPositive | fcNegZero | fcNan);
+        return ExactClass(fcPositive | fcNegZero | fcNan);
       case FCmpInst::FCMP_OLT: // x < 0
-        return exactClass(Src, fcNegSubnormal | fcNegNormal | fcNegInf);
+        return ExactClass(fcNegSubnormal | fcNegNormal | fcNegInf);
       case FCmpInst::FCMP_ULT: // isnan(x) || x < 0
-        return exactClass(Src, fcNegSubnormal | fcNegNormal | fcNegInf | fcNan);
+        return ExactClass(fcNegSubnormal | fcNegNormal | fcNegInf | fcNan);
       case FCmpInst::FCMP_OLE: // x <= 0
-        return exactClass(Src, fcNegative | fcPosZero);
+        return ExactClass(fcNegative | fcPosZero);
       case FCmpInst::FCMP_ULE: // isnan(x) || x <= 0
-        return exactClass(Src, fcNegative | fcPosZero | fcNan);
+        return ExactClass(fcNegative | fcPosZero | fcNan);
       default:
         llvm_unreachable("all compare types are handled");
       }
-
-      return {Invalid, fcAllFlags, fcAllFlags};
     }
 
     const bool IsDenormalRHS = (OrigClass & fcSubnormal) == OrigClass;


Regression tests:
file: llvm/test/Transforms/InstSimplify/floating-point-arithmetic.ll
commands: opt < %s -passes=instsimplify -S
tests: test_name: fabs_select_fabs
test_body: define half @fabs_select_fabs(half noundef %x) {
entry:
  %abs1 = call half @llvm.fabs.f16(half %x)
  %cmp = fcmp ogt half %abs1, 0xH0000
  %sel = select i1 %cmp, half %x, half 0xH0000
  %abs2 = call half @llvm.fabs.f16(half %sel)
  ret half %abs2
}

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare half @llvm.fabs.f16(half) #0

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