[llvm] [InstCombine] Fold select of adjacent constants for boolean values (PR #207934)

via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 7 01:57:12 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Jaeuk Lee (skku970412)

<details>
<summary>Changes</summary>

## Summary

Fold selects of adjacent integer constants into an add when the selected value
is controlled by a value known to be 0 or 1:

```llvm
select (X == 0), C, C + 1
select (X != 0), C + 1, C
```

to:

```llvm
add X, C
```

The fold also handles the equivalent `X == 1` spelling. This covers the
`llvm.assume(X < 2)` pattern from #<!-- -->206708 and other cases where known bits prove
that `X` is a 0/1 value.

Legality is guarded by requiring:

- an equality compare against constant `0` or `1`;
- the compared value type to match the select result type;
- both select arms to be integer splat constants;
- `computeKnownBits` to prove the compared value is 0/1;
- the selected values for `X == 0` and `X == 1` to be exactly `C` and `C + 1`.

The replacement add is marked `nuw` unless the base constant is unsigned max,
and `nsw` unless the element bitwidth is one or the base constant is signed max.

Fixes #<!-- -->206708.

## Tests

Added InstCombine coverage for:

- issue-style `icmp eq X, 0` with `llvm.assume(X < 2)`;
- `icmp ne X, 0`;
- `icmp eq X, 1`;
- scalar and vector mask-known 0/1 values;
- signed-max and unsigned-wrap flag boundary cases;
- unknown range, reversed direction, and non-adjacent negative cases.

Local verification:

```bash
ninja -C ../build -j20 opt
llvm/utils/update_test_checks.py --opt-binary ../build/bin/opt llvm/test/Transforms/InstCombine/select.ll
../build/bin/llvm-lit -sv llvm/test/Transforms/InstCombine/select.ll
../build/bin/llvm-lit -sv llvm/test/Transforms/InstCombine
../build/bin/opt -passes=instcombine -S /tmp/pr206708.ll
git diff --check
```

## AI Tool Disclosure

Assisted-by: OpenAI Codex

Codex was used for code navigation, testcase exploration, implementation
drafting, and local verification command execution. I manually reviewed the
implementation, tests, and generated code before submission.


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


2 Files Affected:

- (modified) llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp (+24-2) 
- (modified) llvm/test/Transforms/InstCombine/select.ll (+141) 


``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index f107c15304d9f..5a11d76edfcac 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -2126,7 +2126,8 @@ static Instruction *foldSelectZeroOrOnes(ICmpInst *Cmp, Value *TVal,
 }
 
 static Value *foldSelectInstWithICmpConst(SelectInst &SI, ICmpInst *ICI,
-                                          InstCombiner::BuilderTy &Builder) {
+                                          InstCombiner::BuilderTy &Builder,
+                                          const SimplifyQuery &SQ) {
   const APInt *CmpC;
   Value *V;
   CmpPredicate Pred;
@@ -2151,6 +2152,27 @@ static Value *foldSelectInstWithICmpConst(SelectInst &SI, ICmpInst *ICI,
       return Builder.CreateBinaryIntrinsic(Intrinsic::smin, V, TVal);
   }
 
+  // If V is known to be 0 or 1, a select between adjacent constants based on
+  // V == 0/1 can be represented directly as V + C.
+  const APInt *TrueC, *FalseC;
+  if (ICmpInst::isEquality(Pred) && V->getType() == SI.getType() &&
+      (CmpC->isZero() || CmpC->isOne()) &&
+      match(TVal, m_APInt(TrueC)) && match(FVal, m_APInt(FalseC)) &&
+      computeKnownBits(V, SQ.getWithInstruction(&SI)).countMaxActiveBits() <=
+          1) {
+    bool IsEqual = Pred == ICmpInst::ICMP_EQ;
+    bool CondIfZero = IsEqual == CmpC->isZero();
+    const APInt &ValueIfZero = CondIfZero ? *TrueC : *FalseC;
+    const APInt &ValueIfOne = CondIfZero ? *FalseC : *TrueC;
+    if (ValueIfOne == ValueIfZero + 1) {
+      bool HasNUW = !ValueIfZero.isMaxValue();
+      bool HasNSW = SI.getType()->getScalarSizeInBits() > 1 &&
+                    !ValueIfZero.isMaxSignedValue();
+      return Builder.CreateAdd(V, ConstantInt::get(V->getType(), ValueIfZero),
+                               "", HasNUW, HasNSW);
+    }
+  }
+
   // Fold icmp(X) ? f(X) : C to f(X) when f(X) is guaranteed to be equal to C
   // for all X in the exact range of the inverse predicate.
   Instruction *Op;
@@ -2404,7 +2426,7 @@ Instruction *InstCombinerImpl::foldSelectInstWithICmp(SelectInst &SI,
           canonicalizeSPF(*ICI, SI.getTrueValue(), SI.getFalseValue(), *this))
     return replaceInstUsesWith(SI, V);
 
-  if (Value *V = foldSelectInstWithICmpConst(SI, ICI, Builder))
+  if (Value *V = foldSelectInstWithICmpConst(SI, ICI, Builder, SQ))
     return replaceInstUsesWith(SI, V);
 
   if (Value *V = canonicalizeClampLike(SI, *ICI, Builder, *this))
diff --git a/llvm/test/Transforms/InstCombine/select.ll b/llvm/test/Transforms/InstCombine/select.ll
index a1e84fad9a827..48bb2d06e2e63 100644
--- a/llvm/test/Transforms/InstCombine/select.ll
+++ b/llvm/test/Transforms/InstCombine/select.ll
@@ -1635,6 +1635,147 @@ define i8 @assume_cond_false(i1 %cond, i8 %x, i8 %y) {
   ret i8 %sel
 }
 
+define i32 @select_eq_zero_known01_assume(i32 %a) {
+; CHECK-LABEL: define i32 @select_eq_zero_known01_assume(
+; CHECK-SAME: i32 [[A:%.*]]) {
+; CHECK-NEXT:    [[RANGE:%.*]] = icmp ult i32 [[A]], 2
+; CHECK-NEXT:    call void @llvm.assume(i1 [[RANGE]])
+; CHECK-NEXT:    [[SEL:%.*]] = add nuw nsw i32 [[A]], 1
+; CHECK-NEXT:    ret i32 [[SEL]]
+;
+  %range = icmp ult i32 %a, 2
+  call void @llvm.assume(i1 %range)
+  %iszero = icmp eq i32 %a, 0
+  %sel = select i1 %iszero, i32 1, i32 2
+  ret i32 %sel
+}
+
+define i32 @select_ne_zero_known01_assume(i32 %a) {
+; CHECK-LABEL: define i32 @select_ne_zero_known01_assume(
+; CHECK-SAME: i32 [[A:%.*]]) {
+; CHECK-NEXT:    [[RANGE:%.*]] = icmp ult i32 [[A]], 2
+; CHECK-NEXT:    call void @llvm.assume(i1 [[RANGE]])
+; CHECK-NEXT:    [[SEL:%.*]] = add nuw nsw i32 [[A]], 1
+; CHECK-NEXT:    ret i32 [[SEL]]
+;
+  %range = icmp ult i32 %a, 2
+  call void @llvm.assume(i1 %range)
+  %isnonzero = icmp ne i32 %a, 0
+  %sel = select i1 %isnonzero, i32 2, i32 1
+  ret i32 %sel
+}
+
+define i32 @select_eq_one_known01_assume(i32 %a) {
+; CHECK-LABEL: define i32 @select_eq_one_known01_assume(
+; CHECK-SAME: i32 [[A:%.*]]) {
+; CHECK-NEXT:    [[RANGE:%.*]] = icmp ult i32 [[A]], 2
+; CHECK-NEXT:    call void @llvm.assume(i1 [[RANGE]])
+; CHECK-NEXT:    [[SEL:%.*]] = add nuw nsw i32 [[A]], 1
+; CHECK-NEXT:    ret i32 [[SEL]]
+;
+  %range = icmp ult i32 %a, 2
+  call void @llvm.assume(i1 %range)
+  %isone = icmp eq i32 %a, 1
+  %sel = select i1 %isone, i32 2, i32 1
+  ret i32 %sel
+}
+
+define i8 @select_eq_zero_known01_mask(i8 %x) {
+; CHECK-LABEL: define i8 @select_eq_zero_known01_mask(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT:    [[X01:%.*]] = and i8 [[X]], 1
+; CHECK-NEXT:    [[SEL:%.*]] = or disjoint i8 [[X01]], 42
+; CHECK-NEXT:    ret i8 [[SEL]]
+;
+  %x01 = and i8 %x, 1
+  %iszero = icmp eq i8 %x01, 0
+  %sel = select i1 %iszero, i8 42, i8 43
+  ret i8 %sel
+}
+
+define <2 x i8> @select_eq_zero_known01_mask_vec(<2 x i8> %x) {
+; CHECK-LABEL: define <2 x i8> @select_eq_zero_known01_mask_vec(
+; CHECK-SAME: <2 x i8> [[X:%.*]]) {
+; CHECK-NEXT:    [[X01:%.*]] = and <2 x i8> [[X]], splat (i8 1)
+; CHECK-NEXT:    [[SEL:%.*]] = or disjoint <2 x i8> [[X01]], splat (i8 42)
+; CHECK-NEXT:    ret <2 x i8> [[SEL]]
+;
+  %x01 = and <2 x i8> %x, splat (i8 1)
+  %iszero = icmp eq <2 x i8> %x01, zeroinitializer
+  %sel = select <2 x i1> %iszero, <2 x i8> splat (i8 42), <2 x i8> splat (i8 43)
+  ret <2 x i8> %sel
+}
+
+define i8 @select_eq_zero_known01_signed_max(i8 %x) {
+; CHECK-LABEL: define i8 @select_eq_zero_known01_signed_max(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT:    [[X01:%.*]] = and i8 [[X]], 1
+; CHECK-NEXT:    [[SEL:%.*]] = add nuw i8 [[X01]], 127
+; CHECK-NEXT:    ret i8 [[SEL]]
+;
+  %x01 = and i8 %x, 1
+  %iszero = icmp eq i8 %x01, 0
+  %sel = select i1 %iszero, i8 127, i8 -128
+  ret i8 %sel
+}
+
+define i8 @select_eq_zero_known01_unsigned_wrap(i8 %x) {
+; CHECK-LABEL: define i8 @select_eq_zero_known01_unsigned_wrap(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT:    [[X01:%.*]] = and i8 [[X]], 1
+; CHECK-NEXT:    [[SEXT:%.*]] = add nsw i8 [[X01]], -1
+; CHECK-NEXT:    ret i8 [[SEXT]]
+;
+  %x01 = and i8 %x, 1
+  %iszero = icmp eq i8 %x01, 0
+  %sel = select i1 %iszero, i8 -1, i8 0
+  ret i8 %sel
+}
+
+define i32 @select_eq_zero_unknown(i32 %a) {
+; CHECK-LABEL: define i32 @select_eq_zero_unknown(
+; CHECK-SAME: i32 [[A:%.*]]) {
+; CHECK-NEXT:    [[ISZERO:%.*]] = icmp eq i32 [[A]], 0
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[ISZERO]], i32 1, i32 2
+; CHECK-NEXT:    ret i32 [[SEL]]
+;
+  %iszero = icmp eq i32 %a, 0
+  %sel = select i1 %iszero, i32 1, i32 2
+  ret i32 %sel
+}
+
+define i32 @select_eq_zero_known01_not_adjacent(i32 %a) {
+; CHECK-LABEL: define i32 @select_eq_zero_known01_not_adjacent(
+; CHECK-SAME: i32 [[A:%.*]]) {
+; CHECK-NEXT:    [[RANGE:%.*]] = icmp ult i32 [[A]], 2
+; CHECK-NEXT:    call void @llvm.assume(i1 [[RANGE]])
+; CHECK-NEXT:    [[ISZERO:%.*]] = icmp eq i32 [[A]], 0
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[ISZERO]], i32 1, i32 3
+; CHECK-NEXT:    ret i32 [[SEL]]
+;
+  %range = icmp ult i32 %a, 2
+  call void @llvm.assume(i1 %range)
+  %iszero = icmp eq i32 %a, 0
+  %sel = select i1 %iszero, i32 1, i32 3
+  ret i32 %sel
+}
+
+define i32 @select_eq_zero_known01_reversed(i32 %a) {
+; CHECK-LABEL: define i32 @select_eq_zero_known01_reversed(
+; CHECK-SAME: i32 [[A:%.*]]) {
+; CHECK-NEXT:    [[RANGE:%.*]] = icmp ult i32 [[A]], 2
+; CHECK-NEXT:    call void @llvm.assume(i1 [[RANGE]])
+; CHECK-NEXT:    [[ISZERO:%.*]] = icmp eq i32 [[A]], 0
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[ISZERO]], i32 2, i32 1
+; CHECK-NEXT:    ret i32 [[SEL]]
+;
+  %range = icmp ult i32 %a, 2
+  call void @llvm.assume(i1 %range)
+  %iszero = icmp eq i32 %a, 0
+  %sel = select i1 %iszero, i32 2, i32 1
+  ret i32 %sel
+}
+
 ; Test case to make sure we don't consider an all ones float values for converting the select into a sext.
 define <4 x float> @PR33721(<4 x float> %w) {
 ; CHECK-LABEL: define <4 x float> @PR33721(

``````````

</details>


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


More information about the llvm-commits mailing list