1 /* 2 * Copyright (c) 2016, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package jdk.tools.jlink.internal.plugins; 26 27 import java.io.BufferedReader; 28 import java.io.File; 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.io.InputStreamReader; 32 import java.lang.invoke.MethodType; 33 import java.lang.module.ModuleDescriptor; 34 import java.nio.file.Files; 35 import java.util.EnumSet; 36 import java.util.Map; 37 import java.util.Optional; 38 import java.util.Set; 39 import java.util.TreeMap; 40 import java.util.TreeSet; 41 import java.util.stream.Collectors; 42 import java.util.stream.Stream; 43 import jdk.internal.misc.SharedSecrets; 44 import jdk.internal.misc.JavaLangInvokeAccess; 45 import jdk.tools.jlink.plugin.ResourcePoolEntry; 46 import jdk.tools.jlink.plugin.PluginException; 47 import jdk.tools.jlink.plugin.ResourcePool; 48 import jdk.tools.jlink.plugin.ResourcePoolBuilder; 49 import jdk.tools.jlink.plugin.Plugin; 50 51 /** 52 * Plugin to generate java.lang.invoke classes. 53 */ 54 public final class GenerateJLIClassesPlugin implements Plugin { 55 56 private static final String NAME = "generate-jli-classes"; 57 58 private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME); 59 60 private static final String DEFAULT_TRACE_FILE = "default_jli_trace.txt"; 61 62 private static final String DIRECT_HOLDER = "java/lang/invoke/DirectMethodHandle$Holder"; 63 private static final String DMH_INVOKE_VIRTUAL = "invokeVirtual"; 64 private static final String DMH_INVOKE_STATIC = "invokeStatic"; 65 private static final String DMH_INVOKE_SPECIAL = "invokeSpecial"; 66 private static final String DMH_NEW_INVOKE_SPECIAL = "newInvokeSpecial"; 67 private static final String DMH_INVOKE_INTERFACE = "invokeInterface"; 68 private static final String DMH_INVOKE_STATIC_INIT = "invokeStaticInit"; 69 private static final String DMH_INVOKE_SPECIAL_IFC = "invokeSpecialIFC"; 70 71 private static final String DELEGATING_HOLDER = "java/lang/invoke/DelegatingMethodHandle$Holder"; 72 private static final String BASIC_FORMS_HOLDER = "java/lang/invoke/LambdaForm$Holder"; 73 74 private static final String INVOKERS_HOLDER_NAME = "java.lang.invoke.Invokers$Holder"; 75 private static final String INVOKERS_HOLDER_INTERNAL_NAME = INVOKERS_HOLDER_NAME.replace('.', '/'); 76 77 private static final JavaLangInvokeAccess JLIA 78 = SharedSecrets.getJavaLangInvokeAccess(); 79 80 Set<String> speciesTypes = Set.of(); 81 82 Set<String> invokerTypes = Set.of(); 83 84 Set<String> callSiteTypes = Set.of(); 85 86 Map<String, Set<String>> dmhMethods = Map.of(); 87 88 String mainArgument; 89 90 public GenerateJLIClassesPlugin() { 91 } 92 93 @Override 94 public String getName() { 95 return NAME; 96 } 97 98 @Override 99 public String getDescription() { 100 return DESCRIPTION; 101 } 102 103 @Override 104 public Set<State> getState() { 105 return EnumSet.of(State.AUTO_ENABLED, State.FUNCTIONAL); 106 } 107 108 @Override 109 public boolean hasArguments() { 110 return true; 111 } 112 113 @Override 114 public String getArgumentsDescription() { 115 return PluginsResourceBundle.getArgument(NAME); 116 } 117 118 /** 119 * @return the default Species forms to generate. 120 * 121 * This list was derived from running a small startup benchmark. 122 * A better long-term solution is to define and run a set of quick 123 * generators and extracting this list as a step in the build process. 124 */ 125 public static Set<String> defaultSpecies() { 126 return Set.of("LL", "L3", "L4", "L5", "L6", "L7", "L7I", 127 "L7II", "L7IIL", "L8", "L9", "L10", "L10I", "L10II", "L10IIL", 128 "L11", "L12", "L13", "LI", "D", "L3I", "LIL", "LLI", "LLIL", 129 "LILL", "I", "LLILL"); 130 } 131 132 /** 133 * @return the default invoker forms to generate. 134 */ 135 private static Set<String> defaultInvokers() { 136 return Set.of("LL_L", "LL_I", "LLLL_L", "LLLL_I", "LLIL_L", "LLIL_I", 137 "L6_L"); 138 } 139 140 /** 141 * @return the default call site forms to generate (linkToTargetMethod). 142 */ 143 private static Set<String> defaultCallSiteTypes() { 144 return Set.of("L5_L", "LIL3_L", "ILL_L"); 145 } 146 147 /** 148 * @return the list of default DirectMethodHandle methods to generate. 149 */ 150 private static Map<String, Set<String>> defaultDMHMethods() { 151 return Map.of( 152 DMH_INVOKE_INTERFACE, Set.of("LL_L", "L3_I", "L3_V"), 153 DMH_INVOKE_VIRTUAL, Set.of("L_L", "LL_L", "LLI_I", "L3_V"), 154 DMH_INVOKE_SPECIAL, Set.of("LL_I", "LL_L", "LLF_L", "LLD_L", 155 "L3_I", "L3_L", "L4_L", "L5_L", "L6_L", "L7_L", "L8_L", 156 "LLI_I", "LLI_L", "LLIL_I", "LLIL_L", "LLII_I", "LLII_L", 157 "L3I_L", "L3I_I", "L3ILL_L", "LLILI_I", "LLIIL_L", "LLIILL_L", 158 "LLIILL_I", "LLIIL_I", "LLILIL_I", "LLILILL_I", "LLILII_I", 159 "LLI3_I", "LLI3L_I", "LLI3LL_I", "LLI3_L", "LLI4_I"), 160 DMH_INVOKE_STATIC, Set.of("LII_I", "LIL_I", "LILIL_I", "LILII_I", 161 "L_I", "L_L", "L_V", "LD_L", "LF_L", "LI_I", "LII_L", "LLI_L", 162 "LL_I", "LLILL_L", "LLIL3_L", "LL_V", "LL_L", "L3_I", "L3_L", 163 "L3_V", "L4_I", "L4_L", "L5_L", "L6_L", "L7_L", "L8_L", "L9_L", 164 "L10_L", "L10I_L", "L10II_L", "L10IIL_L", "L11_L", "L12_L", 165 "L13_L", "L14_L", "L14I_L", "L14II_L"), 166 DMH_NEW_INVOKE_SPECIAL, Set.of("L_L", "LL_L"), 167 DMH_INVOKE_SPECIAL_IFC, Set.of("L5_I") 168 ); 169 } 170 171 // Map from DirectMethodHandle method type to internal ID, matching values 172 // of the corresponding constants in java.lang.invoke.MethodTypeForm 173 private static final Map<String, Integer> DMH_METHOD_TYPE_MAP = 174 Map.of( 175 DMH_INVOKE_VIRTUAL, 0, 176 DMH_INVOKE_STATIC, 1, 177 DMH_INVOKE_SPECIAL, 2, 178 DMH_NEW_INVOKE_SPECIAL, 3, 179 DMH_INVOKE_INTERFACE, 4, 180 DMH_INVOKE_STATIC_INIT, 5, 181 DMH_INVOKE_SPECIAL_IFC, 20 182 ); 183 184 @Override 185 public void configure(Map<String, String> config) { 186 mainArgument = config.get(NAME); 187 } 188 189 public void initialize(ResourcePool in) { 190 // Start with the default configuration 191 speciesTypes = defaultSpecies().stream() 192 .map(type -> expandSignature(type)) 193 .collect(Collectors.toSet()); 194 195 invokerTypes = defaultInvokers(); 196 validateMethodTypes(invokerTypes); 197 198 callSiteTypes = defaultCallSiteTypes(); 199 200 dmhMethods = defaultDMHMethods(); 201 for (Set<String> dmhMethodTypes : dmhMethods.values()) { 202 validateMethodTypes(dmhMethodTypes); 203 } 204 205 // Extend the default configuration with the contents in the supplied 206 // input file - if none was supplied we look for the default file 207 if (mainArgument == null || !mainArgument.startsWith("@")) { 208 try (InputStream traceFile = 209 this.getClass().getResourceAsStream(DEFAULT_TRACE_FILE)) { 210 if (traceFile != null) { 211 readTraceConfig( 212 new BufferedReader( 213 new InputStreamReader(traceFile)).lines()); 214 } 215 } catch (Exception e) { 216 throw new PluginException("Couldn't read " + DEFAULT_TRACE_FILE, e); 217 } 218 } else { 219 File file = new File(mainArgument.substring(1)); 220 if (file.exists()) { 221 readTraceConfig(fileLines(file)); 222 } 223 } 224 } 225 226 private void readTraceConfig(Stream<String> lines) { 227 // Use TreeSet/TreeMap to keep things sorted in a deterministic 228 // order to avoid scrambling the layout on small changes and to 229 // ease finding methods in the generated code 230 speciesTypes = new TreeSet<>(speciesTypes); 231 invokerTypes = new TreeSet<>(invokerTypes); 232 callSiteTypes = new TreeSet<>(callSiteTypes); 233 234 TreeMap<String, Set<String>> newDMHMethods = new TreeMap<>(); 235 for (Map.Entry<String, Set<String>> entry : dmhMethods.entrySet()) { 236 newDMHMethods.put(entry.getKey(), new TreeSet<>(entry.getValue())); 237 } 238 dmhMethods = newDMHMethods; 239 lines.map(line -> line.split(" ")) 240 .forEach(parts -> { 241 switch (parts[0]) { 242 case "[SPECIES_RESOLVE]": 243 // Allow for new types of species data classes being resolved here 244 if (parts.length == 3 && parts[1].startsWith("java.lang.invoke.BoundMethodHandle$Species_")) { 245 String species = parts[1].substring("java.lang.invoke.BoundMethodHandle$Species_".length()); 246 if (!"L".equals(species)) { 247 speciesTypes.add(expandSignature(species)); 248 } 249 } 250 break; 251 case "[LF_RESOLVE]": 252 String methodType = parts[3]; 253 validateMethodType(methodType); 254 if (parts[1].equals(INVOKERS_HOLDER_NAME)) { 255 if ("linkToTargetMethod".equals(parts[2]) || 256 "linkToCallSite".equals(parts[2])) { 257 callSiteTypes.add(methodType); 258 } else { 259 invokerTypes.add(methodType); 260 } 261 } else if (parts[1].contains("DirectMethodHandle")) { 262 String dmh = parts[2]; 263 // ignore getObject etc for now (generated 264 // by default) 265 if (DMH_METHOD_TYPE_MAP.containsKey(dmh)) { 266 addDMHMethodType(dmh, methodType); 267 } 268 } 269 break; 270 default: break; // ignore 271 } 272 }); 273 } 274 275 private void addDMHMethodType(String dmh, String methodType) { 276 validateMethodType(methodType); 277 Set<String> methodTypes = dmhMethods.get(dmh); 278 if (methodTypes == null) { 279 methodTypes = new TreeSet<>(); 280 dmhMethods.put(dmh, methodTypes); 281 } 282 methodTypes.add(methodType); 283 } 284 285 private Stream<String> fileLines(File file) { 286 try { 287 return Files.lines(file.toPath()); 288 } catch (IOException io) { 289 throw new PluginException("Couldn't read file"); 290 } 291 } 292 293 private void validateMethodTypes(Set<String> dmhMethodTypes) { 294 for (String type : dmhMethodTypes) { 295 validateMethodType(type); 296 } 297 } 298 299 private void validateMethodType(String type) { 300 String[] typeParts = type.split("_"); 301 // check return type (second part) 302 if (typeParts.length != 2 || typeParts[1].length() != 1 303 || "LJIFDV".indexOf(typeParts[1].charAt(0)) == -1) { 304 throw new PluginException( 305 "Method type signature must be of form [LJIFD]*_[LJIFDV]"); 306 } 307 // expand and check arguments (first part) 308 expandSignature(typeParts[0]); 309 } 310 311 private static void requireBasicType(char c) { 312 if ("LIJFD".indexOf(c) < 0) { 313 throw new PluginException( 314 "Character " + c + " must correspond to a basic field type: LIJFD"); 315 } 316 } 317 318 @Override 319 public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) { 320 initialize(in); 321 // Copy all but DMH_ENTRY to out 322 in.transformAndCopy(entry -> { 323 // filter out placeholder entries 324 String path = entry.path(); 325 if (path.equals(DIRECT_METHOD_HOLDER_ENTRY) || 326 path.equals(DELEGATING_METHOD_HOLDER_ENTRY) || 327 path.equals(INVOKERS_HOLDER_ENTRY) || 328 path.equals(BASIC_FORMS_HOLDER_ENTRY)) { 329 return null; 330 } else { 331 return entry; 332 } 333 }, out); 334 335 // Generate BMH Species classes 336 speciesTypes.forEach(types -> generateBMHClass(types, out)); 337 338 // Generate LambdaForm Holder classes 339 generateHolderClasses(out); 340 341 // Let it go 342 speciesTypes = null; 343 invokerTypes = null; 344 dmhMethods = null; 345 346 return out.build(); 347 } 348 349 @SuppressWarnings("unchecked") 350 private void generateBMHClass(String types, ResourcePoolBuilder out) { 351 try { 352 // Generate class 353 Map.Entry<String, byte[]> result = 354 JLIA.generateConcreteBMHClassBytes(types); 355 String className = result.getKey(); 356 byte[] bytes = result.getValue(); 357 358 // Add class to pool 359 ResourcePoolEntry ndata = ResourcePoolEntry.create( 360 "/java.base/" + className + ".class", 361 bytes); 362 out.add(ndata); 363 } catch (Exception ex) { 364 throw new PluginException(ex); 365 } 366 } 367 368 private void generateHolderClasses(ResourcePoolBuilder out) { 369 int count = 0; 370 for (Set<String> entry : dmhMethods.values()) { 371 count += entry.size(); 372 } 373 MethodType[] directMethodTypes = new MethodType[count]; 374 int[] dmhTypes = new int[count]; 375 int index = 0; 376 for (Map.Entry<String, Set<String>> entry : dmhMethods.entrySet()) { 377 String dmhType = entry.getKey(); 378 for (String type : entry.getValue()) { 379 // The DMH type to actually ask for is retrieved by removing 380 // the first argument, which needs to be of Object.class 381 MethodType mt = asMethodType(type); 382 if (mt.parameterCount() < 1 || 383 mt.parameterType(0) != Object.class) { 384 throw new PluginException( 385 "DMH type parameter must start with L"); 386 } 387 directMethodTypes[index] = mt.dropParameterTypes(0, 1); 388 dmhTypes[index] = DMH_METHOD_TYPE_MAP.get(dmhType); 389 index++; 390 } 391 } 392 393 // The invoker type to ask for is retrieved by removing the first 394 // and the last argument, which needs to be of Object.class 395 MethodType[] invokerMethodTypes = new MethodType[this.invokerTypes.size()]; 396 int i = 0; 397 for (String invokerType : invokerTypes) { 398 MethodType mt = asMethodType(invokerType); 399 final int lastParam = mt.parameterCount() - 1; 400 if (mt.parameterCount() < 2 || 401 mt.parameterType(0) != Object.class || 402 mt.parameterType(lastParam) != Object.class) { 403 throw new PluginException( 404 "Invoker type parameter must start and end with Object: " + invokerType); 405 } 406 mt = mt.dropParameterTypes(lastParam, lastParam + 1); 407 invokerMethodTypes[i] = mt.dropParameterTypes(0, 1); 408 i++; 409 } 410 411 // The callSite type to ask for is retrieved by removing the last 412 // argument, which needs to be of Object.class 413 MethodType[] callSiteMethodTypes = new MethodType[this.callSiteTypes.size()]; 414 i = 0; 415 for (String callSiteType : callSiteTypes) { 416 MethodType mt = asMethodType(callSiteType); 417 final int lastParam = mt.parameterCount() - 1; 418 if (mt.parameterCount() < 1 || 419 mt.parameterType(lastParam) != Object.class) { 420 throw new PluginException( 421 "CallSite type parameter must end with Object: " + callSiteType); 422 } 423 callSiteMethodTypes[i] = mt.dropParameterTypes(lastParam, lastParam + 1); 424 i++; 425 } 426 try { 427 byte[] bytes = JLIA.generateDirectMethodHandleHolderClassBytes( 428 DIRECT_HOLDER, directMethodTypes, dmhTypes); 429 ResourcePoolEntry ndata = ResourcePoolEntry 430 .create(DIRECT_METHOD_HOLDER_ENTRY, bytes); 431 out.add(ndata); 432 433 bytes = JLIA.generateDelegatingMethodHandleHolderClassBytes( 434 DELEGATING_HOLDER, directMethodTypes); 435 ndata = ResourcePoolEntry.create(DELEGATING_METHOD_HOLDER_ENTRY, bytes); 436 out.add(ndata); 437 438 bytes = JLIA.generateInvokersHolderClassBytes(INVOKERS_HOLDER_INTERNAL_NAME, 439 invokerMethodTypes, callSiteMethodTypes); 440 ndata = ResourcePoolEntry.create(INVOKERS_HOLDER_ENTRY, bytes); 441 out.add(ndata); 442 443 bytes = JLIA.generateBasicFormsClassBytes(BASIC_FORMS_HOLDER); 444 ndata = ResourcePoolEntry.create(BASIC_FORMS_HOLDER_ENTRY, bytes); 445 out.add(ndata); 446 } catch (Exception ex) { 447 throw new PluginException(ex); 448 } 449 } 450 private static final String DIRECT_METHOD_HOLDER_ENTRY = 451 "/java.base/" + DIRECT_HOLDER + ".class"; 452 private static final String DELEGATING_METHOD_HOLDER_ENTRY = 453 "/java.base/" + DELEGATING_HOLDER + ".class"; 454 private static final String BASIC_FORMS_HOLDER_ENTRY = 455 "/java.base/" + BASIC_FORMS_HOLDER + ".class"; 456 private static final String INVOKERS_HOLDER_ENTRY = 457 "/java.base/" + INVOKERS_HOLDER_INTERNAL_NAME + ".class"; 458 459 // Convert LL -> LL, L3 -> LLL 460 public static String expandSignature(String signature) { 461 StringBuilder sb = new StringBuilder(); 462 char last = 'X'; 463 int count = 0; 464 for (int i = 0; i < signature.length(); i++) { 465 char c = signature.charAt(i); 466 if (c >= '0' && c <= '9') { 467 count *= 10; 468 count += (c - '0'); 469 } else { 470 requireBasicType(c); 471 for (int j = 1; j < count; j++) { 472 sb.append(last); 473 } 474 sb.append(c); 475 last = c; 476 count = 0; 477 } 478 } 479 480 // ended with a number, e.g., "L2": append last char count - 1 times 481 if (count > 1) { 482 requireBasicType(last); 483 for (int j = 1; j < count; j++) { 484 sb.append(last); 485 } 486 } 487 return sb.toString(); 488 } 489 490 private static MethodType asMethodType(String basicSignatureString) { 491 String[] parts = basicSignatureString.split("_"); 492 assert(parts.length == 2); 493 assert(parts[1].length() == 1); 494 String parameters = expandSignature(parts[0]); 495 Class<?> rtype = simpleType(parts[1].charAt(0)); 496 if (parameters.isEmpty()) { 497 return MethodType.methodType(rtype); 498 } else { 499 Class<?>[] ptypes = new Class<?>[parameters.length()]; 500 for (int i = 0; i < ptypes.length; i++) { 501 ptypes[i] = simpleType(parameters.charAt(i)); 502 } 503 return MethodType.methodType(rtype, ptypes); 504 } 505 } 506 507 private static Class<?> simpleType(char c) { 508 switch (c) { 509 case 'F': 510 return float.class; 511 case 'D': 512 return double.class; 513 case 'I': 514 return int.class; 515 case 'L': 516 return Object.class; 517 case 'J': 518 return long.class; 519 case 'V': 520 return void.class; 521 case 'Z': 522 case 'B': 523 case 'S': 524 case 'C': 525 throw new IllegalArgumentException("Not a valid primitive: " + c + 526 " (use I instead)"); 527 default: 528 throw new IllegalArgumentException("Not a primitive: " + c); 529 } 530 } 531 }