JEP 303: Intrinsics for the LDC and INVOKEDYNAMIC Instructions
Author | Brian Goetz |
Owner | Vicente Arturo Romero Zaldivar |
Type | Feature |
Scope | SE |
Status | Candidate |
Component | tools / javac |
Discussion | valhalla dash dev at openjdk dot java dot net |
Effort | M |
Duration | M |
Depends | JEP 334: JVM Constants API |
Relates to | JEP 309: Dynamic Class-File Constants |
Created | 2017/04/07 14:01 |
Updated | 2018/09/11 15:05 |
Issue | 8178320 |
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 invokeClassConstant.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 aMethodHandleConstant
via a factory with with only constant inputs, and then call itstype()
method, the result is a CEMethodTypeConstant
.
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).