view graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/graphbuilderconf/InvocationPlugins.java @ 23389:69a9c45e2143

Add constructor to create InvocationPlugins from map of already resolved methods
author Christian Wimmer <christian.wimmer@oracle.com>
date Thu, 04 Feb 2016 17:18:03 -0800
parents 2160e7da7fb0
children
line wrap: on
line source

/*
 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.oracle.graal.nodes.graphbuilderconf;

import static java.lang.String.format;

import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jdk.vm.ci.common.JVMCIError;
import jdk.vm.ci.meta.MetaAccessProvider;
import jdk.vm.ci.meta.MetaUtil;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import sun.misc.Launcher;

import com.oracle.graal.graph.Node;
import com.oracle.graal.graph.iterators.NodeIterable;
import com.oracle.graal.nodes.ValueNode;

/**
 * Manages a set of {@link InvocationPlugin}s.
 */
public class InvocationPlugins {

    public static class InvocationPluginReceiver implements InvocationPlugin.Receiver {
        private final GraphBuilderContext parser;
        private ValueNode[] args;
        private ValueNode value;

        public InvocationPluginReceiver(GraphBuilderContext parser) {
            this.parser = parser;
        }

        @Override
        public ValueNode get() {
            assert args != null : "Cannot get the receiver of a static method";
            if (value == null) {
                value = parser.nullCheckedValue(args[0]);
                if (value != args[0]) {
                    args[0] = value;
                }
            }
            return value;
        }

        @Override
        public boolean isConstant() {
            return args[0].isConstant();
        }

        public InvocationPluginReceiver init(ResolvedJavaMethod targetMethod, ValueNode[] newArgs) {
            if (!targetMethod.isStatic()) {
                this.args = newArgs;
                this.value = null;
                return this;
            }
            return null;
        }
    }

    /**
     * A symbol that is lazily {@linkplain OptionalLazySymbol#resolve() resolved} to a {@link Type}.
     */
    static class OptionalLazySymbol implements Type {
        private static final Class<?> MASK_NULL = OptionalLazySymbol.class;
        private final String name;
        private Class<?> resolved;

        OptionalLazySymbol(String name) {
            this.name = name;
        }

        public String getTypeName() {
            return name;
        }

        /**
         * Gets the resolved {@link Class} corresponding to this symbol or {@code null} if
         * resolution fails.
         */
        public Class<?> resolve() {
            if (resolved == null) {
                Class<?> resolvedOrNull = resolveClass(name, true);
                resolved = resolvedOrNull == null ? MASK_NULL : resolvedOrNull;
            }
            return resolved == MASK_NULL ? null : resolved;
        }

        @Override
        public String toString() {
            return name;
        }
    }

    /**
     * Utility for
     * {@linkplain InvocationPlugins#register(InvocationPlugin, Class, String, Class...)
     * registration} of invocation plugins.
     */
    public static class Registration {

        private final InvocationPlugins plugins;
        private final Type declaringType;
        private boolean allowOverwrite;

        /**
         * Creates an object for registering {@link InvocationPlugin}s for methods declared by a
         * given class.
         *
         * @param plugins where to register the plugins
         * @param declaringType the class declaring the methods for which plugins will be registered
         *            via this object
         */
        public Registration(InvocationPlugins plugins, Type declaringType) {
            this.plugins = plugins;
            this.declaringType = declaringType;
        }

        /**
         * Creates an object for registering {@link InvocationPlugin}s for methods declared by a
         * given class.
         *
         * @param plugins where to register the plugins
         * @param declaringClassName the name of the class class declaring the methods for which
         *            plugins will be registered via this object
         */
        public Registration(InvocationPlugins plugins, String declaringClassName) {
            this.plugins = plugins;
            this.declaringType = new OptionalLazySymbol(declaringClassName);
        }

        /**
         * Configures this registration to allow or disallow overwriting of invocation plugins.
         */
        public Registration setAllowOverwrite(boolean allowOverwrite) {
            this.allowOverwrite = allowOverwrite;
            return this;
        }

        /**
         * Registers a plugin for a method with no arguments.
         *
         * @param name the name of the method
         * @param plugin the plugin to be registered
         */
        public void register0(String name, InvocationPlugin plugin) {
            plugins.register(plugin, false, allowOverwrite, declaringType, name);
        }

        /**
         * Registers a plugin for a method with 1 argument.
         *
         * @param name the name of the method
         * @param plugin the plugin to be registered
         */
        public void register1(String name, Type arg, InvocationPlugin plugin) {
            plugins.register(plugin, false, allowOverwrite, declaringType, name, arg);
        }

