1 /* 2 * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 * 23 */ 24 package java.lang.invoke; 25 26 import jdk.internal.loader.BuiltinClassLoader; 27 import jdk.internal.misc.VM; 28 import jdk.internal.access.JavaLangInvokeAccess; 29 import jdk.internal.access.SharedSecrets; 30 import java.io.BufferedReader; 31 import java.io.File; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.io.InputStreamReader; 35 import java.lang.invoke.MethodType; 36 import java.nio.file.Files; 37 import java.util.Arrays; 38 import java.util.EnumSet; 39 import java.util.Map; 40 import java.util.HashMap; 41 import java.util.Set; 42 import java.util.TreeMap; 43 import java.util.TreeSet; 44 import java.util.stream.Stream; 45 /** 46 * Helper to generate java.lang.invoke classes. 47 * 48 * This class takes stream of strings generated by running any application with 49 * {@code -Djava.lang.invoke.MethodHandle.TRACE_RESOLVE=true}. This is done 50 * automatically during build, see make/GenerateLinkOptData.gmk. See 51 * build/tools/classlist/HelloClasslist.java for the training application. 52 * 53 * HelloClasslist tries to reflect common use of java.lang.invoke during early 54 * startup and warmup in various applications. To ensure a good default 55 * trade-off between static footprint and startup the application should be 56 * relatively conservative. 57 * 58 * help to improve startup time. 59 */ 60 61 class InvokerBytecodeGeneratorHelper { 62 // @overide Can be accessed in children 63 private static int DMH_INVOKE_VIRTUAL_TYPE = 0; 64 private static int DMH_INVOKE_INTERFACE_TYPE = 4; 65 66 private static final String DIRECT_HOLDER = "java/lang/invoke/DirectMethodHandle$Holder"; 67 private static final String DMH_INVOKE_VIRTUAL = "invokeVirtual"; 68 private static final String DMH_INVOKE_STATIC = "invokeStatic"; 69 private static final String DMH_INVOKE_SPECIAL = "invokeSpecial"; 70 private static final String DMH_NEW_INVOKE_SPECIAL = "newInvokeSpecial"; 71 private static final String DMH_INVOKE_INTERFACE = "invokeInterface"; 72 private static final String DMH_INVOKE_STATIC_INIT = "invokeStaticInit"; 73 private static final String DMH_INVOKE_SPECIAL_IFC = "invokeSpecialIFC"; 74 75 private static final String DELEGATING_HOLDER = "java/lang/invoke/DelegatingMethodHandle$Holder"; 76 private static final String BASIC_FORMS_HOLDER = "java/lang/invoke/LambdaForm$Holder"; 77 78 private static final String INVOKERS_HOLDER_NAME = "java.lang.invoke.Invokers$Holder"; 79 private static final String INVOKERS_HOLDER_INTERNAL_NAME = INVOKERS_HOLDER_NAME.replace('.', '/'); 80 81 // Map from DirectMethodHandle method type to internal ID, matching values 82 // of the corresponding constants in java.lang.invoke.MethodTypeForm 83 private static final Map<String, Integer> DMH_METHOD_TYPE_MAP = 84 Map.of( 85 DMH_INVOKE_VIRTUAL, DMH_INVOKE_VIRTUAL_TYPE, 86 DMH_INVOKE_STATIC, 1, 87 DMH_INVOKE_SPECIAL, 2, 88 DMH_NEW_INVOKE_SPECIAL, 3, 89 DMH_INVOKE_INTERFACE, DMH_INVOKE_INTERFACE_TYPE, 90 DMH_INVOKE_STATIC_INIT, 5, 91 DMH_INVOKE_SPECIAL_IFC, 20 92 ); 93 94 /** 95 * Output to DumpLoadedClassList, format as LF_RESOLVE 96 * @see InvokerBytecodeGenerator 97 * @param line the line to output. 98 */ 99 static native void cdsTraceResolve(String line); 100 101 private static TreeSet<String> getSpeciesTyes() { return speciesTypes; } 102 private static TreeSet<String> speciesTypes = new TreeSet<>(); 103 private static TreeSet<String> invokerTypes = new TreeSet<>(); 104 private static TreeSet<String> callSiteTypes = new TreeSet<>(); 105 private static Map<String, Set<String>> dmhMethods = new TreeMap<>(); 106 107 private static void clear() { 108 speciesTypes.clear(); 109 invokerTypes.clear(); 110 callSiteTypes.clear(); 111 dmhMethods.clear(); 112 } 113 114 private static void addSpeciesType(String type) { 115 speciesTypes.add(expandSignature(type)); 116 } 117 118 private static void addInvokerType(String methodType) { 119 validateMethodType(methodType); 120 invokerTypes.add(methodType); 121 } 122 123 private static void addCallSiteType(String csType) { 124 validateMethodType(csType); 125 callSiteTypes.add(csType); 126 } 127 128 private static void readTraceConfig(Stream<String> lines) { 129 lines.map(line -> line.split(" ")) 130 .forEach(parts -> { 131 switch (parts[0]) { 132 case "[SPECIES_RESOLVE]": 133 // Allow for new types of species data classes being resolved here 134 if (parts.length == 3 && parts[1].startsWith("java.lang.invoke.BoundMethodHandle$Species_")) { 135 String species = parts[1].substring("java.lang.invoke.BoundMethodHandle$Species_".length()); 136 if (!"L".equals(species)) { 137 addSpeciesType(species); 138 } 139 } 140 break; 141 case "[LF_RESOLVE]": 142 String methodType = parts[3]; 143 if (parts[1].equals(INVOKERS_HOLDER_NAME)) { 144 if ("linkToTargetMethod".equals(parts[2]) || 145 "linkToCallSite".equals(parts[2])) { 146 addCallSiteType(methodType); 147 } else { 148 addInvokerType(methodType); 149 } 150 } else if (parts[1].contains("DirectMethodHandle")) { 151 String dmh = parts[2]; 152 // ignore getObject etc for now (generated 153 // by default) 154 if (DMH_METHOD_TYPE_MAP.containsKey(dmh)) { 155 addDMHMethodType(dmh, methodType); 156 } 157 } 158 break; 159 default: break; // ignore 160 } 161 }); 162 } 163 164 /** 165 * called from vm to create generated lambda-form class 166 * The input is as TRACE_RESOLVE 167 * @return @code { Object[] } if holder classes can be generated. 168 * @param lines the output line string from @cdsTraceResolve 169 */ 170 static Object[] generateMethodHandleHolderClasses(String[] lines) { 171 try { 172 Map<String, byte[]> result = generateMHHolderClasses(lines); 173 clear(); 174 if (result == null) { 175 return null; 176 } 177 int size = result.size(); 178 Object[] ret_array = new Object[size * 2]; 179 int i = 0; 180 for (Map.Entry<String, byte[]> entry : result.entrySet()) { 181 ret_array[i++] = entry.getKey(); 182 ret_array[i++] = entry.getValue(); 183 }; 184 return ret_array; 185 } catch (Exception e) { 186 return null; 187 } 188 } 189 190 /* return a map of <class with module pkg, class bytes? */ 191 static Map<String, byte[]> generateMHHolderClasses(String[] lines) throws InvokerGenerateBytesException { 192 if (lines == null || lines.length == 0) { 193 return null; 194 } 195 readTraceConfig(Arrays.stream(lines)); 196 int count = 0; 197 for (Set<String> entry : dmhMethods.values()) { 198 count += entry.size(); 199 } 200 MethodType[] directMethodTypes = new MethodType[count]; 201 int[] dmhTypes = new int[count]; 202 int index = 0; 203 for (Map.Entry<String, Set<String>> entry : dmhMethods.entrySet()) { 204 String dmhType = entry.getKey(); 205 for (String type : entry.getValue()) { 206 // The DMH type to actually ask for is retrieved by removing 207 // the first argument, which needs to be of Object.class 208 MethodType mt = asMethodType(type); 209 if (mt.parameterCount() < 1 || 210 mt.parameterType(0) != Object.class) { 211 throw new InvokerGenerateBytesException( 212 "DMH type parameter must start with L: " + dmhType + " " + type); 213 } 214 215 // Adapt the method type of the LF to retrieve 216 directMethodTypes[index] = mt.dropParameterTypes(0, 1); 217 218 // invokeVirtual and invokeInterface must have a leading Object 219 // parameter, i.e., the receiver 220 dmhTypes[index] = DMH_METHOD_TYPE_MAP.get(dmhType); 221 if (dmhTypes[index] == DMH_INVOKE_INTERFACE_TYPE || 222 dmhTypes[index] == DMH_INVOKE_VIRTUAL_TYPE) { 223 if (mt.parameterCount() < 2 || 224 mt.parameterType(1) != Object.class) { 225 throw new InvokerGenerateBytesException( 226 "DMH type parameter must start with LL: " + dmhType + " " + type); 227 } 228 } 229 index++; 230 } 231 } 232 233 // The invoker type to ask for is retrieved by removing the first 234 // and the last argument, which needs to be of Object.class 235 MethodType[] invokerMethodTypes = new MethodType[invokerTypes.size()]; 236 int i = 0; 237 for (String invokerType : invokerTypes) { 238 MethodType mt = asMethodType(invokerType); 239 final int lastParam = mt.parameterCount() - 1; 240 if (mt.parameterCount() < 2 || 241 mt.parameterType(0) != Object.class || 242 mt.parameterType(lastParam) != Object.class) { 243 throw new InvokerGenerateBytesException( 244 "Invoker type parameter must start and end with Object: " + invokerType); 245 } 246 mt = mt.dropParameterTypes(lastParam, lastParam + 1); 247 invokerMethodTypes[i] = mt.dropParameterTypes(0, 1); 248 i++; 249 } 250 251 // The callSite type to ask for is retrieved by removing the last 252 // argument, which needs to be of Object.class 253 MethodType[] callSiteMethodTypes = new MethodType[callSiteTypes.size()]; 254 i = 0; 255 for (String callSiteType : callSiteTypes) { 256 MethodType mt = asMethodType(callSiteType); 257 final int lastParam = mt.parameterCount() - 1; 258 if (mt.parameterCount() < 1 || 259 mt.parameterType(lastParam) != Object.class) { 260 throw new InvokerGenerateBytesException( 261 "CallSite type parameter must end with Object: " + callSiteType); 262 } 263 callSiteMethodTypes[i] = mt.dropParameterTypes(lastParam, lastParam + 1); 264 i++; 265 } 266 Map<String, byte[]> result = new HashMap<String, byte[]>(); 267 268 byte[] res = GenerateJLIClassesHelper.generateDirectMethodHandleHolderClassBytes( 269 DIRECT_HOLDER, directMethodTypes, dmhTypes); 270 result.put(DIRECT_METHOD_HOLDER_ENTRY, res); 271 272 res = GenerateJLIClassesHelper.generateDelegatingMethodHandleHolderClassBytes( 273 DELEGATING_HOLDER, directMethodTypes); 274 result.put(DELEGATING_METHOD_HOLDER_ENTRY, res); 275 276 277 res = GenerateJLIClassesHelper.generateInvokersHolderClassBytes(INVOKERS_HOLDER_INTERNAL_NAME, 278 invokerMethodTypes, callSiteMethodTypes); 279 result.put(INVOKERS_HOLDER_ENTRY, res); 280 281 res = GenerateJLIClassesHelper.generateBasicFormsClassBytes(BASIC_FORMS_HOLDER); 282 result.put(BASIC_FORMS_HOLDER_ENTRY, res); 283 284 speciesTypes.forEach(types -> { 285 Map.Entry<String, byte[]> entry = GenerateJLIClassesHelper.generateConcreteBMHClassBytes(types); 286 String className = entry.getKey(); 287 String key = "/java.base/" + className + ".class"; 288 byte[] value = entry.getValue(); 289 result.put(key, value); 290 }); 291 292 return result; 293 } 294 295 private static final String DIRECT_METHOD_HOLDER_ENTRY = 296 "/java.base/" + DIRECT_HOLDER + ".class"; 297 private static final String DELEGATING_METHOD_HOLDER_ENTRY = 298 "/java.base/" + DELEGATING_HOLDER + ".class"; 299 private static final String BASIC_FORMS_HOLDER_ENTRY = 300 "/java.base/" + BASIC_FORMS_HOLDER + ".class"; 301 private static final String INVOKERS_HOLDER_ENTRY = 302 "/java.base/" + INVOKERS_HOLDER_INTERNAL_NAME + ".class"; 303 304 private static MethodType asMethodType(String basicSignatureString) { 305 String[] parts = basicSignatureString.split("_"); 306 assert(parts.length == 2); 307 assert(parts[1].length() == 1); 308 String parameters = expandSignature(parts[0]); 309 Class<?> rtype = simpleType(parts[1].charAt(0)); 310 if (parameters.isEmpty()) { 311 return MethodType.methodType(rtype); 312 } else { 313 Class<?>[] ptypes = new Class<?>[parameters.length()]; 314 for (int i = 0; i < ptypes.length; i++) { 315 ptypes[i] = simpleType(parameters.charAt(i)); 316 } 317 return MethodType.methodType(rtype, ptypes); 318 } 319 } 320 321 private static void addDMHMethodType(String dmh, String methodType) { 322 validateMethodType(methodType); 323 Set<String> methodTypes = dmhMethods.get(dmh); 324 if (methodTypes == null) { 325 methodTypes = new TreeSet<>(); 326 dmhMethods.put(dmh, methodTypes); 327 } 328 methodTypes.add(methodType); 329 } 330 331 private static void validateMethodType(String type) { 332 String[] typeParts = type.split("_"); 333 // check return type (second part) 334 if (typeParts.length != 2 || typeParts[1].length() != 1 335 || "LJIFDV".indexOf(typeParts[1].charAt(0)) == -1) { 336 throw new InvokerGenerateBytesException( 337 "Method type signature must be of form [LJIFD]*_[LJIFDV]"); 338 } 339 // expand and check arguments (first part) 340 expandSignature(typeParts[0]); 341 } 342 343 // Convert LL -> LL, L3 -> LLL 344 private static String expandSignature(String signature) { 345 StringBuilder sb = new StringBuilder(); 346 char last = 'X'; 347 int count = 0; 348 for (int i = 0; i < signature.length(); i++) { 349 char c = signature.charAt(i); 350 if (c >= '0' && c <= '9') { 351 count *= 10; 352 count += (c - '0'); 353 } else { 354 requireBasicType(c); 355 for (int j = 1; j < count; j++) { 356 sb.append(last); 357 } 358 sb.append(c); 359 last = c; 360 count = 0; 361 } 362 } 363 364 // ended with a number, e.g., "L2": append last char count - 1 times 365 if (count > 1) { 366 requireBasicType(last); 367 for (int j = 1; j < count; j++) { 368 sb.append(last); 369 } 370 } 371 return sb.toString(); 372 } 373 374 private static void requireBasicType(char c) { 375 if ("LIJFD".indexOf(c) < 0) { 376 throw new InvokerGenerateBytesException( 377 "Character " + c + " must correspond to a basic field type: LIJFD"); 378 } 379 } 380 381 private static Class<?> simpleType(char c) { 382 switch (c) { 383 case 'F': 384 return float.class; 385 case 'D': 386 return double.class; 387 case 'I': 388 return int.class; 389 case 'L': 390 return Object.class; 391 case 'J': 392 return long.class; 393 case 'V': 394 return void.class; 395 case 'Z': 396 case 'B': 397 case 'S': 398 case 'C': 399 throw new IllegalArgumentException("Not a valid primitive: " + c + 400 " (use I instead)"); 401 default: 402 throw new IllegalArgumentException("Not a primitive: " + c); 403 } 404 } 405 }