[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