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 }