[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