Skip to content

[InstCombine] Prevent infinite loop with two shifts #118806

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 5, 2024
Merged

Conversation

momo5502
Copy link
Contributor

@momo5502 momo5502 commented Dec 5, 2024

The following pattern: (C2 << X) << C1 will usually be transformed into (C2 << C1) << X, essentially swapping X and C1.

However, this should only be done when C1 is an immediate constant, otherwise this
can lead to both constants being swapped forever

This fixes #118798

@momo5502 momo5502 requested a review from nikic as a code owner December 5, 2024 13:56
@llvmbot llvmbot added llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:transforms labels Dec 5, 2024
@llvmbot
Copy link
Member

llvmbot commented Dec 5, 2024

@llvm/pr-subscribers-llvm-transforms

Author: Maurice Heumann (momo5502)

Changes

The following pattern: (C2 &lt;&lt; X) &lt;&lt; C1 will usually be transformed into (C2 &lt;&lt; C1) &lt;&lt; X, essentially swapping X and C1.

However, this should not be done when X is also a constant, as this can lead to both constants being swapped forever.

This fixes #118798


Full diff: https://github.com/llvm/llvm-project/pull/118806.diff

2 Files Affected:

  • (modified) llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp (+3-1)
  • (added) llvm/test/Transforms/InstCombine/shl-twice-constant.ll (+22)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
index 10c3ccdb2243a1..3fb094579d585a 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
@@ -786,7 +786,9 @@ Instruction *InstCombinerImpl::FoldShiftByConstant(Value *Op0, Constant *C1,
   Constant *C2;
   Value *X;
   bool IsLeftShift = I.getOpcode() == Instruction::Shl;
-  if (match(Op0, m_BinOp(I.getOpcode(), m_ImmConstant(C2), m_Value(X)))) {
+  if (match(Op0,
+            m_BinOp(I.getOpcode(), m_ImmConstant(C2),
+                    m_CombineAnd(m_Value(X), m_Unless(m_Constant()))))) {
     Instruction *R = BinaryOperator::Create(
         I.getOpcode(), Builder.CreateBinOp(I.getOpcode(), C2, C1), X);
     BinaryOperator *BO0 = cast<BinaryOperator>(Op0);
diff --git a/llvm/test/Transforms/InstCombine/shl-twice-constant.ll b/llvm/test/Transforms/InstCombine/shl-twice-constant.ll
new file mode 100644
index 00000000000000..54c5611400f46c
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/shl-twice-constant.ll
@@ -0,0 +1,22 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+@c = external constant i8
+@c2 = external constant i8
+
+define i16 @testfunc() {
+; CHECK-LABEL: @testfunc(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = shl nuw i64 1, ptrtoint (ptr @c2 to i64)
+; CHECK-NEXT:    [[TMP1:%.*]] = shl i64 [[TMP0]], ptrtoint (ptr @c to i64)
+; CHECK-NEXT:    [[TMP2:%.*]] = inttoptr i64 [[TMP1]] to ptr
+; CHECK-NEXT:    [[TMP3:%.*]] = load i16, ptr [[TMP2]], align 1
+; CHECK-NEXT:    ret i16 [[TMP3]]
+;
+entry:
+  %0 = shl i64 1, ptrtoint (ptr @c2 to i64)
+  %1 = shl i64 %0, ptrtoint (ptr @c to i64)
+  %2 = inttoptr i64 %1 to ptr
+  %3 = load i16, ptr %2, align 1
+  ret i16 %3
+}

Copy link

github-actions bot commented Dec 5, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it may be better to address this by replacing the check in

if (Constant *CUI = dyn_cast<Constant>(Op1))
with a check for ImmConstant.

@momo5502
Copy link
Contributor Author

momo5502 commented Dec 5, 2024

I think it may be better to address this by replacing the check in

if (Constant *CUI = dyn_cast<Constant>(Op1))

with a check for ImmConstant.

done

Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LG

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last two instructions in this test shouldn't be needed, it can be reduced to:

@c = external constant i8
@c2 = external constant i8

define i64 @testfunc() {
  %shl1 = shl i64 1, ptrtoint (ptr @c2 to i64)
  %shl2 = shl i64 %shl1, ptrtoint (ptr @c to i64)
  ret i64 %shl2
}
```.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test adjusted. thank you for the review :)

The following pattern: `(C2 << X) << C1` will usually be
transformed into `(C2 << C1) << X`, essentially swapping `X` and `C1`.

However, this should not only done when `C1` is an immediate constant, otherwise this
can lead to both constants being swapped forever

This fixes llvm#118798
@nikic nikic merged commit 27eaa8a into llvm:main Dec 5, 2024
6 of 7 checks passed
@lukel97
Copy link
Contributor

lukel97 commented Mar 21, 2025

I think this might have broken some combines with scalable vectors, beforehand this

define <vscale x 4 x i8> @f(<vscale x 4 x i8> %x) {
  %1 = and <vscale x 4 x i8> %x, splat (i8 2)
  %2 = shl <vscale x 4 x i8> %1, splat (i8 2)
  ret <vscale x 4 x i8> %2
}

used to get combined to

define <vscale x 4 x i8> @f(<vscale x 4 x i8> %x) {
  %1 = shl <vscale x 4 x i8> %x, splat (i8 2)
  %2 = and <vscale x 4 x i8> %1, splat (i8 8)
  ret <vscale x 4 x i8> %2
}

But now this fails because a scalable splat vector is a constant expression IIUC. Maybe we need some sort of pattern matcher that matches constant immediates + constant splats?

lukel97 added a commit that referenced this pull request Apr 2, 2025
#118806 fixed an infinite loop in FoldShiftByConstant that could occur
when the shift amount was a ConstantExpr.

However this meant that FoldShiftByConstant no longer kicked in for
scalable vectors because scalable splats are represented by
ConstantExprs.

This fixes it by allowing scalable splats of non-ConstantExprs in
m_ImmConstant, which also fixes a few other test cases where scalable
splats were being missed.

But I'm also hoping that UseConstantIntForScalableSplat will eventually
remove the need for this.

I noticed this when trying to reverse a combine on RISC-V in #132245,
and saw that the resulting vector and scalar forms were different.

---------

Co-authored-by: Yingwei Zheng <[email protected]>
lukel97 added a commit that referenced this pull request Apr 3, 2025
…" (#134262)

This reapplies #132522.

Previously casts of scalable m_ImmConstant splats weren't being folded
by ConstantFoldCastOperand, triggering the "Constant-fold of ImmConstant
should not fail" assertion.

There are no changes to the code in this PR, instead we just needed
#133207 to land first.

A test has been added for the assertion in
llvm/test/Transforms/InstSimplify/vec-icmp-of-cast.ll
@icmp_ult_sext_scalable_splat_is_true.

<hr/>

#118806 fixed an infinite loop in FoldShiftByConstant that could occur
when the shift amount was a ConstantExpr.

However this meant that FoldShiftByConstant no longer kicked in for
scalable vectors because scalable splats are represented by
ConstantExprs.

This fixes it by allowing scalable splats of non-ConstantExprs in
m_ImmConstant, which also fixes a few other test cases where scalable
splats were being missed.

But I'm also hoping that UseConstantIntForScalableSplat will eventually
remove the need for this.

I noticed this when trying to reverse a combine on RISC-V in #132245,
and saw that the resulting vector and scalar forms were different.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:transforms
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[InstCombine] Infinite loop when combining two shl instructions
4 participants