001/*
002 * Copyright (c) 2011, 2014, 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.replacements;
024
025import static jdk.internal.jvmci.meta.MetaUtil.*;
026
027import java.lang.reflect.*;
028import java.util.*;
029
030import jdk.internal.jvmci.common.*;
031
032import com.oracle.graal.debug.*;
033import com.oracle.graal.debug.internal.*;
034
035import jdk.internal.jvmci.meta.*;
036
037import com.oracle.graal.api.replacements.*;
038import com.oracle.graal.compiler.common.spi.*;
039import com.oracle.graal.compiler.common.type.*;
040import com.oracle.graal.graph.*;
041import com.oracle.graal.graph.Node.ConstantNodeParameter;
042import com.oracle.graal.graph.Node.InjectedNodeParameter;
043import com.oracle.graal.graph.Node.NodeIntrinsic;
044import com.oracle.graal.nodes.*;
045import com.oracle.graal.nodes.calc.*;
046import com.oracle.graal.nodes.extended.*;
047import com.oracle.graal.nodes.java.*;
048import com.oracle.graal.nodes.spi.*;
049import com.oracle.graal.nodes.util.*;
050import com.oracle.graal.phases.*;
051
052/**
053 * Replaces calls to {@link NodeIntrinsic}s with nodes and calls to methods annotated with
054 * {@link Fold} with the result of invoking the annotated method via reflection.
055 */
056public class NodeIntrinsificationPhase extends Phase {
057
058    private final MetaAccessProvider metaAccess;
059    private final ConstantReflectionProvider constantReflection;
060    private final SnippetReflectionProvider snippetReflection;
061    private final ForeignCallsProvider foreignCalls;
062    private final StampProvider stampProvider;
063
064    public NodeIntrinsificationPhase(MetaAccessProvider metaAccess, ConstantReflectionProvider constantReflection, SnippetReflectionProvider snippetReflection, ForeignCallsProvider foreignCalls,
065                    StampProvider stampProvider) {
066        this.metaAccess = metaAccess;
067        this.constantReflection = constantReflection;
068        this.snippetReflection = snippetReflection;
069        this.foreignCalls = foreignCalls;
070        this.stampProvider = stampProvider;
071    }
072
073    @Override
074    protected void run(StructuredGraph graph) {
075        ArrayList<Node> cleanUpReturnList = new ArrayList<>();
076        for (MethodCallTargetNode node : graph.getNodes(MethodCallTargetNode.TYPE)) {
077            tryIntrinsify(node, cleanUpReturnList);
078        }
079
080        for (Node node : cleanUpReturnList) {
081            cleanUpReturnCheckCast(node);
082        }
083    }
084
085    protected boolean tryIntrinsify(MethodCallTargetNode methodCallTargetNode, List<Node> cleanUpReturnList) {
086        ResolvedJavaMethod target = methodCallTargetNode.targetMethod();
087        ResolvedJavaType declaringClass = target.getDeclaringClass();
088        StructuredGraph graph = methodCallTargetNode.graph();
089
090        NodeIntrinsic intrinsic = getIntrinsic(target);
091        if (intrinsic != null) {
092            Stamp stamp = methodCallTargetNode.invoke().asNode().stamp();
093            Node newInstance = createIntrinsicNode(methodCallTargetNode.arguments(), stamp, target, graph, intrinsic);
094            if (newInstance == null) {
095                return false;
096            }
097
098            // Replace the invoke with the new node.
099            newInstance = graph.addOrUnique(newInstance);
100            methodCallTargetNode.invoke().intrinsify(newInstance);
101
102            // Clean up checkcast instructions inserted by javac if the return type is generic.
103            cleanUpReturnList.add(newInstance);
104        } else if (isFoldable(target)) {
105            ResolvedJavaType[] parameterTypes = resolveJavaTypes(target.toParameterTypes(), declaringClass);
106            JavaConstant constant = tryFold(methodCallTargetNode.arguments(), parameterTypes, target);
107            if (constant != null && constant.equals(COULD_NOT_FOLD)) {
108                return false;
109            }
110
111            if (constant != null) {
112                // Replace the invoke with the result of the call
113                ConstantNode node = ConstantNode.forConstant(constant, metaAccess, methodCallTargetNode.graph());
114                methodCallTargetNode.invoke().intrinsify(node);
115
116                // Clean up checkcast instructions inserted by javac if the return type is generic.
117                cleanUpReturnList.add(node);
118            } else {
119                // Remove the invoke
120                methodCallTargetNode.invoke().intrinsify(null);
121            }
122        }
123        return true;
124    }
125
126    public static final JavaConstant COULD_NOT_FOLD = new PrimitiveConstant(Kind.Illegal, 100) {
127        @Override
128        public boolean equals(Object o) {
129            return this == o;
130        }
131    };
132
133    public JavaConstant tryFold(List<ValueNode> args, ResolvedJavaType[] parameterTypes, ResolvedJavaMethod target) {
134        JavaConstant[] reflectArgs = (JavaConstant[]) prepareArguments(args, parameterTypes, target, true);
135        if (reflectArgs == null) {
136            return COULD_NOT_FOLD;
137        }
138        JavaConstant receiver = null;
139        if (!target.isStatic()) {
140            receiver = reflectArgs[0];
141            reflectArgs = Arrays.copyOfRange(reflectArgs, 1, reflectArgs.length);
142        }
143
144        // Call the method
145        return target.invoke(receiver, reflectArgs);
146    }
147
148    private static boolean areAllConstant(List<ValueNode> arguments) {
149        for (ValueNode arg : arguments) {
150            if (!arg.isConstant()) {
151                return false;
152            }
153        }
154        return true;
155    }
156
157    /**
158     * Attempts to create a node to replace a call to a {@link NodeIntrinsic} annotated method.
159     *
160     * @param arguments the arguments of the call
161     * @param stamp the stamp to use for the returned node
162     * @param method the method annotated with {@link NodeIntrinsic}
163     * @param graph the graph into which the created node will be added
164     * @return a {@link ConstantNode} if the intrinsic could be
165     *         {@linkplain NodeIntrinsic#foldable() folded}, {@code null} if intrinsification could
166     *         not (yet) be performed, otherwise the node representing the intrinsic
167     */
168    public ValueNode createIntrinsicNode(List<ValueNode> arguments, Stamp stamp, ResolvedJavaMethod method, StructuredGraph graph, NodeIntrinsic intrinsic) {
169        assert method.getAnnotation(Fold.class) == null;
170        assert method.isStatic() : "node intrinsic must be static: " + method;
171
172        ResolvedJavaType[] parameterTypes = resolveJavaTypes(method.toParameterTypes(), method.getDeclaringClass());
173
174        if (intrinsic.foldable() && areAllConstant(arguments)) {
175            JavaConstant res = tryFold(arguments, parameterTypes, method);
176            if (!res.equals(COULD_NOT_FOLD)) {
177                return ConstantNode.forConstant(res, metaAccess);
178            }
179        }
180
181        // Prepare the arguments for the reflective constructor call on the node class.
182        Object[] nodeConstructorArguments = prepareArguments(arguments, parameterTypes, method, false);
183        if (nodeConstructorArguments == null) {
184            return null;
185        }
186
187        // Create the new node instance.
188        ResolvedJavaType c = getNodeClass(method, intrinsic);
189        return createNodeInstance(graph, c, parameterTypes, stamp, intrinsic.setStampFromReturnType(), nodeConstructorArguments);
190    }
191
192    /**
193     * Permits a subclass to override the default definition of "intrinsic".
194     */
195    public NodeIntrinsic getIntrinsic(ResolvedJavaMethod method) {
196        return method.getAnnotation(Node.NodeIntrinsic.class);
197    }
198
199    /**
200     * Permits a subclass to override the default definition of "foldable".
201     */
202    public boolean isFoldable(ResolvedJavaMethod method) {
203        return method.getAnnotation(Fold.class) != null;
204    }
205
206    /**
207     * Converts the arguments of an invoke node to object values suitable for use as the arguments
208     * to a reflective invocation of a Java constructor or method.
209     *
210     * @param folding specifies if the invocation is for handling a {@link Fold} annotation
211     * @return the arguments for the reflective invocation or null if an argument of {@code invoke}
212     *         that is expected to be constant isn't
213     */
214    private Object[] prepareArguments(List<ValueNode> arguments, ResolvedJavaType[] parameterTypes, ResolvedJavaMethod target, boolean folding) {
215        Object[] reflectionCallArguments = folding ? new JavaConstant[arguments.size()] : new Object[arguments.size()];
216        for (int i = 0; i < reflectionCallArguments.length; ++i) {
217            int parameterIndex = i;
218            if (!target.isStatic()) {
219                parameterIndex--;
220            }
221            ValueNode argument = arguments.get(i);
222            if (folding || target.getParameterAnnotation(ConstantNodeParameter.class, parameterIndex) != null) {
223                if (!(argument instanceof ConstantNode)) {
224                    return null;
225                }
226                ConstantNode constantNode = (ConstantNode) argument;
227                Constant constant = constantNode.asConstant();
228                /*
229                 * For intrinsification (but not for folding) if we have a Class<?> object we want
230                 * the corresponding ResolvedJavaType.
231                 */
232                ResolvedJavaType type = folding ? null : constantReflection.asJavaType(constant);
233                Object arg;
234                if (type != null) {
235                    /* If we found such a type then it's our arg */
236                    arg = type;
237                    parameterTypes[i] = metaAccess.lookupJavaType(ResolvedJavaType.class);
238                } else {
239                    JavaConstant javaConstant = (JavaConstant) constant;
240                    if (folding) {
241                        /* For folding we want JavaConstants */
242                        arg = javaConstant;
243                    } else {
244                        /* For intrinsification we want want corresponding objects */
245                        if (parameterTypes[i].getKind() == Kind.Boolean) {
246                            arg = Boolean.valueOf(javaConstant.asInt() != 0);
247                        } else if (parameterTypes[i].getKind() == Kind.Byte) {
248                            arg = Byte.valueOf((byte) javaConstant.asInt());
249                        } else if (parameterTypes[i].getKind() == Kind.Short) {
250                            arg = Short.valueOf((short) javaConstant.asInt());
251                        } else if (parameterTypes[i].getKind() == Kind.Char) {
252                            arg = Character.valueOf((char) javaConstant.asInt());
253                        } else if (parameterTypes[i].getKind() == Kind.Object) {
254                            arg = snippetReflection.asObject(parameterTypes[i], javaConstant);
255                        } else {
256                            arg = javaConstant.asBoxedPrimitive();
257                        }
258                    }
259                }
260
261                assert folding || !(arg instanceof JavaConstant);
262                reflectionCallArguments[i] = arg;
263            } else {
264                reflectionCallArguments[i] = argument;
265                parameterTypes[i] = metaAccess.lookupJavaType(ValueNode.class);
266            }
267        }
268        return reflectionCallArguments;
269    }
270
271    public ResolvedJavaType getNodeClass(ResolvedJavaMethod target, NodeIntrinsic intrinsic) {
272        ResolvedJavaType result;
273        if (intrinsic.value() == NodeIntrinsic.class) {
274            result = target.getDeclaringClass();
275        } else {
276            result = metaAccess.lookupJavaType(intrinsic.value());
277        }
278        assert metaAccess.lookupJavaType(ValueNode.class).isAssignableFrom(result) : "Node intrinsic class " + result.toJavaName(false) + " derived from @" + NodeIntrinsic.class.getSimpleName() +
279                        " annotation on " + target.format("%H.%n(%p)") + " is not a subclass of " + ValueNode.class;
280        return result;
281    }
282
283    protected ValueNode createNodeInstance(StructuredGraph graph, ResolvedJavaType nodeClass, ResolvedJavaType[] parameterTypes, Stamp invokeStamp, boolean setStampFromReturnType,
284                    Object[] nodeConstructorArguments) {
285        ResolvedJavaMethod constructor = null;
286        Object[] arguments = null;
287
288        for (ResolvedJavaMethod c : nodeClass.getDeclaredConstructors()) {
289            Object[] match = match(graph, invokeStamp, c, parameterTypes, nodeConstructorArguments);
290
291            if (match != null) {
292                if (constructor == null) {
293                    constructor = c;
294                    arguments = match;
295                    if (!Debug.isEnabled()) {
296                        // Don't verify there's a unique match in non-debug mode
297                        break;
298                    }
299                } else {
300                    throw new JVMCIError("Found multiple constructors in %s compatible with signature %s: %s, %s", nodeClass.toJavaName(), sigString(parameterTypes), constructor, c);
301                }
302            }
303        }
304        if (constructor == null) {
305            throw new JVMCIError("Could not find constructor in %s compatible with signature %s", nodeClass.toJavaName(), sigString(parameterTypes));
306        }
307
308        try {
309            ValueNode intrinsicNode = (ValueNode) invokeConstructor(constructor, arguments);
310
311            if (setStampFromReturnType) {
312                intrinsicNode.setStamp(invokeStamp);
313            }
314            return intrinsicNode;
315        } catch (Exception e) {
316            throw new RuntimeException(constructor + Arrays.toString(nodeConstructorArguments), e);
317        }
318    }
319
320    protected Object invokeConstructor(ResolvedJavaMethod constructor, Object[] arguments) {
321        return snippetReflection.invoke(constructor, null, arguments);
322    }
323
324    private static String sigString(ResolvedJavaType[] types) {
325        StringBuilder sb = new StringBuilder("(");
326        for (int i = 0; i < types.length; i++) {
327            if (i != 0) {
328                sb.append(", ");
329            }
330            sb.append(types[i].toJavaName());
331        }
332        return sb.append(")").toString();
333    }
334
335    private static boolean checkNoMoreInjected(ResolvedJavaMethod c, int start) {
336        int count = c.getSignature().getParameterCount(false);
337        for (int i = start; i < count; i++) {
338            if (c.getParameterAnnotation(InjectedNodeParameter.class, i) != null) {
339                throw new JVMCIError("Injected parameter %d of type %s must precede all non-injected parameters of %s", i,
340                                c.getSignature().getParameterType(i, c.getDeclaringClass()).toJavaName(false), c.format("%H.%n(%p)"));
341            }
342        }
343        return true;
344    }
345
346    private Object[] match(StructuredGraph graph, Stamp invokeStamp, ResolvedJavaMethod c, ResolvedJavaType[] parameterTypes, Object[] nodeConstructorArguments) {
347        Object[] arguments = null;
348        Object[] injected = null;
349
350        ResolvedJavaType[] signature = resolveJavaTypes(c.getSignature().toParameterTypes(null), c.getDeclaringClass());
351        for (int i = 0; i < signature.length; i++) {
352            if (c.getParameterAnnotation(InjectedNodeParameter.class, i) != null) {
353                injected = injected == null ? new Object[1] : Arrays.copyOf(injected, injected.length + 1);
354                Object injectedParameter = snippetReflection.getInjectedNodeIntrinsicParameter(signature[i]);
355                if (injectedParameter != null) {
356                    injected[injected.length - 1] = injectedParameter;
357                } else if (signature[i].equals(metaAccess.lookupJavaType(MetaAccessProvider.class))) {
358                    injected[injected.length - 1] = metaAccess;
359                } else if (signature[i].equals(metaAccess.lookupJavaType(StructuredGraph.class))) {
360                    injected[injected.length - 1] = graph;
361                } else if (signature[i].equals(metaAccess.lookupJavaType(ForeignCallsProvider.class))) {
362                    injected[injected.length - 1] = foreignCalls;
363                } else if (signature[i].equals(metaAccess.lookupJavaType(SnippetReflectionProvider.class))) {
364                    injected[injected.length - 1] = snippetReflection;
365                } else if (signature[i].isAssignableFrom(metaAccess.lookupJavaType(Stamp.class))) {
366                    injected[injected.length - 1] = invokeStamp;
367                } else if (signature[i].isAssignableFrom(metaAccess.lookupJavaType(StampProvider.class))) {
368                    injected[injected.length - 1] = stampProvider;
369                } else {
370                    throw new JVMCIError("Cannot handle injected argument of type %s in %s", signature[i].toJavaName(), c.format("%H.%n(%p)"));
371                }
372            } else {
373                assert checkNoMoreInjected(c, i);
374                break;
375            }
376        }
377        if (injected != null) {
378            // Chop injected arguments from signature
379            signature = Arrays.copyOfRange(signature, injected.length, signature.length);
380        }
381
382        if (Arrays.equals(parameterTypes, signature)) {
383            // Exact match
384            arguments = nodeConstructorArguments;
385
386        } else if (signature.length > 0 && signature[signature.length - 1].isArray()) {
387            // Last constructor parameter is an array, so check if we have a vararg match
388            int fixedArgs = signature.length - 1;
389            if (parameterTypes.length < fixedArgs) {
390                return null;
391            }
392            for (int i = 0; i < fixedArgs; i++) {
393                if (!parameterTypes[i].equals(signature[i])) {
394                    return null;
395                }
396            }
397
398            ResolvedJavaType componentType = signature[fixedArgs].getComponentType();
399            assert componentType != null;
400            for (int i = fixedArgs; i < nodeConstructorArguments.length; i++) {
401                if (!parameterTypes[i].equals(componentType)) {
402                    return null;
403                }
404            }
405            arguments = Arrays.copyOf(nodeConstructorArguments, fixedArgs + 1);
406            arguments[fixedArgs] = snippetReflection.newArray(componentType, nodeConstructorArguments.length - fixedArgs);
407
408            Object varargs = arguments[fixedArgs];
409            for (int i = fixedArgs; i < nodeConstructorArguments.length; i++) {
410                if (componentType.isPrimitive()) {
411                    Array.set(varargs, i - fixedArgs, nodeConstructorArguments[i]);
412                } else {
413                    ((Object[]) varargs)[i - fixedArgs] = nodeConstructorArguments[i];
414                }
415            }
416        } else {
417            return null;
418        }
419
420        if (injected != null) {
421            Object[] copy = new Object[injected.length + arguments.length];
422            System.arraycopy(injected, 0, copy, 0, injected.length);
423            System.arraycopy(arguments, 0, copy, injected.length, arguments.length);
424            arguments = copy;
425        }
426        return arguments;
427    }
428
429    private static String sourceLocation(Node n) {
430        String loc = GraphUtil.approxSourceLocation(n);
431        return loc == null ? "<unknown>" : loc;
432    }
433
434    public void cleanUpReturnCheckCast(Node newInstance) {
435        if (newInstance instanceof ValueNode && (((ValueNode) newInstance).getKind() != Kind.Object || ((ValueNode) newInstance).stamp() == StampFactory.forNodeIntrinsic())) {
436            StructuredGraph graph = (StructuredGraph) newInstance.graph();
437            for (CheckCastNode checkCastNode : newInstance.usages().filter(CheckCastNode.class).snapshot()) {
438                for (Node checkCastUsage : checkCastNode.usages().snapshot()) {
439                    checkCheckCastUsage(graph, newInstance, checkCastNode, checkCastUsage);
440                }
441                GraphUtil.unlinkFixedNode(checkCastNode);
442                GraphUtil.killCFG(checkCastNode);
443            }
444        }
445    }
446
447    private static void checkCheckCastUsage(StructuredGraph graph, Node intrinsifiedNode, Node input, Node usage) {
448        if (usage instanceof ValueAnchorNode) {
449            ValueAnchorNode valueAnchorNode = (ValueAnchorNode) usage;
450            valueAnchorNode.removeAnchoredNode();
451            Debug.log("%s: Removed a ValueAnchor input", Debug.contextSnapshot(JavaMethod.class));
452        } else if (usage instanceof UnboxNode) {
453            UnboxNode unbox = (UnboxNode) usage;
454            unbox.replaceAtUsages(intrinsifiedNode);
455            graph.removeFixed(unbox);
456            Debug.log("%s: Removed an UnboxNode", Debug.contextSnapshot(JavaMethod.class));
457        } else if (usage instanceof UnsafeStoreNode) {
458            UnsafeStoreNode store = (UnsafeStoreNode) usage;
459            store.replaceFirstInput(input, intrinsifiedNode);
460        } else if (usage instanceof LoadFieldNode) {
461            LoadFieldNode load = (LoadFieldNode) usage;
462            load.replaceAtUsages(intrinsifiedNode);
463            graph.removeFixed(load);
464        } else if (usage instanceof MethodCallTargetNode) {
465            MethodCallTargetNode checkCastCallTarget = (MethodCallTargetNode) usage;
466            assert checkCastCallTarget.targetMethod().getAnnotation(NodeIntrinsic.class) != null : "checkcast at " + sourceLocation(input) +
467                            " not used by an unboxing method or node intrinsic, but by a call at " + sourceLocation(checkCastCallTarget.usages().first()) + " to " + checkCastCallTarget.targetMethod();
468            usage.replaceFirstInput(input, intrinsifiedNode);
469            Debug.log("%s: Checkcast used in an other node intrinsic", Debug.contextSnapshot(JavaMethod.class));
470        } else if (usage instanceof FrameState) {
471            usage.replaceFirstInput(input, null);
472            Debug.log("%s: Checkcast used in a FS", Debug.contextSnapshot(JavaMethod.class));
473        } else if (usage instanceof ReturnNode && ((ValueNode) intrinsifiedNode).stamp() == StampFactory.forNodeIntrinsic()) {
474            usage.replaceFirstInput(input, intrinsifiedNode);
475            Debug.log("%s: Checkcast used in a return with forNodeIntrinsic stamp", Debug.contextSnapshot(JavaMethod.class));
476        } else if (usage instanceof IsNullNode) {
477            if (!usage.hasNoUsages()) {
478                assert usage.getUsageCount() == 1 && usage.usages().first().predecessor() == input : usage + " " + input;
479                graph.replaceFloating((FloatingNode) usage, LogicConstantNode.contradiction(graph));
480                Debug.log("%s: Replaced IsNull with false", Debug.contextSnapshot(JavaMethod.class));
481            } else {
482                // Removed as usage of a GuardingPiNode
483            }
484        } else if (usage instanceof ProxyNode) {
485            ProxyNode proxy = (ProxyNode) usage;
486            assert proxy instanceof ValueProxyNode;
487            ProxyNode newProxy = ProxyNode.forValue((ValueNode) intrinsifiedNode, proxy.proxyPoint(), graph);
488            for (Node proxyUsage : usage.usages().snapshot()) {
489                checkCheckCastUsage(graph, newProxy, proxy, proxyUsage);
490            }
491        } else if (usage instanceof PiNode) {
492            for (Node piUsage : usage.usages().snapshot()) {
493                checkCheckCastUsage(graph, intrinsifiedNode, usage, piUsage);
494            }
495        } else {
496            DebugScope.forceDump(graph, "exception");
497            assert false : sourceLocation(usage) + " has unexpected usage " + usage + " of checkcast " + input + " at " + sourceLocation(input);
498        }
499    }
500}