/*
* Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.truffle.dsl.processor.parser;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic.Kind;
import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.CreateCast;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
import com.oracle.truffle.api.dsl.GeneratedBy;
import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.Introspectable;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeChildren;
import com.oracle.truffle.api.dsl.NodeField;
import com.oracle.truffle.api.dsl.NodeFields;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.dsl.TypeSystemReference;
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.NodeInterface;
import com.oracle.truffle.dsl.processor.CompileErrorException;
import com.oracle.truffle.dsl.processor.Log;
import com.oracle.truffle.dsl.processor.expression.DSLExpression;
import com.oracle.truffle.dsl.processor.expression.DSLExpressionResolver;
import com.oracle.truffle.dsl.processor.expression.InvalidExpressionException;
import com.oracle.truffle.dsl.processor.java.ElementUtils;
import com.oracle.truffle.dsl.processor.java.compiler.CompilerFactory;
import com.oracle.truffle.dsl.processor.java.model.CodeExecutableElement;
import com.oracle.truffle.dsl.processor.java.model.CodeTypeMirror.ArrayCodeTypeMirror;
import com.oracle.truffle.dsl.processor.java.model.CodeVariableElement;
import com.oracle.truffle.dsl.processor.model.AssumptionExpression;
import com.oracle.truffle.dsl.processor.model.CacheExpression;
import com.oracle.truffle.dsl.processor.model.ExecutableTypeData;
import com.oracle.truffle.dsl.processor.model.GuardExpression;
import com.oracle.truffle.dsl.processor.model.MethodSpec;
import com.oracle.truffle.dsl.processor.model.NodeChildData;
import com.oracle.truffle.dsl.processor.model.NodeChildData.Cardinality;
import com.oracle.truffle.dsl.processor.model.NodeData;
import com.oracle.truffle.dsl.processor.model.NodeExecutionData;
import com.oracle.truffle.dsl.processor.model.NodeFieldData;
import com.oracle.truffle.dsl.processor.model.Parameter;
import com.oracle.truffle.dsl.processor.model.ParameterSpec;
import com.oracle.truffle.dsl.processor.model.ShortCircuitData;
import com.oracle.truffle.dsl.processor.model.SpecializationData;
import com.oracle.truffle.dsl.processor.model.SpecializationData.SpecializationKind;
import com.oracle.truffle.dsl.processor.model.SpecializationThrowsData;
import com.oracle.truffle.dsl.processor.model.TemplateMethod;
import com.oracle.truffle.dsl.processor.model.TypeSystemData;
@SuppressWarnings("deprecation")
@com.oracle.truffle.api.dsl.internal.DSLOptions
public class NodeParser extends AbstractParser<NodeData> {
public static final List<Class<? extends Annotation>> ANNOTATIONS = Arrays.asList(Fallback.class, TypeSystemReference.class,
com.oracle.truffle.api.dsl.ShortCircuit.class, Specialization.class,
NodeChild.class,
NodeChildren.class);
@Override
protected NodeData parse(Element element, AnnotationMirror mirror) {
NodeData node = parseRootType((TypeElement) element);
if (Log.isDebug() && node != null) {
String dump = node.dump();
log.message(Kind.ERROR, null, null, null, dump);
}
return node;
}
@Override
protected NodeData filterErrorElements(NodeData model) {
for (Iterator<NodeData> iterator = model.getEnclosingNodes().iterator(); iterator.hasNext();) {
NodeData node = filterErrorElements(iterator.next());
if (node == null) {
iterator.remove();
}
}
if (model.hasErrors()) {
return null;
}
return model;
}
@Override
public boolean isDelegateToRootDeclaredType() {
return true;
}
@Override
public Class<? extends Annotation> getAnnotationType() {
return null;
}
@Override
public List<Class<? extends Annotation>> getTypeDelegatedAnnotationTypes() {
return ANNOTATIONS;
}
private NodeData parseRootType(TypeElement rootType) {
List<NodeData> enclosedNodes = new ArrayList<>();
for (TypeElement enclosedType : ElementFilter.typesIn(rootType.getEnclosedElements())) {
NodeData enclosedChild = parseRootType(enclosedType);
if (enclosedChild != null) {
enclosedNodes.add(enclosedChild);
}
}
NodeData node;
try {
node = parseNode(rootType);
} catch (CompileErrorException e) {
throw e;
} catch (Throwable e) {
RuntimeException e2 = new RuntimeException(String.format("Parsing of Node %s failed.", ElementUtils.getQualifiedName(rootType)));
e2.addSuppressed(e);
throw e2;
}
if (node == null && !enclosedNodes.isEmpty()) {
node = new NodeData(context, rootType);
}
if (node != null) {
for (NodeData enclosedNode : enclosedNodes) {
node.addEnclosedNode(enclosedNode);
}
}
return node;
}
private NodeData parseNode(TypeElement originalTemplateType) {
// reloading the type elements is needed for ecj
TypeElement templateType = ElementUtils.fromTypeMirror(context.reloadTypeElement(originalTemplateType));
if (ElementUtils.findAnnotationMirror(processingEnv, originalTemplateType, GeneratedBy.class) != null) {
// generated nodes should not get called again.
return null;
}
if (!ElementUtils.isAssignable(templateType.asType(), context.getTruffleTypes().getNode())) {
return null;
}
List<TypeElement> lookupTypes = collectSuperClasses(new ArrayList<TypeElement>(), templateType);
List<Element> members = loadMembers(templateType);
// ensure the processed element has at least one @Specialization annotation.
if (!containsSpecializations(members)) {
return null;
}
NodeData node = parseNodeData(templateType, lookupTypes);
if (node.hasErrors()) {
return node;
}
AnnotationMirror reflectable = findFirstAnnotation(lookupTypes, Introspectable.class);
if (reflectable != null) {
node.setReflectable(true);
if (node.getTypeSystem().getOptions().defaultGenerator() != com.oracle.truffle.api.dsl.internal.DSLOptions.DSLGenerator.FLAT) {
node.addError(reflectable, null, "Reflection is not supported by the used DSL layout. Only the flat DSL layout supports reflection.");
}
}
node.getFields().addAll(parseFields(lookupTypes, members));
node.getChildren().addAll(parseChildren(lookupTypes, members));
node.getChildExecutions().addAll(parseExecutions(node.getFields(), node.getChildren(), members));
node.getExecutableTypes().addAll(parseExecutableTypeData(node, members, node.getSignatureSize(), context.getFrameTypes(), false));
initializeExecutableTypes(node);
initializeImportGuards(node, lookupTypes, members);
initializeChildren(node);
if (node.hasErrors()) {
return node; // error sync point
}
node.getSpecializations().addAll(new SpecializationMethodParser(context, node).parse(members));
node.getSpecializations().addAll(new FallbackParser(context, node).parse(members));
node.getCasts().addAll(new CreateCastParser(context, node).parse(members));
node.getShortCircuits().addAll(new ShortCircuitParser(context, node).parse(members));
if (node.hasErrors()) {
return node; // error sync point
}
initializeSpecializations(members, node);
initializeExecutableTypeHierarchy(node);
verifySpecializationSameLength(node);
initializeShortCircuits(node); // requires specializations and polymorphic specializations
verifyVisibilities(node);
verifyMissingAbstractMethods(node, members);
verifyConstructors(node);
verifyNamingConvention(node.getShortCircuits(), "needs");
verifySpecializationThrows(node);
return node;
}
private static void initializeExecutableTypeHierarchy(NodeData node) {
SpecializationData polymorphic = node.getPolymorphicSpecialization();
if (polymorphic != null) {
boolean polymorphicSignatureFound = false;
List<TypeMirror> dynamicTypes = polymorphic.getDynamicTypes();
TypeMirror frame = null;
if (polymorphic.getFrame() != null) {
frame = dynamicTypes.remove(0);
}
ExecutableTypeData polymorphicType = new ExecutableTypeData(node, polymorphic.getReturnType().getType(), "execute", frame, dynamicTypes);
String genericName = ExecutableTypeData.createName(polymorphicType) + "_";
polymorphicType.setUniqueName(genericName);
for (ExecutableTypeData type : node.getExecutableTypes()) {
if (polymorphicType.sameSignature(type)) {
polymorphicSignatureFound = true;
break;
}
}
if (!polymorphicSignatureFound) {
node.getExecutableTypes().add(polymorphicType);
}
}
List<ExecutableTypeData> rootTypes = buildExecutableHierarchy(node);
List<ExecutableTypeData> additionalAbstractRootTypes = new ArrayList<>();
for (int i = 1; i < rootTypes.size(); i++) {
ExecutableTypeData rootType = rootTypes.get(i);
if (rootType.isAbstract()) {
// cannot implemement root
additionalAbstractRootTypes.add(rootType);
} else {
node.getExecutableTypes().remove(rootType);
}
}
if (!additionalAbstractRootTypes.isEmpty()) {
node.addError("Incompatible abstract execute methods found %s.", additionalAbstractRootTypes);
}
namesUnique(node.getExecutableTypes());
}
private static List<ExecutableTypeData> buildExecutableHierarchy(NodeData node) {
List<ExecutableTypeData> executes = node.getExecutableTypes();
if (executes.isEmpty()) {
return Collections.emptyList();
}
List<ExecutableTypeData> hierarchyExecutes = new ArrayList<>(executes);
Collections.sort(hierarchyExecutes);
ExecutableTypeData parent = hierarchyExecutes.get(0);
ListIterator<ExecutableTypeData> executesIterator = hierarchyExecutes.listIterator(1);
buildExecutableHierarchy(node, parent, executesIterator);
return hierarchyExecutes;
}
private static void buildExecutableHierarchy(NodeData node, ExecutableTypeData parent, ListIterator<ExecutableTypeData> executesIterator) {
while (executesIterator.hasNext()) {
ExecutableTypeData other = executesIterator.next();
if (other.canDelegateTo(parent)) {
parent.addDelegatedFrom(other);
executesIterator.remove();
}
}
for (int i = 1; i < parent.getDelegatedFrom().size(); i++) {
buildExecutableHierarchy(node, parent.getDelegatedFrom().get(i - 1), parent.getDelegatedFrom().listIterator(i));
}
}
private List<Element> loadMembers(TypeElement templateType) {
return newElementList(CompilerFactory.getCompiler(templateType).getAllMembersInDeclarationOrder(context.getEnvironment(), templateType));
}
private boolean containsSpecializations(List<Element> elements) {
boolean foundSpecialization = false;
for (ExecutableElement method : ElementFilter.methodsIn(elements)) {
if (ElementUtils.findAnnotationMirror(processingEnv, method, Specialization.class) != null) {
foundSpecialization = true;
break;
}
}
return foundSpecialization;
}
private void initializeImportGuards(NodeData node, List<TypeElement> lookupTypes, List<Element> elements) {
for (TypeElement lookupType : lookupTypes) {
AnnotationMirror importAnnotation = ElementUtils.findAnnotationMirror(processingEnv, lookupType, ImportStatic.class);
if (importAnnotation == null) {
continue;
}
AnnotationValue importClassesValue = ElementUtils.getAnnotationValue(importAnnotation, "value");
List<TypeMirror> importClasses = ElementUtils.getAnnotationValueList(TypeMirror.class, importAnnotation, "value");
if (importClasses.isEmpty()) {
node.addError(importAnnotation, importClassesValue, "At least import guard classes must be specified.");
continue;
}
for (TypeMirror importGuardClass : importClasses) {
if (importGuardClass.getKind() != TypeKind.DECLARED) {
node.addError(importAnnotation, importClassesValue, "The specified import guard class '%s' is not a declared type.", ElementUtils.getQualifiedName(importGuardClass));
continue;
}
TypeElement typeElement = ElementUtils.fromTypeMirror(importGuardClass);
if (typeElement.getEnclosingElement().getKind().isClass() && !typeElement.getModifiers().contains(Modifier.PUBLIC)) {
node.addError(importAnnotation, importClassesValue, "The specified import guard class '%s' must be public.", ElementUtils.getQualifiedName(importGuardClass));
continue;
}
elements.addAll(importPublicStaticMembers(typeElement, false));
}
}
}
@SuppressWarnings("unchecked")
private List<Element> importPublicStaticMembers(TypeElement importGuardClass, boolean includeConstructors) {
// hack to reload type is necessary for incremental compiling in eclipse.
// otherwise methods inside of import guard types are just not found.
TypeElement typeElement = ElementUtils.fromTypeMirror(context.reloadType(importGuardClass.asType()));
List<Element> members = new ArrayList<>();
List<Element> typeElementMembers = (List<Element>) processingEnv.getElementUtils().getAllMembers(typeElement);
// add default constructor
if (typeElement.getModifiers().contains(Modifier.PUBLIC) && ElementFilter.constructorsIn(typeElementMembers).isEmpty()) {
typeElementMembers = new ArrayList<>(typeElementMembers);
typeElementMembers.add(new CodeExecutableElement(ElementUtils.modifiers(Modifier.PUBLIC), typeElement.asType(), null));
}
for (Element importElement : typeElementMembers) {
if (!importElement.getModifiers().contains(Modifier.PUBLIC)) {
continue;
}
if (includeConstructors && importElement.getKind() == ElementKind.CONSTRUCTOR) {
members.add(importElement);
}
if (!importElement.getModifiers().contains(Modifier.STATIC)) {
continue;
}
ElementKind kind = importElement.getKind();
if (kind.isField() || kind == ElementKind.METHOD) {
members.add(importElement);
}
}
/*
* Sort elements by enclosing type to ensure that duplicate static methods are used from the
* most concrete subtype.
*/
Collections.sort(members, new Comparator<Element>() {
Map<TypeMirror, Set<String>> cachedQualifiedNames = new HashMap<>();
public int compare(Element o1, Element o2) {
TypeMirror e1 = o1.getEnclosingElement() != null ? o1.getEnclosingElement().asType() : null;
TypeMirror e2 = o2.getEnclosingElement() != null ? o2.getEnclosingElement().asType() : null;
Set<String> e1SuperTypes = getCachedSuperTypes(e1);
Set<String> e2SuperTypes = getCachedSuperTypes(e2);
return ElementUtils.compareByTypeHierarchy(e1, e1SuperTypes, e2, e2SuperTypes);
}
private Set<String> getCachedSuperTypes(TypeMirror e) {
if (e == null) {
return Collections.emptySet();
}
Set<String> superTypes = cachedQualifiedNames.get(e);
if (superTypes == null) {
superTypes = new HashSet<>(ElementUtils.getQualifiedSuperTypeNames(ElementUtils.fromTypeMirror(e)));
cachedQualifiedNames.put(e, superTypes);
}
return superTypes;
}
});
return members;
}
private NodeData parseNodeData(TypeElement templateType, List<TypeElement> typeHierarchy) {
AnnotationMirror typeSystemMirror = findFirstAnnotation(typeHierarchy, TypeSystemReference.class);
TypeSystemData typeSystem = null;
if (typeSystemMirror != null) {
TypeMirror typeSystemType = ElementUtils.getAnnotationValue(TypeMirror.class, typeSystemMirror, "value");
typeSystem = (TypeSystemData) context.getTemplate(typeSystemType, true);
if (typeSystem == null) {
NodeData nodeData = new NodeData(context, templateType);
nodeData.addError("The used type system '%s' is invalid. Fix errors in the type system first.", ElementUtils.getQualifiedName(typeSystemType));
return nodeData;
}
} else {
// default dummy type system
typeSystem = new TypeSystemData(context, templateType, null, NodeParser.class.getAnnotation(com.oracle.truffle.api.dsl.internal.DSLOptions.class), true);
}
AnnotationMirror nodeInfoMirror = findFirstAnnotation(typeHierarchy, NodeInfo.class);
String shortName = null;
if (nodeInfoMirror != null) {
shortName = ElementUtils.getAnnotationValue(String.class, nodeInfoMirror, "shortName");
}
boolean useNodeFactory = findFirstAnnotation(typeHierarchy, GenerateNodeFactory.class) != null;
return new NodeData(context, templateType, shortName, typeSystem, useNodeFactory);
}
private List<NodeFieldData> parseFields(List<TypeElement> typeHierarchy, List<? extends Element> elements) {
Set<String> names = new HashSet<>();
List<NodeFieldData> fields = new ArrayList<>();
for (VariableElement field : ElementFilter.fieldsIn(elements)) {
if (field.getModifiers().contains(Modifier.STATIC)) {
continue;
}
if (field.getModifiers().contains(Modifier.PUBLIC) || field.getModifiers().contains(Modifier.PROTECTED)) {
String name = field.getSimpleName().toString();
fields.add(new NodeFieldData(field, null, field, false));
names.add(name);
}
}
List<TypeElement> reversedTypeHierarchy = new ArrayList<>(typeHierarchy);
Collections.reverse(reversedTypeHierarchy);
for (TypeElement typeElement : reversedTypeHierarchy) {
AnnotationMirror nodeChildrenMirror = ElementUtils.findAnnotationMirror(processingEnv, typeElement, NodeFields.class);
List<AnnotationMirror> children = ElementUtils.collectAnnotations(context, nodeChildrenMirror, "value", typeElement, NodeField.class);
for (AnnotationMirror mirror : children) {
String name = ElementUtils.firstLetterLowerCase(ElementUtils.getAnnotationValue(String.class, mirror, "name"));
TypeMirror type = ElementUtils.getAnnotationValue(TypeMirror.class, mirror, "type");
if (type != null) {
NodeFieldData field = new NodeFieldData(typeElement, mirror, new CodeVariableElement(type, name), true);
if (name.isEmpty()) {
field.addError(ElementUtils.getAnnotationValue(mirror, "name"), "Field name cannot be empty.");
} else if (names.contains(name)) {
field.addError(ElementUtils.getAnnotationValue(mirror, "name"), "Duplicate field name '%s'.", name);
}
names.add(name);
fields.add(field);
} else {
// Type is null here. This indicates that the type could not be resolved.
// The Java compiler will subsequently raise the appropriate error.
}
}
}
for (NodeFieldData nodeFieldData : fields) {
nodeFieldData.setGetter(findGetter(elements, nodeFieldData.getName(), nodeFieldData.getType()));
}
return fields;
}
private List<NodeChildData> parseChildren(final List<TypeElement> typeHierarchy, List<? extends Element> elements) {
Set<String> shortCircuits = new HashSet<>();
for (ExecutableElement method : ElementFilter.methodsIn(elements)) {
AnnotationMirror mirror = ElementUtils.findAnnotationMirror(processingEnv, method, com.oracle.truffle.api.dsl.ShortCircuit.class);
if (mirror != null) {
shortCircuits.add(ElementUtils.getAnnotationValue(String.class, mirror, "value"));
}
}
Map<String, TypeMirror> castNodeTypes = new HashMap<>();
for (ExecutableElement method : ElementFilter.methodsIn(elements)) {
AnnotationMirror mirror = ElementUtils.findAnnotationMirror(processingEnv, method, CreateCast.class);
if (mirror != null) {
List<String> children = (ElementUtils.getAnnotationValueList(String.class, mirror, "value"));
if (children != null) {
for (String child : children) {
castNodeTypes.put(child, method.getReturnType());
}
}
}
}
List<NodeChildData> parsedChildren = new ArrayList<>();
List<TypeElement> typeHierarchyReversed = new ArrayList<>(typeHierarchy);
Collections.reverse(typeHierarchyReversed);
for (TypeElement type : typeHierarchyReversed) {
AnnotationMirror nodeChildrenMirror = ElementUtils.findAnnotationMirror(processingEnv, type, NodeChildren.class);
TypeMirror nodeClassType = type.getSuperclass();
if (!ElementUtils.isAssignable(nodeClassType, context.getTruffleTypes().getNode())) {
nodeClassType = null;
}
List<AnnotationMirror> children = ElementUtils.collectAnnotations(context, nodeChildrenMirror, "value", type, NodeChild.class);
int index = 0;
for (AnnotationMirror childMirror : children) {
String name = ElementUtils.getAnnotationValue(String.class, childMirror, "value");
if (name.equals("")) {
name = "child" + index;
}
Cardinality cardinality = Cardinality.ONE;
TypeMirror childType = inheritType(childMirror, "type", nodeClassType);
if (childType.getKind() == TypeKind.ARRAY) {
cardinality = Cardinality.MANY;
}
TypeMirror originalChildType = childType;
TypeMirror castNodeType = castNodeTypes.get(name);
if (castNodeType != null) {
childType = castNodeType;
}
Element getter = findGetter(elements, name, childType);
NodeChildData nodeChild = new NodeChildData(type, childMirror, name, childType, originalChildType, getter, cardinality);
parsedChildren.add(nodeChild);
if (nodeChild.getNodeType() == null) {
nodeChild.addError("No valid node type could be resoleved.");
}
if (nodeChild.hasErrors()) {
continue;
}
index++;
}
}
List<NodeChildData> filteredChildren = new ArrayList<>();
Set<String> encounteredNames = new HashSet<>();
for (int i = parsedChildren.size() - 1; i >= 0; i--) {
NodeChildData child = parsedChildren.get(i);
if (!encounteredNames.contains(child.getName())) {
filteredChildren.add(0, child);
encounteredNames.add(child.getName());
}
}
return filteredChildren;
}
private List<NodeExecutionData> parseExecutions(List<NodeFieldData> fields, List<NodeChildData> children, List<? extends Element> elements) {
// pre-parse short circuits
Set<String> shortCircuits = new HashSet<>();
List<ExecutableElement> methods = ElementFilter.methodsIn(elements);
for (ExecutableElement method : methods) {
AnnotationMirror mirror = ElementUtils.findAnnotationMirror(processingEnv, method, com.oracle.truffle.api.dsl.ShortCircuit.class);
if (mirror != null) {
shortCircuits.add(ElementUtils.getAnnotationValue(String.class, mirror, "value"));
}
}
boolean hasVarArgs = false;
int maxSignatureSize = 0;
if (!children.isEmpty()) {
int lastIndex = children.size() - 1;
hasVarArgs = children.get(lastIndex).getCardinality() == Cardinality.MANY;
if (hasVarArgs) {
maxSignatureSize = lastIndex;
} else {
maxSignatureSize = children.size();
}
}
List<NodeFieldData> nonGetterFields = new ArrayList<>();
for (NodeFieldData field : fields) {
if (field.getGetter() == null && field.isGenerated()) {
nonGetterFields.add(field);
}
}
TypeMirror cacheAnnotation = context.getType(Cached.class);
List<TypeMirror> frameTypes = context.getFrameTypes();
// pre-parse specializations to find signature size
for (ExecutableElement method : methods) {
AnnotationMirror mirror = ElementUtils.findAnnotationMirror(processingEnv, method, Specialization.class);
if (mirror == null) {
continue;
}
int currentArgumentIndex = 0;
boolean skipShortCircuit = false;
parameter: for (VariableElement var : method.getParameters()) {
if (skipShortCircuit) {
skipShortCircuit = false;
continue parameter;
}
TypeMirror type = var.asType();
if (currentArgumentIndex == 0) {
// skip optionals
for (TypeMirror frameType : frameTypes) {
if (ElementUtils.typeEquals(type, frameType)) {
continue parameter;
}
}
}
if (currentArgumentIndex < nonGetterFields.size()) {
for (NodeFieldData field : nonGetterFields) {
if (ElementUtils.typeEquals(var.asType(), field.getType())) {
continue parameter;
}
}
}
if (ElementUtils.findAnnotationMirror(var.getAnnotationMirrors(), cacheAnnotation) != null) {
continue parameter;
}
int childIndex = currentArgumentIndex < children.size() ? currentArgumentIndex : children.size() - 1;
if (childIndex != -1) {
NodeChildData child = children.get(childIndex);
if (shortCircuits.contains(NodeExecutionData.createIndexedName(child, currentArgumentIndex - childIndex))) {
skipShortCircuit = true;
}
}
currentArgumentIndex++;
}
maxSignatureSize = Math.max(maxSignatureSize, currentArgumentIndex);
}
List<NodeExecutionData> executions = new ArrayList<>();
for (int i = 0; i < maxSignatureSize; i++) {
boolean varArgParameter = false;
int childIndex = i;
if (i >= children.size() - 1) {
if (hasVarArgs) {
varArgParameter = hasVarArgs;
childIndex = Math.min(i, children.size() - 1);
} else if (i >= children.size()) {
childIndex = -1;
}
}
int varArgsIndex = -1;
boolean shortCircuit = false;
NodeChildData child = null;
if (childIndex != -1) {
varArgsIndex = varArgParameter ? Math.abs(childIndex - i) : -1;
child = children.get(childIndex);
shortCircuit = shortCircuits.contains(NodeExecutionData.createIndexedName(child, varArgsIndex));
}
executions.add(new NodeExecutionData(child, i, varArgsIndex, shortCircuit));
}
return executions;
}
private List<ExecutableTypeData> parseExecutableTypeData(NodeData node, List<? extends Element> elements, int signatureSize, List<TypeMirror> frameTypes, boolean includeFinals) {
List<ExecutableTypeData> typeData = new ArrayList<>();
for (ExecutableElement method : ElementFilter.methodsIn(elements)) {
Set<Modifier> modifiers = method.getModifiers();
if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.STATIC)) {
continue;
}
if (!includeFinals && modifiers.contains(Modifier.FINAL)) {
continue;
}
if (!method.getSimpleName().toString().startsWith("execute")) {
continue;
}
if (ElementUtils.findAnnotationMirror(context.getEnvironment(), method, Specialization.class) != null) {
continue;
}
ExecutableTypeData executableType = new ExecutableTypeData(node, method, signatureSize, context.getFrameTypes());
if (executableType.getFrameParameter() != null) {
boolean supportedType = false;
for (TypeMirror type : frameTypes) {
if (ElementUtils.isAssignable(type, executableType.getFrameParameter())) {
supportedType = true;
break;
}
}
if (!supportedType) {
continue;
}
}
typeData.add(executableType);
}
namesUnique(typeData);
return typeData;
}
private static void namesUnique(List<ExecutableTypeData> typeData) {
List<String> names = new ArrayList<>();
for (ExecutableTypeData type : typeData) {
names.add(type.getUniqueName());
}
while (renameDuplicateIds(names)) {
// fix point
}
for (int i = 0; i < typeData.size(); i++) {
typeData.get(i).setUniqueName(names.get(i));
}
}
private void initializeExecutableTypes(NodeData node) {
List<ExecutableTypeData> allExecutes = node.getExecutableTypes();
Set<String> inconsistentFrameTypes = new HashSet<>();
TypeMirror frameType = null;
for (ExecutableTypeData execute : allExecutes) {
TypeMirror frame = execute.getFrameParameter();
TypeMirror resolvedFrameType;
if (frame != null) {
resolvedFrameType = frame;
if (frameType == null) {
frameType = resolvedFrameType;
} else if (!ElementUtils.typeEquals(frameType, resolvedFrameType)) {
// found inconsistent frame types
inconsistentFrameTypes.add(ElementUtils.getSimpleName(frameType));
inconsistentFrameTypes.add(ElementUtils.getSimpleName(resolvedFrameType));
}
}
}
if (!inconsistentFrameTypes.isEmpty()) {
// ensure they are sorted somehow
List<String> inconsistentFrameTypesList = new ArrayList<>(inconsistentFrameTypes);
Collections.sort(inconsistentFrameTypesList);
node.addError("Invalid inconsistent frame types %s found for the declared execute methods. The frame type must be identical for all execute methods.", inconsistentFrameTypesList);
}
if (frameType == null) {
frameType = context.getType(void.class);
}
node.setFrameType(frameType);
boolean genericFound = false;
for (ExecutableTypeData type : node.getExecutableTypes()) {
if (!type.hasUnexpectedValue(context)) {
genericFound = true;
break;
}
}
// no generic executes
if (!genericFound) {
node.addError("No accessible and overridable generic execute method found. Generic execute methods usually have the " +
"signature 'public abstract {Type} execute(VirtualFrame)' and must not throw any checked exceptions.");
}
int nodeChildDeclarations = 0;
int nodeChildDeclarationsRequired = 0;
List<NodeExecutionData> executions = node.getChildExecutions();
for (NodeExecutionData execution : executions) {
if (execution.getChild() == null) {
nodeChildDeclarationsRequired = execution.getIndex() + 1;
} else {
nodeChildDeclarations++;
}
}
List<String> requireNodeChildDeclarations = new ArrayList<>();
for (ExecutableTypeData type : allExecutes) {
if (type.getEvaluatedCount() < nodeChildDeclarationsRequired) {
requireNodeChildDeclarations.add(ElementUtils.createReferenceName(type.getMethod()));
}
}
if (!requireNodeChildDeclarations.isEmpty()) {
node.addError("Not enough child node declarations found. Please annotate the node class with addtional @NodeChild annotations or remove all execute methods that do not provide all evaluated values. " +
"The following execute methods do not provide all evaluated values for the expected signature size %s: %s.", executions.size(), requireNodeChildDeclarations);
}
if (nodeChildDeclarations > 0 && executions.size() == node.getMinimalEvaluatedParameters()) {
for (NodeChildData child : node.getChildren()) {
child.addError("Unnecessary @NodeChild declaration. All evaluated child values are provided as parameters in execute methods.");
}
}
}
private void initializeChildren(NodeData node) {
initializeExecuteWith(node);
for (NodeChildData child : node.getChildren()) {
TypeMirror nodeType = child.getNodeType();
NodeData fieldNodeData = parseChildNodeData(node, child, ElementUtils.fromTypeMirror(nodeType));
child.setNode(fieldNodeData);
if (fieldNodeData == null || fieldNodeData.hasErrors()) {
child.addError("Node type '%s' is invalid or not a subclass of Node.", ElementUtils.getQualifiedName(nodeType));
} else {
List<ExecutableTypeData> types = child.findGenericExecutableTypes(context);
if (types.isEmpty()) {
AnnotationValue executeWithValue = ElementUtils.getAnnotationValue(child.getMessageAnnotation(), "executeWith");
child.addError(executeWithValue, "No generic execute method found with %s evaluated arguments for node type %s and frame types %s.", child.getExecuteWith().size(),
ElementUtils.getSimpleName(nodeType), ElementUtils.getUniqueIdentifiers(createAllowedChildFrameTypes(node)));
}
}
}
}
private static void initializeExecuteWith(NodeData node) {
for (NodeChildData child : node.getChildren()) {
List<String> executeWithStrings = ElementUtils.getAnnotationValueList(String.class, child.getMessageAnnotation(), "executeWith");
AnnotationValue executeWithValue = ElementUtils.getAnnotationValue(child.getMessageAnnotation(), "executeWith");
List<NodeExecutionData> executeWith = new ArrayList<>();
for (String executeWithString : executeWithStrings) {
if (child.getName().equals(executeWithString)) {
child.addError(executeWithValue, "The child node '%s' cannot be executed with itself.", executeWithString);
continue;
}
NodeExecutionData found = null;
boolean before = true;
for (NodeExecutionData resolveChild : node.getChildExecutions()) {
if (resolveChild.getChild() == child) {
before = false;
continue;
}
if (resolveChild.getIndexedName().equals(executeWithString)) {
found = resolveChild;
break;
}
}
if (found == null) {
child.addError(executeWithValue, "The child node '%s' cannot be executed with '%s'. The child node was not found.", child.getName(), executeWithString);
continue;
} else if (!before) {
child.addError(executeWithValue, "The child node '%s' cannot be executed with '%s'. The node %s is executed after the current node.", child.getName(), executeWithString,
executeWithString);
continue;
}
executeWith.add(found);
}
child.setExecuteWith(executeWith);
}
}
private NodeData parseChildNodeData(NodeData parentNode, NodeChildData child, TypeElement originalTemplateType) {
TypeElement templateType = ElementUtils.fromTypeMirror(context.reloadTypeElement(originalTemplateType));
if (ElementUtils.findAnnotationMirror(processingEnv, originalTemplateType, GeneratedBy.class) != null) {
// generated nodes should not get called again.
return null;
}
if (!ElementUtils.isAssignable(templateType.asType(), context.getTruffleTypes().getNode())) {
return null;
}
List<TypeElement> lookupTypes = collectSuperClasses(new ArrayList<TypeElement>(), templateType);
// Declaration order is not required for child nodes.
List<? extends Element> members = processingEnv.getElementUtils().getAllMembers(templateType);
NodeData node = parseNodeData(templateType, lookupTypes);
if (node.hasErrors()) {
return node;
}
List<TypeMirror> frameTypes = Collections.emptyList();
if (parentNode.getFrameType() != null) {
frameTypes = Arrays.asList(parentNode.getFrameType());
}
node.getExecutableTypes().addAll(parseExecutableTypeData(node, members, child.getExecuteWith().size(), frameTypes, true));
node.setFrameType(parentNode.getFrameType());
return node;
}
private List<TypeMirror> createAllowedChildFrameTypes(NodeData parentNode) {
List<TypeMirror> allowedFrameTypes = new ArrayList<>();
for (TypeMirror frameType : context.getFrameTypes()) {
if (ElementUtils.isAssignable(parentNode.getFrameType(), frameType)) {
allowedFrameTypes.add(frameType);
}
}
return allowedFrameTypes;
}
private void initializeSpecializations(List<? extends Element> elements, final NodeData node) {
if (node.getSpecializations().isEmpty()) {
return;
}
initializeExpressions(elements, node);
if (node.hasErrors()) {
return;
}
initializeGeneric(node);
initializeUninitialized(node);
initializeOrder(node);
initializePolymorphism(node); // requires specializations
initializeReachability(node);
initializeReplaces(node);
resolveReplaces(node);
List<SpecializationData> specializations = node.getSpecializations();
for (SpecializationData cur : specializations) {
for (SpecializationData contained : cur.getReplaces()) {
if (contained != cur) {
contained.getExcludedBy().add(cur);
}
}
}
initializeSpecializationIdsWithMethodNames(node.getSpecializations());
}
private static void initializeOrder(NodeData node) {
List<SpecializationData> specializations = node.getSpecializations();
Collections.sort(specializations);
for (SpecializationData specialization : specializations) {
String searchName = specialization.getInsertBeforeName();
if (searchName == null || specialization.getMethod() == null) {
continue;
}
SpecializationData found = lookupSpecialization(node, searchName);
if (found == null || found.getMethod() == null) {
AnnotationValue value = ElementUtils.getAnnotationValue(specialization.getMarkerAnnotation(), "insertBefore");
specialization.addError(value, "The referenced specialization '%s' could not be found.", searchName);
continue;
}
ExecutableElement currentMethod = specialization.getMethod();
ExecutableElement insertBeforeMethod = found.getMethod();
TypeMirror currentEnclosedType = currentMethod.getEnclosingElement().asType();
TypeMirror insertBeforeEnclosedType = insertBeforeMethod.getEnclosingElement().asType();
if (ElementUtils.typeEquals(currentEnclosedType, insertBeforeEnclosedType) || !ElementUtils.isSubtype(currentEnclosedType, insertBeforeEnclosedType)) {
AnnotationValue value = ElementUtils.getAnnotationValue(specialization.getMarkerAnnotation(), "insertBefore");
specialization.addError(value, "Specializations can only be inserted before specializations in superclasses.", searchName);
continue;
}
specialization.setInsertBefore(found);
}
for (int i = 0; i < specializations.size(); i++) {
SpecializationData specialization = specializations.get(i);
SpecializationData insertBefore = specialization.getInsertBefore();
if (insertBefore != null) {
int insertIndex = specializations.indexOf(insertBefore);
if (insertIndex < i) {
specializations.remove(i);
specializations.add(insertIndex, specialization);
}
}
}
for (int i = 0; i < specializations.size(); i++) {
specializations.get(i).setIndex(i);
}
}
private static void initializeReplaces(NodeData node) {
for (SpecializationData specialization : node.getSpecializations()) {
Set<SpecializationData> resolvedSpecializations = specialization.getReplaces();
resolvedSpecializations.clear();
Set<String> includeNames = specialization.getReplacesNames();
for (String includeName : includeNames) {
// TODO reduce complexity of this lookup.
SpecializationData foundSpecialization = lookupSpecialization(node, includeName);
AnnotationValue value = ElementUtils.getAnnotationValue(specialization.getMarkerAnnotation(), "replaces");
if (value == null) {
// TODO remove if deprecated api was removed.
value = ElementUtils.getAnnotationValue(specialization.getMarkerAnnotation(), "contains");
}
if (foundSpecialization == null) {
specialization.addError(value, "The referenced specialization '%s' could not be found.", includeName);
} else {
if (foundSpecialization.compareTo(specialization) > 0) {
if (foundSpecialization.compareTo(specialization) > 0) {
specialization.addError(value, "The replaced specialization '%s' must be declared before the replacing specialization.", includeName);
}
}
resolvedSpecializations.add(foundSpecialization);
}
}
}
}
private void resolveReplaces(NodeData node) {
// flatten transitive includes
for (SpecializationData specialization : node.getSpecializations()) {
if (specialization.getReplaces().isEmpty()) {
continue;
}
Set<SpecializationData> foundSpecializations = new HashSet<>();
collectIncludes(specialization, foundSpecializations, new HashSet<SpecializationData>());
specialization.getReplaces().addAll(foundSpecializations);
}
}
private static SpecializationData lookupSpecialization(NodeData node, String includeName) {
SpecializationData foundSpecialization = null;
for (SpecializationData searchSpecialization : node.getSpecializations()) {
if (searchSpecialization.getMethodName().equals(includeName)) {
foundSpecialization = searchSpecialization;
break;
}
}
return foundSpecialization;
}
private void collectIncludes(SpecializationData specialization, Set<SpecializationData> found, Set<SpecializationData> visited) {
if (visited.contains(specialization)) {
// circle found
specialization.addError("Circular replaced specialization '%s' found.", specialization.createReferenceName());
return;
}
visited.add(specialization);
for (SpecializationData included : specialization.getReplaces()) {
collectIncludes(included, found, new HashSet<>(visited));
found.add(included);
}
}
private static void initializeReachability(final NodeData node) {
List<SpecializationData> specializations = node.getSpecializations();
for (int i = specializations.size() - 1; i >= 0; i--) {
SpecializationData current = specializations.get(i);
if (current.isPolymorphic()) {
current.setReachable(true);
continue;
}
List<SpecializationData> shadowedBy = null;
for (int j = i - 1; j >= 0; j--) {
SpecializationData prev = specializations.get(j);
if (prev.isPolymorphic()) {
continue;
}
if (!current.isReachableAfter(prev)) {
if (shadowedBy == null) {
shadowedBy = new ArrayList<>();
}
shadowedBy.add(prev);
}
}
if (shadowedBy != null) {
StringBuilder name = new StringBuilder();
String sep = "";
for (SpecializationData shadowSpecialization : shadowedBy) {
name.append(sep);
name.append(shadowSpecialization.createReferenceName());
sep = ", ";
}
current.addError("%s is not reachable. It is shadowed by %s.", current.isFallback() ? "Generic" : "Specialization", name);
}
current.setReachable(shadowedBy == null);
}
}
private static void initializeSpecializationIdsWithMethodNames(List<SpecializationData> specializations) {
List<String> signatures = new ArrayList<>();
for (SpecializationData specialization : specializations) {
if (specialization.isFallback()) {
signatures.add("Fallback");
} else if (specialization.isUninitialized()) {
signatures.add("Uninitialized");
} else if (specialization.isPolymorphic()) {
signatures.add("Polymorphic");
} else {
String name = specialization.getMethodName();
// hack for name clashes with BaseNode.
if (name.equalsIgnoreCase("base")) {
name = name + "0";
} else if (name.startsWith("do")) {
String filteredDo = name.substring(2, name.length());
if (!filteredDo.isEmpty() && Character.isJavaIdentifierStart(filteredDo.charAt(0))) {
name = filteredDo;
}
}
signatures.add(ElementUtils.firstLetterUpperCase(name));
}
}
while (renameDuplicateIds(signatures)) {
// fix point
}
for (int i = 0; i < specializations.size(); i++) {
specializations.get(i).setId(signatures.get(i));
}
}
private static boolean renameDuplicateIds(List<String> signatures) {
boolean changed = false;
Map<String, Integer> counts = new HashMap<>();
for (String s1 : signatures) {
Integer count = counts.get(s1.toLowerCase());
if (count == null) {
count = 0;
}
count++;
counts.put(s1.toLowerCase(), count);
}
for (String s : counts.keySet()) {
int count = counts.get(s);
if (count > 1) {
changed = true;
int number = 0;
for (ListIterator<String> iterator = signatures.listIterator(); iterator.hasNext();) {
String s2 = iterator.next();
if (s.equalsIgnoreCase(s2)) {
iterator.set(s2 + number);
number++;
}
}
}
}
return changed;
}
private void initializeExpressions(List<? extends Element> elements, NodeData node) {
List<Element> members = filterNotAccessibleElements(node.getTemplateType(), elements);
List<VariableElement> fields = new ArrayList<>();
for (NodeFieldData field : node.getFields()) {
fields.add(field.getVariable());
}
for (SpecializationData specialization : node.getSpecializations()) {
if (specialization.getMethod() == null) {
continue;
}
List<Element> specializationMembers = new ArrayList<>(members.size() + specialization.getParameters().size() + fields.size());
for (Parameter p : specialization.getParameters()) {
specializationMembers.add(p.getVariableElement());
}
specializationMembers.addAll(fields);
specializationMembers.addAll(members);
DSLExpressionResolver resolver = new DSLExpressionResolver(context, specializationMembers);
initializeCaches(specialization, resolver);
initializeGuards(specialization, resolver);
if (specialization.hasErrors()) {
continue;
}
initializeLimit(specialization, resolver);
initializeAssumptions(specialization, resolver);
}
}
private void initializeAssumptions(SpecializationData specialization, DSLExpressionResolver resolver) {
final DeclaredType assumptionType = context.getDeclaredType(Assumption.class);
final TypeMirror assumptionArrayType = new ArrayCodeTypeMirror(assumptionType);
final List<String> assumptionDefinitions = ElementUtils.getAnnotationValueList(String.class, specialization.getMarkerAnnotation(), "assumptions");
List<AssumptionExpression> assumptionExpressions = new ArrayList<>();
int assumptionId = 0;
for (String assumption : assumptionDefinitions) {
AssumptionExpression assumptionExpression;
DSLExpression expression = null;
try {
expression = DSLExpression.parse(assumption);
expression.accept(resolver);
assumptionExpression = new AssumptionExpression(specialization, expression, "assumption" + assumptionId);
if (!ElementUtils.isAssignable(expression.getResolvedType(), assumptionType) && !ElementUtils.isAssignable(expression.getResolvedType(), assumptionArrayType)) {
assumptionExpression.addError("Incompatible return type %s. Assumptions must be assignable to %s or %s.", ElementUtils.getSimpleName(expression.getResolvedType()),
ElementUtils.getSimpleName(assumptionType), ElementUtils.getSimpleName(assumptionArrayType));
}
if (specialization.isDynamicParameterBound(expression)) {
specialization.addError("Assumption expressions must not bind dynamic parameter values.");
}
} catch (InvalidExpressionException e) {
assumptionExpression = new AssumptionExpression(specialization, null, "assumption" + assumptionId);
assumptionExpression.addError("Error parsing expression '%s': %s", assumption, e.getMessage());
}
assumptionExpressions.add(assumptionExpression);
assumptionId++;
}
specialization.setAssumptionExpressions(assumptionExpressions);
}
private void initializeLimit(SpecializationData specialization, DSLExpressionResolver resolver) {
AnnotationValue annotationValue = ElementUtils.getAnnotationValue(specialization.getMessageAnnotation(), "limit");
String limitValue;
if (annotationValue == null) {
limitValue = "";
} else {
limitValue = (String) annotationValue.getValue();
}
if (limitValue.isEmpty()) {
limitValue = "3";
} else if (!specialization.hasMultipleInstances()) {
specialization.addWarning(annotationValue, "The limit expression has no effect. Multiple specialization instantiations are impossible for this specialization.");
return;
}
TypeMirror expectedType = context.getType(int.class);
try {
DSLExpression expression = DSLExpression.parse(limitValue);
expression.accept(resolver);
if (!ElementUtils.typeEquals(expression.getResolvedType(), expectedType)) {
specialization.addError(annotationValue, "Incompatible return type %s. Limit expressions must return %s.", ElementUtils.getSimpleName(expression.getResolvedType()),
ElementUtils.getSimpleName(expectedType));
}
if (specialization.isDynamicParameterBound(expression)) {
specialization.addError(annotationValue, "Limit expressions must not bind dynamic parameter values.");
}
specialization.setLimitExpression(expression);
} catch (InvalidExpressionException e) {
specialization.addError(annotationValue, "Error parsing expression '%s': %s", limitValue, e.getMessage());
}
}
private void initializeCaches(SpecializationData specialization, DSLExpressionResolver resolver) {
TypeMirror cacheMirror = context.getType(Cached.class);
List<CacheExpression> expressions = new ArrayList<>();
for (Parameter parameter : specialization.getParameters()) {
AnnotationMirror annotationMirror = ElementUtils.findAnnotationMirror(parameter.getVariableElement().getAnnotationMirrors(), cacheMirror);
if (annotationMirror != null) {
String initializer = ElementUtils.getAnnotationValue(String.class, annotationMirror, "value");
TypeMirror parameterType = parameter.getType();
DSLExpressionResolver localResolver = resolver;
if (parameterType.getKind() == TypeKind.DECLARED) {
localResolver = localResolver.copy(importPublicStaticMembers(ElementUtils.fromTypeMirror(parameterType), true));
}
CacheExpression cacheExpression;
DSLExpression expression = null;
try {
expression = DSLExpression.parse(initializer);
expression.accept(localResolver);
cacheExpression = new CacheExpression(parameter, annotationMirror, expression);
if (!ElementUtils.typeEquals(expression.getResolvedType(), parameter.getType())) {
cacheExpression.addError("Incompatible return type %s. The expression type must be equal to the parameter type %s.", ElementUtils.getSimpleName(expression.getResolvedType()),
ElementUtils.getSimpleName(parameter.getType()));
}
} catch (InvalidExpressionException e) {
cacheExpression = new CacheExpression(parameter, annotationMirror, null);
cacheExpression.addError("Error parsing expression '%s': %s", initializer, e.getMessage());
}
if (!cacheExpression.hasErrors()) {
Cached cached = cacheExpression.getParameter().getVariableElement().getAnnotation(Cached.class);
cacheExpression.setDimensions(cached.dimensions());
if (parameterType.getKind() == TypeKind.ARRAY &&
!ElementUtils.isSubtype(((ArrayType) parameterType).getComponentType(), context.getType(NodeInterface.class))) {
if (cacheExpression.getDimensions() == -1) {
cacheExpression.addWarning("The cached dimensions attribute must be specified for array types.");
}
} else {
if (cacheExpression.getDimensions() != -1) {
cacheExpression.addError("The dimensions attribute has no affect for the type %s.", ElementUtils.getSimpleName(parameterType));
}
}
}
expressions.add(cacheExpression);
}
}
specialization.setCaches(expressions);
if (specialization.hasErrors()) {
return;
}
// verify that cache expressions are bound in the correct order.
for (int i = 0; i < expressions.size(); i++) {
CacheExpression currentExpression = expressions.get(i);
Set<VariableElement> boundVariables = currentExpression.getExpression().findBoundVariableElements();
for (int j = i + 1; j < expressions.size(); j++) {
CacheExpression boundExpression = expressions.get(j);
if (boundVariables.contains(boundExpression.getParameter().getVariableElement())) {
currentExpression.addError("The initializer expression of parameter '%s' binds unitialized parameter '%s. Reorder the parameters to resolve the problem.",
currentExpression.getParameter().getLocalName(), boundExpression.getParameter().getLocalName());
break;
}
}
}
}
private void initializeGuards(SpecializationData specialization, DSLExpressionResolver resolver) {
final TypeMirror booleanType = context.getType(boolean.class);
List<String> guardDefinitions = ElementUtils.getAnnotationValueList(String.class, specialization.getMarkerAnnotation(), "guards");
List<GuardExpression> guardExpressions = new ArrayList<>();
for (String guard : guardDefinitions) {
GuardExpression guardExpression;
DSLExpression expression = null;
try {
expression = DSLExpression.parse(guard);
expression.accept(resolver);
guardExpression = new GuardExpression(specialization, expression);
if (!ElementUtils.typeEquals(expression.getResolvedType(), booleanType)) {
guardExpression.addError("Incompatible return type %s. Guards must return %s.", ElementUtils.getSimpleName(expression.getResolvedType()), ElementUtils.getSimpleName(booleanType));
}
} catch (InvalidExpressionException e) {
guardExpression = new GuardExpression(specialization, null);
guardExpression.addError("Error parsing expression '%s': %s", guard, e.getMessage());
}
guardExpressions.add(guardExpression);
}
specialization.setGuards(guardExpressions);
}
private static List<Element> filterNotAccessibleElements(TypeElement templateType, List<? extends Element> elements) {
String packageName = ElementUtils.getPackageName(templateType);
List<Element> filteredElements = newElementList(elements);
for (Element element : elements) {
Modifier visibility = ElementUtils.getVisibility(element.getModifiers());
if (visibility == Modifier.PRIVATE) {
continue;
} else if (visibility == null) {
String elementPackageName = ElementUtils.getPackageName(element.getEnclosingElement().asType());
if (!Objects.equals(packageName, elementPackageName) && !elementPackageName.equals("java.lang")) {
continue;
}
}
filteredElements.add(element);
}
return filteredElements;
}
private void initializeGeneric(final NodeData node) {
List<SpecializationData> generics = new ArrayList<>();
for (SpecializationData spec : node.getSpecializations()) {
if (spec.isFallback()) {
generics.add(spec);
}
}
if (generics.size() == 1 && node.getSpecializations().size() == 1) {
// TODO this limitation should be lifted
for (SpecializationData generic : generics) {
generic.addError("@%s defined but no @%s.", Fallback.class.getSimpleName(), Specialization.class.getSimpleName());
}
}
if (generics.isEmpty()) {
node.getSpecializations().add(createGenericSpecialization(node));
} else {
if (generics.size() > 1) {
for (SpecializationData generic : generics) {
generic.addError("Only one @%s is allowed per operation.", Fallback.class.getSimpleName());
}
}
}
}
private SpecializationData createGenericSpecialization(final NodeData node) {
FallbackParser parser = new FallbackParser(context, node);
MethodSpec specification = parser.createDefaultMethodSpec(node.getSpecializations().iterator().next().getMethod(), null, true, null);
List<VariableElement> parameterTypes = new ArrayList<>();
int signatureIndex = 1;
for (ParameterSpec spec : specification.getRequired()) {
parameterTypes.add(new CodeVariableElement(createGenericType(node, spec), "arg" + signatureIndex));
if (spec.isSignature()) {
signatureIndex++;
}
}
TypeMirror returnType = createGenericType(node, specification.getReturnType());
SpecializationData generic = parser.create("Generic", TemplateMethod.NO_NATURAL_ORDER, null, null, returnType, parameterTypes);
if (generic == null) {
throw new RuntimeException("Unable to create generic signature for node " + node.getNodeId() + " with " + parameterTypes + ". Specification " + specification + ".");
}
return generic;
}
private TypeMirror createGenericType(NodeData node, ParameterSpec spec) {
NodeExecutionData execution = spec.getExecution();
Collection<TypeMirror> allowedTypes;
if (execution == null) {
allowedTypes = spec.getAllowedTypes();
} else {
allowedTypes = Arrays.asList(node.getGenericType(execution));
}
if (allowedTypes.size() == 1) {
return allowedTypes.iterator().next();
} else {
return ElementUtils.getCommonSuperType(context, allowedTypes);
}
}
private static void initializeUninitialized(final NodeData node) {
SpecializationData generic = node.getGenericSpecialization();
if (generic == null) {
return;
}
TemplateMethod uninializedMethod = new TemplateMethod("Uninitialized", -1, node, generic.getSpecification(), null, null, generic.getReturnType(), generic.getParameters());
// should not use messages from generic specialization
uninializedMethod.getMessages().clear();
node.getSpecializations().add(new SpecializationData(node, uninializedMethod, SpecializationKind.UNINITIALIZED));
}
private void initializePolymorphism(NodeData node) {
if (!node.needsRewrites(context)) {
return;
}
SpecializationData generic = node.getGenericSpecialization();
List<VariableElement> types = new ArrayList<>();
Collection<TypeMirror> frameTypes = new HashSet<>();
for (SpecializationData specialization : node.getSpecializations()) {
if (specialization.getFrame() != null) {
frameTypes.add(specialization.getFrame().getType());
}
}
if (node.supportsFrame()) {
frameTypes.add(node.getFrameType());
}
if (!frameTypes.isEmpty()) {
frameTypes = ElementUtils.uniqueSortedTypes(frameTypes, false);
TypeMirror frameType;
if (frameTypes.size() == 1) {
frameType = frameTypes.iterator().next();
} else {
frameType = context.getType(Frame.class);
}
types.add(new CodeVariableElement(frameType, TemplateMethod.FRAME_NAME));
}
TypeMirror returnType = null;
int index = 0;
for (Parameter genericParameter : generic.getReturnTypeAndParameters()) {
TypeMirror polymorphicType;
if (genericParameter.getLocalName().equals(TemplateMethod.FRAME_NAME)) {
continue;
}
boolean isReturnParameter = genericParameter == generic.getReturnType();
if (!genericParameter.getSpecification().isSignature()) {
polymorphicType = genericParameter.getType();
} else {
Collection<TypeMirror> usedTypes = new HashSet<>();
for (SpecializationData specialization : node.getSpecializations()) {
if (specialization.isUninitialized()) {
continue;
}
Parameter parameter = specialization.findParameter(genericParameter.getLocalName());
if (parameter == specialization.getReturnType() && specialization.isFallback() && specialization.getMethod() == null) {
continue;
}
if (parameter == null) {
throw new AssertionError("Parameter existed in generic specialization but not in specialized. param = " + genericParameter.getLocalName());
}
usedTypes.add(parameter.getType());
}
usedTypes = ElementUtils.uniqueSortedTypes(usedTypes, false);
if (usedTypes.size() == 1) {
polymorphicType = usedTypes.iterator().next();
} else {
polymorphicType = ElementUtils.getCommonSuperType(context, usedTypes);
}
NodeExecutionData execution = genericParameter.getSpecification().getExecution();
if (execution != null && !ElementUtils.isSubtypeBoxed(context, polymorphicType, node.getGenericType(execution))) {
throw new AssertionError(String.format("Polymorphic types %s not compatible to generic type %s.", polymorphicType, node.getGenericType(execution)));
}
}
if (isReturnParameter) {
returnType = polymorphicType;
} else {
types.add(new CodeVariableElement(polymorphicType, "param" + index));
}
index++;
}
SpecializationMethodParser parser = new SpecializationMethodParser(context, node);
SpecializationData polymorphic = parser.create("Polymorphic", TemplateMethod.NO_NATURAL_ORDER, null, null, returnType, types);
if (polymorphic == null) {
throw new AssertionError("Failed to parse polymorphic signature. " + parser.createDefaultMethodSpec(null, null, false, null) + " Types: " + returnType + " - " + types);
}
polymorphic.setKind(SpecializationKind.POLYMORPHIC);
node.getSpecializations().add(polymorphic);
}
private void initializeShortCircuits(NodeData node) {
Map<String, List<ShortCircuitData>> groupedShortCircuits = groupShortCircuits(node.getShortCircuits());
boolean valid = true;
List<NodeExecutionData> shortCircuitExecutions = new ArrayList<>();
for (NodeExecutionData execution : node.getChildExecutions()) {
if (!execution.isShortCircuit()) {
continue;
}
shortCircuitExecutions.add(execution);
String valueName = execution.getIndexedName();
List<ShortCircuitData> availableCircuits = groupedShortCircuits.get(valueName);
if (availableCircuits == null || availableCircuits.isEmpty()) {
node.addError("@%s method for short cut value '%s' required.", com.oracle.truffle.api.dsl.ShortCircuit.class.getSimpleName(), valueName);
valid = false;
continue;
}
boolean sameMethodName = true;
String methodName = availableCircuits.get(0).getMethodName();
for (ShortCircuitData circuit : availableCircuits) {
if (!circuit.getMethodName().equals(methodName)) {
sameMethodName = false;
}
}
if (!sameMethodName) {
for (ShortCircuitData circuit : availableCircuits) {
circuit.addError("All short circuits for short cut value '%s' must have the same method name.", valueName);
}
valid = false;
continue;
}
ShortCircuitData genericCircuit = null;
for (ShortCircuitData circuit : availableCircuits) {
if (isGenericShortCutMethod(circuit)) {
genericCircuit = circuit;
break;
}
}
if (genericCircuit == null) {
node.addError("No generic @%s method available for short cut value '%s'.", com.oracle.truffle.api.dsl.ShortCircuit.class.getSimpleName(), valueName);
valid = false;
continue;
}
for (ShortCircuitData circuit : availableCircuits) {
if (circuit != genericCircuit) {
circuit.setGenericShortCircuitMethod(genericCircuit);
}
}
}
if (!valid) {
return;
}
List<SpecializationData> specializations = new ArrayList<>();
specializations.addAll(node.getSpecializations());
for (SpecializationData specialization : specializations) {
List<ShortCircuitData> assignedShortCuts = new ArrayList<>(shortCircuitExecutions.size());
for (NodeExecutionData shortCircuit : shortCircuitExecutions) {
List<ShortCircuitData> availableShortCuts = groupedShortCircuits.get(shortCircuit.getIndexedName());
ShortCircuitData genericShortCircuit = null;
ShortCircuitData compatibleShortCircuit = null;
for (ShortCircuitData circuit : availableShortCuts) {
if (circuit.isGeneric()) {
genericShortCircuit = circuit;
} else if (circuit.isCompatibleTo(specialization)) {
compatibleShortCircuit = circuit;
}
}
if (compatibleShortCircuit == null) {
compatibleShortCircuit = genericShortCircuit;
}
assignedShortCuts.add(compatibleShortCircuit);
}
specialization.setShortCircuits(assignedShortCuts);
}
}
private boolean isGenericShortCutMethod(ShortCircuitData method) {
for (Parameter parameter : method.getParameters()) {
NodeExecutionData execution = parameter.getSpecification().getExecution();
if (execution == null) {
continue;
}
ExecutableTypeData found = null;
List<ExecutableTypeData> executableElements = execution.getChild().findGenericExecutableTypes(context);
for (ExecutableTypeData executable : executableElements) {
if (ElementUtils.typeEquals(executable.getReturnType(), parameter.getType())) {
found = executable;
break;
}
}
if (found == null) {
return false;
}
}
return true;
}
private static Map<String, List<ShortCircuitData>> groupShortCircuits(List<ShortCircuitData> shortCircuits) {
Map<String, List<ShortCircuitData>> group = new HashMap<>();
for (ShortCircuitData shortCircuit : shortCircuits) {
List<ShortCircuitData> circuits = group.get(shortCircuit.getValueName());
if (circuits == null) {
circuits = new ArrayList<>();
group.put(shortCircuit.getValueName(), circuits);
}
circuits.add(shortCircuit);
}
return group;
}
private static boolean verifySpecializationSameLength(NodeData nodeData) {
int lastArgs = -1;
for (SpecializationData specializationData : nodeData.getSpecializations()) {
int signatureArgs = specializationData.getSignatureSize();
if (lastArgs == signatureArgs) {
continue;
}
if (lastArgs != -1) {
for (SpecializationData specialization : nodeData.getSpecializations()) {
specialization.addError("All specializations must have the same number of arguments.");
}
return false;
} else {
lastArgs = signatureArgs;
}
}
return true;
}
private static void verifyVisibilities(NodeData node) {
if (node.getTemplateType().getModifiers().contains(Modifier.PRIVATE) && node.getSpecializations().size() > 0) {
node.addError("Classes containing a @%s annotation must not be private.", Specialization.class.getSimpleName());
}
}
/**
* @see "https://bugs.openjdk.java.net/browse/JDK-8039214"
*/
@SuppressWarnings("unused")
private static List<Element> newElementList(List<? extends Element> src) {
List<Element> workaround = new ArrayList<Element>(src);
return workaround;
}
private static void verifyMissingAbstractMethods(NodeData nodeData, List<? extends Element> originalElements) {
if (!nodeData.needsFactory()) {
// missing abstract methods only needs to be implemented
// if we need go generate factory for it.
return;
}
List<Element> elements = newElementList(originalElements);
Set<Element> unusedElements = new HashSet<>(elements);
for (ExecutableElement method : nodeData.getAllTemplateMethods()) {
unusedElements.remove(method);
}
for (NodeFieldData field : nodeData.getFields()) {
if (field.getGetter() != null) {
unusedElements.remove(field.getGetter());
}
}
for (NodeChildData child : nodeData.getChildren()) {
if (child.getAccessElement() != null) {
unusedElements.remove(child.getAccessElement());
}
}
for (ExecutableElement unusedMethod : ElementFilter.methodsIn(unusedElements)) {
if (unusedMethod.getModifiers().contains(Modifier.ABSTRACT)) {
nodeData.addError("The type %s must implement the inherited abstract method %s.", ElementUtils.getSimpleName(nodeData.getTemplateType()),
ElementUtils.getReadableSignature(unusedMethod));
}
}
}
private static void verifyNamingConvention(List<? extends TemplateMethod> methods, String prefix) {
for (int i = 0; i < methods.size(); i++) {
TemplateMethod m1 = methods.get(i);
if (m1.getMethodName().length() < 3 || !m1.getMethodName().startsWith(prefix)) {
m1.addError("Naming convention: method name must start with '%s'.", prefix);
}
}
}
private static void verifySpecializationThrows(NodeData node) {
Map<String, SpecializationData> specializationMap = new HashMap<>();
for (SpecializationData spec : node.getSpecializations()) {
specializationMap.put(spec.getMethodName(), spec);
}
for (SpecializationData sourceSpecialization : node.getSpecializations()) {
if (sourceSpecialization.getExceptions() != null) {
for (SpecializationThrowsData throwsData : sourceSpecialization.getExceptions()) {
for (SpecializationThrowsData otherThrowsData : sourceSpecialization.getExceptions()) {
if (otherThrowsData != throwsData && ElementUtils.typeEquals(otherThrowsData.getJavaClass(), throwsData.getJavaClass())) {
throwsData.addError("Duplicate exception type.");
}
}
}
}
}
}
private static void verifyConstructors(NodeData nodeData) {
List<ExecutableElement> constructors = ElementFilter.constructorsIn(nodeData.getTemplateType().getEnclosedElements());
if (constructors.isEmpty()) {
return;
}
boolean oneNonPrivate = false;
for (ExecutableElement constructor : constructors) {
if (ElementUtils.getVisibility(constructor.getModifiers()) != Modifier.PRIVATE) {
oneNonPrivate = true;
break;
}
}
if (!oneNonPrivate && !nodeData.getTemplateType().getModifiers().contains(Modifier.PRIVATE)) {
nodeData.addError("At least one constructor must be non-private.");
}
}
private AnnotationMirror findFirstAnnotation(List<? extends Element> elements, Class<? extends Annotation> annotation) {
for (Element element : elements) {
AnnotationMirror mirror = ElementUtils.findAnnotationMirror(processingEnv, element, annotation);
if (mirror != null) {
return mirror;
}
}
return null;
}
private TypeMirror inheritType(AnnotationMirror annotation, String valueName, TypeMirror parentType) {
TypeMirror inhertNodeType = context.getTruffleTypes().getNode();
TypeMirror value = ElementUtils.getAnnotationValue(TypeMirror.class, annotation, valueName);
if (ElementUtils.typeEquals(inhertNodeType, value)) {
return parentType;
} else {
return value;
}
}
private ExecutableElement findGetter(List<? extends Element> elements, String variableName, TypeMirror type) {
if (type == null) {
return null;
}
String methodName;
if (ElementUtils.typeEquals(type, context.getType(boolean.class))) {
methodName = "is" + ElementUtils.firstLetterUpperCase(variableName);
} else {
methodName = "get" + ElementUtils.firstLetterUpperCase(variableName);
}
for (ExecutableElement method : ElementFilter.methodsIn(elements)) {
if (method.getSimpleName().toString().equals(methodName) && method.getParameters().size() == 0 && ElementUtils.isAssignable(type, method.getReturnType())) {
return method;
}
}
return null;
}
private static List<TypeElement> collectSuperClasses(List<TypeElement> collection, TypeElement element) {
if (element != null) {
collection.add(element);
if (element.getSuperclass() != null) {
collectSuperClasses(collection, ElementUtils.fromTypeMirror(element.getSuperclass()));
}
}
return collection;
}
}