[llvm] [ValueTracking] Propagate non-negativity through fptosi (PR #217868)

Aayush Shrivastava via llvm-commits llvm-commits at lists.llvm.org
Sat Aug 22 07:27:12 PDT 2026


https://github.com/iamaayushrivastava updated https://github.com/llvm/llvm-project/pull/217868

>From bd88110e2867d8d38701308f1caeeeb0be29526b Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Fri, 21 Aug 2026 14:44:12 +0530
Subject: [PATCH 1/3] [ValueTracking] Propagate non-negativity through fptosi

---
 llvm/lib/Analysis/ValueTracking.cpp           | 14 ++++++++-
 llvm/test/Transforms/InstCombine/fabs.ll      | 31 +++++++++++++++++++
 llvm/unittests/Analysis/ValueTrackingTest.cpp | 21 +++++++++++++
 3 files changed, 65 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 3fd1cb8a0a4d4..233976ed0f0ac 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -1476,10 +1476,22 @@ static void computeKnownBitsFromOperator(const Operator *I,
             .intersectWith(ComputeForArm(I->getOperand(2), /*Invert=*/true));
     break;
   }
+  case Instruction::FPToSI: {
+    // fptosi is poison if the rounded value doesn't fit in the result type,
+    // so we can assume the conversion is well-defined and rounds towards
+    // zero. Negative subnormals and negative zero round to 0, so only
+    // negative normals and negative infinity (i.e. values <= -1.0) can
+    // produce a negative result.
+    FPClassTest NegativeResultClasses = fcNegNormal | fcNegInf;
+    KnownFPClass SrcFPClass = computeKnownFPClass(
+        I->getOperand(0), DemandedElts, NegativeResultClasses, Q, Depth + 1);
+    if (SrcFPClass.isKnownNever(NegativeResultClasses))
+      Known.makeNonNegative();
+    break;
+  }
   case Instruction::FPTrunc:
   case Instruction::FPExt:
   case Instruction::FPToUI:
-  case Instruction::FPToSI:
   case Instruction::SIToFP:
   case Instruction::UIToFP:
     break; // Can't work with floating point.
diff --git a/llvm/test/Transforms/InstCombine/fabs.ll b/llvm/test/Transforms/InstCombine/fabs.ll
index 86456b8b4c02d..7c9e1d5b2a82a 100644
--- a/llvm/test/Transforms/InstCombine/fabs.ll
+++ b/llvm/test/Transforms/InstCombine/fabs.ll
@@ -1825,3 +1825,34 @@ define i1 @test_fabs_used_is_fpclass_pzero(float %x) {
   %is_fpclass = call i1 @llvm.is.fpclass.f32(float %sel, i32 64)
   ret i1 %is_fpclass
 }
+
+define i1 @fptosi_fabs_is_never_negative(float %x) {
+; CHECK-LABEL: @fptosi_fabs_is_never_negative(
+; CHECK-NEXT:    ret i1 false
+;
+  %fabs = call float @llvm.fabs.f32(float %x)
+  %fptosi = fptosi float %fabs to i32
+  %cmp = icmp slt i32 %fptosi, 0
+  ret i1 %cmp
+}
+
+define <2 x i1> @fptosi_fabs_is_never_negative_vec(<2 x float> %x) {
+; CHECK-LABEL: @fptosi_fabs_is_never_negative_vec(
+; CHECK-NEXT:    ret <2 x i1> zeroinitializer
+;
+  %fabs = call <2 x float> @llvm.fabs.v2f32(<2 x float> %x)
+  %fptosi = fptosi <2 x float> %fabs to <2 x i32>
+  %cmp = icmp slt <2 x i32> %fptosi, zeroinitializer
+  ret <2 x i1> %cmp
+}
+
+define i1 @fptosi_no_fabs_unknown_sign(float %x) {
+; CHECK-LABEL: @fptosi_no_fabs_unknown_sign(
+; CHECK-NEXT:    [[FPTOSI:%.*]] = fptosi float [[X:%.*]] to i32
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[FPTOSI]], 0
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fptosi = fptosi float %x to i32
+  %cmp = icmp slt i32 %fptosi, 0
+  ret i1 %cmp
+}
diff --git a/llvm/unittests/Analysis/ValueTrackingTest.cpp b/llvm/unittests/Analysis/ValueTrackingTest.cpp
index 382cf0f2b57ed..6dd46b71aee2d 100644
--- a/llvm/unittests/Analysis/ValueTrackingTest.cpp
+++ b/llvm/unittests/Analysis/ValueTrackingTest.cpp
@@ -3110,6 +3110,27 @@ TEST_F(ComputeKnownBitsTest, ComputeKnownBitsGEPOnlyIndexBits) {
   EXPECT_EQ(0, Known.One);
 }
 
+TEST_F(ComputeKnownBitsTest, ComputeKnownBitsFPToSIFabs) {
+  // fptosi(fabs(x)) is never negative.
+  parseAssembly("define i32 @test(float %a) {\n"
+                "  %fabs = call float @llvm.fabs.f32(float %a)\n"
+                "  %A = fptosi float %fabs to i32\n"
+                "  ret i32 %A\n"
+                "}\n"
+                "declare float @llvm.fabs.f32(float)\n");
+  expectKnownBits(/*Zero*/ 0x80000000u, /*One*/ 0u);
+}
+
+TEST_F(ComputeKnownBitsTest, ComputeKnownBitsFPToSIUnknownSign) {
+  // Without any knowledge of the sign of the source, nothing is known about
+  // the sign of the result.
+  parseAssembly("define i32 @test(float %a) {\n"
+                "  %A = fptosi float %a to i32\n"
+                "  ret i32 %A\n"
+                "}\n");
+  expectKnownBits(/*Zero*/ 0u, /*One*/ 0u);
+}
+
 TEST_F(ValueTrackingTest, HaveNoCommonBitsSet) {
   {
     // Check for an inverted mask: (X & ~M) op (Y & M).

>From d967bfc5b15bf78b2ac2c10161d4c3f80b47e3b2 Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Sat, 22 Aug 2026 03:00:20 +0530
Subject: [PATCH 2/3] [ValueTracking] Propagate non-negativity through fptosi

---
 llvm/lib/Analysis/ValueTracking.cpp           | 12 +++----
 .../AMDGPU/amdgpu-simplify-libcall-pow.ll     |  6 ++--
 llvm/test/Transforms/InstCombine/fabs.ll      | 34 +++++++++++++++++++
 3 files changed, 43 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 233976ed0f0ac..c0d67b88cdc95 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -1479,13 +1479,13 @@ static void computeKnownBitsFromOperator(const Operator *I,
   case Instruction::FPToSI: {
     // fptosi is poison if the rounded value doesn't fit in the result type,
     // so we can assume the conversion is well-defined and rounds towards
-    // zero. Negative subnormals and negative zero round to 0, so only
-    // negative normals and negative infinity (i.e. values <= -1.0) can
-    // produce a negative result.
-    FPClassTest NegativeResultClasses = fcNegNormal | fcNegInf;
+    // zero. +-Inf can never fit in an integer type, so it is always poison,
+    // like NaN. Negative subnormals and negative zero round to 0. That
+    // leaves negative normals as the only class that can produce a defined
+    // negative result.
     KnownFPClass SrcFPClass = computeKnownFPClass(
-        I->getOperand(0), DemandedElts, NegativeResultClasses, Q, Depth + 1);
-    if (SrcFPClass.isKnownNever(NegativeResultClasses))
+        I->getOperand(0), DemandedElts, fcNegNormal, Q, Depth + 1);
+    if (SrcFPClass.isKnownNever(fcNegNormal))
       Known.makeNonNegative();
     break;
   }
diff --git a/llvm/test/CodeGen/AMDGPU/amdgpu-simplify-libcall-pow.ll b/llvm/test/CodeGen/AMDGPU/amdgpu-simplify-libcall-pow.ll
index 11745f83fe119..fe3dd0ae141b5 100644
--- a/llvm/test/CodeGen/AMDGPU/amdgpu-simplify-libcall-pow.ll
+++ b/llvm/test/CodeGen/AMDGPU/amdgpu-simplify-libcall-pow.ll
@@ -5259,7 +5259,7 @@ define float @test_pow_afn_nnan_ninf_f32_known_integral_uitofp(float %x, i32 %y)
 ; PRELINK-NEXT:    [[TMP1:%.*]] = fptosi float [[Y_CAST]] to i32
 ; PRELINK-NEXT:    [[__FABS:%.*]] = call nnan ninf afn float @llvm.fabs.f32(float [[X]])
 ; PRELINK-NEXT:    [[__LOG2:%.*]] = call nnan ninf afn float @llvm.log2.f32(float [[__FABS]])
-; PRELINK-NEXT:    [[POWNI2F:%.*]] = sitofp nnan ninf afn i32 [[TMP1]] to float
+; PRELINK-NEXT:    [[POWNI2F:%.*]] = uitofp nneg i32 [[TMP1]] to float
 ; PRELINK-NEXT:    [[__YLOGX:%.*]] = fmul nnan ninf afn float [[__LOG2]], [[POWNI2F]]
 ; PRELINK-NEXT:    [[__EXP2:%.*]] = call nnan ninf afn nofpclass(nan ninf nzero nsub nnorm) float @llvm.exp2.f32(float [[__YLOGX]])
 ; PRELINK-NEXT:    [[__YEVEN:%.*]] = shl i32 [[TMP1]], 31
@@ -5410,7 +5410,7 @@ define float @test_pow_afn_nnan_ninf_f32_known_integral_uitofp_i256(float %x, i2
 ; PRELINK-NEXT:    [[TMP1:%.*]] = fptosi float [[Y_CAST]] to i32
 ; PRELINK-NEXT:    [[__FABS:%.*]] = call nnan ninf afn float @llvm.fabs.f32(float [[X]])
 ; PRELINK-NEXT:    [[__LOG2:%.*]] = call nnan ninf afn float @llvm.log2.f32(float [[__FABS]])
-; PRELINK-NEXT:    [[POWNI2F:%.*]] = sitofp nnan ninf afn i32 [[TMP1]] to float
+; PRELINK-NEXT:    [[POWNI2F:%.*]] = uitofp nneg i32 [[TMP1]] to float
 ; PRELINK-NEXT:    [[__YLOGX:%.*]] = fmul nnan ninf afn float [[__LOG2]], [[POWNI2F]]
 ; PRELINK-NEXT:    [[__EXP2:%.*]] = call nnan ninf afn nofpclass(nan ninf nzero nsub nnorm) float @llvm.exp2.f32(float [[__YLOGX]])
 ; PRELINK-NEXT:    [[__YEVEN:%.*]] = shl i32 [[TMP1]], 31
@@ -5589,7 +5589,7 @@ define <2 x float> @test_pow_afn_nnan_ninf_v2f32_known_integral_uitofp(<2 x floa
 ; PRELINK-NEXT:    [[TMP1:%.*]] = fptosi <2 x float> [[Y_CAST]] to <2 x i32>
 ; PRELINK-NEXT:    [[__FABS:%.*]] = call nnan ninf afn <2 x float> @llvm.fabs.v2f32(<2 x float> [[X]])
 ; PRELINK-NEXT:    [[__LOG2:%.*]] = call nnan ninf afn <2 x float> @llvm.log2.v2f32(<2 x float> [[__FABS]])
-; PRELINK-NEXT:    [[POWNI2F:%.*]] = sitofp nnan ninf afn <2 x i32> [[TMP1]] to <2 x float>
+; PRELINK-NEXT:    [[POWNI2F:%.*]] = uitofp nneg <2 x i32> [[TMP1]] to <2 x float>
 ; PRELINK-NEXT:    [[__YLOGX:%.*]] = fmul nnan ninf afn <2 x float> [[__LOG2]], [[POWNI2F]]
 ; PRELINK-NEXT:    [[__EXP2:%.*]] = call nnan ninf afn nofpclass(nan ninf nzero nsub nnorm) <2 x float> @llvm.exp2.v2f32(<2 x float> [[__YLOGX]])
 ; PRELINK-NEXT:    [[__YEVEN:%.*]] = shl <2 x i32> [[TMP1]], splat (i32 31)
diff --git a/llvm/test/Transforms/InstCombine/fabs.ll b/llvm/test/Transforms/InstCombine/fabs.ll
index 7c9e1d5b2a82a..c14b748b5b8d6 100644
--- a/llvm/test/Transforms/InstCombine/fabs.ll
+++ b/llvm/test/Transforms/InstCombine/fabs.ll
@@ -16,6 +16,8 @@ declare float @llvm.fmuladd.f32(float, float, float)
 
 declare void @use(float)
 declare void @usebool(i1)
+declare i1 @llvm.is.fpclass.f32(float, i32 immarg)
+declare void @llvm.assume(i1 noundef)
 
 define float @replace_fabs_call_f32(float %x) {
 ; CHECK-LABEL: @replace_fabs_call_f32(
@@ -1856,3 +1858,35 @@ define i1 @fptosi_no_fabs_unknown_sign(float %x) {
   %cmp = icmp slt i32 %fptosi, 0
   ret i1 %cmp
 }
+
+; fptosi of -inf is poison (it can never fit in the result type), so a
+; source known to be -inf does not disqualify the fold.
+define i1 @fptosi_known_neg_inf_is_never_negative(float %x) {
+; CHECK-LABEL: @fptosi_known_neg_inf_is_never_negative(
+; CHECK-NEXT:    [[ISNINF:%.*]] = fcmp oeq float [[X:%.*]], -inf
+; CHECK-NEXT:    call void @llvm.assume(i1 [[ISNINF]])
+; CHECK-NEXT:    ret i1 false
+;
+  %isninf = call i1 @llvm.is.fpclass.f32(float %x, i32 4)
+  call void @llvm.assume(i1 %isninf)
+  %fptosi = fptosi float %x to i32
+  %cmp = icmp slt i32 %fptosi, 0
+  ret i1 %cmp
+}
+
+; A source that is known to be a genuine negative normal (e.g. -2.0) must
+; not be folded.
+define i1 @fptosi_known_neg_normal_no_fold(float %x) {
+; CHECK-LABEL: @fptosi_known_neg_normal_no_fold(
+; CHECK-NEXT:    [[ISNEGTWO:%.*]] = fcmp oeq float [[X:%.*]], -2.000000e+00
+; CHECK-NEXT:    call void @llvm.assume(i1 [[ISNEGTWO]])
+; CHECK-NEXT:    [[FPTOSI:%.*]] = fptosi float [[X]] to i32
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[FPTOSI]], 0
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %isnegtwo = fcmp oeq float %x, -2.0
+  call void @llvm.assume(i1 %isnegtwo)
+  %fptosi = fptosi float %x to i32
+  %cmp = icmp slt i32 %fptosi, 0
+  ret i1 %cmp
+}

>From 48a3f89208080f74208e25c01411c12ebdb25372 Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Sat, 22 Aug 2026 19:56:41 +0530
Subject: [PATCH 3/3] [InstCombine] Preserve ninf/nsz when canonicalizing
 sitofp to uitofp nneg

---
 .../Transforms/InstCombine/InstCombineCasts.cpp    |  5 +++++
 .../CodeGen/AMDGPU/amdgpu-simplify-libcall-pow.ll  |  6 +++---
 llvm/test/Transforms/InstCombine/sitofp.ll         | 14 ++++++++++++++
 3 files changed, 22 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
index 2db6396b9d661..26011a3124419 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
@@ -2618,6 +2618,11 @@ Instruction *InstCombinerImpl::visitSIToFP(CastInst &CI) {
     auto *UI =
         CastInst::Create(Instruction::UIToFP, CI.getOperand(0), CI.getType());
     UI->setNonNeg(true);
+    // nnan/afn/reassoc/contract/arcp carry no meaning for a value-preserving
+    // cast, but ninf/nsz are semantically meaningful for {u,s}itofp and
+    // remain valid after reinterpreting the operand as unsigned.
+    UI->setHasNoInfs(CI.hasNoInfs());
+    UI->setHasNoSignedZeros(CI.hasNoSignedZeros());
     return UI;
   }
   return nullptr;
diff --git a/llvm/test/CodeGen/AMDGPU/amdgpu-simplify-libcall-pow.ll b/llvm/test/CodeGen/AMDGPU/amdgpu-simplify-libcall-pow.ll
index fe3dd0ae141b5..97a2e1cc89b74 100644
--- a/llvm/test/CodeGen/AMDGPU/amdgpu-simplify-libcall-pow.ll
+++ b/llvm/test/CodeGen/AMDGPU/amdgpu-simplify-libcall-pow.ll
@@ -5259,7 +5259,7 @@ define float @test_pow_afn_nnan_ninf_f32_known_integral_uitofp(float %x, i32 %y)
 ; PRELINK-NEXT:    [[TMP1:%.*]] = fptosi float [[Y_CAST]] to i32
 ; PRELINK-NEXT:    [[__FABS:%.*]] = call nnan ninf afn float @llvm.fabs.f32(float [[X]])
 ; PRELINK-NEXT:    [[__LOG2:%.*]] = call nnan ninf afn float @llvm.log2.f32(float [[__FABS]])
-; PRELINK-NEXT:    [[POWNI2F:%.*]] = uitofp nneg i32 [[TMP1]] to float
+; PRELINK-NEXT:    [[POWNI2F:%.*]] = uitofp ninf nneg i32 [[TMP1]] to float
 ; PRELINK-NEXT:    [[__YLOGX:%.*]] = fmul nnan ninf afn float [[__LOG2]], [[POWNI2F]]
 ; PRELINK-NEXT:    [[__EXP2:%.*]] = call nnan ninf afn nofpclass(nan ninf nzero nsub nnorm) float @llvm.exp2.f32(float [[__YLOGX]])
 ; PRELINK-NEXT:    [[__YEVEN:%.*]] = shl i32 [[TMP1]], 31
@@ -5410,7 +5410,7 @@ define float @test_pow_afn_nnan_ninf_f32_known_integral_uitofp_i256(float %x, i2
 ; PRELINK-NEXT:    [[TMP1:%.*]] = fptosi float [[Y_CAST]] to i32
 ; PRELINK-NEXT:    [[__FABS:%.*]] = call nnan ninf afn float @llvm.fabs.f32(float [[X]])
 ; PRELINK-NEXT:    [[__LOG2:%.*]] = call nnan ninf afn float @llvm.log2.f32(float [[__FABS]])
-; PRELINK-NEXT:    [[POWNI2F:%.*]] = uitofp nneg i32 [[TMP1]] to float
+; PRELINK-NEXT:    [[POWNI2F:%.*]] = uitofp ninf nneg i32 [[TMP1]] to float
 ; PRELINK-NEXT:    [[__YLOGX:%.*]] = fmul nnan ninf afn float [[__LOG2]], [[POWNI2F]]
 ; PRELINK-NEXT:    [[__EXP2:%.*]] = call nnan ninf afn nofpclass(nan ninf nzero nsub nnorm) float @llvm.exp2.f32(float [[__YLOGX]])
 ; PRELINK-NEXT:    [[__YEVEN:%.*]] = shl i32 [[TMP1]], 31
@@ -5589,7 +5589,7 @@ define <2 x float> @test_pow_afn_nnan_ninf_v2f32_known_integral_uitofp(<2 x floa
 ; PRELINK-NEXT:    [[TMP1:%.*]] = fptosi <2 x float> [[Y_CAST]] to <2 x i32>
 ; PRELINK-NEXT:    [[__FABS:%.*]] = call nnan ninf afn <2 x float> @llvm.fabs.v2f32(<2 x float> [[X]])
 ; PRELINK-NEXT:    [[__LOG2:%.*]] = call nnan ninf afn <2 x float> @llvm.log2.v2f32(<2 x float> [[__FABS]])
-; PRELINK-NEXT:    [[POWNI2F:%.*]] = uitofp nneg <2 x i32> [[TMP1]] to <2 x float>
+; PRELINK-NEXT:    [[POWNI2F:%.*]] = uitofp ninf nneg <2 x i32> [[TMP1]] to <2 x float>
 ; PRELINK-NEXT:    [[__YLOGX:%.*]] = fmul nnan ninf afn <2 x float> [[__LOG2]], [[POWNI2F]]
 ; PRELINK-NEXT:    [[__EXP2:%.*]] = call nnan ninf afn nofpclass(nan ninf nzero nsub nnorm) <2 x float> @llvm.exp2.v2f32(<2 x float> [[__YLOGX]])
 ; PRELINK-NEXT:    [[__YEVEN:%.*]] = shl <2 x i32> [[TMP1]], splat (i32 31)
diff --git a/llvm/test/Transforms/InstCombine/sitofp.ll b/llvm/test/Transforms/InstCombine/sitofp.ll
index ccd0dc65d83ea..c96d75fce93ff 100644
--- a/llvm/test/Transforms/InstCombine/sitofp.ll
+++ b/llvm/test/Transforms/InstCombine/sitofp.ll
@@ -486,3 +486,17 @@ entry:
   %is.inf = call i1 @llvm.is.fpclass.f16(half %f, i32 516)
   ret i1 %is.inf
 }
+
+; The sitofp -> uitofp nneg canonicalization should keep ninf/nsz (still
+; semantically meaningful for the cast), but drop nnan/afn (meaningless for
+; a value-preserving cast).
+define float @sitofp_to_uitofp_keeps_ninf_nsz(i32 %a) {
+; CHECK-LABEL: @sitofp_to_uitofp_keeps_ninf_nsz(
+; CHECK-NEXT:    [[M:%.*]] = and i32 [[A:%.*]], 2147483647
+; CHECK-NEXT:    [[F:%.*]] = uitofp ninf nsz nneg i32 [[M]] to float
+; CHECK-NEXT:    ret float [[F]]
+;
+  %m = and i32 %a, 2147483647
+  %f = sitofp nnan ninf nsz afn i32 %m to float
+  ret float %f
+}



More information about the llvm-commits mailing list