| /* |
| * 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 org.graalvm.compiler.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.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.graalvm.compiler.api.replacements.MethodSubstitution; |
| import org.graalvm.compiler.api.replacements.MethodSubstitutionRegistry; |
| import org.graalvm.compiler.bytecode.BytecodeProvider; |
| import org.graalvm.compiler.debug.GraalError; |
| import org.graalvm.compiler.graph.Node; |
| import org.graalvm.compiler.graph.iterators.NodeIterable; |
| import org.graalvm.compiler.nodes.ValueNode; |
| import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin.Receiver; |
| |
| import jdk.vm.ci.meta.MetaAccessProvider; |
| import jdk.vm.ci.meta.MetaUtil; |
| import jdk.vm.ci.meta.ResolvedJavaMethod; |
| |
| /** |
| * 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(boolean performNullCheck) { |
| assert args != null : "Cannot get the receiver of a static method"; |
| if (!performNullCheck) { |
| return args[0]; |
| } |
| 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; |
| } |
| |
| @Override |
| 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 implements MethodSubstitutionRegistry { |
| |
| private final InvocationPlugins plugins; |
| private final Type declaringType; |
| private final BytecodeProvider methodSubstitutionBytecodeProvider; |
| private boolean allowOverwrite; |
| |
| @Override |
| public Class<?> getReceiverType() { |
| return Receiver.class; |
| } |
| |
| /** |
| * 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; |
| this.methodSubstitutionBytecodeProvider = null; |
| } |
| |
| /** |
| * 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 |
| * @param methodSubstitutionBytecodeProvider provider used to get the bytecodes to parse for |
| * method substitutions |
| */ |
| public Registration(InvocationPlugins plugins, Type declaringType, BytecodeProvider methodSubstitutionBytecodeProvider) { |
| this.plugins = plugins; |
| this.declaringType = declaringType; |
| this.methodSubstitutionBytecodeProvider = methodSubstitutionBytecodeProvider; |
| } |
| |
| /** |
| * 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 |
| * @param methodSubstitutionBytecodeProvider provider used to get the bytecodes to parse for |
| * method substitutions |
| */ |
| public Registration(InvocationPlugins plugins, String declaringClassName, BytecodeProvider methodSubstitutionBytecodeProvider) { |
| this.plugins = plugins; |
| this.declaringType = new OptionalLazySymbol(declaringClassName); |
| this.methodSubstitutionBytecodeProvider = methodSubstitutionBytecodeProvider; |
| } |
| |
| /** |
| * 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} |
| */ |
| @Override |
| 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} |
| */ |
| @Override |
| public void registerMethodSubstitution(Class<?> substituteDeclaringClass, String name, String substituteName, Type... argumentTypes) { |
| assert methodSubstitutionBytecodeProvider != null : "Registration used for method substitutions requires a non-null methodSubstitutionBytecodeProvider"; |
| MethodSubstitutionPlugin plugin = new MethodSubstitutionPlugin(methodSubstitutionBytecodeProvider, substituteDeclaringClass, substituteName, argumentTypes); |
| plugins.register(plugin, false, allowOverwrite, declaringType, name, argumentTypes); |
| } |
| } |
| |
| /** |
| * Key for a {@linkplain ClassPlugins#entries resolved} plugin registration. Due to the |
| * possibility of class redefinition, we cannot directly use {@link ResolvedJavaMethod}s as |
| * keys. A {@link ResolvedJavaMethod} implementation might implement {@code equals()} and |
| * {@code hashCode()} based on internal representation subject to change by class redefinition. |
| */ |
| static final class ResolvedJavaMethodKey { |
| private final ResolvedJavaMethod method; |
| |
| ResolvedJavaMethodKey(ResolvedJavaMethod method) { |
| this.method = method; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj instanceof ResolvedJavaMethodKey) { |
| ResolvedJavaMethodKey that = (ResolvedJavaMethodKey) obj; |
| if (this.method.isStatic() == that.method.isStatic()) { |
| if (this.method.getDeclaringClass().equals(that.method.getDeclaringClass())) { |
| if (this.method.getName().equals(that.method.getName())) { |
| if (this.method.getSignature().equals(that.method.getSignature())) { |
| return true; |
| } |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return this.method.getName().hashCode(); |
| } |
| |
| @Override |
| public String toString() { |
| return "ResolvedJavaMethodKey<" + method + ">"; |
| } |
| } |
| |
| /** |
| * Key for {@linkplain ClassPlugins#registrations registering} an {@link InvocationPlugin} for a |
| * specific 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 : res; |
| 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<ResolvedJavaMethodKey, InvocationPlugin> entries; |
| |
| void initializeMap() { |
| if (!isClosed()) { |
| 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<ResolvedJavaMethodKey, InvocationPlugin> newEntries = new HashMap<>(); |
| for (MethodKey methodKey : registrations) { |
| ResolvedJavaMethod m = methodKey.resolve(declaringClass); |
| if (m != null) { |
| newEntries.put(new ResolvedJavaMethodKey(m), methodKey.value); |
| if (entries != null) { |
| // Another thread finished initializing entries first |
| return; |
| } |
| } |
| } |
| entries = newEntries; |
| } |
| } |
| } |
| } |
| |
| public InvocationPlugin get(ResolvedJavaMethod method) { |
| if (!isClosed()) { |
| initializeMap(); |
| } |
| return entries.get(new ResolvedJavaMethodKey(method)); |
| } |
| |
| public void register(MethodKey methodKey, boolean allowOverwrite) { |
| assert !isClosed() : "registration is closed: " + methodKey + " " + Arrays.toString(entries.keySet().toArray()); |
| 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); |
| } |
| |
| public boolean isClosed() { |
| return entries != null; |
| } |
| } |
| |
| /** |
| * 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(new ResolvedJavaMethodKey(method), plugin); |
| } |
| } |
| |
| public MetaAccessProvider getMetaAccess() { |
| return metaAccess; |
| } |
| |
| public InvocationPlugins(MetaAccessProvider metaAccess) { |
| this(null, metaAccess); |
| } |
| |
| protected 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 set of methods for which invocation plugins have been registered. Once this method |
| * is called, no further registrations can be made. |
| */ |
| public Set<ResolvedJavaMethod> getMethods() { |
| Set<ResolvedJavaMethod> res = new HashSet<>(); |
| if (parent != null) { |
| res.addAll(parent.getMethods()); |
| } |
| flushDeferrables(); |
| for (ClassPlugins cp : registrations.values()) { |
| for (ResolvedJavaMethodKey key : cp.entries.keySet()) { |
| res.add(key.method); |
| } |
| } |
| return res; |
| } |
| |
| /** |
| * 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; |
| Method substitute = msplugin.getJavaSubstitute(); |
| assert substitute.getAnnotation(MethodSubstitution.class) != null : format("Substitute method must be annotated with @%s: %s", MethodSubstitution.class.getSimpleName(), substitute); |
| 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 the system class loader to handle classes |
| // loaded by the application class loader which is not |
| // delegated to by the JVMCI class loader. |
| ClassLoader cl = ClassLoader.getSystemClassLoader(); |
| return Class.forName(className, false, cl); |
| } catch (ClassNotFoundException e) { |
| if (optional) { |
| return null; |
| } |
| throw new GraalError("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; |
| } |
| } |