Mercurial > hg > graal-compiler
view graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/graphbuilderconf/InvocationPlugins.java @ 23389:69a9c45e2143
Add constructor to create InvocationPlugins from map of already resolved methods
author | Christian Wimmer <christian.wimmer@oracle.com> |
---|---|
date | Thu, 04 Feb 2016 17:18:03 -0800 |
parents | 2160e7da7fb0 |
children |
line wrap: on
line source
/* * Copyright (c) 2015, 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. */ package com.oracle.graal.nodes.graphbuilderconf; import static java.lang.String.format; import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import jdk.vm.ci.common.JVMCIError; import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.MetaUtil; import jdk.vm.ci.meta.ResolvedJavaMethod; import sun.misc.Launcher; import com.oracle.graal.graph.Node; import com.oracle.graal.graph.iterators.NodeIterable; import com.oracle.graal.nodes.ValueNode; /** * Manages a set of {@link InvocationPlugin}s. */ public class InvocationPlugins { public static class InvocationPluginReceiver implements InvocationPlugin.Receiver { private final GraphBuilderContext parser; private ValueNode[] args; private ValueNode value; public InvocationPluginReceiver(GraphBuilderContext parser) { this.parser = parser; } @Override public ValueNode get() { assert args != null : "Cannot get the receiver of a static method"; if (value == null) { value = parser.nullCheckedValue(args[0]); if (value != args[0]) { args[0] = value; } } return value; } @Override public boolean isConstant() { return args[0].isConstant(); } public InvocationPluginReceiver init(ResolvedJavaMethod targetMethod, ValueNode[] newArgs) { if (!targetMethod.isStatic()) { this.args = newArgs; this.value = null; return this; } return null; } } /** * A symbol that is lazily {@linkplain OptionalLazySymbol#resolve() resolved} to a {@link Type}. */ static class OptionalLazySymbol implements Type { private static final Class<?> MASK_NULL = OptionalLazySymbol.class; private final String name; private Class<?> resolved; OptionalLazySymbol(String name) { this.name = name; } public String getTypeName() { return name; } /** * Gets the resolved {@link Class} corresponding to this symbol or {@code null} if * resolution fails. */ public Class<?> resolve() { if (resolved == null) { Class<?> resolvedOrNull = resolveClass(name, true); resolved = resolvedOrNull == null ? MASK_NULL : resolvedOrNull; } return resolved == MASK_NULL ? null : resolved; } @Override public String toString() { return name; } } /** * Utility for * {@linkplain InvocationPlugins#register(InvocationPlugin, Class, String, Class...) * registration} of invocation plugins. */ public static class Registration { private final InvocationPlugins plugins; private final Type declaringType; private boolean allowOverwrite; /** * Creates an object for registering {@link InvocationPlugin}s for methods declared by a * given class. * * @param plugins where to register the plugins * @param declaringType the class declaring the methods for which plugins will be registered * via this object */ public Registration(InvocationPlugins plugins, Type declaringType) { this.plugins = plugins; this.declaringType = declaringType; } /** * Creates an object for registering {@link InvocationPlugin}s for methods declared by a * given class. * * @param plugins where to register the plugins * @param declaringClassName the name of the class class declaring the methods for which * plugins will be registered via this object */ public Registration(InvocationPlugins plugins, String declaringClassName) { this.plugins = plugins; this.declaringType = new OptionalLazySymbol(declaringClassName); } /** * Configures this registration to allow or disallow overwriting of invocation plugins. */ public Registration setAllowOverwrite(boolean allowOverwrite) { this.allowOverwrite = allowOverwrite; return this; } /** * Registers a plugin for a method with no arguments. * * @param name the name of the method * @param plugin the plugin to be registered */ public void register0(String name, InvocationPlugin plugin) { plugins.register(plugin, false, allowOverwrite, declaringType, name); } /** * Registers a plugin for a method with 1 argument. * * @param name the name of the method * @param plugin the plugin to be registered */ public void register1(String name, Type arg, InvocationPlugin plugin) { plugins.register(plugin, false, allowOverwrite, declaringType, name, arg); } /** * Registers a plugin for a method with 2 arguments. * * @param name the name of the method * @param plugin the plugin to be registered */ public void register2(String name, Type arg1, Type arg2, InvocationPlugin plugin) { plugins.register(plugin, false, allowOverwrite, declaringType, name, arg1, arg2); } /** * Registers a plugin for a method with 3 arguments. * * @param name the name of the method * @param plugin the plugin to be registered */ public void register3(String name, Type arg1, Type arg2, Type arg3, InvocationPlugin plugin) { plugins.register(plugin, false, allowOverwrite, declaringType, name, arg1, arg2, arg3); } /** * Registers a plugin for a method with 4 arguments. * * @param name the name of the method * @param plugin the plugin to be registered */ public void register4(String name, Type arg1, Type arg2, Type arg3, Type arg4, InvocationPlugin plugin) { plugins.register(plugin, false, allowOverwrite, declaringType, name, arg1, arg2, arg3, arg4); } /** * Registers a plugin for a method with 5 arguments. * * @param name the name of the method * @param plugin the plugin to be registered */ public void register5(String name, Type arg1, Type arg2, Type arg3, Type arg4, Type arg5, InvocationPlugin plugin) { plugins.register(plugin, false, allowOverwrite, declaringType, name, arg1, arg2, arg3, arg4, arg5); } /** * Registers a plugin for an optional method with no arguments. * * @param name the name of the method * @param plugin the plugin to be registered */ public void registerOptional0(String name, InvocationPlugin plugin) { plugins.register(plugin, true, allowOverwrite, declaringType, name); } /** * Registers a plugin for an optional method with 1 argument. * * @param name the name of the method * @param plugin the plugin to be registered */ public void registerOptional1(String name, Type arg, InvocationPlugin plugin) { plugins.register(plugin, true, allowOverwrite, declaringType, name, arg); } /** * Registers a plugin for an optional method with 2 arguments. * * @param name the name of the method * @param plugin the plugin to be registered */ public void registerOptional2(String name, Type arg1, Type arg2, InvocationPlugin plugin) { plugins.register(plugin, true, allowOverwrite, declaringType, name, arg1, arg2); } /** * Registers a plugin for an optional method with 3 arguments. * * @param name the name of the method * @param plugin the plugin to be registered */ public void registerOptional3(String name, Type arg1, Type arg2, Type arg3, InvocationPlugin plugin) { plugins.register(plugin, true, allowOverwrite, declaringType, name, arg1, arg2, arg3); } /** * Registers a plugin for an optional method with 4 arguments. * * @param name the name of the method * @param plugin the plugin to be registered */ public void registerOptional4(String name, Type arg1, Type arg2, Type arg3, Type arg4, InvocationPlugin plugin) { plugins.register(plugin, true, allowOverwrite, declaringType, name, arg1, arg2, arg3, arg4); } /** * Registers a plugin that implements a method based on the bytecode of a substitute method. * * @param substituteDeclaringClass the class declaring the substitute method * @param name the name of both the original and substitute method * @param argumentTypes the argument types of the method. Element 0 of this array must be * the {@link Class} value for {@link InvocationPlugin.Receiver} iff the method * is non-static. Upon returning, element 0 will have been rewritten to * {@code declaringClass} */ public void registerMethodSubstitution(Class<?> substituteDeclaringClass, String name, Type... argumentTypes) { registerMethodSubstitution(substituteDeclaringClass, name, name, argumentTypes); } /** * Registers a plugin that implements a method based on the bytecode of a substitute method. * * @param substituteDeclaringClass the class declaring the substitute method * @param name the name of both the original method * @param substituteName the name of the substitute method * @param argumentTypes the argument types of the method. Element 0 of this array must be * the {@link Class} value for {@link InvocationPlugin.Receiver} iff the method * is non-static. Upon returning, element 0 will have been rewritten to * {@code declaringClass} */ public void registerMethodSubstitution(Class<?> substituteDeclaringClass, String name, String substituteName, Type... argumentTypes) { MethodSubstitutionPlugin plugin = new MethodSubstitutionPlugin(substituteDeclaringClass, substituteName, argumentTypes); plugins.register(plugin, false, allowOverwrite, declaringType, name, argumentTypes); } } /** * Key for a method. */ static class MethodKey { final boolean isStatic; /** * This method is optional. This is used for new API methods not present in previous JDK * versions. */ final boolean isOptional; final String name; final Type[] argumentTypes; final InvocationPlugin value; /** * Used to lazily initialize {@link #resolved}. */ private final MetaAccessProvider metaAccess; private volatile ResolvedJavaMethod resolved; MethodKey(MetaAccessProvider metaAccess, InvocationPlugin data, boolean isStatic, boolean isOptional, String name, Type... argumentTypes) { this.metaAccess = metaAccess; this.value = data; this.isStatic = isStatic; this.isOptional = isOptional; this.name = name; this.argumentTypes = argumentTypes; } @Override public boolean equals(Object obj) { if (obj instanceof MethodKey) { MethodKey that = (MethodKey) obj; boolean res = this.name.equals(that.name) && areEqual(this.argumentTypes, that.argumentTypes); assert !res || this.isStatic == that.isStatic; return res; } return false; } private static boolean areEqual(Type[] args1, Type[] args2) { if (args1.length == args2.length) { for (int i = 0; i < args1.length; i++) { if (!args1[i].getTypeName().equals(args2[i].getTypeName())) { return false; } } return true; } return false; } public int getDeclaredParameterCount() { return isStatic ? argumentTypes.length : argumentTypes.length - 1; } @Override public int hashCode() { return name.hashCode(); } private ResolvedJavaMethod resolve(Class<?> declaringClass) { if (resolved == null) { Executable method = resolveJava(declaringClass); if (method == null) { return null; } resolved = metaAccess.lookupJavaMethod(method); } return resolved; } private Executable resolveJava(Class<?> declaringClass) { try { Executable res; Class<?>[] parameterTypes = resolveTypes(argumentTypes, isStatic ? 0 : 1, argumentTypes.length); if (name.equals("<init>")) { res = declaringClass.getDeclaredConstructor(parameterTypes); } else { res = declaringClass.getDeclaredMethod(name, parameterTypes); } assert Modifier.isStatic(res.getModifiers()) == isStatic; return res; } catch (NoSuchMethodException | SecurityException e) { if (isOptional) { return null; } throw new InternalError(e); } } @Override public String toString() { StringBuilder sb = new StringBuilder(name).append('('); for (Type p : argumentTypes) { if (sb.charAt(sb.length() - 1) != '(') { sb.append(", "); } sb.append(p.getTypeName()); } return sb.append(')').toString(); } } private final MetaAccessProvider metaAccess; private final Map<String, ClassPlugins> registrations = new HashMap<>(); /** * Deferred registrations as well as guard for initialization. The guard uses double-checked * locking which is why this field is {@code volatile}. */ private volatile List<Runnable> deferredRegistrations = new ArrayList<>(); /** * Adds a {@link Runnable} for doing registration deferred until the first time * {@link #get(ResolvedJavaMethod)} or {@link #closeRegistration()} is called on this object. */ public void defer(Runnable deferrable) { assert deferredRegistrations != null : "registration is closed"; deferredRegistrations.add(deferrable); } /** * Per-class invocation plugins. */ protected static class ClassPlugins { private final Type declaringType; private final List<MethodKey> registrations = new ArrayList<>(); public ClassPlugins(Type declaringClass) { this.declaringType = declaringClass; } /** * Entry map that is initialized upon first call to {@link #get(ResolvedJavaMethod)}. * * Note: this must be volatile as threads may race to initialize it. */ private volatile Map<ResolvedJavaMethod, InvocationPlugin> entries; void initializeMap() { if (entries == null) { if (registrations.isEmpty()) { entries = Collections.emptyMap(); } else { Class<?> declaringClass = resolveType(declaringType, true); if (declaringClass == null) { // An optional type that could not be resolved entries = Collections.emptyMap(); } else { Map<ResolvedJavaMethod, InvocationPlugin> newEntries = new HashMap<>(); for (MethodKey methodKey : registrations) { ResolvedJavaMethod m = methodKey.resolve(declaringClass); newEntries.put(m, methodKey.value); if (entries != null) { // Another thread finished initializing entries first return; } } entries = newEntries; } } } } public InvocationPlugin get(ResolvedJavaMethod method) { if (entries == null) { initializeMap(); } return entries.get(method); } public void register(MethodKey methodKey, boolean allowOverwrite) { assert entries == null : "registration is closed"; if (allowOverwrite) { int index = registrations.indexOf(methodKey); if (index >= 0) { registrations.set(index, methodKey); return; } } else { assert !registrations.contains(methodKey) : "a value is already registered for " + declaringType + "." + methodKey; } registrations.add(methodKey); } } /** * Adds an entry to this map for a specified method. * * @param value value to be associated with the specified method * @param isStatic specifies if the method is static * @param isOptional specifies if the method is optional * @param declaringClass the class declaring the method * @param name the name of the method * @param argumentTypes the argument types of the method. Element 0 of this array must be * {@code declaringClass} iff the method is non-static. * @return an object representing the method */ MethodKey put(InvocationPlugin value, boolean isStatic, boolean isOptional, boolean allowOverwrite, Type declaringClass, String name, Type... argumentTypes) { assert isStatic || argumentTypes[0] == declaringClass; String internalName = MetaUtil.toInternalName(declaringClass.getTypeName()); ClassPlugins classPlugins = registrations.get(internalName); if (classPlugins == null) { classPlugins = new ClassPlugins(declaringClass); registrations.put(internalName, classPlugins); } assert isStatic || argumentTypes[0] == declaringClass; MethodKey methodKey = new MethodKey(metaAccess, value, isStatic, isOptional, name, argumentTypes); classPlugins.register(methodKey, allowOverwrite); return methodKey; } /** * Determines if a method denoted by a given {@link MethodKey} is in this map. */ boolean containsKey(Type declaringType, MethodKey key) { String internalName = MetaUtil.toInternalName(declaringType.getTypeName()); ClassPlugins classPlugins = registrations.get(internalName); return classPlugins != null && classPlugins.registrations.contains(key); } InvocationPlugin get(ResolvedJavaMethod method) { flushDeferrables(); String internalName = method.getDeclaringClass().getName(); ClassPlugins classPlugins = registrations.get(internalName); if (classPlugins != null) { return classPlugins.get(method); } return null; } private void flushDeferrables() { if (deferredRegistrations != null) { synchronized (this) { if (deferredRegistrations != null) { for (Runnable deferrable : deferredRegistrations) { deferrable.run(); } deferredRegistrations = null; } } for (Map.Entry<String, ClassPlugins> e : registrations.entrySet()) { e.getValue().initializeMap(); } } } /** * Disallows new registrations of new plugins, and creates the internal tables for method * lookup. */ public void closeRegistration() { flushDeferrables(); for (Map.Entry<String, ClassPlugins> e : registrations.entrySet()) { e.getValue().initializeMap(); } } public int size() { return registrations.size(); } /** * The plugins {@linkplain #lookupInvocation(ResolvedJavaMethod) searched} before searching in * this object. */ protected final InvocationPlugins parent; private InvocationPlugins(InvocationPlugins parent, MetaAccessProvider metaAccess) { this.metaAccess = metaAccess; InvocationPlugins p = parent; this.parent = p; } /** * Creates a set of invocation plugins with a non-null {@linkplain #getParent() parent}. */ public InvocationPlugins(InvocationPlugins parent) { this(parent, parent.getMetaAccess()); } public InvocationPlugins(Map<ResolvedJavaMethod, InvocationPlugin> plugins, InvocationPlugins parent, MetaAccessProvider metaAccess) { this.metaAccess = metaAccess; this.parent = parent; this.deferredRegistrations = null; for (Map.Entry<ResolvedJavaMethod, InvocationPlugin> entry : plugins.entrySet()) { ResolvedJavaMethod method = entry.getKey(); InvocationPlugin plugin = entry.getValue(); String internalName = method.getDeclaringClass().getName(); ClassPlugins classPlugins = registrations.get(internalName); if (classPlugins == null) { classPlugins = new ClassPlugins(null); registrations.put(internalName, classPlugins); classPlugins.entries = new HashMap<>(); } classPlugins.entries.put(method, plugin); } } public MetaAccessProvider getMetaAccess() { return metaAccess; } public InvocationPlugins(MetaAccessProvider metaAccess) { this(null, metaAccess); } private void register(InvocationPlugin plugin, boolean isOptional, boolean allowOverwrite, Type declaringClass, String name, Type... argumentTypes) { boolean isStatic = argumentTypes.length == 0 || argumentTypes[0] != InvocationPlugin.Receiver.class; if (!isStatic) { argumentTypes[0] = declaringClass; } MethodKey methodKey = put(plugin, isStatic, isOptional, allowOverwrite, declaringClass, name, argumentTypes); assert Checker.check(this, declaringClass, methodKey, plugin); } /** * Registers an invocation plugin for a given method. There must be no plugin currently * registered for {@code method}. * * @param argumentTypes the argument types of the method. Element 0 of this array must be the * {@link Class} value for {@link InvocationPlugin.Receiver} iff the method is * non-static. Upon returning, element 0 will have been rewritten to * {@code declaringClass} */ public void register(InvocationPlugin plugin, Type declaringClass, String name, Type... argumentTypes) { register(plugin, false, false, declaringClass, name, argumentTypes); } public void register(InvocationPlugin plugin, String declaringClass, String name, Type... argumentTypes) { register(plugin, false, false, new OptionalLazySymbol(declaringClass), name, argumentTypes); } /** * Registers an invocation plugin for a given, optional method. There must be no plugin * currently registered for {@code method}. * * @param argumentTypes the argument types of the method. Element 0 of this array must be the * {@link Class} value for {@link InvocationPlugin.Receiver} iff the method is * non-static. Upon returning, element 0 will have been rewritten to * {@code declaringClass} */ public void registerOptional(InvocationPlugin plugin, Type declaringClass, String name, Type... argumentTypes) { register(plugin, true, false, declaringClass, name, argumentTypes); } /** * Gets the plugin for a given method. * * @param method the method to lookup * @return the plugin associated with {@code method} or {@code null} if none exists */ public InvocationPlugin lookupInvocation(ResolvedJavaMethod method) { if (parent != null) { InvocationPlugin plugin = parent.lookupInvocation(method); if (plugin != null) { return plugin; } } return get(method); } /** * Gets the invocation plugins {@linkplain #lookupInvocation(ResolvedJavaMethod) searched} * before searching in this object. */ public InvocationPlugins getParent() { return parent; } @Override public String toString() { StringBuilder buf = new StringBuilder(); registrations.forEach((name, cp) -> buf.append(name).append('.').append(cp).append(", ")); String s = buf.toString(); if (buf.length() != 0) { s = s.substring(buf.length() - ", ".length()); } return s + " / parent: " + this.parent; } private static class Checker { private static final int MAX_ARITY = 5; /** * The set of all {@link InvocationPlugin#apply} method signatures. */ static final Class<?>[][] SIGS; static { ArrayList<Class<?>[]> sigs = new ArrayList<>(MAX_ARITY); for (Method method : InvocationPlugin.class.getDeclaredMethods()) { if (!Modifier.isStatic(method.getModifiers()) && method.getName().equals("apply")) { Class<?>[] sig = method.getParameterTypes(); assert sig[0] == GraphBuilderContext.class; assert sig[1] == ResolvedJavaMethod.class; assert sig[2] == InvocationPlugin.Receiver.class; assert Arrays.asList(sig).subList(3, sig.length).stream().allMatch(c -> c == ValueNode.class); while (sigs.size() < sig.length - 2) { sigs.add(null); } sigs.set(sig.length - 3, sig); } } assert sigs.indexOf(null) == -1 : format("need to add an apply() method to %s that takes %d %s arguments ", InvocationPlugin.class.getName(), sigs.indexOf(null), ValueNode.class.getSimpleName()); SIGS = sigs.toArray(new Class<?>[sigs.size()][]); } public static boolean check(InvocationPlugins plugins, Type declaringType, MethodKey method, InvocationPlugin plugin) { InvocationPlugins p = plugins.parent; while (p != null) { assert !p.containsKey(declaringType, method) : "a plugin is already registered for " + method; p = p.parent; } if (plugin instanceof ForeignCallPlugin || plugin instanceof GeneratedInvocationPlugin) { return true; } if (plugin instanceof MethodSubstitutionPlugin) { MethodSubstitutionPlugin msplugin = (MethodSubstitutionPlugin) plugin; msplugin.getJavaSubstitute(); return true; } int arguments = method.getDeclaredParameterCount(); assert arguments < SIGS.length : format("need to extend %s to support method with %d arguments: %s", InvocationPlugin.class.getSimpleName(), arguments, method); for (Method m : plugin.getClass().getDeclaredMethods()) { if (m.getName().equals("apply")) { Class<?>[] parameterTypes = m.getParameterTypes(); if (Arrays.equals(SIGS[arguments], parameterTypes)) { return true; } } } throw new AssertionError(format("graph builder plugin for %s not found", method)); } } /** * Checks a set of nodes added to the graph by an {@link InvocationPlugin}. * * @param b the graph builder that applied the plugin * @param plugin a plugin that was just applied * @param newNodes the nodes added to the graph by {@code plugin} * @throws AssertionError if any check fail */ public void checkNewNodes(GraphBuilderContext b, InvocationPlugin plugin, NodeIterable<Node> newNodes) { if (parent != null) { parent.checkNewNodes(b, plugin, newNodes); } } /** * Resolves a name to a class. * * @param className the name of the class to resolve * @param optional if true, resolution failure returns null * @return the resolved class or null if resolution fails and {@code optional} is true */ public static Class<?> resolveClass(String className, boolean optional) { try { // Need to use launcher class path to handle classes // that are not on the boot class path ClassLoader cl = Launcher.getLauncher().getClassLoader(); return Class.forName(className, false, cl); } catch (ClassNotFoundException e) { if (optional) { return null; } throw new JVMCIError("Could not resolve type " + className); } } /** * Resolves a {@link Type} to a {@link Class}. * * @param type the type to resolve * @param optional if true, resolution failure returns null * @return the resolved class or null if resolution fails and {@code optional} is true */ public static Class<?> resolveType(Type type, boolean optional) { if (type instanceof Class) { return (Class<?>) type; } if (optional && type instanceof OptionalLazySymbol) { return ((OptionalLazySymbol) type).resolve(); } return resolveClass(type.getTypeName(), optional); } private static final Class<?>[] NO_CLASSES = {}; /** * Resolves an array of {@link Type}s to an array of {@link Class}es. * * @param types the types to resolve * @param from the initial index of the range to be resolved, inclusive * @param to the final index of the range to be resolved, exclusive * @return the resolved class or null if resolution fails and {@code optional} is true */ public static Class<?>[] resolveTypes(Type[] types, int from, int to) { int length = to - from; if (length <= 0) { return NO_CLASSES; } Class<?>[] classes = new Class<?>[length]; for (int i = 0; i < length; i++) { classes[i] = resolveType(types[i + from], false); } return classes; } }