Skip to content
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

8267807: C2: Downcast receiver to target holder during inlining #4212

Closed
wants to merge 1 commit into from

Conversation

iwanowww
Copy link
Contributor

@iwanowww iwanowww commented May 26, 2021

Virtual method calls involve an implicit subtype check against callee holder.
But if receiver type is too broad, it has to be narrowed before parsing the callee method.
Otherwise, it may cause problems during parsing and currently it simply blocks inlining.

Proposed fix implements the narrowing step and re-enables inlining.

Testing:

  • hs-tier1 - hs-tier9

Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed

Issue

  • JDK-8267807: C2: Downcast receiver to target holder during inlining

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.java.net/jdk pull/4212/head:pull/4212
$ git checkout pull/4212

Update a local copy of the PR:
$ git checkout pull/4212
$ git pull https://git.openjdk.java.net/jdk pull/4212/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 4212

View PR using the GUI difftool:
$ git pr show -t 4212

Using diff file

Download this PR as a diff file:
https://git.openjdk.java.net/jdk/pull/4212.diff

@iwanowww
Copy link
Contributor Author

/label add hotspot-compiler

@bridgekeeper
Copy link

bridgekeeper bot commented May 26, 2021

👋 Welcome back vlivanov! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk openjdk bot added the hotspot-compiler hotspot-compiler-dev@openjdk.org label May 26, 2021
@openjdk
Copy link

openjdk bot commented May 26, 2021

@iwanowww
The hotspot-compiler label was successfully added.

@iwanowww iwanowww marked this pull request as ready for review May 26, 2021 18:01
@openjdk openjdk bot added the rfr Pull request is ready for review label May 26, 2021
@mlbridge
Copy link

mlbridge bot commented May 26, 2021

Webrevs