        /**
         * Registers a plugin for a method with 2 arguments.
         *
         * @param name the name of the method
         * @param plugin the plugin to be registered
         */
        public void register2(String name, Type arg1, Type arg2, InvocationPlugin plugin) {
            plugins.register(plugin, false, allowOverwrite, declaringType, name, arg1, arg2);
        }

        /**
         * Registers a plugin for a method with 3 arguments.
         *
         * @param name the name of the method
         * @param plugin the plugin to be registered
         */
        public void register3(String name, Type arg1, Type arg2, Type arg3, InvocationPlugin plugin) {
            plugins.register(plugin, false, allowOverwrite, declaringType, name, arg1, arg2, arg3);
        }

        /**
         * Registers a plugin for a method with 4 arguments.
         *
         * @param name the name of the method
         * @param plugin the plugin to be registered
         */
        public void register4(String name, Type arg1, Type arg2, Type arg3, Type arg4, InvocationPlugin plugin) {
            plugins.register(plugin, false, allowOverwrite, declaringType, name, arg1, arg2, arg3, arg4);
        }

        /**
         * Registers a plugin for a method with 5 arguments.
         *
         * @param name the name of the method
         * @param plugin the plugin to be registered
         */
        public void register5(String name, Type arg1, Type arg2, Type arg3, Type arg4, Type arg5, InvocationPlugin plugin) {
            plugins.register(plugin, false, allowOverwrite, declaringType, name, arg1, arg2, arg3, arg4, arg5);
        }

        /**
         * Registers a plugin for an optional method with no arguments.
         *
         * @param name the name of the method
         * @param plugin the plugin to be registered
         */
        public void registerOptional0(String name, InvocationPlugin plugin) {
            plugins.register(plugin, true, allowOverwrite, declaringType, name);
        }

        /**
         * Registers a plugin for an optional method with 1 argument.
         *
         * @param name the name of the method
         * @param plugin the plugin to be registered
         */
        public void registerOptional1(String name, Type arg, InvocationPlugin plugin) {
            plugins.register(plugin, true, allowOverwrite, declaringType, name, arg);
        }

        /**
         * Registers a plugin for an optional method with 2 arguments.
         *
         * @param name the name of the method
         * @param plugin the plugin to be registered
         */
        public void registerOptional2(String name, Type arg1, Type arg2, InvocationPlugin plugin) {
            plugins.register(plugin, true, allowOverwrite, declaringType, name, arg1, arg2);
        }

        /**
         * Registers a plugin for an optional method with 3 arguments.
         *
         * @param name the name of the method
         * @param plugin the plugin to be registered
         */
        public void registerOptional3(String name, Type arg1, Type arg2, Type arg3, InvocationPlugin plugin) {
            plugins.register(plugin, true, allowOverwrite, declaringType, name, arg1, arg2, arg3);
        }

        /**
         * Registers a plugin for an optional method with 4 arguments.
         *
         * @param name the name of the method
         * @param plugin the plugin to be registered
         */
        public void registerOptional4(String name, Type arg1, Type arg2, Type arg3, Type arg4, InvocationPlugin plugin) {
            plugins.register(plugin, true, allowOverwrite, declaringType, name, arg1, arg2, arg3, arg4);
        }

        /**
         * Registers a plugin that implements a method based on the bytecode of a substitute method.
         *
         * @param substituteDeclaringClass the class declaring the substitute method
         * @param name the name of both the original and substitute method
         * @param argumentTypes the argument types of the method. Element 0 of this array must be
         *            the {@link Class} value for {@link InvocationPlugin.Receiver} iff the method
         *            is non-static. Upon returning, element 0 will have been rewritten to
         *            {@code declaringClass}
         */
        public void registerMethodSubstitution(Class<?> substituteDeclaringClass, String name, Type... argumentTypes) {
            registerMethodSubstitution(substituteDeclaringClass, name, name, argumentTypes);
        }

        /**
         * Registers a plugin that implements a method based on the bytecode of a substitute method.
         *
         * @param substituteDeclaringClass the class declaring the substitute method
         * @param name the name of both the original method
         * @param substituteName the name of the substitute method
         * @param argumentTypes the argument types of the method. Element 0 of this array must be
         *            the {@link Class} value for {@link InvocationPlugin.Receiver} iff the method
         *            is non-static. Upon returning, element 0 will have been rewritten to
         *            {@code declaringClass}
         */
        public void registerMethodSubstitution(Class<?> substituteDeclaringClass, String name, String substituteName, Type... argumentTypes) {
            MethodSubstitutionPlugin plugin = new MethodSubstitutionPlugin(substituteDeclaringClass, substituteName, argumentTypes);
            plugins.register(plugin, false, allowOverwrite, declaringType, name, argumentTypes);
        }
    }

