diff --git a/jdk/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java b/jdk/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java index fe77e01fcc7..e346bd33421 100644 --- a/jdk/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java +++ b/jdk/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java @@ -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 {} } diff --git a/jdk/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java b/jdk/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java index d42bad62892..507bcdb4c61 100644 --- a/jdk/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java +++ b/jdk/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java @@ -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(); diff --git a/jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java b/jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java index 2a722db28e0..87a97dad368 100644 --- a/jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java +++ b/jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java @@ -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)) { diff --git a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/GenerateJLIClassesPlugin.java b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/GenerateJLIClassesPlugin.java index 0fdd27ec01d..3de8ffee099 100644 --- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/GenerateJLIClassesPlugin.java +++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/GenerateJLIClassesPlugin.java @@ -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 speciesTypes; + Map> 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 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> 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 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 config) { String mainArgument = config.get(NAME); // Enable by default boolean bmhEnabled = true; + boolean dmhEnabled = true; if (mainArgument != null) { - Set args = Arrays.stream(mainArgument.split(",")) - .collect(Collectors.toSet()); + List 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 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 result = (Map.Entry) - 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 entry : dmhMethods.values()) { + count += entry.size(); + } + MethodType[] methodTypes = new MethodType[count]; + int[] dmhTypes = new int[count]; + int index = 0; + for (Map.Entry> 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); + } + } }