# HG changeset patch # User redestad # Date 1459346935 -7200 # Wed Mar 30 16:08:55 2016 +0200 # Node ID b0444592b9116f8ca70f096d0a5c0da5127a7445 # Parent 841f1fe6d486e45d4a036db226c8228994b59f21 8152641: Plugin to generate BMH$Species classes ahead-of-time Reviewed-by: plevart, mchung, forax 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 @@ -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; @@ -43,6 +44,7 @@ import static java.lang.invoke.LambdaForm.BasicType; import static java.lang.invoke.LambdaForm.BasicType.*; import static java.lang.invoke.MethodHandleStatics.*; +import java.util.Map; import static jdk.internal.org.objectweb.asm.Opcodes.*; /** @@ -489,7 +491,15 @@ types, new Function>() { @Override public Class apply(String types) { - return generateConcreteBMHClass(types); + String name = "java.lang.invoke.BoundMethodHandle$Species_" + + LambdaForm.shortenSignature(types); + Class c = BootLoader.loadClassOrNull(name); + if (c != null) { + return c.asSubclass(BoundMethodHandle.class); + } else { + // Not pregenerated, generate the class + return generateConcreteBMHClass(types); + } } }); } @@ -559,20 +569,34 @@ * @return the generated concrete BMH class */ static Class generateConcreteBMHClass(String types) { - final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); + Map.Entry result = generateConcreteBMHClassBytes(types); + String className = result.getKey(); + byte[] classFile = result.getValue(); + // 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 Map.Entry generateConcreteBMHClassBytes(final String types) { String shortTypes = LambdaForm.shortenSignature(types); final String className = SPECIES_PREFIX_PATH + shortTypes; 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); - // 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 +604,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 +622,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 +632,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 +662,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 +683,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 +710,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 Map.entry(className, 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,176 @@ +/* + * 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 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 = 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 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 shortTypes, Pool.ModuleData data, Pool out) { + try { + String types = expandSignature(shortTypes); + + // Generate class + Map.Entry result = (Map.Entry) + 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(); + } +} 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; }