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
8303737: C2: Load can bypass subtype check that enforces it's from the right object type #15595
Conversation
👋 Welcome back roland! A progress list of the required criteria for merging this PR into |
Webrevs
|
test/hotspot/jtreg/compiler/controldependency/TestLoadBypassesClassCast.java
Outdated
Show resolved
Hide resolved
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! Took me some time to grasp it but after thinking about it some more after our offline discussion, I think it's a good way to fix this problem.
Apart from some small code style comments, it looks good to me!
src/hotspot/share/opto/cfgnode.cpp
Outdated
// Collect types at casts that are going to be eliminated at that Phi and store them in a TypeTuple. | ||
// Sort the types using an arbitrary order so a list of some types always hashes to the same TypeTuple (and TypeTuple | ||
// pointer comparison is enough to tell if 2 list of types are the same or not) | ||
const TypeTuple* PhiNode::collect_types(PhaseGVN* phase, const Node* r, const Type* phi_type) const { |
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 suggest to rename r
-> region
.
src/hotspot/share/opto/castnode.hpp
Outdated
|
||
bool higher_equal_types(PhaseGVN* phase, const Node* other) const; | ||
|
||
int nb_extra_types() const { |
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.
Maybe we could rename this to extra_types_count()
to make it more clear.
src/hotspot/share/opto/castnode.hpp
Outdated
ConstraintCastNode(Node *n, const Type *t, DependencyType dependency) | ||
: TypeNode(t,2), _dependency(dependency) { | ||
ConstraintCastNode(Node* n, const Type* t, ConstraintCastNode::DependencyType dependency, | ||
const TypeTuple* types) |
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 suggest to rename types
to extra_types
to match the field name. Same in the other make*
methods.
src/hotspot/share/opto/castnode.cpp
Outdated
if (!TypeNode::cmp(n)) { | ||
return false; | ||
} | ||
if (((ConstraintCastNode&) n)._dependency != _dependency) { |
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.
Maybe you can put ((ConstraintCastNode&) n)._dependency
into a variable as you are using it a couple of times.
src/hotspot/share/opto/cfgnode.cpp
Outdated
@@ -2133,10 +2133,12 @@ Node *PhiNode::Ideal(PhaseGVN *phase, bool can_reshape) { | |||
// Add casts to carry the control dependency of the Phi that is | |||
// going away | |||
Node* cast = nullptr; | |||
const TypeTuple* extra_types = collect_types(phase, r, phi_type); |
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.
Completely optional but you could just directly use bottom_type()
and in(0)
inside collect_types
instead of passing it with r
and phi_type
.
@rwestrel 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 |
…ClassCast.java Co-authored-by: Andrey Turbanov <turbanoff@gmail.com>
Co-authored-by: Christian Hagedorn <christian.hagedorn@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.
That looks good to me. Correctness and performance testing also passed.
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 for the updates!
/integrate |
Going to push as commit 52983ed.
Your commit was automatically rebased without conflicts. |
This patch has 3 test cases:
TestAddPChainMismatchedBase.java
TestAddPChainMismatchedBase2.java
Both fail with a "Base pointers must match" assert
TestLoadBypassesClassCast.java
fails with: assert(Universe::is_in_heap(result)) failed: object not in heap
All failures are related to 8139771 (Eliminating CastPP nodes at Phis
when they all come from a unique input may cause crash). The problem
there was that if a Phi merges say:
it was transformed to
in
. Doing that would cause the controldependency from the
Phi
on the null check that guards theCastPP
to be dropped. As demonstrated by the test case of 8139771, a load
could then float above the null check that should have guaranteed the
object being read from was non null.
The fix for 8139771 was to transform:
to:
That
CastPP
inherits the type of thePhi
and is flagged as aspecial kind of cast node so it's not eliminated if the
CastPP
doesn't narrow the type of its input. As a result some depending load
cannot float above the cast.
Because that was found to be too conservative, code was added to try
to eliminate cast node added as replacement of a
Phi
by looking fora dominating cast with the same input and a narrower type that the
cast. The idea was that if there was some dominating null check then
we would not need the cast. As TestLoadBypassesClassCast.java shows,
this is flawed.
TestLoadBypassesClassCast.java demonstrates that what used to happen
with a null check (a load that bypasses a null check) can still happen
with a type check: in the case of the test, the load of
objectField
from
o
happens beforeo
is checked to be of typeA
(the holderof the field). When
o
is not of typeA
, the result of the load isnever used by compiled code but if the gc runs while the result of the
load is live (as it does in the test case), gc code sees something
that's not a valid oop and crashes.
In the test case, the same logic is duplicated in 2 branches of
ifs. There are 2 loads of
objectField
, one in each copy of thelogic. After transformation, the
objectField
loads lose theirdependency on type checks for
A
and common above a safepoint causinga load from something that may not be a
A
to be live across asafepoint.
Now looking at one copy of the logic:
right above the
objectField
load, aPhi
(that merges the 2banches of
if (flag) {
and has type non null Object) is tested tobe a subtype of
A
.the subtype checks is split throught
Phi
but theCheckCastPP
stays at the merge point.
The
Phi
is replaced by aCastPP
to non nullObject
.The
CastPP
is replaced by a dominatingCastPP
to non null that'sat the beginning of the method.
The
if (flag)
goes away and theCheckCastPP
becomes controldependent on the range check for the array load that's above. That
range check is replaced by a dominating range check, itself at the
beginning of the method. Now the
CheckCastPP
is above any subtypecheck. The load can float as high as the
CheckCastPP
.The flaw here I think is the logic from 8139771 that assumes a cast
node can be replaced by any cast with a narrower or equal type. In
that case, the cast replaces a
Phi
that merges types not nullA
and not null
Object
which as a result has type non nullObject
. Itwould be fine to replace the cast with a dominating cast that's
narrower that the type of each input to the Phi (so in this case non
null
A
and non nullObject
). That's not the case for the cast ofthe null check. The problem is when the
Phi
is transformed to acast, the types of its inputs are lost. What I propose in the fix is
to attach those types to the cast and change the logic that decides
whether a cast is redundant (whether because of the type of its input
or because of a dominating cast) so it goes over the list of attached
types.
How does that relate to the other 2 test cases that fail because in a
chain of
Addp
nodes, not all nodes have the same base (some casted,some not)? What I found looking at those test cases is that the graph
contained some casts created as a result of the
PhiNode::Ideal
thatwere "obviously" useless but that are conservatively kept. Those casts
were involved in a chain of transformations that cause the assert. The
new logic does a better job of removing the casts and make the
failures go away. So the change doesn't directly fix it but, at the
very least, it makes it less likely and I'm not sure there's more work
needed at this point unless we see that failure again.
Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/15595/head:pull/15595
$ git checkout pull/15595
Update a local copy of the PR:
$ git checkout pull/15595
$ git pull https://git.openjdk.org/jdk.git pull/15595/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 15595
View PR using the GUI difftool:
$ git pr show -t 15595
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/15595.diff
Webrev
Link to Webrev Comment