# HG changeset patch # User redestad # Date 1459289692 -7200 # Wed Mar 30 00:14:52 2016 +0200 # Node ID becdc6612c0aa331734ea66d1fca42a2e9514b17 # Parent 841f1fe6d486e45d4a036db226c8228994b59f21 8152641: Plugin to generate BMH$Species classes ahead-of-time Reviewed-by: mchung, plevart diff --git a/src/java.base/share/classes/java/lang/invoke/BoundMethodHandle.java b/src/java.base/share/classes/java/lang/invoke/BoundMethodHandle.java --- a/src/java.base/share/classes/java/lang/invoke/BoundMethodHandle.java +++ b/src/java.base/share/classes/java/lang/invoke/BoundMethodHandle.java @@ -485,13 +485,20 @@ static Class getConcreteBMHClass(String types) { // CHM.computeIfAbsent ensures generateConcreteBMHClass is called // only once per key. - return CLASS_CACHE.computeIfAbsent( - types, new Function>() { - @Override - public Class apply(String types) { - return generateConcreteBMHClass(types); - } - }); + return CLASS_CACHE.computeIfAbsent(types, SPECIES_LOOKUP); + } + + static final SpeciesLookup SPECIES_LOOKUP = new SpeciesLookup(); + + /** + * @implNote this Function class is intentionally made a named inner + * class to act as a hook for generating BMHs ahead-of-time. + */ + static class SpeciesLookup implements Function> { + @Override + public Class apply(String types) { + return generateConcreteBMHClass(types); + } } /** @@ -559,20 +566,32 @@ * @return the generated concrete BMH class */ static Class generateConcreteBMHClass(String types) { - final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); - String shortTypes = LambdaForm.shortenSignature(types); final String className = SPECIES_PREFIX_PATH + shortTypes; final String sourceFile = SPECIES_PREFIX_NAME + shortTypes; + + byte[] classFile = generateConcreteBMHClassBytes(className, sourceFile, types); + + // load class + InvokerBytecodeGenerator.maybeDump(className, classFile); + Class 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; + } + + static byte[] generateConcreteBMHClassBytes(final String className, final String sourceFile, final String types) { + 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); - // emit static types and SPECIES_DATA fields FieldVisitor fw = cw.visitField(NOT_ACC_PUBLIC + ACC_STATIC, "SPECIES_DATA", SPECIES_DATA_SIG, null, null); fw.visitAnnotation(STABLE_SIG, true); fw.visitEnd(); - // emit bound argument fields for (int i = 0; i < types.length(); ++i) { final char t = types.charAt(i); @@ -580,18 +599,14 @@ final String fieldDesc = t == 'L' ? JLO_SIG : String.valueOf(t); cw.visitField(ACC_FINAL, fieldName, fieldDesc, null, null).visitEnd(); } - MethodVisitor mv; - // emit constructor mv = cw.visitMethod(ACC_PRIVATE, "", makeSignature(types, true), null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); // this mv.visitVarInsn(ALOAD, 1); // type mv.visitVarInsn(ALOAD, 2); // form - mv.visitMethodInsn(INVOKESPECIAL, BMH, "", makeSignature("", true), false); - for (int i = 0, j = 0; i < types.length(); ++i, ++j) { // i counts the arguments, j counts corresponding argument slots char t = types.charAt(i); @@ -602,11 +617,9 @@ ++j; // adjust argument register access } } - mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); - // emit implementation of speciesData() mv = cw.visitMethod(NOT_ACC_PUBLIC + ACC_FINAL, "speciesData", MYSPECIES_DATA_SIG, null, null); mv.visitCode(); @@ -614,7 +627,6 @@ mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); - // emit implementation of fieldCount() mv = cw.visitMethod(NOT_ACC_PUBLIC + ACC_FINAL, "fieldCount", INT_SIG, null, null); mv.visitCode(); @@ -645,13 +657,11 @@ ++j; // adjust argument register access } } - // finally, invoke the constructor and return mv.visitMethodInsn(INVOKESPECIAL, className, "", makeSignature(types, true), false); mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); - // emit copyWith() mv = cw.visitMethod(NOT_ACC_PUBLIC + ACC_FINAL, "copyWith", makeSignature("", false), null, null); mv.visitCode(); @@ -668,7 +678,6 @@ mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); - // for each type, emit copyWithExtendT() for (BasicType type : BasicType.ARG_TYPES) { int ord = type.ordinal(); @@ -696,19 +705,8 @@ mv.visitMaxs(0, 0); mv.visitEnd(); } - cw.visitEnd(); - - // load class - final byte[] classFile = cw.toByteArray(); - InvokerBytecodeGenerator.maybeDump(className, classFile); - Class 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) { diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/GenerateBMHClassesPlugin.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/GenerateBMHClassesPlugin.java new file mode 100644 --- /dev/null +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/GenerateBMHClassesPlugin.java @@ -0,0 +1,344 @@ +/* + * 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.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.Label; +import jdk.internal.org.objectweb.asm.MethodVisitor; +import jdk.internal.org.objectweb.asm.Opcodes; +import jdk.internal.org.objectweb.asm.Type; +import static jdk.internal.org.objectweb.asm.Opcodes.*; +import jdk.tools.jlink.plugin.PluginException; +import jdk.tools.jlink.plugin.Pool; +import jdk.tools.jlink.plugin.TransformerPlugin; + +/** + * Plugin to generate BoundMethodHandle classes. + */ +public final class GenerateBMHClassesPlugin implements TransformerPlugin { + + private static final String NAME = "generate-bmh"; + + private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME); + + private static final String BMH = "java/lang/invoke/BoundMethodHandle"; + + private static final Method FACTORY_METHOD; + + List speciesTypes; + + public GenerateBMHClassesPlugin() { + } + + @Override + public Set getType() { + return Collections.singleton(CATEGORY.TRANSFORMER); + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public Set getState() { + return EnumSet.of(STATE.AUTO_ENABLED, STATE.FUNCTIONAL); + } + + @Override + public boolean hasArguments() { + return true; + } + + @Override + public String getArgumentsDescription() { + return PluginsResourceBundle.getArgument(NAME); + } + + @Override + public void configure(Map config) { + String args = config.get(NAME); + if (args != null && !args.isEmpty()) { + speciesTypes = Arrays.stream(args.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + } else { + // Default set of Species forms to generate + //speciesTypes = Collections.emptyList(); + speciesTypes = List.of("LL", "L3", "L4", "L5", "L6", "L7", "L7I", "L7II", + "L8", "L9", "L10", "L11", "L11I", "L11II", "L11IIL", + "LI", "D", "L3I", "LIL", "LLI", "LLIL", "LILL", "I", "LLILL" + ); + } + } + + @Override + public void visit(Pool in, Pool out) { + for (Pool.ModuleData data : in.getContent()) { + if (("/java.base/" + BMH + "$Factory$SpeciesLookup.class").equals(data.getPath())) { + speciesTypes.forEach(types -> generateConcreteClass(types, data, out)); + generateSpeciesLookupClass(data, out); + } else { + out.add(data); + } + } + } + + private void generateSpeciesLookupClass(Pool.ModuleData data, Pool out) { + System.out.println("Generating SpeciesLookup"); + final String CLASSNAME = "java/lang/invoke/BoundMethodHandle$Factory$SpeciesLookup"; + final String GENERATED_SPECIES = "SPECIES"; + final String STRING_BUILDER = "java/lang/StringBuilder"; + + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); + cw.visit(Opcodes.V1_8, ACC_STATIC + ACC_SUPER, CLASSNAME, + "Ljava/lang/Object;Ljava/util/function/Function;>;", + "java/lang/Object", + new String[] { "java/util/function/Function" }); + + cw.visitInnerClass("java/lang/invoke/BoundMethodHandle$Factory", + "java/lang/invoke/BoundMethodHandle", + "Factory", + ACC_STATIC); + + cw.visitInnerClass("java/lang/invoke/BoundMethodHandle$Factory$SpeciesLookup", + "java/lang/invoke/BoundMethodHandle$Factory", + "SpeciesLookup", + ACC_STATIC); + + // static HashSet GENERATED_SPECIES + cw.visitField(ACC_STATIC, GENERATED_SPECIES, + "Ljava/util/HashSet;", "Ljava/util/HashSet;", null) + .visitEnd(); + + // static { + MethodVisitor mv = cw.visitMethod(ACC_STATIC, "", "()V", + null, null); + mv.visitCode(); + mv.visitTypeInsn(NEW, "java/util/HashSet"); + mv.visitInsn(DUP); + mv.visitMethodInsn(INVOKESPECIAL, "java/util/HashSet", + "", "()V", false); + mv.visitVarInsn(ASTORE, 0); + + for (String types : speciesTypes) { + mv.visitVarInsn(ALOAD, 0); + mv.visitLdcInsn(expandSignature(types)); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/util/HashSet", + "add", "(Ljava/lang/Object;)Z", false); + mv.visitInsn(POP); + } + + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(PUTSTATIC, CLASSNAME, GENERATED_SPECIES, "Ljava/util/HashSet;"); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + // Empty constructor + mv = cw.visitMethod(0, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + /* apply method, equivalent of: + * + * @Override + * @SuppressWarnings("unchecked") + * public Class apply(String types) { + * if (GENERATED_SPECIES.contains(types)) { + * String cl = "java.lang.invoke.BoundMethodHandle$Species_" + * + LambdaForm.shortenSignature(types); + * try { + * return (Class) + * Class.forName(cl); + * } catch (ClassNotFoundException cnf) { + * throw new InternalError(cnf); + * } + * } + * return generateConcreteBMHClass(types); + * } + */ + mv = cw.visitMethod(ACC_PUBLIC, "apply", + "(Ljava/lang/String;)Ljava/lang/Class;", + "(Ljava/lang/String;)Ljava/lang/Class<+Ljava/lang/invoke/BoundMethodHandle;>;", + null); + mv.visitCode(); + Label start = new Label(); + Label end = new Label(); + Label handler = new Label(); + mv.visitTryCatchBlock(start, end, handler, "java/lang/ClassNotFoundException"); + mv.visitFieldInsn(GETSTATIC, CLASSNAME, GENERATED_SPECIES, "Ljava/util/HashSet;"); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/util/HashSet", "contains", + "(Ljava/lang/Object;)Z", false); + Label generateClass = new Label(); + mv.visitJumpInsn(IFEQ, generateClass); + mv.visitTypeInsn(NEW, STRING_BUILDER); + mv.visitInsn(DUP); + mv.visitMethodInsn(INVOKESPECIAL, STRING_BUILDER, "", "()V", false); + mv.visitLdcInsn("java.lang.invoke.BoundMethodHandle$Species_"); + mv.visitMethodInsn(INVOKEVIRTUAL, STRING_BUILDER, "append", + "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); + mv.visitVarInsn(ALOAD, 1); + + mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/LambdaForm", "shortenSignature", + "(Ljava/lang/String;)Ljava/lang/String;", false); + mv.visitMethodInsn(INVOKEVIRTUAL, STRING_BUILDER, "append", + "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); + mv.visitMethodInsn(INVOKEVIRTUAL, STRING_BUILDER, "toString", + "()Ljava/lang/String;", false); + mv.visitVarInsn(ASTORE, 2); + mv.visitLabel(start); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Class", "forName", + "(Ljava/lang/String;)Ljava/lang/Class;", false); + mv.visitLabel(end); + mv.visitInsn(ARETURN); + mv.visitLabel(handler); + mv.visitFrame(Opcodes.F_FULL, 3, + new Object[] {"java/lang/invoke/BoundMethodHandle$Factory$SpeciesLookup", + "java/lang/String", "java/lang/String"}, 1, + new Object[] {"java/lang/ClassNotFoundException"}); + mv.visitVarInsn(ASTORE, 3); + mv.visitTypeInsn(NEW, "java/lang/InternalError"); + mv.visitInsn(DUP); + mv.visitVarInsn(ALOAD, 3); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/InternalError", "", + "(Ljava/lang/Throwable;)V", false); + mv.visitInsn(ATHROW); + mv.visitLabel(generateClass); + mv.visitFrame(Opcodes.F_CHOP, 1, null, 0, null); + + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/BoundMethodHandle$Factory", + "generateConcreteBMHClass", "(Ljava/lang/String;)Ljava/lang/Class;", false); + + mv.visitInsn(ARETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + // Synthetic bridge method to make apply non-abstract + mv = cw.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC, + "apply", "(Ljava/lang/Object;)Ljava/lang/Object;", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitTypeInsn(CHECKCAST, "java/lang/String"); + mv.visitMethodInsn(INVOKEVIRTUAL, + "java/lang/invoke/BoundMethodHandle$Factory$SpeciesLookup", + "apply", "(Ljava/lang/String;)Ljava/lang/Class;", false); + mv.visitInsn(ARETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + cw.visitEnd(); + + System.out.println("Generated SpeciesLookup"); + byte[] bytes = cw.toByteArray(); + Pool.ModuleData ndata = new Pool.ModuleData(data.getModule(), + "/java.base/" + BMH + "$Factory$SpeciesLookup.class", + Pool.ModuleDataType.CLASS_OR_RESOURCE, + new ByteArrayInputStream(bytes), bytes.length); + out.add(ndata); + } + + private void generateConcreteClass(String shortTypes, Pool.ModuleData data, Pool out) { + try { + String types = expandSignature(shortTypes); + + // Generate class + byte[] bytes = (byte[])FACTORY_METHOD.invoke(null, + BMH + "$Species_" + shortTypes, // class name + "Species_" + shortTypes, // source name + types); + + // Add class to pool + Pool.ModuleData ndata = new Pool.ModuleData(data.getModule(), + "/java.base/" + BMH + "$Species_" + shortTypes + ".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, String.class, 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(); + } + +} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties @@ -44,6 +44,11 @@ exclude-resources.description=\ Specify resources to exclude. eg: *.jcov, */META-INF/* +generate-bmh.argument= + +generate-bmh.description=\ +Concrete BoundMethodHandle classes to generate. + installed-modules.description=Fast loading of module descriptors (always enabled) onoff.argument= diff --git a/src/jdk.jlink/share/classes/module-info.java b/src/jdk.jlink/share/classes/module-info.java --- a/src/jdk.jlink/share/classes/module-info.java +++ b/src/jdk.jlink/share/classes/module-info.java @@ -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.GenerateBMHClassesPlugin; provides jdk.tools.jlink.plugin.PostProcessorPlugin with jdk.tools.jlink.internal.plugins.ReleaseInfoPlugin; }