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