Skip to content

[ConstraintElim] Use cond from header as upper bound on IV in exit BB. #94610

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 4 commits into from
Jul 9, 2024

Conversation

fhahn
Copy link
Contributor

@fhahn fhahn commented Jun 6, 2024

For loops, we can use the condition in the loop header as upper bound on the compared induction in the unique exit block, if it exists. This can be done even if there are multiple in-loop edges to the unique exit block, as any other exit may only exit earlier.

More generally, we could add the OR of all exit conditions to the exit, but that's a possible future extension.

@llvmbot
Copy link
Member

llvmbot commented Jun 6, 2024

@llvm/pr-subscribers-llvm-transforms

Author: Florian Hahn (fhahn)

Changes

For loops, we can use the condition in the loop header as upper bound on the compared induction in the unique exit block, if it exists. This can be done even if there are multiple in-loop edges to the unique exit block, as any other exit may only exit earlier.

More generally, we could add the OR of all exit conditions to the exit, but that's a possible future extension.

Fixes #90417.


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

2 Files Affected:

  • (modified) llvm/lib/Transforms/Scalar/ConstraintElimination.cpp (+17)
  • (modified) llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit.ll (+3-6)
diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index 70bfa469193bf..4b3ef4d4c222c 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -1031,6 +1031,23 @@ void State::addInfoForInductions(BasicBlock &BB) {
   WorkList.push_back(FactOrCheck::getConditionFact(
       DTN, CmpInst::ICMP_SLT, PN, B,
       ConditionTy(CmpInst::ICMP_SLE, StartValue, B)));
+
+  assert(!StepOffset.isNegative() && "induction must be increasing");
+  // Try to add condition from header to the unique exit block, if there is one.
+  // When exiting either with EQ or NE, we know that the induction value must be
+  // u<= B, as a different exit may exit earlier.
+  if (Pred == CmpInst::ICMP_EQ) {
+    BasicBlock *EB = cast<BranchInst>(BB.getTerminator())->getSuccessor(0);
+    if (L->getUniqueExitBlock() == EB)
+      WorkList.emplace_back(FactOrCheck::getConditionFact(
+          DT.getNode(EB), CmpInst::ICMP_ULE, A, B));
+  }
+  if (Pred == CmpInst::ICMP_NE) {
+    BasicBlock *EB = cast<BranchInst>(BB.getTerminator())->getSuccessor(1);
+    if (L->getUniqueExitBlock() == EB)
+      WorkList.emplace_back(FactOrCheck::getConditionFact(
+          DT.getNode(EB), CmpInst::ICMP_ULE, A, B));
+  }
 }
 
 void State::addInfoFor(BasicBlock &BB) {
diff --git a/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit.ll b/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit.ll
index 44ce82b51d707..86828b5e7f369 100644
--- a/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit.ll
+++ b/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit.ll
@@ -17,8 +17,7 @@ define i1 @multi_exiting_loop_eq_same_unique_exit_const_compare_known(ptr %s) {
 ; CHECK-NEXT:    [[IV_NEXT]] = add nuw nsw i32 [[IV]], 1
 ; CHECK-NEXT:    br i1 [[LATCH_C]], label %[[LOOP_HEADER]], label %[[EXIT]]
 ; CHECK:       [[EXIT]]:
-; CHECK-NEXT:    [[T:%.*]] = icmp ult i32 [[IV]], 1235
-; CHECK-NEXT:    ret i1 [[T]]
+; CHECK-NEXT:    ret i1 true
 ;
 entry:
   br label %loop.header
@@ -175,8 +174,7 @@ define i1 @multi_exiting_loop_eq_same_unique_exit_var_compare_known(ptr %s, i32
 ; CHECK-NEXT:    [[IV_NEXT]] = add nuw nsw i32 [[IV]], 1
 ; CHECK-NEXT:    br i1 [[LATCH_C]], label %[[LOOP_HEADER]], label %[[EXIT]]
 ; CHECK:       [[EXIT]]:
-; CHECK-NEXT:    [[T:%.*]] = icmp ule i32 [[IV]], [[N]]
-; CHECK-NEXT:    ret i1 [[T]]
+; CHECK-NEXT:    ret i1 true
 ;
 entry:
   br label %loop.header
@@ -214,8 +212,7 @@ define i1 @multi_exiting_loop_ne_same_unique_exit_const_compare_known(ptr %s) {
 ; CHECK-NEXT:    [[IV_NEXT]] = add nuw nsw i32 [[IV]], 1
 ; CHECK-NEXT:    br i1 [[LATCH_C]], label %[[LOOP_HEADER]], label %[[EXIT]]
 ; CHECK:       [[EXIT]]:
-; CHECK-NEXT:    [[T:%.*]] = icmp ult i32 [[IV]], 1235
-; CHECK-NEXT:    ret i1 [[T]]
+; CHECK-NEXT:    ret i1 true
 ;
 entry:
   br label %loop.header

// Try to add condition from header to the unique exit block, if there is one.
// When exiting either with EQ or NE, we know that the induction value must be
// u<= B, as a different exit may exit earlier.
if (Pred == CmpInst::ICMP_EQ) {
Copy link
Member

Choose a reason for hiding this comment

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

It is incorrect when the initial value of indvar is greater than the bound.
Alive2: https://alive2.llvm.org/ce/z/UYsbAQ

We need ConditionTy(CmpInst::ICMP_ULE, StartValue, B) here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great point thanks! Incorrectly assume that we only reach here for monotonically increasing cases, should have added tests... Done in b7b8d02

and update the code here

fhahn added a commit that referenced this pull request Jun 6, 2024
@fhahn fhahn force-pushed the ce-loop-header-exit branch from dc6c9f5 to ff5519f Compare June 6, 2024 14:39
dtcxzyw added a commit to dtcxzyw/llvm-opt-benchmark that referenced this pull request Jun 6, 2024
Copy link
Member

@dtcxzyw dtcxzyw left a comment

Choose a reason for hiding this comment

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

LGTM.

Comment on lines 1039 to 1047
ConditionTy Precond;
if (!MonotonicallyIncreasingUnsigned)
Precond = {CmpInst::ICMP_ULE, StartValue, B};
if (Pred == CmpInst::ICMP_EQ) {
BasicBlock *EB = cast<BranchInst>(BB.getTerminator())->getSuccessor(0);
if (L->getUniqueExitBlock() == EB) {
WorkList.emplace_back(FactOrCheck::getConditionFact(
DT.getNode(EB), CmpInst::ICMP_ULE, A, B, Precond));
}
}
if (Pred == CmpInst::ICMP_NE) {
BasicBlock *EB = cast<BranchInst>(BB.getTerminator())->getSuccessor(1);
if (L->getUniqueExitBlock() == EB)
WorkList.emplace_back(FactOrCheck::getConditionFact(
DT.getNode(EB), CmpInst::ICMP_ULE, A, B, Precond));
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
ConditionTy Precond;
if (!MonotonicallyIncreasingUnsigned)
Precond = {CmpInst::ICMP_ULE, StartValue, B};
if (Pred == CmpInst::ICMP_EQ) {
BasicBlock *EB = cast<BranchInst>(BB.getTerminator())->getSuccessor(0);
if (L->getUniqueExitBlock() == EB) {
WorkList.emplace_back(FactOrCheck::getConditionFact(
DT.getNode(EB), CmpInst::ICMP_ULE, A, B, Precond));
}
}
if (Pred == CmpInst::ICMP_NE) {
BasicBlock *EB = cast<BranchInst>(BB.getTerminator())->getSuccessor(1);
if (L->getUniqueExitBlock() == EB)
WorkList.emplace_back(FactOrCheck::getConditionFact(
DT.getNode(EB), CmpInst::ICMP_ULE, A, B, Precond));
}
if (ICmpInst::isEquality(Pred)) {
ConditionTy Precond;
if (!MonotonicallyIncreasingUnsigned)
Precond = {CmpInst::ICMP_ULE, StartValue, B};
BasicBlock *EB = cast<BranchInst>(BB.getTerminator())->getSuccessor(Pred == CmpInst::ICMP_NE);
if (L->getUniqueExitBlock() == EB)
WorkList.emplace_back(FactOrCheck::getConditionFact(
DT.getNode(EB), CmpInst::ICMP_ULE, A, B, Precond));
}

It should be simpler :)

BasicBlock *EB = cast<BranchInst>(BB.getTerminator())->getSuccessor(0);
if (L->getUniqueExitBlock() == EB) {
WorkList.emplace_back(FactOrCheck::getConditionFact(
DT.getNode(EB), CmpInst::ICMP_ULE, A, B, Precond));
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
DT.getNode(EB), CmpInst::ICMP_ULE, A, B, Precond));
DT.getNode(EB), CmpInst::ICMP_ULE, PN, B, Precond));

I found the switch from PN in all prior code to A here confusing.

// u<= B, as a different exit may exit earlier.
ConditionTy Precond;
if (!MonotonicallyIncreasingUnsigned)
Precond = {CmpInst::ICMP_ULE, StartValue, B};
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm confused, why do we only need this pre-condition if !MonotonicallyIncreasingUnsigned? Let's say we have a monotonic nuw addrec, a header eq exit where the start value is greater that is not taken and some other exit that is taken. Wouldn't we miscompile that?

Copy link
Member

Choose a reason for hiding this comment

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

Doesn't MonotonicallyIncreasingUnsigned imply that StartValue u<= ExitValue == B?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think so. It just implies that PN is monotonically increasing from StartValue, but it doesn't make a statement about how that relates to B.

If I take your first test case from https://alive2.llvm.org/ce/z/UYsbAQ, the current patch still folds it to true.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, updated to always add the precond, originally also thought that MonotonicallyIncreasingUnsigned was implying StartValue <= B. Test added in 3d11b3d

}
if (Pred == CmpInst::ICMP_NE) {
BasicBlock *EB = cast<BranchInst>(BB.getTerminator())->getSuccessor(1);
if (L->getUniqueExitBlock() == EB)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure I understand the significance of the unique exit block here. Even if the exit is non-unique, wouldn't we be still add this condition fact to the header exit?

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure I understand the significance of the unique exit block here. Even if the exit is non-unique, wouldn't we be still add this condition fact to the header exit?

It may cause a miscompilation if there is a path from another exit block to the header exit.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why? Isn't the argument here that even if we go through another exit, it can only exit earlier than the header, so the condition still holds? Why would it matter whether the other exit has a direct edge to the header exit vs going there indirectly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not needed I think, removed the restriction (for the case where each exit block only has a single predecessor in the loop, the patch isn't needed). Tests added in 798754f

@v01dXYZ
Copy link
Contributor

v01dXYZ commented Jun 7, 2024

  • It seems to me only the loop with a predicate EQ/NE are supported. But the canonicalisation of the predicate for an AddRec happens in IndVarSimplify (linearFunctionTestReplace):
    linearFunctionTestReplace(Loop *L, BasicBlock *ExitingBB,
  • I really don't want to sound like a smart*ss but I tested with clang and I didn't manage to get the snippet from the issue to be optimised (even by replacing the predicate from <= to !=). I could have made a mistake though.

fhahn added a commit that referenced this pull request Jun 28, 2024
Additional test coverage for a miscompile in earlier versions of
#94610.
fhahn added a commit that referenced this pull request Jun 28, 2024
Additional test coverage with multi-exit loops for
 #94610.
fhahn added 3 commits June 29, 2024 09:28
For loops, we can use the condition in the loop header as upper bound on
the compared induction in the unique exit block, if it exists. This can
be done even if there are multiple in-loop edges to the unique exit
block, as any other exit may only exit earlier.

More generally, we could add the OR of all exit conditions to the exit,
but that's a possible future extension.

Fixes llvm#90417.
@fhahn fhahn force-pushed the ce-loop-header-exit branch from ff5519f to d51a036 Compare June 29, 2024 09:36
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.

LGTM if no compile-time impact.

Please double-check whether this actually fixes #90417 (with current phase ordering) and drop the line from the PR description if not.

lravenclaw pushed a commit to lravenclaw/llvm-project that referenced this pull request Jul 3, 2024
Additional test coverage for a miscompile in earlier versions of
llvm#94610.
lravenclaw pushed a commit to lravenclaw/llvm-project that referenced this pull request Jul 3, 2024
Additional test coverage with multi-exit loops for
 llvm#94610.
@fhahn
Copy link
Contributor Author

fhahn commented Jul 9, 2024

LGTM if no compile-time impact.

Please double-check whether this actually fixes #90417 (with current phase ordering) and drop the line from the PR description if not.

Thanks, compile-time impact looks neutral. As @v01dXYZ pointed out, it the current version doesn't apply to #90417 with the current phase ordering. I removed the Fixes... and I'll add some phase ordering tests. We would also need to handle phis with multiple incoming values (a separate improvement which would probably be good to have anyways)

@fhahn fhahn merged commit 5b92713 into llvm:main Jul 9, 2024
7 checks passed
@fhahn fhahn deleted the ce-loop-header-exit branch July 9, 2024 18:37
aaryanshukla pushed a commit to aaryanshukla/llvm-project that referenced this pull request Jul 14, 2024
llvm#94610)

For loops, we can use the condition in the loop header as upper bound on
the compared induction in the unique exit block, if it exists. This can
be done even if there are multiple in-loop edges to the unique exit
block, as any other exit may only exit earlier.

More generally, we could add the OR of all exit conditions to the exit,
but that's a possible future extension.

PR: llvm#94610
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants