< prev index next >

src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/graphbuilderconf/InvocationPlugins.java

Print this page

        

@@ -21,39 +21,50 @@
  * 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.Executable;
+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.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;
+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;

@@ -258,10 +269,30 @@
         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
          */

@@ -335,172 +366,155 @@
          *            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);
-            plugins.register(plugin, false, allowOverwrite, declaringType, name, argumentTypes);
+            return plugin;
         }
+
     }
 
     /**
-     * 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.
+     * Utility for registering plugins after Graal may have been initialized. Registrations made via
+     * this class are not finalized until {@link #close} is called.
      */
-    static final class ResolvedJavaMethodKey {
-        private final ResolvedJavaMethod method;
+    public static class LateRegistration implements AutoCloseable {
 
-        ResolvedJavaMethodKey(ResolvedJavaMethod method) {
-            this.method = method;
-        }
+        private InvocationPlugins plugins;
+        private final List<Binding> bindings = new ArrayList<>();
+        private final Type declaringType;
 
-        @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;
-                            }
-                        }
-                    }
-                }
+        /**
+         * 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;
             }
-            return false;
+
+        /**
+         * 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;
         }
 
-        @Override
-        public int hashCode() {
-            return this.method.getName().hashCode();
+            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 String toString() {
-            return "ResolvedJavaMethodKey<" + method + ">";
+        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;
         }
     }
 
     /**
-     * Key for {@linkplain ClassPlugins#registrations registering} an {@link InvocationPlugin} for a
-     * specific method.
+     * Associates an {@link InvocationPlugin} with the details of a method it substitutes.
      */
-    static class MethodKey {
-        final boolean isStatic;
+    public static class Binding {
+        /**
+         * The plugin this binding is for.
+         */
+        public final InvocationPlugin plugin;
 
         /**
-         * This method is optional. This is used for new API methods not present in previous JDK
-         * versions.
+         * Specifies if the associated method is static.
          */
-        final boolean isOptional;
+        public final boolean isStatic;
 
-        final String name;
-        final Type[] argumentTypes;
-        final InvocationPlugin value;
+        /**
+         * The name of the associated method.
+         */
+        public final String name;
 
         /**
-         * Used to lazily initialize {@link #resolved}.
+         * 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.
          */
-        private final MetaAccessProvider metaAccess;
+        public final String argumentsDescriptor;
 
-        private volatile ResolvedJavaMethod resolved;
+        /**
+         * Link in a list of bindings.
+         */
+        private Binding next;
 
-        MethodKey(MetaAccessProvider metaAccess, InvocationPlugin data, boolean isStatic, boolean isOptional, String name, Type... argumentTypes) {
-            this.metaAccess = metaAccess;
-            this.value = data;
+        Binding(InvocationPlugin data, boolean isStatic, String name, Type... argumentTypes) {
+            this.plugin = 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);
-            }
+            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() {
-            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();
+            return name + argumentsDescriptor;
         }
     }
 
-    private final MetaAccessProvider metaAccess;
+    /**
+     * 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;
 
-    private final Map<String, ClassPlugins> registrations = new HashMap<>();
+    /**
+     * 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 guard for initialization. The guard uses double-checked
-     * locking which is why this field is {@code volatile}.
+     * 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

@@ -510,123 +524,163 @@
         assert deferredRegistrations != null : "registration is closed";
         deferredRegistrations.add(deferrable);
     }
 
     /**
-     * Per-class invocation plugins.
+     * Support for registering plugins once this object may be accessed by multiple threads.
      */
-    protected static class ClassPlugins {
-        private final Type declaringType;
+    private volatile LateClassPlugins lateRegistrations;
 
-        private final List<MethodKey> registrations = new ArrayList<>();
+    /**
+     * Per-class bindings.
+     */
+    static class ClassPlugins {
 
-        public ClassPlugins(Type declaringClass) {
-            this.declaringType = declaringClass;
-        }
+        /**
+         * Maps method names to binding lists.
+         */
+        private final Map<String, Binding> bindings = new HashMap<>();
 
         /**
-         * Entry map that is initialized upon first call to {@link #get(ResolvedJavaMethod)}.
+         * Gets the invocation plugin for a given method.
          *
-         * Note: this must be volatile as threads may race to initialize it.
+         * @return the invocation plugin for {@code method} or {@code null}
          */
-        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;
+        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;
                         }
-                        entries = newEntries;
+            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);
         }
 
-        public InvocationPlugin get(ResolvedJavaMethod method) {
-            if (!isClosed()) {
-                initializeMap();
+        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;
             }
-            return entries.get(new ResolvedJavaMethodKey(method));
+                b = b.next;
         }
-
-        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;
+            return null;
                 }
