8372634: C2: Materialize type information from instanceof checks#28517
8372634: C2: Materialize type information from instanceof checks#28517iwanowww wants to merge 8 commits intoopenjdk:masterfrom
Conversation
|
👋 Welcome back vlivanov! A progress list of the required criteria for merging this PR into |
|
@iwanowww 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 113 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 |
| static boolean lateInlineInstanceOfCondPre(A o, boolean cond) { | ||
| return cond && (o instanceof B); | ||
| } | ||
|
|
||
| static boolean lateInlineInstanceOfCondPost(A o, boolean cond) { | ||
| return (o instanceof B) && cond; | ||
| } | ||
|
|
||
| static boolean lateInlineIsInstanceCondPre(A o, boolean cond) { | ||
| return cond && B.class.isInstance(o); | ||
| } | ||
| static boolean lateInlineIsInstanceCondPost(A o, boolean cond) { | ||
| return B.class.isInstance(o) && cond; | ||
| } |
There was a problem hiding this comment.
What about the non‑late version of these methods?
There was a problem hiding this comment.
There are corresponding test cases (testInstanceOfCondPre et al) where conditions are embedded.
The idea of testInstanceOfCondLate and similar test cases is to check how inlining works when condition improves receiver type during incremental inlining phase.
There was a problem hiding this comment.
What I meant was where the instanceof is in the called method, the testInstanceOfCondPre all have the instanceof checks as part of the if statement.
Something like:
static void testInstanceOfCondDefaultInlinePre(A a, boolean cond) {
if (defaultInlineInstanceOfCondPre(a, cond)) {
a.m();
}
}
static void testInstanceOfCondDefaultInlinePost(A a, boolean cond) {
if (defaultInlineInstanceOfCondPost(a, cond)) {
a.m();
}
}
static void testIsInstanceCondDefaultInlinePre(A a, boolean cond) {
if (defaultInlineIsInstanceCondPre(a, cond)) {
a.m();
}
}
static void testIsInstanceCondDefaultInlinePost(A a, boolean cond) {
if (defaultInlineIsInstanceCondPost(a, cond)) {
a.m();
}
}I suggest adding such a test because of real world code which use different internal implementation classes but expose their public API as only a single common supertype, like java.lang.constant.ClassDesc and its isPrimitive()/isArray()/isClassOrInterface() methods (which currently don’t do the instanceof check, but they probably should so that they can be reliably inlined).
There was a problem hiding this comment.
The test is intended as a white-box test. It focuses on bytecode shapes which result in different IR representations and exercise different optimizations. From compiler perspective, there's no difference between if (defaultInlineInstanceOfCond(a)) { ... } and if (a instanceof B) {...} when inlining happens during parsing. Both test cases produce the very same IR after parsing is over.
There was a problem hiding this comment.
It might be useful to have these tests in case the default inlining IR changes in the future.
There was a problem hiding this comment.
It's intended as a unit test. It's better to catch inlining issue with targeted tests on inlining. From compiler perspective, there's no reason to cover other cases here. There are so many different scenarios how a subtype check can show up in IR. And different scenarios can theoretically fail due to different reasons.
Webrevs
|
|
I tried the patch and the test case out of curiosity but when I removed the change to |
| return true; // found | ||
| } | ||
|
|
||
| // Match an instanceof check. |
There was a problem hiding this comment.
We seem to require that the input of SubTypeCheck is not null. What do you think about allowing SubTypeCheck to accept null and return false?
There was a problem hiding this comment.
Yes, it's a good idea and the right direction to move. While experimenting with a different enhancement, I noticed that a subtype check leaves a null check behind irrespective of whether the check goes away or not.
Unfortunately, there are some engineering considerations which complicates the change. SubTypeCheck is shared across all the places where subtype checks are performed, but checkcast and instanceof differ in the way null is handled. So, the proper way to fix it is to introduce a higher-level representation which implicitly handles nulls and then eventually lower it to SubTypeCheck and materialize null check if needed.
There was a problem hiding this comment.
There are multiple ways without having to have yet another higher-level representation. The first one is that since SubTypeCheck does not accept null now, we can just choose one result for null. Choosing the instanceof approach may be a little more desirable, as it removes the need to perform this complicated match, and for checkcast we can manually insert a CheckCastPP anyway. Another solution is to have another input to SubTypeCheck which gives the result when the obj is null. On a whim, I kind of like this, as we can match both the checkcast and the instanceof pattern here, it also simplifies GraphKit::gen_checkcast, as we do not have to worry about "the cast that always succeeds will leave behind a null check".
Just a suggestion, though. This PR is fine as it is to me.
There was a problem hiding this comment.
I agree it can be implemented without introducing new fancy IR nodes. The open question to me though is whether we can live without materializing null check until SubTypeCheck nodes are macro expanded. Otherwise, it'll turn into a gradual lowering through different representations.
There was a problem hiding this comment.
BTW null checks which have to be merged back break the matching of the IR shape. Something to improve as a follow-up change. Added new test cases to track it.
|
Thanks, Roland! I slightly reworked the test to make it more robust. |
|
Any reviews, please? |
| (*obj) = extract_obj_from_klass_load(&gvn, val); | ||
| (*cast_type) = tcon->isa_klassptr()->as_instance_type(); | ||
| return true; // found | ||
| } |
There was a problem hiding this comment.
The old code checked klass_is_exact() for this case, but the new code does not, so was it redundant, given we have a constant?
There was a problem hiding this comment.
Yes, the check is redundant. Moreover, I tested the patch having the check turned into an assert.
dean-long
left a comment
There was a problem hiding this comment.
Looks reasonable, but I'm not an expert in this area.
|
Thanks for the reviews, Dean and Quan. @rwestrel do you want to take a look? |
| // Parse compilation log (-XX:+PrintCompilation -XX:+PrintInlining output). | ||
| static int parseOutput(List<String> output) { | ||
| int successCount = 0; | ||
| Pattern compilation = Pattern.compile("^\\d+\\s+\\d+.*"); | ||
| StringBuilder inlineTree = new StringBuilder(); | ||
| for (String line : output) { | ||
| // Detect start of next compilation. | ||
| if (compilation.matcher(line).matches()) { | ||
| // Parse output for previous compilation. | ||
| successCount += (validateInliningOutput(inlineTree.toString()) ? 1 : 0); | ||
| inlineTree = new StringBuilder(); // reset | ||
| } | ||
| inlineTree.append(line); | ||
| } | ||
| // Process last compilation | ||
| successCount += (validateInliningOutput(inlineTree.toString()) ? 1 : 0); | ||
| return successCount; | ||
| } | ||
|
|
||
| // Sample: | ||
| // 213 42 b compiler.inlining.TestSubtypeCheckTypeInfo::testIsInstanceCondLatePost (13 bytes) | ||
| static final Pattern TEST_CASE = Pattern.compile("^\\d+\\s+\\d+\\s+b\\s+" + TEST_CLASS_NAME + "::(\\w+) .*"); |
There was a problem hiding this comment.
Drive by comment, no need to change things here now:
@iwanowww @chhagedorn Would it not be nice if we could do this kind of matching with the TestFramework? Instead of IR matching, just match the output of any compilation tracing / printing.
There was a problem hiding this comment.
Indeed, that would be a much better way.
Also, -XX:+LogCompilation is a nice option since publishes all information in a structured way, but it would introduce a dependency on LogCompilation tool in the test library.
There was a problem hiding this comment.
That's an interesting idea to think about more. But I it would be a separate concept next to @IR even though some code could probably be shared.
but it would introduce a dependency on LogCompilation tool in the test library.
The IR framework already uses LogCompilation to parse the IR dumps from. But I think that's quite an overhead - a lot of the information is not needed and makes the parsing logic more complicated.
I've been thinking about introducing a separate IR framework file for all relevant dumps, similar to dumping IGV dumps to a separate file. This will simplify things and only dumps relevant information. It will also have a positive impact on performance.
We could even go a step further and send the dumps over a port to the IR framework (similar to dumping the graph directly to the opened IGV over the network). Today, we already have sockets in place to send messages from the test VM to the driver VM. I guess we could extend that to also allow HotSpot to send dumps to the driver VM (it sounds feasible to do but would need to experiment with it).
Sending dumps from HotSpot to the IR framework has another benefit that HotSpot can provide all the information needed for the IR framework to figure out where this dump belongs to (i.e. no additional parsing of a file needed). This might also allow us to do IR matching in parallel to the test VM execution. This is much more efficient because today, the driver VM just waits until the test VM is finished and only then starts to do IR matching.
Anyway, that was just a digression about future ideas for the IR framework. What you have now to parse the output is the best we can do today.
|
Updated comments and improved the test (improved robustness and added new test cases with nulls). Please, re-review. |
|
Why the reversal on should_delay_inlining unification? Did it cause problems? |
Yes, there were some assertion failures observed during testing. I thought that it is an equivalent change, but it turned out it's not. So, I reverted it. I plan to look into it separately. |
|
Thanks for the reviews, Quan, Roland, and Dean. /integrate |
|
Going to push as commit f2e56e4.
Your commit was automatically rebased without conflicts. |
Even though
instanceofcheck (and reflectiveClass.isInstancecall) narrows operand's type, sharpened type information is not explicitly materialized in the IR.There's a
SubTypeChecknode present, but it is not a substitute for aCheckCastPPnode with a proper type.The difference can be illustrated with the following simple cases:
vs
Proposed fix annotates operands of subtype checks with proper type information which reflects the effects of subtype check. Not-yet-canonicalized IR shape poses some challenges, but I decided to match it early so information is available right away, rather than waiting for IGVN pass and delay inlining to post-parse phase.
FTR it is not a complete fix. It works for trivial cases, but for more complex conditions the IR shape becomes too complex during parsing (as illustrated by some test cases). I experimented with annotating subtype checks after initial parsing pass is over, but the crucial simplification step happens as part of split-if transformation which happens when no more inlining is possible. So, the only possible benefit (without forcing split-if optimization earlier) is virtual-to-direct call strength reduction. I plan to explore it separately.
Testing: hs-tier1 - hs-tier5
Progress
Issue
Reviewers
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/28517/head:pull/28517$ git checkout pull/28517Update a local copy of the PR:
$ git checkout pull/28517$ git pull https://git.openjdk.org/jdk.git pull/28517/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 28517View PR using the GUI difftool:
$ git pr show -t 28517Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/28517.diff
Using Webrev
Link to Webrev Comment