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