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