[llvm] [InstCombine] Fold trunc(lshr(add(shl(X, ShAmt), C), ShrAmt)) (PR #214562)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Aug 12 04:53:34 PDT 2026
https://github.com/Ineshmcw updated https://github.com/llvm/llvm-project/pull/214562
>From 697bef34604954e97751b64f1858bae4e9ebd987 Mon Sep 17 00:00:00 2001
From: Sree Inesh Murugan Palanisamy <inesh.murugan at multicorewareinc.com>
Date: Thu, 6 Aug 2026 15:39:26 +0530
Subject: [PATCH 1/3] [InstCombine] Fold trunc(lshr(BinOp(shl(X, ShAmt), C),
ShrAmt)) (llvm#214339)
InstCombine currently misses simplifying this pattern to a single
shl+BinOp when the lshr is provably redundant. For example,
'trunc i8 (lshr (add (shl X, 12), 34816), 8)' always equals
'add (shl X, 4), 136' for every possible X. However, the chain from the
issue's zlib_deflate-derived example was left as 4 instructions instead
of 3. The same simplification also holds when the inner operator is
'or', 'xor', or 'and' instead of 'add'.
This patch adds a new case to the LShr handling in
SimplifyDemandedUseBits that recognizes this pattern for 'add', 'or',
'xor', and 'and', and folds it into the simplified shl+BinOp form.
'add' requires C's low ShrAmt bits to be zero, since it doesn't
distribute over 'lshr' unconditionally like the other three. Placing
the fold here, rather than in visitTrunc, makes it apply whenever
demanded bits are narrowed on the lshr's result, not only for trunc.
Fixes #214339
---
.../InstCombineSimplifyDemanded.cpp | 20 ++
.../InstCombine/trunc-lshr-add-shl.ll | 276 ++++++++++++++++++
2 files changed, 296 insertions(+)
create mode 100644 llvm/test/Transforms/InstCombine/trunc-lshr-add-shl.ll
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
index 0a7c0375106f2..06819a6339de4 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
@@ -863,6 +863,26 @@ Value *InstCombinerImpl::SimplifyDemandedUseBits(Instruction *I,
X, ConstantInt::get(X->getType(), Factor->lshr(ShiftAmt)));
return InsertNewInstWith(Mul, I->getIterator());
}
+
+ // (BinOp (shl InnerX, InnerShAmt), BOC) >> ShiftAmt -->
+ // BinOp (shl InnerX, InnerShAmt - ShiftAmt), (BOC >> ShiftAmt)
+ BinaryOperator *BO;
+ Value *InnerX;
+ const APInt *InnerShAmt, *BOC;
+ if (match(I->getOperand(0), m_OneUse(m_BinOp(BO))) &&
+ match(BO->getOperand(0),
+ m_Shl(m_Value(InnerX), m_APInt(InnerShAmt))) &&
+ match(BO->getOperand(1), m_APInt(BOC)) &&
+ InnerShAmt->uge(ShiftAmt) &&
+ (BO->isBitwiseLogicOp() || (BO->getOpcode() == Instruction::Add &&
+ BOC->countr_zero() >= ShiftAmt))) {
+ Value *NewShl = Builder.CreateShl(
+ InnerX, ConstantInt::get(VTy, *InnerShAmt - ShiftAmt));
+ Constant *NewBOC = ConstantInt::get(VTy, BOC->lshr(ShiftAmt));
+ Instruction *NewBO =
+ BinaryOperator::Create(BO->getOpcode(), NewShl, NewBOC);
+ return InsertNewInstWith(NewBO, I->getIterator());
+ }
}
// Unsigned shift right.
diff --git a/llvm/test/Transforms/InstCombine/trunc-lshr-add-shl.ll b/llvm/test/Transforms/InstCombine/trunc-lshr-add-shl.ll
new file mode 100644
index 0000000000000..62abbd0284c90
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/trunc-lshr-add-shl.ll
@@ -0,0 +1,276 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -S -passes=instcombine < %s | FileCheck %s
+
+; trunc(lshr(BinOp(shl(X, ShAmt), C), ShrAmt))
+; --> trunc(BinOp(shl(X, ShAmt - ShrAmt), C >> ShrAmt))
+; where BinOp is add, or, xor, or and. or/xor/and distribute over lshr
+; unconditionally; add additionally needs C's low ShrAmt bits to be zero.
+;
+; %x is the result of an opaque call in most tests below purely so the
+; RUN line's output is stable and easy to read; InstCombine's generic
+; "shrink the whole expression to a smaller type" machinery still applies
+; on top of this fold once the lshr is gone, which is expected -- it just
+; means the fold enables further cleanup, not that it's being bypassed.
+declare i32 @opaque(i32)
+declare void @use32(i32)
+
+define i8 @src_from_issue(i32 %x) {
+; CHECK-LABEL: define i8 @src_from_issue(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[X_TR:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT: [[TMP1:%.*]] = shl i8 [[X_TR]], 4
+; CHECK-NEXT: [[V17:%.*]] = add i8 [[TMP1]], -120
+; CHECK-NEXT: ret i8 [[V17]]
+;
+ %v2 = shl i32 %x, 12
+ %v3 = add i32 %v2, 34816
+ %v16 = lshr i32 %v3, 8
+ %v17 = trunc i32 %v16 to i8
+ ret i8 %v17
+}
+
+define i8 @positive_basic(i32 %y) {
+; CHECK-LABEL: define i8 @positive_basic(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT: [[X_TR:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT: [[TMP1:%.*]] = shl i8 [[X_TR]], 4
+; CHECK-NEXT: [[V17:%.*]] = add i8 [[TMP1]], -120
+; CHECK-NEXT: ret i8 [[V17]]
+;
+ %x = call i32 @opaque(i32 %y)
+ %v2 = shl i32 %x, 12
+ %v3 = add i32 %v2, 34816
+ %v16 = lshr i32 %v3, 8
+ %v17 = trunc i32 %v16 to i8
+ ret i8 %v17
+}
+
+; ShrAmt == ShAmt: the new shift amount is 0.
+define i8 @positive_boundary_shramt_eq_shamt(i32 %y) {
+; CHECK-LABEL: define i8 @positive_boundary_shramt_eq_shamt(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT: [[V17:%.*]] = add i8 [[TMP1]], -120
+; CHECK-NEXT: ret i8 [[V17]]
+;
+ %x = call i32 @opaque(i32 %y)
+ %v2 = shl i32 %x, 8
+ %v3 = add i32 %v2, 34816
+ %v16 = lshr i32 %v3, 8
+ %v17 = trunc i32 %v16 to i8
+ ret i8 %v17
+}
+
+; ShrAmt == SrcWidth - DestWidth (24): still safe, right at the boundary.
+define i8 @positive_boundary_shramt_eq_srcwidth_minus_destwidth(i32 %y) {
+; CHECK-LABEL: define i8 @positive_boundary_shramt_eq_srcwidth_minus_destwidth(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT: [[V17:%.*]] = add i8 [[TMP1]], 1
+; CHECK-NEXT: ret i8 [[V17]]
+;
+ %x = call i32 @opaque(i32 %y)
+ %v2 = shl i32 %x, 24
+ %v3 = add i32 %v2, 16777216
+ %v16 = lshr i32 %v3, 24
+ %v17 = trunc i32 %v16 to i8
+ ret i8 %v17
+}
+
+; The fold also applies when the inner binop is or/xor/and instead of add.
+define i8 @positive_or(i32 %y) {
+; CHECK-LABEL: define i8 @positive_or(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT: [[X_TR:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT: [[TMP1:%.*]] = shl i8 [[X_TR]], 4
+; CHECK-NEXT: [[V17:%.*]] = or i8 [[TMP1]], -120
+; CHECK-NEXT: ret i8 [[V17]]
+;
+ %x = call i32 @opaque(i32 %y)
+ %v2 = shl i32 %x, 12
+ %v3 = or i32 %v2, 34816
+ %v16 = lshr i32 %v3, 8
+ %v17 = trunc i32 %v16 to i8
+ ret i8 %v17
+}
+
+define i8 @positive_xor(i32 %y) {
+; CHECK-LABEL: define i8 @positive_xor(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT: [[X_TR:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT: [[TMP1:%.*]] = shl i8 [[X_TR]], 4
+; CHECK-NEXT: [[V17:%.*]] = xor i8 [[TMP1]], -120
+; CHECK-NEXT: ret i8 [[V17]]
+;
+ %x = call i32 @opaque(i32 %y)
+ %v2 = shl i32 %x, 12
+ %v3 = xor i32 %v2, 34816
+ %v16 = lshr i32 %v3, 8
+ %v17 = trunc i32 %v16 to i8
+ ret i8 %v17
+}
+
+define i8 @positive_and(i32 %y) {
+; CHECK-LABEL: define i8 @positive_and(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT: [[X_TR:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT: [[TMP1:%.*]] = shl i8 [[X_TR]], 4
+; CHECK-NEXT: [[V17:%.*]] = and i8 [[TMP1]], -128
+; CHECK-NEXT: ret i8 [[V17]]
+;
+ %x = call i32 @opaque(i32 %y)
+ %v2 = shl i32 %x, 12
+ %v3 = and i32 %v2, 34816
+ %v16 = lshr i32 %v3, 8
+ %v17 = trunc i32 %v16 to i8
+ ret i8 %v17
+}
+
+; Unlike add, or/xor/and distribute over lshr unconditionally, so they still
+; fold even with a constant (34817, odd) whose low bits aren't aligned to
+; ShrAmt -- compare against @negative_addc_not_aligned below, which uses the
+; same constant with add and correctly does not fold.
+define i8 @positive_or_misaligned_c(i32 %y) {
+; CHECK-LABEL: define i8 @positive_or_misaligned_c(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT: [[X_TR:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT: [[TMP1:%.*]] = shl i8 [[X_TR]], 4
+; CHECK-NEXT: [[V17:%.*]] = or i8 [[TMP1]], -120
+; CHECK-NEXT: ret i8 [[V17]]
+;
+ %x = call i32 @opaque(i32 %y)
+ %v2 = shl i32 %x, 12
+ %v3 = or i32 %v2, 34817
+ %v16 = lshr i32 %v3, 8
+ %v17 = trunc i32 %v16 to i8
+ ret i8 %v17
+}
+
+define i8 @positive_xor_misaligned_c(i32 %y) {
+; CHECK-LABEL: define i8 @positive_xor_misaligned_c(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT: [[X_TR:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT: [[TMP1:%.*]] = shl i8 [[X_TR]], 4
+; CHECK-NEXT: [[V17:%.*]] = xor i8 [[TMP1]], -120
+; CHECK-NEXT: ret i8 [[V17]]
+;
+ %x = call i32 @opaque(i32 %y)
+ %v2 = shl i32 %x, 12
+ %v3 = xor i32 %v2, 34817
+ %v16 = lshr i32 %v3, 8
+ %v17 = trunc i32 %v16 to i8
+ ret i8 %v17
+}
+
+define i8 @positive_and_misaligned_c(i32 %y) {
+; CHECK-LABEL: define i8 @positive_and_misaligned_c(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT: [[X_TR:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT: [[TMP1:%.*]] = shl i8 [[X_TR]], 4
+; CHECK-NEXT: [[V17:%.*]] = and i8 [[TMP1]], -128
+; CHECK-NEXT: ret i8 [[V17]]
+;
+ %x = call i32 @opaque(i32 %y)
+ %v2 = shl i32 %x, 12
+ %v3 = and i32 %v2, 34817
+ %v16 = lshr i32 %v3, 8
+ %v17 = trunc i32 %v16 to i8
+ ret i8 %v17
+}
+
+; Negative test: ShrAmt (4) < ShAmt is fine, but here ShrAmt (8) > ShAmt (4),
+; which would require a negative shift amount, so the fold must not fire.
+define i8 @negative_shramt_gt_shamt(i32 %y) {
+; CHECK-LABEL: define i8 @negative_shramt_gt_shamt(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT: [[V2:%.*]] = shl i32 [[X]], 4
+; CHECK-NEXT: [[V3:%.*]] = add i32 [[V2]], 34816
+; CHECK-NEXT: [[V16:%.*]] = lshr i32 [[V3]], 8
+; CHECK-NEXT: [[V17:%.*]] = trunc i32 [[V16]] to i8
+; CHECK-NEXT: ret i8 [[V17]]
+;
+ %x = call i32 @opaque(i32 %y)
+ %v2 = shl i32 %x, 4
+ %v3 = add i32 %v2, 34816
+ %v16 = lshr i32 %v3, 8
+ %v17 = trunc i32 %v16 to i8
+ ret i8 %v17
+}
+
+; Negative test: AddC's low ShrAmt bits are not zero (34817 is not a
+; multiple of 256), so the lshr can't be distributed over the add exactly.
+; %v3 is kept alive via @use32 so an unrelated demanded-bits simplification
+; can't quietly clear that low bit before this fold's check ever runs.
+define i8 @negative_addc_not_aligned(i32 %y) {
+; CHECK-LABEL: define i8 @negative_addc_not_aligned(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT: [[V2:%.*]] = shl i32 [[X]], 12
+; CHECK-NEXT: [[V3:%.*]] = add i32 [[V2]], 34817
+; CHECK-NEXT: call void @use32(i32 [[V3]])
+; CHECK-NEXT: [[V16:%.*]] = lshr i32 [[V3]], 8
+; CHECK-NEXT: [[V17:%.*]] = trunc i32 [[V16]] to i8
+; CHECK-NEXT: ret i8 [[V17]]
+;
+ %x = call i32 @opaque(i32 %y)
+ %v2 = shl i32 %x, 12
+ %v3 = add i32 %v2, 34817
+ call void @use32(i32 %v3)
+ %v16 = lshr i32 %v3, 8
+ %v17 = trunc i32 %v16 to i8
+ ret i8 %v17
+}
+
+; Negative test: ShrAmt (25) > SrcWidth - DestWidth (24), even though AddC
+; (2^25) is still a multiple of 2^25. A freshly computed "X << (ShAmt -
+; ShrAmt)" would disagree with "(X << ShAmt) >> ShrAmt" in a bit that the
+; final trunc to i8 would still observe, so the fold must not fire.
+define i8 @negative_shramt_too_large(i32 %y) {
+; CHECK-LABEL: define i8 @negative_shramt_too_large(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT: [[X_TR:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT: [[TMP1:%.*]] = shl i8 [[X_TR]], 5
+; CHECK-NEXT: [[TMP2:%.*]] = and i8 [[TMP1]], 96
+; CHECK-NEXT: [[V17:%.*]] = or disjoint i8 [[TMP2]], 1
+; CHECK-NEXT: ret i8 [[V17]]
+;
+ %x = call i32 @opaque(i32 %y)
+ %v2 = shl i32 %x, 30
+ %v3 = add i32 %v2, 33554432
+ %v16 = lshr i32 %v3, 25
+ %v17 = trunc i32 %v16 to i8
+ ret i8 %v17
+}
+
+; Negative test: the lshr has another use, so replacing the trunc's operand
+; would leave the original computation behind rather than eliminating it.
+define i8 @negative_lshr_multi_use(i32 %y, ptr %p) {
+; CHECK-LABEL: define i8 @negative_lshr_multi_use(
+; CHECK-SAME: i32 [[Y:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT: [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT: [[V2:%.*]] = shl i32 [[X]], 12
+; CHECK-NEXT: [[V3:%.*]] = add i32 [[V2]], 34816
+; CHECK-NEXT: [[V16:%.*]] = lshr exact i32 [[V3]], 8
+; CHECK-NEXT: store i32 [[V16]], ptr [[P]], align 4
+; CHECK-NEXT: [[V17:%.*]] = trunc i32 [[V16]] to i8
+; CHECK-NEXT: ret i8 [[V17]]
+;
+ %x = call i32 @opaque(i32 %y)
+ %v2 = shl i32 %x, 12
+ %v3 = add i32 %v2, 34816
+ %v16 = lshr i32 %v3, 8
+ store i32 %v16, ptr %p
+ %v17 = trunc i32 %v16 to i8
+ ret i8 %v17
+}
>From 2b09c8764ce28d4124934e971b2b878e140ec61e Mon Sep 17 00:00:00 2001
From: Sree Inesh Murugan Palanisamy <inesh.murugan at multicorewareinc.com>
Date: Tue, 11 Aug 2026 14:18:07 +0530
Subject: [PATCH 2/3] [InstCombine] Fix instruction ordering in the new
shl+BinOp fold (llvm#214339)
The new shl was inserted via Builder.CreateShl, which uses whatever
insertion point the IRBuilder happened to be at rather than the
lshr's location. When that point came later in the block than the add
that consumes the shl, the add ended up dominating the shl instead of
the other way around, and the verifier rightly complained.
Insert the shl with InsertNewInstWith right before the lshr, same as
the add already was. Added a test with an and-mask consumer since
that's the shape that triggered it.
---
.../InstCombine/InstCombineSimplifyDemanded.cpp | 6 ++++--
.../InstCombine/trunc-lshr-add-shl.ll | 17 +++++++++++++++++
2 files changed, 21 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
index 06819a6339de4..9d38d3d6893e4 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
@@ -876,8 +876,10 @@ Value *InstCombinerImpl::SimplifyDemandedUseBits(Instruction *I,
InnerShAmt->uge(ShiftAmt) &&
(BO->isBitwiseLogicOp() || (BO->getOpcode() == Instruction::Add &&
BOC->countr_zero() >= ShiftAmt))) {
- Value *NewShl = Builder.CreateShl(
- InnerX, ConstantInt::get(VTy, *InnerShAmt - ShiftAmt));
+ Value *NewShl = InsertNewInstWith(
+ BinaryOperator::CreateShl(
+ InnerX, ConstantInt::get(VTy, *InnerShAmt - ShiftAmt)),
+ I->getIterator());
Constant *NewBOC = ConstantInt::get(VTy, BOC->lshr(ShiftAmt));
Instruction *NewBO =
BinaryOperator::Create(BO->getOpcode(), NewShl, NewBOC);
diff --git a/llvm/test/Transforms/InstCombine/trunc-lshr-add-shl.ll b/llvm/test/Transforms/InstCombine/trunc-lshr-add-shl.ll
index 62abbd0284c90..463dc41ff5b6e 100644
--- a/llvm/test/Transforms/InstCombine/trunc-lshr-add-shl.ll
+++ b/llvm/test/Transforms/InstCombine/trunc-lshr-add-shl.ll
@@ -187,6 +187,23 @@ define i8 @positive_and_misaligned_c(i32 %y) {
ret i8 %v17
}
+; Regression test: an "and" mask (not just trunc) used to crash the fold
+; with a dominance violation.
+define i32 @regression_and_mask_dominance(i32 %x) {
+; CHECK-LABEL: define i32 @regression_and_mask_dominance(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = shl i32 [[X]], 4
+; CHECK-NEXT: [[TMP2:%.*]] = add i32 [[TMP1]], 136
+; CHECK-NEXT: [[R:%.*]] = and i32 [[TMP2]], 248
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %v2 = shl i32 %x, 12
+ %v3 = add i32 %v2, 34816
+ %v16 = lshr i32 %v3, 8
+ %r = and i32 %v16, 255
+ ret i32 %r
+}
+
; Negative test: ShrAmt (4) < ShAmt is fine, but here ShrAmt (8) > ShAmt (4),
; which would require a negative shift amount, so the fold must not fire.
define i8 @negative_shramt_gt_shamt(i32 %y) {
>From 794e7e4855c3ee75087691d892d9db57691537d6 Mon Sep 17 00:00:00 2001
From: Sree Inesh Murugan Palanisamy <inesh.murugan at multicorewareinc.com>
Date: Wed, 12 Aug 2026 17:09:40 +0530
Subject: [PATCH 3/3] [InstCombine] Add more tests for the shl+BinOp fold
(llvm#214339)
Extend the test coverage for the trunc(lshr(BinOp(shl(X, ShAmt), C),
ShrAmt)) fold:
- partial demanded-bits masks (0xF0) for 'add' and 'or', in addition to
the existing full-byte mask
- splat vectors, plus a non-splat negative test
- a wider type with shift amounts that aren't multiples of the
destination width
- negative tests isolating the binop's one-use guard and the
demanded-bits gate
---
.../InstCombine/trunc-lshr-add-shl.ll | 123 ++++++++++++++++++
1 file changed, 123 insertions(+)
diff --git a/llvm/test/Transforms/InstCombine/trunc-lshr-add-shl.ll b/llvm/test/Transforms/InstCombine/trunc-lshr-add-shl.ll
index 463dc41ff5b6e..98c6c977bf698 100644
--- a/llvm/test/Transforms/InstCombine/trunc-lshr-add-shl.ll
+++ b/llvm/test/Transforms/InstCombine/trunc-lshr-add-shl.ll
@@ -187,6 +187,55 @@ define i8 @positive_and_misaligned_c(i32 %y) {
ret i8 %v17
}
+; Splat vectors are matched by m_APInt, so the fold applies to them too.
+define <4 x i32> @positive_vector_splat(<4 x i32> %x) {
+; CHECK-LABEL: define <4 x i32> @positive_vector_splat(
+; CHECK-SAME: <4 x i32> [[X:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = shl <4 x i32> [[X]], splat (i32 4)
+; CHECK-NEXT: [[TMP2:%.*]] = add <4 x i32> [[TMP1]], splat (i32 136)
+; CHECK-NEXT: [[R:%.*]] = and <4 x i32> [[TMP2]], splat (i32 248)
+; CHECK-NEXT: ret <4 x i32> [[R]]
+;
+ %v2 = shl <4 x i32> %x, splat (i32 12)
+ %v3 = add <4 x i32> %v2, splat (i32 34816)
+ %v16 = lshr <4 x i32> %v3, splat (i32 8)
+ %r = and <4 x i32> %v16, splat (i32 255)
+ ret <4 x i32> %r
+}
+
+; Non-splat shift amounts are not matched by m_APInt, so this must not fold.
+define <4 x i32> @negative_vector_nonsplat(<4 x i32> %x) {
+; CHECK-LABEL: define <4 x i32> @negative_vector_nonsplat(
+; CHECK-SAME: <4 x i32> [[X:%.*]]) {
+; CHECK-NEXT: [[V2:%.*]] = shl <4 x i32> [[X]], <i32 12, i32 12, i32 16, i32 12>
+; CHECK-NEXT: [[V3:%.*]] = add <4 x i32> [[V2]], splat (i32 34816)
+; CHECK-NEXT: [[V16:%.*]] = lshr <4 x i32> [[V3]], splat (i32 8)
+; CHECK-NEXT: [[R:%.*]] = and <4 x i32> [[V16]], splat (i32 255)
+; CHECK-NEXT: ret <4 x i32> [[R]]
+;
+ %v2 = shl <4 x i32> %x, <i32 12, i32 12, i32 16, i32 12>
+ %v3 = add <4 x i32> %v2, splat (i32 34816)
+ %v16 = lshr <4 x i32> %v3, splat (i32 8)
+ %r = and <4 x i32> %v16, splat (i32 255)
+ ret <4 x i32> %r
+}
+
+; Wider type and shift amounts that aren't multiples of the dest width.
+define i16 @positive_i64_odd_shifts(i64 %x) {
+; CHECK-LABEL: define i16 @positive_i64_odd_shifts(
+; CHECK-SAME: i64 [[X:%.*]]) {
+; CHECK-NEXT: [[X_TR:%.*]] = trunc i64 [[X]] to i16
+; CHECK-NEXT: [[TMP1:%.*]] = shl i16 [[X_TR]], 8
+; CHECK-NEXT: [[V17:%.*]] = add i16 [[TMP1]], 1024
+; CHECK-NEXT: ret i16 [[V17]]
+;
+ %v2 = shl i64 %x, 21
+ %v3 = add i64 %v2, 8388608
+ %v16 = lshr i64 %v3, 13
+ %v17 = trunc i64 %v16 to i16
+ ret i16 %v17
+}
+
; Regression test: an "and" mask (not just trunc) used to crash the fold
; with a dominance violation.
define i32 @regression_and_mask_dominance(i32 %x) {
@@ -204,6 +253,40 @@ define i32 @regression_and_mask_dominance(i32 %x) {
ret i32 %r
}
+; Partial demanded-bits mask (0xF0) rather than a full byte: the fold still
+; fires, and the narrower demand turns the add into an equivalent and+xor.
+define i32 @positive_and_mask_partial_demanded_bits(i32 %x) {
+; CHECK-LABEL: define i32 @positive_and_mask_partial_demanded_bits(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = shl i32 [[X]], 4
+; CHECK-NEXT: [[TMP2:%.*]] = and i32 [[TMP1]], 240
+; CHECK-NEXT: [[R:%.*]] = xor i32 [[TMP2]], 128
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %v2 = shl i32 %x, 12
+ %v3 = add i32 %v2, 34816
+ %v16 = lshr i32 %v3, 8
+ %r = and i32 %v16, 240
+ ret i32 %r
+}
+
+; Same partial mask (0xF0) with 'or' instead of 'add', covering the bitwise
+; path with a partial demanded mask.
+define i32 @positive_or_mask_partial_demanded_bits(i32 %x) {
+; CHECK-LABEL: define i32 @positive_or_mask_partial_demanded_bits(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = shl i32 [[X]], 4
+; CHECK-NEXT: [[V16:%.*]] = and i32 [[TMP1]], 112
+; CHECK-NEXT: [[R:%.*]] = or disjoint i32 [[V16]], 128
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %v2 = shl i32 %x, 12
+ %v3 = or i32 %v2, 34816
+ %v16 = lshr i32 %v3, 8
+ %r = and i32 %v16, 240
+ ret i32 %r
+}
+
; Negative test: ShrAmt (4) < ShAmt is fine, but here ShrAmt (8) > ShAmt (4),
; which would require a negative shift amount, so the fold must not fire.
define i8 @negative_shramt_gt_shamt(i32 %y) {
@@ -291,3 +374,43 @@ define i8 @negative_lshr_multi_use(i32 %y, ptr %p) {
%v17 = trunc i32 %v16 to i8
ret i8 %v17
}
+
+; Negative test: the binop has another use, so m_OneUse rejects the fold even
+; though the constant is aligned.
+define i8 @negative_binop_multi_use(i32 %y) {
+; CHECK-LABEL: define i8 @negative_binop_multi_use(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT: [[V2:%.*]] = shl i32 [[X]], 12
+; CHECK-NEXT: [[V3:%.*]] = add i32 [[V2]], 34816
+; CHECK-NEXT: call void @use32(i32 [[V3]])
+; CHECK-NEXT: [[V16:%.*]] = lshr exact i32 [[V3]], 8
+; CHECK-NEXT: [[V17:%.*]] = trunc i32 [[V16]] to i8
+; CHECK-NEXT: ret i8 [[V17]]
+;
+ %x = call i32 @opaque(i32 %y)
+ %v2 = shl i32 %x, 12
+ %v3 = add i32 %v2, 34816
+ call void @use32(i32 %v3)
+ %v16 = lshr i32 %v3, 8
+ %v17 = trunc i32 %v16 to i8
+ ret i8 %v17
+}
+
+; Negative test: with no trunc or mask every bit is demanded, so the high
+; ShrAmt bits where the two forms differ are observable and it must not fold.
+define i32 @negative_all_bits_demanded(i32 %y) {
+; CHECK-LABEL: define i32 @negative_all_bits_demanded(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT: [[V2:%.*]] = shl i32 [[X]], 12
+; CHECK-NEXT: [[V3:%.*]] = add i32 [[V2]], 34816
+; CHECK-NEXT: [[V16:%.*]] = lshr exact i32 [[V3]], 8
+; CHECK-NEXT: ret i32 [[V16]]
+;
+ %x = call i32 @opaque(i32 %y)
+ %v2 = shl i32 %x, 12
+ %v3 = add i32 %v2, 34816
+ %v16 = lshr i32 %v3, 8
+ ret i32 %v16
+}
More information about the llvm-commits
mailing list