--- /dev/null 2019-11-19 22:05:02.069813242 -0800 +++ new/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGeneratorHelper.java 2020-08-05 15:54:01.258647371 -0700 @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package java.lang.invoke; + +import jdk.internal.loader.BuiltinClassLoader; +import jdk.internal.misc.VM; +import jdk.internal.access.JavaLangInvokeAccess; +import jdk.internal.access.SharedSecrets; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.invoke.MethodType; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Map; +import java.util.HashMap; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.stream.Stream; +/** + * Helper to generate java.lang.invoke classes. + * + * This class takes stream of strings generated by running any application with + * {@code -Djava.lang.invoke.MethodHandle.TRACE_RESOLVE=true}. This is done + * automatically during build, see make/GenerateLinkOptData.gmk. See + * build/tools/classlist/HelloClasslist.java for the training application. + * + * HelloClasslist tries to reflect common use of java.lang.invoke during early + * startup and warmup in various applications. To ensure a good default + * trade-off between static footprint and startup the application should be + * relatively conservative. + * + * help to improve startup time. + */ + +class InvokerBytecodeGeneratorHelper { + // @overide Can be accessed in children + private static int DMH_INVOKE_VIRTUAL_TYPE = 0; + private static int DMH_INVOKE_INTERFACE_TYPE = 4; + + private static final String DIRECT_HOLDER = "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 String DMH_INVOKE_SPECIAL_IFC = "invokeSpecialIFC"; + + private static final String DELEGATING_HOLDER = "java/lang/invoke/DelegatingMethodHandle$Holder"; + private static final String BASIC_FORMS_HOLDER = "java/lang/invoke/LambdaForm$Holder"; + + private static final String INVOKERS_HOLDER_NAME = "java.lang.invoke.Invokers$Holder"; + private static final String INVOKERS_HOLDER_INTERNAL_NAME = INVOKERS_HOLDER_NAME.replace('.', '/'); + + // Map from DirectMethodHandle method type to internal ID, matching values + // of the corresponding constants in java.lang.invoke.MethodTypeForm + private static final Map DMH_METHOD_TYPE_MAP = + Map.of( + DMH_INVOKE_VIRTUAL, DMH_INVOKE_VIRTUAL_TYPE, + DMH_INVOKE_STATIC, 1, + DMH_INVOKE_SPECIAL, 2, + DMH_NEW_INVOKE_SPECIAL, 3, + DMH_INVOKE_INTERFACE, DMH_INVOKE_INTERFACE_TYPE, + DMH_INVOKE_STATIC_INIT, 5, + DMH_INVOKE_SPECIAL_IFC, 20 + ); + + /** + * Output to DumpLoadedClassList, format as LF_RESOLVE + * @see InvokerBytecodeGenerator + * @param line the line to output. + */ + static native void cdsTraceResolve(String line); + + private static TreeSet getSpeciesTyes() { return speciesTypes; } + private static TreeSet speciesTypes = new TreeSet<>(); + private static TreeSet invokerTypes = new TreeSet<>(); + private static TreeSet callSiteTypes = new TreeSet<>(); + private static Map> dmhMethods = new TreeMap<>(); + + private static void clear() { + speciesTypes.clear(); + invokerTypes.clear(); + callSiteTypes.clear(); + dmhMethods.clear(); + } + + private static void addSpeciesType(String type) { + speciesTypes.add(expandSignature(type)); + } + + private static void addInvokerType(String methodType) { + validateMethodType(methodType); + invokerTypes.add(methodType); + } + + private static void addCallSiteType(String csType) { + validateMethodType(csType); + callSiteTypes.add(csType); + } + + private static void readTraceConfig(Stream lines) { + lines.map(line -> line.split(" ")) + .forEach(parts -> { + switch (parts[0]) { + case "[SPECIES_RESOLVE]": + // Allow for new types of species data classes being resolved here + if (parts.length == 3 && parts[1].startsWith("java.lang.invoke.BoundMethodHandle$Species_")) { + String species = parts[1].substring("java.lang.invoke.BoundMethodHandle$Species_".length()); + if (!"L".equals(species)) { + addSpeciesType(species); + } + } + break; + case "[LF_RESOLVE]": + String methodType = parts[3]; + if (parts[1].equals(INVOKERS_HOLDER_NAME)) { + if ("linkToTargetMethod".equals(parts[2]) || + "linkToCallSite".equals(parts[2])) { + addCallSiteType(methodType); + } else { + addInvokerType(methodType); + } + } else if (parts[1].contains("DirectMethodHandle")) { + String dmh = parts[2]; + // ignore getObject etc for now (generated + // by default) + if (DMH_METHOD_TYPE_MAP.containsKey(dmh)) { + addDMHMethodType(dmh, methodType); + } + } + break; + default: break; // ignore + } + }); + } + + /** + * called from vm to create generated lambda-form class + * The input is as TRACE_RESOLVE + * @return @code { Object[] } if holder classes can be generated. + * @param lines the output line string from @cdsTraceResolve + */ + static Object[] generateMethodHandleHolderClasses(String[] lines) { + try { + Map result = generateMHHolderClasses(lines); + clear(); + if (result == null) { + return null; + } + int size = result.size(); + Object[] ret_array = new Object[size * 2]; + int i = 0; + for (Map.Entry entry : result.entrySet()) { + ret_array[i++] = entry.getKey(); + ret_array[i++] = entry.getValue(); + }; + return ret_array; + } catch (Exception e) { + return null; + } + } + + /* return a map of generateMHHolderClasses(String[] lines) throws InvokerGenerateBytesException { + if (lines == null || lines.length == 0) { + return null; + } + readTraceConfig(Arrays.stream(lines)); + int count = 0; + for (Set entry : dmhMethods.values()) { + count += entry.size(); + } + MethodType[] directMethodTypes = 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()) { + // The DMH type to actually ask for is retrieved by removing + // the first argument, which needs to be of Object.class + MethodType mt = asMethodType(type); + if (mt.parameterCount() < 1 || + mt.parameterType(0) != Object.class) { + throw new InvokerGenerateBytesException( + "DMH type parameter must start with L: " + dmhType + " " + type); + } + + // Adapt the method type of the LF to retrieve + directMethodTypes[index] = mt.dropParameterTypes(0, 1); + + // invokeVirtual and invokeInterface must have a leading Object + // parameter, i.e., the receiver + dmhTypes[index] = DMH_METHOD_TYPE_MAP.get(dmhType); + if (dmhTypes[index] == DMH_INVOKE_INTERFACE_TYPE || + dmhTypes[index] == DMH_INVOKE_VIRTUAL_TYPE) { + if (mt.parameterCount() < 2 || + mt.parameterType(1) != Object.class) { + throw new InvokerGenerateBytesException( + "DMH type parameter must start with LL: " + dmhType + " " + type); + } + } + index++; + } + } + + // The invoker type to ask for is retrieved by removing the first + // and the last argument, which needs to be of Object.class + MethodType[] invokerMethodTypes = new MethodType[invokerTypes.size()]; + int i = 0; + for (String invokerType : invokerTypes) { + MethodType mt = asMethodType(invokerType); + final int lastParam = mt.parameterCount() - 1; + if (mt.parameterCount() < 2 || + mt.parameterType(0) != Object.class || + mt.parameterType(lastParam) != Object.class) { + throw new InvokerGenerateBytesException( + "Invoker type parameter must start and end with Object: " + invokerType); + } + mt = mt.dropParameterTypes(lastParam, lastParam + 1); + invokerMethodTypes[i] = mt.dropParameterTypes(0, 1); + i++; + } + + // The callSite type to ask for is retrieved by removing the last + // argument, which needs to be of Object.class + MethodType[] callSiteMethodTypes = new MethodType[callSiteTypes.size()]; + i = 0; + for (String callSiteType : callSiteTypes) { + MethodType mt = asMethodType(callSiteType); + final int lastParam = mt.parameterCount() - 1; + if (mt.parameterCount() < 1 || + mt.parameterType(lastParam) != Object.class) { + throw new InvokerGenerateBytesException( + "CallSite type parameter must end with Object: " + callSiteType); + } + callSiteMethodTypes[i] = mt.dropParameterTypes(lastParam, lastParam + 1); + i++; + } + Map result = new HashMap(); + + byte[] res = GenerateJLIClassesHelper.generateDirectMethodHandleHolderClassBytes( + DIRECT_HOLDER, directMethodTypes, dmhTypes); + result.put(DIRECT_METHOD_HOLDER_ENTRY, res); + + res = GenerateJLIClassesHelper.generateDelegatingMethodHandleHolderClassBytes( + DELEGATING_HOLDER, directMethodTypes); + result.put(DELEGATING_METHOD_HOLDER_ENTRY, res); + + + res = GenerateJLIClassesHelper.generateInvokersHolderClassBytes(INVOKERS_HOLDER_INTERNAL_NAME, + invokerMethodTypes, callSiteMethodTypes); + result.put(INVOKERS_HOLDER_ENTRY, res); + + res = GenerateJLIClassesHelper.generateBasicFormsClassBytes(BASIC_FORMS_HOLDER); + result.put(BASIC_FORMS_HOLDER_ENTRY, res); + + speciesTypes.forEach(types -> { + Map.Entry entry = GenerateJLIClassesHelper.generateConcreteBMHClassBytes(types); + String className = entry.getKey(); + String key = "/java.base/" + className + ".class"; + byte[] value = entry.getValue(); + result.put(key, value); + }); + + return result; + } + + private static final String DIRECT_METHOD_HOLDER_ENTRY = + "/java.base/" + DIRECT_HOLDER + ".class"; + private static final String DELEGATING_METHOD_HOLDER_ENTRY = + "/java.base/" + DELEGATING_HOLDER + ".class"; + private static final String BASIC_FORMS_HOLDER_ENTRY = + "/java.base/" + BASIC_FORMS_HOLDER + ".class"; + private static final String INVOKERS_HOLDER_ENTRY = + "/java.base/" + INVOKERS_HOLDER_INTERNAL_NAME + ".class"; + + 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 = simpleType(parts[1].charAt(0)); + if (parameters.isEmpty()) { + return MethodType.methodType(rtype); + } else { + Class[] ptypes = new Class[parameters.length()]; + for (int i = 0; i < ptypes.length; i++) { + ptypes[i] = simpleType(parameters.charAt(i)); + } + return MethodType.methodType(rtype, ptypes); + } + } + + private static void addDMHMethodType(String dmh, String methodType) { + validateMethodType(methodType); + Set methodTypes = dmhMethods.get(dmh); + if (methodTypes == null) { + methodTypes = new TreeSet<>(); + dmhMethods.put(dmh, methodTypes); + } + methodTypes.add(methodType); + } + + private static void validateMethodType(String type) { + 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 InvokerGenerateBytesException( + "Method type signature must be of form [LJIFD]*_[LJIFDV]"); + } + // expand and check arguments (first part) + expandSignature(typeParts[0]); + } + + // Convert LL -> LL, L3 -> LLL + private static String expandSignature(String signature) { + StringBuilder sb = new StringBuilder(); + char last = 'X'; + int count = 0; + for (int i = 0; i < signature.length(); i++) { + char c = signature.charAt(i); + if (c >= '0' && c <= '9') { + count *= 10; + count += (c - '0'); + } else { + requireBasicType(c); + for (int j = 1; j < count; j++) { + sb.append(last); + } + sb.append(c); + last = c; + count = 0; + } + } + + // 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 void requireBasicType(char c) { + if ("LIJFD".indexOf(c) < 0) { + throw new InvokerGenerateBytesException( + "Character " + c + " must correspond to a basic field type: LIJFD"); + } + } + + private static Class simpleType(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); + } + } +}