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