    /**
     * Key for a method.
     */
    static class MethodKey {
        final boolean isStatic;

        /**
         * This method is optional. This is used for new API methods not present in previous JDK
         * versions.
         */
        final boolean isOptional;

        final String name;
        final Type[] argumentTypes;
        final InvocationPlugin value;

        /**
         * Used to lazily initialize {@link #resolved}.
         */
        private final MetaAccessProvider metaAccess;

        private volatile ResolvedJavaMethod resolved;

        MethodKey(MetaAccessProvider metaAccess, InvocationPlugin data, boolean isStatic, boolean isOptional, String name, Type... argumentTypes) {
            this.metaAccess = metaAccess;
            this.value = data;
            this.isStatic = isStatic;
            this.isOptional = isOptional;
            this.name = name;
            this.argumentTypes = argumentTypes;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof MethodKey) {
                MethodKey that = (MethodKey) obj;
                boolean res = this.name.equals(that.name) && areEqual(this.argumentTypes, that.argumentTypes);
                assert !res || this.isStatic == that.isStatic;
                return res;
            }
            return false;
        }

        private static boolean areEqual(Type[] args1, Type[] args2) {
            if (args1.length == args2.length) {
                for (int i = 0; i < args1.length; i++) {
                    if (!args1[i].getTypeName().equals(args2[i].getTypeName())) {
                        return false;
                    }
                }
                return true;
            }
            return false;
        }

        public int getDeclaredParameterCount() {
            return isStatic ? argumentTypes.length : argumentTypes.length - 1;
        }

        @Override
        public int hashCode() {
            return name.hashCode();
        }

        private ResolvedJavaMethod resolve(Class<?> declaringClass) {
            if (resolved == null) {
                Executable method = resolveJava(declaringClass);
                if (method == null) {
                    return null;
                }
                resolved = metaAccess.lookupJavaMethod(method);
            }
            return resolved;
        }

