You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Next »

JSR 292 introduces a flexible invokedynamic instruction which is bound to a user-defined graph of method handles.

Invokedynamic

As defined in the JVMS, invokedynamic consists of a name, a method
type signature, and bootstrap specifier.

The caller-visible behavior of the instruction is defined only by the
type signatures, which determines exactly which types of arguments and
return values are shuffled through the stack.

The actual behavior of the instruction is determined when the instruction is first executed.
As with the other invoke instructions, the LinkResolver modules handles
setup operations performed on first execution.

For invokedynamic, the bootstrap specifier is resolved into a method handle
and zero or more extra constant arguments. (These are all drawn from the constant pool.)
The name and signature are pushed on the stack, along with the extra arguments
and a MethodHandles.Lookup parameter to reify the requesting class, and
the bootstrap method handle is invoked.

(This appeal to a user-specified method may seem startling, but to the JVM
is it not much more complex than the ClassLoader operations which must
be performed to locate a new class's bytecodes.)

When the bootstrap method returns, it presents a CallSite object to the JVM runtime.
This call site contains a method handle, which (in the end) determines the exact
behavior of the linked invokedynamic instruction.
Since method handle can do just about anything, the invokedynamic instruction,
after linking, is a fully general virtual machine instruction.

(The alert reader will wonder why the bootstrap method doesn't just return a method
handle. The answer is that some call sites can, potentially, be bound over time to a
succession of different method handles. This gives Java programmers the a low-level
code-patching technique similar to that used by the JVM to manage monomorphic and
polymorphic virtual call sites.)

Invokedynamic implementation

Because each invokedynamic instruction links (in general) to a different call site,
the constant pool cache must contain a separate entry for each invokedynamic instruction.
(Other invoke instructions can share CP cache entries, if they use the same
symbolic reference in the constant pool.)

A CP cache entry ("CPCE"), when resolved, has one or two words of metadata and/or
offset information.

For invokedynamic, a resolved CPCE contains a Method* pointer to a concrete
adapter method providing the exact behavior of the call.
There is also a reference parameter associated with the call site called the
appendix, which is stored in the resolved_references array for the CPCE.

The method is called an adapter because (generally speaking) it shuffles arguments,
extracts a target method handle from the call site, and invokes the method handle.

The extra reference parameter is called the appendix because it is appended
to the argument list when the invokedynamic instruction is executed.

Typically the appendix is the CallSite reference produced by the bootstrap
method, but the JVM does not care about this. As long as the adapter method in
the CPCE knows what to do with the appendix stored with the CPCE, all is well.

As a corner case, if the appendix value is null, it is not pushed at all, and the
adapter method must not expect the extra argument. The adapter method in this case
could be a permanently linked reference to a static method with a signature consistent
with the invokedynamic instruction. This would in effect turn the
invokedynamic into a simple invokestatic.
Many other such strength reduction optimizations are possible.

Linkage handshake

The adapter Method* pointer for an invokedynamic CPCE is not chosen by the JVM,
but rather by trusted Java code. The same is true of the appendix reference.

In fact, the JVM does not directly invoke the bootstrap method.
Instead, the JVM calls a HotSpot-specific method MethodHandleNatives.linkCallSite
with the resolved bootstrap specifier information.
(Other JVM implementations do not necessarily use this handshake.)
The linkCallSite method performs the steps demanded by the JSR 292
bootstrap rules, and returns two coordinated values, the adapter method
and its appendix.

Since Java cannot represent a raw Method* pointer, the method is wrapped
in a private Java type called MemberName, akin to a Java mirror for a Klass*.
The appendix is a simple Object reference (or null).
After a little unpacking, these are plugged into the CPCE.

Adapter method for invokedynamic

In general, the adapter method is a specially generated method created on the
fly by the JSR 292 runtime. It is generated from a lambda form which
computes the current call site target and invokes that target.
The lambda form takes leading parameters corresponding to the arguments stacked
for the invokedynamic instruction, i.e., those required by the method
signature of the instruction. The lambda form also takes a trailing
appendix argument (if relevant). It then performs whatever actions
required by the bootstrap method and its call site.

Here is an example of an adapter method, taken from an actual application:

LambdaForm(a0:D,a1:L,a2:L)=>{
    t3:L=Invokers.getCallSiteTarget(a2:L);
    t4:L=MethodHandle.invokeBasic(t3:L,a0:D,a1:L);
    t4:L}

Here the invokedynamic instruction takes two arguments, a double a0 and a reference
a1, and returns a reference t4. The appendix trails along at the end, in a2.

The body of the lambda form extracts a method handle target from the appendix using
the subroutine Invokers.getCallSiteTarget. The method handle is bound to t3,
and then immediately invoked on the two leading arguments, a0 adnd a1.

As may be seen by inspecting the Java code, getCallSiteTarget expects to get a non-null
CallSite argument. If this were to fail, it would mean that the trusted Java code has
a bug in it, since the trusted code is responsible for returning to the JVM a consistent
pair of adapter and appendix.

The special non-public routine MethodHandle.invokeBasic is an unchecked version of
MethodHandle.invokeExact. It differs in two ways from invokeExact. First,
it does not check that its callee has a type which (exactly) matches the types at
the call site. (For better or worse, it will never throw WrongMethodTypeException.)

Second, it allows loose typing of its arguments and return value, according to the
basic type scheme used in the JSR 292 runtime.
Under basic typing rules, all reference types are represented by java.lang.Object.
If a reference of a narrower type is required somewhere,
an explicit checkcast must be issued before the reference is used.
Also, in the basic type system, all 32-bit types are erased to simple int.
Thus if a byte value is required somewhere, it must be masked down from a full int.
Normally, these extra conversions disappear in the optimizer.

Method handle invocation

Internally to HotSpot (in rewriter.cpp) method handle invocations are rewritten
to use a special instruction called invokehandle. This instruction in many ways is parallel
to invokedynamic. It resolves to an adapter method pointer and an appendix.
The appendix (if not null) is pushed after the explicit arguments to invoke or invokeExact.

The resolution is done via a call to trusted Java code, to a method called
MethodHandleNatives.linkMethod. As with linkCallSite, the JVM passes
all resolved constant pool references to linkMethod, and receives back
a coordinated pair of values, a MemberName and an Object.
After unpacking, these are plugged into the CPCE as the adapter and appendix.

The same degrees of freedom apply to invokehandle CPCE entries as apply
to invokedynamic CPCE entries, and similar optimization opportunities apply.

There is one major difference from invokedynamic: Many invokehandle
instructions can share a single CPCE entry, if they all have the same signature
and method name ("invokeExact" vs. "invoke").

Adapter method for invokeExact

The standard semantics of an invokevirtual of MethodHandle.invokeExact are simple.
The signature for the call site (which may contain any mix of any references and primitive types)
is resolved (once, at link time) into a MethodType.
Every time the method handle is invoked, the method type is checked against the type of the
method handle being invoked.
(Since the method handle is a computed value, it can of course be different every time,
and the type does not necessarily match.)
If the two types differ in any way, a WrongMethodTypeException is thrown.
Otherwise, the method handle is invoked on the given types,
and returns a value of the type expected by the caller.

An adapter method for invokeExact is correspondingly simple.
It merely performs a method type check and then calls invokeBasic.
The appendix is a reference to the resolved MethodType,
required to make the type check.

Here is an example:

LambdaForm(a0:L,a1:L,a2:L)=>{
    t3:V=Invokers.checkExactType(a0:L,a2:L);
    t4:L=MethodHandle.invokeBasic(a0:L,a1:L);
    t4:L}

The leading argument a0 is a method handle.
The trailing argument a2 is the method type appendix, computed when the call site is resolved.
The middle argument a1 is the sole argument to the method handle.

First the subroutine Invokers.checkExactType is called on
the method handle and the appendix, extracting the type
from the method handle and comparing it with the static call site type.
If no exception is thrown, control returns to the adapter method.
No value is returned, and the name t3 has a pseudo-type of void.

(Since the appendix represents the static call site type, and the
type of the method handle is the dynamically acceptable type,
this could be viewed as a simple dynamic type check.)

Next, invokeBasic is used to jump into the method handle
(which is now known to be completely safe for this call).
The result comes back, briefly named t4, and is returned to the caller.

Adapter method for generic invoke

Method handles also support a more complex invocation mode, which can perform
type conversions on individual arguments and return values, and even group
arguments into varargs arrays.

As with invokeExact the resolution of such a call site is carried out by
a call to MethodHandleNatives.linkMethod.
In this case, the trusted Java code must return a more flexible and complex
adapter method.

Here is an example:

LambdaForm(a0:L,a1:L,a2:I,a3:L)=>{
    t4:L=Invokers.checkGenericType(a0:L,a3:L);
    t5:I=MethodHandle.invokeBasic(t4:L,a3:L,a0:L,a1:L,a2:I);
    t5:I}

As before, a0 is the method handle and the trailing a3 is a method type.
Here, there are two regular arguments, a reference and an int.

As before, the first job is to check the type, and this is done by
Invokers.checkGenericType.
Unlike the simpler check, this routine returns a value.
(The routine can also throw WrongMethodTypeException if necessary.)

The value returned from checkGenericType is in fact a method handle t4.
This method handle is immediately invoked on the original arguments,
plus the original method handle a0,
plus the desired call site type a3 (not in that order).

Depending on the match or mismatch between the type of a0 and the call
site type a3, the actual behavior of t4 could be very simple
(basically another invokeBasic) or very complex (an arity change
with type conversions. That's up to the runtime.

  • No labels