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.lang.invoke.MethodType; 28 import java.util.Arrays; 29 import java.util.EnumSet; 30 import java.util.HashMap; 31 import java.util.List; 32 import java.util.Map; 33 import java.util.Set; 34 import java.util.stream.Collectors; 35 import jdk.internal.misc.SharedSecrets; 36 import jdk.internal.misc.JavaLangInvokeAccess; 37 import jdk.tools.jlink.plugin.ResourcePoolEntry; 38 import jdk.tools.jlink.plugin.PluginException; 39 import jdk.tools.jlink.plugin.ResourcePool; 40 import jdk.tools.jlink.plugin.ResourcePoolBuilder; 41 import jdk.tools.jlink.plugin.Plugin; 42 43 /** 44 * Plugin to generate java.lang.invoke classes. 45 */ 46 public final class GenerateJLIClassesPlugin implements Plugin { 47 48 private static final String NAME = "generate-jli-classes"; 49 50 private static final String BMH_PARAM = "bmh"; 51 52 private static final String BMH_SPECIES_PARAM = "bmh-species"; 53 54 private static final String DMH_PARAM = "dmh"; 55 56 private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME); 57 58 private static final String DIRECT_METHOD_HANDLE = "java/lang/invoke/DirectMethodHandle$Holder"; 59 private static final String DMH_INVOKE_VIRTUAL = "invokeVirtual"; 60 private static final String DMH_INVOKE_STATIC = "invokeStatic"; 61 private static final String DMH_INVOKE_SPECIAL = "invokeSpecial"; 62 private static final String DMH_NEW_INVOKE_SPECIAL = "newInvokeSpecial"; 63 private static final String DMH_INVOKE_INTERFACE = "invokeInterface"; 64 private static final String DMH_INVOKE_STATIC_INIT = "invokeStaticInit"; 65 66 private static final String DELEGATING_METHOD_HANDLE = "java/lang/invoke/DelegatingMethodHandle$Holder"; 67 68 private static final JavaLangInvokeAccess JLIA 69 = SharedSecrets.getJavaLangInvokeAccess(); 70 71 List<String> speciesTypes; 72 73 Map<String, List<String>> dmhMethods; 74 75 public GenerateJLIClassesPlugin() { 76 } 77 78 @Override 79 public String getName() { 80 return NAME; 81 } 82 83 @Override 84 public String getDescription() { 85 return DESCRIPTION; 86 } 87 88 @Override 89 public Set<State> getState() { 90 return EnumSet.of(State.AUTO_ENABLED, State.FUNCTIONAL); 91 } 92 93 @Override 94 public boolean hasArguments() { 95 return true; 96 } 97 98 @Override 99 public String getArgumentsDescription() { 100 return PluginsResourceBundle.getArgument(NAME); 101 } 102 103 /** 104 * @return the default Species forms to generate. 105 * 106 * This list was derived from running a small startup benchmark. 107 * A better long-term solution is to define and run a set of quick 108 * generators and extracting this list as a step in the build process. 109 */ 110 public static List<String> defaultSpecies() { 111 return List.of("LL", "L3", "L4", "L5", "L6", "L7", "L7I", 112 "L7II", "L7IIL", "L8", "L9", "L10", "L10I", "L10II", "L10IIL", 113 "L11", "L12", "L13", "LI", "D", "L3I", "LIL", "LLI", "LLIL", 114 "LILL", "I", "LLILL"); 115 } 116 117 /** 118 * @return the list of default DirectMethodHandle methods to generate. 119 */ 120 public static Map<String, List<String>> defaultDMHMethods() { 121 return Map.of( 122 DMH_INVOKE_VIRTUAL, List.of("_L", "L_L", "LI_I", "LL_V"), 123 DMH_INVOKE_SPECIAL, List.of("L_I", "L_L", "LF_L", "LD_L", "LL_L", 124 "L3_L", "L4_L", "L5_L", "L6_L", "L7_L", "LI_I", "LI_L", "LIL_I", 125 "LII_I", "LII_L", "LLI_L", "LLI_I", "LILI_I", "LIIL_L", 126 "LIILL_L", "LIILL_I", "LIIL_I", "LILIL_I", "LILILL_I", 127 "LILII_I", "LI3_I", "LI3L_I", "LI3LL_I", "LI3_L", "LI4_I"), 128 DMH_INVOKE_STATIC, List.of("II_I", "IL_I", "ILIL_I", "ILII_I", 129 "_I", "_L", "_V", "D_L", "F_L", "I_I", "II_L", "LI_L", 130 "L_V", "L_L", "LL_L", "L3_L", "L4_L", "L5_L", "L6_L", 131 "L7_L", "L8_L", "L9_L", "L9I_L", "L9II_L", "L9IIL_L", 132 "L10_L", "L11_L", "L12_L", "L13_L", "L13I_L", "L13II_L") 133 ); 134 } 135 136 // Map from DirectMethodHandle method type to internal ID 137 private static final Map<String, Integer> DMH_METHOD_TYPE_MAP = 138 Map.of( 139 DMH_INVOKE_VIRTUAL, 0, 140 DMH_INVOKE_STATIC, 1, 141 DMH_INVOKE_SPECIAL, 2, 142 DMH_NEW_INVOKE_SPECIAL, 3, 143 DMH_INVOKE_INTERFACE, 4, 144 DMH_INVOKE_STATIC_INIT, 5 145 ); 146 147 @Override 148 public void configure(Map<String, String> config) { 149 String mainArgument = config.get(NAME); 150 151 // Enable by default 152 boolean bmhEnabled = true; 153 boolean dmhEnabled = true; 154 if (mainArgument != null) { 155 List<String> args = Arrays.asList(mainArgument.split(",")); 156 if (!args.contains(BMH_PARAM)) { 157 bmhEnabled = false; 158 } 159 if (!args.contains(DMH_PARAM)) { 160 dmhEnabled = false; 161 } 162 } 163 164 if (!bmhEnabled) { 165 speciesTypes = List.of(); 166 } else { 167 String args = config.get(BMH_SPECIES_PARAM); 168 List<String> bmhSpecies; 169 if (args != null && !args.isEmpty()) { 170 bmhSpecies = Arrays.stream(args.split(",")) 171 .map(String::trim) 172 .filter(s -> !s.isEmpty()) 173 .collect(Collectors.toList()); 174 } else { 175 bmhSpecies = defaultSpecies(); 176 } 177 178 // Expand BMH species signatures 179 speciesTypes = bmhSpecies.stream() 180 .map(type -> expandSignature(type)) 181 .collect(Collectors.toList()); 182 } 183 184 // DirectMethodHandles 185 if (!dmhEnabled) { 186 dmhMethods = Map.of(); 187 } else { 188 dmhMethods = new HashMap<>(); 189 for (String dmhParam : DMH_METHOD_TYPE_MAP.keySet()) { 190 String args = config.get(dmhParam); 191 if (args != null && !args.isEmpty()) { 192 List<String> dmhMethodTypes = Arrays.stream(args.split(",")) 193 .map(String::trim) 194 .filter(s -> !s.isEmpty()) 195 .collect(Collectors.toList()); 196 dmhMethods.put(dmhParam, dmhMethodTypes); 197 // Validation check 198 for (String type : dmhMethodTypes) { 199 String[] typeParts = type.split("_"); 200 // check return type (second part) 201 if (typeParts.length != 2 || typeParts[1].length() != 1 202 || "LJIFDV".indexOf(typeParts[1].charAt(0)) == -1) { 203 throw new PluginException( 204 "Method type signature must be of form [LJIFD]*_[LJIFDV]"); 205 } 206 // expand and check arguments (first part) 207 expandSignature(typeParts[0]); 208 } 209 } 210 } 211 if (dmhMethods.isEmpty()) { 212 dmhMethods = defaultDMHMethods(); 213 } 214 } 215 } 216 217 private static void requireBasicType(char c) { 218 if ("LIJFD".indexOf(c) < 0) { 219 throw new PluginException( 220 "Character " + c + " must correspond to a basic field type: LIJFD"); 221 } 222 } 223 224 @Override 225 public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) { 226 // Copy all but DMH_ENTRY to out 227 in.transformAndCopy(entry -> { 228 // filter out placeholder entries 229 if (entry.path().equals(DIRECT_METHOD_HANDLE_ENTRY) || 230 entry.path().equals(DELEGATING_METHOD_HANDLE_ENTRY)) { 231 return null; 232 } else { 233 return entry; 234 } 235 }, out); 236 speciesTypes.forEach(types -> generateBMHClass(types, out)); 237 generateDMHClass(out); 238 return out.build(); 239 } 240 241 @SuppressWarnings("unchecked") 242 private void generateBMHClass(String types, ResourcePoolBuilder out) { 243 try { 244 // Generate class 245 Map.Entry<String, byte[]> result = 246 JLIA.generateConcreteBMHClassBytes(types); 247 String className = result.getKey(); 248 byte[] bytes = result.getValue(); 249 250 // Add class to pool 251 ResourcePoolEntry ndata = ResourcePoolEntry.create( 252 "/java.base/" + className + ".class", 253 bytes); 254 out.add(ndata); 255 } catch (Exception ex) { 256 throw new PluginException(ex); 257 } 258 } 259 260 private void generateDMHClass(ResourcePoolBuilder out) { 261 int count = 0; 262 for (List<String> entry : dmhMethods.values()) { 263 count += entry.size(); 264 } 265 MethodType[] methodTypes = new MethodType[count]; 266 int[] dmhTypes = new int[count]; 267 int index = 0; 268 for (Map.Entry<String, List<String>> entry : dmhMethods.entrySet()) { 269 String dmhType = entry.getKey(); 270 for (String type : entry.getValue()) { 271 methodTypes[index] = asMethodType(type); 272 dmhTypes[index] = DMH_METHOD_TYPE_MAP.get(dmhType); 273 index++; 274 } 275 } 276 try { 277 byte[] bytes = JLIA.generateDirectMethodHandleHolderClassBytes( 278 DIRECT_METHOD_HANDLE, methodTypes, dmhTypes); 279 ResourcePoolEntry ndata = ResourcePoolEntry 280 .create(DIRECT_METHOD_HANDLE_ENTRY, bytes); 281 out.add(ndata); 282 283 bytes = JLIA.generateDelegatingMethodHandleHolderClassBytes( 284 DELEGATING_METHOD_HANDLE, methodTypes); 285 ndata = ResourcePoolEntry.create(DELEGATING_METHOD_HANDLE_ENTRY, bytes); 286 out.add(ndata); 287 } catch (Exception ex) { 288 throw new PluginException(ex); 289 } 290 } 291 private static final String DIRECT_METHOD_HANDLE_ENTRY = 292 "/java.base/" + DIRECT_METHOD_HANDLE + ".class"; 293 private static final String DELEGATING_METHOD_HANDLE_ENTRY = 294 "/java.base/" + DELEGATING_METHOD_HANDLE + ".class"; 295 296 // Convert LL -> LL, L3 -> LLL 297 private static String expandSignature(String signature) { 298 StringBuilder sb = new StringBuilder(); 299 char last = 'X'; 300 int count = 0; 301 for (int i = 0; i < signature.length(); i++) { 302 char c = signature.charAt(i); 303 if (c >= '0' && c <= '9') { 304 count *= 10; 305 count += (c - '0'); 306 } else { 307 requireBasicType(c); 308 for (int j = 1; j < count; j++) { 309 sb.append(last); 310 } 311 sb.append(c); 312 last = c; 313 count = 0; 314 } 315 } 316 317 // ended with a number, e.g., "L2": append last char count - 1 times 318 if (count > 1) { 319 requireBasicType(last); 320 for (int j = 1; j < count; j++) { 321 sb.append(last); 322 } 323 } 324 return sb.toString(); 325 } 326 327 private static MethodType asMethodType(String basicSignatureString) { 328 String[] parts = basicSignatureString.split("_"); 329 assert(parts.length == 2); 330 assert(parts[1].length() == 1); 331 String parameters = expandSignature(parts[0]); 332 Class<?> rtype = simpleType(parts[1].charAt(0)); 333 if (parameters.isEmpty()) { 334 return MethodType.methodType(rtype); 335 } else { 336 Class<?>[] ptypes = new Class<?>[parameters.length()]; 337 for (int i = 0; i < ptypes.length; i++) { 338 ptypes[i] = simpleType(parameters.charAt(i)); 339 } 340 return MethodType.methodType(rtype, ptypes); 341 } 342 } 343 344 private static Class<?> simpleType(char c) { 345 switch (c) { 346 case 'F': 347 return float.class; 348 case 'D': 349 return double.class; 350 case 'I': 351 return int.class; 352 case 'L': 353 return Object.class; 354 case 'J': 355 return long.class; 356 case 'V': 357 return void.class; 358 case 'Z': 359 case 'B': 360 case 'S': 361 case 'C': 362 throw new IllegalArgumentException("Not a valid primitive: " + c + 363 " (use I instead)"); 364 default: 365 throw new IllegalArgumentException("Not a primitive: " + c); 366 } 367 } 368 }