-
Notifications
You must be signed in to change notification settings - Fork 6.1k
8359344: C2: Malformed control flow after intrinsic bailout #25936
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
8359344: C2: Malformed control flow after intrinsic bailout #25936
Conversation
|
👋 Welcome back mchevalier! A progress list of the required criteria for merging this PR into |
|
@marc-chevalier 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 52 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 |
|
@marc-chevalier The following labels 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 lists. 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.
Nice analysis! In general, the fix looks good to me. I added a few comments / suggestions.
| return false; | ||
| } | ||
| destruct_map_clone(old_map); | ||
| destruct_map_clone(old_state.map); |
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 think destruct_map_clone could be refactored to take a SavedState.
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've made an override of destruct_map_clone taking a SavedState (and delegating to the existing one) rather than changing the existing one for the following reasons:
destruct_map_cloneis inGraphKitso doesn't know aboutSavedState. Either I'd need to bringSavedStateto the base class (useless visibility) (or something with forward declarations...) or movedestruct_map_cloneto the derived classLibraryCallKitdestruct_map_clonemakes sense to have next toclone_map. Butclone_mapis used also inGraphKit, so not possible to move to the derived class- The existing
destruct_map_clonedoesn't need aSavedStateand makes sense without. Requiring more information just make it less usable, but it's fine to have a thin adapter that one can by-pass if one has aSafePointNodeand not a wholeSavedState.
| state.jvms = jvms(); | ||
| state.map = clone_map(); | ||
| for (DUIterator_Fast imax, i = control()->fast_outs(imax); i < imax; i++) { | ||
| Node* out = control()->fast_out(i); |
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 have a similar issue with non-control users? For example, couldn't we also have stray memory users after bailout?
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.
We could, but it should be relatively harmless. Control is more annoying to have more than one successor.
test/hotspot/jtreg/compiler/intrinsics/VectorIntoArrayInvalidControlFlow.java
Outdated
Show resolved
Hide resolved
|
@marc-chevalier this pull request can not be integrated into git checkout fix/too-many-ctrl-successor-after-intrinsic
git fetch https://git.openjdk.org/jdk.git master
git merge FETCH_HEAD
# resolve conflicts and follow the instructions given by git merge
git commit -m "Merge master"
git push |
…uccessor-after-intrinsic
|
I've addressed the comments, ready for a second pass! |
| struct SavedState { | ||
| uint sp; | ||
| JVMState* jvms; | ||
| SafePointNode* map; | ||
| Unique_Node_List ctrl_succ; | ||
| }; | ||
| SavedState clone_map_and_save_state(); | ||
| void restore_state(const SavedState&); | ||
| void destruct_map_clone(const SavedState& sfp); | ||
|
|
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.
Can this be a class instead of struct? These methods could be members. Initialization can be done through constructor. The destructor can do restoration by default unless destruct_map_clone() was called before.
I don't like name destruct_map_clone() for this. How about SavedState::remove() or something.
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 like it. Since intrinsic implementations have mostly bailing out returns, and few success paths, it's nice to say when we are good, rather than every path that ends with bailing out.
I've called the member function discard. It gives as old_state.discard(), which reads well, I think.
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! Looks good to me.
|
Turns out in 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.
Looks good.
|
/integrate Thanks @vnkozlov and @TobiHartmann for reviews! |
|
Going to push as commit 3ffc5b9.
Your commit was automatically rebased without conflicts. |
|
@marc-chevalier Pushed as commit 3ffc5b9. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
When intrinsic bailout, we assume that the control in the
LibraryCallKitdid not change:jdk/src/hotspot/share/opto/library_call.cpp
Line 137 in c4fb00a
This is enforced by restoring the old state, like in
jdk/src/hotspot/share/opto/library_call.cpp
Lines 1722 to 1732 in c4fb00a
That is good, but not sufficient. First, the most obvious, one could have already built some structure without moving the control. For instance, we can obtain something such as:
Here, during late inlining, the call
323is candidate to be inline, but that bails out. Yet, a call tomake_unsafe_addresswas made, which built nodes354 Ifand everything under. This is needed as tests are made on the resulting nodes (especially366 AddP) to know whether we should bail out or not. At the end, we get 2 control successor to346 IfFalse: the call that is not removed and the leftover of the intrinsic that will be cleanup much later, but not by RemoveUseless.Another situation is somewhat worse, when happening during parsing. It can lead to such cases:
The nodes
31 OpaqueNotNull,31 If,36 IfTrue,33 IfFalse,35 Halt,44 If,45 IfTrue,46 IfFalseare leftover from a bailing out intrinsic. The replacement call49 CallStaticJavashould come just under5 Parm, but the control was updated and the call is actually built under36 If. Then, why does the previous assert doesn't complain?This is because there is more than one control, or one map. In intrinsics that need to restore their state, the initial
SafePointmap is cloned, the clone is kept aside, and if needed (bailing out), we set the current map to this saved clone. But there is another map from which the one of theLibraryCallKitcomes, and that survives longer, it's the one that is contained in theJVMState:jdk/src/hotspot/share/opto/library_call.cpp
Lines 101 to 102 in c4fb00a
And here there is the challenge:
JVMState jvmscontains aSafePointmap, this map must havejvmsasjvms(pointer comparison)jvmsto be where it was, so that the graph construction can continue where it was.So... let's do that!
When a intrinsic bails out and regret its choice, we need to have remembered the old
JVMState, set the map of it correctly, set thejvmsof the map of it correctly, restore the map and sp of theLibraryCallKitas it was done before. On top of that, we remember control nodes that existed under ourcontrol()before trying to intrinsify: new control nodes that is not the (new) current map(= the clone of the map before) are disconnected to leave a nice CFG.This has 2 interesting consequences:
compiler/intrinsics/VectorIntoArrayInvalidControlFlow.java, compilation used to bailout because of malformed CFG on Aarch64. I'm adding a test that reproduced on x64 and Aarch64 and make the compilation bailout into a crash. The graph is now correctly shape on intrinsic bailout.compiler/unsafe/OpaqueAccesses.java, the whole (useless) structure introduced by the bailing out intrinsic is now removed. The call is now connected to where the intrinsic started, not where it ended (as shown under). I've adapted this test to check we don't have both a call and intrinsic leftover. Some of these cases are being intrinsiced, some are left as a call, and I don't want to be too strict about which must be which, as long as they are not both at the same time.Thanks,
Marc
Progress
Issue
Reviewers
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/25936/head:pull/25936$ git checkout pull/25936Update a local copy of the PR:
$ git checkout pull/25936$ git pull https://git.openjdk.org/jdk.git pull/25936/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 25936View PR using the GUI difftool:
$ git pr show -t 25936Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/25936.diff
Using Webrev
Link to Webrev Comment