[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