[llvm] [InstCombine] Fix miscompilation for `select` + `FCmp` (PR #177872)
Tirthankar Mazumder via llvm-commits
llvm-commits at lists.llvm.org
Mon Feb 16 10:19:39 PST 2026
https://github.com/wermos updated https://github.com/llvm/llvm-project/pull/177872
>From 8fce9711db26acaf58e2ae09baa4e4ea8ea4d5d6 Mon Sep 17 00:00:00 2001
From: Tirthankar Mazumder <tmazumder.github at gmail.com>
Date: Mon, 26 Jan 2026 00:31:57 +0530
Subject: [PATCH 1/3] Pre-commit test
---
.../Transforms/InstCombine/clamp-to-minmax.ll | 24 ++++++++++++++++---
1 file changed, 21 insertions(+), 3 deletions(-)
diff --git a/llvm/test/Transforms/InstCombine/clamp-to-minmax.ll b/llvm/test/Transforms/InstCombine/clamp-to-minmax.ll
index 0ccaa9c393654..2b0b16cf0c199 100644
--- a/llvm/test/Transforms/InstCombine/clamp-to-minmax.ll
+++ b/llvm/test/Transforms/InstCombine/clamp-to-minmax.ll
@@ -140,8 +140,8 @@ define float @clamp_test_1(float %x) {
; CHECK-LABEL: @clamp_test_1(
; CHECK-NEXT: [[INNER_CMP_INV:%.*]] = fcmp fast oge float [[X:%.*]], 2.550000e+02
; CHECK-NEXT: [[INNER_SEL:%.*]] = select nnan ninf i1 [[INNER_CMP_INV]], float 2.550000e+02, float [[X]]
-; CHECK-NEXT: [[OUTER_CMP:%.*]] = fcmp fast oge float [[INNER_SEL]], 1.000000e+00
-; CHECK-NEXT: [[R:%.*]] = select nnan ninf i1 [[OUTER_CMP]], float [[INNER_SEL]], float 1.000000e+00
+; CHECK-NEXT: [[DOTINV:%.*]] = fcmp fast oge float [[INNER_SEL]], 1.000000e+00
+; CHECK-NEXT: [[R:%.*]] = select nnan ninf i1 [[DOTINV]], float [[INNER_SEL]], float 1.000000e+00
; CHECK-NEXT: ret float [[R]]
;
%inner_cmp = fcmp fast ult float %x, 255.0
@@ -574,7 +574,6 @@ define i32 @mixed_clamp_to_i32_2(float %x) {
ret i32 %r
}
-
define <2 x float> @mixed_clamp_to_float_vec(<2 x i32> %x) {
; CHECK-LABEL: @mixed_clamp_to_float_vec(
; CHECK-NEXT: [[R1:%.*]] = call <2 x i32> @llvm.smax.v2i32(<2 x i32> [[SI_MIN:%.*]], <2 x i32> splat (i32 1))
@@ -590,3 +589,22 @@ define <2 x float> @mixed_clamp_to_float_vec(<2 x i32> %x) {
%r = select <2 x i1> %lo_cmp, <2 x float> <float 1.0, float 1.0>, <2 x float> %f_min
ret <2 x float> %r
}
+
+; The min/max clamp code should NOT fire here because doing so results
+; in a form that is more poisonous than the original.
+define float @clamp_select_with_poison(float %x) {
+; CHECK-LABEL: @clamp_select_with_poison(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[A_INV:%.*]] = fcmp nnan ninf oge float [[X:%.*]], 2.550000e+02
+; CHECK-NEXT: [[B:%.*]] = select nnan ninf i1 [[A_INV]], float [[X]], float 2.550000e+02
+; CHECK-NEXT: [[DOTINV:%.*]] = fcmp nnan ole float [[B]], 5.120000e+02
+; CHECK-NEXT: [[R:%.*]] = select nnan i1 [[DOTINV]], float [[B]], float 5.120000e+02
+; CHECK-NEXT: ret float [[R]]
+;
+entry:
+ %a = fcmp nnan ninf ult float %x, 255.0
+ %b = select i1 %a, float 255.0, float %x
+ %c = fcmp nnan ugt float %x, 512.0
+ %r = select i1 %c, float 512.0, float %b
+ ret float %r
+}
>From e6a91ce0b4ab913fdf0900eb60192e64a6f01ea4 Mon Sep 17 00:00:00 2001
From: Tirthankar Mazumder <tmazumder.github at gmail.com>
Date: Wed, 28 Jan 2026 01:17:01 +0530
Subject: [PATCH 2/3] Intersect fast-math flags between `select` and `FCmp`
correctly.
---
.../InstCombine/InstCombineSelect.cpp | 31 ++++++++++++++++---
.../Transforms/InstCombine/clamp-to-minmax.ll | 14 ++++-----
2 files changed, 32 insertions(+), 13 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index 00f4118d538d8..a9e990e59d5b5 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -4510,13 +4510,34 @@ Instruction *InstCombinerImpl::visitSelectInst(SelectInst &SI) {
CmpInst::Predicate MinMaxPred = getMinMaxPred(SPF, SPR.Ordered);
Value *Cmp;
- if (CmpInst::isIntPredicate(MinMaxPred))
+ Value *NewSI;
+ if (CmpInst::isIntPredicate(MinMaxPred)) {
Cmp = Builder.CreateICmp(MinMaxPred, LHS, RHS);
- else
- Cmp = Builder.CreateFCmpFMF(MinMaxPred, LHS, RHS,
- cast<Instruction>(SI.getCondition()));
+ NewSI = Builder.CreateSelect(Cmp, LHS, RHS, SI.getName(), &SI);
+ } else {
+ // Intersect fast-math flags between the original select and its
+ // condition before forming a min/max fcmp+select pattern.
+ //
+ // Therefore, only retain fast-math flags that were already required
+ // by both the original select and its condition.
+ FastMathFlags CmpFMF =
+ cast<Instruction>(SI.getCondition())->getFastMathFlags();
+
+ // We can't unconditionally query fast-math flags from the select
+ // because only selects that produce floating-point values implement
+ // FPMathOperator. Treat non-FP selects as having no fast-math
+ // guarantees.
+ FastMathFlags SelFMF;
+ if (SIFPOp)
+ SelFMF = SI.getFastMathFlags();
+
+ FastMathFlags NewFMF = SelFMF & CmpFMF;
+
+ Cmp = Builder.CreateFCmpFMF(MinMaxPred, LHS, RHS, NewFMF);
+
+ NewSI = Builder.CreateSelectFMF(Cmp, LHS, RHS, NewFMF);
+ }
- Value *NewSI = Builder.CreateSelect(Cmp, LHS, RHS, SI.getName(), &SI);
if (!IsCastNeeded)
return replaceInstUsesWith(SI, NewSI);
diff --git a/llvm/test/Transforms/InstCombine/clamp-to-minmax.ll b/llvm/test/Transforms/InstCombine/clamp-to-minmax.ll
index 2b0b16cf0c199..57e357fc9dcb7 100644
--- a/llvm/test/Transforms/InstCombine/clamp-to-minmax.ll
+++ b/llvm/test/Transforms/InstCombine/clamp-to-minmax.ll
@@ -590,15 +590,13 @@ define <2 x float> @mixed_clamp_to_float_vec(<2 x i32> %x) {
ret <2 x float> %r
}
-; The min/max clamp code should NOT fire here because doing so results
-; in a form that is more poisonous than the original.
-define float @clamp_select_with_poison(float %x) {
-; CHECK-LABEL: @clamp_select_with_poison(
+define float @clamp_select_no_fmf_strengthening(float %x) {
+; CHECK-LABEL: @clamp_select_no_fmf_strengthening(
; CHECK-NEXT: entry:
-; CHECK-NEXT: [[A_INV:%.*]] = fcmp nnan ninf oge float [[X:%.*]], 2.550000e+02
-; CHECK-NEXT: [[B:%.*]] = select nnan ninf i1 [[A_INV]], float [[X]], float 2.550000e+02
-; CHECK-NEXT: [[DOTINV:%.*]] = fcmp nnan ole float [[B]], 5.120000e+02
-; CHECK-NEXT: [[R:%.*]] = select nnan i1 [[DOTINV]], float [[B]], float 5.120000e+02
+; CHECK-NEXT: [[A_INV:%.*]] = fcmp oge float [[X:%.*]], 2.550000e+02
+; CHECK-NEXT: [[B:%.*]] = select i1 [[A_INV]], float [[X]], float 2.550000e+02
+; CHECK-NEXT: [[DOTINV:%.*]] = fcmp ole float [[B]], 5.120000e+02
+; CHECK-NEXT: [[R:%.*]] = select i1 [[DOTINV]], float [[B]], float 5.120000e+02
; CHECK-NEXT: ret float [[R]]
;
entry:
>From eee7ae43bd725b32bedb8cc48273aacceed9e5c1 Mon Sep 17 00:00:00 2001
From: Tirthankar Mazumder <tmazumder.github at gmail.com>
Date: Wed, 28 Jan 2026 01:23:05 +0530
Subject: [PATCH 3/3] Commit changes in other tests
---
.../Transforms/InstCombine/clamp-to-minmax.ll | 40 +++++++++----------
llvm/test/Transforms/InstCombine/minmax-fp.ll | 4 +-
2 files changed, 22 insertions(+), 22 deletions(-)
diff --git a/llvm/test/Transforms/InstCombine/clamp-to-minmax.ll b/llvm/test/Transforms/InstCombine/clamp-to-minmax.ll
index 57e357fc9dcb7..6d81cf9174c46 100644
--- a/llvm/test/Transforms/InstCombine/clamp-to-minmax.ll
+++ b/llvm/test/Transforms/InstCombine/clamp-to-minmax.ll
@@ -7,8 +7,8 @@ define float @clamp_float_fast_ordered_strict_maxmin(float %x) {
; CHECK-LABEL: @clamp_float_fast_ordered_strict_maxmin(
; CHECK-NEXT: [[CMP2:%.*]] = fcmp fast olt float [[X:%.*]], 2.550000e+02
; CHECK-NEXT: [[MIN:%.*]] = select i1 [[CMP2]], float [[X]], float 2.550000e+02
-; CHECK-NEXT: [[DOTINV:%.*]] = fcmp fast oge float [[MIN]], 1.000000e+00
-; CHECK-NEXT: [[R1:%.*]] = select nnan ninf i1 [[DOTINV]], float [[MIN]], float 1.000000e+00
+; CHECK-NEXT: [[DOTINV:%.*]] = fcmp oge float [[MIN]], 1.000000e+00
+; CHECK-NEXT: [[R1:%.*]] = select i1 [[DOTINV]], float [[MIN]], float 1.000000e+00
; CHECK-NEXT: ret float [[R1]]
;
%cmp2 = fcmp fast olt float %x, 255.0
@@ -23,8 +23,8 @@ define float @clamp_float_fast_ordered_nonstrict_maxmin(float %x) {
; CHECK-LABEL: @clamp_float_fast_ordered_nonstrict_maxmin(
; CHECK-NEXT: [[CMP2:%.*]] = fcmp fast olt float [[X:%.*]], 2.550000e+02
; CHECK-NEXT: [[MIN:%.*]] = select i1 [[CMP2]], float [[X]], float 2.550000e+02
-; CHECK-NEXT: [[DOTINV:%.*]] = fcmp fast oge float [[MIN]], 1.000000e+00
-; CHECK-NEXT: [[R1:%.*]] = select nnan ninf i1 [[DOTINV]], float [[MIN]], float 1.000000e+00
+; CHECK-NEXT: [[DOTINV:%.*]] = fcmp oge float [[MIN]], 1.000000e+00
+; CHECK-NEXT: [[R1:%.*]] = select i1 [[DOTINV]], float [[MIN]], float 1.000000e+00
; CHECK-NEXT: ret float [[R1]]
;
%cmp2 = fcmp fast olt float %x, 255.0
@@ -39,8 +39,8 @@ define float @clamp_float_fast_ordered_strict_minmax(float %x) {
; CHECK-LABEL: @clamp_float_fast_ordered_strict_minmax(
; CHECK-NEXT: [[CMP2:%.*]] = fcmp fast ogt float [[X:%.*]], 1.000000e+00
; CHECK-NEXT: [[MAX:%.*]] = select i1 [[CMP2]], float [[X]], float 1.000000e+00
-; CHECK-NEXT: [[DOTINV:%.*]] = fcmp fast ole float [[MAX]], 2.550000e+02
-; CHECK-NEXT: [[R1:%.*]] = select nnan ninf i1 [[DOTINV]], float [[MAX]], float 2.550000e+02
+; CHECK-NEXT: [[DOTINV:%.*]] = fcmp ole float [[MAX]], 2.550000e+02
+; CHECK-NEXT: [[R1:%.*]] = select i1 [[DOTINV]], float [[MAX]], float 2.550000e+02
; CHECK-NEXT: ret float [[R1]]
;
%cmp2 = fcmp fast ogt float %x, 1.0
@@ -55,8 +55,8 @@ define float @clamp_float_fast_ordered_nonstrict_minmax(float %x) {
; CHECK-LABEL: @clamp_float_fast_ordered_nonstrict_minmax(
; CHECK-NEXT: [[CMP2:%.*]] = fcmp fast ogt float [[X:%.*]], 1.000000e+00
; CHECK-NEXT: [[MAX:%.*]] = select i1 [[CMP2]], float [[X]], float 1.000000e+00
-; CHECK-NEXT: [[DOTINV:%.*]] = fcmp fast ole float [[MAX]], 2.550000e+02
-; CHECK-NEXT: [[R1:%.*]] = select nnan ninf i1 [[DOTINV]], float [[MAX]], float 2.550000e+02
+; CHECK-NEXT: [[DOTINV:%.*]] = fcmp ole float [[MAX]], 2.550000e+02
+; CHECK-NEXT: [[R1:%.*]] = select i1 [[DOTINV]], float [[MAX]], float 2.550000e+02
; CHECK-NEXT: ret float [[R1]]
;
%cmp2 = fcmp fast ogt float %x, 1.0
@@ -74,8 +74,8 @@ define float @clamp_float_fast_unordered_strict_maxmin(float %x) {
; CHECK-LABEL: @clamp_float_fast_unordered_strict_maxmin(
; CHECK-NEXT: [[CMP2_INV:%.*]] = fcmp fast oge float [[X:%.*]], 2.550000e+02
; CHECK-NEXT: [[MIN:%.*]] = select nnan ninf i1 [[CMP2_INV]], float 2.550000e+02, float [[X]]
-; CHECK-NEXT: [[DOTINV:%.*]] = fcmp fast oge float [[MIN]], 1.000000e+00
-; CHECK-NEXT: [[R:%.*]] = select nnan ninf i1 [[DOTINV]], float [[MIN]], float 1.000000e+00
+; CHECK-NEXT: [[DOTINV:%.*]] = fcmp oge float [[MIN]], 1.000000e+00
+; CHECK-NEXT: [[R:%.*]] = select i1 [[DOTINV]], float [[MIN]], float 1.000000e+00
; CHECK-NEXT: ret float [[R]]
;
%cmp2 = fcmp fast ult float %x, 255.0
@@ -90,8 +90,8 @@ define float @clamp_float_fast_unordered_nonstrict_maxmin(float %x) {
; CHECK-LABEL: @clamp_float_fast_unordered_nonstrict_maxmin(
; CHECK-NEXT: [[CMP2_INV:%.*]] = fcmp fast oge float [[X:%.*]], 2.550000e+02
; CHECK-NEXT: [[MIN:%.*]] = select nnan ninf i1 [[CMP2_INV]], float 2.550000e+02, float [[X]]
-; CHECK-NEXT: [[DOTINV:%.*]] = fcmp fast oge float [[MIN]], 1.000000e+00
-; CHECK-NEXT: [[R:%.*]] = select nnan ninf i1 [[DOTINV]], float [[MIN]], float 1.000000e+00
+; CHECK-NEXT: [[DOTINV:%.*]] = fcmp oge float [[MIN]], 1.000000e+00
+; CHECK-NEXT: [[R:%.*]] = select i1 [[DOTINV]], float [[MIN]], float 1.000000e+00
; CHECK-NEXT: ret float [[R]]
;
%cmp2 = fcmp fast ult float %x, 255.0
@@ -106,8 +106,8 @@ define float @clamp_float_fast_unordered_strict_minmax(float %x) {
; CHECK-LABEL: @clamp_float_fast_unordered_strict_minmax(
; CHECK-NEXT: [[CMP2_INV:%.*]] = fcmp fast ole float [[X:%.*]], 1.000000e+00
; CHECK-NEXT: [[MAX:%.*]] = select nnan ninf i1 [[CMP2_INV]], float 1.000000e+00, float [[X]]
-; CHECK-NEXT: [[DOTINV:%.*]] = fcmp fast ole float [[MAX]], 2.550000e+02
-; CHECK-NEXT: [[R:%.*]] = select nnan ninf i1 [[DOTINV]], float [[MAX]], float 2.550000e+02
+; CHECK-NEXT: [[DOTINV:%.*]] = fcmp ole float [[MAX]], 2.550000e+02
+; CHECK-NEXT: [[R:%.*]] = select i1 [[DOTINV]], float [[MAX]], float 2.550000e+02
; CHECK-NEXT: ret float [[R]]
;
%cmp2 = fcmp fast ugt float %x, 1.0
@@ -122,8 +122,8 @@ define float @clamp_float_fast_unordered_nonstrict_minmax(float %x) {
; CHECK-LABEL: @clamp_float_fast_unordered_nonstrict_minmax(
; CHECK-NEXT: [[CMP2_INV:%.*]] = fcmp fast ole float [[X:%.*]], 1.000000e+00
; CHECK-NEXT: [[MAX:%.*]] = select nnan ninf i1 [[CMP2_INV]], float 1.000000e+00, float [[X]]
-; CHECK-NEXT: [[DOTINV:%.*]] = fcmp fast ole float [[MAX]], 2.550000e+02
-; CHECK-NEXT: [[R:%.*]] = select nnan ninf i1 [[DOTINV]], float [[MAX]], float 2.550000e+02
+; CHECK-NEXT: [[DOTINV:%.*]] = fcmp ole float [[MAX]], 2.550000e+02
+; CHECK-NEXT: [[R:%.*]] = select i1 [[DOTINV]], float [[MAX]], float 2.550000e+02
; CHECK-NEXT: ret float [[R]]
;
%cmp2 = fcmp fast ugt float %x, 1.0
@@ -140,8 +140,8 @@ define float @clamp_test_1(float %x) {
; CHECK-LABEL: @clamp_test_1(
; CHECK-NEXT: [[INNER_CMP_INV:%.*]] = fcmp fast oge float [[X:%.*]], 2.550000e+02
; CHECK-NEXT: [[INNER_SEL:%.*]] = select nnan ninf i1 [[INNER_CMP_INV]], float 2.550000e+02, float [[X]]
-; CHECK-NEXT: [[DOTINV:%.*]] = fcmp fast oge float [[INNER_SEL]], 1.000000e+00
-; CHECK-NEXT: [[R:%.*]] = select nnan ninf i1 [[DOTINV]], float [[INNER_SEL]], float 1.000000e+00
+; CHECK-NEXT: [[DOTINV:%.*]] = fcmp oge float [[INNER_SEL]], 1.000000e+00
+; CHECK-NEXT: [[R:%.*]] = select i1 [[DOTINV]], float [[INNER_SEL]], float 1.000000e+00
; CHECK-NEXT: ret float [[R]]
;
%inner_cmp = fcmp fast ult float %x, 255.0
@@ -593,8 +593,8 @@ define <2 x float> @mixed_clamp_to_float_vec(<2 x i32> %x) {
define float @clamp_select_no_fmf_strengthening(float %x) {
; CHECK-LABEL: @clamp_select_no_fmf_strengthening(
; CHECK-NEXT: entry:
-; CHECK-NEXT: [[A_INV:%.*]] = fcmp oge float [[X:%.*]], 2.550000e+02
-; CHECK-NEXT: [[B:%.*]] = select i1 [[A_INV]], float [[X]], float 2.550000e+02
+; CHECK-NEXT: [[A_INV:%.*]] = fcmp nnan ninf oge float [[X:%.*]], 2.550000e+02
+; CHECK-NEXT: [[B:%.*]] = select nnan ninf i1 [[A_INV]], float [[X]], float 2.550000e+02
; CHECK-NEXT: [[DOTINV:%.*]] = fcmp ole float [[B]], 5.120000e+02
; CHECK-NEXT: [[R:%.*]] = select i1 [[DOTINV]], float [[B]], float 5.120000e+02
; CHECK-NEXT: ret float [[R]]
diff --git a/llvm/test/Transforms/InstCombine/minmax-fp.ll b/llvm/test/Transforms/InstCombine/minmax-fp.ll
index 15fc42f4bc97a..3e10f4f1b8a7e 100644
--- a/llvm/test/Transforms/InstCombine/minmax-fp.ll
+++ b/llvm/test/Transforms/InstCombine/minmax-fp.ll
@@ -157,7 +157,7 @@ define i8 @t9(float %a) {
ret i8 %3
}
- ; Either operand could be NaN, but fast modifier applied.
+; Either operand could be NaN, but fast modifier applied.
define i8 @t11(float %a, float %b) {
; CHECK-LABEL: @t11(
; CHECK-NEXT: [[DOTV:%.*]] = call nnan ninf nsz float @llvm.minnum.f32(float [[B:%.*]], float [[A:%.*]])
@@ -228,7 +228,7 @@ define i8 @t14_commute(float %a) {
define i8 @t15(float %a) {
; CHECK-LABEL: @t15(
-; CHECK-NEXT: [[DOTINV:%.*]] = fcmp nsz oge float [[A:%.*]], 0.000000e+00
+; CHECK-NEXT: [[DOTINV:%.*]] = fcmp oge float [[A:%.*]], 0.000000e+00
; CHECK-NEXT: [[TMP1:%.*]] = select i1 [[DOTINV]], float 0.000000e+00, float [[A]]
; CHECK-NEXT: [[TMP2:%.*]] = fptosi float [[TMP1]] to i8
; CHECK-NEXT: ret i8 [[TMP2]]
More information about the llvm-commits
mailing list