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 }