[llvm] [InstCombine] Fold shift of a constant into a reverse shift (PR #192982)
Piotr Fusik via llvm-commits
llvm-commits at lists.llvm.org
Wed Apr 22 23:06:02 PDT 2026
https://github.com/pfusik updated https://github.com/llvm/llvm-project/pull/192982
>From 74f909da060341b21dd983114b4e4ade0fe16c92 Mon Sep 17 00:00:00 2001
From: Piotr Fusik <p.fusik at samsung.com>
Date: Mon, 20 Apr 2026 16:20:24 +0200
Subject: [PATCH 1/7] [InstCombine] Fold shift of a constant into a reverse
shift
C1 << (C2 - X) -> (C1 << C2) >> X
C1 << (C2 ^ X) -> (C1 << C2) >> X (if equivalent to the above)
C1 >> (C2 - X) -> (C1 >> C2) << X (right shift types match)
C1 >> (C2 ^ X) -> (C1 >> C2) << X (if equivalent to the above)
---
.../InstCombine/InstCombineInternal.h | 3 ++
.../InstCombine/InstCombineShifts.cpp | 54 ++++++++++++++++++-
2 files changed, 56 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
index 915022b2829f2..c7ede74723494 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
+++ b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
@@ -432,6 +432,9 @@ class LLVM_LIBRARY_VISIBILITY InstCombinerImpl final
bool InvertFalseVal = false);
Value *getSelectCondition(Value *A, Value *B, bool ABIsTheSame);
+ bool matchShiftConstNUWSub(BinaryOperator &I, const APInt *&C1, uint64_t &C2,
+ Value *&X);
+ Instruction *foldShrConstToShl(BinaryOperator &I);
Instruction *foldLShrOverflowBit(BinaryOperator &I);
Instruction *foldExtractOfOverflowIntrinsic(ExtractValueInst &EV);
Instruction *foldIntrinsicWithOverflowCommon(IntrinsicInst *II);
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
index e416059ef28ae..5a9934a61d77f 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
@@ -1043,6 +1043,35 @@ static bool setShiftFlags(BinaryOperator &I, const SimplifyQuery &Q) {
return Changed;
}
+// Match (C1 bop (C2 sub nuw X))
+// or (C1 bop (X xor C2)) if equivalent to the above.
+bool InstCombinerImpl::matchShiftConstNUWSub(BinaryOperator &I,
+ const APInt *&C1, uint64_t &C2,
+ Value *&X) {
+ if (!match(I.getOperand(0), m_APInt(C1)))
+ return false;
+ if (match(I.getOperand(1), m_NUWSub(m_ConstantInt(C2), m_Value(X))))
+ return true;
+ return match(I.getOperand(1), m_Xor(m_Value(X), m_ConstantInt(C2))) &&
+ isMask_64(C2) && (C2 | computeKnownBits(X, &I).Zero).isAllOnes();
+}
+
+// C1 >> (C2 - X) -> (C1 >> C2) << X
+// X must be >= (checked by NUWSub).
+// C1 must have at least C2 trailing zeros.
+// Also match (X ^ C2) if equivalent to (C2 - X).
+Instruction *InstCombinerImpl::foldShrConstToShl(BinaryOperator &I) {
+ const APInt *C1;
+ uint64_t C2;
+ Value *X;
+ if (!matchShiftConstNUWSub(I, C1, C2, X))
+ return nullptr;
+ if (C1->countr_zero() < C2)
+ return nullptr;
+ APInt C = I.getOpcode() == Instruction::LShr ? C1->lshr(C2) : C1->ashr(C2);
+ return BinaryOperator::CreateShl(ConstantInt::get(I.getType(), C), X);
+}
+
Instruction *InstCombinerImpl::visitShl(BinaryOperator &I) {
const SimplifyQuery Q = SQ.getWithInstruction(&I);
@@ -1266,7 +1295,7 @@ Instruction *InstCombinerImpl::visitShl(BinaryOperator &I) {
if (match(Op0, m_One())) {
// (1 << (C - x)) -> ((1 << C) >> x) if C is bitwidth - 1
if (match(Op1, m_Sub(m_SpecificInt(BitWidth - 1), m_Value(X))))
- return BinaryOperator::CreateLShr(
+ return BinaryOperator::CreateExactLShr(
ConstantInt::get(Ty, APInt::getSignMask(BitWidth)), X);
// Canonicalize "extract lowest set bit" using cttz to and-with-negate:
@@ -1278,6 +1307,24 @@ Instruction *InstCombinerImpl::visitShl(BinaryOperator &I) {
}
}
+ {
+ const APInt *C1;
+ uint64_t C2;
+ // C1 << (C2 - X) -> (C1 << C2) >> X
+ // X must be >=0 (checked by NUWSub).
+ // C1 must have at least C2 leading zeros (for LShr)
+ // or at least C2+1 leading ones (for AShr).
+ // Also match (X ^ C2) if equivalent to (C2 - X).
+ if (matchShiftConstNUWSub(I, C1, C2, X)) {
+ if (C1->countl_zero() >= C2)
+ return BinaryOperator::CreateExactLShr(
+ ConstantInt::get(Ty, C1->shl(C2)), X);
+ if (C1->countl_one() > C2)
+ return BinaryOperator::CreateExactAShr(
+ ConstantInt::get(Ty, C1->shl(C2)), X);
+ }
+ }
+
return nullptr;
}
@@ -1641,6 +1688,9 @@ Instruction *InstCombinerImpl::visitLShr(BinaryOperator &I) {
return BinaryOperator::CreateLShr(NewShl, Shl1_Op1);
}
}
+
+ if (Instruction *R = foldShrConstToShl(I))
+ return R;
return nullptr;
}
@@ -1849,5 +1899,7 @@ Instruction *InstCombinerImpl::visitAShr(BinaryOperator &I) {
return BinaryOperator::CreateNot(NewAShr);
}
+ if (Instruction *R = foldShrConstToShl(I))
+ return R;
return nullptr;
}
>From 34888162f04b0e197e705e084a02fc77456053d5 Mon Sep 17 00:00:00 2001
From: Piotr Fusik <p.fusik at samsung.com>
Date: Tue, 21 Apr 2026 15:40:54 +0200
Subject: [PATCH 2/7] Revert "[InstCombine] Fold shift of a constant into a
reverse shift"
This reverts commit 74f909da060341b21dd983114b4e4ade0fe16c92.
---
.../InstCombine/InstCombineInternal.h | 3 --
.../InstCombine/InstCombineShifts.cpp | 54 +------------------
2 files changed, 1 insertion(+), 56 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
index c7ede74723494..915022b2829f2 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
+++ b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
@@ -432,9 +432,6 @@ class LLVM_LIBRARY_VISIBILITY InstCombinerImpl final
bool InvertFalseVal = false);
Value *getSelectCondition(Value *A, Value *B, bool ABIsTheSame);
- bool matchShiftConstNUWSub(BinaryOperator &I, const APInt *&C1, uint64_t &C2,
- Value *&X);
- Instruction *foldShrConstToShl(BinaryOperator &I);
Instruction *foldLShrOverflowBit(BinaryOperator &I);
Instruction *foldExtractOfOverflowIntrinsic(ExtractValueInst &EV);
Instruction *foldIntrinsicWithOverflowCommon(IntrinsicInst *II);
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
index 5a9934a61d77f..e416059ef28ae 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
@@ -1043,35 +1043,6 @@ static bool setShiftFlags(BinaryOperator &I, const SimplifyQuery &Q) {
return Changed;
}
-// Match (C1 bop (C2 sub nuw X))
-// or (C1 bop (X xor C2)) if equivalent to the above.
-bool InstCombinerImpl::matchShiftConstNUWSub(BinaryOperator &I,
- const APInt *&C1, uint64_t &C2,
- Value *&X) {
- if (!match(I.getOperand(0), m_APInt(C1)))
- return false;
- if (match(I.getOperand(1), m_NUWSub(m_ConstantInt(C2), m_Value(X))))
- return true;
- return match(I.getOperand(1), m_Xor(m_Value(X), m_ConstantInt(C2))) &&
- isMask_64(C2) && (C2 | computeKnownBits(X, &I).Zero).isAllOnes();
-}
-
-// C1 >> (C2 - X) -> (C1 >> C2) << X
-// X must be >= (checked by NUWSub).
-// C1 must have at least C2 trailing zeros.
-// Also match (X ^ C2) if equivalent to (C2 - X).
-Instruction *InstCombinerImpl::foldShrConstToShl(BinaryOperator &I) {
- const APInt *C1;
- uint64_t C2;
- Value *X;
- if (!matchShiftConstNUWSub(I, C1, C2, X))
- return nullptr;
- if (C1->countr_zero() < C2)
- return nullptr;
- APInt C = I.getOpcode() == Instruction::LShr ? C1->lshr(C2) : C1->ashr(C2);
- return BinaryOperator::CreateShl(ConstantInt::get(I.getType(), C), X);
-}
-
Instruction *InstCombinerImpl::visitShl(BinaryOperator &I) {
const SimplifyQuery Q = SQ.getWithInstruction(&I);
@@ -1295,7 +1266,7 @@ Instruction *InstCombinerImpl::visitShl(BinaryOperator &I) {
if (match(Op0, m_One())) {
// (1 << (C - x)) -> ((1 << C) >> x) if C is bitwidth - 1
if (match(Op1, m_Sub(m_SpecificInt(BitWidth - 1), m_Value(X))))
- return BinaryOperator::CreateExactLShr(
+ return BinaryOperator::CreateLShr(
ConstantInt::get(Ty, APInt::getSignMask(BitWidth)), X);
// Canonicalize "extract lowest set bit" using cttz to and-with-negate:
@@ -1307,24 +1278,6 @@ Instruction *InstCombinerImpl::visitShl(BinaryOperator &I) {
}
}
- {
- const APInt *C1;
- uint64_t C2;
- // C1 << (C2 - X) -> (C1 << C2) >> X
- // X must be >=0 (checked by NUWSub).
- // C1 must have at least C2 leading zeros (for LShr)
- // or at least C2+1 leading ones (for AShr).
- // Also match (X ^ C2) if equivalent to (C2 - X).
- if (matchShiftConstNUWSub(I, C1, C2, X)) {
- if (C1->countl_zero() >= C2)
- return BinaryOperator::CreateExactLShr(
- ConstantInt::get(Ty, C1->shl(C2)), X);
- if (C1->countl_one() > C2)
- return BinaryOperator::CreateExactAShr(
- ConstantInt::get(Ty, C1->shl(C2)), X);
- }
- }
-
return nullptr;
}
@@ -1688,9 +1641,6 @@ Instruction *InstCombinerImpl::visitLShr(BinaryOperator &I) {
return BinaryOperator::CreateLShr(NewShl, Shl1_Op1);
}
}
-
- if (Instruction *R = foldShrConstToShl(I))
- return R;
return nullptr;
}
@@ -1899,7 +1849,5 @@ Instruction *InstCombinerImpl::visitAShr(BinaryOperator &I) {
return BinaryOperator::CreateNot(NewAShr);
}
- if (Instruction *R = foldShrConstToShl(I))
- return R;
return nullptr;
}
>From 8f1243e566595757c0e098fc4ec48a5ab9a4ad0f Mon Sep 17 00:00:00 2001
From: Piotr Fusik <p.fusik at samsung.com>
Date: Tue, 21 Apr 2026 15:45:50 +0200
Subject: [PATCH 3/7] [InstCombine][test] Fold shift of a constant into a
reverse shift
---
llvm/test/Transforms/InstCombine/shift-sub.ll | 494 ++++++++++++++++++
1 file changed, 494 insertions(+)
create mode 100644 llvm/test/Transforms/InstCombine/shift-sub.ll
diff --git a/llvm/test/Transforms/InstCombine/shift-sub.ll b/llvm/test/Transforms/InstCombine/shift-sub.ll
new file mode 100644
index 0000000000000..1d50867f756f2
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/shift-sub.ll
@@ -0,0 +1,494 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -passes=instcombine -S < %s | FileCheck %s
+
+declare void @use(i8)
+
+define i8 @shl_sub(i8 %x) {
+; CHECK-LABEL: @shl_sub(
+; CHECK-NEXT: [[S:%.*]] = sub nuw i8 4, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = shl i8 2, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %s = sub nuw i8 4, %x
+ %r = shl i8 2, %s
+ ret i8 %r
+}
+
+define i8 @shl_sub_maxlzero(i8 %x) {
+; CHECK-LABEL: @shl_sub_maxlzero(
+; CHECK-NEXT: [[S:%.*]] = sub nuw i8 6, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = shl i8 3, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %s = sub nuw i8 6, %x
+ %r = shl i8 3, %s
+ ret i8 %r
+}
+
+define i8 @shl_sub_maxlone(i8 %x) {
+; CHECK-LABEL: @shl_sub_maxlone(
+; CHECK-NEXT: [[S:%.*]] = sub nuw i8 6, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = shl i8 -2, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %s = sub nuw i8 6, %x
+ %r = shl i8 -2, %s
+ ret i8 %r
+}
+
+define i8 @shl_sub_multiuse(i8 %x) {
+; CHECK-LABEL: @shl_sub_multiuse(
+; CHECK-NEXT: [[S:%.*]] = sub nuw i8 3, [[X:%.*]]
+; CHECK-NEXT: call void @use(i8 [[S]])
+; CHECK-NEXT: [[R:%.*]] = shl nuw nsw i8 2, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %s = sub nuw i8 3, %x
+ call void @use(i8 %s)
+ %r = shl i8 2, %s
+ ret i8 %r
+}
+
+define <2 x i8> @shl_sub_vec_splat(<2 x i8> %x) {
+; CHECK-LABEL: @shl_sub_vec_splat(
+; CHECK-NEXT: [[S:%.*]] = sub nuw <2 x i8> splat (i8 4), [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = shl <2 x i8> splat (i8 3), [[S]]
+; CHECK-NEXT: ret <2 x i8> [[R]]
+;
+ %s = sub nuw <2 x i8> <i8 4, i8 4>, %x
+ %r = shl <2 x i8> <i8 3, i8 3>, %s
+ ret <2 x i8> %r
+}
+
+define <2 x i8> @shl_sub_vec_splat_poison(<2 x i8> %x) {
+; CHECK-LABEL: @shl_sub_vec_splat_poison(
+; CHECK-NEXT: [[S:%.*]] = sub nuw <2 x i8> <i8 4, i8 poison>, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = shl <2 x i8> <i8 3, i8 poison>, [[S]]
+; CHECK-NEXT: ret <2 x i8> [[R]]
+;
+ %s = sub nuw <2 x i8> <i8 4, i8 poison>, %x
+ %r = shl <2 x i8> <i8 3, i8 poison>, %s
+ ret <2 x i8> %r
+}
+
+define <2 x i8> @shl_sub_vec_non_splat(<2 x i8> %x) {
+; CHECK-LABEL: @shl_sub_vec_non_splat(
+; CHECK-NEXT: [[S:%.*]] = sub nuw <2 x i8> <i8 4, i8 3>, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = shl <2 x i8> <i8 3, i8 2>, [[S]]
+; CHECK-NEXT: ret <2 x i8> [[R]]
+;
+ %s = sub nuw <2 x i8> <i8 4, i8 3>, %x
+ %r = shl <2 x i8> <i8 3, i8 2>, %s
+ ret <2 x i8> %r
+}
+
+define i8 @shl_sub_negative_uw(i8 %x) {
+; CHECK-LABEL: @shl_sub_negative_uw(
+; CHECK-NEXT: [[S:%.*]] = sub i8 4, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = shl i8 2, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %s = sub i8 4, %x
+ %r = shl i8 2, %s
+ ret i8 %r
+}
+
+define i8 @shl_sub_negative_nolzero(i8 %x) {
+; CHECK-LABEL: @shl_sub_negative_nolzero(
+; CHECK-NEXT: [[S:%.*]] = sub nuw i8 7, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = shl i8 2, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %s = sub nuw i8 7, %x
+ %r = shl i8 2, %s
+ ret i8 %r
+}
+
+define i8 @shl_sub_negative_nolone(i8 %x) {
+; CHECK-LABEL: @shl_sub_negative_nolone(
+; CHECK-NEXT: [[S:%.*]] = sub nuw i8 7, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = shl i8 -2, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %s = sub nuw i8 7, %x
+ %r = shl i8 -2, %s
+ ret i8 %r
+}
+
+define i8 @shl_xor(i8 %x) {
+; CHECK-LABEL: @shl_xor(
+; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 3
+; CHECK-NEXT: [[S:%.*]] = xor i8 [[M]], 3
+; CHECK-NEXT: [[R:%.*]] = shl nuw nsw i8 2, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %m = and i8 %x, 3
+ %s = xor i8 %m, 3
+ %r = shl i8 2, %s
+ ret i8 %r
+}
+
+define i16 @shl_xor_i2(i2 %x) {
+; CHECK-LABEL: @shl_xor_i2(
+; CHECK-NEXT: [[M:%.*]] = zext i2 [[X:%.*]] to i16
+; CHECK-NEXT: [[S:%.*]] = xor i16 [[M]], 7
+; CHECK-NEXT: [[R:%.*]] = shl nuw nsw i16 2, [[S]]
+; CHECK-NEXT: ret i16 [[R]]
+;
+ %m = zext i2 %x to i16
+ %s = xor i16 %m, 7
+ %r = shl i16 2, %s
+ ret i16 %r
+}
+
+define i8 @shl_xor_multiuse(i8 %x) {
+; CHECK-LABEL: @shl_xor_multiuse(
+; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 3
+; CHECK-NEXT: [[S:%.*]] = xor i8 [[M]], 3
+; CHECK-NEXT: call void @use(i8 [[S]])
+; CHECK-NEXT: [[R:%.*]] = shl nuw nsw i8 2, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %m = and i8 %x, 3
+ %s = xor i8 %m, 3
+ call void @use(i8 %s)
+ %r = shl i8 2, %s
+ ret i8 %r
+}
+
+define <2 x i8> @shl_xor_vec_splat(<2 x i8> %x) {
+; CHECK-LABEL: @shl_xor_vec_splat(
+; CHECK-NEXT: [[M:%.*]] = and <2 x i8> [[X:%.*]], splat (i8 3)
+; CHECK-NEXT: [[S:%.*]] = xor <2 x i8> [[M]], splat (i8 3)
+; CHECK-NEXT: [[R:%.*]] = shl nuw nsw <2 x i8> splat (i8 5), [[S]]
+; CHECK-NEXT: ret <2 x i8> [[R]]
+;
+ %m = and <2 x i8> <i8 3, i8 3>, %x
+ %s = xor <2 x i8> <i8 3, i8 3>, %m
+ %r = shl <2 x i8> <i8 5, i8 5>, %s
+ ret <2 x i8> %r
+}
+
+define <2 x i8> @shl_xor_vec_splat_poison(<2 x i8> %x) {
+; CHECK-LABEL: @shl_xor_vec_splat_poison(
+; CHECK-NEXT: [[M:%.*]] = and <2 x i8> [[X:%.*]], splat (i8 3)
+; CHECK-NEXT: [[S:%.*]] = xor <2 x i8> [[M]], <i8 poison, i8 3>
+; CHECK-NEXT: [[R:%.*]] = shl nuw nsw <2 x i8> splat (i8 5), [[S]]
+; CHECK-NEXT: ret <2 x i8> [[R]]
+;
+ %m = and <2 x i8> <i8 3, i8 3>, %x
+ %s = xor <2 x i8> <i8 poison, i8 3>, %m
+ %r = shl <2 x i8> <i8 5, i8 5>, %s
+ ret <2 x i8> %r
+}
+
+define <2 x i8> @shl_xor_vec_non_splat(<2 x i8> %x) {
+; CHECK-LABEL: @shl_xor_vec_non_splat(
+; CHECK-NEXT: [[M:%.*]] = and <2 x i8> [[X:%.*]], splat (i8 3)
+; CHECK-NEXT: [[S:%.*]] = xor <2 x i8> [[M]], splat (i8 3)
+; CHECK-NEXT: [[R:%.*]] = shl nuw nsw <2 x i8> <i8 4, i8 5>, [[S]]
+; CHECK-NEXT: ret <2 x i8> [[R]]
+;
+ %m = and <2 x i8> <i8 3, i8 3>, %x
+ %s = xor <2 x i8> <i8 3, i8 3>, %m
+ %r = shl <2 x i8> <i8 4, i8 5>, %s
+ ret <2 x i8> %r
+}
+
+define i8 @shl_xor_negative_notmask(i8 %x) {
+; CHECK-LABEL: @shl_xor_negative_notmask(
+; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 3
+; CHECK-NEXT: [[S:%.*]] = xor i8 [[M]], 2
+; CHECK-NEXT: [[R:%.*]] = shl nuw nsw i8 2, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %m = and i8 %x, 3
+ %s = xor i8 %m, 2
+ %r = shl i8 2, %s
+ ret i8 %r
+}
+
+define i8 @shl_xor_negative_abovemask(i8 %x) {
+; CHECK-LABEL: @shl_xor_negative_abovemask(
+; CHECK-NEXT: [[S:%.*]] = xor i8 [[X:%.*]], 7
+; CHECK-NEXT: [[R:%.*]] = shl i8 2, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %s = xor i8 %x, 7
+ %r = shl i8 2, %s
+ ret i8 %r
+}
+
+define i8 @lshr_sub(i8 %x) {
+; CHECK-LABEL: @lshr_sub(
+; CHECK-NEXT: [[S:%.*]] = sub nuw i8 3, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = lshr exact i8 48, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %s = sub nuw i8 3, %x
+ %r = lshr i8 48, %s
+ ret i8 %r
+}
+
+define i8 @lshr_sub_maxtzero(i8 %x) {
+; CHECK-LABEL: @lshr_sub_maxtzero(
+; CHECK-NEXT: [[S:%.*]] = sub nuw i8 5, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = lshr i8 32, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %s = sub nuw i8 5, %x
+ %r = lshr i8 32, %s
+ ret i8 %r
+}
+
+define i8 @lshr_sub_multiuse(i8 %x) {
+; CHECK-LABEL: @lshr_sub_multiuse(
+; CHECK-NEXT: [[S:%.*]] = sub nuw i8 3, [[X:%.*]]
+; CHECK-NEXT: call void @use(i8 [[S]])
+; CHECK-NEXT: [[R:%.*]] = lshr exact i8 32, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %s = sub nuw i8 3, %x
+ call void @use(i8 %s)
+ %r = lshr i8 32, %s
+ ret i8 %r
+}
+
+define <2 x i8> @lshr_sub_vec_splat(<2 x i8> %x) {
+; CHECK-LABEL: @lshr_sub_vec_splat(
+; CHECK-NEXT: [[S:%.*]] = sub nuw <2 x i8> splat (i8 4), [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = lshr <2 x i8> splat (i8 64), [[S]]
+; CHECK-NEXT: ret <2 x i8> [[R]]
+;
+ %s = sub nuw <2 x i8> <i8 4, i8 4>, %x
+ %r = lshr <2 x i8> <i8 64, i8 64>, %s
+ ret <2 x i8> %r
+}
+
+define <2 x i8> @lshr_sub_vec_splat_poison(<2 x i8> %x) {
+; CHECK-LABEL: @lshr_sub_vec_splat_poison(
+; CHECK-NEXT: [[S:%.*]] = sub nuw <2 x i8> <i8 4, i8 poison>, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = lshr <2 x i8> <i8 64, i8 poison>, [[S]]
+; CHECK-NEXT: ret <2 x i8> [[R]]
+;
+ %s = sub nuw <2 x i8> <i8 4, i8 poison>, %x
+ %r = lshr <2 x i8> <i8 64, i8 poison>, %s
+ ret <2 x i8> %r
+}
+
+define <2 x i8> @lshr_sub_vec_non_splat(<2 x i8> %x) {
+; CHECK-LABEL: @lshr_sub_vec_non_splat(
+; CHECK-NEXT: [[S:%.*]] = sub nuw <2 x i8> <i8 4, i8 1>, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = lshr <2 x i8> splat (i8 64), [[S]]
+; CHECK-NEXT: ret <2 x i8> [[R]]
+;
+ %s = sub nuw <2 x i8> <i8 4, i8 1>, %x
+ %r = lshr <2 x i8> <i8 64, i8 64>, %s
+ ret <2 x i8> %r
+}
+
+define i8 @lshr_sub_negative_uw(i8 %x) {
+; CHECK-LABEL: @lshr_sub_negative_uw(
+; CHECK-NEXT: [[S:%.*]] = sub i8 3, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = lshr i8 32, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %s = sub i8 3, %x
+ %r = lshr i8 32, %s
+ ret i8 %r
+}
+
+define i8 @lshr_sub_negative_notrzero(i8 %x) {
+; CHECK-LABEL: @lshr_sub_negative_notrzero(
+; CHECK-NEXT: [[S:%.*]] = sub nuw i8 5, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = lshr i8 16, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %s = sub nuw i8 5, %x
+ %r = lshr i8 16, %s
+ ret i8 %r
+}
+
+define i8 @lshr_xor(i8 %x) {
+; CHECK-LABEL: @lshr_xor(
+; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 6
+; CHECK-NEXT: [[S:%.*]] = xor i8 [[M]], 7
+; CHECK-NEXT: [[R:%.*]] = lshr exact i8 -128, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %m = and i8 %x, 6
+ %s = xor i8 %m, 7
+ %r = lshr i8 -128, %s
+ ret i8 %r
+}
+
+define i8 @lshr_xor_multiuse(i8 %x) {
+; CHECK-LABEL: @lshr_xor_multiuse(
+; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 6
+; CHECK-NEXT: [[S:%.*]] = xor i8 [[M]], 7
+; CHECK-NEXT: call void @use(i8 [[S]])
+; CHECK-NEXT: [[R:%.*]] = lshr exact i8 -128, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %m = and i8 %x, 6
+ %s = xor i8 %m, 7
+ call void @use(i8 %s)
+ %r = lshr i8 -128, %s
+ ret i8 %r
+}
+
+define <2 x i8> @lshr_xor_vec_splat(<2 x i8> %x) {
+; CHECK-LABEL: @lshr_xor_vec_splat(
+; CHECK-NEXT: [[M:%.*]] = and <2 x i8> [[X:%.*]], <i8 2, i8 3>
+; CHECK-NEXT: [[S:%.*]] = xor <2 x i8> [[M]], splat (i8 3)
+; CHECK-NEXT: [[R:%.*]] = lshr exact <2 x i8> splat (i8 16), [[S]]
+; CHECK-NEXT: ret <2 x i8> [[R]]
+;
+ %m = and <2 x i8> <i8 2, i8 3>, %x
+ %s = xor <2 x i8> %m, <i8 3, i8 3>
+ %r = lshr <2 x i8> <i8 16, i8 16>, %s
+ ret <2 x i8> %r
+}
+
+define <2 x i8> @lshr_xor_vec_splat_poison(<2 x i8> %x) {
+; CHECK-LABEL: @lshr_xor_vec_splat_poison(
+; CHECK-NEXT: [[M:%.*]] = and <2 x i8> [[X:%.*]], <i8 2, i8 3>
+; CHECK-NEXT: [[S:%.*]] = xor <2 x i8> [[M]], splat (i8 3)
+; CHECK-NEXT: [[R:%.*]] = lshr exact <2 x i8> <i8 poison, i8 16>, [[S]]
+; CHECK-NEXT: ret <2 x i8> [[R]]
+;
+ %m = and <2 x i8> <i8 2, i8 3>, %x
+ %s = xor <2 x i8> %m, <i8 3, i8 3>
+ %r = lshr <2 x i8> <i8 poison, i8 16>, %s
+ ret <2 x i8> %r
+}
+
+define <2 x i8> @lshr_xor_vec_non_splat(<2 x i8> %x) {
+; CHECK-LABEL: @lshr_xor_vec_non_splat(
+; CHECK-NEXT: [[M:%.*]] = and <2 x i8> [[X:%.*]], splat (i8 3)
+; CHECK-NEXT: [[S:%.*]] = xor <2 x i8> [[M]], splat (i8 3)
+; CHECK-NEXT: [[R:%.*]] = lshr exact <2 x i8> <i8 32, i8 16>, [[S]]
+; CHECK-NEXT: ret <2 x i8> [[R]]
+;
+ %m = and <2 x i8> <i8 3, i8 3>, %x
+ %s = xor <2 x i8> %m, <i8 3, i8 3>
+ %r = lshr <2 x i8> <i8 32, i8 16>, %s
+ ret <2 x i8> %r
+}
+
+define i8 @lshr_xor_negative_notmask(i8 %x) {
+; CHECK-LABEL: @lshr_xor_negative_notmask(
+; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 3
+; CHECK-NEXT: [[S:%.*]] = xor i8 [[M]], 2
+; CHECK-NEXT: [[R:%.*]] = lshr exact i8 96, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %m = and i8 %x, 3
+ %s = xor i8 %m, 2
+ %r = lshr i8 96, %s
+ ret i8 %r
+}
+
+define i8 @lshr_xor_negative_notrzero(i8 %x) {
+; CHECK-LABEL: @lshr_xor_negative_notrzero(
+; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 3
+; CHECK-NEXT: [[S:%.*]] = xor i8 [[M]], 3
+; CHECK-NEXT: [[R:%.*]] = lshr i8 68, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %m = and i8 %x, 3
+ %s = xor i8 %m, 3
+ %r = lshr i8 68, %s
+ ret i8 %r
+}
+
+define i8 @ashr_sub(i8 %x) {
+; CHECK-LABEL: @ashr_sub(
+; CHECK-NEXT: [[S:%.*]] = sub nuw i8 3, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = ashr exact i8 -64, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %s = sub nuw i8 3, %x
+ %r = ashr i8 -64, %s
+ ret i8 %r
+}
+
+define i8 @ashr_sub_maxtzero(i8 %x) {
+; CHECK-LABEL: @ashr_sub_maxtzero(
+; CHECK-NEXT: [[S:%.*]] = sub nuw i8 6, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = ashr i8 -64, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %s = sub nuw i8 6, %x
+ %r = ashr i8 -64, %s
+ ret i8 %r
+}
+
+define i8 @ashr_sub_multiuse(i8 %x) {
+; CHECK-LABEL: @ashr_sub_multiuse(
+; CHECK-NEXT: [[S:%.*]] = sub nuw i8 3, [[X:%.*]]
+; CHECK-NEXT: call void @use(i8 [[S]])
+; CHECK-NEXT: [[R:%.*]] = lshr exact i8 96, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %s = sub nuw i8 3, %x
+ call void @use(i8 %s)
+ %r = ashr i8 -160, %s
+ ret i8 %r
+}
+
+define <2 x i8> @ashr_sub_vec_splat(<2 x i8> %x) {
+; CHECK-LABEL: @ashr_sub_vec_splat(
+; CHECK-NEXT: [[S:%.*]] = sub nuw <2 x i8> splat (i8 4), [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = ashr <2 x i8> splat (i8 -64), [[S]]
+; CHECK-NEXT: ret <2 x i8> [[R]]
+;
+ %s = sub nuw <2 x i8> <i8 4, i8 4>, %x
+ %r = ashr <2 x i8> <i8 -64, i8 -64>, %s
+ ret <2 x i8> %r
+}
+
+define <2 x i8> @ashr_sub_vec_splat_poison(<2 x i8> %x) {
+; CHECK-LABEL: @ashr_sub_vec_splat_poison(
+; CHECK-NEXT: [[S:%.*]] = sub nuw <2 x i8> <i8 4, i8 poison>, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = ashr <2 x i8> splat (i8 -64), [[S]]
+; CHECK-NEXT: ret <2 x i8> [[R]]
+;
+ %s = sub nuw <2 x i8> <i8 4, i8 poison>, %x
+ %r = ashr <2 x i8> <i8 -64, i8 -64>, %s
+ ret <2 x i8> %r
+}
+
+define <2 x i8> @ashr_sub_vec_non_splat(<2 x i8> %x) {
+; CHECK-LABEL: @ashr_sub_vec_non_splat(
+; CHECK-NEXT: [[S:%.*]] = sub nuw <2 x i8> splat (i8 4), [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = ashr <2 x i8> <i8 64, i8 -64>, [[S]]
+; CHECK-NEXT: ret <2 x i8> [[R]]
+;
+ %s = sub nuw <2 x i8> <i8 4, i8 4>, %x
+ %r = ashr <2 x i8> <i8 64, i8 -64>, %s
+ ret <2 x i8> %r
+}
+
+define i8 @ashr_sub_negative_notrzero(i8 %x) {
+; CHECK-LABEL: @ashr_sub_negative_notrzero(
+; CHECK-NEXT: [[S:%.*]] = sub nuw i8 7, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = ashr i8 -64, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %s = sub nuw i8 7, %x
+ %r = ashr i8 -64, %s
+ ret i8 %r
+}
+
+define i8 @ashr_xor(i8 %x) {
+; CHECK-LABEL: @ashr_xor(
+; CHECK-NEXT: [[S:%.*]] = xor i8 [[X:%.*]], 3
+; CHECK-NEXT: [[R:%.*]] = ashr i8 -64, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %m = and i8 %x, 3
+ %s = xor i8 %x, 3
+ %r = ashr i8 -64, %s
+ ret i8 %r
+}
>From 206bf984a3381743ac67870b9c9823f0be3a432c Mon Sep 17 00:00:00 2001
From: Piotr Fusik <p.fusik at samsung.com>
Date: Tue, 21 Apr 2026 16:13:35 +0200
Subject: [PATCH 4/7] [InstCombine] Fold shift of a constant into a reverse
shift
C1 << (C2 - X) -> (C1 << C2) >> X
C1 << (C2 ^ X) -> (C1 << C2) >> X (if equivalent to the above)
C1 >> (C2 - X) -> (C1 >> C2) << X (right shift modes match)
C1 >> (C2 ^ X) -> (C1 >> C2) << X (if equivalent to the above)
Proof: https://alive2.llvm.org/ce/z/nTGWSi
---
.../InstCombine/InstCombineShifts.cpp | 89 ++++++++++++-------
llvm/test/Transforms/InstCombine/shift-sub.ll | 55 +++++-------
2 files changed, 78 insertions(+), 66 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
index e416059ef28ae..461574f70652a 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
@@ -460,42 +460,69 @@ Instruction *InstCombinerImpl::commonShiftTransforms(BinaryOperator &I) {
unsigned BitWidth = Ty->getScalarSizeInBits();
- const APInt *AC, *AddC;
- // Try to pre-shift a constant shifted by a variable amount added with a
- // negative number:
- // C << (X - AddC) --> (C >> AddC) << X
- // and
- // C >> (X - AddC) --> (C << AddC) >> X
- if (match(Op0, m_APInt(AC)) && match(Op1, m_Add(m_Value(A), m_APInt(AddC))) &&
- AddC->isNegative() && (-*AddC).ult(BitWidth)) {
+ const APInt *AC;
+ if (match(Op0, m_APInt(AC))) {
assert(!AC->isZero() && "Expected simplify of shifted zero");
- unsigned PosOffset = (-*AddC).getZExtValue();
- auto isSuitableForPreShift = [PosOffset, &I, AC]() {
- switch (I.getOpcode()) {
- default:
- return false;
- case Instruction::Shl:
- return (I.hasNoSignedWrap() || I.hasNoUnsignedWrap()) &&
- AC->eq(AC->lshr(PosOffset).shl(PosOffset));
- case Instruction::LShr:
- return I.isExact() && AC->eq(AC->shl(PosOffset).lshr(PosOffset));
- case Instruction::AShr:
- return I.isExact() && AC->eq(AC->shl(PosOffset).ashr(PosOffset));
+ // Try to pre-shift a constant shifted by a variable amount added with a
+ // negative number:
+ // C << (X - AddC) --> (C >> AddC) << X
+ // and
+ // C >> (X - AddC) --> (C << AddC) >> X
+ const APInt *AddC;
+ if (match(Op1, m_Add(m_Value(A), m_APInt(AddC))) && AddC->isNegative() &&
+ (-*AddC).ult(BitWidth)) {
+ unsigned PosOffset = (-*AddC).getZExtValue();
+
+ auto isSuitableForPreShift = [PosOffset, &I, AC]() {
+ switch (I.getOpcode()) {
+ default:
+ return false;
+ case Instruction::Shl:
+ return (I.hasNoSignedWrap() || I.hasNoUnsignedWrap()) &&
+ AC->eq(AC->lshr(PosOffset).shl(PosOffset));
+ case Instruction::LShr:
+ return I.isExact() && AC->eq(AC->shl(PosOffset).lshr(PosOffset));
+ case Instruction::AShr:
+ return I.isExact() && AC->eq(AC->shl(PosOffset).ashr(PosOffset));
+ }
+ };
+ if (isSuitableForPreShift()) {
+ Constant *NewC = ConstantInt::get(Ty, I.getOpcode() == Instruction::Shl
+ ? AC->lshr(PosOffset)
+ : AC->shl(PosOffset));
+ BinaryOperator *NewShiftOp =
+ BinaryOperator::Create(I.getOpcode(), NewC, A);
+ if (I.getOpcode() == Instruction::Shl) {
+ NewShiftOp->setHasNoUnsignedWrap(I.hasNoUnsignedWrap());
+ } else {
+ NewShiftOp->setIsExact();
+ }
+ return NewShiftOp;
}
- };
- if (isSuitableForPreShift()) {
- Constant *NewC = ConstantInt::get(Ty, I.getOpcode() == Instruction::Shl
- ? AC->lshr(PosOffset)
- : AC->shl(PosOffset));
- BinaryOperator *NewShiftOp =
- BinaryOperator::Create(I.getOpcode(), NewC, A);
+ }
+
+ // C1 << (C2 - X) -> (C1 << C2) >> X
+ // C1 >> (C2 - X) -> (C1 >> C2) << X
+ // X must be >=0 (checked by NUWSub).
+ // Also match (X ^ C2) if equivalent to (C2 - X).
+ uint64_t C2;
+ Value *X;
+ if (match(Op1, m_NUWSub(m_ConstantInt(C2), m_Value(X))) ||
+ (match(Op1, m_Xor(m_Value(X), m_ConstantInt(C2))) && isMask_64(C2) &&
+ (C2 | computeKnownBits(X, &I).Zero).isAllOnes())) {
if (I.getOpcode() == Instruction::Shl) {
- NewShiftOp->setHasNoUnsignedWrap(I.hasNoUnsignedWrap());
- } else {
- NewShiftOp->setIsExact();
+ if (AC->countl_zero() >= C2)
+ return BinaryOperator::CreateExactLShr(
+ ConstantInt::get(Ty, AC->shl(C2)), X);
+ if (AC->countl_one() > C2)
+ return BinaryOperator::CreateExactAShr(
+ ConstantInt::get(Ty, AC->shl(C2)), X);
+ } else if (AC->countr_zero() >= C2) {
+ APInt C =
+ I.getOpcode() == Instruction::LShr ? AC->lshr(C2) : AC->ashr(C2);
+ return BinaryOperator::CreateShl(ConstantInt::get(Ty, C), X);
}
- return NewShiftOp;
}
}
diff --git a/llvm/test/Transforms/InstCombine/shift-sub.ll b/llvm/test/Transforms/InstCombine/shift-sub.ll
index 1d50867f756f2..9c5e49b472614 100644
--- a/llvm/test/Transforms/InstCombine/shift-sub.ll
+++ b/llvm/test/Transforms/InstCombine/shift-sub.ll
@@ -5,8 +5,7 @@ declare void @use(i8)
define i8 @shl_sub(i8 %x) {
; CHECK-LABEL: @shl_sub(
-; CHECK-NEXT: [[S:%.*]] = sub nuw i8 4, [[X:%.*]]
-; CHECK-NEXT: [[R:%.*]] = shl i8 2, [[S]]
+; CHECK-NEXT: [[R:%.*]] = lshr exact i8 32, [[X:%.*]]
; CHECK-NEXT: ret i8 [[R]]
;
%s = sub nuw i8 4, %x
@@ -16,8 +15,7 @@ define i8 @shl_sub(i8 %x) {
define i8 @shl_sub_maxlzero(i8 %x) {
; CHECK-LABEL: @shl_sub_maxlzero(
-; CHECK-NEXT: [[S:%.*]] = sub nuw i8 6, [[X:%.*]]
-; CHECK-NEXT: [[R:%.*]] = shl i8 3, [[S]]
+; CHECK-NEXT: [[R:%.*]] = lshr exact i8 -64, [[X:%.*]]
; CHECK-NEXT: ret i8 [[R]]
;
%s = sub nuw i8 6, %x
@@ -27,8 +25,7 @@ define i8 @shl_sub_maxlzero(i8 %x) {
define i8 @shl_sub_maxlone(i8 %x) {
; CHECK-LABEL: @shl_sub_maxlone(
-; CHECK-NEXT: [[S:%.*]] = sub nuw i8 6, [[X:%.*]]
-; CHECK-NEXT: [[R:%.*]] = shl i8 -2, [[S]]
+; CHECK-NEXT: [[R:%.*]] = ashr exact i8 -128, [[X:%.*]]
; CHECK-NEXT: ret i8 [[R]]
;
%s = sub nuw i8 6, %x
@@ -40,7 +37,7 @@ define i8 @shl_sub_multiuse(i8 %x) {
; CHECK-LABEL: @shl_sub_multiuse(
; CHECK-NEXT: [[S:%.*]] = sub nuw i8 3, [[X:%.*]]
; CHECK-NEXT: call void @use(i8 [[S]])
-; CHECK-NEXT: [[R:%.*]] = shl nuw nsw i8 2, [[S]]
+; CHECK-NEXT: [[R:%.*]] = lshr exact i8 16, [[X]]
; CHECK-NEXT: ret i8 [[R]]
;
%s = sub nuw i8 3, %x
@@ -51,8 +48,7 @@ define i8 @shl_sub_multiuse(i8 %x) {
define <2 x i8> @shl_sub_vec_splat(<2 x i8> %x) {
; CHECK-LABEL: @shl_sub_vec_splat(
-; CHECK-NEXT: [[S:%.*]] = sub nuw <2 x i8> splat (i8 4), [[X:%.*]]
-; CHECK-NEXT: [[R:%.*]] = shl <2 x i8> splat (i8 3), [[S]]
+; CHECK-NEXT: [[R:%.*]] = lshr exact <2 x i8> splat (i8 48), [[X:%.*]]
; CHECK-NEXT: ret <2 x i8> [[R]]
;
%s = sub nuw <2 x i8> <i8 4, i8 4>, %x
@@ -118,8 +114,7 @@ define i8 @shl_sub_negative_nolone(i8 %x) {
define i8 @shl_xor(i8 %x) {
; CHECK-LABEL: @shl_xor(
; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 3
-; CHECK-NEXT: [[S:%.*]] = xor i8 [[M]], 3
-; CHECK-NEXT: [[R:%.*]] = shl nuw nsw i8 2, [[S]]
+; CHECK-NEXT: [[R:%.*]] = lshr exact i8 16, [[M]]
; CHECK-NEXT: ret i8 [[R]]
;
%m = and i8 %x, 3
@@ -131,8 +126,7 @@ define i8 @shl_xor(i8 %x) {
define i16 @shl_xor_i2(i2 %x) {
; CHECK-LABEL: @shl_xor_i2(
; CHECK-NEXT: [[M:%.*]] = zext i2 [[X:%.*]] to i16
-; CHECK-NEXT: [[S:%.*]] = xor i16 [[M]], 7
-; CHECK-NEXT: [[R:%.*]] = shl nuw nsw i16 2, [[S]]
+; CHECK-NEXT: [[R:%.*]] = lshr exact i16 256, [[M]]
; CHECK-NEXT: ret i16 [[R]]
;
%m = zext i2 %x to i16
@@ -146,7 +140,7 @@ define i8 @shl_xor_multiuse(i8 %x) {
; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 3
; CHECK-NEXT: [[S:%.*]] = xor i8 [[M]], 3
; CHECK-NEXT: call void @use(i8 [[S]])
-; CHECK-NEXT: [[R:%.*]] = shl nuw nsw i8 2, [[S]]
+; CHECK-NEXT: [[R:%.*]] = lshr exact i8 16, [[M]]
; CHECK-NEXT: ret i8 [[R]]
;
%m = and i8 %x, 3
@@ -159,8 +153,7 @@ define i8 @shl_xor_multiuse(i8 %x) {
define <2 x i8> @shl_xor_vec_splat(<2 x i8> %x) {
; CHECK-LABEL: @shl_xor_vec_splat(
; CHECK-NEXT: [[M:%.*]] = and <2 x i8> [[X:%.*]], splat (i8 3)
-; CHECK-NEXT: [[S:%.*]] = xor <2 x i8> [[M]], splat (i8 3)
-; CHECK-NEXT: [[R:%.*]] = shl nuw nsw <2 x i8> splat (i8 5), [[S]]
+; CHECK-NEXT: [[R:%.*]] = lshr exact <2 x i8> splat (i8 40), [[M]]
; CHECK-NEXT: ret <2 x i8> [[R]]
;
%m = and <2 x i8> <i8 3, i8 3>, %x
@@ -221,8 +214,7 @@ define i8 @shl_xor_negative_abovemask(i8 %x) {
define i8 @lshr_sub(i8 %x) {
; CHECK-LABEL: @lshr_sub(
-; CHECK-NEXT: [[S:%.*]] = sub nuw i8 3, [[X:%.*]]
-; CHECK-NEXT: [[R:%.*]] = lshr exact i8 48, [[S]]
+; CHECK-NEXT: [[R:%.*]] = shl i8 6, [[X:%.*]]
; CHECK-NEXT: ret i8 [[R]]
;
%s = sub nuw i8 3, %x
@@ -232,8 +224,7 @@ define i8 @lshr_sub(i8 %x) {
define i8 @lshr_sub_maxtzero(i8 %x) {
; CHECK-LABEL: @lshr_sub_maxtzero(
-; CHECK-NEXT: [[S:%.*]] = sub nuw i8 5, [[X:%.*]]
-; CHECK-NEXT: [[R:%.*]] = lshr i8 32, [[S]]
+; CHECK-NEXT: [[R:%.*]] = shl nuw i8 1, [[X:%.*]]
; CHECK-NEXT: ret i8 [[R]]
;
%s = sub nuw i8 5, %x
@@ -245,7 +236,7 @@ define i8 @lshr_sub_multiuse(i8 %x) {
; CHECK-LABEL: @lshr_sub_multiuse(
; CHECK-NEXT: [[S:%.*]] = sub nuw i8 3, [[X:%.*]]
; CHECK-NEXT: call void @use(i8 [[S]])
-; CHECK-NEXT: [[R:%.*]] = lshr exact i8 32, [[S]]
+; CHECK-NEXT: [[R:%.*]] = shl i8 4, [[X]]
; CHECK-NEXT: ret i8 [[R]]
;
%s = sub nuw i8 3, %x
@@ -256,8 +247,7 @@ define i8 @lshr_sub_multiuse(i8 %x) {
define <2 x i8> @lshr_sub_vec_splat(<2 x i8> %x) {
; CHECK-LABEL: @lshr_sub_vec_splat(
-; CHECK-NEXT: [[S:%.*]] = sub nuw <2 x i8> splat (i8 4), [[X:%.*]]
-; CHECK-NEXT: [[R:%.*]] = lshr <2 x i8> splat (i8 64), [[S]]
+; CHECK-NEXT: [[R:%.*]] = shl <2 x i8> splat (i8 4), [[X:%.*]]
; CHECK-NEXT: ret <2 x i8> [[R]]
;
%s = sub nuw <2 x i8> <i8 4, i8 4>, %x
@@ -312,8 +302,7 @@ define i8 @lshr_sub_negative_notrzero(i8 %x) {
define i8 @lshr_xor(i8 %x) {
; CHECK-LABEL: @lshr_xor(
; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 6
-; CHECK-NEXT: [[S:%.*]] = xor i8 [[M]], 7
-; CHECK-NEXT: [[R:%.*]] = lshr exact i8 -128, [[S]]
+; CHECK-NEXT: [[R:%.*]] = shl nuw nsw i8 1, [[M]]
; CHECK-NEXT: ret i8 [[R]]
;
%m = and i8 %x, 6
@@ -327,7 +316,7 @@ define i8 @lshr_xor_multiuse(i8 %x) {
; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 6
; CHECK-NEXT: [[S:%.*]] = xor i8 [[M]], 7
; CHECK-NEXT: call void @use(i8 [[S]])
-; CHECK-NEXT: [[R:%.*]] = lshr exact i8 -128, [[S]]
+; CHECK-NEXT: [[R:%.*]] = shl nuw nsw i8 1, [[M]]
; CHECK-NEXT: ret i8 [[R]]
;
%m = and i8 %x, 6
@@ -340,8 +329,7 @@ define i8 @lshr_xor_multiuse(i8 %x) {
define <2 x i8> @lshr_xor_vec_splat(<2 x i8> %x) {
; CHECK-LABEL: @lshr_xor_vec_splat(
; CHECK-NEXT: [[M:%.*]] = and <2 x i8> [[X:%.*]], <i8 2, i8 3>
-; CHECK-NEXT: [[S:%.*]] = xor <2 x i8> [[M]], splat (i8 3)
-; CHECK-NEXT: [[R:%.*]] = lshr exact <2 x i8> splat (i8 16), [[S]]
+; CHECK-NEXT: [[R:%.*]] = shl nuw nsw <2 x i8> splat (i8 2), [[M]]
; CHECK-NEXT: ret <2 x i8> [[R]]
;
%m = and <2 x i8> <i8 2, i8 3>, %x
@@ -404,8 +392,7 @@ define i8 @lshr_xor_negative_notrzero(i8 %x) {
define i8 @ashr_sub(i8 %x) {
; CHECK-LABEL: @ashr_sub(
-; CHECK-NEXT: [[S:%.*]] = sub nuw i8 3, [[X:%.*]]
-; CHECK-NEXT: [[R:%.*]] = ashr exact i8 -64, [[S]]
+; CHECK-NEXT: [[R:%.*]] = shl i8 -8, [[X:%.*]]
; CHECK-NEXT: ret i8 [[R]]
;
%s = sub nuw i8 3, %x
@@ -415,8 +402,7 @@ define i8 @ashr_sub(i8 %x) {
define i8 @ashr_sub_maxtzero(i8 %x) {
; CHECK-LABEL: @ashr_sub_maxtzero(
-; CHECK-NEXT: [[S:%.*]] = sub nuw i8 6, [[X:%.*]]
-; CHECK-NEXT: [[R:%.*]] = ashr i8 -64, [[S]]
+; CHECK-NEXT: [[R:%.*]] = shl nsw i8 -1, [[X:%.*]]
; CHECK-NEXT: ret i8 [[R]]
;
%s = sub nuw i8 6, %x
@@ -428,7 +414,7 @@ define i8 @ashr_sub_multiuse(i8 %x) {
; CHECK-LABEL: @ashr_sub_multiuse(
; CHECK-NEXT: [[S:%.*]] = sub nuw i8 3, [[X:%.*]]
; CHECK-NEXT: call void @use(i8 [[S]])
-; CHECK-NEXT: [[R:%.*]] = lshr exact i8 96, [[S]]
+; CHECK-NEXT: [[R:%.*]] = shl i8 12, [[X]]
; CHECK-NEXT: ret i8 [[R]]
;
%s = sub nuw i8 3, %x
@@ -439,8 +425,7 @@ define i8 @ashr_sub_multiuse(i8 %x) {
define <2 x i8> @ashr_sub_vec_splat(<2 x i8> %x) {
; CHECK-LABEL: @ashr_sub_vec_splat(
-; CHECK-NEXT: [[S:%.*]] = sub nuw <2 x i8> splat (i8 4), [[X:%.*]]
-; CHECK-NEXT: [[R:%.*]] = ashr <2 x i8> splat (i8 -64), [[S]]
+; CHECK-NEXT: [[R:%.*]] = shl <2 x i8> splat (i8 -4), [[X:%.*]]
; CHECK-NEXT: ret <2 x i8> [[R]]
;
%s = sub nuw <2 x i8> <i8 4, i8 4>, %x
>From 8a73ed611f174aebc0b0079fc55c1717941a6fab Mon Sep 17 00:00:00 2001
From: Piotr Fusik <p.fusik at samsung.com>
Date: Wed, 22 Apr 2026 08:07:47 +0200
Subject: [PATCH 5/7] [InstCombine][test] Test xor where C2 is not an
all-low-bits mask
---
llvm/test/Transforms/InstCombine/shift-sub.ll | 21 +++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)
diff --git a/llvm/test/Transforms/InstCombine/shift-sub.ll b/llvm/test/Transforms/InstCombine/shift-sub.ll
index 9c5e49b472614..b5b64b8221b10 100644
--- a/llvm/test/Transforms/InstCombine/shift-sub.ll
+++ b/llvm/test/Transforms/InstCombine/shift-sub.ll
@@ -135,6 +135,19 @@ define i16 @shl_xor_i2(i2 %x) {
ret i16 %r
}
+define i8 @shl_xor_notmask(i8 %x) {
+; CHECK-LABEL: @shl_xor_notmask(
+; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 2
+; CHECK-NEXT: [[S:%.*]] = xor i8 [[M]], 6
+; CHECK-NEXT: [[R:%.*]] = shl nuw i8 2, [[S]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %m = and i8 %x, 2
+ %s = xor i8 %m, 6
+ %r = shl i8 2, %s
+ ret i8 %r
+}
+
define i8 @shl_xor_multiuse(i8 %x) {
; CHECK-LABEL: @shl_xor_multiuse(
; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 3
@@ -188,8 +201,8 @@ define <2 x i8> @shl_xor_vec_non_splat(<2 x i8> %x) {
ret <2 x i8> %r
}
-define i8 @shl_xor_negative_notmask(i8 %x) {
-; CHECK-LABEL: @shl_xor_negative_notmask(
+define i8 @shl_xor_negative_notsub(i8 %x) {
+; CHECK-LABEL: @shl_xor_negative_notsub(
; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 3
; CHECK-NEXT: [[S:%.*]] = xor i8 [[M]], 2
; CHECK-NEXT: [[R:%.*]] = shl nuw nsw i8 2, [[S]]
@@ -364,8 +377,8 @@ define <2 x i8> @lshr_xor_vec_non_splat(<2 x i8> %x) {
ret <2 x i8> %r
}
-define i8 @lshr_xor_negative_notmask(i8 %x) {
-; CHECK-LABEL: @lshr_xor_negative_notmask(
+define i8 @lshr_xor_negative_notsub(i8 %x) {
+; CHECK-LABEL: @lshr_xor_negative_notsub(
; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 3
; CHECK-NEXT: [[S:%.*]] = xor i8 [[M]], 2
; CHECK-NEXT: [[R:%.*]] = lshr exact i8 96, [[S]]
>From e5e57f24725eede55cfe0912df8701ef6679ab4b Mon Sep 17 00:00:00 2001
From: Piotr Fusik <p.fusik at samsung.com>
Date: Wed, 22 Apr 2026 08:14:22 +0200
Subject: [PATCH 6/7] [InstCombine] Accept xor where C2 is not an all-low-bits
mask
---
llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp | 2 +-
llvm/test/Transforms/InstCombine/shift-sub.ll | 3 +--
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
index 461574f70652a..373a42c42b156 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
@@ -509,7 +509,7 @@ Instruction *InstCombinerImpl::commonShiftTransforms(BinaryOperator &I) {
uint64_t C2;
Value *X;
if (match(Op1, m_NUWSub(m_ConstantInt(C2), m_Value(X))) ||
- (match(Op1, m_Xor(m_Value(X), m_ConstantInt(C2))) && isMask_64(C2) &&
+ (match(Op1, m_Xor(m_Value(X), m_ConstantInt(C2))) &&
(C2 | computeKnownBits(X, &I).Zero).isAllOnes())) {
if (I.getOpcode() == Instruction::Shl) {
if (AC->countl_zero() >= C2)
diff --git a/llvm/test/Transforms/InstCombine/shift-sub.ll b/llvm/test/Transforms/InstCombine/shift-sub.ll
index b5b64b8221b10..cf6c173a4352c 100644
--- a/llvm/test/Transforms/InstCombine/shift-sub.ll
+++ b/llvm/test/Transforms/InstCombine/shift-sub.ll
@@ -138,8 +138,7 @@ define i16 @shl_xor_i2(i2 %x) {
define i8 @shl_xor_notmask(i8 %x) {
; CHECK-LABEL: @shl_xor_notmask(
; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 2
-; CHECK-NEXT: [[S:%.*]] = xor i8 [[M]], 6
-; CHECK-NEXT: [[R:%.*]] = shl nuw i8 2, [[S]]
+; CHECK-NEXT: [[R:%.*]] = lshr exact i8 -128, [[M]]
; CHECK-NEXT: ret i8 [[R]]
;
%m = and i8 %x, 2
>From 4c61945b063cc276ed0fdc38fcf9bcf8b7ec8b05 Mon Sep 17 00:00:00 2001
From: Piotr Fusik <p.fusik at samsung.com>
Date: Thu, 23 Apr 2026 08:02:54 +0200
Subject: [PATCH 7/7] [InstCombine] Set nuw/nsw on the shl.
---
.../InstCombine/InstCombineShifts.cpp | 16 ++++++---
llvm/test/Transforms/InstCombine/shift-sub.ll | 36 +++++++++++++++----
2 files changed, 41 insertions(+), 11 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
index 373a42c42b156..44b552fe3141a 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
@@ -504,7 +504,7 @@ Instruction *InstCombinerImpl::commonShiftTransforms(BinaryOperator &I) {
// C1 << (C2 - X) -> (C1 << C2) >> X
// C1 >> (C2 - X) -> (C1 >> C2) << X
- // X must be >=0 (checked by NUWSub).
+ // X must be u<= C2 (checked by NUWSub).
// Also match (X ^ C2) if equivalent to (C2 - X).
uint64_t C2;
Value *X;
@@ -519,9 +519,17 @@ Instruction *InstCombinerImpl::commonShiftTransforms(BinaryOperator &I) {
return BinaryOperator::CreateExactAShr(
ConstantInt::get(Ty, AC->shl(C2)), X);
} else if (AC->countr_zero() >= C2) {
- APInt C =
- I.getOpcode() == Instruction::LShr ? AC->lshr(C2) : AC->ashr(C2);
- return BinaryOperator::CreateShl(ConstantInt::get(Ty, C), X);
+ if (AC->isSignBitClear()) {
+ auto *Shl = BinaryOperator::CreateNUWShl(
+ ConstantInt::get(Ty, AC->lshr(C2)), X);
+ Shl->setHasNoSignedWrap();
+ return Shl;
+ }
+ if (I.getOpcode() == Instruction::LShr)
+ return BinaryOperator::CreateNUWShl(
+ ConstantInt::get(Ty, AC->lshr(C2)), X);
+ return BinaryOperator::CreateNSWShl(ConstantInt::get(Ty, AC->ashr(C2)),
+ X);
}
}
}
diff --git a/llvm/test/Transforms/InstCombine/shift-sub.ll b/llvm/test/Transforms/InstCombine/shift-sub.ll
index cf6c173a4352c..12d5469e36504 100644
--- a/llvm/test/Transforms/InstCombine/shift-sub.ll
+++ b/llvm/test/Transforms/InstCombine/shift-sub.ll
@@ -226,7 +226,7 @@ define i8 @shl_xor_negative_abovemask(i8 %x) {
define i8 @lshr_sub(i8 %x) {
; CHECK-LABEL: @lshr_sub(
-; CHECK-NEXT: [[R:%.*]] = shl i8 6, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = shl nuw nsw i8 6, [[X:%.*]]
; CHECK-NEXT: ret i8 [[R]]
;
%s = sub nuw i8 3, %x
@@ -234,9 +234,19 @@ define i8 @lshr_sub(i8 %x) {
ret i8 %r
}
+define i8 @lshr_sub_neg(i8 %x) {
+; CHECK-LABEL: @lshr_sub_neg(
+; CHECK-NEXT: [[R:%.*]] = shl nuw i8 24, [[X:%.*]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %s = sub nuw i8 3, %x
+ %r = lshr i8 -64, %s
+ ret i8 %r
+}
+
define i8 @lshr_sub_maxtzero(i8 %x) {
; CHECK-LABEL: @lshr_sub_maxtzero(
-; CHECK-NEXT: [[R:%.*]] = shl nuw i8 1, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = shl nuw nsw i8 1, [[X:%.*]]
; CHECK-NEXT: ret i8 [[R]]
;
%s = sub nuw i8 5, %x
@@ -248,7 +258,7 @@ define i8 @lshr_sub_multiuse(i8 %x) {
; CHECK-LABEL: @lshr_sub_multiuse(
; CHECK-NEXT: [[S:%.*]] = sub nuw i8 3, [[X:%.*]]
; CHECK-NEXT: call void @use(i8 [[S]])
-; CHECK-NEXT: [[R:%.*]] = shl i8 4, [[X]]
+; CHECK-NEXT: [[R:%.*]] = shl nuw nsw i8 4, [[X]]
; CHECK-NEXT: ret i8 [[R]]
;
%s = sub nuw i8 3, %x
@@ -259,7 +269,7 @@ define i8 @lshr_sub_multiuse(i8 %x) {
define <2 x i8> @lshr_sub_vec_splat(<2 x i8> %x) {
; CHECK-LABEL: @lshr_sub_vec_splat(
-; CHECK-NEXT: [[R:%.*]] = shl <2 x i8> splat (i8 4), [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = shl nuw nsw <2 x i8> splat (i8 4), [[X:%.*]]
; CHECK-NEXT: ret <2 x i8> [[R]]
;
%s = sub nuw <2 x i8> <i8 4, i8 4>, %x
@@ -323,6 +333,18 @@ define i8 @lshr_xor(i8 %x) {
ret i8 %r
}
+define i8 @lshr_xor_neg(i8 %x) {
+; CHECK-LABEL: @lshr_xor_neg(
+; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 3
+; CHECK-NEXT: [[R:%.*]] = shl nuw i8 28, [[M]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %m = and i8 %x, 3
+ %s = xor i8 %m, 3
+ %r = lshr i8 -32, %s
+ ret i8 %r
+}
+
define i8 @lshr_xor_multiuse(i8 %x) {
; CHECK-LABEL: @lshr_xor_multiuse(
; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 6
@@ -404,7 +426,7 @@ define i8 @lshr_xor_negative_notrzero(i8 %x) {
define i8 @ashr_sub(i8 %x) {
; CHECK-LABEL: @ashr_sub(
-; CHECK-NEXT: [[R:%.*]] = shl i8 -8, [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = shl nsw i8 -8, [[X:%.*]]
; CHECK-NEXT: ret i8 [[R]]
;
%s = sub nuw i8 3, %x
@@ -426,7 +448,7 @@ define i8 @ashr_sub_multiuse(i8 %x) {
; CHECK-LABEL: @ashr_sub_multiuse(
; CHECK-NEXT: [[S:%.*]] = sub nuw i8 3, [[X:%.*]]
; CHECK-NEXT: call void @use(i8 [[S]])
-; CHECK-NEXT: [[R:%.*]] = shl i8 12, [[X]]
+; CHECK-NEXT: [[R:%.*]] = shl nuw nsw i8 12, [[X]]
; CHECK-NEXT: ret i8 [[R]]
;
%s = sub nuw i8 3, %x
@@ -437,7 +459,7 @@ define i8 @ashr_sub_multiuse(i8 %x) {
define <2 x i8> @ashr_sub_vec_splat(<2 x i8> %x) {
; CHECK-LABEL: @ashr_sub_vec_splat(
-; CHECK-NEXT: [[R:%.*]] = shl <2 x i8> splat (i8 -4), [[X:%.*]]
+; CHECK-NEXT: [[R:%.*]] = shl nsw <2 x i8> splat (i8 -4), [[X:%.*]]
; CHECK-NEXT: ret <2 x i8> [[R]]
;
%s = sub nuw <2 x i8> <i8 4, i8 4>, %x
More information about the llvm-commits
mailing list