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