1/*
2 * Copyright (c) 2014, 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 */
23package org.graalvm.compiler.replacements.verifier;
24
25import java.lang.annotation.Annotation;
26import java.util.ArrayList;
27import java.util.Arrays;
28import java.util.Collections;
29import java.util.Formatter;
30import java.util.HashMap;
31import java.util.List;
32import java.util.Map;
33
34import javax.annotation.processing.ProcessingEnvironment;
35import javax.lang.model.element.AnnotationMirror;
36import javax.lang.model.element.Element;
37import javax.lang.model.element.ElementKind;
38import javax.lang.model.element.ExecutableElement;
39import javax.lang.model.element.Modifier;
40import javax.lang.model.element.TypeElement;
41import javax.lang.model.element.VariableElement;
42import javax.lang.model.type.ArrayType;
43import javax.lang.model.type.TypeKind;
44import javax.lang.model.type.TypeMirror;
45import javax.lang.model.type.TypeVariable;
46import javax.lang.model.util.ElementFilter;
47import javax.tools.Diagnostic.Kind;
48
49import org.graalvm.compiler.graph.Node.ConstantNodeParameter;
50import org.graalvm.compiler.graph.Node.InjectedNodeParameter;
51import org.graalvm.compiler.graph.Node.NodeIntrinsic;
52import org.graalvm.compiler.nodeinfo.InputType;
53import org.graalvm.compiler.nodeinfo.NodeInfo;
54import org.graalvm.compiler.nodeinfo.StructuralInput.MarkerType;
55
56public final class NodeIntrinsicVerifier extends AbstractVerifier {
57
58    private static final String NODE_CLASS_NAME = "value";
59
60    private TypeMirror nodeType() {
61        return env.getElementUtils().getTypeElement("org.graalvm.compiler.graph.Node").asType();
62    }
63
64    private TypeMirror stampType() {
65        return env.getElementUtils().getTypeElement("org.graalvm.compiler.core.common.type.Stamp").asType();
66    }
67
68    private TypeMirror valueNodeType() {
69        return env.getElementUtils().getTypeElement("org.graalvm.compiler.nodes.ValueNode").asType();
70    }
71
72    private TypeMirror classType() {
73        return env.getElementUtils().getTypeElement("java.lang.Class").asType();
74    }
75
76    private TypeMirror resolvedJavaTypeType() {
77        return env.getElementUtils().getTypeElement("jdk.vm.ci.meta.ResolvedJavaType").asType();
78    }
79
80    private TypeMirror resolvedJavaMethodType() {
81        return env.getElementUtils().getTypeElement("jdk.vm.ci.meta.ResolvedJavaMethod").asType();
82    }
83
84    private TypeMirror structuralInputType() {
85        return env.getElementUtils().getTypeElement("org.graalvm.compiler.nodeinfo.StructuralInput").asType();
86    }
87
88    private TypeMirror graphBuilderContextType() {
89        return env.getElementUtils().getTypeElement("org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext").asType();
90    }
91
92    public NodeIntrinsicVerifier(ProcessingEnvironment env) {
93        super(env);
94    }
95
96    @Override
97    public Class<? extends Annotation> getAnnotationClass() {
98        return NodeIntrinsic.class;
99    }
100
101    @Override
102    public void verify(Element element, AnnotationMirror annotation, PluginGenerator generator) {
103        if (element.getKind() != ElementKind.METHOD) {
104            assert false : "Element is guaranteed to be a method.";
105            return;
106        }
107
108        ExecutableElement intrinsicMethod = (ExecutableElement) element;
109        if (!intrinsicMethod.getModifiers().contains(Modifier.STATIC)) {
110            env.getMessager().printMessage(Kind.ERROR, String.format("A @%s method must be static.", NodeIntrinsic.class.getSimpleName()), element, annotation);
111        }
112        if (!intrinsicMethod.getModifiers().contains(Modifier.NATIVE)) {
113            env.getMessager().printMessage(Kind.ERROR, String.format("A @%s method must be native.", NodeIntrinsic.class.getSimpleName()), element, annotation);
114        }
115
116        TypeMirror nodeClassMirror = resolveAnnotationValue(TypeMirror.class, findAnnotationValue(annotation, NODE_CLASS_NAME));
117        TypeElement nodeClass = (TypeElement) env.getTypeUtils().asElement(nodeClassMirror);
118        if (nodeClass.getSimpleName().contentEquals(NodeIntrinsic.class.getSimpleName())) {
119            // default value
120            Element enclosingElement = intrinsicMethod.getEnclosingElement();
121            while (enclosingElement != null && enclosingElement.getKind() != ElementKind.CLASS) {
122                enclosingElement = enclosingElement.getEnclosingElement();
123            }
124            if (enclosingElement != null) {
125                nodeClass = (TypeElement) enclosingElement;
126            }
127        }
128
129        TypeMirror returnType = intrinsicMethod.getReturnType();
130        if (returnType instanceof TypeVariable) {
131            env.getMessager().printMessage(Kind.ERROR, "@NodeIntrinsic cannot have a generic return type.", element, annotation);
132        }
133
134        boolean injectedStampIsNonNull = intrinsicMethod.getAnnotation(NodeIntrinsic.class).injectedStampIsNonNull();
135
136        if (returnType.getKind() == TypeKind.VOID) {
137            for (VariableElement parameter : intrinsicMethod.getParameters()) {
138                if (parameter.getAnnotation(InjectedNodeParameter.class) != null) {
139                    env.getMessager().printMessage(Kind.ERROR, "@NodeIntrinsic with an injected Stamp parameter cannot have a void return type.", element, annotation);
140                    break;
141                }
142            }
143        }
144
145        TypeMirror[] constructorSignature = constructorSignature(intrinsicMethod);
146        Map<ExecutableElement, String> nonMatches = new HashMap<>();
147        List<ExecutableElement> factories = findIntrinsifyFactoryMethod(nodeClass, constructorSignature, nonMatches, injectedStampIsNonNull);
148        List<ExecutableElement> constructors = Collections.emptyList();
149        if (nodeClass.getModifiers().contains(Modifier.ABSTRACT)) {
150            if (factories.isEmpty()) {
151                env.getMessager().printMessage(Kind.ERROR, String.format("Cannot make a node intrinsic for an abstract class %s.", nodeClass.getSimpleName()), element, annotation);
152            }
153        } else if (!isNodeType(nodeClass)) {
154            if (factories.isEmpty()) {
155                env.getMessager().printMessage(Kind.ERROR, String.format("%s is not a subclass of %s.", nodeClass.getSimpleName(), nodeType()), element, annotation);
156            }
157        } else {
158            TypeMirror ret = returnType;
159            if (env.getTypeUtils().isAssignable(ret, structuralInputType())) {
160                checkInputType(nodeClass, ret, element, annotation);
161            }
162
163            constructors = findConstructors(nodeClass, constructorSignature, nonMatches, injectedStampIsNonNull);
164        }
165        Formatter msg = new Formatter();
166        if (factories.size() > 1) {
167            msg.format("Found more than one factory in %s matching node intrinsic:", nodeClass);
168            for (ExecutableElement candidate : factories) {
169                msg.format("%n  %s", candidate);
170            }
171            env.getMessager().printMessage(Kind.ERROR, msg.toString(), intrinsicMethod, annotation);
172        } else if (constructors.size() > 1) {
173            msg.format("Found more than one constructor in %s matching node intrinsic:", nodeClass);
174            for (ExecutableElement candidate : constructors) {
175                msg.format("%n  %s", candidate);
176            }
177            env.getMessager().printMessage(Kind.ERROR, msg.toString(), intrinsicMethod, annotation);
178        } else if (factories.size() == 1) {
179            generator.addPlugin(new GeneratedNodeIntrinsicPlugin.CustomFactoryPlugin(intrinsicMethod, factories.get(0), constructorSignature));
180        } else if (constructors.size() == 1) {
181            generator.addPlugin(new GeneratedNodeIntrinsicPlugin.ConstructorPlugin(intrinsicMethod, constructors.get(0), constructorSignature));
182        } else {
183            msg.format("Could not find any factories or constructors in %s matching node intrinsic", nodeClass);
184            if (!nonMatches.isEmpty()) {
185                msg.format("%nFactories and constructors that failed to match:");
186                for (Map.Entry<ExecutableElement, String> e : nonMatches.entrySet()) {
187                    msg.format("%n  %s: %s", e.getKey(), e.getValue());
188                }
189            }
190            env.getMessager().printMessage(Kind.ERROR, msg.toString(), intrinsicMethod, annotation);
191        }
192    }
193
194    private void checkInputType(TypeElement nodeClass, TypeMirror returnType, Element element, AnnotationMirror annotation) {
195        InputType inputType = getInputType(returnType, element, annotation);
196        if (inputType != InputType.Value) {
197            boolean allowed = false;
198            InputType[] allowedTypes = nodeClass.getAnnotation(NodeInfo.class).allowedUsageTypes();
199            for (InputType allowedType : allowedTypes) {
200                if (inputType == allowedType) {
201                    allowed = true;
202                    break;
203                }
204            }
205            if (!allowed) {
206                env.getMessager().printMessage(Kind.ERROR, String.format("@NodeIntrinsic returns input type %s, but only %s is allowed.", inputType, Arrays.toString(allowedTypes)), element,
207                                annotation);
208            }
209        }
210    }
211
212    private InputType getInputType(TypeMirror type, Element element, AnnotationMirror annotation) {
213        TypeElement current = (TypeElement) env.getTypeUtils().asElement(type);
214        while (current != null) {
215            MarkerType markerType = current.getAnnotation(MarkerType.class);
216            if (markerType != null) {
217                return markerType.value();
218            }
219
220            current = (TypeElement) env.getTypeUtils().asElement(current.getSuperclass());
221        }
222
223        env.getMessager().printMessage(Kind.ERROR, String.format("The class %s is a subclass of StructuralInput, but isn't annotated with @MarkerType.", type), element, annotation);
224        return InputType.Value;
225    }
226
227    private boolean isNodeType(TypeElement nodeClass) {
228        return env.getTypeUtils().isSubtype(nodeClass.asType(), nodeType());
229    }
230
231    private TypeMirror[] constructorSignature(ExecutableElement method) {
232        TypeMirror[] parameters = new TypeMirror[method.getParameters().size()];
233        for (int i = 0; i < method.getParameters().size(); i++) {
234            VariableElement parameter = method.getParameters().get(i);
235            if (parameter.getAnnotation(ConstantNodeParameter.class) == null) {
236                parameters[i] = valueNodeType();
237            } else {
238                TypeMirror type = parameter.asType();
239                if (isTypeCompatible(type, classType())) {
240                    type = resolvedJavaTypeType();
241                }
242                parameters[i] = type;
243            }
244        }
245        return parameters;
246    }
247
248    private List<ExecutableElement> findConstructors(TypeElement nodeClass, TypeMirror[] signature, Map<ExecutableElement, String> nonMatches, boolean requiresInjectedStamp) {
249        List<ExecutableElement> constructors = ElementFilter.constructorsIn(nodeClass.getEnclosedElements());
250        List<ExecutableElement> found = new ArrayList<>(constructors.size());
251        for (ExecutableElement constructor : constructors) {
252            if (matchSignature(0, constructor, signature, nonMatches, requiresInjectedStamp)) {
253                found.add(constructor);
254            }
255        }
256        return found;
257    }
258
259    private List<ExecutableElement> findIntrinsifyFactoryMethod(TypeElement nodeClass, TypeMirror[] signature, Map<ExecutableElement, String> nonMatches, boolean requiresInjectedStamp) {
260        List<ExecutableElement> methods = ElementFilter.methodsIn(nodeClass.getEnclosedElements());
261        List<ExecutableElement> found = new ArrayList<>(methods.size());
262        for (ExecutableElement method : methods) {
263            if (!method.getSimpleName().toString().equals("intrinsify")) {
264                continue;
265            }
266
267            if (method.getParameters().size() < 2) {
268                continue;
269            }
270
271            VariableElement firstArg = method.getParameters().get(0);
272            if (!isTypeCompatible(firstArg.asType(), graphBuilderContextType())) {
273                continue;
274            }
275
276            VariableElement secondArg = method.getParameters().get(1);
277            if (!isTypeCompatible(secondArg.asType(), resolvedJavaMethodType())) {
278                continue;
279            }
280
281            if (method.getReturnType().getKind() != TypeKind.BOOLEAN) {
282                continue;
283            }
284
285            if (matchSignature(2, method, signature, nonMatches, requiresInjectedStamp)) {
286                found.add(method);
287            }
288        }
289        return found;
290    }
291
292    private boolean matchSignature(int numSkippedParameters, ExecutableElement method, TypeMirror[] signature, Map<ExecutableElement, String> nonMatches, boolean requiresInjectedStamp) {
293        int sIdx = 0;
294        int cIdx = numSkippedParameters;
295        boolean missingStampArgument = requiresInjectedStamp;
296        while (cIdx < method.getParameters().size()) {
297            VariableElement parameter = method.getParameters().get(cIdx++);
298            TypeMirror paramType = parameter.asType();
299            if (parameter.getAnnotation(InjectedNodeParameter.class) != null) {
300                if (missingStampArgument && env.getTypeUtils().isSameType(paramType, stampType())) {
301                    missingStampArgument = false;
302                }
303                // skip injected parameters
304                continue;
305            }
306            if (missingStampArgument) {
307                nonMatches.put(method, String.format("missing injected %s argument", stampType()));
308                return false;
309            }
310
311            if (cIdx == method.getParameters().size() && paramType.getKind() == TypeKind.ARRAY) {
312                // last argument of constructor is varargs, match remaining intrinsic arguments
313                TypeMirror varargsType = ((ArrayType) paramType).getComponentType();
314                while (sIdx < signature.length) {
315                    if (!isTypeCompatible(varargsType, signature[sIdx++])) {
316                        nonMatches.put(method, String.format("the types of argument %d are incompatible: %s != %s", sIdx, varargsType, signature[sIdx - 1]));
317                        return false;
318                    }
319                }
320            } else if (sIdx >= signature.length) {
321                // too many arguments in intrinsic method
322                nonMatches.put(method, "too many arguments");
323                return false;
324            } else if (!isTypeCompatible(paramType, signature[sIdx++])) {
325                nonMatches.put(method, String.format("the type of argument %d is incompatible: %s != %s", sIdx, paramType, signature[sIdx - 1]));
326                return false;
327            }
328        }
329        if (missingStampArgument) {
330            nonMatches.put(method, String.format("missing injected %s argument", stampType()));
331            return false;
332        }
333
334        if (sIdx != signature.length) {
335            nonMatches.put(method, "not enough arguments");
336            return false;
337        }
338        return true;
339    }
340
341    private boolean isTypeCompatible(TypeMirror originalType, TypeMirror substitutionType) {
342        TypeMirror original = originalType;
343        TypeMirror substitution = substitutionType;
344        if (needsErasure(original)) {
345            original = env.getTypeUtils().erasure(original);
346        }
347        if (needsErasure(substitution)) {
348            substitution = env.getTypeUtils().erasure(substitution);
349        }
350        return env.getTypeUtils().isSameType(original, substitution);
351    }
352
353    private static boolean needsErasure(TypeMirror typeMirror) {
354        return typeMirror.getKind() != TypeKind.NONE && typeMirror.getKind() != TypeKind.VOID && !typeMirror.getKind().isPrimitive() && typeMirror.getKind() != TypeKind.OTHER &&
355                        typeMirror.getKind() != TypeKind.NULL;
356    }
357}
358