[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