        private Executable resolveJava(Class<?> declaringClass) {
            try {
                Executable res;
                Class<?>[] parameterTypes = resolveTypes(argumentTypes, isStatic ? 0 : 1, argumentTypes.length);
                if (name.equals("<init>")) {
                    res = declaringClass.getDeclaredConstructor(parameterTypes);
                } else {
                    res = declaringClass.getDeclaredMethod(name, parameterTypes);
                }
                assert Modifier.isStatic(res.getModifiers()) == isStatic;
                return res;
            } catch (NoSuchMethodException | SecurityException e) {
                if (isOptional) {
                    return null;
                }
                throw new InternalError(e);
            }
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder(name).append('(');
            for (Type p : argumentTypes) {
                if (sb.charAt(sb.length() - 1) != '(') {
                    sb.append(", ");
                }
                sb.append(p.getTypeName());
            }
            return sb.append(')').toString();
        }
    }

    private final MetaAccessProvider metaAccess;

    private final Map<String, ClassPlugins> registrations = new HashMap<>();

    /**
     * Deferred registrations as well as guard for initialization. The guard uses double-checked
     * locking which is why this field is {@code volatile}.
     */
    private volatile List<Runnable> deferredRegistrations = new ArrayList<>();

    /**
     * Adds a {@link Runnable} for doing registration deferred until the first time
     * {@link #get(ResolvedJavaMethod)} or {@link #closeRegistration()} is called on this object.
     */
    public void defer(Runnable deferrable) {
        assert deferredRegistrations != null : "registration is closed";
        deferredRegistrations.add(deferrable);
    }

    /**
     * Per-class invocation plugins.
     */
    protected static class ClassPlugins {
        private final Type declaringType;

        private final List<MethodKey> registrations = new ArrayList<>();

        public ClassPlugins(Type declaringClass) {
            this.declaringType = declaringClass;
        }

        /**
         * Entry map that is initialized upon first call to {@link #get(ResolvedJavaMethod)}.
         *
         * Note: this must be volatile as threads may race to initialize it.
         */
        private volatile Map<ResolvedJavaMethod, InvocationPlugin> entries;

        void initializeMap() {
            if (entries == null) {
                if (registrations.isEmpty()) {
                    entries = Collections.emptyMap();
                } else {
                    Class<?> declaringClass = resolveType(declaringType, true);
                    if (declaringClass == null) {
                        // An optional type that could not be resolved
                        entries = Collections.emptyMap();
                    } else {
                        Map<ResolvedJavaMethod, InvocationPlugin> newEntries = new HashMap<>();
                        for (MethodKey methodKey : registrations) {
                            ResolvedJavaMethod m = methodKey.resolve(declaringClass);
                            newEntries.put(m, methodKey.value);
                            if (entries != null) {
                                // Another thread finished initializing entries first
                                return;
                            }
                        }
                        entries = newEntries;
                    }
                }
            }
        }

        public InvocationPlugin get(ResolvedJavaMethod method) {
            if (entries == null) {
                initializeMap();
            }
            return entries.get(method);
        }

        public void register(MethodKey methodKey, boolean allowOverwrite) {
            assert entries == null : "registration is closed";
            if (allowOverwrite) {
                int index = registrations.indexOf(methodKey);
                if (index >= 0) {
                    registrations.set(index, methodKey);
                    return;
                }
            } else {
                assert !registrations.contains(methodKey) : "a value is already registered for " + declaringType + "." + methodKey;
            }
            registrations.add(methodKey);
        }
    }

    /**
     * Adds an entry to this map for a specified method.
     *
     * @param value value to be associated with the specified method
     * @param isStatic specifies if the method is static
     * @param isOptional specifies if the method is optional
     * @param declaringClass the class declaring the method
     * @param name the name of the method
     * @param argumentTypes the argument types of the method. Element 0 of this array must be
     *            {@code declaringClass} iff the method is non-static.
     * @return an object representing the method
     */
    MethodKey put(InvocationPlugin value, boolean isStatic, boolean isOptional, boolean allowOverwrite, Type declaringClass, String name, Type... argumentTypes) {
        assert isStatic || argumentTypes[0] == declaringClass;

        String internalName = MetaUtil.toInternalName(declaringClass.getTypeName());
        ClassPlugins classPlugins = registrations.get(internalName);
        if (classPlugins == null) {
            classPlugins = new ClassPlugins(declaringClass);
            registrations.put(internalName, classPlugins);
        }
        assert isStatic || argumentTypes[0] == declaringClass;
        MethodKey methodKey = new MethodKey(metaAccess, value, isStatic, isOptional, name, argumentTypes);
        classPlugins.register(methodKey, allowOverwrite);
        return methodKey;
    }

    /**
     * Determines if a method denoted by a given {@link MethodKey} is in this map.
     */
    boolean containsKey(Type declaringType, MethodKey key) {
        String internalName = MetaUtil.toInternalName(declaringType.getTypeName());
        ClassPlugins classPlugins = registrations.get(internalName);
        return classPlugins != null && classPlugins.registrations.contains(key);
    }

    InvocationPlugin get(ResolvedJavaMethod method) {
        flushDeferrables();
        String internalName = method.getDeclaringClass().getName();
        ClassPlugins classPlugins = registrations.get(internalName);
        if (classPlugins != null) {
            return classPlugins.get(method);
        }
        return null;
    }

    private void flushDeferrables() {
        if (deferredRegistrations != null) {
            synchronized (this) {
                if (deferredRegistrations != null) {
                    for (Runnable deferrable : deferredRegistrations) {
                        deferrable.run();
                    }
                    deferredRegistrations = null;
                }
            }
            for (Map.Entry<String, ClassPlugins> e : registrations.entrySet()) {
                e.getValue().initializeMap();
            }
        }
    }

    /**
     * Disallows new registrations of new plugins, and creates the internal tables for method
     * lookup.
     */
    public void closeRegistration() {
        flushDeferrables();
        for (Map.Entry<String, ClassPlugins> e : registrations.entrySet()) {
            e.getValue().initializeMap();
        }
    }

    public int size() {
        return registrations.size();
    }

    /**
     * The plugins {@linkplain #lookupInvocation(ResolvedJavaMethod) searched} before searching in
     * this object.
     */
    protected final InvocationPlugins parent;

    private InvocationPlugins(InvocationPlugins parent, MetaAccessProvider metaAccess) {
        this.metaAccess = metaAccess;
        InvocationPlugins p = parent;
        this.parent = p;
    }

    /**
     * Creates a set of invocation plugins with a non-null {@linkplain #getParent() parent}.
     */
    public InvocationPlugins(InvocationPlugins parent) {
        this(parent, parent.getMetaAccess());
    }

    public InvocationPlugins(Map<ResolvedJavaMethod, InvocationPlugin> plugins, InvocationPlugins parent, MetaAccessProvider metaAccess) {
        this.metaAccess = metaAccess;
        this.parent = parent;

        this.deferredRegistrations = null;

        for (Map.Entry<ResolvedJavaMethod, InvocationPlugin> entry : plugins.entrySet()) {
            ResolvedJavaMethod method = entry.getKey();
            InvocationPlugin plugin = entry.getValue();

            String internalName = method.getDeclaringClass().getName();
            ClassPlugins classPlugins = registrations.get(internalName);
            if (classPlugins == null) {
                classPlugins = new ClassPlugins(null);
                registrations.put(internalName, classPlugins);
                classPlugins.entries = new HashMap<>();
            }

            classPlugins.entries.put(method, plugin);
        }
    }

    public MetaAccessProvider getMetaAccess() {
        return metaAccess;
    }

    public InvocationPlugins(MetaAccessProvider metaAccess) {
        this(null, metaAccess);
    }

    private void register(InvocationPlugin plugin, boolean isOptional, boolean allowOverwrite, Type declaringClass, String name, Type... argumentTypes) {
        boolean isStatic = argumentTypes.length == 0 || argumentTypes[0] != InvocationPlugin.Receiver.class;
        if (!isStatic) {
            argumentTypes[0] = declaringClass;
        }
        MethodKey methodKey = put(plugin, isStatic, isOptional, allowOverwrite, declaringClass, name, argumentTypes);
        assert Checker.check(this, declaringClass, methodKey, plugin);
    }

    /**
     * Registers an invocation plugin for a given method. There must be no plugin currently
     * registered for {@code method}.
     *
     * @param argumentTypes the argument types of the method. Element 0 of this array must be the
     *            {@link Class} value for {@link InvocationPlugin.Receiver} iff the method is
     *            non-static. Upon returning, element 0 will have been rewritten to
     *            {@code declaringClass}
     */
    public void register(InvocationPlugin plugin, Type declaringClass, String name, Type... argumentTypes) {
        register(plugin, false, false, declaringClass, name, argumentTypes);
    }

    public void register(InvocationPlugin plugin, String declaringClass, String name, Type... argumentTypes) {
        register(plugin, false, false, new OptionalLazySymbol(declaringClass), name, argumentTypes);
    }

    /**
     * Registers an invocation plugin for a given, optional method. There must be no plugin
     * currently registered for {@code method}.
     *
     * @param argumentTypes the argument types of the method. Element 0 of this array must be the
     *            {@link Class} value for {@link InvocationPlugin.Receiver} iff the method is
     *            non-static. Upon returning, element 0 will have been rewritten to
     *            {@code declaringClass}
     */
    public void registerOptional(InvocationPlugin plugin, Type declaringClass, String name, Type... argumentTypes) {
        register(plugin, true, false, declaringClass, name, argumentTypes);
    }

    /**
     * Gets the plugin for a given method.
     *
     * @param method the method to lookup
     * @return the plugin associated with {@code method} or {@code null} if none exists
     */
    public InvocationPlugin lookupInvocation(ResolvedJavaMethod method) {
        if (parent != null) {
            InvocationPlugin plugin = parent.lookupInvocation(method);
            if (plugin != null) {
                return plugin;
            }
        }
        return get(method);
    }

    /**
     * Gets the invocation plugins {@linkplain #lookupInvocation(ResolvedJavaMethod) searched}
     * before searching in this object.
     */
    public InvocationPlugins getParent() {
        return parent;
    }

    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder();
        registrations.forEach((name, cp) -> buf.append(name).append('.').append(cp).append(", "));
        String s = buf.toString();
        if (buf.length() != 0) {
            s = s.substring(buf.length() - ", ".length());
        }
        return s + " / parent: " + this.parent;
    }

    private static class Checker {
        private static final int MAX_ARITY = 5;
        /**
         * The set of all {@link InvocationPlugin#apply} method signatures.
         */
        static final Class<?>[][] SIGS;

        static {
            ArrayList<Class<?>[]> sigs = new ArrayList<>(MAX_ARITY);
            for (Method method : InvocationPlugin.class.getDeclaredMethods()) {
                if (!Modifier.isStatic(method.getModifiers()) && method.getName().equals("apply")) {
                    Class<?>[] sig = method.getParameterTypes();
                    assert sig[0] == GraphBuilderContext.class;
                    assert sig[1] == ResolvedJavaMethod.class;
                    assert sig[2] == InvocationPlugin.Receiver.class;
                    assert Arrays.asList(sig).subList(3, sig.length).stream().allMatch(c -> c == ValueNode.class);
                    while (sigs.size() < sig.length - 2) {
                        sigs.add(null);
                    }
                    sigs.set(sig.length - 3, sig);
                }
            }
            assert sigs.indexOf(null) == -1 : format("need to add an apply() method to %s that takes %d %s arguments ", InvocationPlugin.class.getName(), sigs.indexOf(null),
                            ValueNode.class.getSimpleName());
            SIGS = sigs.toArray(new Class<?>[sigs.size()][]);
        }

        public static boolean check(InvocationPlugins plugins, Type declaringType, MethodKey method, InvocationPlugin plugin) {
            InvocationPlugins p = plugins.parent;
            while (p != null) {
                assert !p.containsKey(declaringType, method) : "a plugin is already registered for " + method;
                p = p.parent;
            }
            if (plugin instanceof ForeignCallPlugin || plugin instanceof GeneratedInvocationPlugin) {
                return true;
            }
            if (plugin instanceof MethodSubstitutionPlugin) {
                MethodSubstitutionPlugin msplugin = (MethodSubstitutionPlugin) plugin;
                msplugin.getJavaSubstitute();
                return true;
            }
            int arguments = method.getDeclaredParameterCount();
            assert arguments < SIGS.length : format("need to extend %s to support method with %d arguments: %s", InvocationPlugin.class.getSimpleName(), arguments, method);
            for (Method m : plugin.getClass().getDeclaredMethods()) {
                if (m.getName().equals("apply")) {
                    Class<?>[] parameterTypes = m.getParameterTypes();
                    if (Arrays.equals(SIGS[arguments], parameterTypes)) {
                        return true;
                    }
                }
            }
            throw new AssertionError(format("graph builder plugin for %s not found", method));
        }
    }

    /**
     * Checks a set of nodes added to the graph by an {@link InvocationPlugin}.
     *
     * @param b the graph builder that applied the plugin
     * @param plugin a plugin that was just applied
     * @param newNodes the nodes added to the graph by {@code plugin}
     * @throws AssertionError if any check fail
     */
    public void checkNewNodes(GraphBuilderContext b, InvocationPlugin plugin, NodeIterable<Node> newNodes) {
        if (parent != null) {
            parent.checkNewNodes(b, plugin, newNodes);
        }
    }

    /**
     * Resolves a name to a class.
     *
     * @param className the name of the class to resolve
     * @param optional if true, resolution failure returns null
     * @return the resolved class or null if resolution fails and {@code optional} is true
     */
    public static Class<?> resolveClass(String className, boolean optional) {
        try {
            // Need to use launcher class path to handle classes
            // that are not on the boot class path
            ClassLoader cl = Launcher.getLauncher().getClassLoader();
            return Class.forName(className, false, cl);
        } catch (ClassNotFoundException e) {
            if (optional) {
                return null;
            }
            throw new JVMCIError("Could not resolve type " + className);
        }
    }

    /**
     * Resolves a {@link Type} to a {@link Class}.
     *
     * @param type the type to resolve
     * @param optional if true, resolution failure returns null
     * @return the resolved class or null if resolution fails and {@code optional} is true
     */
    public static Class<?> resolveType(Type type, boolean optional) {
        if (type instanceof Class) {
            return (Class<?>) type;
        }
        if (optional && type instanceof OptionalLazySymbol) {
            return ((OptionalLazySymbol) type).resolve();
        }
        return resolveClass(type.getTypeName(), optional);
    }

    private static final Class<?>[] NO_CLASSES = {};

    /**
     * Resolves an array of {@link Type}s to an array of {@link Class}es.
     *
     * @param types the types to resolve
     * @param from the initial index of the range to be resolved, inclusive
     * @param to the final index of the range to be resolved, exclusive
     * @return the resolved class or null if resolution fails and {@code optional} is true
     */
    public static Class<?>[] resolveTypes(Type[] types, int from, int to) {
        int length = to - from;
        if (length <= 0) {
            return NO_CLASSES;
        }
        Class<?>[] classes = new Class<?>[length];
        for (int i = 0; i < length; i++) {
            classes[i] = resolveType(types[i + from], false);
        }
        return classes;
    }
}