JEP 303: Intrinsics for the LDC and INVOKEDYNAMIC Instructions

AuthorBrian Goetz
OwnerVicente Arturo Romero Zaldivar
TypeFeature
ScopeSE
StatusCandidate
Componenttools / javac
Discussionvalhalla dash dev at openjdk dot java dot net
EffortM
DurationM
DependsJEP 334: JVM Constants API
Relates toJEP 309: Dynamic Class-File Constants
Created2017/04/07 14:01
Updated2018/09/11 15:05
Issue8178320

Summary

Expose intrinsified library support allowing Java developers to write Java source code that reliably translates into INVOKEDYNAMIC invocations, so they can directly access functionality implemented via invokedynamic bootstraps. Expose the ability to lazily initialize create runtime entities expressible as JVM constants (like Class and MethodHandle) without compromising performance. Enhance constant propagation to include descriptor types for relevant constant pool entry types, such as Class, MethodType, and MethodHandle.

Non-Goals

It is not a goal to provide a mechanism to represent all possible bytecodes, provide fine-grained control over code generation, or provide a translation specification from Java source code to Java classfiles. It is not a goal to extend the treatment of the ConstantValue attribute to support greater constant inlining across compilation units. It would be highly undesirable for this feature to introduce new language syntax.

Motivation

JSR-292 added the invokedynamic ("indy") bytecode and several new constant pool types for representing method types and method handles. Originally, these facilities were aimed at dynamically typed languages, but statically typed languages like Java have discovered ways to benefit from them as well (both lambda conversion and string concatenation in javac are translated using indy, and wider use is anticipated.) As more runtime functionality is expressed in terms of indy bootstraps, the inability to access this functionality from Java source code (including for purposes of testing these bootstraps) increasingly becomes an impediment. (This problem will get even worse with the introduction of "constant dynamic" (condy), the subject of a separate JEP.)

Code that uses the new constant pool forms -- MethodType and MethodHandle -- typically initialized them statically, to avoid the cost of lazy initialization, but the cost of this is extra work (including loading of classes) at class initialization time. The JVM already provides a mechanism to efficiently lazily initialize and intern shared constants -- the constant pool. Providing a means to nominally describe constants, and load them via LDC, means that Java developers can leverage this mechanism directly.

Further, a nominal form for complex constant pool types is needed anyway for bytecode APIs and compiler plugin APIs, which often have to reinvent them anew each time, such as ASMs Handle class. (And again, this problem will get worse with the introduction of "constant dynamic.")

Description

For every object that can be described via a constant pool entry, we want a companion object that describes that entry in terms only of other constants (as the constant pool does), where names of classes are interpreted relative to the class loader of the class in which they are resolved. (This means that one can create a description for a class that is not loaded, and that creating a descriptor does not trigger any class loading.) Some constant pool forms (such as Integer or String) can act as their own descriptor; for more complex constants (Class, MethodType) an explicit descriptor is needed.

public interface Constable<T> {
    T resolveConstant(MethodHandles.Lookup lookup) throws ReflectiveOperationException;
}

public static final class ClassConstant implements Constable<Class> {
    private final String descriptor;

    private ClassConstant(String descriptor) { ... }

    public static ClassConstant of(String descriptor) {
        return new ClassConstant(descriptor);
    }

    public String descriptorString() {
        return descriptor;
    }

    public Class resolveConstant(MethodHandles.Lookup lookup) throws ReflectiveOperationException { ... }
}

Creating a ClassConstant merely validates the structure of the descriptor and stores it; no class loading takes place. More complex constant descriptors, such as MethodTypeConstant, use ClassConstant to describe its parameter and return types, and MethodHandleConstant uses ClassConstant and MethodTypeConstant, so that complex descriptors are "nominal all the way down."

Constant propagation

We enhance the set of variables and expressions that are considered to be constant expressions (CE), and for these constant expressions, the compiler tracks and propagates their values during compilation, and ultimately can use these propagated constants when they are used as arguments to intrinsified methods. We do not perform any new constant folding -- we merely track the value of any expressions that are CE according to the following rules, and if a CE is used as an argument to an intrinsic method, the constant value is used at the point of intrinsification.

LDC intrinsification

There will be some method corresponding to the LDC bytecode:

class Intrinsics {
    public static<T> T ldc(Constable<T> constant) { ... }
}

which the compiler will intrinsify, meaning that it will replace the invocation of the method with an actual LDC bytecode, with an operand corresponding to the constant described by constant. It will be a compile-time error if the Constable passed is not a CE (and the compiler can issue compile-time warnings if this constant or any of the constants indirectly referenced by it describe a class or member that is not present on the compile-time class path.) These intrinsic methods would most likely not be reflectively invocable.

Invokedynamic descriptors

An invokedynamic bootstrap is described by a structure similar to XxxConstant, for which the compiler also provide CE propagation:

public static final class BootstrapSpecifier {
    final MethodHandleConstant bsm;
    final String invocationName;
    final MethodTypeConstant invocationDesc;
    final Constable<?>[] bsmArgs;

    private BootstrapSpecifier(MethodHandleConstant bsm, String invocationName, MethodTypeConstant invocationDesc, Constable<?>... bsmArgs) {
        this.bsm = bsm;
        this.invocationName = invocationName;
        this.invocationDesc = invocationDesc;
        this.bsmArgs = bsmArgs;
    }

    public static BootstrapSpecifier of(MethodHandleConstant bsm, String invocationName, MethodTypeConstant invocationDesc, Constable<?>... bsmArgs) {
        return new BootstrapSpecifier(bsm, invocationName, invocationDesc, bsmArgs);
    }
}

Invokedynamic intrinsification

Corresponding to ldc(), there is an intrinsic for invokedynamic:

class Intrinsics {
    @PolymorphicSignature
    public static Object invokedynamic(BootstrapSpecifier indy, Object... args) { return null; }
}

Again, the indy argument must be CE, or it is a compile-time error. The compiler intrinsifies calls to invokedynamic() to a real invokedynamic instruction, described by a BootstrapMethods entry corresponding to the BootstrapSpecifier. The invocation descriptor is copied from that of the BootstrapSpecifier, and arguments or return values are typed-checked and adapted against that signature.

Alternatives

JDK 7 explored direct syntactic support for indy, which was rejected because it added language complexity for a use case only needed by an extreme minority of Java developers.

We also considered deterministic constant folding, where initialization of Class and MethodHandle objects from constant inputs could be intrinsified, propagated, and folded by the compiler, but rejected this approach because the timing of side-effects was insufficiently transparent.

Dependencies

This feature interacts with "constant dynamic"; when that is available, additional support for dynamic constants will be required.

This feature would likely be useful to Isolated Methods (JDK-8158765).