JDK-8178320 : JEP 303: Intrinsics for the LDC and INVOKEDYNAMIC Instructions
  • Type: JEP
  • Component: tools
  • Sub-Component: javac
  • Priority: P3
  • Status: Candidate
  • Resolution: Unresolved
  • Submitted: 2017-04-07
  • Updated: 2018-09-11
Related Reports
Blocks :  
Relates :  
Relates :  
Description
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.

  - int, long, float, double, and String literals are CE.
  - static final fields whose initializer is a CE are CE.
  - effectively final local variables whose initializer is a CE are CE. 
  - For a suitable set of static factory methods in `XxxConstant`, when all arguments are CE, the result is CE. So for example, if we invoke `ClassConstant.of()` with the CE argument `"Lcom/Foo;", the result is CE.  
  - For a suitable set of instance methods in `XxxConstant`, when all arguments are CE and the receiver is CE, then the result is CE. So for example, if we create a `MethodHandleConstant` via a factory with with only constant inputs, and then call its `type()` method, the result is a CE `MethodTypeConstant`.  

### 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).
Comments
There should be some provision for injecting "Foo.class" constants into the scheme, even though they have side effects. Use of descriptor syntax ("Lpkg/Foo;", "(Lpkg/Foo;)V") should be optional, not required. (See below about inferring method types.) The API for indy needs to follow the JVMS more closely. Bootstrap specifier is a kind of constant and does not contain name or type, just BSM and args. There is no need for reifying a CONSTANT_InvokeDynamic, which is just a tuple of (BSS, (name, type)) with no identity. (Note that it is normal for one BSS to handle many combinations of name and type. This is another reason to factor the API as suggested. This point is also true for condy. BSSs are where the main action is, but names and types contribute their little bits.) Translation of @SP methods always pushes an exact concrete MethodType from source code through backend, so the only thing needed to complete an indy instruction is BSS, name, and @SP argument package. (There is no upside to allowing the @SP argument types to disagree with a separate user-supplied method type constant.) Because of the peculiarities of @SP methods, it is best not to mix their arguments with additional arguments, especially metadata arguments (like BSS above or name below). These points could be emphasized by making the API fluent: First create a "package" of indy metadata (BSS + name) and then separately apply it to a pure set of dynamic arguments. Using the name "invokeExact" instead of "invokedynamic" would make it clearer how the @SP aspect works, since that is also how MH.invokeExact works. Therefore the API should omit the type and factor the naming (at least optionally) into a separate chained method. (Also, it's simpler if the @SP methods take NO metadata arguments, just dynamic parameters.) See below. class BootstrapSpecifier { ... /** Create a bootstrap specifier from a method and optional series of static arguments. */ public static BootstrapSpecifier of(MethodHandleConstant bsm, Constable<?>... bsmArgs) ... /** Prepare to make an invokedynamic call by choosing a method name. */ public //non-static class WithName { public non-static @SignaturePolymorphic native Object invokeExact(Object... args); } // OR, problematically: public //non-static @SignaturePolymorphic native Object invokeExact(String name, Object... args); // previous method is problematic because of mix of constant and non-constant arguments! } // all-in-one utility method: class InvokeDynamic { public WithName of(String name, MethodHandleConstant bsm, Constable<?>... bsmArgs) { return BootstrapSpecifier.of(bsm, bsmArgs).withName(name); } }
06-05-2017