-
Notifications
You must be signed in to change notification settings - Fork 6.1k
8332920: C2: Partial Peeling is wrongly applied for CmpU with negative limit #19522
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
Conversation
👋 Welcome back chagedorn! A progress list of the required criteria for merging this PR into |
@chhagedorn This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 81 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. ➡️ To integrate this PR with the above commit message to the |
@chhagedorn The following label will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command. |
Webrevs
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good.
Thanks Vladimir for your review! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the Signed Loop Exit Test is false, then the (original) Unsigned Loop Exit Test will make sure to exit the loop if required.
Is that comment incorrect then?
jdk/src/hotspot/share/opto/loopopts.cpp
Lines 2993 to 2994 in 2edb6d9
// if and it's projections. The original if test is replaced with | |
// a constant to force the stay-in-loop path. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More to come later
src/hotspot/share/opto/loopopts.cpp
Outdated
// If | ||
// limit >= 0 (COND) | ||
// then the unsigned loop exit condition is equivalent to the signed loop exit condition | ||
// i < 0 || i >= limit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we come up with an alternative equation if limit < 0
? And maybe even a combined condition? Not sure if that is helpful, but I'd like to think about it. Could also be a follow-up RFE.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have not thought about it, yet, but I also have a feeling that we might can do better. Since this is a P2 bug fix, I propose to do that separately in an RFE as you suggested.
src/hotspot/share/opto/loopopts.cpp
Outdated
// to be true (otherwise, we wrongly exit a loop that should not have been exited). More formally, we need to ensure: | ||
// "Signed Loop Exit Test" implies "Unsigned Loop Exit Test" | ||
// This is trivially given: | ||
// - Stride < 0: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// - Stride < 0: | |
// - stride < 0: |
Stylistic decision, leave this to your
src/hotspot/share/opto/loopopts.cpp
Outdated
return nullptr; | ||
} | ||
|
||
// For stride < 0, we split off the signed loop exit condition |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// For stride < 0, we split off the signed loop exit condition | |
// For stride < 0, we insert off the signed loop exit condition |
Why do you say "split", we are inserting this extra check, right? Or is the idea that the "rotation" puts this new condition as the last check, hence the exit check in the loop body? Being a big more explicit could help here.
src/hotspot/share/opto/loopopts.cpp
Outdated
// Unsigned Loop Exit Condition i >=u limit | ||
// <rest of unpeeled section> | ||
// <peeled section> | ||
// Signed Loop Exit Condition i < 0 or i >= limit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is not litterally an OR
here, right? It's a bit confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great otherwise. Nice test and proof!
test/hotspot/jtreg/compiler/loopopts/TestPartialPeelAtUnsignedTestsNegativeLimit.java
Outdated
Show resolved
Hide resolved
src/hotspot/share/opto/loopopts.cpp
Outdated
// | ||
// After Partial Peeling, we have the following structure: | ||
// <cloned peeled section> | ||
// Signed Loop Exit Condition i < 0 or i >= limit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Signed Loop Exit Condition i < 0 or i >= limit | |
// Signed Loop Exit Condition i < 0 (or i >= limit) |
src/hotspot/share/opto/loopopts.cpp
Outdated
// Unsigned Loop Exit Condition i >=u limit | ||
// <rest of unpeeled section> | ||
// <peeled section> | ||
// Signed Loop Exit Condition i < 0 or i >= limit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Signed Loop Exit Condition i < 0 or i >= limit | |
// Signed Loop Exit Condition i < 0 (or i >= limit) |
src/hotspot/share/opto/loopopts.cpp
Outdated
// | ||
// Loop: | ||
// <peeled section> | ||
// Signed Loop Exit Condition i < 0 or i >= limit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Signed Loop Exit Condition i < 0 or i >= limit | |
// Signed Loop Exit Condition i < 0 (or i >= limit) |
src/hotspot/share/opto/loopopts.cpp
Outdated
// then the unsigned loop exit condition is equivalent to the signed loop exit condition | ||
// i < 0 || i >= limit | ||
// | ||
// Note that this does not hold for limit < 0: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we discussed, rephrase this as counterexample.
src/hotspot/share/opto/loopopts.cpp
Outdated
// - Stride < 0: | ||
// i < 0 // Signed Loop Exit Condition | ||
// i >u MAX_INT // all negative values are greater than MAX_INT when converted to unsigned | ||
// i >=u limit // limit <= MAX_INT (trivially) and since limit >= 0 assumption (COND) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we discussed, an intermediate step in the proof would be good here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! That looks good to me.
Thanks Tobias for your review! |
src/hotspot/share/opto/loopopts.cpp
Outdated
// dummy-if | | ||
// / | | | ||
// other | | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This dummy-if is created with insert_region_before_proj()
such that we can have a region between the exit projection (exit-proj
) of the original unsigned loop exit test and the If node while keeping both the If and the exit-proj. Also see:
jdk/src/hotspot/share/opto/loopopts.cpp
Lines 2934 to 2960 in cbb6747
//------------------------------ insert_region_before_proj ------------------------------------- | |
// Insert a region before an if projection (* - new node) | |
// | |
// before | |
// if(test) | |
// / | | |
// v | | |
// proj v | |
// other-proj | |
// | |
// after | |
// if(test) | |
// / | | |
// v | | |
// * proj-clone v | |
// | other-proj | |
// v | |
// * new-region | |
// | | |
// v | |
// * dum_if | |
// / \ | |
// v \ | |
// * dum-proj v | |
// proj | |
// | |
RegionNode* PhaseIdealLoop::insert_region_before_proj(ProjNode* proj) { |
testWhileLTDecr(MIN_VALUE + 2000, -2000); | ||
check(MIN_VALUE + 2001); // MAX_VALUE + 2002 iterations | ||
testWhileLTDecr(MIN_VALUE + 2000, MIN_VALUE + 2001); | ||
check(MIN_VALUE + 2001); // MAX_VALUE + 2002 iterations |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you also add a randomized input test here, that plays close to the boundaries? Just to make sure we would catch things like some off-by one errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, I've added some random test and computed the number of iteration that I expect. I only did it for the interesting cases.
Co-authored-by: Emanuel Peter <emanuel.peter@oracle.com> Co-authored-by: Tobias Hartmann <tobias.hartmann@oracle.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update is good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work with the proofs, good work @chhagedorn !
// v v v | | ||
// exit-region | | ||
// | | | ||
// dummy-if | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still, I don't see this dummy-if
mentioned in any of the comments. Can you add a comment line about what this is, and where it comes from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point, added a note.
Thanks Vladimir and Emanuel for your reviews! I've submitted some sanity performance testing since we could now be bailing out more. I'm not sure though how much else we could do if there are some performance regressions. |
Performance testing looked good. Thanks again for your careful reviews! /integrate |
Going to push as commit ef101f1.
Your commit was automatically rebased without conflicts. |
@chhagedorn Pushed as commit ef101f1. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
/backport jdk:jdk23 |
@chhagedorn the backport was successfully created on the branch backport-chhagedorn-ef101f1b-jdk23 in my personal fork of openjdk/jdk. To create a pull request with this backport targeting openjdk/jdk:jdk23, just click the following link: The title of the pull request is automatically filled in correctly and below you find a suggestion for the pull request body:
If you need to update the source branch of the pull then run the following commands in a local clone of your personal fork of openjdk/jdk:
|
A signed test is wrongly split off an unsigned test during Partial Peeling which results in not entering a loop even though it should.
Idea of Partial Peeling
Partial Peeling rotates a loop with the hope to being able to convert the loop into a counted loop later. It is therefore preferable to use signed tests over unsigned tests because counted loops must use a signed loop exit test (i.e. with a
BaseCountedLoopEnd
).Partial Peeling with Unsigned Test
However, if there is only a suitable unsigned test, we can still try to use it for Partial Peeling. The idea is to then split off a signed version of the unsigned test and place it right before the unsigned test which can then be used as a loop exit test and later as
BaseCountedLoopEnd
:jdk/src/hotspot/share/opto/loopopts.cpp
Lines 3074 to 3080 in 0ea3bac
Requirements for Using an Unsigned Test
The Signed and Unsigned Loop Exit Test do not need have the same result at runtime. It is sufficient if the Signed Loop Exit Test implies the Unsigned Loop Exit Test:
The Requirements Are Currently Broken
This strong requirement for splitting off a signed test is currently broken as seen in the test cases (for example,
testWhileLTIncr()
): We split off the signed loop exit testi >= limit
, then partial peel and we get the signed loop exit testi >= limit
as entry guard to the loop which is wrong:jdk/test/hotspot/jtreg/compiler/loopopts/TestPartialPeelAtUnsignedTestsNegativeLimit.java
Lines 159 to 178 in 0ea3bac
Why Are the Requirements Broken?
The reason is that
can only be converted into the two signed comparisons
if
limit
is non-negative (i.e.limit >= 0
):jdk/src/hotspot/share/opto/loopopts.cpp
Lines 3054 to 3061 in 0ea3bac
This is currently missing and we wrongly use
i < 0
ori >= limit
as split off signed test.Fixing the Broken Requirements
To fix this, I've added a bailout when
limit
could be negative which is the same as checking if the type oflimit
contains a negative value:jdk/src/hotspot/share/opto/loopopts.cpp
Lines 3062 to 3066 in 0ea3bac
Thanks,
Christian
Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/19522/head:pull/19522
$ git checkout pull/19522
Update a local copy of the PR:
$ git checkout pull/19522
$ git pull https://git.openjdk.org/jdk.git pull/19522/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 19522
View PR using the GUI difftool:
$ git pr show -t 19522
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/19522.diff
Webrev
Link to Webrev Comment