JEP 274: Enhanced Method Handles

OwnerMichael Haupt
TypeFeature
ScopeSE
StatusClosed / Delivered
Release9
Componentcore-libs / java.lang.invoke
Discussionmlvm dash dev at openjdk dot java dot net
Reviewed byAlex Buckley, Paul Sandoz, Vladimir Ivanov
Endorsed byJohn Rose
Created2015/07/01 06:43
Updated2017/05/17 01:03
Issue8130227

Summary

Enhance the MethodHandle, MethodHandles, and MethodHandles.Lookup classes of the java.lang.invoke package to ease common use cases and enable better compiler optimizations by means of new MethodHandle combinators and lookup refinement.

Goals

Non-Goals

Motivation

In a thread on the mlvm-dev mailing list (part 1, part 2) developers have discussed possible extensions to the MethodHandle, MethodHandles, and MethodHandles.Lookup classes in the java.lang.invoke package to make the realization of common use cases easier, and also to allow for use cases that are deemed important but are currently not supported.

The extensions proposed below not only allow for more concise usage of the MethodHandle API, but they also reduce the amount of MethodHandle instances created in some cases. This, in turn, will facilitate better optimizations on behalf of the VM's compiler.

Combinators for More Statements

Loops. The MethodHandles class provides no abstractions for loop construction from MethodHandle instances. There should be a means for constructing loops from MethodHandles representing the loop's body, as well as initialization and condition, or count.

Try/finally blocks. MethodHandles also provides no abstraction for try/finally blocks. A method to construct such blocks from method handles representing the try and finally parts should be provided.

Better Argument Handling

Argument spreading. With MethodHandle.asSpreader(Class<?> arrayType, int arrayLength), there exists an operation to create a method handle that will spread the contents of a trailing array argument to a number of arguments. An additional asSpreader method should be provided, allowing to expand a number of arguments contained in an array anywhere in a method signature to a number of distinct arguments.

Argument collection. The method MethodHandle.asCollector(Class<?> arrayType, int arrayLength) produces a handle that collects the trailing arrayLength arguments into an array. There is no means for achieving the same for a number of arguments elsewhere in a method signature. There should be an additional asCollector method that supports this.

Argument folding. The folding combinator, foldArguments(MethodHandle target, MethodHandle combinator), does not allow to control the position in the argument list at which folding should start. A position argument should be added; the number of arguments to fold is implicitly given as the number of arguments the combinator accepts.

More Lookup Functions

Non-abstract methods in interfaces. Currently, a use case such as this one will fail at run-time at the indicated position:

interface I1 {
    default void m() { System.err.println("I1.m"); }
}

interface I2 {
    default void m() { System.err.println("I2.m"); }
}

class C implements I1, I2 {
    public void m() { I2.super.m(); System.err.println("C.m"); }
}

public class IfcSuper {
    public static void main(String[] args) throws Throwable {
        C c = new C();
        MethodHandles.Lookup l = MethodHandles.lookup();
        MethodType t = MethodType.methodType(void.class);
        // This lookup will fail with an IllegalAccessException.
        MethodHandle di1m = l.findSpecial(I1.class, "m", t, C.class);
        ci1m.invoke(c);
    }
}

It should, however, be possible to construct MethodHandles that bind to non-abstract methods in interfaces.

Class lookup. Finally, the lookup API should allow for looking up classes from different contexts, which is currently not possible. In the MethodHandles area, all required access checks are done at lookup time (as opposed to run-time, as is the case with reflection). Classes are passed in terms of their .class instance. To facilitate lookups with a certain control over the context, e.g., across module boundaries, there should be a lookup method that delivers a Class instance with the right restrictions for further use in MethodHandle combinators.

Description

Combinators for Loops

Most Generic Loop Abstraction

The core abstractions for loops include an initialization of the loop, a predicate to check, and a body to evaluate. The most generic MethodHandle combinator for creating a loop, to be added to MethodHandles, is as follows:

MethodHandle loop(MethodHandle[]... clauses)

Constructs a method handle representing a loop with several loop variables that are updated and checked upon each iteration. Upon termination of the loop due to one of the predicates, a corresponding finalizer is run and delivers the loop's result, which is the return value of the resulting handle.

Intuitively, every loop is formed by one or more "clauses", each specifying a local iteration value and/or a loop exit. Each iteration of the loop executes each clause in order. A clause can optionally update its iteration variable; it can also optionally perform a test and conditional loop exit. In order to express this logic in terms of method handles, each clause will determine four actions:

Some of these clause parts may be omitted according to certain rules, and useful default behavior is provided in this case. See below for a detailed description.

Each clause function, with the exception of clause initializers, is able to observe the entire loop state, because it will be passed all current iteration variable values, as well as all incoming loop parameters. Most clause functions will not need all of this information, but they will be formally connected as if by dropArguments.