bool has_receiver = !cha_monomorphic_target->is_static();
bool is_interface_holder = cha_monomorphic_target->holder()->is_interface();
if (has_receiver && !is_interface_holder) {
if (!cha_monomorphic_target->holder()->is_subtype_of(receiver_type->klass())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given we can't trust interface types, shouldn't we test for receiver_type not an interface here?

Copy link
Contributor Author

@iwanowww iwanowww May 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though it doesn't happen in practice now, we can trust interface types on the receiver object because virtual/interface method invocation implies proper type check of the receiver.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need a CheckCastPP as the comment (written by me long ago IIRC) suggests.

I’d like to believe you can trust the type of a CHA receiver, but I don’t. What the IR says about interface types is (last time I looked) just as untrustworthy as what the verifier thinks about them.

(We should fix this decisively someday. Or did we do this cleanup already and missed it? We have had dozens of bugs
in early C2 because of untrustworthy interface types.)

Suppose CHA (of default methods, yay!) says that since x is interface Foo, then default Foo::m is the only possible target
of x.m. Before we execute Foo::m we still need a type check of x, because it could be any Object whatsoever.

I suggest adding the CheckCastPP in, in all modes, with fallback to de-opt not VM crash. The IR types are saying there is not a dominating check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I closely looked at the relevant logic and couldn't find any possibility to bypass receiver subtype check.

There are 2 call site shapes possible:

  1. invokevirtual C.m RECV => J.m DEFAULT (J.m - resolved and selected method)

Verifier guarantees that RECV <: C (REFC) and method resolution ensures that C <: J (REFC <: DECC).

  1. invokeinterface I.m RECV => J.m DEFAULT (J.m - resolved and selected method)

I (REFC) is required to be an interface (structural constraint of invokeinterface instruction).

Then, here's the relevant code:

  ciInstanceKlass* actual_receiver = resolved_iklass;
  ...
  ciInstanceKlass *ikl = receiver_type->klass()->as_instance_klass();
  if (ikl->is_loaded() && ikl->is_initialized() && !ikl->is_interface() &&
      (ikl == actual_receiver || ikl->is_subtype_of(actual_receiver))) {
    // ikl is a same or better type than the original actual_receiver,
    // e.g. static receiver from bytecodes.
    actual_receiver = ikl;
    // Is the actual_receiver exact?
    actual_receiver_is_exact = receiver_type->klass_is_exact();
  }

  ciInstanceKlass*   calling_klass = caller->holder();
  ciMethod* cha_monomorphic_target = resolved_method->find_monomorphic_target(calling_klass, resolved_iklass, actual_receiver, check_access);

...

ciMethod* ciMethod::find_monomorphic_target(...) {
  ...
  if (actual_recv->is_interface()) {
    // %%% We cannot trust interface types, yet.  See bug 6312651.
    return NULL;
  }

receiver_type can represent either an interface (1a) or a class (1b ikl).

1a. But if ikl->is_interface(), then actual_receiver stays as an interface (I, REFC) and find_monomorphic_target() returns NULL.

1b. If ikl is a class, then ikl != actual_receiver and depending on whether ikl->is_subtype_of(actual_receiver)), actual_receiver is either REFC (interface) or receiver_type.

find_monomorphic_target() can succeed only in the latter case (actual_receiver is a class and not an interface), but previous static subtype check guarantees that the receiver type (a class) implements the interface.

Q.E.D

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, CHA is still kept conservative when interface types on receiver are encountered.
Currently, only single implementor case is optimised, but there are proper receiver subtype checks in place.

It should be quite straightforward to relax aforementioned constraints on receiver types (only classes, no interfaces), but then we would require additional receiver subtype check accompanying Compile::optimize_virtual_call() calls.

Copy link
Contributor Author

@iwanowww iwanowww Jun 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, as a defense in depth measure, we could still insert receiver subtype check when CHA returns a default method and hope that the check will go away once the type system better tracks interface types. Though the subtype check against an interface can be quite expensive, I don't expect any performance regressions considering CHA hasn't optimized default methods before.

Copy link
Contributor Author

@iwanowww iwanowww Jun 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Evidently, it's not the whole story :-) Otherwise, we wouldn't have needed downcasts at all.

It becomes more interesting when CHA discovers unique concrete method under REFC:

3a. invokevirtual C.m RECV => J.m ABSTRACT, unique = K.m DEFAULT, K <: J (I.m - resolved method, REFC = C)
3b. invokeinterface I.m RECV => J.m ABSTRACT, unique = K.m DEFAULT, K <: J (I.m - resolved method, REFC = I)

(Moreover, 3b is equivalent to 3a since actual_receiver has to be a class and
not an interface. Otherwise, CHA fails fast. There's additional case - unique
implementor - but it strength-reduces invokeinterface to invokevirtual.)

It is guaranteed that RECV <: C <: J and RECV <: I < J, but there's nothing except the CHA itself that
proves RECV <: J always holds.

In 3a and 3b case, CHA iterates all the subtypes under statically known receiver type
(actual_receiver, subtype of I/C). Assuming the analysis is correct, it proves
that RECV <: J.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a summary: if we feel safer when there's a dynamic receiver subtype check always present, it could be added.

We would pay with performance (the check can become quite costly in some corner cases), but it shouldn't introduce performance regressions (compared to previous releases): CHA didn't return default methods before and interface call involves implicit receiver type check.

(Need to be cautious not to replace a type profile-guided monomorphic case with a statically determined target interface method + interface subtype check. So, additional complexity in implementation as well.)

Copy link
Contributor

@rwestrel rwestrel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me

@openjdk
Copy link

openjdk bot commented May 27, 2021

@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:

8267807: C2: Downcast receiver to target holder during inlining

Reviewed-by: roland, thartmann

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 86 new commits pushed to the master branch:

  • ae2f37f: 8267616: AArch64: Fix AES assertion messages in stubGenerator_aarch64.cpp
  • c06db45: 8267921: Remove redundant loop from sun.reflect.misc.ReflectUtil.privateCheckPackageAccess()
  • 382e7ec: 8246351: elements in headings are of incorrect size
  • 5df25dc088cfc3069e451b48c4f013d1d0491aa2: 8266807: Windows os_windows-gtest broken for UseLargePages
  • ce44cd6881bcbef81a840d7961a951ba586c0eae: 8267845: Add @requires to avoid running G1 large pages test with wrong page size
  • 4ade125c8a53e0bdc105e5f65e8c1d7aa13db950: 8267934: remove dead code in CLD
  • bd31653e6f99d4337e4af1f7f138d688ec99c19d: 8267938: (sctp) SCTP channel factory methods should check platform support
  • 7ab6b4012026d4786a4c3937b559da9d3142a228: 8267375: Aarch64: JVM crashes with option -XX:PrintIdealGraphLevel=3 on SVE backend
  • 2c8e94f6804fee269a882a3e92b7ce844451eb11: 8247403: JShell: No custom input (e.g. from GUI) possible with JavaShellToolBuilder
  • 64f0f68958a74d4ee34c4b738759fc2278fa8b47: 8267464: Circular-dependency resilient inline headers
  • ... and 76 more: https://git.openjdk.java.net/jdk/compare/ebc9357d58957702abbd003d21082badc630876d...master

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 master branch, type /integrate in a new comment.

@openjdk openjdk bot added the ready Pull request is ready to be integrated label May 27, 2021
Copy link
Member

@TobiHartmann TobiHartmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me too.

What about adding a simple test for this? Maybe by using the new IR testing framework once it's there.

@iwanowww
Copy link
Contributor Author

iwanowww commented Jun 1, 2021

What about adding a simple test for this? Maybe by using the new IR testing framework once it's there.

Existing tests (and new CHA tests under review) trigger the assert in Parse::do_field_access() when the cast is absent:

    const TypeInstPtr *tjp = TypeInstPtr::make(TypePtr::NotNull, iter().get_declared_field_holder());
    assert(_gvn.type(obj)->higher_equal(tjp), "cast_up is no longer needed");

Once IR testing framework is integrated, it may be worth the effort to prepare a stand-alone simple test case.

@TobiHartmann
Copy link
Member

Okay, thanks for the details!

@iwanowww
Copy link
Contributor Author

iwanowww commented Jun 1, 2021

Thanks for the reviews, Roland and Tobias.

/integrate

@openjdk openjdk bot closed this Jun 1, 2021
@openjdk openjdk bot added integrated Pull request has been integrated and removed ready Pull request is ready to be integrated rfr Pull request is ready for review labels Jun 1, 2021
@openjdk
Copy link

openjdk bot commented Jun 1, 2021

@iwanowww Since your change was applied there have been 92 commits pushed to the master branch:

  • 1c7c0e1: 8267937: Wrong indentation in G1 gc+phases log for free cset subphases
  • ffd28c4: 8267947: CI: Preserve consistency between has_subklass() and is_subclass_of()
  • 229a6e2: 8267095: Miscellaneous cleanups in vm.runtime.defmeth tests
  • 6149b9a: 8267914: Remove DeferredObjectToKlass workaround
  • 4eb2168: 8267670: Update java.io, java.math, and java.text to use switch expressions
  • f5634fe: 8267979: C2: Fix verification code in SubTypeCheckNode::Ideal()
  • ae2f37f: 8267616: AArch64: Fix AES assertion messages in stubGenerator_aarch64.cpp
  • c06db45: 8267921: Remove redundant loop from sun.reflect.misc.ReflectUtil.privateCheckPackageAccess()
  • 382e7ec: 8246351: elements in headings are of incorrect size
  • 5df25dc088cfc3069e451b48c4f013d1d0491aa2: 8266807: Windows os_windows-gtest broken for UseLargePages
  • ... and 82 more: https://git.openjdk.java.net/jdk/compare/ebc9357d58957702abbd003d21082badc630876d...master

Your commit was automatically rebased without conflicts.

Pushed as commit 68f3b3acce0393aa9c91878f7ad848d4a41a2fe1.

:bulb: You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
hotspot-compiler hotspot-compiler-dev@openjdk.org integrated Pull request has been integrated
Development

Successfully merging this pull request may close these issues.

4 participants