[llvm] [InstCombine] Fold sub(x, select(icmp ult/eq (x & Mask), C, add(x & Mask, P), x & Mask)) to and(sub(x, C), -P) (PR #201469)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 3 15:49:10 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Aayush Shrivastava (iamaayushrivastava)
<details>
<summary>Changes</summary>
Fixes #<!-- -->201328
This PR implements a missed optimization in InstCombine where a subtraction of a select-based alignment adjustment can be reduced to a subtract-and-mask. Two new folds are added at the end of `visitSub` in `InstCombineAddSub.cpp`.
The select-based fold handles both the `icmp eq` form (equivalent to C = 1) and the general `icmp ult` form (C ≤ P), transforming `sub x, select(icmp ult (x & Mask), C, add(x & Mask, P), x & Mask)` into `and(sub(x, C), ~Mask)`. The add-like fold handles the degenerate C = P case, where the always-true icmp ult causes a prior pass to eliminate the select, leaving a bare `sub x, add_like(x & Mask, P)`.
---
Full diff: https://github.com/llvm/llvm-project/pull/201469.diff
2 Files Affected:
- (modified) llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp (+60)
- (added) llvm/test/Transforms/InstCombine/sub-and-csel.ll (+223)
``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index 8c0dcc8029a1e..c1d9ca6a1a502 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -3041,6 +3041,66 @@ Instruction *InstCombinerImpl::visitSub(BinaryOperator &I) {
}
}
+ // Fold:
+ // sub x, select(icmp eq (x & Mask), P, (x & Mask))
+ // --> and (sub x, 1), ~Mask
+ // sub x, select(icmp ult (x & Mask), C, add((x & Mask), P), (x & Mask))
+ // --> and (sub x, C), ~Mask
+ // where P = Mask+1 is a power of 2 and C <= P.
+ // This rounds x down to the nearest multiple of P, adjusting the alignment
+ // threshold by C.
+ {
+ CmpPredicate Pred;
+ Value *R;
+ const APInt *MaskC, *CmpC;
+ Value *SelTrue;
+ if (match(Op1, m_OneUse(m_Select(m_ICmp(Pred, m_Value(R), m_APInt(CmpC)),
+ m_Value(SelTrue), m_Deferred(R)))) &&
+ match(R, m_And(m_Specific(Op0), m_APInt(MaskC)))) {
+ APInt P = *MaskC + 1;
+ APInt C;
+ bool Matched = false;
+ if (P.isPowerOf2()) {
+ if (Pred == ICmpInst::ICMP_EQ && CmpC->isZero()) {
+ // sub x, select(icmp eq (x & Mask), 0, P, x & Mask) --> C=1 case
+ const APInt *SelTrueC;
+ if (match(SelTrue, m_APInt(SelTrueC)) && *SelTrueC == P) {
+ C = APInt(P.getBitWidth(), 1);
+ Matched = true;
+ }
+ } else if (Pred == ICmpInst::ICMP_ULT && CmpC->ule(P)) {
+ // sub x, select(icmp ult (x & Mask), C, add(x & Mask, P), x & Mask)
+ // Note: add may be canonicalized to or-disjoint, so use m_AddLike.
+ const APInt *AddC;
+ if (match(SelTrue, m_AddLike(m_Specific(R), m_APInt(AddC))) &&
+ *AddC == P) {
+ C = *CmpC;
+ Matched = true;
+ }
+ }
+ }
+ if (Matched) {
+ Value *Sub = Builder.CreateSub(Op0, ConstantInt::get(Ty, C));
+ return BinaryOperator::CreateAnd(Sub, ConstantInt::get(Ty, ~(*MaskC)));
+ }
+ }
+ }
+ // Fold: sub x, add_like(x & Mask, P) --> and(sub(x, P), ~Mask)
+ // where P = Mask+1 is a power of 2. This handles the case where the
+ // select was simplified away (icmp ult (x & Mask), P is always true).
+ {
+ Value *R;
+ const APInt *MaskC, *AddC;
+ if (match(Op1, m_OneUse(m_AddLike(m_Value(R), m_APInt(AddC)))) &&
+ match(R, m_And(m_Specific(Op0), m_APInt(MaskC)))) {
+ APInt P = *MaskC + 1;
+ if (P.isPowerOf2() && *AddC == P) {
+ Value *Sub = Builder.CreateSub(Op0, ConstantInt::get(Ty, P));
+ return BinaryOperator::CreateAnd(Sub, ConstantInt::get(Ty, ~(*MaskC)));
+ }
+ }
+ }
+
return TryToNarrowDeduceFlags();
}
diff --git a/llvm/test/Transforms/InstCombine/sub-and-csel.ll b/llvm/test/Transforms/InstCombine/sub-and-csel.ll
new file mode 100644
index 0000000000000..9fb9564606426
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/sub-and-csel.ll
@@ -0,0 +1,223 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; Test folding:
+; sub x, select(icmp eq (x & Mask), P, (x & Mask)) --> and (sub x, 1), ~Mask
+; sub x, select(icmp ult (x & Mask), C, add((x & Mask), P), (x & Mask))
+; --> and (sub x, C), ~Mask
+; where P = Mask+1 is a power of 2 and C <= P.
+
+; Specific case from the issue: icmp eq form (C=1 implicit), P=32
+define i8 @test_eq_zero(i8 %x) {
+; CHECK-LABEL: @test_eq_zero(
+; CHECK-NEXT: [[TMP1:%.*]] = add i8 [[X:%.*]], -1
+; CHECK-NEXT: [[RES:%.*]] = and i8 [[TMP1]], -32
+; CHECK-NEXT: ret i8 [[RES]]
+;
+ %rem = and i8 %x, 31
+ %is_zero = icmp eq i8 %rem, 0
+ %sel = select i1 %is_zero, i8 32, i8 %rem
+ %res = sub i8 %x, %sel
+ ret i8 %res
+}
+
+; General case: icmp ult with C=5, P=32
+define i8 @test_ult_c5(i8 %x) {
+; CHECK-LABEL: @test_ult_c5(
+; CHECK-NEXT: [[TMP1:%.*]] = add i8 [[X:%.*]], -5
+; CHECK-NEXT: [[RES:%.*]] = and i8 [[TMP1]], -32
+; CHECK-NEXT: ret i8 [[RES]]
+;
+ %r = and i8 %x, 31
+ %cmp = icmp ult i8 %r, 5
+ %r_plus_p = add i8 %r, 32
+ %sel = select i1 %cmp, i8 %r_plus_p, i8 %r
+ %res = sub i8 %x, %sel
+ ret i8 %res
+}
+
+; P=32, C=1 via icmp ult
+define i8 @test_ult_c1(i8 %x) {
+; CHECK-LABEL: @test_ult_c1(
+; CHECK-NEXT: [[TMP1:%.*]] = add i8 [[X:%.*]], -1
+; CHECK-NEXT: [[RES:%.*]] = and i8 [[TMP1]], -32
+; CHECK-NEXT: ret i8 [[RES]]
+;
+ %r = and i8 %x, 31
+ %cmp = icmp ult i8 %r, 1
+ %r_plus_p = add i8 %r, 32
+ %sel = select i1 %cmp, i8 %r_plus_p, i8 %r
+ %res = sub i8 %x, %sel
+ ret i8 %res
+}
+
+; P=16 (Mask=15), C=3
+define i8 @test_p16_c3(i8 %x) {
+; CHECK-LABEL: @test_p16_c3(
+; CHECK-NEXT: [[TMP1:%.*]] = add i8 [[X:%.*]], -3
+; CHECK-NEXT: [[RES:%.*]] = and i8 [[TMP1]], -16
+; CHECK-NEXT: ret i8 [[RES]]
+;
+ %r = and i8 %x, 15
+ %cmp = icmp ult i8 %r, 3
+ %r_plus_p = add i8 %r, 16
+ %sel = select i1 %cmp, i8 %r_plus_p, i8 %r
+ %res = sub i8 %x, %sel
+ ret i8 %res
+}
+
+; i32 icmp eq case
+define i32 @test_eq_zero_i32(i32 %x) {
+; CHECK-LABEL: @test_eq_zero_i32(
+; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X:%.*]], -1
+; CHECK-NEXT: [[RES:%.*]] = and i32 [[TMP1]], -32
+; CHECK-NEXT: ret i32 [[RES]]
+;
+ %rem = and i32 %x, 31
+ %is_zero = icmp eq i32 %rem, 0
+ %sel = select i1 %is_zero, i32 32, i32 %rem
+ %res = sub i32 %x, %sel
+ ret i32 %res
+}
+
+; i32 icmp ult case
+define i32 @test_ult_c5_i32(i32 %x) {
+; CHECK-LABEL: @test_ult_c5_i32(
+; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X:%.*]], -5
+; CHECK-NEXT: [[RES:%.*]] = and i32 [[TMP1]], -32
+; CHECK-NEXT: ret i32 [[RES]]
+;
+ %r = and i32 %x, 31
+ %cmp = icmp ult i32 %r, 5
+ %r_plus_p = add i32 %r, 32
+ %sel = select i1 %cmp, i32 %r_plus_p, i32 %r
+ %res = sub i32 %x, %sel
+ ret i32 %res
+}
+
+; Vector version of the icmp eq case
+define <4 x i32> @test_vec_eq(<4 x i32> %x) {
+; CHECK-LABEL: @test_vec_eq(
+; CHECK-NEXT: [[TMP1:%.*]] = add <4 x i32> [[X:%.*]], splat (i32 -1)
+; CHECK-NEXT: [[RES:%.*]] = and <4 x i32> [[TMP1]], splat (i32 -32)
+; CHECK-NEXT: ret <4 x i32> [[RES]]
+;
+ %rem = and <4 x i32> %x, splat (i32 31)
+ %is_zero = icmp eq <4 x i32> %rem, zeroinitializer
+ %sel = select <4 x i1> %is_zero, <4 x i32> splat (i32 32), <4 x i32> %rem
+ %res = sub <4 x i32> %x, %sel
+ ret <4 x i32> %res
+}
+
+; Multi-use of r: only select must be single-use
+declare void @use(i8)
+define i8 @test_multiuse_r(i8 %x) {
+; CHECK-LABEL: @test_multiuse_r(
+; CHECK-NEXT: [[R:%.*]] = and i8 [[X:%.*]], 31
+; CHECK-NEXT: call void @use(i8 [[R]])
+; CHECK-NEXT: [[TMP1:%.*]] = add i8 [[X]], -1
+; CHECK-NEXT: [[RES:%.*]] = and i8 [[TMP1]], -32
+; CHECK-NEXT: ret i8 [[RES]]
+;
+ %r = and i8 %x, 31
+ call void @use(i8 %r)
+ %is_zero = icmp eq i8 %r, 0
+ %sel = select i1 %is_zero, i8 32, i8 %r
+ %res = sub i8 %x, %sel
+ ret i8 %res
+}
+
+; C=0 degenerate case: icmp ult r, 0 is always false, select = r, sub = x - r = x & ~mask
+define i8 @test_ult_c0(i8 %x) {
+; CHECK-LABEL: @test_ult_c0(
+; CHECK-NEXT: [[RES:%.*]] = and i8 [[X:%.*]], -32
+; CHECK-NEXT: ret i8 [[RES]]
+;
+ %r = and i8 %x, 31
+ %cmp = icmp ult i8 %r, 0
+ %r_plus_p = add i8 %r, 32
+ %sel = select i1 %cmp, i8 %r_plus_p, i8 %r
+ %res = sub i8 %x, %sel
+ ret i8 %res
+}
+
+; C=P degenerate case: icmp ult r, 32 is always true (r in [0,31]), handled via add-like pattern
+define i8 @test_ult_c_eq_p(i8 %x) {
+; CHECK-LABEL: @test_ult_c_eq_p(
+; CHECK-NEXT: [[TMP1:%.*]] = and i8 [[X:%.*]], -32
+; CHECK-NEXT: [[RES:%.*]] = add i8 [[TMP1]], -32
+; CHECK-NEXT: ret i8 [[RES]]
+;
+ %r = and i8 %x, 31
+ %cmp = icmp ult i8 %r, 32
+ %r_plus_p = add i8 %r, 32
+ %sel = select i1 %cmp, i8 %r_plus_p, i8 %r
+ %res = sub i8 %x, %sel
+ ret i8 %res
+}
+
+; Negative: select has multiple uses -- should NOT fold
+define i8 @test_neg_multiuse_sel(i8 %x) {
+; CHECK-LABEL: @test_neg_multiuse_sel(
+; CHECK-NEXT: [[REM:%.*]] = and i8 [[X:%.*]], 31
+; CHECK-NEXT: [[IS_ZERO:%.*]] = icmp eq i8 [[REM]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[IS_ZERO]], i8 32, i8 [[REM]]
+; CHECK-NEXT: call void @use(i8 [[SEL]])
+; CHECK-NEXT: [[RES:%.*]] = sub i8 [[X]], [[SEL]]
+; CHECK-NEXT: ret i8 [[RES]]
+;
+ %rem = and i8 %x, 31
+ %is_zero = icmp eq i8 %rem, 0
+ %sel = select i1 %is_zero, i8 32, i8 %rem
+ call void @use(i8 %sel)
+ %res = sub i8 %x, %sel
+ ret i8 %res
+}
+
+; Negative: select false arm is not r -- should NOT fold
+define i8 @test_neg_wrong_false_arm(i8 %x, i8 %y) {
+; CHECK-LABEL: @test_neg_wrong_false_arm(
+; CHECK-NEXT: [[R:%.*]] = and i8 [[X:%.*]], 31
+; CHECK-NEXT: [[IS_ZERO:%.*]] = icmp eq i8 [[R]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[IS_ZERO]], i8 32, i8 [[Y:%.*]]
+; CHECK-NEXT: [[RES:%.*]] = sub i8 [[X]], [[SEL]]
+; CHECK-NEXT: ret i8 [[RES]]
+;
+ %r = and i8 %x, 31
+ %is_zero = icmp eq i8 %r, 0
+ %sel = select i1 %is_zero, i8 32, i8 %y
+ %res = sub i8 %x, %sel
+ ret i8 %res
+}
+
+; Negative: P is not a power of 2 (Mask=30 gives P=31) -- should NOT fold
+define i8 @test_neg_nonpow2(i8 %x) {
+; CHECK-LABEL: @test_neg_nonpow2(
+; CHECK-NEXT: [[REM:%.*]] = and i8 [[X:%.*]], 30
+; CHECK-NEXT: [[IS_ZERO:%.*]] = icmp eq i8 [[REM]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[IS_ZERO]], i8 31, i8 [[REM]]
+; CHECK-NEXT: [[RES:%.*]] = sub i8 [[X]], [[SEL]]
+; CHECK-NEXT: ret i8 [[RES]]
+;
+ %rem = and i8 %x, 30
+ %is_zero = icmp eq i8 %rem, 0
+ %sel = select i1 %is_zero, i8 31, i8 %rem
+ %res = sub i8 %x, %sel
+ ret i8 %res
+}
+
+; Negative: select true arm constant doesn't match P (should be 32, not 33)
+define i8 @test_neg_wrong_true_val(i8 %x) {
+; CHECK-LABEL: @test_neg_wrong_true_val(
+; CHECK-NEXT: [[REM:%.*]] = and i8 [[X:%.*]], 31
+; CHECK-NEXT: [[IS_ZERO:%.*]] = icmp eq i8 [[REM]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[IS_ZERO]], i8 33, i8 [[REM]]
+; CHECK-NEXT: [[RES:%.*]] = sub i8 [[X]], [[SEL]]
+; CHECK-NEXT: ret i8 [[RES]]
+;
+ %rem = and i8 %x, 31
+ %is_zero = icmp eq i8 %rem, 0
+ %sel = select i1 %is_zero, i8 33, i8 %rem
+ %res = sub i8 %x, %sel
+ ret i8 %res
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/201469
More information about the llvm-commits
mailing list