8163369: Enable generating DMH classes at link time

Reviewed-by: vlivanov, shade
This commit is contained in:
Claes Redestad 2016-08-09 10:00:31 +02:00
parent 3be823e0db
commit cdef6ef876
4 changed files with 295 additions and 57 deletions

View File

@ -27,6 +27,7 @@ package java.lang.invoke;
import jdk.internal.misc.Unsafe;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Stable;
import sun.invoke.util.ValueConversions;
import sun.invoke.util.VerifyAccess;
import sun.invoke.util.VerifyType;
@ -190,14 +191,15 @@ class DirectMethodHandle extends MethodHandle {
boolean doesAlloc = (which == LF_NEWINVSPECIAL);
String linkerName, lambdaName;
switch (which) {
case LF_INVVIRTUAL: linkerName = "linkToVirtual"; lambdaName = "DMH.invokeVirtual"; break;
case LF_INVSTATIC: linkerName = "linkToStatic"; lambdaName = "DMH.invokeStatic"; break;
case LF_INVSTATIC_INIT:linkerName = "linkToStatic"; lambdaName = "DMH.invokeStaticInit"; break;
case LF_INVSPECIAL: linkerName = "linkToSpecial"; lambdaName = "DMH.invokeSpecial"; break;
case LF_INVINTERFACE: linkerName = "linkToInterface"; lambdaName = "DMH.invokeInterface"; break;
case LF_NEWINVSPECIAL: linkerName = "linkToSpecial"; lambdaName = "DMH.newInvokeSpecial"; break;
case LF_INVVIRTUAL: linkerName = "linkToVirtual"; lambdaName = "invokeVirtual"; break;
case LF_INVSTATIC: linkerName = "linkToStatic"; lambdaName = "invokeStatic"; break;
case LF_INVSTATIC_INIT:linkerName = "linkToStatic"; lambdaName = "invokeStaticInit"; break;
case LF_INVSPECIAL: linkerName = "linkToSpecial"; lambdaName = "invokeSpecial"; break;
case LF_INVINTERFACE: linkerName = "linkToInterface"; lambdaName = "invokeInterface"; break;
case LF_NEWINVSPECIAL: linkerName = "linkToSpecial"; lambdaName = "newInvokeSpecial"; break;
default: throw new InternalError("which="+which);
}
MethodType mtypeWithArg = mtype.appendParameterTypes(MemberName.class);
if (doesAlloc)
mtypeWithArg = mtypeWithArg
@ -240,11 +242,26 @@ class DirectMethodHandle extends MethodHandle {
names[LINKER_CALL] = new Name(linker, outArgs);
lambdaName += "_" + shortenSignature(basicTypeSignature(mtype));
LambdaForm lform = new LambdaForm(lambdaName, ARG_LIMIT, names, result);
// This is a tricky bit of code. Don't send it through the LF interpreter.
lform.compileToBytecode();
lform.compileToBytecode(Holder.class);
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) {
if (name.function == NF_internalMemberName ||
name.function == NF_internalMemberNameEnsureInit ||
@ -487,7 +504,7 @@ class DirectMethodHandle extends MethodHandle {
}
// Caching machinery for field accessors:
private static byte
private static final byte
AF_GETFIELD = 0,
AF_PUTFIELD = 1,
AF_GETSTATIC = 2,
@ -497,7 +514,7 @@ class DirectMethodHandle extends MethodHandle {
AF_LIMIT = 6;
// Enumerate the different field kinds using Wrapper,
// with an extra case added for checked references.
private static int
private static final int
FT_LAST_WRAPPER = Wrapper.values().length-1,
FT_UNCHECKED_REF = Wrapper.OBJECT.ordinal(),
FT_CHECKED_REF = FT_LAST_WRAPPER+1,
@ -507,6 +524,7 @@ class DirectMethodHandle extends MethodHandle {
+ (isVolatile ? FT_LIMIT : 0)
+ ftypeKind);
}
@Stable
private static final LambdaForm[] ACCESSOR_FORMS
= new LambdaForm[afIndex(AF_LIMIT, false, 0)];
private static int ftypeKind(Class<?> ftype) {
@ -549,10 +567,11 @@ class DirectMethodHandle extends MethodHandle {
return lform;
}
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];
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
return lform;
}
@ -682,4 +701,15 @@ class DirectMethodHandle extends MethodHandle {
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 {}
}

View File

@ -70,7 +70,7 @@ class InvokerBytecodeGenerator {
private static final String LLV_SIG = "(L" + OBJ + ";L" + OBJ + ";)V";
/** Name of its super class*/
private static final String superName = OBJ;
private static final String INVOKER_SUPER_NAME = OBJ;
/** Name of new class */
private final String className;
@ -296,12 +296,15 @@ class InvokerBytecodeGenerator {
/**
* Set up class file generation.
*/
private void classFilePrologue() {
private ClassWriter classFilePrologue() {
final int NOT_ACC_PUBLIC = 0; // not ACC_PUBLIC
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);
return cw;
}
private void methodPrologue() {
String invokerDesc = invokerType.toMethodDescriptorString();
mv = cw.visitMethod(Opcodes.ACC_STATIC, invokerName, invokerDesc, null, null);
}
@ -309,7 +312,7 @@ class InvokerBytecodeGenerator {
/**
* Tear down class file generation.
*/
private void classFileEpilogue() {
private void methodEpilogue() {
mv.visitMaxs(0, 0);
mv.visitEnd();
}
@ -644,6 +647,44 @@ class InvokerBytecodeGenerator {
*/
private byte[] generateCustomizedCodeBytes() {
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.
mv.visitAnnotation(LF_HIDDEN_SIG, true);
@ -748,19 +789,19 @@ class InvokerBytecodeGenerator {
// return statement
emitReturn(onStack);
classFileEpilogue();
bogusMethod(lambdaForm);
methodEpilogue();
}
final byte[] classFile;
/*
* @throws BytecodeGenerationException if something goes wrong when
* generating the byte code
*/
private byte[] toByteArray() {
try {
classFile = cw.toByteArray();
return cw.toByteArray();
} 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);
}
maybeDump(className, classFile);
return classFile;
}
@SuppressWarnings("serial")
@ -1607,6 +1648,7 @@ class InvokerBytecodeGenerator {
private byte[] generateLambdaFormInterpreterEntryPointBytes() {
classFilePrologue();
methodPrologue();
// Suppress this method in backtraces displayed to the user.
mv.visitAnnotation(LF_HIDDEN_SIG, true);
@ -1645,7 +1687,7 @@ class InvokerBytecodeGenerator {
// return statement
emitReturnInsn(basicType(rtype));
classFileEpilogue();
methodEpilogue();
bogusMethod(invokerType);
final byte[] classFile = cw.toByteArray();
@ -1666,6 +1708,7 @@ class InvokerBytecodeGenerator {
private byte[] generateNamedFunctionInvokerImpl(MethodTypeForm typeForm) {
MethodType dstType = typeForm.erasedType();
classFilePrologue();
methodPrologue();
// Suppress this method in backtraces displayed to the user.
mv.visitAnnotation(LF_HIDDEN_SIG, true);
@ -1685,7 +1728,6 @@ class InvokerBytecodeGenerator {
// Maybe unbox
Class<?> dptype = dstType.parameterType(i);
if (dptype.isPrimitive()) {
Class<?> sptype = dstType.basicType().wrap().parameterType(i);
Wrapper dstWrapper = Wrapper.forBasicType(dptype);
Wrapper srcWrapper = dstWrapper.isSubwordOrInt() ? Wrapper.INT : dstWrapper; // narrow subword from int
emitUnboxing(srcWrapper);
@ -1713,7 +1755,7 @@ class InvokerBytecodeGenerator {
}
emitReturnInsn(L_TYPE); // NOTE: NamedFunction invokers always return a reference value.
classFileEpilogue();
methodEpilogue();
bogusMethod(dstType);
final byte[] classFile = cw.toByteArray();

View File

@ -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() {
// Find all predefined invokers and associate them with canonical empty lambda forms.
for (MemberName m : MemberName.getFactory().getMethods(LambdaForm.class, false, null, null, null)) {

View File

@ -24,9 +24,11 @@
*/
package jdk.tools.jlink.internal.plugins;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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 DMH_PARAM = "dmh";
private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME);
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;
Map<String, List<String>> dmhMethods;
public GenerateJLIClassesPlugin() {
}
@ -87,11 +101,9 @@ public final class GenerateJLIClassesPlugin implements Plugin {
/**
* @return the default Species forms to generate.
*
* This list was derived from running a Java concatenating strings
* with -Djava.lang.invoke.stringConcat=MH_INLINE_SIZED_EXACT set
* plus a subset of octane. A better long-term solution is to define
* and run a set of quick generators and extracting this list as a
* step in the build process.
* This list was derived from running a small startup benchmark.
* A better long-term solution is to define and run a set of quick
* generators and extracting this list as a step in the build process.
*/
public static List<String> defaultSpecies() {
return List.of("LL", "L3", "L4", "L5", "L6", "L7", "L7I",
@ -100,18 +112,51 @@ public final class GenerateJLIClassesPlugin implements Plugin {
"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
public void configure(Map<String, String> config) {
String mainArgument = config.get(NAME);
// Enable by default
boolean bmhEnabled = true;
boolean dmhEnabled = true;
if (mainArgument != null) {
Set<String> args = Arrays.stream(mainArgument.split(","))
.collect(Collectors.toSet());
List<String> args = Arrays.asList(mainArgument.split(","));
if (!args.contains(BMH_PARAM)) {
bmhEnabled = false;
}
if (!args.contains(DMH_PARAM)) {
dmhEnabled = false;
}
}
if (!bmhEnabled) {
@ -132,40 +177,63 @@ public final class GenerateJLIClassesPlugin implements Plugin {
speciesTypes = bmhSpecies.stream()
.map(type -> expandSignature(type))
.collect(Collectors.toList());
}
// Validation check
for (String type : speciesTypes) {
for (char c : type.toCharArray()) {
if ("LIJFD".indexOf(c) < 0) {
throw new PluginException("All characters must "
+ "correspond to a basic field type: LIJFD");
// DirectMethodHandles
if (!dmhEnabled) {
dmhMethods = Map.of();
} else {
dmhMethods = new HashMap<>();
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
public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
in.entries().forEach(data -> {
if (("/java.base/" + BMH + ".class").equals(data.path())) {
// Add BoundMethodHandle unchanged
out.add(data);
speciesTypes.forEach(types -> generateConcreteClass(types, data, out));
} else {
out.add(data);
}
});
// Copy all but DMH_ENTRY to out
in.transformAndCopy(entry -> entry.path().equals(DMH_ENTRY) ? null : entry, out);
speciesTypes.forEach(types -> generateBMHClass(types, out));
generateDMHClass(out);
return out.build();
}
@SuppressWarnings("unchecked")
private void generateConcreteClass(String types, ResourcePoolEntry data, ResourcePoolBuilder out) {
private void generateBMHClass(String types, ResourcePoolBuilder out) {
try {
// Generate class
Map.Entry<String, byte[]> result = (Map.Entry<String, byte[]>)
FACTORY_METHOD.invoke(null, types);
BMH_FACTORY_METHOD.invoke(null, types);
String className = result.getKey();
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 {
try {
Class<?> BMHFactory = Class.forName("java.lang.invoke.BoundMethodHandle$Factory");
Method genClassMethod = BMHFactory.getDeclaredMethod("generateConcreteBMHClassBytes",
BMH_FACTORY_METHOD = BMHFactory.getDeclaredMethod("generateConcreteBMHClassBytes",
String.class);
genClassMethod.setAccessible(true);
FACTORY_METHOD = genClassMethod;
BMH_FACTORY_METHOD.setAccessible(true);
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) {
throw new PluginException(e);
}
@ -202,6 +304,7 @@ public final class GenerateJLIClassesPlugin implements Plugin {
count *= 10;
count += (c - '0');
} else {
requireBasicType(c);
for (int j = 1; j < count; j++) {
sb.append(last);
}
@ -210,9 +313,52 @@ public final class GenerateJLIClassesPlugin implements Plugin {
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();
}
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);
}
}
}