# HG changeset patch # User redestad # Date 1459354464 -7200 # Wed Mar 30 18:14:24 2016 +0200 # Node ID c100f3f3205fc0f8363f49a55dbce1fdcf3ba6ee # Parent 841f1fe6d486e45d4a036db226c8228994b59f21 8152641: Plugin to generate BMH$Species classes ahead-of-time Reviewed-by: plevart, mchung, forax, vlivanov 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; @@ -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>() { @Override public Class 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 generateConcreteBMHClass(String types) { + static Class generateConcreteBMHClass(String shortTypes, + String types) { + final String className = speciesInternalClassName(shortTypes); + byte[] classFile = generateConcreteBMHClassBytes(shortTypes, types, className); + + // load class + InvokerBytecodeGenerator.maybeDump(className, classFile); + Class 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 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 = 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); - - String shortTypes = LambdaForm.shortenSignature(types); - final String className = SPECIES_PREFIX_PATH + shortTypes; - final String sourceFile = SPECIES_PREFIX_NAME + shortTypes; 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 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,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) { + System.err.println("Warning: Unable to generate BMH class for " + shortTypes); + } + } + + 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; }