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