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