[llvm] [InstCombine] Fold a condition-derived shifted LSB into select arms (PR #213882)

via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 4 02:27:21 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Vito Kortbeek (vkortbeek-gf)

<details>
<summary>Changes</summary>

If a select condition determines bit 0 of X and an operand is shl(zext/trunc/self X, BW - 1), replace that operand with either the sign-bit mask or zero on each select arm.

Proof: https://alive2.llvm.org/ce/z/8M3ksf
Fixes: #<!-- -->213881 

Assisted-by: AI

---
Full diff: https://github.com/llvm/llvm-project/pull/213882.diff


2 Files Affected:

- (modified) llvm/lib/Transforms/InstCombine/InstructionCombining.cpp (+54-11) 
- (modified) llvm/test/Transforms/InstCombine/binop-select.ll (+77) 


``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index acb144e1e807d..31996d62a36d5 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -45,6 +45,7 @@
 #include "llvm/Analysis/BasicAliasAnalysis.h"
 #include "llvm/Analysis/BlockFrequencyInfo.h"
 #include "llvm/Analysis/CFG.h"
+#include "llvm/Analysis/CmpInstAnalysis.h"
 #include "llvm/Analysis/ConstantFolding.h"
 #include "llvm/Analysis/GlobalsModRef.h"
 #include "llvm/Analysis/InstructionSimplify.h"
@@ -1745,6 +1746,32 @@ Instruction *InstCombinerImpl::foldBinopOfSextBoolToSelect(BinaryOperator &BO) {
   return createSelectInstWithUnknownProfile(X, TVal, FVal);
 }
 
+// If SI's condition determines bit 0 of X and Op is
+// shl (zext/trunc/self X), BW - 1, return Op's known value on the given
+// select arm.
+static Constant *getShiftedLsbValueForSelectArm(Value *Op, SelectInst *SI,
+                                                bool IsTrueArm) {
+  auto BitTest =
+      decomposeBitTest(SI->getCondition(), /*LookThroughTrunc=*/true,
+                       /*AllowNonZeroC=*/false, /*DecomposeAnd=*/true);
+  if (!BitTest || !BitTest->Mask.isOne())
+    return nullptr;
+
+  Type *Ty = Op->getType();
+  if (!Ty->isIntegerTy())
+    return nullptr;
+
+  unsigned BitWidth = Ty->getIntegerBitWidth();
+  if (!match(Op, m_Shl(m_ZExtOrTruncOrSelf(m_Specific(BitTest->X)),
+                       m_SpecificInt(BitWidth - 1))))
+    return nullptr;
+
+  bool BitSetOnTrue = BitTest->Pred == ICmpInst::ICMP_NE;
+  return IsTrueArm == BitSetOnTrue
+             ? ConstantInt::get(Ty, APInt::getSignMask(BitWidth))
+             : ConstantInt::getNullValue(Ty);
+}
+
 static Value *simplifyOperationIntoSelectOperand(Instruction &I, SelectInst *SI,
                                                  bool IsTrueArm) {
   SmallVector<Value *> Ops;
@@ -1761,6 +1788,9 @@ static Value *simplifyOperationIntoSelectOperand(Instruction &I, SelectInst *SI,
     } else if (match(Op, m_ZExt(m_Specific(SI->getCondition())))) {
       V = IsTrueArm ? ConstantInt::get(Op->getType(), 1)
                     : ConstantInt::getNullValue(Op->getType());
+    } else if (Constant *C =
+                   getShiftedLsbValueForSelectArm(Op, SI, IsTrueArm)) {
+      V = C;
     } else {
       V = Op;
     }
@@ -1771,9 +1801,13 @@ static Value *simplifyOperationIntoSelectOperand(Instruction &I, SelectInst *SI,
 }
 
 static Value *foldOperationIntoSelectOperand(Instruction &I, SelectInst *SI,
-                                             Value *NewOp, InstCombiner &IC) {
+                                             Value *NewOp, bool IsTrueArm,
+                                             InstCombiner &IC) {
   Instruction *Clone = I.clone();
   Clone->replaceUsesOfWith(SI, NewOp);
+  for (Use &U : Clone->operands())
+    if (Constant *C = getShiftedLsbValueForSelectArm(U, SI, IsTrueArm))
+      U.set(C);
   Clone->dropUBImplyingAttrsAndMetadata();
   IC.InsertNewInstBefore(Clone, I.getIterator());
   return Clone;
@@ -1830,9 +1864,11 @@ Instruction *InstCombinerImpl::FoldOpIntoSelect(Instruction &Op, SelectInst *SI,
 
   // Create an instruction for the arm that did not fold.
   if (!NewTV)
-    NewTV = foldOperationIntoSelectOperand(Op, SI, TV, *this);
+    NewTV =
+        foldOperationIntoSelectOperand(Op, SI, TV, /*IsTrueArm=*/true, *this);
   if (!NewFV)
-    NewFV = foldOperationIntoSelectOperand(Op, SI, FV, *this);
+    NewFV =
+        foldOperationIntoSelectOperand(Op, SI, FV, /*IsTrueArm=*/false, *this);
 
   SelectInst *NewSel = SelectInst::Create(SI->getCondition(), NewTV, NewFV);
 
@@ -2319,19 +2355,26 @@ Instruction *InstCombinerImpl::foldBinopWithPhiOperands(BinaryOperator &BO) {
 }
 
 Instruction *InstCombinerImpl::foldBinOpIntoSelectOrPhi(BinaryOperator &I) {
-  auto TryFoldOperand = [&](unsigned OpIdx,
-                            bool IsOtherParamConst) -> Instruction * {
-    if (auto *Sel = dyn_cast<SelectInst>(I.getOperand(OpIdx)))
-      return FoldOpIntoSelect(I, Sel, false, !IsOtherParamConst);
-    if (auto *PN = dyn_cast<PHINode>(I.getOperand(OpIdx)))
+  auto TryFoldOperand = [&](Value *Op, Value *OtherOp) -> Instruction * {
+    if (auto *Sel = dyn_cast<SelectInst>(Op)) {
+      // A shifted LSB determined by the condition is constant on each select
+      // arm. If the shift has one use, folding makes it dead, so one-arm
+      // folding is profitable.
+      bool IsProfitableToFoldOneArm =
+          OtherOp->hasOneUse() &&
+          getShiftedLsbValueForSelectArm(OtherOp, Sel, /*IsTrueArm=*/true);
+      return FoldOpIntoSelect(I, Sel, /*FoldWithMultiUse=*/false,
+                              !isa<Constant>(OtherOp) &&
+                                  !IsProfitableToFoldOneArm);
+    }
+    if (auto *PN = dyn_cast<PHINode>(Op))
       return foldOpIntoPhi(I, PN);
     return nullptr;
   };
 
-  if (Instruction *NewI =
-          TryFoldOperand(/*OpIdx=*/0, isa<Constant>(I.getOperand(1))))
+  if (Instruction *NewI = TryFoldOperand(I.getOperand(0), I.getOperand(1)))
     return NewI;
-  return TryFoldOperand(/*OpIdx=*/1, isa<Constant>(I.getOperand(0)));
+  return TryFoldOperand(I.getOperand(1), I.getOperand(0));
 }
 
 static bool shouldMergeGEPs(GEPOperator &GEP, GEPOperator &Src) {
diff --git a/llvm/test/Transforms/InstCombine/binop-select.ll b/llvm/test/Transforms/InstCombine/binop-select.ll
index 3265ea54a831e..da4bb67df00b5 100644
--- a/llvm/test/Transforms/InstCombine/binop-select.ll
+++ b/llvm/test/Transforms/InstCombine/binop-select.ll
@@ -2,6 +2,7 @@
 ; RUN: opt < %s -passes=instcombine -S | FileCheck %s
 
 declare void @use(i32)
+declare void @use_i8(i8)
 declare void @use_f32(float)
 declare void @use_v2f16(<2 x half>)
 declare void @use_v2i8(<2 x i8>)
@@ -545,6 +546,82 @@ define i8 @commonArgWithAdd0(i1 %arg0) {
   ret i8 %v3
 }
 
+define i8 @commonArgWithOrShl0(i8 %arg0) {
+; CHECK-LABEL: @commonArgWithOrShl0(
+; CHECK-NEXT:    [[V0:%.*]] = trunc i8 [[ARG0:%.*]] to i1
+; CHECK-NEXT:    [[V4:%.*]] = select i1 [[V0]], i8 -123, i8 9
+; CHECK-NEXT:    ret i8 [[V4]]
+;
+  %v0 = trunc i8 %arg0 to i1
+  %v1 = select i1 %v0, i8 5, i8 9
+  %v3 = shl i8 %arg0, 7
+  %v4 = or i8 %v1, %v3
+  ret i8 %v4
+}
+
+define i8 @commonArgWithOrShl1(i8 %arg0, i8 %arg1) {
+; CHECK-LABEL: @commonArgWithOrShl1(
+; CHECK-NEXT:    [[V0:%.*]] = trunc i8 [[ARG0:%.*]] to i1
+; CHECK-NEXT:    [[TMP1:%.*]] = or i8 [[ARG1:%.*]], -128
+; CHECK-NEXT:    [[V4:%.*]] = select i1 [[V0]], i8 [[TMP1]], i8 0
+; CHECK-NEXT:    ret i8 [[V4]]
+;
+  %v0 = trunc i8 %arg0 to i1
+  %v1 = select i1 %v0, i8 %arg1, i8 0
+  %v3 = shl i8 %arg0, 7
+  %v4 = or i8 %v1, %v3
+  ret i8 %v4
+}
+
+define i16 @commonArgWithOrShlEqZext(i8 %x, i16 %y) {
+; CHECK-LABEL: @commonArgWithOrShlEqZext(
+; CHECK-NEXT:    [[MASKED:%.*]] = and i8 [[X:%.*]], 1
+; CHECK-NEXT:    [[COND:%.*]] = icmp eq i8 [[MASKED]], 0
+; CHECK-NEXT:    [[TMP1:%.*]] = or i16 [[Y:%.*]], -32768
+; CHECK-NEXT:    [[RESULT:%.*]] = select i1 [[COND]], i16 0, i16 [[TMP1]]
+; CHECK-NEXT:    ret i16 [[RESULT]]
+;
+  %masked = and i8 %x, 1
+  %cond = icmp eq i8 %masked, 0
+  %sel = select i1 %cond, i16 0, i16 %y
+  %wide = zext i8 %x to i16
+  %shift = shl i16 %wide, 15
+  %result = or i16 %sel, %shift
+  ret i16 %result
+}
+
+define i8 @commonArgWithOrShlTrunc(i16 %x, i8 %y) {
+; CHECK-LABEL: @commonArgWithOrShlTrunc(
+; CHECK-NEXT:    [[COND:%.*]] = trunc i16 [[X:%.*]] to i1
+; CHECK-NEXT:    [[TMP1:%.*]] = or i8 [[Y:%.*]], -128
+; CHECK-NEXT:    [[RESULT:%.*]] = select i1 [[COND]], i8 [[TMP1]], i8 0
+; CHECK-NEXT:    ret i8 [[RESULT]]
+;
+  %cond = trunc i16 %x to i1
+  %sel = select i1 %cond, i8 %y, i8 0
+  %narrow = trunc i16 %x to i8
+  %shift = shl i8 %narrow, 7
+  %result = or i8 %sel, %shift
+  ret i8 %result
+}
+
+define i8 @commonArgWithOrShlMultiUse(i8 %x, i8 %y) {
+; CHECK-LABEL: @commonArgWithOrShlMultiUse(
+; CHECK-NEXT:    [[COND:%.*]] = trunc i8 [[X:%.*]] to i1
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i8 [[Y:%.*]], i8 0
+; CHECK-NEXT:    [[SHIFT:%.*]] = shl i8 [[X]], 7
+; CHECK-NEXT:    call void @use_i8(i8 [[SHIFT]])
+; CHECK-NEXT:    [[RESULT:%.*]] = or i8 [[SEL]], [[SHIFT]]
+; CHECK-NEXT:    ret i8 [[RESULT]]
+;
+  %cond = trunc i8 %x to i1
+  %sel = select i1 %cond, i8 %y, i8 0
+  %shift = shl i8 %x, 7
+  call void @use_i8(i8 %shift)
+  %result = or i8 %sel, %shift
+  ret i8 %result
+}
+
 define i32 @OrSelectIcmpZero(i32 %a, i32 %b) {
 ; CHECK-LABEL: @OrSelectIcmpZero(
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[A:%.*]], 0

``````````

</details>


https://github.com/llvm/llvm-project/pull/213882


More information about the llvm-commits mailing list