1 /*
   2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package org.graalvm.compiler.nodes.graphbuilderconf;
  24 
  25 import static java.lang.String.format;
  26 
  27 import java.lang.reflect.Executable;
  28 import java.lang.reflect.Method;
  29 import java.lang.reflect.Modifier;
  30 import java.lang.reflect.Type;
  31 import java.util.ArrayList;
  32 import java.util.Arrays;
  33 import java.util.List;
  34 import java.util.Map;
  35 import org.graalvm.compiler.api.replacements.MethodSubstitution;
  36 import org.graalvm.compiler.api.replacements.MethodSubstitutionRegistry;
  37 import org.graalvm.compiler.bytecode.BytecodeProvider;
  38 import org.graalvm.compiler.debug.GraalError;
  39 import org.graalvm.compiler.graph.Node;
  40 import org.graalvm.compiler.graph.iterators.NodeIterable;
  41 import org.graalvm.compiler.nodes.ValueNode;
  42 import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin.Receiver;
  43 import org.graalvm.util.Equivalence;
  44 import org.graalvm.util.EconomicMap;
  45 import org.graalvm.util.EconomicSet;
  46 import org.graalvm.util.UnmodifiableMapCursor;
  47 
  48 import jdk.vm.ci.meta.MetaAccessProvider;
  49 import jdk.vm.ci.meta.MetaUtil;
  50 import jdk.vm.ci.meta.ResolvedJavaMethod;
  51 
  52 /**
  53  * Manages a set of {@link InvocationPlugin}s.
  54  */
  55 public class InvocationPlugins {
  56 
  57     public static class InvocationPluginReceiver implements InvocationPlugin.Receiver {
  58         private final GraphBuilderContext parser;
  59         private ValueNode[] args;
  60         private ValueNode value;
  61 
  62         public InvocationPluginReceiver(GraphBuilderContext parser) {
  63             this.parser = parser;
  64         }
  65 
  66         @Override
  67         public ValueNode get(boolean performNullCheck) {
  68             assert args != null : "Cannot get the receiver of a static method";
  69             if (!performNullCheck) {
  70                 return args[0];
  71             }
  72             if (value == null) {
  73                 value = parser.nullCheckedValue(args[0]);
  74                 if (value != args[0]) {
  75                     args[0] = value;
  76                 }
  77             }
  78             return value;
  79         }
  80 
  81         @Override
  82         public boolean isConstant() {
  83             return args[0].isConstant();
  84         }
  85 
  86         public InvocationPluginReceiver init(ResolvedJavaMethod targetMethod, ValueNode[] newArgs) {
  87             if (!targetMethod.isStatic()) {
  88                 this.args = newArgs;
  89                 this.value = null;
  90                 return this;
  91             }
  92             return null;
  93         }
  94     }
  95 
  96     /**
  97      * A symbol that is lazily {@linkplain OptionalLazySymbol#resolve() resolved} to a {@link Type}.
  98      */
  99     static class OptionalLazySymbol implements Type {
 100         private static final Class<?> MASK_NULL = OptionalLazySymbol.class;
 101         private final String name;
 102         private Class<?> resolved;
 103 
 104         OptionalLazySymbol(String name) {
 105             this.name = name;
 106         }
 107 
 108         @Override
 109         public String getTypeName() {
 110             return name;
 111         }
 112 
 113         /**
 114          * Gets the resolved {@link Class} corresponding to this symbol or {@code null} if
 115          * resolution fails.
 116          */
 117         public Class<?> resolve() {
 118             if (resolved == null) {
 119                 Class<?> resolvedOrNull = resolveClass(name, true);
 120                 resolved = resolvedOrNull == null ? MASK_NULL : resolvedOrNull;
 121             }
 122             return resolved == MASK_NULL ? null : resolved;
 123         }
 124 
 125         @Override
 126         public String toString() {
 127             return name;
 128         }
 129     }
 130 
 131     /**
 132      * Utility for {@linkplain InvocationPlugins#register(InvocationPlugin, Class, String, Class...)
 133      * registration} of invocation plugins.
 134      */
 135     public static class Registration implements MethodSubstitutionRegistry {
 136 
 137         private final InvocationPlugins plugins;
 138         private final Type declaringType;
 139         private final BytecodeProvider methodSubstitutionBytecodeProvider;
 140         private boolean allowOverwrite;
 141 
 142         @Override
 143         public Class<?> getReceiverType() {
 144             return Receiver.class;
 145         }
 146 
 147         /**
 148          * Creates an object for registering {@link InvocationPlugin}s for methods declared by a
 149          * given class.
 150          *
 151          * @param plugins where to register the plugins
 152          * @param declaringType the class declaring the methods for which plugins will be registered
 153          *            via this object
 154          */
 155         public Registration(InvocationPlugins plugins, Type declaringType) {
 156             this.plugins = plugins;
 157             this.declaringType = declaringType;
 158             this.methodSubstitutionBytecodeProvider = null;
 159         }
 160 
 161         /**
 162          * Creates an object for registering {@link InvocationPlugin}s for methods declared by a
 163          * given class.
 164          *
 165          * @param plugins where to register the plugins
 166          * @param declaringType the class declaring the methods for which plugins will be registered
 167          *            via this object
 168          * @param methodSubstitutionBytecodeProvider provider used to get the bytecodes to parse for
 169          *            method substitutions
 170          */
 171         public Registration(InvocationPlugins plugins, Type declaringType, BytecodeProvider methodSubstitutionBytecodeProvider) {
 172             this.plugins = plugins;
 173             this.declaringType = declaringType;
 174             this.methodSubstitutionBytecodeProvider = methodSubstitutionBytecodeProvider;
 175         }
 176 
 177         /**
 178          * Creates an object for registering {@link InvocationPlugin}s for methods declared by a
 179          * given class.
 180          *
 181          * @param plugins where to register the plugins
 182          * @param declaringClassName the name of the class class declaring the methods for which
 183          *            plugins will be registered via this object
 184          * @param methodSubstitutionBytecodeProvider provider used to get the bytecodes to parse for
 185          *            method substitutions
 186          */
 187         public Registration(InvocationPlugins plugins, String declaringClassName, BytecodeProvider methodSubstitutionBytecodeProvider) {
 188             this.plugins = plugins;
 189             this.declaringType = new OptionalLazySymbol(declaringClassName);
 190             this.methodSubstitutionBytecodeProvider = methodSubstitutionBytecodeProvider;
 191         }
 192 
 193         /**
 194          * Configures this registration to allow or disallow overwriting of invocation plugins.
 195          */
 196         public Registration setAllowOverwrite(boolean allowOverwrite) {
 197             this.allowOverwrite = allowOverwrite;
 198             return this;
 199         }
 200 
 201         /**
 202          * Registers a plugin for a method with no arguments.
 203          *
 204          * @param name the name of the method
 205          * @param plugin the plugin to be registered
 206          */
 207         public void register0(String name, InvocationPlugin plugin) {
 208             plugins.register(plugin, false, allowOverwrite, declaringType, name);
 209         }
 210 
 211         /**
 212          * Registers a plugin for a method with 1 argument.
 213          *
 214          * @param name the name of the method
 215          * @param plugin the plugin to be registered
 216          */
 217         public void register1(String name, Type arg, InvocationPlugin plugin) {
 218             plugins.register(plugin, false, allowOverwrite, declaringType, name, arg);
 219         }
 220 
 221         /**
 222          * Registers a plugin for a method with 2 arguments.
 223          *
 224          * @param name the name of the method
 225          * @param plugin the plugin to be registered
 226          */
 227         public void register2(String name, Type arg1, Type arg2, InvocationPlugin plugin) {
 228             plugins.register(plugin, false, allowOverwrite, declaringType, name, arg1, arg2);
 229         }
 230 
 231         /**
 232          * Registers a plugin for a method with 3 arguments.
 233          *
 234          * @param name the name of the method
 235          * @param plugin the plugin to be registered
 236          */
 237         public void register3(String name, Type arg1, Type arg2, Type arg3, InvocationPlugin plugin) {
 238             plugins.register(plugin, false, allowOverwrite, declaringType, name, arg1, arg2, arg3);
 239         }
 240 
 241         /**
 242          * Registers a plugin for a method with 4 arguments.
 243          *
 244          * @param name the name of the method
 245          * @param plugin the plugin to be registered
 246          */
 247         public void register4(String name, Type arg1, Type arg2, Type arg3, Type arg4, InvocationPlugin plugin) {
 248             plugins.register(plugin, false, allowOverwrite, declaringType, name, arg1, arg2, arg3, arg4);
 249         }
 250 
 251         /**
 252          * Registers a plugin for a method with 5 arguments.
 253          *
 254          * @param name the name of the method
 255          * @param plugin the plugin to be registered
 256          */
 257         public void register5(String name, Type arg1, Type arg2, Type arg3, Type arg4, Type arg5, InvocationPlugin plugin) {
 258             plugins.register(plugin, false, allowOverwrite, declaringType, name, arg1, arg2, arg3, arg4, arg5);
 259         }
 260 
 261         /**
 262          * Registers a plugin for a method with 6 arguments.
 263          *
 264          * @param name the name of the method
 265          * @param plugin the plugin to be registered
 266          */
 267         public void register6(String name, Type arg1, Type arg2, Type arg3, Type arg4, Type arg5, Type arg6, InvocationPlugin plugin) {
 268             plugins.register(plugin, false, allowOverwrite, declaringType, name, arg1, arg2, arg3, arg4, arg5, arg6);
 269         }
 270 
 271         /**
 272          * Registers a plugin for a method with 7 arguments.
 273          *
 274          * @param name the name of the method
 275          * @param plugin the plugin to be registered
 276          */
 277         public void register7(String name, Type arg1, Type arg2, Type arg3, Type arg4, Type arg5, Type arg6, Type arg7, InvocationPlugin plugin) {
 278             plugins.register(plugin, false, allowOverwrite, declaringType, name, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
 279         }
 280 
 281         /**
 282          * Registers a plugin for an optional method with no arguments.
 283          *
 284          * @param name the name of the method
 285          * @param plugin the plugin to be registered
 286          */
 287         public void registerOptional0(String name, InvocationPlugin plugin) {
 288             plugins.register(plugin, true, allowOverwrite, declaringType, name);
 289         }
 290 
 291         /**
 292          * Registers a plugin for an optional method with 1 argument.
 293          *
 294          * @param name the name of the method
 295          * @param plugin the plugin to be registered
 296          */
 297         public void registerOptional1(String name, Type arg, InvocationPlugin plugin) {
 298             plugins.register(plugin, true, allowOverwrite, declaringType, name, arg);
 299         }
 300 
 301         /**
 302          * Registers a plugin for an optional method with 2 arguments.
 303          *
 304          * @param name the name of the method
 305          * @param plugin the plugin to be registered
 306          */
 307         public void registerOptional2(String name, Type arg1, Type arg2, InvocationPlugin plugin) {
 308             plugins.register(plugin, true, allowOverwrite, declaringType, name, arg1, arg2);
 309         }
 310 
 311         /**
 312          * Registers a plugin for an optional method with 3 arguments.
 313          *
 314          * @param name the name of the method
 315          * @param plugin the plugin to be registered
 316          */
 317         public void registerOptional3(String name, Type arg1, Type arg2, Type arg3, InvocationPlugin plugin) {
 318             plugins.register(plugin, true, allowOverwrite, declaringType, name, arg1, arg2, arg3);
 319         }
 320 
 321         /**
 322          * Registers a plugin for an optional method with 4 arguments.
 323          *
 324          * @param name the name of the method
 325          * @param plugin the plugin to be registered
 326          */
 327         public void registerOptional4(String name, Type arg1, Type arg2, Type arg3, Type arg4, InvocationPlugin plugin) {
 328             plugins.register(plugin, true, allowOverwrite, declaringType, name, arg1, arg2, arg3, arg4);
 329         }
 330 
 331         /**
 332          * Registers a plugin that implements a method based on the bytecode of a substitute method.
 333          *
 334          * @param substituteDeclaringClass the class declaring the substitute method
 335          * @param name the name of both the original and substitute method
 336          * @param argumentTypes the argument types of the method. Element 0 of this array must be
 337          *            the {@link Class} value for {@link InvocationPlugin.Receiver} iff the method
 338          *            is non-static. Upon returning, element 0 will have been rewritten to
 339          *            {@code declaringClass}
 340          */
 341         @Override
 342         public void registerMethodSubstitution(Class<?> substituteDeclaringClass, String name, Type... argumentTypes) {
 343             registerMethodSubstitution(substituteDeclaringClass, name, name, argumentTypes);
 344         }
 345 
 346         /**
 347          * Registers a plugin that implements a method based on the bytecode of a substitute method.
 348          *
 349          * @param substituteDeclaringClass the class declaring the substitute method
 350          * @param name the name of both the original method
 351          * @param substituteName the name of the substitute method
 352          * @param argumentTypes the argument types of the method. Element 0 of this array must be
 353          *            the {@link Class} value for {@link InvocationPlugin.Receiver} iff the method
 354          *            is non-static. Upon returning, element 0 will have been rewritten to
 355          *            {@code declaringClass}
 356          */
 357         @Override
 358         public void registerMethodSubstitution(Class<?> substituteDeclaringClass, String name, String substituteName, Type... argumentTypes) {
 359             MethodSubstitutionPlugin plugin = createMethodSubstitution(substituteDeclaringClass, substituteName, argumentTypes);
 360             plugins.register(plugin, false, allowOverwrite, declaringType, name, argumentTypes);
 361         }
 362 
 363         public MethodSubstitutionPlugin createMethodSubstitution(Class<?> substituteDeclaringClass, String substituteName, Type... argumentTypes) {
 364             assert methodSubstitutionBytecodeProvider != null : "Registration used for method substitutions requires a non-null methodSubstitutionBytecodeProvider";
 365             MethodSubstitutionPlugin plugin = new MethodSubstitutionPlugin(methodSubstitutionBytecodeProvider, substituteDeclaringClass, substituteName, argumentTypes);
 366             return plugin;
 367         }
 368 
 369     }
 370 
 371     /**
 372      * Key for a {@linkplain ClassPlugins#entries resolved} plugin registration. Due to the
 373      * possibility of class redefinition, we cannot directly use {@link ResolvedJavaMethod}s as
 374      * keys. A {@link ResolvedJavaMethod} implementation might implement {@code equals()} and
 375      * {@code hashCode()} based on internal representation subject to change by class redefinition.
 376      */
 377     static final class ResolvedJavaMethodKey {
 378         private final ResolvedJavaMethod method;
 379 
 380         ResolvedJavaMethodKey(ResolvedJavaMethod method) {
 381             this.method = method;
 382         }
 383 
 384         @Override
 385         public boolean equals(Object obj) {
 386             if (obj instanceof ResolvedJavaMethodKey) {
 387                 ResolvedJavaMethodKey that = (ResolvedJavaMethodKey) obj;
 388                 if (this.method.isStatic() == that.method.isStatic()) {
 389                     if (this.method.getDeclaringClass().equals(that.method.getDeclaringClass())) {
 390                         if (this.method.getName().equals(that.method.getName())) {
 391                             if (this.method.getSignature().equals(that.method.getSignature())) {
 392                                 return true;
 393                             }
 394                         }
 395                     }
 396                 }
 397             }
 398             return false;
 399         }
 400 
 401         @Override
 402         public int hashCode() {
 403             return this.method.getName().hashCode();
 404         }
 405 
 406         @Override
 407         public String toString() {
 408             return "ResolvedJavaMethodKey<" + method + ">";
 409         }
 410     }
 411 
 412     /**
 413      * Key for {@linkplain ClassPlugins#registrations registering} an {@link InvocationPlugin} for a
 414      * specific method.
 415      */
 416     static class MethodKey {
 417         final boolean isStatic;
 418 
 419         /**
 420          * This method is optional. This is used for new API methods not present in previous JDK
 421          * versions.
 422          */
 423         final boolean isOptional;
 424 
 425         final String name;
 426         final Type[] argumentTypes;
 427         final InvocationPlugin value;
 428 
 429         /**
 430          * Used to lazily initialize {@link #resolved}.
 431          */
 432         private final MetaAccessProvider metaAccess;
 433 
 434         private volatile ResolvedJavaMethod resolved;
 435 
 436         MethodKey(MetaAccessProvider metaAccess, InvocationPlugin data, boolean isStatic, boolean isOptional, String name, Type... argumentTypes) {
 437             this.metaAccess = metaAccess;
 438             this.value = data;
 439             this.isStatic = isStatic;
 440             this.isOptional = isOptional;
 441             this.name = name;
 442             this.argumentTypes = argumentTypes;
 443         }
 444 
 445         @Override
 446         public boolean equals(Object obj) {
 447             if (obj instanceof MethodKey) {
 448                 MethodKey that = (MethodKey) obj;
 449                 boolean res = this.name.equals(that.name) && areEqual(this.argumentTypes, that.argumentTypes);
 450                 assert !res || this.isStatic == that.isStatic;
 451                 return res;
 452             }
 453             return false;
 454         }
 455 
 456         private static boolean areEqual(Type[] args1, Type[] args2) {
 457             if (args1.length == args2.length) {
 458                 for (int i = 0; i < args1.length; i++) {
 459                     if (!args1[i].getTypeName().equals(args2[i].getTypeName())) {
 460                         return false;
 461                     }
 462                 }
 463                 return true;
 464             }
 465             return false;
 466         }
 467 
 468         public int getDeclaredParameterCount() {
 469             return isStatic ? argumentTypes.length : argumentTypes.length - 1;
 470         }
 471 
 472         @Override
 473         public int hashCode() {
 474             return name.hashCode();
 475         }
 476 
 477         private ResolvedJavaMethod resolve(Class<?> declaringClass) {
 478             if (resolved == null) {
 479                 Executable method = resolveJava(declaringClass);
 480                 if (method == null) {
 481                     return null;
 482                 }
 483                 resolved = metaAccess.lookupJavaMethod(method);
 484             }
 485             return resolved;
 486         }
 487 
 488         private Executable resolveJava(Class<?> declaringClass) {
 489             try {
 490                 Executable res;
 491                 Class<?>[] parameterTypes = resolveTypes(argumentTypes, isStatic ? 0 : 1, argumentTypes.length);
 492                 if (name.equals("<init>")) {
 493                     res = declaringClass.getDeclaredConstructor(parameterTypes);
 494                 } else {
 495                     res = declaringClass.getDeclaredMethod(name, parameterTypes);
 496                 }
 497                 assert Modifier.isStatic(res.getModifiers()) == isStatic : res;
 498                 return res;
 499             } catch (NoSuchMethodException | SecurityException e) {
 500                 if (isOptional) {
 501                     return null;
 502                 }
 503                 throw new InternalError(e);
 504             }
 505         }
 506 
 507         @Override
 508         public String toString() {
 509             StringBuilder sb = new StringBuilder(name).append('(');
 510             for (Type p : argumentTypes) {
 511                 if (sb.charAt(sb.length() - 1) != '(') {
 512                     sb.append(", ");
 513                 }
 514                 sb.append(p.getTypeName());
 515             }
 516             return sb.append(')').toString();
 517         }
 518     }
 519 
 520     private final MetaAccessProvider metaAccess;
 521 
 522     private final EconomicMap<String, ClassPlugins> registrations = EconomicMap.create(Equivalence.DEFAULT);
 523 
 524     /**
 525      * Deferred registrations as well as guard for initialization. The guard uses double-checked
 526      * locking which is why this field is {@code volatile}.
 527      */
 528     private volatile List<Runnable> deferredRegistrations = new ArrayList<>();
 529 
 530     /**
 531      * Adds a {@link Runnable} for doing registration deferred until the first time
 532      * {@link #get(ResolvedJavaMethod)} or {@link #closeRegistration()} is called on this object.
 533      */
 534     public void defer(Runnable deferrable) {
 535         assert deferredRegistrations != null : "registration is closed";
 536         deferredRegistrations.add(deferrable);
 537     }
 538 
 539     /**
 540      * Per-class invocation plugins.
 541      */
 542     protected static class ClassPlugins {
 543         private final Type declaringType;
 544 
 545         private final List<MethodKey> registrations = new ArrayList<>();
 546 
 547         public ClassPlugins(Type declaringClass) {
 548             this.declaringType = declaringClass;
 549         }
 550 
 551         /**
 552          * Entry map that is initialized upon first call to {@link #get(ResolvedJavaMethod)}.
 553          *
 554          * Note: this must be volatile as threads may race to initialize it.
 555          */
 556         private volatile EconomicMap<ResolvedJavaMethodKey, InvocationPlugin> entries;
 557 
 558         void initializeMap() {
 559             if (!isClosed()) {
 560                 if (registrations.isEmpty()) {
 561                     entries = EconomicMap.create(Equivalence.DEFAULT);
 562                 } else {
 563                     Class<?> declaringClass = resolveType(declaringType, true);
 564                     if (declaringClass == null) {
 565                         // An optional type that could not be resolved
 566                         entries = EconomicMap.create(Equivalence.DEFAULT);
 567                     } else {
 568                         EconomicMap<ResolvedJavaMethodKey, InvocationPlugin> newEntries = EconomicMap.create(Equivalence.DEFAULT);
 569                         for (MethodKey methodKey : registrations) {
 570                             ResolvedJavaMethod m = methodKey.resolve(declaringClass);
 571                             if (m != null) {
 572                                 newEntries.put(new ResolvedJavaMethodKey(m), methodKey.value);
 573                                 if (entries != null) {
 574                                     // Another thread finished initializing entries first
 575                                     return;
 576                                 }
 577                             }
 578                         }
 579                         entries = newEntries;
 580                     }
 581                 }
 582             }
 583         }
 584 
 585         public InvocationPlugin get(ResolvedJavaMethod method) {
 586             if (!isClosed()) {
 587                 initializeMap();
 588             }
 589             return entries.get(new ResolvedJavaMethodKey(method));
 590         }
 591 
 592         public void register(MethodKey methodKey, boolean allowOverwrite) {
 593             assert !isClosed() : "registration is closed: " + methodKey;
 594             if (allowOverwrite) {
 595                 int index = registrations.indexOf(methodKey);
 596                 if (index >= 0) {
 597                     registrations.set(index, methodKey);
 598                     return;
 599                 }
 600             } else {
 601                 assert !registrations.contains(methodKey) : "a value is already registered for " + declaringType + "." + methodKey;
 602             }
 603             registrations.add(methodKey);
 604         }
 605 
 606         public boolean isClosed() {
 607             return entries != null;
 608         }
 609     }
 610 
 611     /**
 612      * Adds an entry to this map for a specified method.
 613      *
 614      * @param value value to be associated with the specified method
 615      * @param isStatic specifies if the method is static
 616      * @param isOptional specifies if the method is optional
 617      * @param declaringClass the class declaring the method
 618      * @param name the name of the method
 619      * @param argumentTypes the argument types of the method. Element 0 of this array must be
 620      *            {@code declaringClass} iff the method is non-static.
 621      * @return an object representing the method
 622      */
 623     MethodKey put(InvocationPlugin value, boolean isStatic, boolean isOptional, boolean allowOverwrite, Type declaringClass, String name, Type... argumentTypes) {
 624         assert isStatic || argumentTypes[0] == declaringClass;
 625 
 626         String internalName = MetaUtil.toInternalName(declaringClass.getTypeName());
 627         ClassPlugins classPlugins = registrations.get(internalName);
 628         if (classPlugins == null) {
 629             classPlugins = new ClassPlugins(declaringClass);
 630             registrations.put(internalName, classPlugins);
 631         }
 632         assert isStatic || argumentTypes[0] == declaringClass;
 633         MethodKey methodKey = new MethodKey(metaAccess, value, isStatic, isOptional, name, argumentTypes);
 634         classPlugins.register(methodKey, allowOverwrite);
 635         return methodKey;
 636     }
 637 
 638     /**
 639      * Determines if a method denoted by a given {@link MethodKey} is in this map.
 640      */
 641     boolean containsKey(Type declaringType, MethodKey key) {
 642         String internalName = MetaUtil.toInternalName(declaringType.getTypeName());
 643         ClassPlugins classPlugins = registrations.get(internalName);
 644         return classPlugins != null && classPlugins.registrations.contains(key);
 645     }
 646 
 647     InvocationPlugin get(ResolvedJavaMethod method) {
 648         flushDeferrables();
 649         String internalName = method.getDeclaringClass().getName();
 650         ClassPlugins classPlugins = registrations.get(internalName);
 651         if (classPlugins != null) {
 652             return classPlugins.get(method);
 653         }
 654         return null;
 655     }
 656 
 657     private void flushDeferrables() {
 658         if (deferredRegistrations != null) {
 659             synchronized (this) {
 660                 if (deferredRegistrations != null) {
 661                     for (Runnable deferrable : deferredRegistrations) {
 662                         deferrable.run();
 663                     }
 664                     deferredRegistrations = null;
 665                 }
 666             }
 667             for (ClassPlugins e : registrations.getValues()) {
 668                 e.initializeMap();
 669             }
 670         }
 671     }
 672 
 673     /**
 674      * Disallows new registrations of new plugins, and creates the internal tables for method
 675      * lookup.
 676      */
 677     public void closeRegistration() {
 678         flushDeferrables();
 679         for (ClassPlugins e : registrations.getValues()) {
 680             e.initializeMap();
 681         }
 682     }
 683 
 684     public int size() {
 685         return registrations.size();
 686     }
 687 
 688     /**
 689      * The plugins {@linkplain #lookupInvocation(ResolvedJavaMethod) searched} before searching in
 690      * this object.
 691      */
 692     protected final InvocationPlugins parent;
 693 
 694     private InvocationPlugins(InvocationPlugins parent, MetaAccessProvider metaAccess) {
 695         this.metaAccess = metaAccess;
 696         InvocationPlugins p = parent;
 697         this.parent = p;
 698     }
 699 
 700     /**
 701      * Creates a set of invocation plugins with a non-null {@linkplain #getParent() parent}.
 702      */
 703     public InvocationPlugins(InvocationPlugins parent) {
 704         this(parent, parent.getMetaAccess());
 705     }
 706 
 707     public InvocationPlugins(Map<ResolvedJavaMethod, InvocationPlugin> plugins, InvocationPlugins parent, MetaAccessProvider metaAccess) {
 708         this.metaAccess = metaAccess;
 709         this.parent = parent;
 710 
 711         this.deferredRegistrations = null;
 712 
 713         for (Map.Entry<ResolvedJavaMethod, InvocationPlugin> entry : plugins.entrySet()) {
 714             ResolvedJavaMethod method = entry.getKey();
 715             InvocationPlugin plugin = entry.getValue();
 716 
 717             String internalName = method.getDeclaringClass().getName();
 718             ClassPlugins classPlugins = registrations.get(internalName);
 719             if (classPlugins == null) {
 720                 classPlugins = new ClassPlugins(null);
 721                 registrations.put(internalName, classPlugins);
 722                 classPlugins.entries = EconomicMap.create(Equivalence.DEFAULT);
 723             }
 724 
 725             classPlugins.entries.put(new ResolvedJavaMethodKey(method), plugin);
 726         }
 727     }
 728 
 729     public MetaAccessProvider getMetaAccess() {
 730         return metaAccess;
 731     }
 732 
 733     public InvocationPlugins(MetaAccessProvider metaAccess) {
 734         this(null, metaAccess);
 735     }
 736 
 737     protected void register(InvocationPlugin plugin, boolean isOptional, boolean allowOverwrite, Type declaringClass, String name, Type... argumentTypes) {
 738         boolean isStatic = argumentTypes.length == 0 || argumentTypes[0] != InvocationPlugin.Receiver.class;
 739         if (!isStatic) {
 740             argumentTypes[0] = declaringClass;
 741         }
 742         MethodKey methodKey = put(plugin, isStatic, isOptional, allowOverwrite, declaringClass, name, argumentTypes);
 743         assert Checker.check(this, declaringClass, methodKey, plugin);
 744     }
 745 
 746     /**
 747      * Registers an invocation plugin for a given method. There must be no plugin currently
 748      * registered for {@code method}.
 749      *
 750      * @param argumentTypes the argument types of the method. Element 0 of this array must be the
 751      *            {@link Class} value for {@link InvocationPlugin.Receiver} iff the method is
 752      *            non-static. Upon returning, element 0 will have been rewritten to
 753      *            {@code declaringClass}
 754      */
 755     public void register(InvocationPlugin plugin, Type declaringClass, String name, Type... argumentTypes) {
 756         register(plugin, false, false, declaringClass, name, argumentTypes);
 757     }
 758 
 759     public void register(InvocationPlugin plugin, String declaringClass, String name, Type... argumentTypes) {
 760         register(plugin, false, false, new OptionalLazySymbol(declaringClass), name, argumentTypes);
 761     }
 762 
 763     /**
 764      * Registers an invocation plugin for a given, optional method. There must be no plugin
 765      * currently registered for {@code method}.
 766      *
 767      * @param argumentTypes the argument types of the method. Element 0 of this array must be the
 768      *            {@link Class} value for {@link InvocationPlugin.Receiver} iff the method is
 769      *            non-static. Upon returning, element 0 will have been rewritten to
 770      *            {@code declaringClass}
 771      */
 772     public void registerOptional(InvocationPlugin plugin, Type declaringClass, String name, Type... argumentTypes) {
 773         register(plugin, true, false, declaringClass, name, argumentTypes);
 774     }
 775 
 776     /**
 777      * Gets the plugin for a given method.
 778      *
 779      * @param method the method to lookup
 780      * @return the plugin associated with {@code method} or {@code null} if none exists
 781      */
 782     public InvocationPlugin lookupInvocation(ResolvedJavaMethod method) {
 783         if (parent != null) {
 784             InvocationPlugin plugin = parent.lookupInvocation(method);
 785             if (plugin != null) {
 786                 return plugin;
 787             }
 788         }
 789         return get(method);
 790     }
 791 
 792     /**
 793      * Gets the set of methods for which invocation plugins have been registered. Once this method
 794      * is called, no further registrations can be made.
 795      */
 796     public EconomicSet<ResolvedJavaMethod> getMethods() {
 797         EconomicSet<ResolvedJavaMethod> res = EconomicSet.create(Equivalence.DEFAULT);
 798         if (parent != null) {
 799             res.addAll(parent.getMethods());
 800         }
 801         flushDeferrables();
 802         for (ClassPlugins cp : registrations.getValues()) {
 803             for (ResolvedJavaMethodKey key : cp.entries.getKeys()) {
 804                 res.add(key.method);
 805             }
 806         }
 807         return res;
 808     }
 809 
 810     /**
 811      * Gets the invocation plugins {@linkplain #lookupInvocation(ResolvedJavaMethod) searched}
 812      * before searching in this object.
 813      */
 814     public InvocationPlugins getParent() {
 815         return parent;
 816     }
 817 
 818     @Override
 819     public String toString() {
 820         StringBuilder buf = new StringBuilder();
 821         UnmodifiableMapCursor<String, ClassPlugins> entries = registrations.getEntries();
 822         while (entries.advance()) {
 823             buf.append(entries.getKey()).append('.').append(entries.getValue()).append(", ");
 824         }
 825 
 826         String s = buf.toString();
 827         if (buf.length() != 0) {
 828             s = s.substring(buf.length() - ", ".length());
 829         }
 830         return s + " / parent: " + this.parent;
 831     }
 832 
 833     private static class Checker {
 834         private static final int MAX_ARITY = 7;
 835         /**
 836          * The set of all {@link InvocationPlugin#apply} method signatures.
 837          */
 838         static final Class<?>[][] SIGS;
 839 
 840         static {
 841             ArrayList<Class<?>[]> sigs = new ArrayList<>(MAX_ARITY);
 842             for (Method method : InvocationPlugin.class.getDeclaredMethods()) {
 843                 if (!Modifier.isStatic(method.getModifiers()) && method.getName().equals("apply")) {
 844                     Class<?>[] sig = method.getParameterTypes();
 845                     assert sig[0] == GraphBuilderContext.class;
 846                     assert sig[1] == ResolvedJavaMethod.class;
 847                     assert sig[2] == InvocationPlugin.Receiver.class;
 848                     assert Arrays.asList(sig).subList(3, sig.length).stream().allMatch(c -> c == ValueNode.class);
 849                     while (sigs.size() < sig.length - 2) {
 850                         sigs.add(null);
 851                     }
 852                     sigs.set(sig.length - 3, sig);
 853                 }
 854             }
 855             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),
 856                             ValueNode.class.getSimpleName());
 857             SIGS = sigs.toArray(new Class<?>[sigs.size()][]);
 858         }
 859 
 860         public static boolean check(InvocationPlugins plugins, Type declaringType, MethodKey method, InvocationPlugin plugin) {
 861             InvocationPlugins p = plugins.parent;
 862             while (p != null) {
 863                 assert !p.containsKey(declaringType, method) : "a plugin is already registered for " + method;
 864                 p = p.parent;
 865             }
 866             if (plugin instanceof ForeignCallPlugin || plugin instanceof GeneratedInvocationPlugin) {
 867                 return true;
 868             }
 869             if (plugin instanceof MethodSubstitutionPlugin) {
 870                 MethodSubstitutionPlugin msplugin = (MethodSubstitutionPlugin) plugin;
 871                 Method substitute = msplugin.getJavaSubstitute();
 872                 assert substitute.getAnnotation(MethodSubstitution.class) != null : format("Substitute method must be annotated with @%s: %s", MethodSubstitution.class.getSimpleName(), substitute);
 873                 return true;
 874             }
 875             int arguments = method.getDeclaredParameterCount();
 876             assert arguments < SIGS.length : format("need to extend %s to support method with %d arguments: %s", InvocationPlugin.class.getSimpleName(), arguments, method);
 877             for (Method m : plugin.getClass().getDeclaredMethods()) {
 878                 if (m.getName().equals("apply")) {
 879                     Class<?>[] parameterTypes = m.getParameterTypes();
 880                     if (Arrays.equals(SIGS[arguments], parameterTypes)) {
 881                         return true;
 882                     }
 883                 }
 884             }
 885             throw new AssertionError(format("graph builder plugin for %s not found", method));
 886         }
 887     }
 888 
 889     /**
 890      * Checks a set of nodes added to the graph by an {@link InvocationPlugin}.
 891      *
 892      * @param b the graph builder that applied the plugin
 893      * @param plugin a plugin that was just applied
 894      * @param newNodes the nodes added to the graph by {@code plugin}
 895      * @throws AssertionError if any check fail
 896      */
 897     public void checkNewNodes(GraphBuilderContext b, InvocationPlugin plugin, NodeIterable<Node> newNodes) {
 898         if (parent != null) {
 899             parent.checkNewNodes(b, plugin, newNodes);
 900         }
 901     }
 902 
 903     /**
 904      * Resolves a name to a class.
 905      *
 906      * @param className the name of the class to resolve
 907      * @param optional if true, resolution failure returns null
 908      * @return the resolved class or null if resolution fails and {@code optional} is true
 909      */
 910     public static Class<?> resolveClass(String className, boolean optional) {
 911         try {
 912             // Need to use the system class loader to handle classes
 913             // loaded by the application class loader which is not
 914             // delegated to by the JVMCI class loader.
 915             ClassLoader cl = ClassLoader.getSystemClassLoader();
 916             return Class.forName(className, false, cl);
 917         } catch (ClassNotFoundException e) {
 918             if (optional) {
 919                 return null;
 920             }
 921             throw new GraalError("Could not resolve type " + className);
 922         }
 923     }
 924 
 925     /**
 926      * Resolves a {@link Type} to a {@link Class}.
 927      *
 928      * @param type the type to resolve
 929      * @param optional if true, resolution failure returns null
 930      * @return the resolved class or null if resolution fails and {@code optional} is true
 931      */
 932     public static Class<?> resolveType(Type type, boolean optional) {
 933         if (type instanceof Class) {
 934             return (Class<?>) type;
 935         }
 936         if (optional && type instanceof OptionalLazySymbol) {
 937             return ((OptionalLazySymbol) type).resolve();
 938         }
 939         return resolveClass(type.getTypeName(), optional);
 940     }
 941 
 942     private static final Class<?>[] NO_CLASSES = {};
 943 
 944     /**
 945      * Resolves an array of {@link Type}s to an array of {@link Class}es.
 946      *
 947      * @param types the types to resolve
 948      * @param from the initial index of the range to be resolved, inclusive
 949      * @param to the final index of the range to be resolved, exclusive
 950      * @return the resolved class or null if resolution fails and {@code optional} is true
 951      */
 952     public static Class<?>[] resolveTypes(Type[] types, int from, int to) {
 953         int length = to - from;
 954         if (length <= 0) {
 955             return NO_CLASSES;
 956         }
 957         Class<?>[] classes = new Class<?>[length];
 958         for (int i = 0; i < length; i++) {
 959             classes[i] = resolveType(types[i + from], false);
 960         }
 961         return classes;
 962     }
 963 }