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