Given a set of clauses, there is a number of checks and adjustments performed to connect all the parts of the loop. They are spelled out in detail in the steps below. In these steps, every occurrence of the word "must" corresponds to a place where IllegalArgumentException may be thrown if the required constraint is not met by the inputs to the loop combinator. The term "effectively identical", applied to parameter type lists, means that they must be identical, or else one list must be a proper prefix of the other.

Step 0: Determine clause structure.

Step 1A: Determine iteration variables.

Step 1B: Determine loop parameters.

Step 1C: Determine loop return type.

Step 1D: Check other types.

(Implementation Note: Steps 1A, 1B, 1C, 1D are logically independent of each other, and may be performed in any order.)

Step 2: Determine parameter lists.

Step 3: Fill in omitted functions.

Step 4: Fill in missing parameter types.

Final observations.

Loop execution.

The semantics of a MethodHandle l returned from loop are as follows:

l(arg*) =>
{
    let v* = init*(arg*);
    for (;;) {
        for ((v, s, p, f) in (v*, step*, pred*, fini*)) {
            v = s(v*, arg*);
            if (!p(v*, arg*)) {
                return f(v*, arg*);
            }
        }
    }
}

Based on this most generic abstraction of loops, several convenient combinators should be added to MethodHandles. They are discussed in the following.

Simple while and do-while Loops

These combinators will be added to MethodHandles:

MethodHandle whileLoop(MethodHandle init, MethodHandle pred, MethodHandle body)

MethodHandle doWhileLoop(MethodHandle init, MethodHandle body, MethodHandle pred)

The semantics of invoking the MethodHandle object wl returned from whileLoop are as follows:

wl(arg*) =>
{
    let r = init(arg*);
    while (pred(r, arg*)) { r = body(r, arg*); }
    return r;
}

For a MethodHandle dwl returned from doWhileLoop, the semantics are as follows:

dwl(arg*) =>
{
    let r = init(arg*);
    do { r = body(r, arg*); } while (pred(r, arg*));
    return r;
}

This scheme imposes some restrictions on the signatures that the three constituent MethodHandles can have:

  1. The return type of the initializer init, is also the return type of the body body and of the entire loop, as well as the type of the first argument of the predicate pred and the body body.

  2. The return type of the predicate pred must be boolean.

Counting Loops

For convenience, the following loop combinators will also be provided:

In these two cases, the type of the first argument of body must be int, and the return types of init and body as well as the second argument of body must be the same.

Iteration Over Data Structures

Furthermore, a loop combinator for iteration is helpful:

Remarks

More convenience loop combinators are conceivable.

While the semantics of continue can easily be emulated by returning from the body, it is an open question how the semantics of break can be emulated. This could be achieved by using a dedicated exception (e.g., LoopMethodHandle.BreakException).

Combinator for try/finally Blocks

To facilitate the construction of functionality with try/finally semantics from MethodHandles, the following new combinator will be introduced to MethodHandles:

MethodHandle tryFinally(MethodHandle target, MethodHandle cleanup)

The semantics of invoking a MethodHandle tf returned from tryFinally are as follows:

tf(arg*) =>
{
    Throwable t;
    Object r;
    try {
        r = target(arg*);
    } catch (Throwable x) {
        t = x;
        throw x;
    } finally {
        r = cleanup(t, r, arg*);
    }
    return r;
}

That is, the return type of the resulting MethodHandle will be that of the target handle. Both the target and the cleanup must have matching argument lists, with the extension for cleanup that it accepts one Throwable argument and the - possibly intermediate - result. In case an exception was thrown during the execution of target, this argument will hold that exception.

Combinators for Argument Handling

As additions to the existing API in MethodHandles, the following methods will be introduced:

These new combinators will be implemented using existing abstractions and API. If required, non-public API will be modified.

Lookups

The implementation of the method MethodHandles.Lookup.findSpecial(Class<?> refc, String name, MethodType type, Class<?> specialCaller) will be modified to allow for finding super-callable methods on interfaces. While this is not a change of the API as such, its documented behaviour changes significantly.

Also, the MethodHandles.Lookup class will be extended with the following two methods:

Risks and Assumptions

As this is a purely additive API extension, no code that existing clients of the MethodHandle API use will be negatively affected. The proposed extensions also do not rely on any other ongoing development.

Unit tests for all of the above API extensions will be provided.

Dependences

This JEP is related to JEP 193 (Variable Handles), and a certain amount of overlap is possible since VarHandles depend on the MethodHandle API. This will be addressed in collaboration with the owner of JEP 193.

The JBS issue on JSR 292 enhancements for maintenance releases can be considered a starting point for this JEP, which distills from that issue those points upon which agreement has been reached.