< 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 >