[llvm] [InstCombine] Fold lshr C1, (add X, -C2) -> lshr (C1 << C2), X (PR #222351)
Julian Gabriel via llvm-commits
llvm-commits at lists.llvm.org
Mon Sep 21 14:00:19 PDT 2026
https://github.com/ParcivalLTD updated https://github.com/llvm/llvm-project/pull/222351
>From 170bf73b7249317ef1169b83656195403928f45f Mon Sep 17 00:00:00 2001
From: Julian Gabriel <juliangabriel570 at gmail.com>
Date: Wed, 9 Sep 2026 17:02:59 +0200
Subject: [PATCH 1/2] [InstCombine] Fold pre-shift of constant with negative
offset when shift operand is in-range
Relax the pre-shift fold in commonShiftTransforms:
C << (X - offset) -> (C >> offset) << X
C >> (X - offset) -> (C << offset) >> X
Previously, this fold required exact on lshr/ashr or nsw/nuw on shl
to guarantee correctness for out-of-range shifts. If the shift operand
X is known to be less than BitWidth (via computeKnownBits), no poison
is introduced and the fold can be safely performed without these flags.
Fixes #222281
---
.../InstCombine/InstCombineShifts.cpp | 54 ++++++---
llvm/test/Transforms/InstCombine/shift-add.ll | 106 ++++++++++++++++++
2 files changed, 144 insertions(+), 16 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
index 7e32abcd6e9ba5..ad34bd17dabf81 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
@@ -474,31 +474,53 @@ Instruction *InstCombinerImpl::commonShiftTransforms(BinaryOperator &I) {
(-*AddC).ult(BitWidth)) {
unsigned PosOffset = (-*AddC).getZExtValue();
- auto isSuitableForPreShift = [PosOffset, &I, AC]() {
+ auto isRoundtrippable = [PosOffset, &I, AC]() {
switch (I.getOpcode()) {
default:
return false;
case Instruction::Shl:
- return (I.hasNoSignedWrap() || I.hasNoUnsignedWrap()) &&
- AC->eq(AC->lshr(PosOffset).shl(PosOffset));
+ return AC->eq(AC->lshr(PosOffset).shl(PosOffset));
case Instruction::LShr:
- return I.isExact() && AC->eq(AC->shl(PosOffset).lshr(PosOffset));
+ return AC->eq(AC->shl(PosOffset).lshr(PosOffset));
case Instruction::AShr:
- return I.isExact() && AC->eq(AC->shl(PosOffset).ashr(PosOffset));
+ return 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();
+
+ if (isRoundtrippable()) {
+ // The shift flags (exact/nsw/nuw) guarantee correctness for any
+ // shift amount. Without them, we need X to be known less than
+ // BitWidth to avoid converting a defined result to poison.
+ bool HasShiftFlags;
+ switch (I.getOpcode()) {
+ default:
+ HasShiftFlags = false;
+ break;
+ case Instruction::Shl:
+ HasShiftFlags = I.hasNoSignedWrap() || I.hasNoUnsignedWrap();
+ break;
+ case Instruction::LShr:
+ case Instruction::AShr:
+ HasShiftFlags = I.isExact();
+ break;
+ }
+
+ if (HasShiftFlags ||
+ computeKnownBits(A, &I).getMaxValue().ult(BitWidth)) {
+ Constant *NewC = ConstantInt::get(
+ Ty, I.getOpcode() == Instruction::Shl ? AC->lshr(PosOffset)
+ : AC->shl(PosOffset));
+ BinaryOperator *NewShiftOp =
+ BinaryOperator::Create(I.getOpcode(), NewC, A);
+ if (HasShiftFlags) {
+ if (I.getOpcode() == Instruction::Shl) {
+ NewShiftOp->setHasNoUnsignedWrap(I.hasNoUnsignedWrap());
+ } else {
+ NewShiftOp->setIsExact();
+ }
+ }
+ return NewShiftOp;
}
- return NewShiftOp;
}
}
diff --git a/llvm/test/Transforms/InstCombine/shift-add.ll b/llvm/test/Transforms/InstCombine/shift-add.ll
index 81cbc2ac23b5f2..d55e73a8416280 100644
--- a/llvm/test/Transforms/InstCombine/shift-add.ll
+++ b/llvm/test/Transforms/InstCombine/shift-add.ll
@@ -804,3 +804,109 @@ define <2 x i8> @lshr_fold_or_disjoint_cnt_out_of_bounds(<2 x i8> %x) {
%r = lshr <2 x i8> <i8 2, i8 3>, %a
ret <2 x i8> %r
}
+
+; Pre-shift fold without exact/nsw/nuw flags, guarded by known range of X.
+
+define i32 @lshr_add_negative_no_exact_known_range(i32 %x) {
+; CHECK-LABEL: @lshr_add_negative_no_exact_known_range(
+; CHECK-NEXT: [[MASKED:%.*]] = and i32 [[X:%.*]], 15
+; CHECK-NEXT: [[R:%.*]] = lshr i32 4, [[MASKED]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %masked = and i32 %x, 15
+ %a = add i32 %masked, -1
+ %r = lshr i32 2, %a
+ ret i32 %r
+}
+
+define i32 @shl_add_negative_no_flags_known_range(i32 %x) {
+; CHECK-LABEL: @shl_add_negative_no_flags_known_range(
+; CHECK-NEXT: [[MASKED:%.*]] = and i32 [[X:%.*]], 15
+; CHECK-NEXT: [[R:%.*]] = shl nuw nsw i32 3, [[MASKED]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %masked = and i32 %x, 15
+ %a = add i32 %masked, -2
+ %r = shl i32 12, %a
+ ret i32 %r
+}
+
+define i32 @ashr_add_negative_no_exact_known_range(i32 %x) {
+; CHECK-LABEL: @ashr_add_negative_no_exact_known_range(
+; CHECK-NEXT: [[MASKED:%.*]] = and i32 [[X:%.*]], 15
+; CHECK-NEXT: [[R:%.*]] = ashr i32 -8, [[MASKED]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %masked = and i32 %x, 15
+ %a = add i32 %masked, -1
+ %r = ashr i32 -4, %a
+ ret i32 %r
+}
+
+define <2 x i32> @lshr_add_negative_no_exact_known_range_vec(<2 x i32> %x) {
+; CHECK-LABEL: @lshr_add_negative_no_exact_known_range_vec(
+; CHECK-NEXT: [[MASKED:%.*]] = and <2 x i32> [[X:%.*]], splat (i32 15)
+; CHECK-NEXT: [[R:%.*]] = lshr <2 x i32> splat (i32 4), [[MASKED]]
+; CHECK-NEXT: ret <2 x i32> [[R]]
+;
+ %masked = and <2 x i32> %x, <i32 15, i32 15>
+ %a = add <2 x i32> %masked, <i32 -1, i32 -1>
+ %r = lshr <2 x i32> <i32 2, i32 2>, %a
+ ret <2 x i32> %r
+}
+
+; Motivating example from issue #222281
+define i32 @from_sub_issue222281(i8 %x) {
+; CHECK-LABEL: @from_sub_issue222281(
+; CHECK-NEXT: [[M:%.*]] = and i8 [[X:%.*]], 120
+; CHECK-NEXT: [[V:%.*]] = or disjoint i8 [[M]], -128
+; CHECK-NEXT: [[TZ:%.*]] = call range(i8 3, 8) i8 @llvm.cttz.i8(i8 [[V]], i1 true)
+; CHECK-NEXT: [[Z:%.*]] = zext nneg i8 [[TZ]] to i32
+; CHECK-NEXT: [[R:%.*]] = lshr exact i32 512, [[Z]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %m = and i8 %x, -8
+ %v = or i8 %m, -128
+ %tz = call i8 @llvm.cttz.i8(i8 %v, i1 true)
+ %z = zext nneg i8 %tz to i32
+ %s = sub nuw nsw i32 %z, 3
+ %r = lshr i32 64, %s
+ ret i32 %r
+}
+
+; negative test: X range is unknown, fold should not fire without exact
+define i32 @lshr_add_negative_no_exact_unknown_range(i32 %x) {
+; CHECK-LABEL: @lshr_add_negative_no_exact_unknown_range(
+; CHECK-NEXT: [[A:%.*]] = add i32 [[X:%.*]], -1
+; CHECK-NEXT: [[R:%.*]] = lshr i32 2, [[A]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %a = add i32 %x, -1
+ %r = lshr i32 2, %a
+ ret i32 %r
+}
+
+; negative test: constant not roundtrippable (3 >> 1 loses bit 0 for shl)
+define i32 @shl_add_negative_no_flags_not_roundtrippable(i32 range(i32 1, 16) %x) {
+; CHECK-LABEL: @shl_add_negative_no_flags_not_roundtrippable(
+; CHECK-NEXT: [[A:%.*]] = add nsw i32 [[X:%.*]], -1
+; CHECK-NEXT: [[R:%.*]] = shl i32 3, [[A]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %a = add nsw i32 %x, -1
+ %r = shl i32 3, %a
+ ret i32 %r
+}
+
+; negative test: constant not roundtrippable (64 << 28 overflows 32-bit integer for lshr)
+define i32 @lshr_add_negative_no_exact_overflow(i32 range(i32 28, 32) %x) {
+; CHECK-LABEL: @lshr_add_negative_no_exact_overflow(
+; CHECK-NEXT: [[A:%.*]] = add nsw i32 [[X:%.*]], -28
+; CHECK-NEXT: [[R:%.*]] = lshr exact i32 64, [[A]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %a = add nsw i32 %x, -28
+ %r = lshr i32 64, %a
+ ret i32 %r
+}
+
>From 06ac6ffc0077ff0fa79257a6af93af32a82c14c6 Mon Sep 17 00:00:00 2001
From: Julian Gabriel <juliangabriel570 at gmail.com>
Date: Mon, 21 Sep 2026 22:23:03 +0200
Subject: [PATCH 2/2] Simplify pre-shift fold and add boundary tests
Merge the flag and roundtrip checks into a single switch, document the
X u< BitWidth condition, and add tests for the BitWidth boundary.
---
.../InstCombine/InstCombineShifts.cpp | 80 ++++++++-----------
llvm/test/Transforms/InstCombine/shift-add.ll | 37 +++++++++
2 files changed, 71 insertions(+), 46 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
index ad34bd17dabf81..6f0dd0bc4f5108 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
@@ -469,58 +469,46 @@ Instruction *InstCombinerImpl::commonShiftTransforms(BinaryOperator &I) {
// C << (X - AddC) --> (C >> AddC) << X
// and
// C >> (X - AddC) --> (C << AddC) >> X
+ // This requires that no set bits of C are lost by the pre-shift, and that
+ // X is a valid shift amount whenever (X - AddC) is. The latter is implied
+ // by nuw/nsw/exact on the original shift, or holds if X u< BitWidth.
const APInt *AddC;
if (match(Op1, m_Add(m_Value(A), m_APInt(AddC))) && AddC->isNegative() &&
(-*AddC).ult(BitWidth)) {
unsigned PosOffset = (-*AddC).getZExtValue();
- auto isRoundtrippable = [PosOffset, &I, AC]() {
- switch (I.getOpcode()) {
- default:
- return false;
- case Instruction::Shl:
- return AC->eq(AC->lshr(PosOffset).shl(PosOffset));
- case Instruction::LShr:
- return AC->eq(AC->shl(PosOffset).lshr(PosOffset));
- case Instruction::AShr:
- return AC->eq(AC->shl(PosOffset).ashr(PosOffset));
- }
- };
-
- if (isRoundtrippable()) {
- // The shift flags (exact/nsw/nuw) guarantee correctness for any
- // shift amount. Without them, we need X to be known less than
- // BitWidth to avoid converting a defined result to poison.
- bool HasShiftFlags;
- switch (I.getOpcode()) {
- default:
- HasShiftFlags = false;
- break;
- case Instruction::Shl:
- HasShiftFlags = I.hasNoSignedWrap() || I.hasNoUnsignedWrap();
- break;
- case Instruction::LShr:
- case Instruction::AShr:
- HasShiftFlags = I.isExact();
- break;
- }
+ bool HasShiftFlags;
+ bool IsRoundtrippable;
+ switch (I.getOpcode()) {
+ default:
+ llvm_unreachable("Unexpected shift opcode");
+ case Instruction::Shl:
+ HasShiftFlags = I.hasNoSignedWrap() || I.hasNoUnsignedWrap();
+ IsRoundtrippable = AC->eq(AC->lshr(PosOffset).shl(PosOffset));
+ break;
+ case Instruction::LShr:
+ HasShiftFlags = I.isExact();
+ IsRoundtrippable = AC->eq(AC->shl(PosOffset).lshr(PosOffset));
+ break;
+ case Instruction::AShr:
+ HasShiftFlags = I.isExact();
+ IsRoundtrippable = AC->eq(AC->shl(PosOffset).ashr(PosOffset));
+ break;
+ }
- if (HasShiftFlags ||
- computeKnownBits(A, &I).getMaxValue().ult(BitWidth)) {
- Constant *NewC = ConstantInt::get(
- Ty, I.getOpcode() == Instruction::Shl ? AC->lshr(PosOffset)
- : AC->shl(PosOffset));
- BinaryOperator *NewShiftOp =
- BinaryOperator::Create(I.getOpcode(), NewC, A);
- if (HasShiftFlags) {
- if (I.getOpcode() == Instruction::Shl) {
- NewShiftOp->setHasNoUnsignedWrap(I.hasNoUnsignedWrap());
- } else {
- NewShiftOp->setIsExact();
- }
- }
- return NewShiftOp;
- }
+ if (IsRoundtrippable &&
+ (HasShiftFlags ||
+ computeKnownBits(A, &I).getMaxValue().ult(BitWidth))) {
+ 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(I.isExact());
+ return NewShiftOp;
}
}
diff --git a/llvm/test/Transforms/InstCombine/shift-add.ll b/llvm/test/Transforms/InstCombine/shift-add.ll
index d55e73a8416280..6439ec0f51af87 100644
--- a/llvm/test/Transforms/InstCombine/shift-add.ll
+++ b/llvm/test/Transforms/InstCombine/shift-add.ll
@@ -910,3 +910,40 @@ define i32 @lshr_add_negative_no_exact_overflow(i32 range(i32 28, 32) %x) {
ret i32 %r
}
+; Boundary: X is at most 31 (BitWidth - 1), so the fold is valid.
+define i32 @lshr_add_negative_no_exact_max_bitwidth_minus_one(i32 %x) {
+; CHECK-LABEL: @lshr_add_negative_no_exact_max_bitwidth_minus_one(
+; CHECK-NEXT: [[MASKED:%.*]] = and i32 [[X:%.*]], 31
+; CHECK-NEXT: [[R:%.*]] = lshr i32 4, [[MASKED]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %masked = and i32 %x, 31
+ %a = add i32 %masked, -1
+ %r = lshr i32 2, %a
+ ret i32 %r
+}
+
+; negative test: X may be 32 (BitWidth), where the original shift is defined
+; but the pre-shifted one would be poison.
+define i32 @lshr_add_negative_no_exact_max_bitwidth(i32 range(i32 1, 33) %x) {
+; CHECK-LABEL: @lshr_add_negative_no_exact_max_bitwidth(
+; CHECK-NEXT: [[A:%.*]] = add nsw i32 [[X:%.*]], -1
+; CHECK-NEXT: [[R:%.*]] = lshr i32 2, [[A]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %a = add nsw i32 %x, -1
+ %r = lshr i32 2, %a
+ ret i32 %r
+}
+
+; negative test: same boundary for shl.
+define i32 @shl_add_negative_no_flags_max_bitwidth(i32 range(i32 2, 34) %x) {
+; CHECK-LABEL: @shl_add_negative_no_flags_max_bitwidth(
+; CHECK-NEXT: [[A:%.*]] = add nsw i32 [[X:%.*]], -2
+; CHECK-NEXT: [[R:%.*]] = shl i32 12, [[A]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %a = add nsw i32 %x, -2
+ %r = shl i32 12, %a
+ ret i32 %r
+}
More information about the llvm-commits
mailing list