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