Mercurial > hg > truffle
view graal/com.oracle.graal.graphbuilderconf/src/com/oracle/graal/graphbuilderconf/InvocationPlugins.java @ 19957:3a1ce0aeb829
added support for checking nodes added to the graph by an Invocation plugin and used this to check that only legal constants are added under ImmutableCode
author | Doug Simon <doug.simon@oracle.com> |
---|---|
date | Thu, 19 Mar 2015 12:46:06 +0100 |
parents | b950967f74c7 |
children | 812fc403db8c |
line wrap: on
line source
/* * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.oracle.graal.graphbuilderconf; import static java.lang.String.*; import java.lang.reflect.*; import java.util.*; import java.util.stream.*; import com.oracle.graal.api.meta.*; import com.oracle.graal.compiler.common.*; import com.oracle.graal.graph.Node; import com.oracle.graal.graph.iterators.*; import com.oracle.graal.nodes.*; /** * Manages a set of {@link InvocationPlugin}s. */ public class InvocationPlugins { /** * Sentinel class for use with * {@link InvocationPlugins#register(InvocationPlugin, Class, String, Class...)} to denote the * receiver argument for a non-static method. */ public static final class Receiver { private Receiver() { throw GraalInternalError.shouldNotReachHere(); } } /** * Utility for * {@linkplain InvocationPlugins#register(InvocationPlugin, Class, String, Class...) * registration} of invocation plugins. */ public static class Registration { private final InvocationPlugins plugins; private final Class<?> declaringClass; /** * Creates an object for registering {@link InvocationPlugin}s for methods declared by a * given class. * * @param plugins where to register the plugins * @param declaringClass the class declaring the methods for which plugins will be * registered via this object */ public Registration(InvocationPlugins plugins, Class<?> declaringClass) { this.plugins = plugins; this.declaringClass = declaringClass; } /** * Registers a plugin for a method with no arguments. * * @param name the name of the method * @param plugin the plugin to be registered */ public void register0(String name, InvocationPlugin plugin) { plugins.register(plugin, declaringClass, name); } /** * Registers a plugin for a method with 1 argument. * * @param name the name of the method * @param plugin the plugin to be registered */ public void register1(String name, Class<?> arg, InvocationPlugin plugin) { plugins.register(plugin, declaringClass, name, arg); } /** * Registers a plugin for a method with 2 arguments. * * @param name the name of the method * @param plugin the plugin to be registered */ public void register2(String name, Class<?> arg1, Class<?> arg2, InvocationPlugin plugin) { plugins.register(plugin, declaringClass, name, arg1, arg2); } /** * Registers a plugin for a method with 3 arguments. * * @param name the name of the method * @param plugin the plugin to be registered */ public void register3(String name, Class<?> arg1, Class<?> arg2, Class<?> arg3, InvocationPlugin plugin) { plugins.register(plugin, declaringClass, name, arg1, arg2, arg3); } /** * Registers a plugin for a method with 4 arguments. * * @param name the name of the method * @param plugin the plugin to be registered */ public void register4(String name, Class<?> arg1, Class<?> arg2, Class<?> arg3, Class<?> arg4, InvocationPlugin plugin) { plugins.register(plugin, declaringClass, name, arg1, arg2, arg3, arg4); } /** * Registers a plugin for a method with 5 arguments. * * @param name the name of the method * @param plugin the plugin to be registered */ public void register5(String name, Class<?> arg1, Class<?> arg2, Class<?> arg3, Class<?> arg4, Class<?> arg5, InvocationPlugin plugin) { plugins.register(plugin, declaringClass, name, arg1, arg2, arg3, arg4, arg5); } } static final class MethodInfo { final boolean isStatic; final Class<?> declaringClass; final String name; final Class<?>[] argumentTypes; final InvocationPlugin plugin; int id; MethodInfo(InvocationPlugin plugin, Class<?> declaringClass, String name, Class<?>... argumentTypes) { this.plugin = plugin; this.isStatic = argumentTypes.length == 0 || argumentTypes[0] != Receiver.class; this.declaringClass = declaringClass; this.name = name; this.argumentTypes = argumentTypes; if (!isStatic) { argumentTypes[0] = declaringClass; } } @Override public boolean equals(Object obj) { if (obj instanceof MethodInfo) { MethodInfo that = (MethodInfo) obj; boolean res = this.name.equals(that.name) && this.declaringClass.equals(that.declaringClass) && Arrays.equals(this.argumentTypes, that.argumentTypes); assert !res || this.isStatic == that.isStatic; return res; } return false; } @Override public int hashCode() { // Replay compilation mandates use of stable hash codes return declaringClass.getName().hashCode() ^ name.hashCode(); } ResolvedJavaMethod resolve(MetaAccessProvider metaAccess) { try { ResolvedJavaMethod method; Class<?>[] parameterTypes = isStatic ? argumentTypes : Arrays.copyOfRange(argumentTypes, 1, argumentTypes.length); if (name.equals("<init>")) { method = metaAccess.lookupJavaMethod(declaringClass.getDeclaredConstructor(parameterTypes)); } else { method = metaAccess.lookupJavaMethod(declaringClass.getDeclaredMethod(name, parameterTypes)); } assert method.isStatic() == isStatic; return method; } catch (NoSuchMethodException | SecurityException e) { throw new GraalInternalError(e); } } @Override public String toString() { StringBuilder sb = new StringBuilder(declaringClass.getName()).append('.').append(name).append('('); for (Class<?> p : argumentTypes) { if (sb.charAt(sb.length() - 1) != '(') { sb.append(", "); } sb.append(p.getSimpleName()); } return sb.append(')').toString(); } } protected final MetaAccessProvider metaAccess; private final List<MethodInfo> registrations; private final Thread registrationThread; /** * The minimum {@linkplain InvocationPluginIdHolder#getInvocationPluginId() id} for a method * associated with a plugin in {@link #plugins}. */ private int minId = Integer.MAX_VALUE; /** * Resolved methods to plugins map. The keys (i.e., indexes) are derived from * {@link InvocationPluginIdHolder#getInvocationPluginId()}. */ private volatile InvocationPlugin[] plugins; /** * The plugins {@linkplain #lookupInvocation(ResolvedJavaMethod) searched} before searching in * this object. */ private InvocationPlugins parent; private InvocationPlugins(InvocationPlugins parent, MetaAccessProvider metaAccess, int estimatePluginCount) { this.registrationThread = Thread.currentThread(); this.metaAccess = metaAccess; this.registrations = new ArrayList<>(estimatePluginCount); InvocationPlugins p = parent; // Only adopt a non-empty parent while (p != null && p.size() == 0) { p = p.parent; } this.parent = p; } private static final int DEFAULT_ESTIMATE_PLUGIN_COUNT = 16; /** * Creates a set of invocation plugins with a non-null {@linkplain #getParent() parent}. */ public InvocationPlugins(InvocationPlugins parent) { this(parent, parent.metaAccess, DEFAULT_ESTIMATE_PLUGIN_COUNT); } public InvocationPlugins(MetaAccessProvider metaAccess) { this(metaAccess, DEFAULT_ESTIMATE_PLUGIN_COUNT); } public InvocationPlugins(MetaAccessProvider metaAccess, int estimatePluginCount) { this(null, metaAccess, estimatePluginCount); } /** * Registers an invocation plugin for a given method. There must be no plugin currently * registered for {@code method}. */ public void register(InvocationPlugin plugin, Class<?> declaringClass, String name, Class<?>... argumentTypes) { assert Thread.currentThread() == registrationThread : "invocation plugin registration must be single threaded"; MethodInfo methodInfo = new MethodInfo(plugin, declaringClass, name, argumentTypes); assert Checker.check(this, methodInfo, plugin); assert plugins == null : "invocation plugin registration is closed"; registrations.add(methodInfo); } private static int nextInvocationPluginId = 1; /** * Gets the plugin for a given method. * * @param method the method to lookup * @return the plugin associated with {@code method} or {@code null} if none exists */ public InvocationPlugin lookupInvocation(ResolvedJavaMethod method) { assert method instanceof InvocationPluginIdHolder; if (parent != null) { InvocationPlugin plugin = parent.lookupInvocation(method); if (plugin != null) { return plugin; } } InvocationPluginIdHolder pluggable = (InvocationPluginIdHolder) method; if (plugins == null) { // Must synchronize across all InvocationPlugins objects to ensure thread safe // allocation of InvocationPlugin identifiers synchronized (InvocationPlugins.class) { if (plugins == null) { if (registrations.isEmpty()) { plugins = new InvocationPlugin[0]; } else { int max = Integer.MIN_VALUE; for (MethodInfo methodInfo : registrations) { InvocationPluginIdHolder p = (InvocationPluginIdHolder) methodInfo.resolve(metaAccess); int id = p.getInvocationPluginId(); if (id == 0) { id = nextInvocationPluginId++; p.setInvocationPluginId(id); } if (id < minId) { minId = id; } if (id > max) { max = id; } methodInfo.id = id; } int length = (max - minId) + 1; plugins = new InvocationPlugin[length]; for (MethodInfo m : registrations) { int index = m.id - minId; plugins[index] = m.plugin; } } } } } int id = pluggable.getInvocationPluginId(); int index = id - minId; return index >= 0 && index < plugins.length ? plugins[index] : null; } /** * Gets the invocation plugins {@linkplain #lookupInvocation(ResolvedJavaMethod) searched} * before searching in this object. */ public InvocationPlugins getParent() { return parent; } @Override public String toString() { return registrations.stream().map(MethodInfo::toString).collect(Collectors.joining(", ")) + " / parent: " + this.parent; } private static class Checker { private static final int MAX_ARITY = 5; /** * The set of all {@link InvocationPlugin#apply} method signatures. */ static final Class<?>[][] SIGS; static { ArrayList<Class<?>[]> sigs = new ArrayList<>(MAX_ARITY); for (Method method : InvocationPlugin.class.getDeclaredMethods()) { if (!Modifier.isStatic(method.getModifiers()) && method.getName().equals("apply")) { Class<?>[] sig = method.getParameterTypes(); assert sig[0] == GraphBuilderContext.class; assert sig[1] == ResolvedJavaMethod.class; assert Arrays.asList(Arrays.copyOfRange(sig, 2, sig.length)).stream().allMatch(c -> c == ValueNode.class); while (sigs.size() < sig.length - 1) { sigs.add(null); } sigs.set(sig.length - 2, sig); } } assert sigs.indexOf(null) == -1 : format("need to add an apply() method to %s that takes %d %s arguments ", InvocationPlugin.class.getName(), sigs.indexOf(null), ValueNode.class.getSimpleName()); SIGS = sigs.toArray(new Class<?>[sigs.size()][]); } public static boolean check(InvocationPlugins plugins, MethodInfo method, InvocationPlugin plugin) { InvocationPlugins p = plugins; while (p != null) { assert !p.registrations.contains(method) : "a plugin is already registered for " + method; p = p.parent; } int arguments = method.argumentTypes.length; assert arguments < SIGS.length : format("need to extend %s to support method with %d arguments: %s", InvocationPlugin.class.getSimpleName(), arguments, method); for (Method m : plugin.getClass().getDeclaredMethods()) { if (m.getName().equals("apply")) { Class<?>[] parameterTypes = m.getParameterTypes(); if (Arrays.equals(SIGS[arguments], parameterTypes)) { return true; } } } throw new AssertionError(format("graph builder plugin for %s not found", method)); } } public MetaAccessProvider getMetaAccess() { return metaAccess; } public int size() { return registrations.size(); } /** * Checks a set of nodes added to the graph by an {@link InvocationPlugin}. * * @param b the graph builder that applied the plugin * @param plugin a plugin that was just applied * @param newNodes the nodes added to the graph by {@code plugin} * @throws AssertionError if any check fail */ public void checkNewNodes(GraphBuilderContext b, InvocationPlugin plugin, NodeIterable<Node> newNodes) { if (parent != null) { parent.checkNewNodes(b, plugin, newNodes); } } }