001/*
002 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.
008 *
009 * This code is distributed in the hope that it will be useful, but WITHOUT
010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
011 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
012 * version 2 for more details (a copy is included in the LICENSE file that
013 * accompanied this code).
014 *
015 * You should have received a copy of the GNU General Public License version
016 * 2 along with this work; if not, write to the Free Software Foundation,
017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
018 *
019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
020 * or visit www.oracle.com if you need additional information or have any
021 * questions.
022 */
023package com.oracle.graal.graphbuilderconf;
024
025import static java.lang.String.*;
026
027import java.lang.reflect.*;
028import java.util.*;
029
030import jdk.internal.jvmci.meta.*;
031import jdk.internal.jvmci.meta.MethodIdMap.*;
032
033import com.oracle.graal.graph.*;
034import com.oracle.graal.graph.iterators.*;
035import com.oracle.graal.nodes.*;
036
037/**
038 * Manages a set of {@link InvocationPlugin}s.
039 */
040public class InvocationPlugins {
041
042    public static class InvocationPluginReceiver implements InvocationPlugin.Receiver {
043        private final GraphBuilderContext parser;
044        private ValueNode[] args;
045        private ValueNode value;
046
047        public InvocationPluginReceiver(GraphBuilderContext parser) {
048            this.parser = parser;
049        }
050
051        @Override
052        public ValueNode get() {
053            assert args != null : "Cannot get the receiver of a static method";
054            if (value == null) {
055                value = parser.nullCheckedValue(args[0]);
056                if (value != args[0]) {
057                    args[0] = value;
058                }
059            }
060            return value;
061        }
062
063        @Override
064        public boolean isConstant() {
065            return args[0].isConstant();
066        }
067
068        public InvocationPluginReceiver init(ResolvedJavaMethod targetMethod, ValueNode[] newArgs) {
069            if (!targetMethod.isStatic()) {
070                this.args = newArgs;
071                this.value = null;
072                return this;
073            }
074            return null;
075        }
076    }
077
078    /**
079     * Utility for
080     * {@linkplain InvocationPlugins#register(InvocationPlugin, Class, String, Class...)
081     * registration} of invocation plugins.
082     */
083    public static class Registration {
084
085        private final InvocationPlugins plugins;
086        private final Class<?> declaringClass;
087
088        /**
089         * Creates an object for registering {@link InvocationPlugin}s for methods declared by a
090         * given class.
091         *
092         * @param plugins where to register the plugins
093         * @param declaringClass the class declaring the methods for which plugins will be
094         *            registered via this object
095         */
096        public Registration(InvocationPlugins plugins, Class<?> declaringClass) {
097            this.plugins = plugins;
098            this.declaringClass = declaringClass;
099        }
100
101        /**
102         * Registers a plugin for a method with no arguments.
103         *
104         * @param name the name of the method
105         * @param plugin the plugin to be registered
106         */
107        public void register0(String name, InvocationPlugin plugin) {
108            plugins.register(plugin, declaringClass, name);
109        }
110
111        /**
112         * Registers a plugin for a method with 1 argument.
113         *
114         * @param name the name of the method
115         * @param plugin the plugin to be registered
116         */
117        public void register1(String name, Class<?> arg, InvocationPlugin plugin) {
118            plugins.register(plugin, declaringClass, name, arg);
119        }
120
121        /**
122         * Registers a plugin for a method with 2 arguments.
123         *
124         * @param name the name of the method
125         * @param plugin the plugin to be registered
126         */
127        public void register2(String name, Class<?> arg1, Class<?> arg2, InvocationPlugin plugin) {
128            plugins.register(plugin, declaringClass, name, arg1, arg2);
129        }
130
131        /**
132         * Registers a plugin for a method with 3 arguments.
133         *
134         * @param name the name of the method
135         * @param plugin the plugin to be registered
136         */
137        public void register3(String name, Class<?> arg1, Class<?> arg2, Class<?> arg3, InvocationPlugin plugin) {
138            plugins.register(plugin, declaringClass, name, arg1, arg2, arg3);
139        }
140
141        /**
142         * Registers a plugin for a method with 4 arguments.
143         *
144         * @param name the name of the method
145         * @param plugin the plugin to be registered
146         */
147        public void register4(String name, Class<?> arg1, Class<?> arg2, Class<?> arg3, Class<?> arg4, InvocationPlugin plugin) {
148            plugins.register(plugin, declaringClass, name, arg1, arg2, arg3, arg4);
149        }
150
151        /**
152         * Registers a plugin for a method with 5 arguments.
153         *
154         * @param name the name of the method
155         * @param plugin the plugin to be registered
156         */
157        public void register5(String name, Class<?> arg1, Class<?> arg2, Class<?> arg3, Class<?> arg4, Class<?> arg5, InvocationPlugin plugin) {
158            plugins.register(plugin, declaringClass, name, arg1, arg2, arg3, arg4, arg5);
159        }
160
161        /**
162         * Registers a plugin for an optional method with 3 arguments.
163         *
164         * @param name the name of the method
165         * @param plugin the plugin to be registered
166         */
167        public void registerOptional3(String name, Class<?> arg1, Class<?> arg2, Class<?> arg3, InvocationPlugin plugin) {
168            plugins.registerOptional(plugin, declaringClass, name, arg1, arg2, arg3);
169        }
170
171        /**
172         * Registers a plugin for an optional method with 4 arguments.
173         *
174         * @param name the name of the method
175         * @param plugin the plugin to be registered
176         */
177        public void registerOptional4(String name, Class<?> arg1, Class<?> arg2, Class<?> arg3, Class<?> arg4, InvocationPlugin plugin) {
178            plugins.registerOptional(plugin, declaringClass, name, arg1, arg2, arg3, arg4);
179        }
180
181        /**
182         * Registers a plugin that implements a method based on the bytecode of a substitute method.
183         *
184         * @param substituteDeclaringClass the class declaring the substitute method
185         * @param name the name of both the original and substitute method
186         * @param argumentTypes the argument types of the method. Element 0 of this array must be
187         *            the {@link Class} value for {@link InvocationPlugin.Receiver} iff the method
188         *            is non-static. Upon returning, element 0 will have been rewritten to
189         *            {@code declaringClass}
190         */
191        public void registerMethodSubstitution(Class<?> substituteDeclaringClass, String name, Class<?>... argumentTypes) {
192            MethodSubstitutionPlugin plugin = new MethodSubstitutionPlugin(substituteDeclaringClass, name, argumentTypes);
193            plugins.register(plugin, declaringClass, name, argumentTypes);
194        }
195    }
196
197    protected final MethodIdMap<InvocationPlugin> plugins;
198
199    /**
200     * The plugins {@linkplain #lookupInvocation(ResolvedJavaMethod) searched} before searching in
201     * this object.
202     */
203    protected final InvocationPlugins parent;
204
205    private InvocationPlugins(InvocationPlugins parent, MetaAccessProvider metaAccess) {
206        this.plugins = new MethodIdMap<>(metaAccess);
207        InvocationPlugins p = parent;
208        // Only adopt a non-empty parent
209        while (p != null && p.size() == 0) {
210            p = p.parent;
211        }
212        this.parent = p;
213    }
214
215    /**
216     * Creates a set of invocation plugins with a non-null {@linkplain #getParent() parent}.
217     */
218    public InvocationPlugins(InvocationPlugins parent) {
219        this(parent, parent.plugins.getMetaAccess());
220    }
221
222    public InvocationPlugins(MetaAccessProvider metaAccess) {
223        this(null, metaAccess);
224    }
225
226    private void register(InvocationPlugin plugin, boolean isOptional, Class<?> declaringClass, String name, Class<?>... argumentTypes) {
227        boolean isStatic = argumentTypes.length == 0 || argumentTypes[0] != InvocationPlugin.Receiver.class;
228        if (!isStatic) {
229            argumentTypes[0] = declaringClass;
230        }
231        MethodKey<InvocationPlugin> methodInfo = plugins.put(plugin, isStatic, isOptional, declaringClass, name, argumentTypes);
232        assert Checker.check(this, methodInfo, plugin);
233    }
234
235    /**
236     * Registers an invocation plugin for a given method. There must be no plugin currently
237     * registered for {@code method}.
238     *
239     * @param argumentTypes the argument types of the method. Element 0 of this array must be the
240     *            {@link Class} value for {@link InvocationPlugin.Receiver} iff the method is
241     *            non-static. Upon returning, element 0 will have been rewritten to
242     *            {@code declaringClass}
243     */
244    public void register(InvocationPlugin plugin, Class<?> declaringClass, String name, Class<?>... argumentTypes) {
245        register(plugin, false, declaringClass, name, argumentTypes);
246    }
247
248    /**
249     * Registers an invocation plugin for a given, optional method. There must be no plugin
250     * currently registered for {@code method}.
251     *
252     * @param argumentTypes the argument types of the method. Element 0 of this array must be the
253     *            {@link Class} value for {@link InvocationPlugin.Receiver} iff the method is
254     *            non-static. Upon returning, element 0 will have been rewritten to
255     *            {@code declaringClass}
256     */
257    public void registerOptional(InvocationPlugin plugin, Class<?> declaringClass, String name, Class<?>... argumentTypes) {
258        register(plugin, true, declaringClass, name, argumentTypes);
259    }
260
261    /**
262     * Gets the plugin for a given method.
263     *
264     * @param method the method to lookup
265     * @return the plugin associated with {@code method} or {@code null} if none exists
266     */
267    public InvocationPlugin lookupInvocation(ResolvedJavaMethod method) {
268        assert method instanceof MethodIdHolder;
269        if (parent != null) {
270            InvocationPlugin plugin = parent.lookupInvocation(method);
271            if (plugin != null) {
272                return plugin;
273            }
274        }
275        return plugins.get((MethodIdHolder) method);
276    }
277
278    /**
279     * Disallows new registrations of new plugins, and creates the internal tables for method
280     * lookup.
281     */
282    public void closeRegistration() {
283        plugins.createEntries();
284    }
285
286    /**
287     * Gets the invocation plugins {@linkplain #lookupInvocation(ResolvedJavaMethod) searched}
288     * before searching in this object.
289     */
290    public InvocationPlugins getParent() {
291        return parent;
292    }
293
294    @Override
295    public String toString() {
296        return plugins + " / parent: " + this.parent;
297    }
298
299    private static class Checker {
300        private static final int MAX_ARITY = 5;
301        /**
302         * The set of all {@link InvocationPlugin#apply} method signatures.
303         */
304        static final Class<?>[][] SIGS;
305
306        static {
307            ArrayList<Class<?>[]> sigs = new ArrayList<>(MAX_ARITY);
308            for (Method method : InvocationPlugin.class.getDeclaredMethods()) {
309                if (!Modifier.isStatic(method.getModifiers()) && method.getName().equals("apply")) {
310                    Class<?>[] sig = method.getParameterTypes();
311                    assert sig[0] == GraphBuilderContext.class;
312                    assert sig[1] == ResolvedJavaMethod.class;
313                    assert sig[2] == InvocationPlugin.Receiver.class;
314                    assert Arrays.asList(sig).subList(3, sig.length).stream().allMatch(c -> c == ValueNode.class);
315                    while (sigs.size() < sig.length - 2) {
316                        sigs.add(null);
317                    }
318                    sigs.set(sig.length - 3, sig);
319                }
320            }
321            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),
322                            ValueNode.class.getSimpleName());
323            SIGS = sigs.toArray(new Class<?>[sigs.size()][]);
324        }
325
326        public static boolean check(InvocationPlugins plugins, MethodKey<InvocationPlugin> method, InvocationPlugin plugin) {
327            InvocationPlugins p = plugins.parent;
328            while (p != null) {
329                assert !p.plugins.containsKey(method) : "a plugin is already registered for " + method;
330                p = p.parent;
331            }
332            if (plugin instanceof ForeignCallPlugin) {
333                return true;
334            }
335            if (plugin instanceof MethodSubstitutionPlugin) {
336                MethodSubstitutionPlugin msplugin = (MethodSubstitutionPlugin) plugin;
337                msplugin.getJavaSubstitute();
338                return true;
339            }
340            int arguments = method.getDeclaredParameterCount();
341            assert arguments < SIGS.length : format("need to extend %s to support method with %d arguments: %s", InvocationPlugin.class.getSimpleName(), arguments, method);
342            for (Method m : plugin.getClass().getDeclaredMethods()) {
343                if (m.getName().equals("apply")) {
344                    Class<?>[] parameterTypes = m.getParameterTypes();
345                    if (Arrays.equals(SIGS[arguments], parameterTypes)) {
346                        return true;
347                    }
348                }
349            }
350            throw new AssertionError(format("graph builder plugin for %s not found", method));
351        }
352    }
353
354    public int size() {
355        return plugins.size();
356    }
357
358    /**
359     * Checks a set of nodes added to the graph by an {@link InvocationPlugin}.
360     *
361     * @param b the graph builder that applied the plugin
362     * @param plugin a plugin that was just applied
363     * @param newNodes the nodes added to the graph by {@code plugin}
364     * @throws AssertionError if any check fail
365     */
366    public void checkNewNodes(GraphBuilderContext b, InvocationPlugin plugin, NodeIterable<Node> newNodes) {
367        if (parent != null) {
368            parent.checkNewNodes(b, plugin, newNodes);
369        }
370    }
371}