-
Notifications
You must be signed in to change notification settings - Fork 5.8k
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
8331717: C2: Crash with SIGFPE Because Loop Predication Wrongly Hoists Division Requiring Zero Check #22666
8331717: C2: Crash with SIGFPE Because Loop Predication Wrongly Hoists Division Requiring Zero Check #22666
Conversation
👋 Welcome back theoweidmannoracle! A progress list of the required criteria for merging this PR into |
@theoweidmannoracle 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 263 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. As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@vnkozlov, @chhagedorn, @merykitty) but any other Committer may sponsor as well. ➡️ To flag this PR as ready for integration with the above commit message, type |
@theoweidmannoracle 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 for this issue.
Would be interesting if we can collapse such graph by propagating |
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 summary! Looks good to me, too.
Would be interesting if we can collapse such graph by propagating
I1 == 0
throughi4
into zero check. As separate RFE.
I think in this case, it's not possible because we have an OSR compilation where i1
is just a LoadI
.
It seems strange to me that a |
Or if the control here is used for something else you can add another input to |
Another way to tackle this is to think of a |
The DivI is rewired and attached to the RangeCheck by IGVN in this case. Looking at the code int i1 = 0;
for (int i4 : iArr) {
i4 = i1;
try {
iArr[0] = 1 / i4; // 183 DivI
i4 = iArr[2 / i4]; // 230 DivI
} catch (ArithmeticException a_e) {
}
} and the resulting relevant nodes: After several rounds of IGVN, we arrive at this state, where everything is still completely expected. 230 DivI is connected to its zero-check IGVN/Idealization will now see that the zero-check-if for 230 DivI is dominated by the zero-check-if for 183 DivI and therefore remove it from the graph and attach 230 DivI to the RangeCheck. This seems a benign and useful transformation in general. |
I think that's fine. After all, depends_only_on_test indicates that
which is absolutely true for division and was also correctly used by the IGVN transformation. The source of the problem here is really with the way Invariance works. |
I believe the issue is right there. If a check is removed because it is equivalent to a dominating check, then the If you want to rewire the jdk/src/hotspot/share/opto/ifnode.cpp Line 1583 in 8efc558
|
It's probably the most safe way to eventually handle this similarly to array accesses as you suggest. Back there when the checks for However, for this bug, I think it's better to go with this rather simple point fix and file a follow-up RFE to revisit this and think about aligning it with array accesses with a pinning that's checkable with |
@chhagedorn I think that the wrong control input of the |
This is a general problem which I've been thinking about many times: Should we make sure that a division, whose divisor could be zero, is always pinned at a zero check? I'm not entirely sure about all places where a division node could become control dependent on other control nodes. Back there, when I worked on JDK-8257822, the problem was that we split a zero check through a region and the div node ended up at a region. Eventually, the division was wrongly hoisted before the actual zero check. We were not really sure about all the places where a zero check could lose the connection to its zero check. As soon as this happens, I think we always have a problem when rewiring a division, pinned at some control node that's unrelated, to an earlier dominating check: We could skip over the original check. Therefore, for JDK-8257822, we've decided to block the rewiring of division nodes instead of relying on spotting all the places where the disconnect could happen to fix it there. There were follow-up fixes to block other places and it seems that we have now found yet another place where we rewire a division that's not control dependent on the original check. Maybe it should have been handled with I'm not sure how big the impact of this patch will be on performance. But my guess is that it's probably on the lower side. If we look again at the graph posted by Theo above: If Loop Predication did not hoist So, from my understanding, the only cases we now forbid and that could have a performance impact are those where we hoist multiple zero checks in one round of loop opts. But as shown above, it's unsafe to perform this hoisting because the current code expects that division nodes are not rewired and stay pinned inside the loop. Long story short, I think there are several ways to handle this general problem of divisions floating above the actual zero check:
Option 1 (i) seems to be the simplest one for now. Option 2 would be more safe/correct but possibly has a bigger performance impact and might not be justified without a failing case. Option 3 would be interesting but seems to have a big impact and might not be so easy to do - could be worth to file an RFE. Even when going with Option 1 or 2, we could still allow rewiring of divisions to a dominating check if we can prove that this is a zero check that covers the division to be rewired (e.g. same divisor and comparison excludes zero etc.) - and maybe that's all we need (discussed this idea with @eme64). Could be investigated with/against Option 3. This became longer than I've intended but I guess it helped to reflect on the situation and the ways to go from here. Let me know what you think about it and the suggestion to go with option 1 for now. And also if there are some more or other things to consider. |
@chhagedorn Thanks for your really detailed explanation! I really appreciate it. I am worried that this change may prohibit hoisting division out of loops in general. For example,
Then the division is anchored in the loop because the zero check of One find suggestion, though, what do you think about overriding |
You're welcome :-)
Unfortunately, this is already the case before this fix. We can only hoist the zero check out of the loop but not the division itself - it ends up at the next control node which is the
I think that aligns with your suggestion here:
Not exactly sure what you mean with the Nevertheless, with this fix, I don't think we make the current situation worse: We cannot hoist divisions out of the loop and Theo's patch just fixes an edge case, where we wrongly hoisted a division out of a loop. My guess is that disallowing that edge case does not affect performance much. |
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.
Got it, thanks for the clarification.
Sure, you're welcome :-) Just to let you know, Theo is currently away and will come back to this after the Christmas break. |
I have looked more deeply into the issue with To start with, what is For example:
Then
On the other hand, consider this case:
Then So, what is the issue in JDK-8257822? When the zero check of the division is split through The fix was to make One important consideration regarding nodes that
If we want to keep
It can easily be seen that the division now does not For example:
Without the fix,
Now For the case in this issue, it is precisely caused by this wrong approach:
Because As a result, this will inevitably converge to us doing However, it is still not enough. The core issue here is that a node being disconnected from its test but still reporting itself as
Then if the test I have skimmed through the code and it seems we do actually pin the node when the new control input is not equivalent to the old control input, but only for array accesses? I believe this should be applied to all In conclusion, I would strongly prefer a proper fix in which we pin all |
@merykitty Thank you for your detailed write-up! @chhagedorn and I talked about it and we also agree with you that there seems to be some fundamental flaw here that needs to be addressed. We should definitely address it soon. It looks like a bigger endeavor though and we should probably file your write-up as RFE so it does not get buried here. Do you think we should give up on this point fix then? Or do you think it's fine if we merge it and address the underlying cause separately? We still believe that there's no harm in applying this band-aid patch in the way I proposed, while, of course, we have to address the underlying issue here too. @rwestrel added pin_array_access_node. Maybe you also want to weigh in on this? |
@theoweidmannoracle I think this fix is fine, please go ahead
|
@merykitty Thanks for opening the RFE! |
/integrate |
@theoweidmannoracle |
/sponsor |
Going to push as commit 55c6904.
Your commit was automatically rebased without conflicts. |
@chhagedorn @theoweidmannoracle Pushed as commit 55c6904. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
Fixes a bug in loop predication where not strictly invariant tests involving divisions or modulo are pulled out of the loop.
The bug can be seen in this code:
The crucial element is the division

