--- old/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/graphbuilderconf/InvocationPlugins.java 2017-04-28 11:49:02.000000000 +0200 +++ new/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/graphbuilderconf/InvocationPlugins.java 2017-04-28 11:49:02.000000000 +0200 @@ -23,8 +23,9 @@ 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; @@ -32,10 +33,8 @@ 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; @@ -46,12 +45,24 @@ 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 { @@ -260,6 +271,26 @@ } /** + * 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 @@ -337,168 +368,151 @@ */ @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 { + + private InvocationPlugins plugins; + private final List bindings = new ArrayList<>(); + private final Type declaringType; - ResolvedJavaMethodKey(ResolvedJavaMethod method) { - this.method = method; + /** + * 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; } - @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; - } - } - } - } + /** + * 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; } - return false; - } - @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 + * method + * descriptor 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("")) { - 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("") || !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("") || !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 resolvedRegistrations; - private final Map 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 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 deferredRegistrations = new ArrayList<>(); @@ -512,119 +526,159 @@ } /** - * 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 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 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. - */ - private volatile Map 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 newEntries = new HashMap<>(); - for (MethodKey methodKey : registrations) { - ResolvedJavaMethod m = methodKey.resolve(declaringClass); - if (m != null) { - newEntries.put(new ResolvedJavaMethodKey(m), methodKey.value); - if (entries != null) { - // Another thread finished initializing entries first - return; - } - } - } - entries = newEntries; + * @return the invocation plugin for {@code method} or {@code null} + */ + InvocationPlugin get(ResolvedJavaMethod method) { + assert !method.isBridge(); + Binding binding = bindings.get(method.getName()); + while (binding != null) { + if (method.isStatic() == binding.isStatic) { + if (method.getSignature().toMethodDescriptor().startsWith(binding.argumentsDescriptor)) { + return binding.plugin; } } + binding = binding.next; } + return null; } - public InvocationPlugin get(ResolvedJavaMethod method) { - if (!isClosed()) { - initializeMap(); - } - return entries.get(new ResolvedJavaMethodKey(method)); - } - - public void register(MethodKey methodKey, boolean allowOverwrite) { - assert !isClosed() : "registration is closed: " + methodKey + " " + Arrays.toString(entries.keySet().toArray()); + public void register(Binding binding, boolean allowOverwrite) { if (allowOverwrite) { - int index = registrations.indexOf(methodKey); - if (index >= 0) { - registrations.set(index, methodKey); + if (lookup(binding) != null) { + register(binding); return; } } else { - assert !registrations.contains(methodKey) : "a value is already registered for " + declaringType + "." + methodKey; + assert lookup(binding) == null : "a value is already registered for " + binding; + } + register(binding); + } + + InvocationPlugin lookup(Binding binding) { + Binding b = bindings.get(binding.name); + while (b != null) { + if (b.isStatic == binding.isStatic && b.argumentsDescriptor.equals(binding.argumentsDescriptor)) { + return b.plugin; + } + b = b.next; } - registrations.add(methodKey); + return null; } - public boolean isClosed() { - return entries != null; + /** + * Registers {@code binding}. + */ + void register(Binding binding) { + Binding head = bindings.get(binding.name); + assert binding.next == null; + binding.next = head; + bindings.put(binding.name, binding); + } + } + + static class LateClassPlugins extends ClassPlugins { + static final String CLOSED_LATE_CLASS_PLUGIN = "-----"; + private final String className; + private final LateClassPlugins next; + + LateClassPlugins(LateClassPlugins next, String className) { + assert next == null || next.className != CLOSED_LATE_CLASS_PLUGIN : "Late registration of invocation plugins is closed"; + this.next = next; + this.className = className; } } /** - * 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; + Binding binding = new Binding(plugin, isStatic, name, argumentTypes); + classPlugins.register(binding, allowOverwrite); + return binding; + } + + InvocationPlugin get(ResolvedJavaMethod method) { + if (resolvedRegistrations != null) { + return resolvedRegistrations.get(method); + } else { + if (!method.isBridge()) { + ResolvedJavaType declaringClass = method.getDeclaringClass(); + flushDeferrables(); + String internalName = declaringClass.getName(); + ClassPlugins classPlugins = registrations.get(internalName); + InvocationPlugin res = null; + if (classPlugins != null) { + res = classPlugins.get(method); + } + if (res == null) { + LateClassPlugins lcp = findLateClassPlugins(internalName); + if (lcp != null) { + res = lcp.get(method); + } + } + if (res != null) { + if (canBeIntrinsified(declaringClass)) { + return res; + } + } + } else { + // Supporting plugins for bridge methods would require including + // the return type in the registered signature. Until needed, + // this extra complexity is best avoided. + } + } + return null; } /** - * Determines if a method denoted by a given {@link MethodKey} is in this map. + * Determines if methods in a given class can have invocation plugins. + * + * @param declaringClass the class to test */ - boolean containsKey(Type declaringType, MethodKey key) { - String internalName = MetaUtil.toInternalName(declaringType.getTypeName()); - ClassPlugins classPlugins = registrations.get(internalName); - return classPlugins != null && classPlugins.registrations.contains(key); + protected boolean canBeIntrinsified(ResolvedJavaType declaringClass) { + return true; } - InvocationPlugin get(ResolvedJavaMethod method) { - flushDeferrables(); - String internalName = method.getDeclaringClass().getName(); - ClassPlugins classPlugins = registrations.get(internalName); - if (classPlugins != null) { - return classPlugins.get(method); + LateClassPlugins findLateClassPlugins(String internalClassName) { + for (LateClassPlugins lcp = lateRegistrations; lcp != null; lcp = lcp.next) { + if (lcp.className.equals(internalClassName)) { + return lcp; + } } return null; } @@ -639,25 +693,39 @@ deferredRegistrations = null; } } - for (Map.Entry e : registrations.entrySet()) { - e.getValue().initializeMap(); - } } } + synchronized void registerLate(Type declaringType, List 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 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; } /** @@ -666,47 +734,39 @@ */ 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 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 plugins, InvocationPlugins parent) { this.parent = parent; - + this.registrations = null; this.deferredRegistrations = null; + Map map = new HashMap<>(plugins.size()); for (Map.Entry 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) { @@ -714,8 +774,9 @@ 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); } /** @@ -765,23 +826,56 @@ } /** - * 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 getMethods() { - Set res = new HashSet<>(); - if (parent != null) { - res.addAll(parent.getMethods()); - } - flushDeferrables(); - for (ClassPlugins cp : registrations.values()) { - for (ResolvedJavaMethodKey key : cp.entries.keySet()) { - res.add(key.method); + public Map> getBindings(boolean includeParents) { + Map> res = new HashMap<>(); + if (parent != null && includeParents) { + res.putAll(parent.getBindings(true)); + } + if (resolvedRegistrations != null) { + for (Map.Entry e : resolvedRegistrations.entrySet()) { + ResolvedJavaMethod method = e.getKey(); + InvocationPlugin plugin = e.getValue(); + String type = method.getDeclaringClass().getName(); + List bindings = res.get(type); + if (bindings == null) { + bindings = new ArrayList<>(); + res.put(type, bindings); + } + bindings.add(new Binding(method, plugin)); + } + } else { + flushDeferrables(); + for (Map.Entry 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> res, String type, ClassPlugins cp) { + for (Map.Entry e : cp.bindings.entrySet()) { + List 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. @@ -792,17 +886,36 @@ @Override public String toString() { + List all = new ArrayList<>(); + for (Map.Entry> 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(); - if (buf.length() != 0) { - s = s.substring(buf.length() - ", ".length()); + String nl = String.format("%n"); + for (String s : all) { + if (buf.length() != 0) { + buf.append(nl); + } + buf.append(s); + } + if (parent != null) { + if (buf.length() != 0) { + buf.append(nl); + } + buf.append("// parent").append(nl).append(parent); } - return s + " / parent: " + this.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. */ @@ -828,10 +941,17 @@ 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) { @@ -843,8 +963,8 @@ 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(); @@ -853,7 +973,24 @@ } } } - 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("")) { + 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; } } @@ -904,31 +1041,119 @@ 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 toInternalTypeNames(Class[] types) { + String[] res = new String[types.length]; + for (int i = 0; i < types.length; i++) { + res[i] = MetaUtil.toInternalName(types[i].getTypeName()); + } + return Arrays.asList(res); + } + + /** + * Resolves a given binding to a method in a given class. If more than one method with the + * parameter types matching {@code binding} is found and the return types of all the matching + * methods form an inheritance chain, the one with the most specific type is returned; otherwise + * {@link NoSuchMethodError} is thrown. + * + * @param declaringClass the class to search for a method matching {@code binding} + * @return the method (if any) in {@code declaringClass} matching binding + */ + public static Method resolveMethod(Class declaringClass, Binding binding) { + if (binding.name.equals("")) { + return null; + } + Method[] methods = declaringClass.getDeclaredMethods(); + List 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 an array of {@link Type}s to an array of {@link Class}es. + * Resolves a given binding to a constructor in a given class. * - * @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 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("")) { + return null; } - return classes; + Constructor[] constructors = declaringClass.getDeclaredConstructors(); + List parameterTypeNames = parseParameters(binding.argumentsDescriptor); + for (int i = 0; i < constructors.length; ++i) { + Constructor c = constructors[i]; + if (parameterTypeNames.equals(toInternalTypeNames(c.getParameterTypes()))) { + return c; + } + } + return null; + } + + private static List parseParameters(String argumentsDescriptor) { + assert argumentsDescriptor.startsWith("(") && argumentsDescriptor.endsWith(")") : argumentsDescriptor; + List 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; } }