8152641: Plugin to generate BMH$Species classes ahead-of-time
authorredestad
Fri, 01 Apr 2016 20:28:03 +0200
changeset 36757 c859c6feee56
parent 36756 d49c2e99417b
child 36758 624c185a6e0f
8152641: Plugin to generate BMH$Species classes ahead-of-time Reviewed-by: plevart, mchung, forax, vlivanov, jrose
jdk/src/java.base/share/classes/java/lang/invoke/BoundMethodHandle.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/GenerateJLIClassesPlugin.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties
jdk/src/jdk.jlink/share/classes/module-info.java
jdk/test/tools/jlink/JLinkTest.java
jdk/test/tools/jlink/plugins/GenerateJLIClassesPluginTest.java
--- a/jdk/src/java.base/share/classes/java/lang/invoke/BoundMethodHandle.java	Fri Apr 01 17:08:53 2016 +0200
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/BoundMethodHandle.java	Fri Apr 01 20:28:03 2016 +0200
@@ -25,6 +25,7 @@
 
 package java.lang.invoke;
 
+import jdk.internal.loader.BootLoader;
 import jdk.internal.vm.annotation.Stable;
 import jdk.internal.org.objectweb.asm.ClassWriter;
 import jdk.internal.org.objectweb.asm.FieldVisitor;
@@ -36,6 +37,7 @@
 import java.lang.invoke.MethodHandles.Lookup;
 import java.lang.reflect.Field;
 import java.util.Arrays;
+import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.function.Function;
@@ -463,6 +465,7 @@
 
         static final String SPECIES_PREFIX_NAME = "Species_";
         static final String SPECIES_PREFIX_PATH = BMH + "$" + SPECIES_PREFIX_NAME;
+        static final String SPECIES_CLASS_PREFIX = SPECIES_PREFIX_PATH.replace('/', '.');
 
         static final String BMHSPECIES_DATA_EWI_SIG = "(B)" + SPECIES_DATA_SIG;
         static final String BMHSPECIES_DATA_GFC_SIG = "(" + JLS_SIG + JLC_SIG + ")" + SPECIES_DATA_SIG;
@@ -489,7 +492,15 @@
                 types, new Function<String, Class<? extends BoundMethodHandle>>() {
                     @Override
                     public Class<? extends BoundMethodHandle> apply(String types) {
-                        return generateConcreteBMHClass(types);
+                        String shortTypes = LambdaForm.shortenSignature(types);
+                        String className = SPECIES_CLASS_PREFIX + shortTypes;
+                        Class<?> c = BootLoader.loadClassOrNull(className);
+                        if (c != null) {
+                            return c.asSubclass(BoundMethodHandle.class);
+                        } else {
+                            // Not pregenerated, generate the class
+                            return generateConcreteBMHClass(shortTypes, types);
+                        }
                     }
                 });
         }
@@ -558,12 +569,49 @@
          * @param types the type signature, wherein reference types are erased to 'L'
          * @return the generated concrete BMH class
          */
