blob: e4a16b0d81dd2f433784307137efce46bc43fed6 [file] [log] [blame]
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library simple_types_inferrer;
import '../native_handler.dart' as native;
import '../elements/elements.dart';
import '../dart2jslib.dart';
import '../tree/tree.dart';
import '../util/util.dart' show Link;
import 'types.dart' show TypesInferrer, ConcreteType, ClassBaseType;
/**
* A work queue that ensures there are no duplicates, and adds and
* removes in LIFO.
*/
class WorkSet<E extends Element> {
final List<E> queue = new List<E>();
final Set<E> elementsInQueue = new Set<E>();
void add(E element) {
element = element.implementation;
if (elementsInQueue.contains(element)) return;
queue.addLast(element);
elementsInQueue.add(element);
}
E remove() {
E element = queue.removeLast();
elementsInQueue.remove(element);
return element;
}
bool get isEmpty => queue.isEmpty;
}
class SimpleTypesInferrer extends TypesInferrer {
/**
* Maps an element to its callers.
*/
final Map<Element, Set<Element>> callersOf =
new Map<Element, Set<Element>>();
/**
* Maps an element to its return type.
*/
final Map<Element, Element> returnTypeOf =
new Map<Element, Element>();
/**
* Maps a name to elements in the universe that have that name.
*/
final Map<SourceString, Set<Element>> methodCache =
new Map<SourceString, Set<Element>>();
/**
* Maps an element to the number of times this type inferrer
* analyzed it.
*/
final Map<Element, int> analyzeCount = new Map<Element, int>();
/**
* The work list of the inferrer.
*/
final WorkSet<Element> workSet = new WorkSet<Element>();
/**
* Heuristic for avoiding too many re-analysis of an element.
*/
final int MAX_ANALYSIS_COUNT_PER_ELEMENT = 5;
/**
* Sentinal used by the inferrer to notify that it gave up finding a type
* on a specific element.
*/
Element giveUpType;
final Compiler compiler;
// Times the computation of the call graph.
final Stopwatch memberWatch = new Stopwatch();
// Times the computation of re-analysis of methods.
final Stopwatch recomputeWatch = new Stopwatch();
// Number of re-analysis.
int recompiles = 0;
SimpleTypesInferrer(this.compiler);
/**
* Main entry point of the inferrer. Analyzes all elements that the
* resolver found as reachable. Returns whether it succeeded.
*/
bool analyzeMain(Element element) {
// We use the given element as the sentinel. This is a temporary
// situation as long as this inferrer is using [ClassElement] for
// expressing types.
giveUpType = element;
buildWorkQueue();
int analyzed = 0;
compiler.progress.reset();
do {
if (compiler.progress.elapsedMilliseconds > 500) {
compiler.log('Inferred $analyzed methods.');
compiler.progress.reset();
}
element = workSet.remove();
if (element.isErroneous()) continue;
bool wasAnalyzed = analyzeCount.containsKey(element);
if (wasAnalyzed) {
recompiles++;
recomputeWatch.start();
}
bool changed = analyze(element);
analyzed++;
if (wasAnalyzed) {
recomputeWatch.stop();
}
if (!changed) continue;
// If something changed during the analysis of [element],
// put back callers of it in the work list.
Set<Element> methodCallers = callersOf[element];
if (methodCallers != null) {
methodCallers.forEach(enqueueAgain);
}
} while (!workSet.isEmpty);
dump();
clear();
return true;
}
/**
* Query method after the analysis to know the type of [element].
*/
getConcreteTypeOfElement(element) {
return getTypeIfValuable(returnTypeOf[element]);
}
getTypeIfValuable(returnType) {
if (returnType == null
|| returnType == compiler.dynamicClass
|| returnType == giveUpType) {
return null;
}
return new ConcreteType.singleton(
compiler.maxConcreteTypeSize, new ClassBaseType(returnType));
}
/**
* Query method after the analysis to know the type of [node],
* defined in the context of [owner].
*/
getConcreteTypeOfNode(Element owner, Node node) {
var elements = compiler.enqueuer.resolution.resolvedElements[owner];
Selector selector = elements.getSelector(node);
// TODO(ngeoffray): Should the builder call this method with a
// SendSet?
if (selector == null || selector.isSetter() || selector.isIndexSet()) {
return null;
}
return getTypeIfValuable(returnTypeOfSelector(selector));
}
/**
* Enqueues [e] in the work queue if it is valuable.
*/
void enqueueAgain(Element e) {
Element returnType = returnTypeOf[e];
// If we have found a type for [e], no need to re-analyze it.
if (returnType != compiler.dynamicClass) return;
if (analyzeCount[e] > MAX_ANALYSIS_COUNT_PER_ELEMENT) return;
workSet.add(e);
}
/**
* Builds the initial work queue by adding all resolved elements in
* the work queue, ordered by the number of selectors they use. This
* order is benficial for the analysis of return types, but we may
* have to refine it once we analyze parameter types too.
*/
void buildWorkQueue() {
int max = 0;
Map<int, Set<Element>> methodSizes = new Map<int, Set<Element>>();
compiler.enqueuer.resolution.resolvedElements.forEach(
(Element element, TreeElementMapping mapping) {
// TODO(ngeoffray): Not sure why the resolver would put a null
// mapping.
if (mapping == null) return;
if (element.isAbstract(compiler)) return;
int length = mapping.selectors.length;
max = length > max ? length : max;
Set<Element> set = methodSizes.putIfAbsent(
length, () => new Set<Element>());
set.add(element);
});
// This iteration assumes the [WorkSet] is LIFO.
for (int i = max; i >= 0; i--) {
Set<Element> set = methodSizes[i];
if (set != null) {
set.forEach((e) { workSet.add(e); });
}
}
}
dump() {
int interestingTypes = 0;
int giveUpTypes = 0;
returnTypeOf.forEach((Element method, Element type) {
if (type == giveUpType) {
giveUpTypes++;
} else if (type != compiler.nullClass && type != compiler.dynamicClass) {
interestingTypes++;
}
});
compiler.log('Type inferrer spent ${memberWatch.elapsedMilliseconds} ms '
'computing a call graph.');
compiler.log('Type inferrer re-analyzed methods $recompiles times '
'in ${recomputeWatch.elapsedMilliseconds} ms.');
compiler.log('Type inferrer found $interestingTypes interesting '
'return types and gave up on $giveUpTypes methods.');
}
/**
* Clear data structures that are not used after the analysis.
*/
void clear() {
callersOf.clear();
analyzeCount.clear();
}
bool analyze(Element element) {
if (element.isField()) {
// TODO(ngeoffray): Analyze its initializer.
return false;
} else {
SimpleTypeInferrerVisitor visitor =
new SimpleTypeInferrerVisitor(element, compiler, this);
return visitor.run();
}
}
/**
* Records [returnType] as the return type of [analyzedElement].
* Returns whether the new type is worth recompiling the callers of
* [analyzedElement].
*/
bool recordReturnType(analyzedElement, returnType) {
assert(returnType != null);
Element existing = returnTypeOf[analyzedElement];
if (existing == null) {
// First time we analyzed [analyzedElement]. Initialize the
// return type.
assert(!analyzeCount.containsKey(analyzedElement));
returnTypeOf[analyzedElement] = returnType;
// If the return type is useful, say it has changed.
return returnType != compiler.dynamicClass
&& returnType != compiler.nullClass;
} else if (existing == compiler.dynamicClass) {
// Previous analysis did not find any type.
returnTypeOf[analyzedElement] = returnType;
// If the return type is useful, say it has changed.
return returnType != compiler.dynamicClass
&& returnType != compiler.nullClass;
} else if (existing == giveUpType) {
// If we already gave up on the return type, we don't change it.
return false;
} else if (existing != returnType) {
// The method is returning two different types. Give up for now.
// TODO(ngeoffray): Compute LUB.
returnTypeOf[analyzedElement] = giveUpType;
return true;
}
return false;
}
/**
* Returns the return type of [element]. Returns [:Dynamic:] if
* [element] has not been analyzed yet.
*/
ClassElement returnTypeOfElement(Element element) {
element = element.implementation;
if (element.isGenerativeConstructor()) return element.getEnclosingClass();
Element returnType = returnTypeOf[element];
if (returnType == null || returnType == giveUpType) {
return compiler.dynamicClass;
}
return returnType;
}
/**
* Returns the union of the return types of all elements that match
* the called [selector].
*/
ClassElement returnTypeOfSelector(Selector selector) {
ClassElement result;
iterateOverElements(selector, (Element element) {
assert(element.isImplementation);
Element cls;
if (element.isFunction() && selector.isGetter()) {
cls = compiler.functionClass;
} else {
cls = returnTypeOf[element];
}
if (cls == null
|| cls == compiler.dynamicClass
|| cls == giveUpType
|| (cls != result && result != null)) {
result = compiler.dynamicClass;
return false;
} else {
result = cls;
return true;
}
});
return result;
}
/**
* Registers that [caller] calls [callee] with the given
* [arguments].
*/
void registerCalledElement(Element caller,
Element callee,
ArgumentsTypes arguments) {
if (analyzeCount.containsKey(caller)) return;
callee = callee.implementation;
Set<FunctionElement> callers = callersOf.putIfAbsent(
callee, () => new Set<FunctionElement>());
callers.add(caller);
}
/**
* Registers that [caller] accesses [callee] through a property
* access.
*/
void registerGetterOnElement(Element caller,
Element callee) {
if (analyzeCount.containsKey(caller)) return;
callee = callee.implementation;
Set<FunctionElement> callers = callersOf.putIfAbsent(
callee, () => new Set<FunctionElement>());
callers.add(caller);
}
/**
* Registers that [caller] calls an element matching [selector]
* with the given [arguments].
*/
void registerCalledSelector(Element caller,
Selector selector,
ArgumentsTypes arguments) {
if (analyzeCount.containsKey(caller)) return;
iterateOverElements(selector, (Element element) {
assert(element.isImplementation);
Set<FunctionElement> callers = callersOf.putIfAbsent(
element, () => new Set<FunctionElement>());
callers.add(caller);
return true;
});
}
/**
* Registers that [caller] accesses an element matching [selector]
* through a property access.
*/
void registerGetterOnSelector(Element caller, Selector selector) {
if (analyzeCount.containsKey(caller)) return;
iterateOverElements(selector, (Element element) {
assert(element.isImplementation);
Set<FunctionElement> callers = callersOf.putIfAbsent(
element, () => new Set<FunctionElement>());
callers.add(caller);
return true;
});
}
/**
* Registers that [caller] closurizes [function].
*/
void registerGetFunction(Element caller, Element function) {
assert(caller.isImplementation);
if (analyzeCount.containsKey(caller)) return;
// We don't register that [caller] calls [function] because we
// don't know if the code is going to call it, and if it is, then
// the inferrer has lost track of its identity anyway.
}
/**
* Applies [f] to all elements in the universe that match
* [selector]. If [f] returns false, aborts the iteration.
*/
void iterateOverElements(Selector selector, bool f(Element element)) {
SourceString name = selector.name;
// The following is already computed by the resolver, but it does
// not save it yet.
Set<Element> methods = methodCache[name];
if (methods == null) {
memberWatch.start();
methods = new Set<Element>();
void add(element) {
if (!element.isInstanceMember()) return;
if (element.isAbstract(compiler)) return;
if (!compiler.enqueuer.resolution.isProcessed(element)) return;
methods.add(element.implementation);
}
for (ClassElement cls in compiler.enqueuer.resolution.seenClasses) {
var element = cls.lookupLocalMember(name);
if (element != null) {
if (element.isAbstractField()) {
if (element.getter != null) add(element.getter);
if (element.setter != null) add(element.setter);
} else {
add(element);
}
}
}
methodCache[name] = methods;
memberWatch.stop();
}
for (Element element in methods) {
if (selector.appliesUnnamed(element, compiler)) {
if (!f(element)) return;
}
}
}
}
/**
* Placeholder for inferred arguments types on sends.
*/
class ArgumentsTypes {
final List<Element> positional;
final Map<Identifier, Element> named;
ArgumentsTypes(this.positional, this.named);
int get length => positional.length + named.length;
toString() => "{ positional = $positional, named = $named }";
}
class SimpleTypeInferrerVisitor extends ResolvedVisitor {
final FunctionElement analyzedElement;
final SimpleTypesInferrer inferrer;
final Compiler compiler;
Element returnType;
SimpleTypeInferrerVisitor(FunctionElement element,
Compiler compiler,
this.inferrer)
: super(compiler.enqueuer.resolution.resolvedElements[element.declaration]),
analyzedElement = element,
compiler = compiler {
assert(elements != null);
}
bool run() {
FunctionExpression node =
analyzedElement.implementation.parseNode(compiler);
bool changed;
if (analyzedElement.isGenerativeConstructor()) {
FunctionSignature signature = analyzedElement.computeSignature(compiler);
// TODO(ngeoffray): handle initializing formals.
// TODO(ngeoffray): handle initializers.
node.body.accept(this);
// We always know the return type of a generative constructor.
changed = false;
} else if (analyzedElement.isNative()) {
// Native methods do not have a body, and we currently just say
// they return dynamic.
inferrer.recordReturnType(analyzedElement, compiler.dynamicClass);
changed = false;
} else {
node.body.accept(this);
if (returnType == null) {
// No return in the body.
returnType = compiler.nullClass;
}
changed = inferrer.recordReturnType(analyzedElement, returnType);
}
if (inferrer.analyzeCount.containsKey(analyzedElement)) {
inferrer.analyzeCount[analyzedElement]++;
} else {
inferrer.analyzeCount[analyzedElement] = 1;
}
return changed;
}
recordReturnType(ClassElement cls) {
if (returnType == null) {
returnType = cls;
} else if (returnType != inferrer.giveUpType
&& cls == compiler.dynamicClass) {
returnType = cls;
} else if (returnType == compiler.dynamicClass) {
// Nothing to do. Stay dynamic.
} else if (leastUpperBound(cls, returnType) == compiler.dynamicClass) {
returnType = inferrer.giveUpType;
}
}
visitNode(Node node) {
node.visitChildren(this);
return compiler.dynamicClass;
}
visitNewExpression(NewExpression node) {
return node.send.accept(this);
}
visitFunctionExpression(FunctionExpression node) {
// We don't put the closure in the work queue of the
// inferrer, because it will share information with its enclosing
// method, like for example the types of local variables.
SimpleTypeInferrerVisitor visitor =
new SimpleTypeInferrerVisitor(elements[node], compiler, inferrer);
visitor.run();
return compiler.functionClass;
}
visitLiteralString(LiteralString node) {
return compiler.stringClass;
}
visitStringInterpolation(StringInterpolation node) {
return compiler.stringClass;
}
visitStringJuxtaposition(StringJuxtaposition node) {
return compiler.stringClass;
}
visitLiteralBool(LiteralBool node) {
return compiler.boolClass;
}
visitLiteralDouble(LiteralDouble node) {
return compiler.doubleClass;
}
visitLiteralInt(LiteralInt node) {
return compiler.intClass;
}
visitLiteralList(LiteralList node) {
return compiler.listClass;
}
visitLiteralMap(LiteralMap node) {
return compiler.mapClass;
}
visitLiteralNull(LiteralNull node) {
return compiler.nullClass;
}
visitTypeReferenceSend(Send node) {
return compiler.typeClass;
}
visitSendSet(SendSet node) {
// TODO(ngeoffray): return the right hand side's type.
node.visitChildren(this);
return compiler.dynamicClass;
}
visitIdentifier(Identifier node) {
if (node.isThis() || node.isSuper()) {
// TODO(ngeoffray): Represent subclasses.
return compiler.dynamicClass;
}
return compiler.dynamicClass;
}
visitSuperSend(Send node) {
Element element = elements[node];
if (Elements.isUnresolved(element)) {
return compiler.dynamicClass;
}
Selector selector = elements.getSelector(node);
if (node.isPropertyAccess) {
inferrer.registerGetterOnElement(analyzedElement, element);
return inferrer.returnTypeOfElement(element);
} else if (element.isFunction()) {
ArgumentsTypes arguments = analyzeArguments(node.arguments);
inferrer.registerCalledElement(analyzedElement, element, arguments);
return inferrer.returnTypeOfElement(element);
} else {
// Closure call on a getter. We don't have function types yet,
// so we just return [:Dynamic:].
return compiler.dynamicClass;
}
}
visitStaticSend(Send node) {
Element element = elements[node];
if (Elements.isUnresolved(element)) {
return compiler.dynamicClass;
}
if (element.isForeign(compiler)) {
return handleForeignSend(node);
}
ArgumentsTypes arguments = analyzeArguments(node.arguments);
inferrer.registerCalledElement(analyzedElement, element, arguments);
return inferrer.returnTypeOfElement(element);
}
handleForeignSend(Send node) {
node.visitChildren(this);
Selector selector = elements.getSelector(node);
SourceString name = selector.name;
if (name == const SourceString('JS')) {
native.NativeBehavior nativeBehavior =
compiler.enqueuer.resolution.nativeEnqueuer.getNativeBehaviorOf(node);
if (nativeBehavior == null) return compiler.dynamicClass;
List typesReturned = nativeBehavior.typesReturned;
if (typesReturned.isEmpty) return compiler.dynamicClass;
ClassElement returnType;
for (var type in typesReturned) {
ClassElement mappedType;
if (type == native.SpecialType.JsObject) {
mappedType = compiler.objectClass;
} else if (type == native.SpecialType.JsArray) {
mappedType = compiler.listClass;
} else {
mappedType = type.element;
// For primitive types, we know how to handle them here and
// in the backend.
if (mappedType != compiler.stringClass
&& mappedType != compiler.intClass
&& mappedType != compiler.doubleClass
&& mappedType != compiler.boolClass
&& mappedType != compiler.numClass) {
Set<ClassElement> subtypes = compiler.world.subtypes[mappedType];
// TODO(ngeoffray): Handle subtypes and subclasses.
if (subtypes != null && !subtypes.isEmpty) {
return compiler.dynamicClass;
}
}
}
if (returnType == null) {
returnType = mappedType;
} else {
return compiler.dynamicClass;
}
}
return returnType;
} else if (name == const SourceString('JS_OPERATOR_IS_PREFIX')) {
return compiler.stringClass;
} else {
return compiler.dynamicClass;
}
}
analyzeArguments(Link<Node> arguments) {
List<ClassElement> positional = [];
Map<Identifier, ClassElement> named = new Map<Identifier, ClassElement>();
for (Node argument in arguments) {
NamedArgument namedArgument = argument.asNamedArgument();
if (namedArgument != null) {
named[namedArgument.name] = namedArgument.expression.accept(this);
} else {
positional.add(argument.accept(this));
}
}
return new ArgumentsTypes(positional, named);
}
visitOperatorSend(Send node) {
Operator op = node.selector;
if (const SourceString("[]") == op.source) {
return visitDynamicSend(node);
} else if (const SourceString("&&") == op.source ||
const SourceString("||") == op.source) {
node.visitChildren(this);
return compiler.boolClass;
} else if (const SourceString("!") == op.source) {
node.visitChildren(this);
return compiler.boolClass;
} else if (const SourceString("is") == op.source) {
node.visitChildren(this);
return compiler.boolClass;
} else if (const SourceString("as") == op.source) {
node.visitChildren(this);
return compiler.dynamicClass;
} else if (node.isParameterCheck) {
node.visitChildren(this);
return compiler.boolClass;
} else if (node.argumentsNode is Prefix) {
// Unary operator.
return visitDynamicSend(node);
} else if (const SourceString('===') == op.source
|| const SourceString('!==') == op.source) {
node.visitChildren(this);
return compiler.boolClass;
} else {
// Binary operator.
return visitDynamicSend(node);
}
}
// Because some nodes just visit their children, we may end up
// visiting a type annotation, that may contain a send in case of a
// prefixed type. Therefore we explicitly visit the type annotation
// to avoid confusing the [ResolvedVisitor].
visitTypeAnnotation(TypeAnnotation node) {}
visitGetterSend(Send node) {
Element element = elements[node];
if (Elements.isStaticOrTopLevelField(element)) {
if (element.isGetter()) {
inferrer.registerGetterOnElement(analyzedElement, element);
return inferrer.returnTypeOfElement(element);
} else {
// Nothing yet.
// TODO: Analyze initializer of element.
return compiler.dynamicClass;
}
} else if (Elements.isInstanceSend(node, elements)) {
ClassElement receiverType;
if (node.receiver == null) {
receiverType = analyzedElement.getEnclosingClass();
} else {
receiverType = node.receiver.accept(this);
}
Selector selector = elements.getSelector(node);
inferrer.registerGetterOnSelector(analyzedElement, selector);
return inferrer.returnTypeOfSelector(selector);
} else if (Elements.isStaticOrTopLevelFunction(element)) {
inferrer.registerGetFunction(analyzedElement, element);
return compiler.functionClass;
} else {
// TODO: Analyze variable.
return compiler.dynamicClass;
}
}
visitClosureSend(Send node) {
node.visitChildren(this);
return compiler.dynamicClass;
}
visitDynamicSend(Send node) {
ClassElement receiverType;
if (node.receiver == null) {
receiverType = analyzedElement.getEnclosingClass();
} else {
receiverType = node.receiver.accept(this);
}
ArgumentsTypes arguments = analyzeArguments(node.arguments);
Selector selector = elements.getSelector(node);
inferrer.registerCalledSelector(analyzedElement, selector, arguments);
return inferrer.returnTypeOfSelector(selector);
}
visitReturn(Return node) {
Node expression = node.expression;
recordReturnType(expression == null
? compiler.nullClass
: expression.accept(this));
}
visitConditional(Conditional node) {
node.condition.accept(this);
Element firstType = node.thenExpression.accept(this);
Element secondType = node.elseExpression.accept(this);
return leastUpperBound(firstType, secondType);
}
leastUpperBound(Element firstType, Element secondType) {
if (firstType == secondType) return firstType;
return compiler.dynamicClass;
}
internalError(String reason, {Node node}) {
compiler.internalError(reason, node: node);
}
}