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 ); 152 } 153 154 // Map from DirectMethodHandle method type to internal ID 155 private static final Map<String, Integer> DMH_METHOD_TYPE_MAP = 156 Map.of( 157 DMH_INVOKE_VIRTUAL, 0, 158 DMH_INVOKE_STATIC, 1, 159 DMH_INVOKE_SPECIAL, 2, 160 DMH_NEW_INVOKE_SPECIAL, 3, 161 DMH_INVOKE_INTERFACE, 4, 162 DMH_INVOKE_STATIC_INIT, 5 163 ); 164 165 @Override 166 public void configure(Map<String, String> config) { 167 mainArgument = config.get(NAME); 168 } 169 170 public void initialize(ResourcePool in) { 171 // Start with the default configuration 172 speciesTypes = defaultSpecies().stream() 173 .map(type -> expandSignature(type)) 174 .collect(Collectors.toSet()); 175 176 invokerTypes = defaultInvokers(); 177 validateMethodTypes(invokerTypes); 178 179 dmhMethods = defaultDMHMethods(); 180 for (Set<String> dmhMethodTypes : dmhMethods.values()) { 181 validateMethodTypes(dmhMethodTypes); 182 } 183 184 // Extend the default configuration with the contents in the supplied 185 // input file - if none was supplied we look for the default file 186 if (mainArgument == null || !mainArgument.startsWith("@")) { 187 try (InputStream traceFile = 188 this.getClass().getResourceAsStream(DEFAULT_TRACE_FILE)) { 189 if (traceFile != null) { 190 readTraceConfig( 191 new BufferedReader( 192 new InputStreamReader(traceFile)).lines()); 193 } 194 } catch (Exception e) { 195 throw new PluginException("Couldn't read " + DEFAULT_TRACE_FILE, e); 196 } 197 } else { 198 File file = new File(mainArgument.substring(1)); 199 if (file.exists()) { 200 readTraceConfig(fileLines(file)); 201 } 202 } 203 } 204 205 private void readTraceConfig(Stream<String> lines) { 206 // Use TreeSet/TreeMap to keep things sorted in a deterministic 207 // order to avoid scrambling the layout on small changes and to 208 // ease finding methods in the generated code 209 speciesTypes = new TreeSet<>(speciesTypes); 210 invokerTypes = new TreeSet<>(invokerTypes); 211 TreeMap<String, Set<String>> newDMHMethods = new TreeMap<>(); 212 for (Map.Entry<String, Set<String>> entry : dmhMethods.entrySet()) { 213 newDMHMethods.put(entry.getKey(), new TreeSet<>(entry.getValue())); 214 } 215 dmhMethods = newDMHMethods; 216 lines.map(line -> line.split(" ")) 217 .forEach(parts -> { 218 switch (parts[0]) { 219 case "[SPECIES_RESOLVE]": 220 // Allow for new types of species data classes being resolved here 221 if (parts.length == 3 && parts[1].startsWith("java.lang.invoke.BoundMethodHandle$Species_")) { 222 String species = parts[1].substring("java.lang.invoke.BoundMethodHandle$Species_".length()); 223 if (!"L".equals(species)) { 224 speciesTypes.add(expandSignature(species)); 225 } 226 } 227 break; 228 case "[LF_RESOLVE]": 229 String methodType = parts[3]; 230 validateMethodType(methodType); 231 if (parts[1].contains("Invokers")) { 232 invokerTypes.add(methodType); 233 } else if (parts[1].contains("DirectMethodHandle")) { 234 String dmh = parts[2]; 235 // ignore getObject etc for now (generated 236 // by default) 237 if (DMH_METHOD_TYPE_MAP.containsKey(dmh)) { 238 addDMHMethodType(dmh, methodType); 239 } 240 } 241 break; 242 default: break; // ignore 243 } 244 }); 245 } 246 247 private void addDMHMethodType(String dmh, String methodType) { 248 validateMethodType(methodType); 249 Set<String> methodTypes = dmhMethods.get(dmh); 250 if (methodTypes == null) { 251 methodTypes = new TreeSet<>(); 252 dmhMethods.put(dmh, methodTypes); 253 } 254 methodTypes.add(methodType); 255 } 256 257 private Stream<String> fileLines(File file) { 258 try { 259 return Files.lines(file.toPath()); 260 } catch (IOException io) { 261 throw new PluginException("Couldn't read file"); 262 } 263 } 264 265 private void validateMethodTypes(Set<String> dmhMethodTypes) { 266 for (String type : dmhMethodTypes) { 267 validateMethodType(type); 268 } 269 } 270 271 private void validateMethodType(String type) { 272 String[] typeParts = type.split("_"); 273 // check return type (second part) 274 if (typeParts.length != 2 || typeParts[1].length() != 1 275 || "LJIFDV".indexOf(typeParts[1].charAt(0)) == -1) { 276 throw new PluginException( 277 "Method type signature must be of form [LJIFD]*_[LJIFDV]"); 278 } 279 // expand and check arguments (first part) 280 expandSignature(typeParts[0]); 281 } 282 283 private static void requireBasicType(char c) { 284 if ("LIJFD".indexOf(c) < 0) { 285 throw new PluginException( 286 "Character " + c + " must correspond to a basic field type: LIJFD"); 287 } 288 } 289 290 @Override 291 public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) { 292 initialize(in); 293 // Copy all but DMH_ENTRY to out 294 in.transformAndCopy(entry -> { 295 // filter out placeholder entries 296 if (entry.path().equals(DIRECT_METHOD_HOLDER_ENTRY) || 297 entry.path().equals(DELEGATING_METHOD_HOLDER_ENTRY) || 298 entry.path().equals(INVOKERS_HOLDER_ENTRY) || 299 entry.path().equals(BASIC_FORMS_HOLDER_ENTRY)) { 300 return null; 301 } else { 302 return entry; 303 } 304 }, out); 305 306 // Generate BMH Species classes 307 speciesTypes.forEach(types -> generateBMHClass(types, out)); 308 309 // Generate LambdaForm Holder classes 310 generateHolderClasses(out); 311 312 // Let it go 313 speciesTypes = null; 314 invokerTypes = null; 315 dmhMethods = null; 316 317 return out.build(); 318 } 319 320 @SuppressWarnings("unchecked") 321 private void generateBMHClass(String types, ResourcePoolBuilder out) { 322 try { 323 // Generate class 324 Map.Entry<String, byte[]> result = 325 JLIA.generateConcreteBMHClassBytes(types); 326 String className = result.getKey(); 327 byte[] bytes = result.getValue(); 328 329 // Add class to pool 330 ResourcePoolEntry ndata = ResourcePoolEntry.create( 331 "/java.base/" + className + ".class", 332 bytes); 333 out.add(ndata); 334 } catch (Exception ex) { 335 throw new PluginException(ex); 336 } 337 } 338 339 private void generateHolderClasses(ResourcePoolBuilder out) { 340 int count = 0; 341 for (Set<String> entry : dmhMethods.values()) { 342 count += entry.size(); 343 } 344 MethodType[] directMethodTypes = new MethodType[count]; 345 int[] dmhTypes = new int[count]; 346 int index = 0; 347 for (Map.Entry<String, Set<String>> entry : dmhMethods.entrySet()) { 348 String dmhType = entry.getKey(); 349 for (String type : entry.getValue()) { 350 // The DMH type to actually ask for is retrieved by removing 351 // the first argument, which needs to be of Object.class 352 MethodType mt = asMethodType(type); 353 if (mt.parameterCount() < 1 || 354 mt.parameterType(0) != Object.class) { 355 throw new PluginException( 356 "DMH type parameter must start with L"); 357 } 358 directMethodTypes[index] = mt.dropParameterTypes(0, 1); 359 dmhTypes[index] = DMH_METHOD_TYPE_MAP.get(dmhType); 360 index++; 361 } 362 } 363 MethodType[] invokerMethodTypes = new MethodType[this.invokerTypes.size()]; 364 int i = 0; 365 for (String invokerType : invokerTypes) { 366 // The invoker type to ask for is retrieved by removing the first 367 // and the last argument, which needs to be of Object.class 368 MethodType mt = asMethodType(invokerType); 369 final int lastParam = mt.parameterCount() - 1; 370 if (mt.parameterCount() < 2 || 371 mt.parameterType(0) != Object.class || 372 mt.parameterType(lastParam) != Object.class) { 373 throw new PluginException( 374 "Invoker type parameter must start and end with L"); 375 } 376 mt = mt.dropParameterTypes(lastParam, lastParam + 1); 377 invokerMethodTypes[i] = mt.dropParameterTypes(0, 1); 378 i++; 379 } 380 try { 381 byte[] bytes = JLIA.generateDirectMethodHandleHolderClassBytes( 382 DIRECT_HOLDER, directMethodTypes, dmhTypes); 383 ResourcePoolEntry ndata = ResourcePoolEntry 384 .create(DIRECT_METHOD_HOLDER_ENTRY, bytes); 385 out.add(ndata); 386 387 bytes = JLIA.generateDelegatingMethodHandleHolderClassBytes( 388 DELEGATING_HOLDER, directMethodTypes); 389 ndata = ResourcePoolEntry.create(DELEGATING_METHOD_HOLDER_ENTRY, bytes); 390 out.add(ndata); 391 392 bytes = JLIA.generateInvokersHolderClassBytes(INVOKERS_HOLDER, 393 invokerMethodTypes); 394 ndata = ResourcePoolEntry.create(INVOKERS_HOLDER_ENTRY, bytes); 395 out.add(ndata); 396 397 bytes = JLIA.generateBasicFormsClassBytes(BASIC_FORMS_HOLDER); 398 ndata = ResourcePoolEntry.create(BASIC_FORMS_HOLDER_ENTRY, bytes); 399 out.add(ndata); 400 } catch (Exception ex) { 401 throw new PluginException(ex); 402 } 403 } 404 private static final String DIRECT_METHOD_HOLDER_ENTRY = 405 "/java.base/" + DIRECT_HOLDER + ".class"; 406 private static final String DELEGATING_METHOD_HOLDER_ENTRY = 407 "/java.base/" + DELEGATING_HOLDER + ".class"; 408 private static final String BASIC_FORMS_HOLDER_ENTRY = 409 "/java.base/" + BASIC_FORMS_HOLDER + ".class"; 410 private static final String INVOKERS_HOLDER_ENTRY = 411 "/java.base/" + INVOKERS_HOLDER + ".class"; 412 413 // Convert LL -> LL, L3 -> LLL 414 public static String expandSignature(String signature) { 415 StringBuilder sb = new StringBuilder(); 416 char last = 'X'; 417 int count = 0; 418 for (int i = 0; i < signature.length(); i++) { 419 char c = signature.charAt(i); 420 if (c >= '0' && c <= '9') { 421 count *= 10; 422 count += (c - '0'); 423 } else { 424 requireBasicType(c); 425 for (int j = 1; j < count; j++) { 426 sb.append(last); 427 } 428 sb.append(c); 429 last = c; 430 count = 0; 431 } 432 } 433 434 // ended with a number, e.g., "L2": append last char count - 1 times 435 if (count > 1) { 436 requireBasicType(last); 437 for (int j = 1; j < count; j++) { 438 sb.append(last); 439 } 440 } 441 return sb.toString(); 442 } 443 444 private static MethodType asMethodType(String basicSignatureString) { 445 String[] parts = basicSignatureString.split("_"); 446 assert(parts.length == 2); 447 assert(parts[1].length() == 1); 448 String parameters = expandSignature(parts[0]); 449 Class<?> rtype = simpleType(parts[1].charAt(0)); 450 if (parameters.isEmpty()) { 451 return MethodType.methodType(rtype); 452 } else { 453 Class<?>[] ptypes = new Class<?>[parameters.length()]; 454 for (int i = 0; i < ptypes.length; i++) { 455 ptypes[i] = simpleType(parameters.charAt(i)); 456 } 457 return MethodType.methodType(rtype, ptypes); 458 } 459 } 460 461 private static Class<?> simpleType(char c) { 462 switch (c) { 463 case 'F': 464 return float.class; 465 case 'D': 466 return double.class; 467 case 'I': 468 return int.class; 469 case 'L': 470 return Object.class; 471 case 'J': 472 return long.class; 473 case 'V': 474 return void.class; 475 case 'Z': 476 case 'B': 477 case 'S': 478 case 'C': 479 throw new IllegalArgumentException("Not a valid primitive: " + c + 480 " (use I instead)"); 481 default: 482 throw new IllegalArgumentException("Not a primitive: " + c); 483 } 484 } 485 }