[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