-            } else {
-                assert !registrations.contains(methodKey) : "a value is already registered for " + declaringType + "." + methodKey;
+
+        /**
+         * 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);
             }
-            registrations.add(methodKey);
         }
 
-        public boolean isClosed() {
-            return entries != null;
+    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;
         }
     }
 
     /**
-     * Adds an entry to this map for a specified method.
+     * Registers a binding of a method to an invocation plugin.
      *
-     * @param value value to be associated with the specified method
+     * @param plugin invocation plugin 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) {
+    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";
 
-        String internalName = MetaUtil.toInternalName(declaringClass.getTypeName());
         ClassPlugins classPlugins = registrations.get(internalName);
         if (classPlugins == null) {
-            classPlugins = new ClassPlugins(declaringClass);
+            classPlugins = new ClassPlugins();
             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);
+        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 = method.getDeclaringClass().getName();
+                String internalName = declaringClass.getName();
         ClassPlugins classPlugins = registrations.get(internalName);
+                InvocationPlugin res = null;
         if (classPlugins != null) {
-            return classPlugins.get(method);
+                    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() {

@@ -637,87 +691,94 @@
                         deferrable.run();
                     }
                     deferredRegistrations = null;
                 }
             }
-            for (Map.Entry<String, ClassPlugins> e : registrations.entrySet()) {
-                e.getValue().initializeMap();
             }
         }
+
+    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;
     }
 
     /**
-     * Disallows new registrations of new plugins, and creates the internal tables for method
-     * lookup.
+     * Processes deferred registrations and then closes this object for future registration.
      */
     public void closeRegistration() {
+        assert closeLateRegistrations();
         flushDeferrables();
-        for (Map.Entry<String, ClassPlugins> e : registrations.entrySet()) {
-            e.getValue().initializeMap();
-        }
     }
 
-    public int size() {
-        return registrations.size();
+    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;
 
-    private InvocationPlugins(InvocationPlugins parent, MetaAccessProvider metaAccess) {
-        this.metaAccess = metaAccess;
-        InvocationPlugins p = parent;
-        this.parent = p;
+    /**
+     * Creates a set of invocation plugins with no parent.
+     */
+    public InvocationPlugins() {
+        this(null);
     }
 
     /**
-     * Creates a set of invocation plugins with a non-null {@linkplain #getParent() parent}.
+     * 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) {
-        this(parent, parent.getMetaAccess());
+        InvocationPlugins p = parent;
+        this.parent = p;
+        this.registrations = new HashMap<>();
+        this.resolvedRegistrations = null;
     }
 
-    public InvocationPlugins(Map<ResolvedJavaMethod, InvocationPlugin> plugins, InvocationPlugins parent, MetaAccessProvider metaAccess) {
-        this.metaAccess = metaAccess;
+    /**
+     * 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()) {
-            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);
-        }
+            map.put(entry.getKey(), entry.getValue());
     }
-
-    public MetaAccessProvider getMetaAccess() {
-        return metaAccess;
-    }
-
-    public InvocationPlugins(MetaAccessProvider metaAccess) {
-        this(null, metaAccess);
+        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;
         }
-        MethodKey methodKey = put(plugin, isStatic, isOptional, allowOverwrite, declaringClass, name, argumentTypes);
-        assert Checker.check(this, declaringClass, methodKey, plugin);
+        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}.

@@ -763,48 +824,100 @@
         }
         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.
+     * 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 Set<ResolvedJavaMethod> getMethods() {
-        Set<ResolvedJavaMethod> res = new HashSet<>();
-        if (parent != null) {
-            res.addAll(parent.getMethods());
+    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 (ClassPlugins cp : registrations.values()) {
-            for (ResolvedJavaMethodKey key : cp.entries.keySet()) {
-                res.add(key.method);
+            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();
-        registrations.forEach((name, cp) -> buf.append(name).append('.').append(cp).append(", "));
-        String s = buf.toString();
+        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) {
-            s = s.substring(buf.length() - ", ".length());
+                buf.append(nl);
         }
-        return s + " / parent: " + this.parent;
+            buf.append("// parent").append(nl).append(parent);
+        }
+        return buf.toString();
     }
 
-    private static class Checker {
-        private static final int MAX_ARITY = 5;
+    /**
+     * 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;
 

@@ -826,14 +939,21 @@
             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) {
+        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 !p.containsKey(declaringType, method) : "a plugin is already registered for " + method;
+                assert !containsBinding(p, declaringType, binding) : "a plugin is already registered for " + binding;
                 p = p.parent;
             }
             if (plugin instanceof ForeignCallPlugin || plugin instanceof GeneratedInvocationPlugin) {
                 return true;
             }

@@ -841,21 +961,38 @@
                 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);
+            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", method));
+            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}.

@@ -902,33 +1039,121 @@
      */
     public static Class<?> resolveType(Type type, boolean optional) {
         if (type instanceof Class) {
             return (Class<?>) type;
         }
-        if (optional && type instanceof OptionalLazySymbol) {
+        if (type instanceof OptionalLazySymbol) {
             return ((OptionalLazySymbol) type).resolve();
         }
         return resolveClass(type.getTypeName(), optional);
     }
 
-    private static final Class<?>[] NO_CLASSES = {};
+    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 an array of {@link Type}s to an array of {@link Class}es.
+     * 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 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
+     * @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 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);
+    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 classes;
+        }
+        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;
     }
 }
< prev index next >