| /* |
| * 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 static org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins.LateClassPlugins.CLOSED_LATE_CLASS_PLUGIN; |
| |
| import java.lang.reflect.Constructor; |
| 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 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.MetaUtil; |
| import jdk.vm.ci.meta.ResolvedJavaMethod; |
| import jdk.vm.ci.meta.ResolvedJavaType; |
| import jdk.vm.ci.meta.Signature; |
| |
| /** |
| * Manages a set of {@link InvocationPlugin}s. |
| * |
| * Most plugins are registered during initialization (i.e., before |
| * {@link #lookupInvocation(ResolvedJavaMethod)} or {@link #getBindings} is called). These |
| * registrations can be made with {@link Registration}, |
| * {@link #register(InvocationPlugin, String, String, Type...)}, |
| * {@link #register(InvocationPlugin, Type, String, Type...)} or |
| * {@link #registerOptional(InvocationPlugin, Type, String, Type...)}. Initialization is not |
| * thread-safe and so must only be performed by a single thread. |
| * |
| * Plugins that are not guaranteed to be made during initialization must use |
| * {@link LateRegistration}. |
| */ |
| 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 a method with 6 arguments. |
| * |
| * @param name the name of the method |
| * @param plugin the plugin to be registered |
| */ |
| public void register6(String name, Type arg1, Type arg2, Type arg3, Type arg4, Type arg5, Type arg6, InvocationPlugin plugin) { |
| plugins.register(plugin, false, allowOverwrite, declaringType, name, arg1, arg2, arg3, arg4, arg5, arg6); |
| } |
| |
| /** |
| * Registers a plugin for a method with 7 arguments. |
| * |
| * @param name the name of the method |
| * @param plugin the plugin to be registered |
| */ |
| public void register7(String name, Type arg1, Type arg2, Type arg3, Type arg4, Type arg5, Type arg6, Type arg7, InvocationPlugin plugin) { |
| plugins.register(plugin, false, allowOverwrite, declaringType, name, arg1, arg2, arg3, arg4, arg5, arg6, arg7); |
| } |
| |
| /** |
| * 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) { |
| MethodSubstitutionPlugin plugin = createMethodSubstitution(substituteDeclaringClass, substituteName, argumentTypes); |
| plugins.register(plugin, false, allowOverwrite, declaringType, name, argumentTypes); |
| } |
| |
| public MethodSubstitutionPlugin createMethodSubstitution(Class<?> substituteDeclaringClass, 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); |
| return plugin; |
| } |
| |
| } |
| |
| /** |
| * Utility for registering plugins after Graal may have been initialized. Registrations made via |
| * this class are not finalized until {@link #close} is called. |
| */ |
| public static class LateRegistration implements AutoCloseable { |
| |
| private InvocationPlugins plugins; |
| private final List<Binding> bindings = new ArrayList<>(); |
| private final Type declaringType; |
| |
| /** |
| * 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 LateRegistration(InvocationPlugins plugins, Type declaringType) { |
| this.plugins = plugins; |
| this.declaringType = declaringType; |
| } |
| |
| /** |
| * 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, String name, Type... argumentTypes) { |
| boolean isStatic = argumentTypes.length == 0 || argumentTypes[0] != InvocationPlugin.Receiver.class; |
| if (!isStatic) { |
| argumentTypes[0] = declaringType; |
| } |
| |
| assert isStatic || argumentTypes[0] == declaringType; |
| Binding binding = new Binding(plugin, isStatic, name, argumentTypes); |
| bindings.add(binding); |
| |
| assert Checks.check(this.plugins, declaringType, binding); |
| assert Checks.checkResolvable(false, declaringType, binding); |
| } |
| |
| @Override |
| public void close() { |
| assert plugins != null : String.format("Late registrations of invocation plugins for %s is already closed", declaringType); |
| plugins.registerLate(declaringType, bindings); |
| plugins = null; |
| } |
| } |
| |
| /** |
| * Associates an {@link InvocationPlugin} with the details of a method it substitutes. |
| */ |
| public static class Binding { |
| /** |
| * The plugin this binding is for. |
| */ |
| public final InvocationPlugin plugin; |
| |
| /** |
| * Specifies if the associated method is static. |
| */ |
| public final boolean isStatic; |
| |
| /** |
| * The name of the associated method. |
| */ |
| public final String name; |
| |
| /** |
| * A partial |
| * <a href="http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.3">method |
| * descriptor</a> for the associated method. The descriptor includes enclosing {@code '('} |
| * and {@code ')'} characters but omits the return type suffix. |
| */ |
| public final String argumentsDescriptor; |
| |
| /** |
| * Link in a list of bindings. |
| */ |
| private Binding next; |
| |
| Binding(InvocationPlugin data, boolean isStatic, String name, Type... argumentTypes) { |
| this.plugin = data; |
| this.isStatic = isStatic; |
| this.name = name; |
| StringBuilder buf = new StringBuilder(); |
| buf.append('('); |
| for (int i = isStatic ? 0 : 1; i < argumentTypes.length; i++) { |
| buf.append(MetaUtil.toInternalName(argumentTypes[i].getTypeName())); |
| } |
| buf.append(')'); |
| this.argumentsDescriptor = buf.toString(); |
| assert !name.equals("<init>") || !isStatic : this; |
| } |
| |
| Binding(ResolvedJavaMethod resolved, InvocationPlugin data) { |
| this.plugin = data; |
| this.isStatic = resolved.isStatic(); |
| this.name = resolved.getName(); |
| Signature sig = resolved.getSignature(); |
| String desc = sig.toMethodDescriptor(); |
| assert desc.indexOf(')') != -1 : desc; |
| this.argumentsDescriptor = desc.substring(0, desc.indexOf(')') + 1); |
| assert !name.equals("<init>") || !isStatic : this; |
| } |
| |
| @Override |
| public String toString() { |
| return name + argumentsDescriptor; |
| } |
| } |
| |
| /** |
| * Plugin registrations for already resolved methods. If non-null, then {@link #registrations} |
| * is null and no further registrations can be made. |
| */ |
| private final Map<ResolvedJavaMethod, InvocationPlugin> resolvedRegistrations; |
| |
| /** |
| * Map from class names in {@linkplain MetaUtil#toInternalName(String) internal} form to the |
| * invocation plugin bindings for the class. Tf non-null, then {@link #resolvedRegistrations} |
| * will be null. |
| */ |
| private final Map<String, ClassPlugins> registrations; |
| |
| /** |
| * Deferred registrations as well as the guard for delimiting the initial registration phase. |
| * 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); |
| } |
| |
| /** |
| * Support for registering plugins once this object may be accessed by multiple threads. |
| */ |
| private volatile LateClassPlugins lateRegistrations; |
| |
| /** |
| * Per-class bindings. |
| */ |
| static class ClassPlugins { |
| |
| /** |
| * Maps method names to binding lists. |
| */ |
| private final Map<String, Binding> bindings = new HashMap<>(); |
| |
| /** |
| * Gets the invocation plugin for a given method. |
| * |
| * @return the invocation plugin for {@code method} or {@code null} |
| */ |
| InvocationPlugin get(ResolvedJavaMethod method) { |
| assert !method.isBridge(); |
| Binding binding = bindings.get(method.getName()); |
| while (binding != null) { |
| if (method.isStatic() == binding.isStatic) { |
| if (method.getSignature().toMethodDescriptor().startsWith(binding.argumentsDescriptor)) { |
| return binding.plugin; |
| } |
| } |
| binding = binding.next; |
| } |
| return null; |
| } |
| |
| public void register(Binding binding, boolean allowOverwrite) { |
| if (allowOverwrite) { |
| if (lookup(binding) != null) { |
| register(binding); |
| return; |
| } |
| } else { |
| assert lookup(binding) == null : "a value is already registered for " + binding; |
| } |
| register(binding); |
| } |
| |
| InvocationPlugin lookup(Binding binding) { |
| Binding b = bindings.get(binding.name); |
| while (b != null) { |
| if (b.isStatic == binding.isStatic && b.argumentsDescriptor.equals(binding.argumentsDescriptor)) { |
| return b.plugin; |
| } |
| b = b.next; |
| } |
| return null; |
| } |
| |
| /** |
| * Registers {@code binding}. |
| */ |
| void register(Binding binding) { |
| Binding head = bindings.get(binding.name); |
| assert binding.next == null; |
| binding.next = head; |
| bindings.put(binding.name, binding); |
| } |
| } |
| |
| static class LateClassPlugins extends ClassPlugins { |
| static final String CLOSED_LATE_CLASS_PLUGIN = "-----"; |
| private final String className; |
| private final LateClassPlugins next; |
| |
| LateClassPlugins(LateClassPlugins next, String className) { |
| assert next == null || next.className != CLOSED_LATE_CLASS_PLUGIN : "Late registration of invocation plugins is closed"; |
| this.next = next; |
| this.className = className; |
| } |
| } |
| |
| /** |
| * Registers a binding of a method to an invocation plugin. |
| * |
| * @param plugin invocation plugin to be associated with the specified method |
| * @param isStatic specifies if the method is static |
| * @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 |
| */ |
| Binding put(InvocationPlugin plugin, boolean isStatic, boolean allowOverwrite, Type declaringClass, String name, Type... argumentTypes) { |
| assert resolvedRegistrations == null : "registration is closed"; |
| String internalName = MetaUtil.toInternalName(declaringClass.getTypeName()); |
| assert isStatic || argumentTypes[0] == declaringClass; |
| assert deferredRegistrations != null : "initial registration is closed - use " + LateRegistration.class.getName() + " for late registrations"; |
| |
| ClassPlugins classPlugins = registrations.get(internalName); |
| if (classPlugins == null) { |
| classPlugins = new ClassPlugins(); |
| registrations.put(internalName, classPlugins); |
| } |
| Binding binding = new Binding(plugin, isStatic, name, argumentTypes); |
| classPlugins.register(binding, allowOverwrite); |
| return binding; |
| } |
| |
| InvocationPlugin get(ResolvedJavaMethod method) { |
| if (resolvedRegistrations != null) { |
| return resolvedRegistrations.get(method); |
| } else { |
| if (!method.isBridge()) { |
| ResolvedJavaType declaringClass = method.getDeclaringClass(); |
| flushDeferrables(); |
| String internalName = declaringClass.getName(); |
| ClassPlugins classPlugins = registrations.get(internalName); |
| InvocationPlugin res = null; |
| if (classPlugins != null) { |
| res = classPlugins.get(method); |
| } |
| if (res == null) { |
| LateClassPlugins lcp = findLateClassPlugins(internalName); |
| if (lcp != null) { |
| res = lcp.get(method); |
| } |
| } |
| if (res != null) { |
| if (canBeIntrinsified(declaringClass)) { |
| return res; |
| } |
| } |
| } else { |
| // Supporting plugins for bridge methods would require including |
| // the return type in the registered signature. Until needed, |
| // this extra complexity is best avoided. |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Determines if methods in a given class can have invocation plugins. |
| * |
| * @param declaringClass the class to test |
| */ |
| protected boolean canBeIntrinsified(ResolvedJavaType declaringClass) { |
| return true; |
| } |
| |
| LateClassPlugins findLateClassPlugins(String internalClassName) { |
| for (LateClassPlugins lcp = lateRegistrations; lcp != null; lcp = lcp.next) { |
| if (lcp.className.equals(internalClassName)) { |
| return lcp; |
| } |
| } |
| return null; |
| } |
| |
| private void flushDeferrables() { |
| if (deferredRegistrations != null) { |
| synchronized (this) { |
| if (deferredRegistrations != null) { |
| for (Runnable deferrable : deferredRegistrations) { |
| deferrable.run(); |
| } |
| deferredRegistrations = null; |
| } |
| } |
| } |
| } |
| |
| synchronized void registerLate(Type declaringType, List<Binding> bindings) { |
| String internalName = MetaUtil.toInternalName(declaringType.getTypeName()); |
| assert findLateClassPlugins(internalName) == null : "Cannot have more than one late registration of invocation plugins for " + internalName; |
| LateClassPlugins lateClassPlugins = new LateClassPlugins(lateRegistrations, internalName); |
| for (Binding b : bindings) { |
| lateClassPlugins.register(b); |
| } |
| lateRegistrations = lateClassPlugins; |
| } |
| |
| private synchronized boolean closeLateRegistrations() { |
| if (lateRegistrations == null || lateRegistrations.className != CLOSED_LATE_CLASS_PLUGIN) { |
| lateRegistrations = new LateClassPlugins(lateRegistrations, CLOSED_LATE_CLASS_PLUGIN); |
| } |
| return true; |
| } |
| |
| /** |
| * Processes deferred registrations and then closes this object for future registration. |
| */ |
| public void closeRegistration() { |
| assert closeLateRegistrations(); |
| flushDeferrables(); |
| } |
| |
| public boolean isEmpty() { |
| if (resolvedRegistrations != null) { |
| return resolvedRegistrations.isEmpty(); |
| } |
| return registrations.size() == 0 && lateRegistrations == null; |
| } |
| |
| /** |
| * The plugins {@linkplain #lookupInvocation(ResolvedJavaMethod) searched} before searching in |
| * this object. |
| */ |
| protected final InvocationPlugins parent; |
| |
| /** |
| * Creates a set of invocation plugins with no parent. |
| */ |
| public InvocationPlugins() { |
| this(null); |
| } |
| |
| /** |
| * Creates a set of invocation plugins. |
| * |
| * @param parent if non-null, this object will be searched first when looking up plugins |
| */ |
| public InvocationPlugins(InvocationPlugins parent) { |
| InvocationPlugins p = parent; |
| this.parent = p; |
| this.registrations = new HashMap<>(); |
| this.resolvedRegistrations = null; |
| } |
| |
| /** |
| * Creates a closed set of invocation plugins for a set of resolved methods. Such an object |
| * cannot have further plugins registered. |
| */ |
| public InvocationPlugins(Map<ResolvedJavaMethod, InvocationPlugin> plugins, InvocationPlugins parent) { |
| this.parent = parent; |
| this.registrations = null; |
| this.deferredRegistrations = null; |
| Map<ResolvedJavaMethod, InvocationPlugin> map = new HashMap<>(plugins.size()); |
| |
| for (Map.Entry<ResolvedJavaMethod, InvocationPlugin> entry : plugins.entrySet()) { |
| map.put(entry.getKey(), entry.getValue()); |
| } |
| this.resolvedRegistrations = map; |
| } |
| |
| 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; |
| } |
| Binding binding = put(plugin, isStatic, allowOverwrite, declaringClass, name, argumentTypes); |
| assert Checks.check(this, declaringClass, binding); |
| assert Checks.checkResolvable(isOptional, declaringClass, binding); |
| } |
| |
| /** |
| * 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 registered invocation plugins. |
| * |
| * @return a map from class names in {@linkplain MetaUtil#toInternalName(String) internal} form |
| * to the invocation plugin bindings for methods in the class |
| */ |
| public Map<String, List<Binding>> getBindings(boolean includeParents) { |
| Map<String, List<Binding>> res = new HashMap<>(); |
| if (parent != null && includeParents) { |
| res.putAll(parent.getBindings(true)); |
| } |
| if (resolvedRegistrations != null) { |
| for (Map.Entry<ResolvedJavaMethod, InvocationPlugin> e : resolvedRegistrations.entrySet()) { |
| ResolvedJavaMethod method = e.getKey(); |
| InvocationPlugin plugin = e.getValue(); |
| String type = method.getDeclaringClass().getName(); |
| List<Binding> bindings = res.get(type); |
| if (bindings == null) { |
| bindings = new ArrayList<>(); |
| res.put(type, bindings); |
| } |
| bindings.add(new Binding(method, plugin)); |
| } |
| } else { |
| flushDeferrables(); |
| for (Map.Entry<String, ClassPlugins> e : registrations.entrySet()) { |
| String type = e.getKey(); |
| ClassPlugins cp = e.getValue(); |
| collectBindingsTo(res, type, cp); |
| } |
| for (LateClassPlugins lcp = lateRegistrations; lcp != null; lcp = lcp.next) { |
| String type = lcp.className; |
| collectBindingsTo(res, type, lcp); |
| } |
| } |
| return res; |
| } |
| |
| private static void collectBindingsTo(Map<String, List<Binding>> res, String type, ClassPlugins cp) { |
| for (Map.Entry<String, Binding> e : cp.bindings.entrySet()) { |
| List<Binding> bindings = res.get(type); |
| if (bindings == null) { |
| bindings = new ArrayList<>(); |
| res.put(type, bindings); |
| } |
| for (Binding b = e.getValue(); b != null; b = b.next) { |
| bindings.add(b); |
| } |
| } |
| } |
| |
| /** |
| * Gets the invocation plugins {@linkplain #lookupInvocation(ResolvedJavaMethod) searched} |
| * before searching in this object. |
| */ |
| public InvocationPlugins getParent() { |
| return parent; |
| } |
| |
| @Override |
| public String toString() { |
| List<String> all = new ArrayList<>(); |
| for (Map.Entry<String, List<Binding>> e : getBindings(false).entrySet()) { |
| String c = MetaUtil.internalNameToJava(e.getKey(), true, false); |
| for (Binding b : e.getValue()) { |
| all.add(c + '.' + b); |
| } |
| } |
| Collections.sort(all); |
| StringBuilder buf = new StringBuilder(); |
| String nl = String.format("%n"); |
| for (String s : all) { |
| if (buf.length() != 0) { |
| buf.append(nl); |
| } |
| buf.append(s); |
| } |
| if (parent != null) { |
| if (buf.length() != 0) { |
| buf.append(nl); |
| } |
| buf.append("// parent").append(nl).append(parent); |
| } |
| return buf.toString(); |
| } |
| |
| /** |
| * Code only used in assertions. Putting this in a separate class reduces class load time. |
| */ |
| private static class Checks { |
| private static final int MAX_ARITY = 7; |
| /** |
| * 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()][]); |
| } |
| |
| static boolean containsBinding(InvocationPlugins p, Type declaringType, Binding key) { |
| String internalName = MetaUtil.toInternalName(declaringType.getTypeName()); |
| ClassPlugins classPlugins = p.registrations.get(internalName); |
| return classPlugins != null && classPlugins.lookup(key) != null; |
| } |
| |
| public static boolean check(InvocationPlugins plugins, Type declaringType, Binding binding) { |
| InvocationPlugin plugin = binding.plugin; |
| InvocationPlugins p = plugins.parent; |
| while (p != null) { |
| assert !containsBinding(p, declaringType, binding) : "a plugin is already registered for " + binding; |
| 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 = parseParameters(binding.argumentsDescriptor).size(); |
| assert arguments < SIGS.length : format("need to extend %s to support method with %d arguments: %s", InvocationPlugin.class.getSimpleName(), arguments, binding); |
| 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", binding)); |
| } |
| |
| static boolean checkResolvable(boolean isOptional, Type declaringType, Binding binding) { |
| Class<?> declaringClass = InvocationPlugins.resolveType(declaringType, isOptional); |
| if (declaringClass == null) { |
| return true; |
| } |
| if (binding.name.equals("<init>")) { |
| if (resolveConstructor(declaringClass, binding) == null && !isOptional) { |
| throw new AssertionError(String.format("Constructor not found: %s%s", declaringClass.getName(), binding.argumentsDescriptor)); |
| } |
| } else { |
| if (resolveMethod(declaringClass, binding) == null && !isOptional) { |
| throw new AssertionError(String.format("Method not found: %s.%s%s", declaringClass.getName(), binding.name, binding.argumentsDescriptor)); |
| } |
| } |
| return true; |
| } |
| } |
| |
| /** |
| * 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 (type instanceof OptionalLazySymbol) { |
| return ((OptionalLazySymbol) type).resolve(); |
| } |
| return resolveClass(type.getTypeName(), optional); |
| } |
| |
| private static List<String> toInternalTypeNames(Class<?>[] types) { |
| String[] res = new String[types.length]; |
| for (int i = 0; i < types.length; i++) { |
| res[i] = MetaUtil.toInternalName(types[i].getTypeName()); |
| } |
| return Arrays.asList(res); |
| } |
| |
| /** |
| * Resolves a given binding to a method in a given class. If more than one method with the |
| * parameter types matching {@code binding} is found and the return types of all the matching |
| * methods form an inheritance chain, the one with the most specific type is returned; otherwise |
| * {@link NoSuchMethodError} is thrown. |
| * |
| * @param declaringClass the class to search for a method matching {@code binding} |
| * @return the method (if any) in {@code declaringClass} matching binding |
| */ |
| public static Method resolveMethod(Class<?> declaringClass, Binding binding) { |
| if (binding.name.equals("<init>")) { |
| return null; |
| } |
| Method[] methods = declaringClass.getDeclaredMethods(); |
| List<String> parameterTypeNames = parseParameters(binding.argumentsDescriptor); |
| for (int i = 0; i < methods.length; ++i) { |
| Method m = methods[i]; |
| if (binding.isStatic == Modifier.isStatic(m.getModifiers()) && m.getName().equals(binding.name)) { |
| if (parameterTypeNames.equals(toInternalTypeNames(m.getParameterTypes()))) { |
| for (int j = i + 1; j < methods.length; ++j) { |
| Method other = methods[j]; |
| if (binding.isStatic == Modifier.isStatic(other.getModifiers()) && other.getName().equals(binding.name)) { |
| if (parameterTypeNames.equals(toInternalTypeNames(other.getParameterTypes()))) { |
| if (m.getReturnType().isAssignableFrom(other.getReturnType())) { |
| // `other` has a more specific return type - choose it |
| // (m is most likely a bridge method) |
| m = other; |
| } else { |
| if (!other.getReturnType().isAssignableFrom(m.getReturnType())) { |
| throw new NoSuchMethodError(String.format( |
| "Found 2 methods with same name and parameter types but unrelated return types:%n %s%n %s", m, other)); |
| } |
| } |
| } |
| } |
| } |
| return m; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Resolves a given binding to a constructor in a given class. |
| * |
| * @param declaringClass the class to search for a constructor matching {@code binding} |
| * @return the constructor (if any) in {@code declaringClass} matching binding |
| */ |
| public static Constructor<?> resolveConstructor(Class<?> declaringClass, Binding binding) { |
| if (!binding.name.equals("<init>")) { |
| return null; |
| } |
| Constructor<?>[] constructors = declaringClass.getDeclaredConstructors(); |
| List<String> parameterTypeNames = parseParameters(binding.argumentsDescriptor); |
| for (int i = 0; i < constructors.length; ++i) { |
| Constructor<?> c = constructors[i]; |
| if (parameterTypeNames.equals(toInternalTypeNames(c.getParameterTypes()))) { |
| return c; |
| } |
| } |
| return null; |
| } |
| |
| private static List<String> parseParameters(String argumentsDescriptor) { |
| assert argumentsDescriptor.startsWith("(") && argumentsDescriptor.endsWith(")") : argumentsDescriptor; |
| List<String> res = new ArrayList<>(); |
| int cur = 1; |
| int end = argumentsDescriptor.length() - 1; |
| while (cur != end) { |
| char first; |
| int start = cur; |
| do { |
| first = argumentsDescriptor.charAt(cur++); |
| } while (first == '['); |
| |
| switch (first) { |
| case 'L': |
| int endObject = argumentsDescriptor.indexOf(';', cur); |
| if (endObject == -1) { |
| throw new GraalError("Invalid object type at index %d in signature: %s", cur, argumentsDescriptor); |
| } |
| cur = endObject + 1; |
| break; |
| case 'V': |
| case 'I': |
| case 'B': |
| case 'C': |
| case 'D': |
| case 'F': |
| case 'J': |
| case 'S': |
| case 'Z': |
| break; |
| default: |
| throw new GraalError("Invalid character at index %d in signature: %s", cur, argumentsDescriptor); |
| } |
| res.add(argumentsDescriptor.substring(start, cur)); |
| } |
| return res; |
| } |
| } |