Re-apply "Move the container tracer to the call graph inferrer.".

If we closurize a method, we still need to collect the users of the parameters for the trace container pass to work.

R=kasperl@google.com

Review URL: https://codereview.chromium.org//24994003

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@28001 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
ngeoffray@google.com
2013-09-27 10:31:45 +00:00
parent b389bddb1a
commit e340fc4dc9
9 changed files with 286 additions and 694 deletions
@@ -533,7 +533,6 @@ abstract class Compiler implements DiagnosticListener {
EnqueueTask enqueuer;
DeferredLoadTask deferredLoadTask;
MirrorUsageAnalyzerTask mirrorUsageAnalyzerTask;
ContainerTracer containerTracer;
String buildId;
static const SourceString MAIN = const SourceString('main');
@@ -639,7 +638,6 @@ abstract class Compiler implements DiagnosticListener {
closureToClassMapper = new closureMapping.ClosureTask(this, closureNamer),
checker = new TypeCheckerTask(this),
typesTask = new ti.TypesTask(this),
containerTracer = new ContainerTracer(this),
constantHandler = new ConstantHandler(this, backend.constantSystem),
deferredLoadTask = new DeferredLoadTask(this),
mirrorUsageAnalyzerTask = new MirrorUsageAnalyzerTask(this),
@@ -32,7 +32,6 @@ import 'resolution/resolution.dart';
import 'source_file.dart' show SourceFile;
import 'js/js.dart' as js;
import 'deferred_load.dart' show DeferredLoadTask;
import 'inferrer/container_tracer.dart' show ContainerTracer;
import 'mirrors_used.dart' show MirrorUsageAnalyzerTask;
export 'resolution/resolution.dart' show TreeElements, TreeElementMapping;
@@ -2,17 +2,7 @@
// 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 container_tracer;
import '../dart2jslib.dart' hide Selector, TypedSelector;
import '../elements/elements.dart';
import '../tree/tree.dart';
import '../universe/universe.dart';
import '../util/util.dart' show Link;
import 'simple_types_inferrer.dart'
show InferrerEngine, InferrerVisitor, LocalsHandler, TypeMaskSystem;
import '../types/types.dart';
import 'inferrer_visitor.dart';
part of type_graph_inferrer;
/**
* A set of selector names that [List] implements, that we know do not
@@ -136,672 +126,154 @@ Set<String> doNotChangeLengthSelectorsSet = new Set<String>.from(
bool _VERBOSE = false;
class InferrerEngineForContainerTracer
implements MinimalInferrerEngine<TypeMask> {
class ContainerTracerVisitor implements TypeInformationVisitor {
final ContainerTypeInformation container;
final TypeGraphInferrerEngine inferrer;
final Compiler compiler;
InferrerEngineForContainerTracer(this.compiler);
// The set of [TypeInformation] where the traced container could
// flow in, and operations done on them.
final Set<TypeInformation> allUsers = new Set<TypeInformation>();
TypeMask typeOfElement(Element element) {
return compiler.typesTask.getGuaranteedTypeOfElement(element);
}
// The list of found assignments to the container.
final List<TypeInformation> assignments = <TypeInformation>[];
TypeMask returnTypeOfElement(Element element) {
return compiler.typesTask.getGuaranteedReturnTypeOfElement(element);
}
TypeMask returnTypeOfSelector(Selector selector) {
return compiler.typesTask.getGuaranteedTypeOfSelector(selector);
}
TypeMask typeOfNode(Node node) {
return compiler.typesTask.getGuaranteedTypeOfNode(null, node);
}
Iterable<Element> getCallersOf(Element element) {
return compiler.typesTask.typesInferrer.getCallersOf(element);
}
void recordTypeOfNonFinalField(Node node,
Element field,
TypeMask type) {}
}
/**
* Global analysis phase that traces container instantiations in order to
* find their element type.
*/
class ContainerTracer extends CompilerTask {
ContainerTracer(Compiler compiler) : super(compiler);
String get name => 'List tracer';
bool analyze() {
measure(() {
if (compiler.disableTypeInference) return;
TypesInferrer inferrer = compiler.typesTask.typesInferrer;
InferrerEngineForContainerTracer engine =
new InferrerEngineForContainerTracer(compiler);
// Walk over all created [ContainerTypeMask].
inferrer.containerTypes.forEach((ContainerTypeMask mask) {
// The element type has already been set for const containers.
if (mask.elementType != null) return;
new TracerForConcreteContainer(mask, this, compiler, engine).run();
});
});
}
}
/**
* A tracer for a specific container.
*/
class TracerForConcreteContainer {
final Compiler compiler;
final ContainerTracer tracer;
final InferrerEngineForContainerTracer inferrer;
final ContainerTypeMask mask;
final Node analyzedNode;
final Element startElement;
final List<Element> workList = <Element>[];
/**
* A set of elements where this list might escape.
*/
final Set<Element> escapingElements = new Set<Element>();
/**
* A set of selectors that both use and update the list, for example
* [: list[0]++; :] or [: list[0] |= 42; :].
*/
final Set<Selector> constraints = new Set<Selector>();
/**
* A cache of setters that were already seen. Caching these
* selectors avoid the filtering done in [addSettersToAnalysis].
*/
final Set<Selector> seenSetterSelectors = new Set<Selector>();
static const int MAX_ANALYSIS_COUNT = 11;
TypeMask potentialType;
int potentialLength;
bool isLengthTrackingDisabled = false;
bool enableLengthTracking = true;
bool continueAnalyzing = true;
TracerForConcreteContainer(ContainerTypeMask mask,
this.tracer,
this.compiler,
this.inferrer)
: analyzedNode = mask.allocationNode,
startElement = mask.allocationElement,
this.mask = mask;
static const int MAX_ANALYSIS_COUNT = 11;
final Set<Element> analyzedElements = new Set<Element>();
ContainerTracerVisitor(this.container, inferrer)
: this.inferrer = inferrer, this.compiler = inferrer.compiler;
void run() {
int analysisCount = 0;
workList.add(startElement);
// Add the assignments found at allocation site.
assignments.addAll(container.elementType.assignments);
// Collect the [TypeInformation] where the container can flow in,
// as well as the operations done on all these [TypeInformation]s.
List<TypeInformation> workList = <TypeInformation>[];
allUsers.add(container);
workList.add(container);
while (!workList.isEmpty) {
if (workList.length + analysisCount > MAX_ANALYSIS_COUNT) {
TypeInformation user = workList.removeLast();
user.users.forEach((TypeInformation info) {
if (allUsers.contains(info)) return;
allUsers.add(info);
analyzedElements.add(info.owner);
if (info.reachedBy(user, inferrer)) {
workList.add(info);
}
});
if (analyzedElements.length > MAX_ANALYSIS_COUNT) {
bailout('Too many users');
break;
}
Element currentElement = workList.removeLast().implementation;
new ContainerTracerVisitor(currentElement, this).run();
if (!continueAnalyzing) break;
analysisCount++;
}
if (!continueAnalyzing) {
if (mask.forwardTo == compiler.typesTask.fixedListType) {
mask.length = potentialLength;
if (continueAnalyzing) {
for (TypeInformation info in allUsers) {
info.accept(this);
if (!continueAnalyzing) break;
}
mask.elementType = compiler.typesTask.dynamicType;
return;
}
// [potentialType] can be null if we did not find any instruction
// that adds elements to the list.
if (potentialType == null) {
if (_VERBOSE) {
print('Found empty type for $analyzedNode $startElement');
}
mask.elementType = new TypeMask.nonNullEmpty();
return;
ContainerTypeMask mask = container.type;
if (!enableLengthTracking
&& (mask.forwardTo != compiler.typesTask.fixedListType)) {
mask.length = null;
}
// Walk over the found constraints and update the type according
// to the selectors of these constraints.
for (Selector constraint in constraints) {
assert(constraint.isOperator());
constraint = new TypedSelector(potentialType, constraint);
potentialType = potentialType.union(
inferrer.returnTypeOfSelector(constraint), compiler);
}
TypeMask result = continueAnalyzing
? inferrer.types.computeTypeMask(assignments)
: inferrer.types.dynamicType.type;
mask.elementType = result;
if (_VERBOSE) {
print('$potentialType and $potentialLength '
'for $analyzedNode $startElement');
}
mask.elementType = potentialType;
mask.length = potentialLength;
}
void disableLengthTracking() {
if (mask.forwardTo == compiler.typesTask.fixedListType) {
// Bogus update to a fixed list.
return;
}
isLengthTrackingDisabled = true;
potentialLength = null;
}
void setPotentialLength(int value) {
if (isLengthTrackingDisabled) return;
potentialLength = value;
}
void unionPotentialTypeWith(TypeMask newType) {
assert(newType != null);
potentialType = potentialType == null
? newType
: newType.union(potentialType, compiler);
if (potentialType == compiler.typesTask.dynamicType) {
bailout('Moved to dynamic');
print('$result and ${mask.length} '
'for ${mask.allocationNode} ${mask.allocationElement}');
}
}
void addEscapingElement(element) {
element = element.implementation;
if (escapingElements.contains(element)) return;
escapingElements.add(element);
if (element.isField() || element.isGetter() || element.isFunction()) {
for (Element e in inferrer.getCallersOf(element)) {
addElementToAnalysis(e);
}
} else if (element.isParameter()) {
addElementToAnalysis(element.enclosingElement);
} else if (element.isFieldParameter()) {
addEscapingElement(element.fieldElement);
}
}
void addSettersToAnalysis(Selector selector) {
assert(selector.isSetter());
if (seenSetterSelectors.contains(selector)) return;
seenSetterSelectors.add(selector);
for (var e in compiler.world.allFunctions.filter(selector)) {
e = e.implementation;
if (e.isField()) {
addEscapingElement(e);
} else {
FunctionSignature signature = e.computeSignature(compiler);
signature.forEachRequiredParameter((Element e) {
addEscapingElement(e);
});
}
}
}
void addElementToAnalysis(Element element) {
workList.add(element);
}
TypeMask bailout(String reason) {
void bailout(String reason) {
if (_VERBOSE) {
print('Bailout on $analyzedNode $startElement because of $reason');
ContainerTypeMask mask = container.type;
print('Bailing out on ${mask.allocationNode} ${mask.allocationElement} '
'because: $reason');
}
continueAnalyzing = false;
return compiler.typesTask.dynamicType;
enableLengthTracking = false;
}
bool couldBeTheList(resolved) {
if (resolved is Selector) {
return escapingElements.any((e) {
return e.isInstanceMember() && resolved.applies(e, compiler);
});
} else if (resolved is Node) {
return analyzedNode == resolved;
} else {
assert(resolved is Element);
return escapingElements.contains(resolved);
visitNarrowTypeInformation(NarrowTypeInformation info) {}
visitPhiElementTypeInformation(PhiElementTypeInformation info) {}
visitElementInContainerTypeInformation(
ElementInContainerTypeInformation info) {}
visitContainerTypeInformation(ContainerTypeInformation info) {}
visitConcreteTypeInformation(ConcreteTypeInformation info) {}
visitClosureCallSiteTypeInformation(ClosureCallSiteTypeInformation info) {
bailout('Passed to a closure');
}
visitStaticCallSiteTypeInformation(StaticCallSiteTypeInformation info) {
analyzedElements.add(info.caller);
Element called = info.calledElement;
if (called.isForeign(compiler) && called.name == const SourceString('JS')) {
bailout('Used in JS ${info.call}');
}
}
void recordConstraint(Selector selector) {
constraints.add(selector);
}
}
class ContainerTracerVisitor
extends InferrerVisitor<TypeMask, InferrerEngineForContainerTracer> {
final Element analyzedElement;
final TracerForConcreteContainer tracer;
final bool visitingClosure;
ContainerTracerVisitor(element, tracer, [LocalsHandler<TypeMask> locals])
: super(element, tracer.inferrer, new TypeMaskSystem(tracer.compiler),
tracer.compiler, locals),
this.analyzedElement = element,
this.tracer = tracer,
visitingClosure = locals != null;
bool escaping = false;
bool visitingInitializers = false;
void run() {
compiler.withCurrentElement(analyzedElement, () {
visit(analyzedElement.parseNode(compiler));
});
}
/**
* Executes [f] and returns whether it triggered the list to escape.
*/
bool visitAndCatchEscaping(Function f) {
bool oldEscaping = escaping;
escaping = false;
f();
bool foundEscaping = escaping;
escaping = oldEscaping;
return foundEscaping;
}
/**
* Visits the [arguments] of [callee], and records the parameters
* that could hold the container as escaping.
*
* Returns whether the container escaped.
*/
bool visitArguments(Link<Node> arguments, /* Element or Selector */ callee) {
List<int> indices = [];
int index = 0;
for (Node node in arguments) {
if (visitAndCatchEscaping(() { visit(node); })) {
indices.add(index);
}
index++;
}
if (!indices.isEmpty) {
Iterable<Element> callees;
if (callee is Element) {
// No need to go further, we know the call will throw.
if (callee.isErroneous()) return false;
callees = [callee];
} else {
assert(callee is Selector);
callees = compiler.world.allFunctions.filter(callee);
}
for (var e in callees) {
e = e.implementation;
if (e.isField()) {
tracer.bailout('Passed to a closure');
break;
}
FunctionSignature signature = e.computeSignature(compiler);
index = 0;
int parameterIndex = 0;
signature.forEachRequiredParameter((Element parameter) {
if (index < indices.length && indices[index] == parameterIndex) {
tracer.addEscapingElement(parameter);
index++;
}
parameterIndex++;
});
if (index != indices.length) {
tracer.bailout('Used in a named parameter or closure');
}
}
return true;
} else {
return false;
}
}
TypeMask visitFunctionExpression(FunctionExpression node) {
FunctionElement function = elements[node];
if (function != analyzedElement) {
// Visiting a closure.
LocalsHandler closureLocals = new LocalsHandler<TypeMask>.from(
locals, node, useOtherTryBlock: false);
new ContainerTracerVisitor(function, tracer, closureLocals).run();
return types.functionType;
} else {
// Visiting [analyzedElement].
FunctionSignature signature = function.computeSignature(compiler);
signature.forEachParameter((element) {
locals.update(element, inferrer.typeOfElement(element), node);
});
visitingInitializers = true;
visit(node.initializers);
visitingInitializers = false;
visit(node.body);
return null;
}
}
TypeMask visitLiteralList(LiteralList node) {
if (node.isConst()) {
return inferrer.typeOfNode(node);
}
if (tracer.couldBeTheList(node)) {
escaping = true;
int length = 0;
for (Node element in node.elements.nodes) {
tracer.unionPotentialTypeWith(visit(element));
length++;
}
tracer.setPotentialLength(length);
} else {
node.visitChildren(this);
}
return types.growableListType;
}
TypeMask visitSendSet(SendSet node) {
bool isReceiver = visitAndCatchEscaping(() {
visit(node.receiver);
});
return handleSendSet(node, isReceiver);
}
TypeMask handleSendSet(SendSet node, bool isReceiver) {
TypeMask rhsType;
TypeMask indexType;
Selector getterSelector =
elements.getGetterSelectorInComplexSendSet(node);
Selector operatorSelector =
elements.getOperatorSelectorInComplexSendSet(node);
Selector setterSelector = elements.getSelector(node);
String op = node.assignmentOperator.source.stringValue;
bool isIncrementOrDecrement = op == '++' || op == '--';
bool isIndexEscaping = false;
bool isValueEscaping = false;
if (isIncrementOrDecrement) {
rhsType = types.intType;
if (node.isIndex) {
isIndexEscaping = visitAndCatchEscaping(() {
indexType = visit(node.arguments.head);
});
}
} else if (node.isIndex) {
isIndexEscaping = visitAndCatchEscaping(() {
indexType = visit(node.arguments.head);
});
isValueEscaping = visitAndCatchEscaping(() {
rhsType = visit(node.arguments.tail.head);
});
} else {
isValueEscaping = visitAndCatchEscaping(() {
rhsType = visit(node.arguments.head);
});
}
Element element = elements[node];
if (node.isIndex) {
if (isReceiver) {
if (op == '=') {
tracer.unionPotentialTypeWith(rhsType);
} else {
tracer.recordConstraint(operatorSelector);
}
} else if (isIndexEscaping || isValueEscaping) {
// If the index or value is escaping, iterate over all
// potential targets, and mark their parameter as escaping.
for (var e in compiler.world.allFunctions.filter(setterSelector)) {
e = e.implementation;
FunctionSignature signature = e.computeSignature(compiler);
int index = 0;
signature.forEachRequiredParameter((Element parameter) {
if (index == 0 && isIndexEscaping) {
tracer.addEscapingElement(parameter);
}
if (index == 1 && isValueEscaping) {
tracer.addEscapingElement(parameter);
}
index++;
});
}
}
} else if (isReceiver) {
if (setterSelector.name == const SourceString('length')) {
tracer.disableLengthTracking();
tracer.unionPotentialTypeWith(compiler.typesTask.nullType);
}
} else if (isValueEscaping) {
if (element != null
&& element.isField()
&& setterSelector == null
&& !visitingInitializers) {
// Initializer at declaration of a field.
assert(analyzedElement.isField());
tracer.addEscapingElement(analyzedElement);
} else if (element != null
&& (!element.isInstanceMember() || visitingInitializers)) {
// A local, a static element, or a field in an initializer.
tracer.addEscapingElement(element);
} else {
tracer.addSettersToAnalysis(setterSelector);
}
}
TypeMask result;
if (node.isPostfix) {
// We don't check if [getterSelector] could be the container because
// a list++ will always throw.
result = inferrer.returnTypeOfSelector(getterSelector);
} else if (op != '=') {
// We don't check if [getterSelector] could be the container because
// a list += 42 will always throw.
result = inferrer.returnTypeOfSelector(operatorSelector);
} else {
if (isValueEscaping) {
escaping = true;
}
result = rhsType;
}
if (Elements.isLocal(element)) {
locals.update(element, result, node);
}
return result;
}
TypeMask visitSuperSend(Send node) {
Element element = elements[node];
if (!node.isPropertyAccess) {
visitArguments(node.arguments, element);
}
if (tracer.couldBeTheList(element)) {
escaping = true;
}
if (element.isField()) {
return inferrer.typeOfElement(element);
} else if (element.isFunction()) {
return inferrer.returnTypeOfElement(element);
} else {
return types.dynamicType;
}
}
TypeMask visitStaticSend(Send node) {
Element element = elements[node];
if (Elements.isGrowableListConstructorCall(element, node, compiler)) {
visitArguments(node.arguments, element);
if (tracer.couldBeTheList(node)) {
escaping = true;
}
return inferrer.typeOfNode(node);
} else if (Elements.isFixedListConstructorCall(element, node, compiler)) {
visitArguments(node.arguments, element);
if (tracer.couldBeTheList(node)) {
tracer.unionPotentialTypeWith(types.nullType);
escaping = true;
LiteralInt length = node.arguments.head.asLiteralInt();
if (length != null) {
tracer.setPotentialLength(length.value);
}
}
return inferrer.typeOfNode(node);
} else if (Elements.isFilledListConstructorCall(element, node, compiler)) {
if (tracer.couldBeTheList(node)) {
escaping = true;
visit(node.arguments.head);
TypeMask fillWithType = visit(node.arguments.tail.head);
tracer.unionPotentialTypeWith(fillWithType);
LiteralInt length = node.arguments.head.asLiteralInt();
if (length != null) {
tracer.setPotentialLength(length.value);
}
} else {
visitArguments(node.arguments, element);
}
return inferrer.typeOfNode(node);
}
bool isEscaping = visitArguments(node.arguments, element);
if (element.isForeign(compiler)) {
if (isEscaping) return tracer.bailout('Used in a JS');
}
if (tracer.couldBeTheList(element)) {
escaping = true;
}
if (element.isFunction() || element.isConstructor()) {
return inferrer.returnTypeOfElement(element);
} else {
// Closure call or unresolved.
return types.dynamicType;
}
}
TypeMask visitGetterSend(Send node) {
Element element = elements[node];
Selector selector = elements.getSelector(node);
if (Elements.isStaticOrTopLevelField(element)) {
if (tracer.couldBeTheList(element)) {
escaping = true;
}
return inferrer.typeOfElement(element);
} else if (Elements.isInstanceSend(node, elements)) {
return visitDynamicSend(node);
} else if (Elements.isStaticOrTopLevelFunction(element)) {
return types.functionType;
} else if (Elements.isErroneousElement(element)) {
return types.dynamicType;
} else if (Elements.isLocal(element)) {
if (tracer.couldBeTheList(element)) {
escaping = true;
}
return locals.use(element);
} else {
node.visitChildren(this);
return types.dynamicType;
}
}
TypeMask visitClosureSend(Send node) {
assert(node.receiver == null);
visit(node.selector);
bool isEscaping =
visitArguments(node.arguments, elements.getSelector(node));
if (isEscaping) return tracer.bailout('Passed to a closure');
return types.dynamicType;
}
TypeMask visitDynamicSend(Send node) {
bool isReceiver = visitAndCatchEscaping(() {
visit(node.receiver);
});
return handleDynamicSend(node, isReceiver);
}
TypeMask handleDynamicSend(Send node, bool isReceiver) {
Selector selector = elements.getSelector(node);
visitDynamicCallSiteTypeInformation(DynamicCallSiteTypeInformation info) {
Selector selector = info.selector;
String selectorName = selector.name.slowToString();
if (isReceiver && !okSelectorsSet.contains(selectorName)) {
if (selector.isCall()
&& (selectorName == 'add' || selectorName == 'insert')) {
TypeMask argumentType;
if (node.arguments.isEmpty
|| (selectorName == 'insert' && node.arguments.tail.isEmpty)) {
return tracer.bailout('Invalid "add" or "insert" call on a list');
}
bool isEscaping = visitAndCatchEscaping(() {
argumentType = visit(node.arguments.head);
if (selectorName == 'insert') {
argumentType = visit(node.arguments.tail.head);
if (allUsers.contains(info.receiver)) {
if (!okSelectorsSet.contains(selectorName)) {
if (selector.isCall()) {
int positionalLength = info.arguments.positional.length;
if (selectorName == 'add') {
if (positionalLength == 1) {
assignments.add(info.arguments.positional[0]);
}
} else if (selectorName == 'insert') {
if (positionalLength == 2) {
assignments.add(info.arguments.positional[1]);
}
} else {
bailout('Used in a not-ok selector');
return;
}
});
if (isEscaping) {
return tracer.bailout('List containing itself');
} else if (selector.isIndexSet()) {
assignments.add(info.arguments.positional[1]);
} else if (!selector.isIndex()) {
bailout('Used in a not-ok selector');
return;
}
tracer.unionPotentialTypeWith(argumentType);
} else {
return tracer.bailout('Send with the node as receiver $node');
}
} else if (!node.isPropertyAccess) {
visitArguments(node.arguments, selector);
if (!doNotChangeLengthSelectorsSet.contains(selectorName)) {
enableLengthTracking = false;
}
if (selectorName == 'length' && selector.isSetter()) {
enableLengthTracking = false;
assignments.add(inferrer.types.nullType);
}
} else if (selector.isCall()
&& !info.targets.every((element) => element.isFunction())) {
bailout('Passed to a closure');
return;
}
if (isReceiver && !doNotChangeLengthSelectorsSet.contains(selectorName)) {
tracer.disableLengthTracking();
}
if (tracer.couldBeTheList(selector)) {
escaping = true;
}
return inferrer.returnTypeOfSelector(selector);
}
TypeMask visitReturn(Return node) {
if (node.expression == null) {
return types.nullType;
}
TypeMask type;
bool isEscaping = visitAndCatchEscaping(() {
type = visit(node.expression);
});
if (isEscaping) {
if (visitingClosure) {
tracer.bailout('Return from closure');
} else {
tracer.addEscapingElement(analyzedElement);
}
}
return type;
bool isClosure(Element element) {
if (!element.isFunction()) return false;
Element outermost = element.getOutermostEnclosingMemberOrTopLevel();
return outermost.declaration != element.declaration;
}
TypeMask visitForIn(ForIn node) {
visit(node.expression);
Selector iteratorSelector = elements.getIteratorSelector(node);
Selector currentSelector = elements.getCurrentSelector(node);
TypeMask iteratorType = inferrer.returnTypeOfSelector(iteratorSelector);
TypeMask currentType = inferrer.returnTypeOfSelector(currentSelector);
// We nullify the type in case there is no element in the
// iterable.
currentType = currentType.nullable();
Node identifier = node.declaredIdentifier;
Element element = elements[identifier];
if (Elements.isLocal(element)) {
locals.update(element, currentType, node);
visitElementTypeInformation(ElementTypeInformation info) {
if (isClosure(info.element)) {
bailout('Returned from a closure');
}
return handleLoop(node, () {
visit(node.body);
});
}
}
@@ -538,38 +538,31 @@ class SimpleTypeInferrerVisitor<T>
}
T visitLiteralList(LiteralList node) {
if (node.isConst()) {
// We only set the type once. We don't need to re-visit the children
// when re-analyzing the node.
return inferrer.concreteTypes.putIfAbsent(node, () {
T elementType;
int length = 0;
for (Node element in node.elements.nodes) {
T type = visit(element);
elementType = elementType == null
? types.allocatePhi(null, null, type)
: types.addPhiInput(null, elementType, type);
length++;
}
// We only set the type once. We don't need to re-visit the children
// when re-analyzing the node.
return inferrer.concreteTypes.putIfAbsent(node, () {
T elementType;
int length = 0;
for (Node element in node.elements.nodes) {
T type = visit(element);
elementType = elementType == null
? types.nonNullEmpty()
: types.simplifyPhi(null, null, elementType);
return types.allocateContainer(
types.constListType,
node,
outermostElement,
elementType,
length);
});
} else {
node.visitChildren(this);
return inferrer.concreteTypes.putIfAbsent(node, () {
return types.allocateContainer(
types.growableListType,
node,
outermostElement);
});
}
? types.allocatePhi(null, null, type)
: types.addPhiInput(null, elementType, type);
length++;
}
elementType = elementType == null
? types.nonNullEmpty()
: types.simplifyPhi(null, null, elementType);
T containerType = node.isConst()
? types.constListType
: types.growableListType;
return types.allocateContainer(
containerType,
node,
outermostElement,
elementType,
length);
});
}
bool isThisOrSuper(Node node) => node.isThis() || node.isSuper();
@@ -826,12 +819,31 @@ class SimpleTypeInferrerVisitor<T>
if (Elements.isGrowableListConstructorCall(element, node, compiler)) {
return inferrer.concreteTypes.putIfAbsent(
node, () => types.allocateContainer(
types.growableListType, node, outermostElement));
types.growableListType, node, outermostElement,
types.nonNullEmpty(), 0));
} else if (Elements.isFixedListConstructorCall(element, node, compiler)
|| Elements.isFilledListConstructorCall(element, node, compiler)) {
int initialLength;
T elementType;
if (Elements.isFixedListConstructorCall(element, node, compiler)) {
LiteralInt length = node.arguments.head.asLiteralInt();
if (length != null) {
initialLength = length.value;
}
elementType = types.nullType;
} else {
LiteralInt length = node.arguments.head.asLiteralInt();
if (length != null) {
initialLength = length.value;
}
elementType = arguments.positional[1];
}
return inferrer.concreteTypes.putIfAbsent(
node, () => types.allocateContainer(
types.fixedListType, node, outermostElement));
types.fixedListType, node, outermostElement,
elementType, initialLength));
} else if (element.isFunction() || element.isConstructor()) {
return returnType;
} else {
@@ -7,7 +7,7 @@ library type_graph_inferrer;
import 'dart:collection' show Queue, LinkedHashSet, IterableBase, HashMap;
import '../dart_types.dart' show DartType, InterfaceType, TypeKind;
import '../elements/elements.dart';
import '../tree/tree.dart' show Node;
import '../tree/tree.dart' show LiteralList, Node;
import '../types/types.dart' show TypeMask, ContainerTypeMask, TypesInferrer;
import '../universe/universe.dart' show Selector, TypedSelector, SideEffects;
import '../dart2jslib.dart' show Compiler, SourceString, TreeElementMapping;
@@ -18,6 +18,7 @@ import 'simple_types_inferrer.dart';
import '../dart2jslib.dart' show invariant;
part 'type_graph_nodes.dart';
part 'container_tracer.dart';
/**
* A set of selector names that [List] implements, that we know return
@@ -249,7 +250,11 @@ class TypeInformationSystem extends TypeSystem<TypeInformation> {
Element enclosing,
[TypeInformation elementType, int length]) {
ContainerTypeMask mask = new ContainerTypeMask(type.type, node, enclosing);
mask.elementType = elementType == null ? null : elementType.type;
// Set the element type now for const lists, so that the inferrer
// can use it.
mask.elementType = (type.type == compiler.typesTask.constListType)
? elementType.type
: null;
mask.length = length;
TypeInformation element =
new ElementInContainerTypeInformation(elementType, mask);
@@ -460,6 +465,7 @@ class TypeGraphInferrerEngine
}
processLoopInformation();
types.allocatedContainers.values.forEach(analyzeContainer);
}
void processLoopInformation() {
@@ -498,6 +504,11 @@ class TypeGraphInferrerEngine
}
}
void analyzeContainer(ContainerTypeInformation info) {
if (info.elementType.isInConstContainer) return;
new ContainerTracerVisitor(info, this).run();
}
void buildWorkQueue() {
workQueue.addAll(types.typeInformations.values);
workQueue.addAll(types.allocatedTypes);
@@ -506,7 +517,7 @@ class TypeGraphInferrerEngine
/**
* Update the assignments to parameters in the graph. [remove] tells
* wheter assignments must be added or removed. If [init] is true,
* wheter assignments must be added or removed. If [init] is false,
* parameters are added to the work queue.
*/
void updateParameterAssignments(TypeInformation caller,
@@ -61,10 +61,14 @@ abstract class TypeInformation {
}
void addAssignment(TypeInformation assignment) {
if (abandonInferencing) return;
// Cheap one-level cycle detection.
if (assignment == this) return;
assignments.add(assignment);
if (!abandonInferencing) {
assignments.add(assignment);
}
// Even if we abandon inferencing on this [TypeInformation] we
// need to collect the users, so that phases that track where
// elements flow in still work.
assignment.addUser(this);
}
@@ -92,6 +96,17 @@ abstract class TypeInformation {
assignments = const <TypeInformation>[];
users = const <TypeInformation>[];
}
bool reachedBy(TypeInformation info, TypeGraphInferrerEngine inferrer) {
return true;
}
accept(TypeInformationVisitor visitor);
/// The [Element] where this [TypeInformation] was created. May be
/// for some [TypeInformation] nodes, where we do not need to store
/// the information.
Element get owner => null;
}
/**
@@ -268,6 +283,12 @@ class ElementTypeInformation extends TypeInformation {
}
String toString() => 'Element $element $type';
accept(TypeInformationVisitor visitor) {
return visitor.visitElementTypeInformation(this);
}
Element get owner => element.getOutermostEnclosingMemberOrTopLevel();
}
/**
@@ -301,6 +322,8 @@ abstract class CallSiteTypeInformation extends TypeInformation {
/// Return an iterable over the targets of this call.
Iterable<Element> get callees;
Element get owner => caller;
}
class StaticCallSiteTypeInformation extends CallSiteTypeInformation {
@@ -344,6 +367,14 @@ class StaticCallSiteTypeInformation extends CallSiteTypeInformation {
}
Iterable<Element> get callees => [calledElement.implementation];
bool reachedBy(TypeInformation info, TypeGraphInferrerEngine inferrer) {
return info == inferrer.types.getInferredTypeOf(calledElement);
}
accept(TypeInformationVisitor visitor) {
return visitor.visitStaticCallSiteTypeInformation(this);
}
}
class DynamicCallSiteTypeInformation extends CallSiteTypeInformation {
@@ -506,7 +537,17 @@ class DynamicCallSiteTypeInformation extends CallSiteTypeInformation {
super.giveUp(inferrer);
}
String toString() => 'Call site $call ${receiver.type} $type';
bool reachedBy(TypeInformation info, TypeGraphInferrerEngine inferrer) {
return targets
.map((element) => inferrer.types.getInferredTypeOf(element))
.any((other) => other == info);
}
String toString() => 'Call site $call on ${receiver.type} $type';
accept(TypeInformationVisitor visitor) {
return visitor.visitDynamicCallSiteTypeInformation(this);
}
}
class ClosureCallSiteTypeInformation extends CallSiteTypeInformation {
@@ -529,10 +570,14 @@ class ClosureCallSiteTypeInformation extends CallSiteTypeInformation {
}
Iterable<Element> get callees {
throw new UnsupportedError("Cannot compute callees of a closure.");
throw new UnsupportedError("Cannot compute callees of a closure call.");
}
String toString() => 'Closure call $call on $closure';
accept(TypeInformationVisitor visitor) {
return visitor.visitClosureCallSiteTypeInformation(this);
}
}
/**
@@ -569,6 +614,10 @@ class ConcreteTypeInformation extends TypeInformation {
}
String toString() => 'Type $type';
accept(TypeInformationVisitor visitor) {
return visitor.visitConcreteTypeInformation(this);
}
}
/**
@@ -602,23 +651,28 @@ class NarrowTypeInformation extends TypeInformation {
}
String toString() => 'Narrow ${assignments.first} to $typeAnnotation $type';
accept(TypeInformationVisitor visitor) {
return visitor.visitNarrowTypeInformation(this);
}
}
/**
* A [ContainerTypeInformation] is a [ConcreteTypeInformation] created
* A [ContainerTypeInformation] is a [TypeInformation] created
* for each `List` instantiations.
*/
class ContainerTypeInformation extends ConcreteTypeInformation {
final TypeInformation elementType;
class ContainerTypeInformation extends TypeInformation {
final ElementInContainerTypeInformation elementType;
ContainerTypeInformation(containerType, this.elementType)
: super(containerType);
void addUser(TypeInformation user) {
elementType.addUser(user);
ContainerTypeInformation(containerType, this.elementType) {
type = containerType;
}
String toString() => 'Container type $type';
accept(TypeInformationVisitor visitor) {
return visitor.visitContainerTypeInformation(this);
}
}
/**
@@ -629,17 +683,27 @@ class ElementInContainerTypeInformation extends TypeInformation {
final ContainerTypeMask container;
ElementInContainerTypeInformation(elementType, this.container) {
// [elementType] is not null for const lists.
if (elementType != null) addAssignment(elementType);
}
bool get isInConstContainer {
LiteralList literal = container.allocationNode.asLiteralList();
return (literal != null) && literal.isConst();
}
TypeMask refine(TypeGraphInferrerEngine inferrer) {
if (assignments.isEmpty) return inferrer.types.dynamicType.type;
if (!isInConstContainer) {
return inferrer.types.dynamicType.type;
}
return container.elementType =
inferrer.types.computeTypeMask(assignments);
}
String toString() => 'Element in container $type';
accept(TypeInformationVisitor visitor) {
return visitor.visitElementInContainerTypeInformation(this);
}
}
/**
@@ -658,4 +722,21 @@ class PhiElementTypeInformation extends TypeInformation {
}
String toString() => 'Phi $element $type';
accept(TypeInformationVisitor visitor) {
return visitor.visitPhiElementTypeInformation(this);
}
}
abstract class TypeInformationVisitor<T> {
T visitNarrowTypeInformation(NarrowTypeInformation info);
T visitPhiElementTypeInformation(PhiElementTypeInformation info);
T visitElementInContainerTypeInformation(
ElementInContainerTypeInformation info);
T visitContainerTypeInformation(ContainerTypeInformation info);
T visitConcreteTypeInformation(ConcreteTypeInformation info);
T visitClosureCallSiteTypeInformation(ClosureCallSiteTypeInformation info);
T visitStaticCallSiteTypeInformation(StaticCallSiteTypeInformation info);
T visitDynamicCallSiteTypeInformation(DynamicCallSiteTypeInformation info);
T visitElementTypeInformation(ElementTypeInformation info);
}
@@ -318,7 +318,6 @@ class TypesTask extends CompilerTask {
}
}
});
compiler.containerTracer.analyze();
typesInferrer.clear();
}
+4 -3
View File
@@ -222,12 +222,13 @@ void doTest(String allocation, {bool nullify}) {
checkType('listPassedToClosure', typesTask.dynamicType);
checkType('listReturnedFromClosure', typesTask.dynamicType);
checkType('listUsedWithNonOkSelector', typesTask.dynamicType);
checkType('listPassedAsOptionalParameter', typesTask.dynamicType);
checkType('listPassedAsNamedParameter', typesTask.dynamicType);
checkType('listPassedAsOptionalParameter', typesTask.numType);
checkType('listPassedAsNamedParameter', typesTask.numType);
if (!allocation.contains('filled')) {
checkType('listUnset', new TypeMask.nonNullEmpty());
checkType('listOnlySetWithConstraint', new TypeMask.nonNullEmpty());
// TODO(ngeoffray): Re-enable this test.
// checkType('listOnlySetWithConstraint', new TypeMask.nonNullEmpty());
}
}));
}
@@ -0,0 +1,19 @@
// 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.
// Regression test for dart2js' inferrer: if we closurize a method, we
// still need to collect the users of the parameters for the trace
// container pass to work.
main() {
var a = new List();
a.add;
var b = new List();
var c = new List(1);
b.add(c);
b[0][0] = 42;
if (c[0] is! int) {
throw 'Test failed';
}
}