-        static Class<? extends BoundMethodHandle> generateConcreteBMHClass(String types) {
-            final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
+        static Class<? extends BoundMethodHandle> generateConcreteBMHClass(String shortTypes,
+                String types) {
+            final String className  = speciesInternalClassName(shortTypes);
+            byte[] classFile = generateConcreteBMHClassBytes(shortTypes, types, className);
+
+            // load class
+            InvokerBytecodeGenerator.maybeDump(className, classFile);
+            Class<? extends BoundMethodHandle> bmhClass =
+                UNSAFE.defineClass(className, classFile, 0, classFile.length,
+                                   BoundMethodHandle.class.getClassLoader(), null)
+                    .asSubclass(BoundMethodHandle.class);
+
+            return bmhClass;
+        }
 
+        /**
+         * @implNote this method is used by GenerateBMHClassesPlugin to enable
+         * ahead-of-time generation of BMH classes at link time. It does
+         * added validation since this string may be user provided.
+         */
+        static Map.Entry<String, byte[]> generateConcreteBMHClassBytes(
+                final String types) {
+            for (char c : types.toCharArray()) {
+                if ("LIJFD".indexOf(c) < 0) {
+                    throw new IllegalArgumentException("All characters must "
+                            + "correspond to a basic field type: LIJFD");
+                }
+            }
             String shortTypes = LambdaForm.shortenSignature(types);
-            final String className  = SPECIES_PREFIX_PATH + shortTypes;
+            final String className  = speciesInternalClassName(shortTypes);
+            return Map.entry(className,
+                    generateConcreteBMHClassBytes(shortTypes, types, className));
+        }
+
+        private static String speciesInternalClassName(String shortTypes) {
+            return SPECIES_PREFIX_PATH + shortTypes;
+        }
+
+        static byte[] generateConcreteBMHClassBytes(final String shortTypes,
+                final String types, final String className) {
             final String sourceFile = SPECIES_PREFIX_NAME + shortTypes;
+
+            final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
             final int NOT_ACC_PUBLIC = 0;  // not ACC_PUBLIC
             cw.visit(V1_6, NOT_ACC_PUBLIC + ACC_FINAL + ACC_SUPER, className, null, BMH, null);
             cw.visitSource(sourceFile, null);
@@ -699,16 +747,7 @@
 
             cw.visitEnd();
 
-            // load class
-            final byte[] classFile = cw.toByteArray();
-            InvokerBytecodeGenerator.maybeDump(className, classFile);
-            Class<? extends BoundMethodHandle> bmhClass =
-                //UNSAFE.defineAnonymousClass(BoundMethodHandle.class, classFile, null).asSubclass(BoundMethodHandle.class);
-                UNSAFE.defineClass(className, classFile, 0, classFile.length,
-                                   BoundMethodHandle.class.getClassLoader(), null)
-                    .asSubclass(BoundMethodHandle.class);
-
-            return bmhClass;
+            return cw.toByteArray();
         }
 
         private static int typeLoadOp(char t) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/GenerateJLIClassesPlugin.java	Fri Apr 01 20:28:03 2016 +0200
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.tools.jlink.internal.plugins;
+
+import java.io.ByteArrayInputStream;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import jdk.tools.jlink.plugin.PluginException;
+import jdk.tools.jlink.plugin.Pool;
+import jdk.tools.jlink.plugin.TransformerPlugin;
+
+/**
+ * Plugin to generate java.lang.invoke classes.
+ */
+public final class GenerateJLIClassesPlugin implements TransformerPlugin {
+
+    private static final String NAME = "generate-jli-classes";
+
+    private static final String BMH_PARAM = "bmh";
+
+    private static final String BMH_SPECIES_PARAM = "bmh-species";
+
+    private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME);
+
+    private static final String BMH = "java/lang/invoke/BoundMethodHandle";
+
+    private static final Method FACTORY_METHOD;
+
+    List<String> speciesTypes;
+
+    public GenerateJLIClassesPlugin() {
+    }
+
+    @Override
+    public Set<PluginType> getType() {
+        return Collections.singleton(CATEGORY.TRANSFORMER);
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public String getDescription() {
+        return DESCRIPTION;
+    }
+
+    @Override
+    public Set<STATE> getState() {
+        return EnumSet.of(STATE.AUTO_ENABLED, STATE.FUNCTIONAL);
+    }
+
+    @Override
+    public boolean hasArguments() {
+        return true;
+    }
+
+    @Override
+    public String getArgumentsDescription() {
+       return PluginsResourceBundle.getArgument(NAME);
+    }
+
+    /**
+     * @return the default Species forms to generate.
+     *
+     * This list was derived from running a Java concatenating strings
+     * with -Djava.lang.invoke.stringConcat=MH_INLINE_SIZED_EXACT set
+     * plus a subset of octane. A better long-term solution is to define
+     * and run a set of quick generators and extracting this list as a
+     * step in the build process.
+     */
+    public static List<String> defaultSpecies() {
+        return List.of("LL", "L3", "L4", "L5", "L6", "L7", "L7I",
+                "L7II", "L7IIL", "L8", "L9", "L10", "L11", "L11I", "L11II",
+                "L12", "L13", "LI", "D", "L3I", "LIL", "LLI", "LLIL",
+                "LILL", "I", "LLILL");
+    }
+
+    @Override
+    public void configure(Map<String, String> config) {
+        String mainArgument = config.get(NAME);
+
+        // Enable by default
+        boolean bmhEnabled = true;
+        if (mainArgument != null) {
+            Set<String> args = Arrays.stream(mainArgument.split(","))
+                    .collect(Collectors.toSet());
+            if (!args.contains(BMH_PARAM)) {
+                bmhEnabled = false;
+            }
+        }
+
+        if (!bmhEnabled) {
+            speciesTypes = List.of();
+        } else {
+            String args = config.get(BMH_SPECIES_PARAM);
+            List<String> bmhSpecies;
+            if (args != null && !args.isEmpty()) {
+                bmhSpecies = Arrays.stream(args.split(","))
+                    .map(String::trim)
+                    .filter(s -> !s.isEmpty())
+                    .collect(Collectors.toList());
+            } else {
+                bmhSpecies = defaultSpecies();
+            }
+
+            // Expand BMH species signatures
+            speciesTypes = bmhSpecies.stream()
+                    .map(type -> expandSignature(type))
+                    .collect(Collectors.toList());
+
+            // Validation check
+            for (String type : speciesTypes) {
+                for (char c : type.toCharArray()) {
+                    if ("LIJFD".indexOf(c) < 0) {
+                        throw new PluginException("All characters must "
+                                + "correspond to a basic field type: LIJFD");
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void visit(Pool in, Pool out) {
+        for (Pool.ModuleData data : in.getContent()) {
+            if (("/java.base/" + BMH + ".class").equals(data.getPath())) {
+                // Add BoundMethodHandle unchanged
+                out.add(data);
+                speciesTypes.forEach(types -> generateConcreteClass(types, data, out));
+            } else {
+                out.add(data);
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void generateConcreteClass(String types, Pool.ModuleData data, Pool out) {
+        try {
+            // Generate class
+            Map.Entry<String, byte[]> result = (Map.Entry<String, byte[]>)
+                    FACTORY_METHOD.invoke(null, types);
+            String className = result.getKey();
+            byte[] bytes = result.getValue();
+
+            // Add class to pool
+            Pool.ModuleData ndata = new Pool.ModuleData(data.getModule(),
+                    "/java.base/" + className + ".class",
+                    Pool.ModuleDataType.CLASS_OR_RESOURCE,
+                    new ByteArrayInputStream(bytes), bytes.length);
+            out.add(ndata);
+        } catch (Exception ex) {
+            throw new PluginException(ex);
+        }
+    }
+
+    static {
+        try {
+            Class<?> BMHFactory = Class.forName("java.lang.invoke.BoundMethodHandle$Factory");
+            Method genClassMethod = BMHFactory.getDeclaredMethod("generateConcreteBMHClassBytes",
+                    String.class);
+            genClassMethod.setAccessible(true);
+            FACTORY_METHOD = genClassMethod;
+        } catch (Exception e) {
+            throw new PluginException(e);
+        }
+    }
+
+    // Convert LL -> LL, L3 -> LLL
+    private static String expandSignature(String signature) {
+        StringBuilder sb = new StringBuilder();
+        char last = 'X';
+        int count = 0;
+        for (int i = 0; i < signature.length(); i++) {
+            char c = signature.charAt(i);
+            if (c >= '0' && c <= '9') {
+                count *= 10;
+                count += (c - '0');
+            } else {
+                for (int j = 1; j < count; j++) {
+                    sb.append(last);
+                }
+                sb.append(c);
+                last = c;
+                count = 0;
+            }
+        }
+        for (int j = 1; j < count; j++) {
+            sb.append(last);
+        }
+        return sb.toString();
+    }
+}
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties	Fri Apr 01 17:08:53 2016 +0200
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties	Fri Apr 01 20:28:03 2016 +0200
@@ -44,6 +44,11 @@
 exclude-resources.description=\
 Specify resources to exclude. e.g.: *.jcov, */META-INF/*
 
+generate-jli-classes.argument=<bmh[:bmh-species=LL,L3,...]>
+
+generate-jli-classes.description=\
+Concrete java.lang.invoke classes to generate
+
 installed-modules.description=Fast loading of module descriptors (always enabled)
 
 onoff.argument=<on|off>
--- a/jdk/src/jdk.jlink/share/classes/module-info.java	Fri Apr 01 17:08:53 2016 +0200
+++ b/jdk/src/jdk.jlink/share/classes/module-info.java	Fri Apr 01 20:28:03 2016 +0200
@@ -45,5 +45,6 @@
     provides jdk.tools.jlink.plugin.TransformerPlugin with jdk.tools.jlink.internal.plugins.OptimizationPlugin;
     provides jdk.tools.jlink.plugin.TransformerPlugin with jdk.tools.jlink.internal.plugins.ExcludeVMPlugin;
     provides jdk.tools.jlink.plugin.TransformerPlugin with jdk.tools.jlink.internal.plugins.IncludeLocalesPlugin;
+    provides jdk.tools.jlink.plugin.TransformerPlugin with jdk.tools.jlink.internal.plugins.GenerateJLIClassesPlugin;
     provides jdk.tools.jlink.plugin.PostProcessorPlugin with jdk.tools.jlink.internal.plugins.ReleaseInfoPlugin;
 }
--- a/jdk/test/tools/jlink/JLinkTest.java	Fri Apr 01 17:08:53 2016 +0200
+++ b/jdk/test/tools/jlink/JLinkTest.java	Fri Apr 01 20:28:03 2016 +0200
@@ -63,7 +63,7 @@
             return;
         }
         helper.generateDefaultModules();
-        int numPlugins = 12;
+        int numPlugins = 13;
         {
             // number of built-in plugins
             List<Plugin> builtInPlugins = new ArrayList<>();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jlink/plugins/GenerateJLIClassesPluginTest.java	Fri Apr 01 20:28:03 2016 +0200
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import jdk.tools.jlink.internal.plugins.GenerateJLIClassesPlugin;
+
+import tests.Helper;
+import tests.JImageGenerator;
+import tests.JImageValidator;
+import tests.Result;
+
+ /*
+ * @test
+ * @library ../../lib
+ * @summary Test --generate-jli-classes plugin
+ * @modules java.base/jdk.internal.jimage
+ *          jdk.jdeps/com.sun.tools.classfile
+ *          jdk.jlink/jdk.tools.jlink.internal
+ *          jdk.jlink/jdk.tools.jlink.internal.plugins
+ *          jdk.jlink/jdk.tools.jmod
+ *          jdk.jlink/jdk.tools.jimage
+ * @build tests.*
+ * @run main/othervm GenerateJLIClassesPluginTest
+ */
+public class GenerateJLIClassesPluginTest {
+
+    private static Helper helper;
+
+    public static void main(String[] args) throws Exception {
+        helper = Helper.newHelper();
+        if (helper == null) {
+            System.err.println("Test not run");
+            return;
+        }
+
+        helper.generateDefaultModules();
+
+
+        // Test that generate-jli is enabled by default
+        Result result = JImageGenerator.getJLinkTask()
+                .modulePath(helper.defaultModulePath())
+                .output(helper.createNewImageDir("generate-jli"))
+                .addMods("java.base")
+                .call();
+
+        Path image = result.assertSuccess();
+
+        JImageValidator.validate(
+            image.resolve("lib").resolve("modules"),
+                    classFilesForSpecies(GenerateJLIClassesPlugin.defaultSpecies()),
+                    List.of());
+
+
+        // Test a valid set of options
+        result = JImageGenerator.getJLinkTask()
+                .modulePath(helper.defaultModulePath())
+                .output(helper.createNewImageDir("generate-jli"))
+                .option("--generate-jli-classes=bmh:bmh-species=LL,L3")
+                .addMods("java.base")
+                .call();
+
+        image = result.assertSuccess();
+
+        JImageValidator.validate(
+                image.resolve("lib").resolve("modules"),
+                classFilesForSpecies(List.of("LL", "L3")),
+                classFilesForSpecies(List.of("L4")));
+
+
+        // Test disabling BMH species generation
+        result = JImageGenerator.getJLinkTask()
+                .modulePath(helper.defaultModulePath())
+                .output(helper.createNewImageDir("generate-jli"))
+                .option("--generate-jli-classes=not-bmh:bmh-species=LL,L3")
+                .addMods("java.base")
+                .call();
+
+        image = result.assertSuccess();
+        JImageValidator.validate(
+            image.resolve("lib").resolve("modules"),
+            List.of(),
+            classFilesForSpecies(List.of("LL", "L3", "L4")));
+
+
+        // Test an invalid set of options
+        result = JImageGenerator.getJLinkTask()
+                .modulePath(helper.defaultModulePath())
+                .output(helper.createNewImageDir("generate-jli"))
+                .option("--generate-jli-classes=bmh:bmh-species=LL,L7V")
+                .addMods("java.base")
+                .call();
+
+        result.assertFailure();
+    }
+
+    private static List<String> classFilesForSpecies(List<String> species) {
+        return species.stream()
+                .map(s -> "/java.base/java/lang/invoke/BoundMethodHandle$Species_" + s + ".class")
+                .collect(Collectors.toList());
+    }
+}