[llvm] [InstCombine] Fold and (or (shr (sub C, X), ShAmt), X), C --> umin(X, C) (PR #212798)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 29 08:38:50 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Marina Taylor (citymarina)
<details>
<summary>Changes</summary>
This pattern was seen in some downstream workloads.
https://alive2.llvm.org/ce/z/_hrkqz
Assisted-by: claude
---
Full diff: https://github.com/llvm/llvm-project/pull/212798.diff
2 Files Affected:
- (modified) llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp (+25)
- (added) llvm/test/Transforms/InstCombine/and-or-shr-sub-umin.ll (+315)
``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index b6f4a55c07e8a..2878adfcd1fef 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -2673,6 +2673,31 @@ Instruction *InstCombinerImpl::visitAnd(BinaryOperator &I) {
}
}
}
+
+ // and (or (shr (sub C, X), ShAmt), X), C --> umin(X, C)
+ // The fold holds when all of:
+ // - C is a low-bit mask: C = (1<<S)-1
+ // - S u<= ShAmt -- When X u<= C, sub(C, X) u< 1<<ShAmt, so shr is 0
+ // - ShAmt + S u<= BW -- the borrow's all-ones run reaches up to bit S-1
+ // - X u< 1<<ShAmt -- bounds X - C u< 1<<ShAmt in the X u> C case
+ // Then shr(sub(C, X), ShAmt) is 0 when X u<= C and has all of C's bits set
+ // when X u> C, so (shr | X) & C clamps X to C. The bound on X also means
+ // ashr and lshr have the same result.
+ const APInt *ShAmtC;
+ if (C->isMask() &&
+ match(Op0,
+ m_OneUse(m_c_Or(
+ m_OneUse(m_Shr(m_OneUse(m_Sub(m_SpecificInt(*C), m_Value(X))),
+ m_APInt(ShAmtC))),
+ m_Deferred(X)))) &&
+ ShAmtC->ult(Width)) {
+ unsigned S = C->popcount();
+ unsigned ShAmt = ShAmtC->getZExtValue();
+ if (S <= ShAmt && ShAmt + S <= Width &&
+ MaskedValueIsZero(X, APInt::getBitsSetFrom(Width, ShAmt), &I))
+ return replaceInstUsesWith(
+ I, Builder.CreateBinaryIntrinsic(Intrinsic::umin, X, Op1));
+ }
}
// If we are clearing the sign bit of a floating-point value, convert this to
diff --git a/llvm/test/Transforms/InstCombine/and-or-shr-sub-umin.ll b/llvm/test/Transforms/InstCombine/and-or-shr-sub-umin.ll
new file mode 100644
index 0000000000000..19db76216660b
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/and-or-shr-sub-umin.ll
@@ -0,0 +1,315 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; Fold:
+; (or (shr (sub C, X), ShAmt), X) & C --> umin(X, C)
+; where C = (1<<S)-1, S u<= ShAmt, ShAmt + S u<= BW, and X u< 1<<ShAmt.
+
+; Positive cases
+
+; Baseline: i32, C = 255, ShAmt = 23.
+define i32 @umin_i32_lshr(i32 range(i32 0, 8388608) %x) {
+; CHECK-LABEL: define i32 @umin_i32_lshr(
+; CHECK-SAME: i32 range(i32 0, 8388608) [[X:%.*]]) {
+; CHECK-NEXT: [[AND:%.*]] = call i32 @llvm.umin.i32(i32 [[X]], i32 255)
+; CHECK-NEXT: ret i32 [[AND]]
+;
+ %sub = sub i32 255, %x
+ %shr = lshr i32 %sub, 23
+ %or = or i32 %shr, %x
+ %and = and i32 %or, 255
+ ret i32 %and
+}
+
+; ashr form should also fold (precondition makes lshr/ashr equivalent).
+define i32 @umin_i32_ashr(i32 range(i32 0, 8388608) %x) {
+; CHECK-LABEL: define i32 @umin_i32_ashr(
+; CHECK-SAME: i32 range(i32 0, 8388608) [[X:%.*]]) {
+; CHECK-NEXT: [[AND:%.*]] = call i32 @llvm.umin.i32(i32 [[X]], i32 255)
+; CHECK-NEXT: ret i32 [[AND]]
+;
+ %sub = sub i32 255, %x
+ %shr = ashr i32 %sub, 23
+ %or = or i32 %shr, %x
+ %and = and i32 %or, 255
+ ret i32 %and
+}
+
+; Pattern with the `or` commuted.
+define i32 @umin_i32_or_commuted(i32 range(i32 0, 8388608) %x) {
+; CHECK-LABEL: define i32 @umin_i32_or_commuted(
+; CHECK-SAME: i32 range(i32 0, 8388608) [[X:%.*]]) {
+; CHECK-NEXT: [[AND:%.*]] = call i32 @llvm.umin.i32(i32 [[X]], i32 255)
+; CHECK-NEXT: ret i32 [[AND]]
+;
+ %sub = sub i32 255, %x
+ %shr = lshr i32 %sub, 23
+ %or = or i32 %x, %shr
+ %and = and i32 %or, 255
+ ret i32 %and
+}
+
+; i8, C = 7, ShAmt = 4.
+define i8 @umin_i8(i8 range(i8 0, 16) %x) {
+; CHECK-LABEL: define i8 @umin_i8(
+; CHECK-SAME: i8 range(i8 0, 16) [[X:%.*]]) {
+; CHECK-NEXT: [[AND:%.*]] = call i8 @llvm.umin.i8(i8 [[X]], i8 7)
+; CHECK-NEXT: ret i8 [[AND]]
+;
+ %sub = sub i8 7, %x
+ %shr = lshr i8 %sub, 4
+ %or = or i8 %shr, %x
+ %and = and i8 %or, 7
+ ret i8 %and
+}
+
+; Tight boundary on S=15, with ShAmt=16.
+define i32 @umin_tight_boundary(i32 range(i32 0, 65536) %x) {
+; CHECK-LABEL: define i32 @umin_tight_boundary(
+; CHECK-SAME: i32 range(i32 0, 65536) [[X:%.*]]) {
+; CHECK-NEXT: [[AND:%.*]] = call i32 @llvm.umin.i32(i32 [[X]], i32 32767)
+; CHECK-NEXT: ret i32 [[AND]]
+;
+ %sub = sub i32 32767, %x
+ %shr = lshr i32 %sub, 16
+ %or = or i32 %shr, %x
+ %and = and i32 %or, 32767
+ ret i32 %and
+}
+
+; Boundary where the X u< 1<<ShAmt bound coincides with C+1 (i33, S=16,
+; ShAmt=16): X u< 2^16 == C+1 means X u<= C always, so umin(X, C) collapses
+; to X. Validates the fold's boundary behavior even though the umin is trivial.
+define i33 @umin_tight_boundary_i33(i33 range(i33 0, 65536) %x) {
+; CHECK-LABEL: define i33 @umin_tight_boundary_i33(
+; CHECK-SAME: i33 range(i33 0, 65536) [[X:%.*]]) {
+; CHECK-NEXT: ret i33 [[X]]
+;
+ %sub = sub i33 65535, %x
+ %shr = lshr i33 %sub, 16
+ %or = or i33 %shr, %x
+ %and = and i33 %or, 65535
+ ret i33 %and
+}
+
+; One step below the tight boundary on an odd BW (i33, S=15, ShAmt=17):
+; precondition X u< 2^17 admits X u> C, so umin(X, C) is non-trivial and the
+; fold produces a real umin call.
+define i33 @umin_near_tight_i33(i33 range(i33 0, 131072) %x) {
+; CHECK-LABEL: define i33 @umin_near_tight_i33(
+; CHECK-SAME: i33 range(i33 0, 131072) [[X:%.*]]) {
+; CHECK-NEXT: [[AND:%.*]] = call i33 @llvm.umin.i33(i33 [[X]], i33 32767)
+; CHECK-NEXT: ret i33 [[AND]]
+;
+ %sub = sub i33 32767, %x
+ %shr = lshr i33 %sub, 17
+ %or = or i33 %shr, %x
+ %and = and i33 %or, 32767
+ ret i33 %and
+}
+
+; Vector splat.
+define <2 x i32> @umin_v2i32(<2 x i32> range(i32 0, 8388608) %x) {
+; CHECK-LABEL: define <2 x i32> @umin_v2i32(
+; CHECK-SAME: <2 x i32> range(i32 0, 8388608) [[X:%.*]]) {
+; CHECK-NEXT: [[AND:%.*]] = call <2 x i32> @llvm.umin.v2i32(<2 x i32> [[X]], <2 x i32> splat (i32 255))
+; CHECK-NEXT: ret <2 x i32> [[AND]]
+;
+ %sub = sub <2 x i32> splat (i32 255), %x
+ %shr = lshr <2 x i32> %sub, splat (i32 23)
+ %or = or <2 x i32> %shr, %x
+ %and = and <2 x i32> %or, splat (i32 255)
+ ret <2 x i32> %and
+}
+
+; Generalized ShAmt: C=255 (S=8) but ShAmt=20, which is < BW-S-1 = 23.
+define i32 @umin_i32_shamt_below_max(i32 range(i32 0, 1048576) %x) {
+; CHECK-LABEL: define i32 @umin_i32_shamt_below_max(
+; CHECK-SAME: i32 range(i32 0, 1048576) [[X:%.*]]) {
+; CHECK-NEXT: [[AND:%.*]] = call i32 @llvm.umin.i32(i32 [[X]], i32 255)
+; CHECK-NEXT: ret i32 [[AND]]
+;
+ %sub = sub i32 255, %x
+ %shr = lshr i32 %sub, 20
+ %or = or i32 %shr, %x
+ %and = and i32 %or, 255
+ ret i32 %and
+}
+
+; Negative cases
+
+; S > ShAmt: C=0xFFFF (S=16), ShAmt=15, so S u<= ShAmt fails. sub(C, X) can be
+; as large as C = 0xFFFF >= 1<<ShAmt, so the shift is nonzero for X u<= C and
+; the "below-mask" case produces X | 1 instead of X.
+define i32 @neg_past_boundary(i32 range(i32 0, 32768) %x) {
+; CHECK-LABEL: define i32 @neg_past_boundary(
+; CHECK-SAME: i32 range(i32 0, 32768) [[X:%.*]]) {
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[X]], 1
+; CHECK-NEXT: ret i32 [[OR]]
+;
+ %sub = sub i32 65535, %x
+ %shr = lshr i32 %sub, 15
+ %or = or i32 %shr, %x
+ %and = and i32 %or, 65535
+ ret i32 %and
+}
+
+; X not known to be bounded.
+define i32 @neg_unbounded(i32 %x) {
+; CHECK-LABEL: define i32 @neg_unbounded(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[SUB:%.*]] = sub i32 255, [[X]]
+; CHECK-NEXT: [[SHR:%.*]] = lshr i32 [[SUB]], 23
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[SHR]], [[X]]
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[OR]], 255
+; CHECK-NEXT: ret i32 [[AND]]
+;
+ %sub = sub i32 255, %x
+ %shr = lshr i32 %sub, 23
+ %or = or i32 %shr, %x
+ %and = and i32 %or, 255
+ ret i32 %and
+}
+
+; ShAmt=22 is structurally fine (S=8 <= 22, 22+8 <= 32), and with X u< 2^22
+; the fold fires. This pins the ShAmt-coupled precondition: the bound on X is
+; 1<<ShAmt, not a fixed quantity.
+define i32 @umin_i32_shamt22_in_range(i32 range(i32 0, 4194304) %x) {
+; CHECK-LABEL: define i32 @umin_i32_shamt22_in_range(
+; CHECK-SAME: i32 range(i32 0, 4194304) [[X:%.*]]) {
+; CHECK-NEXT: [[AND:%.*]] = call i32 @llvm.umin.i32(i32 [[X]], i32 255)
+; CHECK-NEXT: ret i32 [[AND]]
+;
+ %sub = sub i32 255, %x
+ %shr = lshr i32 %sub, 22
+ %or = or i32 %shr, %x
+ %and = and i32 %or, 255
+ ret i32 %and
+}
+
+; Same ShAmt=22, but X is only known u< 2^23, so X u< 1<<ShAmt = 2^22 fails and
+; the fold is correctly rejected.
+define i32 @neg_range_exceeds_shamt(i32 range(i32 0, 8388608) %x) {
+; CHECK-LABEL: define i32 @neg_range_exceeds_shamt(
+; CHECK-SAME: i32 range(i32 0, 8388608) [[X:%.*]]) {
+; CHECK-NEXT: [[SUB:%.*]] = sub nsw i32 255, [[X]]
+; CHECK-NEXT: [[SHR:%.*]] = lshr i32 [[SUB]], 22
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[SHR]], [[X]]
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[OR]], 255
+; CHECK-NEXT: ret i32 [[AND]]
+;
+ %sub = sub i32 255, %x
+ %shr = lshr i32 %sub, 22
+ %or = or i32 %shr, %x
+ %and = and i32 %or, 255
+ ret i32 %and
+}
+
+; ShAmt + S > BW: C=255 (S=8), ShAmt=25, so 25+8 = 33 > 32. Even with X u< 2^25
+; the borrow's all-ones run does not reach bit S-1, so the fold is invalid.
+define i32 @neg_shamt_plus_s_exceeds_bw(i32 range(i32 0, 33554432) %x) {
+; CHECK-LABEL: define i32 @neg_shamt_plus_s_exceeds_bw(
+; CHECK-SAME: i32 range(i32 0, 33554432) [[X:%.*]]) {
+; CHECK-NEXT: [[SUB:%.*]] = sub nsw i32 255, [[X]]
+; CHECK-NEXT: [[SHR:%.*]] = lshr i32 [[SUB]], 25
+; CHECK-NEXT: [[X_MASKED:%.*]] = and i32 [[X]], 255
+; CHECK-NEXT: [[AND:%.*]] = or i32 [[SHR]], [[X_MASKED]]
+; CHECK-NEXT: ret i32 [[AND]]
+;
+ %sub = sub i32 255, %x
+ %shr = lshr i32 %sub, 25
+ %or = or i32 %shr, %x
+ %and = and i32 %or, 255
+ ret i32 %and
+}
+
+; AND constant differs from SUB constant.
+define i32 @neg_mismatched_constants(i32 range(i32 0, 8388608) %x) {
+; CHECK-LABEL: define i32 @neg_mismatched_constants(
+; CHECK-SAME: i32 range(i32 0, 8388608) [[X:%.*]]) {
+; CHECK-NEXT: [[SUB:%.*]] = sub nsw i32 127, [[X]]
+; CHECK-NEXT: [[SHR:%.*]] = lshr i32 [[SUB]], 23
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[SHR]], [[X]]
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[OR]], 255
+; CHECK-NEXT: ret i32 [[AND]]
+;
+ %sub = sub i32 127, %x
+ %shr = lshr i32 %sub, 23
+ %or = or i32 %shr, %x
+ %and = and i32 %or, 255
+ ret i32 %and
+}
+
+; C is not of the form 2^S - 1.
+define i32 @neg_non_mask(i32 range(i32 0, 8388608) %x) {
+; CHECK-LABEL: define i32 @neg_non_mask(
+; CHECK-SAME: i32 range(i32 0, 8388608) [[X:%.*]]) {
+; CHECK-NEXT: [[SUB:%.*]] = sub nsw i32 254, [[X]]
+; CHECK-NEXT: [[SHR:%.*]] = lshr i32 [[SUB]], 23
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[SHR]], [[X]]
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[OR]], 254
+; CHECK-NEXT: ret i32 [[AND]]
+;
+ %sub = sub i32 254, %x
+ %shr = lshr i32 %sub, 23
+ %or = or i32 %shr, %x
+ %and = and i32 %or, 254
+ ret i32 %and
+}
+
+; Multi-use on the `sub`: fold is not profitable.
+define i32 @neg_multiuse_sub(i32 range(i32 0, 8388608) %x, ptr %sink) {
+; CHECK-LABEL: define i32 @neg_multiuse_sub(
+; CHECK-SAME: i32 range(i32 0, 8388608) [[X:%.*]], ptr [[SINK:%.*]]) {
+; CHECK-NEXT: [[SUB:%.*]] = sub nsw i32 255, [[X]]
+; CHECK-NEXT: store i32 [[SUB]], ptr [[SINK]], align 4
+; CHECK-NEXT: [[SHR:%.*]] = lshr i32 [[SUB]], 23
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[SHR]], [[X]]
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[OR]], 255
+; CHECK-NEXT: ret i32 [[AND]]
+;
+ %sub = sub i32 255, %x
+ store i32 %sub, ptr %sink
+ %shr = lshr i32 %sub, 23
+ %or = or i32 %shr, %x
+ %and = and i32 %or, 255
+ ret i32 %and
+}
+
+; Multi-use on the `shr`: fold is not profitable.
+define i32 @neg_multiuse_shr(i32 range(i32 0, 8388608) %x, ptr %sink) {
+; CHECK-LABEL: define i32 @neg_multiuse_shr(
+; CHECK-SAME: i32 range(i32 0, 8388608) [[X:%.*]], ptr [[SINK:%.*]]) {
+; CHECK-NEXT: [[SUB:%.*]] = sub nsw i32 255, [[X]]
+; CHECK-NEXT: [[SHR:%.*]] = lshr i32 [[SUB]], 23
+; CHECK-NEXT: store i32 [[SHR]], ptr [[SINK]], align 4
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[SHR]], [[X]]
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[OR]], 255
+; CHECK-NEXT: ret i32 [[AND]]
+;
+ %sub = sub i32 255, %x
+ %shr = lshr i32 %sub, 23
+ store i32 %shr, ptr %sink
+ %or = or i32 %shr, %x
+ %and = and i32 %or, 255
+ ret i32 %and
+}
+
+; Multi-use on the `or`: fold is not profitable.
+define i32 @neg_multiuse_or(i32 range(i32 0, 8388608) %x, ptr %sink) {
+; CHECK-LABEL: define i32 @neg_multiuse_or(
+; CHECK-SAME: i32 range(i32 0, 8388608) [[X:%.*]], ptr [[SINK:%.*]]) {
+; CHECK-NEXT: [[SUB:%.*]] = sub nsw i32 255, [[X]]
+; CHECK-NEXT: [[SHR:%.*]] = lshr i32 [[SUB]], 23
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[SHR]], [[X]]
+; CHECK-NEXT: store i32 [[OR]], ptr [[SINK]], align 4
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[OR]], 255
+; CHECK-NEXT: ret i32 [[AND]]
+;
+ %sub = sub i32 255, %x
+ %shr = lshr i32 %sub, 23
+ %or = or i32 %shr, %x
+ store i32 %or, ptr %sink
+ %and = and i32 %or, 255
+ ret i32 %and
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/212798
More information about the llvm-commits
mailing list