2 / i4
. Since it is used to access an array, it is the input to a range check. See node 230:Loop predication will try to pull this range check together with its input, the division, before the
for
loop. Due to a bug in Invariance::compute_invariance loop predication is allowed to do so, which results in the division being pulled out without its non-zero check. 322 is a clone of 230 placed before the loop head without any zero check for the divisor:More specifically, this bug occurs because 230's zero check (174 If) is not its direct control. Between the zero check and the division is another unrelated check (293 RangeCheck), which can be hoisted:
Due to the way the Invariance class works, a check that can be hoisted will be marked as invariant. Then, to determine if any given node is invariant, Invariance::compute_invariance checks if all its inputs are invariant:
jdk/src/hotspot/share/opto/loopPredicate.cpp
Lines 456 to 475 in ceb4366
Therefore, when recursively traversing the inputs for 230 Div, the hoisted, unrelated check 293 RangeCheck is hit before the zero check. As that check has been hoisted before already, it is marked invariant and
all_inputs_invariant
will be set to true. (All other inputs are also trivially invariant as they are constant.)To fix this, Invariance::compute_invariance must check that the node not only
depends_only_on_test()
on line 472 but also that it hasno_dependent_zero_check(n)
.Similar past bug, which introduced
no_dependent_zero_check
: openjdk/jdk16#9Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/22666/head:pull/22666
$ git checkout pull/22666
Update a local copy of the PR:
$ git checkout pull/22666
$ git pull https://git.openjdk.org/jdk.git pull/22666/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 22666
View PR using the GUI difftool:
$ git pr show -t 22666
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/22666.diff
Using Webrev
Link to Webrev Comment