8163369: Enable generating DMH classes at link time
Reviewed-by: vlivanov, shade
This commit is contained in:
parent
3be823e0db
commit
cdef6ef876
@ -27,6 +27,7 @@ package java.lang.invoke;
|
|||||||
|
|
||||||
import jdk.internal.misc.Unsafe;
|
import jdk.internal.misc.Unsafe;
|
||||||
import jdk.internal.vm.annotation.ForceInline;
|
import jdk.internal.vm.annotation.ForceInline;
|
||||||
|
import jdk.internal.vm.annotation.Stable;
|
||||||
import sun.invoke.util.ValueConversions;
|
import sun.invoke.util.ValueConversions;
|
||||||
import sun.invoke.util.VerifyAccess;
|
import sun.invoke.util.VerifyAccess;
|
||||||
import sun.invoke.util.VerifyType;
|
import sun.invoke.util.VerifyType;
|
||||||
@ -190,14 +191,15 @@ class DirectMethodHandle extends MethodHandle {
|
|||||||
boolean doesAlloc = (which == LF_NEWINVSPECIAL);
|
boolean doesAlloc = (which == LF_NEWINVSPECIAL);
|
||||||
String linkerName, lambdaName;
|
String linkerName, lambdaName;
|
||||||
switch (which) {
|
switch (which) {
|
||||||
case LF_INVVIRTUAL: linkerName = "linkToVirtual"; lambdaName = "DMH.invokeVirtual"; break;
|
case LF_INVVIRTUAL: linkerName = "linkToVirtual"; lambdaName = "invokeVirtual"; break;
|
||||||
case LF_INVSTATIC: linkerName = "linkToStatic"; lambdaName = "DMH.invokeStatic"; break;
|
case LF_INVSTATIC: linkerName = "linkToStatic"; lambdaName = "invokeStatic"; break;
|
||||||
case LF_INVSTATIC_INIT:linkerName = "linkToStatic"; lambdaName = "DMH.invokeStaticInit"; break;
|
case LF_INVSTATIC_INIT:linkerName = "linkToStatic"; lambdaName = "invokeStaticInit"; break;
|
||||||
case LF_INVSPECIAL: linkerName = "linkToSpecial"; lambdaName = "DMH.invokeSpecial"; break;
|
case LF_INVSPECIAL: linkerName = "linkToSpecial"; lambdaName = "invokeSpecial"; break;
|
||||||
case LF_INVINTERFACE: linkerName = "linkToInterface"; lambdaName = "DMH.invokeInterface"; break;
|
case LF_INVINTERFACE: linkerName = "linkToInterface"; lambdaName = "invokeInterface"; break;
|
||||||
case LF_NEWINVSPECIAL: linkerName = "linkToSpecial"; lambdaName = "DMH.newInvokeSpecial"; break;
|
case LF_NEWINVSPECIAL: linkerName = "linkToSpecial"; lambdaName = "newInvokeSpecial"; break;
|
||||||
default: throw new InternalError("which="+which);
|
default: throw new InternalError("which="+which);
|
||||||
}
|
}
|
||||||
|
|
||||||
MethodType mtypeWithArg = mtype.appendParameterTypes(MemberName.class);
|
MethodType mtypeWithArg = mtype.appendParameterTypes(MemberName.class);
|
||||||
if (doesAlloc)
|
if (doesAlloc)
|
||||||
mtypeWithArg = mtypeWithArg
|
mtypeWithArg = mtypeWithArg
|
||||||
@ -240,11 +242,26 @@ class DirectMethodHandle extends MethodHandle {
|
|||||||
names[LINKER_CALL] = new Name(linker, outArgs);
|
names[LINKER_CALL] = new Name(linker, outArgs);
|
||||||
lambdaName += "_" + shortenSignature(basicTypeSignature(mtype));
|
lambdaName += "_" + shortenSignature(basicTypeSignature(mtype));
|
||||||
LambdaForm lform = new LambdaForm(lambdaName, ARG_LIMIT, names, result);
|
LambdaForm lform = new LambdaForm(lambdaName, ARG_LIMIT, names, result);
|
||||||
|
|
||||||
// This is a tricky bit of code. Don't send it through the LF interpreter.
|
// This is a tricky bit of code. Don't send it through the LF interpreter.
|
||||||
lform.compileToBytecode();
|
lform.compileToBytecode(Holder.class);
|
||||||
return lform;
|
return lform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NOTE: This method acts as an API hook for use by the
|
||||||
|
* GenerateJLIClassesPlugin to generate a class wrapping DirectMethodHandle
|
||||||
|
* methods for an array of method types.
|
||||||
|
*/
|
||||||
|
static byte[] generateDMHClassBytes(String className, MethodType[] methodTypes, int[] types) {
|
||||||
|
LambdaForm[] forms = new LambdaForm[methodTypes.length];
|
||||||
|
for (int i = 0; i < forms.length; i++) {
|
||||||
|
forms[i] = makePreparedLambdaForm(methodTypes[i], types[i]);
|
||||||
|
methodTypes[i] = forms[i].methodType();
|
||||||
|
}
|
||||||
|
return InvokerBytecodeGenerator.generateCodeBytesForMultiple(className, forms, methodTypes);
|
||||||
|
}
|
||||||
|
|
||||||
static Object findDirectMethodHandle(Name name) {
|
static Object findDirectMethodHandle(Name name) {
|
||||||
if (name.function == NF_internalMemberName ||
|
if (name.function == NF_internalMemberName ||
|
||||||
name.function == NF_internalMemberNameEnsureInit ||
|
name.function == NF_internalMemberNameEnsureInit ||
|
||||||
@ -487,7 +504,7 @@ class DirectMethodHandle extends MethodHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Caching machinery for field accessors:
|
// Caching machinery for field accessors:
|
||||||
private static byte
|
private static final byte
|
||||||
AF_GETFIELD = 0,
|
AF_GETFIELD = 0,
|
||||||
AF_PUTFIELD = 1,
|
AF_PUTFIELD = 1,
|
||||||
AF_GETSTATIC = 2,
|
AF_GETSTATIC = 2,
|
||||||
@ -497,7 +514,7 @@ class DirectMethodHandle extends MethodHandle {
|
|||||||
AF_LIMIT = 6;
|
AF_LIMIT = 6;
|
||||||
// Enumerate the different field kinds using Wrapper,
|
// Enumerate the different field kinds using Wrapper,
|
||||||
// with an extra case added for checked references.
|
// with an extra case added for checked references.
|
||||||
private static int
|
private static final int
|
||||||
FT_LAST_WRAPPER = Wrapper.values().length-1,
|
FT_LAST_WRAPPER = Wrapper.values().length-1,
|
||||||
FT_UNCHECKED_REF = Wrapper.OBJECT.ordinal(),
|
FT_UNCHECKED_REF = Wrapper.OBJECT.ordinal(),
|
||||||
FT_CHECKED_REF = FT_LAST_WRAPPER+1,
|
FT_CHECKED_REF = FT_LAST_WRAPPER+1,
|
||||||
@ -507,6 +524,7 @@ class DirectMethodHandle extends MethodHandle {
|
|||||||
+ (isVolatile ? FT_LIMIT : 0)
|
+ (isVolatile ? FT_LIMIT : 0)
|
||||||
+ ftypeKind);
|
+ ftypeKind);
|
||||||
}
|
}
|
||||||
|
@Stable
|
||||||
private static final LambdaForm[] ACCESSOR_FORMS
|
private static final LambdaForm[] ACCESSOR_FORMS
|
||||||
= new LambdaForm[afIndex(AF_LIMIT, false, 0)];
|
= new LambdaForm[afIndex(AF_LIMIT, false, 0)];
|
||||||
private static int ftypeKind(Class<?> ftype) {
|
private static int ftypeKind(Class<?> ftype) {
|
||||||
@ -549,10 +567,11 @@ class DirectMethodHandle extends MethodHandle {
|
|||||||
return lform;
|
return lform;
|
||||||
}
|
}
|
||||||
private static LambdaForm preparedFieldLambdaForm(byte formOp, boolean isVolatile, Class<?> ftype) {
|
private static LambdaForm preparedFieldLambdaForm(byte formOp, boolean isVolatile, Class<?> ftype) {
|
||||||
int afIndex = afIndex(formOp, isVolatile, ftypeKind(ftype));
|
int ftypeKind = ftypeKind(ftype);
|
||||||
|
int afIndex = afIndex(formOp, isVolatile, ftypeKind);
|
||||||
LambdaForm lform = ACCESSOR_FORMS[afIndex];
|
LambdaForm lform = ACCESSOR_FORMS[afIndex];
|
||||||
if (lform != null) return lform;
|
if (lform != null) return lform;
|
||||||
lform = makePreparedFieldLambdaForm(formOp, isVolatile, ftypeKind(ftype));
|
lform = makePreparedFieldLambdaForm(formOp, isVolatile, ftypeKind);
|
||||||
ACCESSOR_FORMS[afIndex] = lform; // don't bother with a CAS
|
ACCESSOR_FORMS[afIndex] = lform; // don't bother with a CAS
|
||||||
return lform;
|
return lform;
|
||||||
}
|
}
|
||||||
@ -682,4 +701,15 @@ class DirectMethodHandle extends MethodHandle {
|
|||||||
throw newInternalError(ex);
|
throw newInternalError(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
// The DMH class will contain pre-generated DirectMethodHandles resolved
|
||||||
|
// speculatively using MemberName.getFactory().resolveOrNull. However, that
|
||||||
|
// doesn't initialize the class, which subtly breaks inlining etc. By forcing
|
||||||
|
// initialization of the Holder class we avoid these issues.
|
||||||
|
UNSAFE.ensureClassInitialized(Holder.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Placeholder class for DirectMethodHandles generated ahead of time */
|
||||||
|
private final class Holder {}
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ class InvokerBytecodeGenerator {
|
|||||||
private static final String LLV_SIG = "(L" + OBJ + ";L" + OBJ + ";)V";
|
private static final String LLV_SIG = "(L" + OBJ + ";L" + OBJ + ";)V";
|
||||||
|
|
||||||
/** Name of its super class*/
|
/** Name of its super class*/
|
||||||
private static final String superName = OBJ;
|
private static final String INVOKER_SUPER_NAME = OBJ;
|
||||||
|
|
||||||
/** Name of new class */
|
/** Name of new class */
|
||||||
private final String className;
|
private final String className;
|
||||||
@ -296,12 +296,15 @@ class InvokerBytecodeGenerator {
|
|||||||
/**
|
/**
|
||||||
* Set up class file generation.
|
* Set up class file generation.
|
||||||
*/
|
*/
|
||||||
private void classFilePrologue() {
|
private ClassWriter classFilePrologue() {
|
||||||
final int NOT_ACC_PUBLIC = 0; // not ACC_PUBLIC
|
final int NOT_ACC_PUBLIC = 0; // not ACC_PUBLIC
|
||||||
cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
|
cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
|
||||||
cw.visit(Opcodes.V1_8, NOT_ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, className, null, superName, null);
|
cw.visit(Opcodes.V1_8, NOT_ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, className, null, INVOKER_SUPER_NAME, null);
|
||||||
cw.visitSource(sourceFile, null);
|
cw.visitSource(sourceFile, null);
|
||||||
|
return cw;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void methodPrologue() {
|
||||||
String invokerDesc = invokerType.toMethodDescriptorString();
|
String invokerDesc = invokerType.toMethodDescriptorString();
|
||||||
mv = cw.visitMethod(Opcodes.ACC_STATIC, invokerName, invokerDesc, null, null);
|
mv = cw.visitMethod(Opcodes.ACC_STATIC, invokerName, invokerDesc, null, null);
|
||||||
}
|
}
|
||||||
@ -309,7 +312,7 @@ class InvokerBytecodeGenerator {
|
|||||||
/**
|
/**
|
||||||
* Tear down class file generation.
|
* Tear down class file generation.
|
||||||
*/
|
*/
|
||||||
private void classFileEpilogue() {
|
private void methodEpilogue() {
|
||||||
mv.visitMaxs(0, 0);
|
mv.visitMaxs(0, 0);
|
||||||
mv.visitEnd();
|
mv.visitEnd();
|
||||||
}
|
}
|
||||||
@ -644,6 +647,44 @@ class InvokerBytecodeGenerator {
|
|||||||
*/
|
*/
|
||||||
private byte[] generateCustomizedCodeBytes() {
|
private byte[] generateCustomizedCodeBytes() {
|
||||||
classFilePrologue();
|
classFilePrologue();
|
||||||
|
addMethod();
|
||||||
|
bogusMethod(lambdaForm);
|
||||||
|
|
||||||
|
final byte[] classFile = toByteArray();
|
||||||
|
maybeDump(className, classFile);
|
||||||
|
return classFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NOTE: This is used from GenerateJLIClassesPlugin via
|
||||||
|
* DirectMethodHandle::generateDMHClassBytes.
|
||||||
|
*
|
||||||
|
* Generate customized code for a set of LambdaForms of specified types into
|
||||||
|
* a class with a specified name.
|
||||||
|
*/
|
||||||
|
static byte[] generateCodeBytesForMultiple(String className,
|
||||||
|
LambdaForm[] forms, MethodType[] types) {
|
||||||
|
assert(forms.length == types.length);
|
||||||
|
|
||||||
|
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
|
||||||
|
cw.visit(Opcodes.V1_8, Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER,
|
||||||
|
className, null, INVOKER_SUPER_NAME, null);
|
||||||
|
cw.visitSource(className.substring(className.lastIndexOf('/') + 1), null);
|
||||||
|
for (int i = 0; i < forms.length; i++) {
|
||||||
|
InvokerBytecodeGenerator g
|
||||||
|
= new InvokerBytecodeGenerator(className, forms[i], types[i]);
|
||||||
|
g.setClassWriter(cw);
|
||||||
|
g.addMethod();
|
||||||
|
}
|
||||||
|
return cw.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setClassWriter(ClassWriter cw) {
|
||||||
|
this.cw = cw;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMethod() {
|
||||||
|
methodPrologue();
|
||||||
|
|
||||||
// Suppress this method in backtraces displayed to the user.
|
// Suppress this method in backtraces displayed to the user.
|
||||||
mv.visitAnnotation(LF_HIDDEN_SIG, true);
|
mv.visitAnnotation(LF_HIDDEN_SIG, true);
|
||||||
@ -748,19 +789,19 @@ class InvokerBytecodeGenerator {
|
|||||||
// return statement
|
// return statement
|
||||||
emitReturn(onStack);
|
emitReturn(onStack);
|
||||||
|
|
||||||
classFileEpilogue();
|
methodEpilogue();
|
||||||
bogusMethod(lambdaForm);
|
}
|
||||||
|
|
||||||
final byte[] classFile;
|
/*
|
||||||
|
* @throws BytecodeGenerationException if something goes wrong when
|
||||||
|
* generating the byte code
|
||||||
|
*/
|
||||||
|
private byte[] toByteArray() {
|
||||||
try {
|
try {
|
||||||
classFile = cw.toByteArray();
|
return cw.toByteArray();
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
// ASM throws RuntimeException if something goes wrong - capture these and wrap them in a meaningful
|
|
||||||
// exception to support falling back to LambdaForm interpretation
|
|
||||||
throw new BytecodeGenerationException(e);
|
throw new BytecodeGenerationException(e);
|
||||||
}
|
}
|
||||||
maybeDump(className, classFile);
|
|
||||||
return classFile;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
@ -1607,6 +1648,7 @@ class InvokerBytecodeGenerator {
|
|||||||
|
|
||||||
private byte[] generateLambdaFormInterpreterEntryPointBytes() {
|
private byte[] generateLambdaFormInterpreterEntryPointBytes() {
|
||||||
classFilePrologue();
|
classFilePrologue();
|
||||||
|
methodPrologue();
|
||||||
|
|
||||||
// Suppress this method in backtraces displayed to the user.
|
// Suppress this method in backtraces displayed to the user.
|
||||||
mv.visitAnnotation(LF_HIDDEN_SIG, true);
|
mv.visitAnnotation(LF_HIDDEN_SIG, true);
|
||||||
@ -1645,7 +1687,7 @@ class InvokerBytecodeGenerator {
|
|||||||
// return statement
|
// return statement
|
||||||
emitReturnInsn(basicType(rtype));
|
emitReturnInsn(basicType(rtype));
|
||||||
|
|
||||||
classFileEpilogue();
|
methodEpilogue();
|
||||||
bogusMethod(invokerType);
|
bogusMethod(invokerType);
|
||||||
|
|
||||||
final byte[] classFile = cw.toByteArray();
|
final byte[] classFile = cw.toByteArray();
|
||||||
@ -1666,6 +1708,7 @@ class InvokerBytecodeGenerator {
|
|||||||
private byte[] generateNamedFunctionInvokerImpl(MethodTypeForm typeForm) {
|
private byte[] generateNamedFunctionInvokerImpl(MethodTypeForm typeForm) {
|
||||||
MethodType dstType = typeForm.erasedType();
|
MethodType dstType = typeForm.erasedType();
|
||||||
classFilePrologue();
|
classFilePrologue();
|
||||||
|
methodPrologue();
|
||||||
|
|
||||||
// Suppress this method in backtraces displayed to the user.
|
// Suppress this method in backtraces displayed to the user.
|
||||||
mv.visitAnnotation(LF_HIDDEN_SIG, true);
|
mv.visitAnnotation(LF_HIDDEN_SIG, true);
|
||||||
@ -1685,7 +1728,6 @@ class InvokerBytecodeGenerator {
|
|||||||
// Maybe unbox
|
// Maybe unbox
|
||||||
Class<?> dptype = dstType.parameterType(i);
|
Class<?> dptype = dstType.parameterType(i);
|
||||||
if (dptype.isPrimitive()) {
|
if (dptype.isPrimitive()) {
|
||||||
Class<?> sptype = dstType.basicType().wrap().parameterType(i);
|
|
||||||
Wrapper dstWrapper = Wrapper.forBasicType(dptype);
|
Wrapper dstWrapper = Wrapper.forBasicType(dptype);
|
||||||
Wrapper srcWrapper = dstWrapper.isSubwordOrInt() ? Wrapper.INT : dstWrapper; // narrow subword from int
|
Wrapper srcWrapper = dstWrapper.isSubwordOrInt() ? Wrapper.INT : dstWrapper; // narrow subword from int
|
||||||
emitUnboxing(srcWrapper);
|
emitUnboxing(srcWrapper);
|
||||||
@ -1713,7 +1755,7 @@ class InvokerBytecodeGenerator {
|
|||||||
}
|
}
|
||||||
emitReturnInsn(L_TYPE); // NOTE: NamedFunction invokers always return a reference value.
|
emitReturnInsn(L_TYPE); // NOTE: NamedFunction invokers always return a reference value.
|
||||||
|
|
||||||
classFileEpilogue();
|
methodEpilogue();
|
||||||
bogusMethod(dstType);
|
bogusMethod(dstType);
|
||||||
|
|
||||||
final byte[] classFile = cw.toByteArray();
|
final byte[] classFile = cw.toByteArray();
|
||||||
|
@ -773,6 +773,26 @@ class LambdaForm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate optimizable bytecode for this form after first looking for a
|
||||||
|
* pregenerated version in a specified class.
|
||||||
|
*/
|
||||||
|
void compileToBytecode(Class<?> lookupClass) {
|
||||||
|
if (vmentry != null && isCompiled) {
|
||||||
|
return; // already compiled somehow
|
||||||
|
}
|
||||||
|
MethodType invokerType = methodType();
|
||||||
|
assert(vmentry == null || vmentry.getMethodType().basicType().equals(invokerType));
|
||||||
|
MemberName member = new MemberName(lookupClass, debugName, invokerType, REF_invokeStatic);
|
||||||
|
MemberName resolvedMember = MemberName.getFactory().resolveOrNull(REF_invokeStatic, member, lookupClass);
|
||||||
|
if (resolvedMember != null) {
|
||||||
|
vmentry = resolvedMember;
|
||||||
|
isCompiled = true;
|
||||||
|
} else {
|
||||||
|
compileToBytecode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void computeInitialPreparedForms() {
|
private static void computeInitialPreparedForms() {
|
||||||
// Find all predefined invokers and associate them with canonical empty lambda forms.
|
// Find all predefined invokers and associate them with canonical empty lambda forms.
|
||||||
for (MemberName m : MemberName.getFactory().getMethods(LambdaForm.class, false, null, null, null)) {
|
for (MemberName m : MemberName.getFactory().getMethods(LambdaForm.class, false, null, null, null)) {
|
||||||
|
@ -24,9 +24,11 @@
|
|||||||
*/
|
*/
|
||||||
package jdk.tools.jlink.internal.plugins;
|
package jdk.tools.jlink.internal.plugins;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -48,14 +50,26 @@ public final class GenerateJLIClassesPlugin implements Plugin {
|
|||||||
|
|
||||||
private static final String BMH_SPECIES_PARAM = "bmh-species";
|
private static final String BMH_SPECIES_PARAM = "bmh-species";
|
||||||
|
|
||||||
|
private static final String DMH_PARAM = "dmh";
|
||||||
|
|
||||||
private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME);
|
private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME);
|
||||||
|
|
||||||
private static final String BMH = "java/lang/invoke/BoundMethodHandle";
|
private static final String BMH = "java/lang/invoke/BoundMethodHandle";
|
||||||
|
private static final Method BMH_FACTORY_METHOD;
|
||||||
|
|
||||||
private static final Method FACTORY_METHOD;
|
private static final String DMH = "java/lang/invoke/DirectMethodHandle$Holder";
|
||||||
|
private static final String DMH_INVOKE_VIRTUAL = "invokeVirtual";
|
||||||
|
private static final String DMH_INVOKE_STATIC = "invokeStatic";
|
||||||
|
private static final String DMH_INVOKE_SPECIAL = "invokeSpecial";
|
||||||
|
private static final String DMH_NEW_INVOKE_SPECIAL = "newInvokeSpecial";
|
||||||
|
private static final String DMH_INVOKE_INTERFACE = "invokeInterface";
|
||||||
|
private static final String DMH_INVOKE_STATIC_INIT = "invokeStaticInit";
|
||||||
|
private static final Method DMH_FACTORY_METHOD;
|
||||||
|
|
||||||
List<String> speciesTypes;
|
List<String> speciesTypes;
|
||||||
|
|
||||||
|
Map<String, List<String>> dmhMethods;
|
||||||
|
|
||||||
public GenerateJLIClassesPlugin() {
|
public GenerateJLIClassesPlugin() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,11 +101,9 @@ public final class GenerateJLIClassesPlugin implements Plugin {
|
|||||||
/**
|
/**
|
||||||
* @return the default Species forms to generate.
|
* @return the default Species forms to generate.
|
||||||
*
|
*
|
||||||
* This list was derived from running a Java concatenating strings
|
* This list was derived from running a small startup benchmark.
|
||||||
* with -Djava.lang.invoke.stringConcat=MH_INLINE_SIZED_EXACT set
|
* A better long-term solution is to define and run a set of quick
|
||||||
* plus a subset of octane. A better long-term solution is to define
|
* generators and extracting this list as a step in the build process.
|
||||||
* and run a set of quick generators and extracting this list as a
|
|
||||||
* step in the build process.
|
|
||||||
*/
|
*/
|
||||||
public static List<String> defaultSpecies() {
|
public static List<String> defaultSpecies() {
|
||||||
return List.of("LL", "L3", "L4", "L5", "L6", "L7", "L7I",
|
return List.of("LL", "L3", "L4", "L5", "L6", "L7", "L7I",
|
||||||
@ -100,18 +112,51 @@ public final class GenerateJLIClassesPlugin implements Plugin {
|
|||||||
"LILL", "I", "LLILL");
|
"LILL", "I", "LLILL");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the list of default DirectMethodHandle methods to generate.
|
||||||
|
*/
|
||||||
|
public static Map<String, List<String>> defaultDMHMethods() {
|
||||||
|
return Map.of(
|
||||||
|
DMH_INVOKE_VIRTUAL, List.of("_L", "L_L", "LI_I"),
|
||||||
|
DMH_INVOKE_SPECIAL, List.of("L_I", "L_L", "LF_L", "LD_L", "LL_L",
|
||||||
|
"L3_L", "L4_L", "L5_L", "L6_L", "L7_L", "LI_I", "LI_L", "LIL_I",
|
||||||
|
"LII_I", "LII_L", "LLI_L", "LLI_I", "LILI_I", "LIIL_L",
|
||||||
|
"LIILL_L", "LIILL_I", "LIIL_I", "LILIL_I", "LILILL_I",
|
||||||
|
"LILII_I", "LI3_I", "LI3L_I", "LI3LL_I", "LI3_L", "LI4_I"),
|
||||||
|
DMH_INVOKE_STATIC, List.of("II_I", "IL_I", "ILIL_I", "ILII_I",
|
||||||
|
"_I", "_L", "_V", "D_L", "F_L", "I_I", "II_L", "LI_L",
|
||||||
|
"L_V", "L_L", "LL_L", "L3_L", "L4_L", "L5_L", "L6_L",
|
||||||
|
"L7_L", "L8_L", "L9_L", "L9I_L", "L9II_L", "L9IIL_L",
|
||||||
|
"L10_L", "L11_L", "L12_L", "L13_L", "L13I_L", "L13II_L")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map from DirectMethodHandle method type to internal ID
|
||||||
|
private static final Map<String, Integer> DMH_METHOD_TYPE_MAP =
|
||||||
|
Map.of(
|
||||||
|
DMH_INVOKE_VIRTUAL, 0,
|
||||||
|
DMH_INVOKE_STATIC, 1,
|
||||||
|
DMH_INVOKE_SPECIAL, 2,
|
||||||
|
DMH_NEW_INVOKE_SPECIAL, 3,
|
||||||
|
DMH_INVOKE_INTERFACE, 4,
|
||||||
|
DMH_INVOKE_STATIC_INIT, 5
|
||||||
|
);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(Map<String, String> config) {
|
public void configure(Map<String, String> config) {
|
||||||
String mainArgument = config.get(NAME);
|
String mainArgument = config.get(NAME);
|
||||||
|
|
||||||
// Enable by default
|
// Enable by default
|
||||||
boolean bmhEnabled = true;
|
boolean bmhEnabled = true;
|
||||||
|
boolean dmhEnabled = true;
|
||||||
if (mainArgument != null) {
|
if (mainArgument != null) {
|
||||||
Set<String> args = Arrays.stream(mainArgument.split(","))
|
List<String> args = Arrays.asList(mainArgument.split(","));
|
||||||
.collect(Collectors.toSet());
|
|
||||||
if (!args.contains(BMH_PARAM)) {
|
if (!args.contains(BMH_PARAM)) {
|
||||||
bmhEnabled = false;
|
bmhEnabled = false;
|
||||||
}
|
}
|
||||||
|
if (!args.contains(DMH_PARAM)) {
|
||||||
|
dmhEnabled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bmhEnabled) {
|
if (!bmhEnabled) {
|
||||||
@ -132,40 +177,63 @@ public final class GenerateJLIClassesPlugin implements Plugin {
|
|||||||
speciesTypes = bmhSpecies.stream()
|
speciesTypes = bmhSpecies.stream()
|
||||||
.map(type -> expandSignature(type))
|
.map(type -> expandSignature(type))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
// Validation check
|
// DirectMethodHandles
|
||||||
for (String type : speciesTypes) {
|
if (!dmhEnabled) {
|
||||||
for (char c : type.toCharArray()) {
|
dmhMethods = Map.of();
|
||||||
if ("LIJFD".indexOf(c) < 0) {
|
} else {
|
||||||
throw new PluginException("All characters must "
|
dmhMethods = new HashMap<>();
|
||||||
+ "correspond to a basic field type: LIJFD");
|
for (String dmhParam : DMH_METHOD_TYPE_MAP.keySet()) {
|
||||||
|
String args = config.get(dmhParam);
|
||||||
|
if (args != null && !args.isEmpty()) {
|
||||||
|
List<String> dmhMethodTypes = Arrays.stream(args.split(","))
|
||||||
|
.map(String::trim)
|
||||||
|
.filter(s -> !s.isEmpty())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
dmhMethods.put(dmhParam, dmhMethodTypes);
|
||||||
|
// Validation check
|
||||||
|
for (String type : dmhMethodTypes) {
|
||||||
|
String[] typeParts = type.split("_");
|
||||||
|
// check return type (second part)
|
||||||
|
if (typeParts.length != 2 || typeParts[1].length() != 1
|
||||||
|
|| "LJIFDV".indexOf(typeParts[1].charAt(0)) == -1) {
|
||||||
|
throw new PluginException(
|
||||||
|
"Method type signature must be of form [LJIFD]*_[LJIFDV]");
|
||||||
|
}
|
||||||
|
// expand and check arguments (first part)
|
||||||
|
expandSignature(typeParts[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (dmhMethods.isEmpty()) {
|
||||||
|
dmhMethods = defaultDMHMethods();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void requireBasicType(char c) {
|
||||||
|
if ("LIJFD".indexOf(c) < 0) {
|
||||||
|
throw new PluginException(
|
||||||
|
"Character " + c + " must correspond to a basic field type: LIJFD");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
|
public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
|
||||||
in.entries().forEach(data -> {
|
// Copy all but DMH_ENTRY to out
|
||||||
if (("/java.base/" + BMH + ".class").equals(data.path())) {
|
in.transformAndCopy(entry -> entry.path().equals(DMH_ENTRY) ? null : entry, out);
|
||||||
// Add BoundMethodHandle unchanged
|
speciesTypes.forEach(types -> generateBMHClass(types, out));
|
||||||
out.add(data);
|
generateDMHClass(out);
|
||||||
speciesTypes.forEach(types -> generateConcreteClass(types, data, out));
|
|
||||||
} else {
|
|
||||||
out.add(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return out.build();
|
return out.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void generateConcreteClass(String types, ResourcePoolEntry data, ResourcePoolBuilder out) {
|
private void generateBMHClass(String types, ResourcePoolBuilder out) {
|
||||||
try {
|
try {
|
||||||
// Generate class
|
// Generate class
|
||||||
Map.Entry<String, byte[]> result = (Map.Entry<String, byte[]>)
|
Map.Entry<String, byte[]> result = (Map.Entry<String, byte[]>)
|
||||||
FACTORY_METHOD.invoke(null, types);
|
BMH_FACTORY_METHOD.invoke(null, types);
|
||||||
String className = result.getKey();
|
String className = result.getKey();
|
||||||
byte[] bytes = result.getValue();
|
byte[] bytes = result.getValue();
|
||||||
|
|
||||||
@ -179,13 +247,47 @@ public final class GenerateJLIClassesPlugin implements Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void generateDMHClass(ResourcePoolBuilder out) {
|
||||||
|
int count = 0;
|
||||||
|
for (List<String> entry : dmhMethods.values()) {
|
||||||
|
count += entry.size();
|
||||||
|
}
|
||||||
|
MethodType[] methodTypes = new MethodType[count];
|
||||||
|
int[] dmhTypes = new int[count];
|
||||||
|
int index = 0;
|
||||||
|
for (Map.Entry<String, List<String>> entry : dmhMethods.entrySet()) {
|
||||||
|
String dmhType = entry.getKey();
|
||||||
|
for (String type : entry.getValue()) {
|
||||||
|
methodTypes[index] = asMethodType(type);
|
||||||
|
dmhTypes[index] = DMH_METHOD_TYPE_MAP.get(dmhType);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
byte[] bytes = (byte[])DMH_FACTORY_METHOD
|
||||||
|
.invoke(null,
|
||||||
|
DMH,
|
||||||
|
methodTypes,
|
||||||
|
dmhTypes);
|
||||||
|
ResourcePoolEntry ndata = ResourcePoolEntry.create(DMH_ENTRY, bytes);
|
||||||
|
out.add(ndata);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new PluginException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static final String DMH_ENTRY = "/java.base/" + DMH + ".class";
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
Class<?> BMHFactory = Class.forName("java.lang.invoke.BoundMethodHandle$Factory");
|
Class<?> BMHFactory = Class.forName("java.lang.invoke.BoundMethodHandle$Factory");
|
||||||
Method genClassMethod = BMHFactory.getDeclaredMethod("generateConcreteBMHClassBytes",
|
BMH_FACTORY_METHOD = BMHFactory.getDeclaredMethod("generateConcreteBMHClassBytes",
|
||||||
String.class);
|
String.class);
|
||||||
genClassMethod.setAccessible(true);
|
BMH_FACTORY_METHOD.setAccessible(true);
|
||||||
FACTORY_METHOD = genClassMethod;
|
|
||||||
|
Class<?> DMHFactory = Class.forName("java.lang.invoke.DirectMethodHandle");
|
||||||
|
DMH_FACTORY_METHOD = DMHFactory.getDeclaredMethod("generateDMHClassBytes",
|
||||||
|
String.class, MethodType[].class, int[].class);
|
||||||
|
DMH_FACTORY_METHOD.setAccessible(true);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new PluginException(e);
|
throw new PluginException(e);
|
||||||
}
|
}
|
||||||
@ -202,6 +304,7 @@ public final class GenerateJLIClassesPlugin implements Plugin {
|
|||||||
count *= 10;
|
count *= 10;
|
||||||
count += (c - '0');
|
count += (c - '0');
|
||||||
} else {
|
} else {
|
||||||
|
requireBasicType(c);
|
||||||
for (int j = 1; j < count; j++) {
|
for (int j = 1; j < count; j++) {
|
||||||
sb.append(last);
|
sb.append(last);
|
||||||
}
|
}
|
||||||
@ -210,9 +313,52 @@ public final class GenerateJLIClassesPlugin implements Plugin {
|
|||||||
count = 0;
|
count = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int j = 1; j < count; j++) {
|
|
||||||
sb.append(last);
|
// ended with a number, e.g., "L2": append last char count - 1 times
|
||||||
|
if (count > 1) {
|
||||||
|
requireBasicType(last);
|
||||||
|
for (int j = 1; j < count; j++) {
|
||||||
|
sb.append(last);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static MethodType asMethodType(String basicSignatureString) {
|
||||||
|
String[] parts = basicSignatureString.split("_");
|
||||||
|
assert(parts.length == 2);
|
||||||
|
assert(parts[1].length() == 1);
|
||||||
|
String parameters = expandSignature(parts[0]);
|
||||||
|
Class<?> rtype = primitiveType(parts[1].charAt(0));
|
||||||
|
Class<?>[] ptypes = new Class<?>[parameters.length()];
|
||||||
|
for (int i = 0; i < ptypes.length; i++) {
|
||||||
|
ptypes[i] = primitiveType(parameters.charAt(i));
|
||||||
|
}
|
||||||
|
return MethodType.methodType(rtype, ptypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<?> primitiveType(char c) {
|
||||||
|
switch (c) {
|
||||||
|
case 'F':
|
||||||
|
return float.class;
|
||||||
|
case 'D':
|
||||||
|
return double.class;
|
||||||
|
case 'I':
|
||||||
|
return int.class;
|
||||||
|
case 'L':
|
||||||
|
return Object.class;
|
||||||
|
case 'J':
|
||||||
|
return long.class;
|
||||||
|
case 'V':
|
||||||
|
return void.class;
|
||||||
|
case 'Z':
|
||||||
|
case 'B':
|
||||||
|
case 'S':
|
||||||
|
case 'C':
|
||||||
|
throw new IllegalArgumentException("Not a valid primitive: " + c +
|
||||||
|
" (use I instead)");
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Not a primitive: " + c);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user