-
Notifications
You must be signed in to change notification settings - Fork 6.2k
8160821: VarHandle accesses are penalized when argument conversion is required #28585
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
base: master
Are you sure you want to change the base?
Conversation
|
👋 Welcome back liach! A progress list of the required criteria for merging this PR into |
|
❗ This change is not yet ready to be integrated. |
|
@liach 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. |
|
java.lang.invoke tests all pass. New benchmark results for Submitting internal CI runs. |
Webrevs
|
|
/cc hotspot-compiler |
|
@liach |
|
Since I removed the return type dropping VarHandle bypass, TestGetAndAdd became affected because it can no longer access the x86 assembly. Updated the Java calling convention to fix it. |
| } | ||
|
|
||
| @ForceInline | ||
| MethodHandle adaptedMethodHandle(VarHandle vh) { |
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 you elaborate, please, how this method is intended to behave?
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.
When this is compiled, constant will become either 1 for constant VH and 2 for non-constant VH. So for constant VH, this becomes a stable read. For a non-constant VH, this becomes getMethodHandle(mode).asType(...), equivalent to before.
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.
What's the purpose of constant == MethodHandleImpl.CONSTANT_YES and constant != MethodHandleImpl.CONSTANT_NO checks then?
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.
Indeed, I should move the adaptedMh read into constant == MethodHandleImpl.CONSTANT_YES block.
constant != MethodHandleImpl.CONSTANT_NO prevents capturing any further if the VH is known non-constant; we keep this branch in constant case in case the adapted MH is not ready when we know the VH is constant.
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 still have a hard time reasoning about state transitions of the cache.
-
Why do you limit successful cache read (
cache != null) to constantvhcase (constant == MethodHandleImpl.CONSTANT_YES)? -
Why do you avoid cache update in non-constant case (
constant != MethodHandleImpl.CONSTANT_NO)? What happens if it runs compiledadaptedMethodHandlemethod?
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 am assuming that if C2 determines this vh is not a constant, we can drop it. Is that a right way to move along, or could C2 transition from "not a constant" to "is a constant" during the phases?
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.
Sorry, I still don't understand how it is intended to work. Why does MethodHandleImpl.isCompileConstant(vh) == true imply that the cached value is compatible with the constant vh?
// Keep capturing - vh may suddenly get promoted to a constant by C2
Capturing happens outside compiler thread. It is not affected by C2 (except when it completely prunes the whole block).
So, either any captured adaptation is valid/compatible or there's a concurrency issue when C2 kicks in and there's a concurrent cache update happening with incompatible version.
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.
any captured adaptation is valid/compatible
Yes, if vh is a constant, any captured adaptation from vh.getMethodHandle(mode).asType(symbolicMethodTypeInvoker) is valid/compatible.
For thread safety, MethodHandle supports safe publication, so I think we are fine publishing this way.
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.
Looking at this, I'm not sure we can assume that we only see one mode and type when the VH is constant. There seems to be a lot of non-local reasoning involved.
For example, you could have a var handle invoker created with MethodHandless::varHandleInvoker, which is cached, so the AccessDescriptor can be shared among many different use sites. For an individual use-site, the receiver VH may well be a constant, but that doesn't mean that the cache isn't polluted by the var handle from another use site, as far as I can tell.
The thread safety issue comes from a C2 thread racing to read the lastAdaption cache vs another Java thread writing to the cache. AFAICS, this race is still possible even when vh is a compile time constant.
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 even without using an invoker, you could end up in a similar situation if you have something like:
static Object m(VarHandle vh) {
return vh.get();
}
Which is called by several different threads. At some point this method may be inlined into one of its callees, where vh then becomes a constant. But at the same time, other threads are still writing to the cache.
src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java
Outdated
Show resolved
Hide resolved
| } | ||
|
|
||
| @ForceInline | ||
| MethodHandle adaptedMethodHandle(VarHandle vh) { |
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 still find it confusing, especially tri-state logic part.
For background, isCompileConstant was introduced as part of LF sharing effort to get rid of Java-level profiling in optimized code. The pattern is was designed for was:
if (isCompileConstant(...)) {
return ...;
} else {
... // do some extra work (either in interpreter, C1, or not-fully-optimized version in C2)
}
In this patch, you don't follow that pattern and aadd new state (CONSTANT_PENDING) to distinguish interpreter/C1 from C2. What's the motivation? Why do you want to avoid cache updates coming from C2-generated code?
|
After consulting with @iwanowww, I realized the non-constant status cannot be determined, that the C2 compiled method can even transition from 0 to 1, so I am simplifying this code to only handle the constant case. It seems the getAndAdd IR test no longer fails with this change, and I removed a lot of other redundant changes. I updated the VarHandleExact benchmark added by @JornVernee, and added a case of dropping return values by changing access mode to |
| } | ||
|
|
||
| @Benchmark | ||
| public void generic_returnDroppingInvocation() { |
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.
What about "all-generic" case ( { generic.getAndAdd(data, 42); })?
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 can change the generic_genericInvocation to an all-generic case.
Since access descriptor is created for each VH operation site, we can optimistically cache the adapted method handle in a site if the site operates on a constant VH. Used a C2 IR test to verify such a setup through an inexact VarHandle invocation can be constant folded through (previously, it was blocked by
asType)Progress
Issue
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/28585/head:pull/28585$ git checkout pull/28585Update a local copy of the PR:
$ git checkout pull/28585$ git pull https://git.openjdk.org/jdk.git pull/28585/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 28585View PR using the GUI difftool:
$ git pr show -t 28585Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/28585.diff
Using Webrev
Link to Webrev Comment