Index: sdk/lib/_internal/compiler/implementation/inferrer/type_graph_inferrer.dart |
diff --git a/sdk/lib/_internal/compiler/implementation/inferrer/type_graph_inferrer.dart b/sdk/lib/_internal/compiler/implementation/inferrer/type_graph_inferrer.dart |
deleted file mode 100644 |
index ae3427a55e791a355daa0ad0478488cb874ccdeb..0000000000000000000000000000000000000000 |
--- a/sdk/lib/_internal/compiler/implementation/inferrer/type_graph_inferrer.dart |
+++ /dev/null |
@@ -1,1322 +0,0 @@ |
-// 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 type_graph_inferrer; |
- |
-import 'dart:collection' show Queue, IterableBase; |
- |
-import '../constants/expressions.dart'; |
-import '../constants/values.dart'; |
-import '../cps_ir/cps_ir_nodes.dart' as cps_ir |
- show Node; |
-import '../dart_types.dart' |
- show DartType, |
- FunctionType, |
- InterfaceType, |
- TypeKind; |
-import '../dart2jslib.dart' |
- show ClassWorld, |
- Compiler, |
- Constant, |
- FunctionConstant, |
- invariant, |
- TreeElementMapping; |
-import '../elements/elements.dart'; |
-import '../native/native.dart' as native; |
-import '../tree/tree.dart' as ast |
- show DartString, |
- Node, |
- TryStatement; |
-import '../types/types.dart' |
- show ContainerTypeMask, |
- DictionaryTypeMask, |
- MapTypeMask, |
- TypeMask, |
- TypesInferrer, |
- ValueTypeMask; |
-import '../universe/universe.dart' |
- show Selector, |
- SideEffects, |
- TypedSelector; |
-import '../util/util.dart' |
- show ImmutableEmptySet, |
- Setlet, |
- Spannable; |
- |
-import 'inferrer_visitor.dart' |
- show ArgumentsTypes, |
- TypeSystem; |
-import 'simple_types_inferrer.dart'; |
- |
-part 'closure_tracer.dart'; |
-part 'list_tracer.dart'; |
-part 'map_tracer.dart'; |
-part 'node_tracer.dart'; |
-part 'type_graph_nodes.dart'; |
- |
-bool _VERBOSE = false; |
-bool _PRINT_SUMMARY = false; |
-final _ANOMALY_WARN = false; |
- |
-class TypeInformationSystem extends TypeSystem<TypeInformation> { |
- final Compiler compiler; |
- final ClassWorld classWorld; |
- |
- /// [ElementTypeInformation]s for elements. |
- final Map<Element, TypeInformation> typeInformations = |
- new Map<Element, TypeInformation>(); |
- |
- /// [ListTypeInformation] for allocated lists. |
- final Map<ast.Node, TypeInformation> allocatedLists = |
- new Map<ast.Node, TypeInformation>(); |
- |
- /// [MapTypeInformation] for allocated Maps. |
- final Map<ast.Node, TypeInformation> allocatedMaps = |
- new Map<ast.Node, TypeInformation>(); |
- |
- /// Closures found during the analysis. |
- final Set<TypeInformation> allocatedClosures = new Set<TypeInformation>(); |
- |
- /// Cache of [ConcreteTypeInformation]. |
- final Map<TypeMask, TypeInformation> concreteTypes = |
- new Map<TypeMask, TypeInformation>(); |
- |
- /// List of [TypeInformation]s allocated inside method bodies (calls, |
- /// narrowing, phis, and containers). |
- final List<TypeInformation> allocatedTypes = <TypeInformation>[]; |
- |
- TypeInformationSystem(Compiler compiler) |
- : this.compiler = compiler, |
- this.classWorld = compiler.world { |
- nonNullEmptyType = getConcreteTypeFor(const TypeMask.nonNullEmpty()); |
- } |
- |
- /// Used to group [TypeInformation] nodes by the element that triggered their |
- /// creation. |
- MemberTypeInformation _currentMember = null; |
- MemberTypeInformation get currentMember => _currentMember; |
- |
- void withMember(MemberElement element, action) { |
- assert(invariant(element, _currentMember == null, |
- message: "Already constructing graph for $_currentMember.")); |
- _currentMember = getInferredTypeOf(element); |
- action(); |
- _currentMember = null; |
- } |
- |
- TypeInformation nullTypeCache; |
- TypeInformation get nullType { |
- if (nullTypeCache != null) return nullTypeCache; |
- return nullTypeCache = getConcreteTypeFor(compiler.typesTask.nullType); |
- } |
- |
- TypeInformation intTypeCache; |
- TypeInformation get intType { |
- if (intTypeCache != null) return intTypeCache; |
- return intTypeCache = getConcreteTypeFor(compiler.typesTask.intType); |
- } |
- |
- TypeInformation uint32TypeCache; |
- TypeInformation get uint32Type { |
- if (uint32TypeCache != null) return uint32TypeCache; |
- return uint32TypeCache = getConcreteTypeFor(compiler.typesTask.uint32Type); |
- } |
- |
- TypeInformation uint31TypeCache; |
- TypeInformation get uint31Type { |
- if (uint31TypeCache != null) return uint31TypeCache; |
- return uint31TypeCache = getConcreteTypeFor(compiler.typesTask.uint31Type); |
- } |
- |
- TypeInformation positiveIntTypeCache; |
- TypeInformation get positiveIntType { |
- if (positiveIntTypeCache != null) return positiveIntTypeCache; |
- return positiveIntTypeCache = |
- getConcreteTypeFor(compiler.typesTask.positiveIntType); |
- } |
- |
- TypeInformation doubleTypeCache; |
- TypeInformation get doubleType { |
- if (doubleTypeCache != null) return doubleTypeCache; |
- return doubleTypeCache = getConcreteTypeFor(compiler.typesTask.doubleType); |
- } |
- |
- TypeInformation numTypeCache; |
- TypeInformation get numType { |
- if (numTypeCache != null) return numTypeCache; |
- return numTypeCache = getConcreteTypeFor(compiler.typesTask.numType); |
- } |
- |
- TypeInformation boolTypeCache; |
- TypeInformation get boolType { |
- if (boolTypeCache != null) return boolTypeCache; |
- return boolTypeCache = getConcreteTypeFor(compiler.typesTask.boolType); |
- } |
- |
- TypeInformation functionTypeCache; |
- TypeInformation get functionType { |
- if (functionTypeCache != null) return functionTypeCache; |
- return functionTypeCache = |
- getConcreteTypeFor(compiler.typesTask.functionType); |
- } |
- |
- TypeInformation listTypeCache; |
- TypeInformation get listType { |
- if (listTypeCache != null) return listTypeCache; |
- return listTypeCache = getConcreteTypeFor(compiler.typesTask.listType); |
- } |
- |
- TypeInformation constListTypeCache; |
- TypeInformation get constListType { |
- if (constListTypeCache != null) return constListTypeCache; |
- return constListTypeCache = |
- getConcreteTypeFor(compiler.typesTask.constListType); |
- } |
- |
- TypeInformation fixedListTypeCache; |
- TypeInformation get fixedListType { |
- if (fixedListTypeCache != null) return fixedListTypeCache; |
- return fixedListTypeCache = |
- getConcreteTypeFor(compiler.typesTask.fixedListType); |
- } |
- |
- TypeInformation growableListTypeCache; |
- TypeInformation get growableListType { |
- if (growableListTypeCache != null) return growableListTypeCache; |
- return growableListTypeCache = |
- getConcreteTypeFor(compiler.typesTask.growableListType); |
- } |
- |
- TypeInformation mapTypeCache; |
- TypeInformation get mapType { |
- if (mapTypeCache != null) return mapTypeCache; |
- return mapTypeCache = getConcreteTypeFor(compiler.typesTask.mapType); |
- } |
- |
- TypeInformation constMapTypeCache; |
- TypeInformation get constMapType { |
- if (constMapTypeCache != null) return constMapTypeCache; |
- return constMapTypeCache = |
- getConcreteTypeFor(compiler.typesTask.constMapType); |
- } |
- |
- TypeInformation stringTypeCache; |
- TypeInformation get stringType { |
- if (stringTypeCache != null) return stringTypeCache; |
- return stringTypeCache = getConcreteTypeFor(compiler.typesTask.stringType); |
- } |
- |
- TypeInformation typeTypeCache; |
- TypeInformation get typeType { |
- if (typeTypeCache != null) return typeTypeCache; |
- return typeTypeCache = getConcreteTypeFor(compiler.typesTask.typeType); |
- } |
- |
- TypeInformation dynamicTypeCache; |
- TypeInformation get dynamicType { |
- if (dynamicTypeCache != null) return dynamicTypeCache; |
- return dynamicTypeCache = |
- getConcreteTypeFor(compiler.typesTask.dynamicType); |
- } |
- |
- TypeInformation nonNullEmptyType; |
- |
- TypeInformation stringLiteralType(ast.DartString value) { |
- return new StringLiteralTypeInformation( |
- value, compiler.typesTask.stringType); |
- } |
- |
- TypeInformation computeLUB(TypeInformation firstType, |
- TypeInformation secondType) { |
- if (firstType == null) return secondType; |
- if (firstType == secondType) return firstType; |
- if (firstType == nonNullEmptyType) return secondType; |
- if (secondType == nonNullEmptyType) return firstType; |
- if (firstType == dynamicType || secondType == dynamicType) { |
- return dynamicType; |
- } |
- return getConcreteTypeFor( |
- firstType.type.union(secondType.type, classWorld)); |
- } |
- |
- bool selectorNeedsUpdate(TypeInformation info, Selector selector) |
- { |
- return info.type != selector.mask; |
- } |
- |
- TypeInformation refineReceiver(Selector selector, TypeInformation receiver) { |
- if (receiver.type.isExact) return receiver; |
- TypeMask otherType = compiler.world.allFunctions.receiverType(selector); |
- // If this is refining to nullable subtype of `Object` just return |
- // the receiver. We know the narrowing is useless. |
- if (otherType.isNullable && otherType.containsAll(classWorld)) { |
- return receiver; |
- } |
- assert(TypeMask.assertIsNormalized(otherType, classWorld)); |
- TypeInformation newType = new NarrowTypeInformation(receiver, otherType); |
- allocatedTypes.add(newType); |
- return newType; |
- } |
- |
- TypeInformation narrowType(TypeInformation type, |
- DartType annotation, |
- {bool isNullable: true}) { |
- if (annotation.treatAsDynamic) return type; |
- if (annotation.isVoid) return nullType; |
- if (annotation.element == classWorld.objectClass && isNullable) return type; |
- TypeMask otherType; |
- if (annotation.isTypedef || annotation.isFunctionType) { |
- otherType = functionType.type; |
- } else if (annotation.isTypeVariable) { |
- // TODO(ngeoffray): Narrow to bound. |
- return type; |
- } else { |
- assert(annotation.isInterfaceType); |
- otherType = annotation.element == classWorld.objectClass |
- ? dynamicType.type.nonNullable() |
- : new TypeMask.nonNullSubtype(annotation.element, classWorld); |
- } |
- if (isNullable) otherType = otherType.nullable(); |
- if (type.type.isExact) { |
- return type; |
- } else { |
- assert(TypeMask.assertIsNormalized(otherType, classWorld)); |
- TypeInformation newType = new NarrowTypeInformation(type, otherType); |
- allocatedTypes.add(newType); |
- return newType; |
- } |
- } |
- |
- ElementTypeInformation getInferredTypeOf(Element element) { |
- element = element.implementation; |
- return typeInformations.putIfAbsent(element, () { |
- return new ElementTypeInformation(element, this); |
- }); |
- } |
- |
- ConcreteTypeInformation getConcreteTypeFor(TypeMask mask) { |
- assert(mask != null); |
- return concreteTypes.putIfAbsent(mask, () { |
- return new ConcreteTypeInformation(mask); |
- }); |
- } |
- |
- String getInferredSignatureOf(FunctionElement function) { |
- ElementTypeInformation info = getInferredTypeOf(function); |
- FunctionElement impl = function.implementation; |
- FunctionSignature signature = impl.functionSignature; |
- var res = ""; |
- signature.forEachParameter((Element parameter) { |
- TypeInformation type = getInferredTypeOf(parameter); |
- res += "${res.isEmpty ? '(' : ', '}${type.type} ${parameter.name}"; |
- }); |
- res += ") -> ${info.type}"; |
- return res; |
- } |
- |
- TypeInformation nonNullSubtype(ClassElement type) { |
- return getConcreteTypeFor( |
- new TypeMask.nonNullSubtype(type.declaration, classWorld)); |
- } |
- |
- TypeInformation nonNullSubclass(ClassElement type) { |
- return getConcreteTypeFor( |
- new TypeMask.nonNullSubclass(type.declaration, classWorld)); |
- } |
- |
- TypeInformation nonNullExact(ClassElement type) { |
- return getConcreteTypeFor( |
- new TypeMask.nonNullExact(type.declaration, classWorld)); |
- } |
- |
- TypeInformation nonNullEmpty() { |
- return nonNullEmptyType; |
- } |
- |
- bool isNull(TypeInformation type) { |
- return type == nullType; |
- } |
- |
- TypeInformation allocateList(TypeInformation type, |
- ast.Node node, |
- Element enclosing, |
- [TypeInformation elementType, int length]) { |
- bool isTypedArray = |
- compiler.typedDataClass != null && |
- classWorld.isInstantiated(compiler.typedDataClass) && |
- type.type.satisfies(compiler.typedDataClass, classWorld); |
- bool isConst = (type.type == compiler.typesTask.constListType); |
- bool isFixed = (type.type == compiler.typesTask.fixedListType) || |
- isConst || |
- isTypedArray; |
- bool isElementInferred = isConst || isTypedArray; |
- |
- int inferredLength = isFixed ? length : null; |
- TypeMask elementTypeMask = isElementInferred |
- ? elementType.type |
- : dynamicType.type; |
- ContainerTypeMask mask = new ContainerTypeMask( |
- type.type, node, enclosing, elementTypeMask, inferredLength); |
- ElementInContainerTypeInformation element = |
- new ElementInContainerTypeInformation(currentMember, elementType); |
- element.inferred = isElementInferred; |
- |
- allocatedTypes.add(element); |
- return allocatedLists[node] = |
- new ListTypeInformation(currentMember, mask, element, length); |
- } |
- |
- TypeInformation allocateClosure(ast.Node node, Element element) { |
- TypeInformation result = |
- new ClosureTypeInformation(currentMember, node, element); |
- allocatedClosures.add(result); |
- return result; |
- } |
- |
- TypeInformation allocateMap(ConcreteTypeInformation type, |
- ast.Node node, |
- Element element, |
- [List<TypeInformation> keyTypes, |
- List<TypeInformation> valueTypes]) { |
- assert(keyTypes.length == valueTypes.length); |
- bool isFixed = (type.type == compiler.typesTask.constMapType); |
- |
- TypeMask keyType, valueType; |
- if (isFixed) { |
- keyType = keyTypes.fold(nonNullEmptyType.type, |
- (type, info) => type.union(info.type, classWorld)); |
- valueType = valueTypes.fold(nonNullEmptyType.type, |
- (type, info) => type.union(info.type, classWorld)); |
- } else { |
- keyType = valueType = dynamicType.type; |
- } |
- MapTypeMask mask = new MapTypeMask(type.type, |
- node, |
- element, |
- keyType, |
- valueType); |
- |
- TypeInformation keyTypeInfo = |
- new KeyInMapTypeInformation(currentMember, null); |
- TypeInformation valueTypeInfo = |
- new ValueInMapTypeInformation(currentMember, null); |
- allocatedTypes.add(keyTypeInfo); |
- allocatedTypes.add(valueTypeInfo); |
- |
- MapTypeInformation map = |
- new MapTypeInformation(currentMember, mask, keyTypeInfo, valueTypeInfo); |
- |
- for (int i = 0; i < keyTypes.length; ++i) { |
- TypeInformation newType = |
- map.addEntryAssignment(keyTypes[i], valueTypes[i], true); |
- if (newType != null) allocatedTypes.add(newType); |
- } |
- |
- // Shortcut: If we already have a first approximation of the key/value type, |
- // start propagating it early. |
- if (isFixed) map.markAsInferred(); |
- |
- allocatedMaps[node] = map; |
- return map; |
- } |
- |
- Selector newTypedSelector(TypeInformation info, Selector selector) { |
- // Only type the selector if [info] is concrete, because the other |
- // kinds of [TypeInformation] have the empty type at this point of |
- // analysis. |
- return info.isConcrete |
- ? new TypedSelector(info.type, selector, classWorld) |
- : selector; |
- } |
- |
- TypeInformation allocateDiamondPhi(TypeInformation firstInput, |
- TypeInformation secondInput) { |
- PhiElementTypeInformation result = |
- new PhiElementTypeInformation(currentMember, null, false, null); |
- result.addAssignment(firstInput); |
- result.addAssignment(secondInput); |
- allocatedTypes.add(result); |
- return result; |
- } |
- |
- PhiElementTypeInformation _addPhi(ast.Node node, |
- Local variable, |
- inputType, |
- bool isLoop) { |
- PhiElementTypeInformation result = |
- new PhiElementTypeInformation(currentMember, node, isLoop, variable); |
- allocatedTypes.add(result); |
- result.addAssignment(inputType); |
- return result; |
- } |
- |
- PhiElementTypeInformation allocatePhi(ast.Node node, |
- Local variable, |
- inputType) { |
- // Check if [inputType] is a phi for a local updated in |
- // the try/catch block [node]. If it is, no need to allocate a new |
- // phi. |
- if (inputType is PhiElementTypeInformation && |
- inputType.branchNode == node && |
- inputType.branchNode is ast.TryStatement) { |
- return inputType; |
- } |
- return _addPhi(node, variable, inputType, false); |
- } |
- |
- PhiElementTypeInformation allocateLoopPhi(ast.Node node, |
- Local variable, |
- inputType) { |
- return _addPhi(node, variable, inputType, true); |
- } |
- |
- TypeInformation simplifyPhi(ast.Node node, |
- Local variable, |
- PhiElementTypeInformation phiType) { |
- assert(phiType.branchNode == node); |
- if (phiType.assignments.length == 1) return phiType.assignments.first; |
- return phiType; |
- } |
- |
- PhiElementTypeInformation addPhiInput(Local variable, |
- PhiElementTypeInformation phiType, |
- TypeInformation newType) { |
- phiType.addAssignment(newType); |
- return phiType; |
- } |
- |
- TypeMask computeTypeMask(Iterable<TypeInformation> assignments) { |
- return joinTypeMasks(assignments.map((e) => e.type)); |
- } |
- |
- TypeMask joinTypeMasks(Iterable<TypeMask> masks) { |
- TypeMask newType = const TypeMask.nonNullEmpty(); |
- for (TypeMask mask in masks) { |
- newType = newType.union(mask, classWorld); |
- } |
- return newType.containsAll(classWorld) ? dynamicType.type : newType; |
- } |
-} |
- |
-/** |
- * A work queue for the inferrer. It filters out nodes that are tagged as |
- * [TypeInformation.doNotEnqueue], as well as ensures through |
- * [TypeInformation.inQueue] that a node is in the queue only once at |
- * a time. |
- */ |
-class WorkQueue { |
- final Queue<TypeInformation> queue = new Queue<TypeInformation>(); |
- |
- void add(TypeInformation element) { |
- if (element.doNotEnqueue) return; |
- if (element.inQueue) return; |
- queue.addLast(element); |
- element.inQueue = true; |
- } |
- |
- void addAll(Iterable<TypeInformation> all) { |
- all.forEach(add); |
- } |
- |
- TypeInformation remove() { |
- TypeInformation element = queue.removeFirst(); |
- element.inQueue = false; |
- return element; |
- } |
- |
- bool get isEmpty => queue.isEmpty; |
- |
- int get length => queue.length; |
-} |
- |
-/** |
- * An inferencing engine that computes a call graph of |
- * [TypeInformation] nodes by visiting the AST of the application, and |
- * then does the inferencing on the graph. |
- * |
- */ |
-class TypeGraphInferrerEngine |
- extends InferrerEngine<TypeInformation, TypeInformationSystem> { |
- final Map<Element, TypeInformation> defaultTypeOfParameter = |
- new Map<Element, TypeInformation>(); |
- final List<CallSiteTypeInformation> allocatedCalls = |
- <CallSiteTypeInformation>[]; |
- final WorkQueue workQueue = new WorkQueue(); |
- final Element mainElement; |
- final Set<Element> analyzedElements = new Set<Element>(); |
- |
- /// The maximum number of times we allow a node in the graph to |
- /// change types. If a node reaches that limit, we give up |
- /// inferencing on it and give it the dynamic type. |
- final int MAX_CHANGE_COUNT = 6; |
- |
- int overallRefineCount = 0; |
- int addedInGraph = 0; |
- |
- TypeGraphInferrerEngine(Compiler compiler, this.mainElement) |
- : super(compiler, new TypeInformationSystem(compiler)); |
- |
- /** |
- * A set of selector names that [List] implements, that we know return |
- * their element type. |
- */ |
- final Set<Selector> _returnsListElementTypeSet = new Set<Selector>.from( |
- <Selector>[ |
- new Selector.getter('first', null), |
- new Selector.getter('last', null), |
- new Selector.getter('single', null), |
- new Selector.call('singleWhere', null, 1), |
- new Selector.call('elementAt', null, 1), |
- new Selector.index(), |
- new Selector.call('removeAt', null, 1), |
- new Selector.call('removeLast', null, 0) |
- ]); |
- |
- bool returnsListElementType(Selector selector) { |
- return (selector.mask != null) && |
- selector.mask.isContainer && |
- _returnsListElementTypeSet.contains(selector.asUntyped); |
- } |
- |
- bool returnsMapValueType(Selector selector) { |
- return (selector.mask != null) && |
- selector.mask.isMap && |
- selector.isIndex; |
- } |
- |
- void analyzeListAndEnqueue(ListTypeInformation info) { |
- if (info.analyzed) return; |
- info.analyzed = true; |
- |
- ListTracerVisitor tracer = new ListTracerVisitor(info, this); |
- bool succeeded = tracer.run(); |
- if (!succeeded) return; |
- |
- info.bailedOut = false; |
- info.elementType.inferred = true; |
- TypeMask fixedListType = compiler.typesTask.fixedListType; |
- if (info.originalType.forwardTo == fixedListType) { |
- info.checksGrowable = tracer.callsGrowableMethod; |
- } |
- tracer.assignments.forEach(info.elementType.addAssignment); |
- // Enqueue the list for later refinement |
- workQueue.add(info); |
- workQueue.add(info.elementType); |
- } |
- |
- void analyzeMapAndEnqueue(MapTypeInformation info) { |
- if (info.analyzed) return; |
- info.analyzed = true; |
- MapTracerVisitor tracer = new MapTracerVisitor(info, this); |
- |
- bool succeeded = tracer.run(); |
- if (!succeeded) return; |
- |
- info.bailedOut = false; |
- for (int i = 0; i < tracer.keyAssignments.length; ++i) { |
- TypeInformation newType = info.addEntryAssignment( |
- tracer.keyAssignments[i], tracer.valueAssignments[i]); |
- if (newType != null) workQueue.add(newType); |
- } |
- for (TypeInformation map in tracer.mapAssignments) { |
- workQueue.addAll(info.addMapAssignment(map)); |
- } |
- |
- info.markAsInferred(); |
- workQueue.add(info.keyType); |
- workQueue.add(info.valueType); |
- workQueue.addAll(info.typeInfoMap.values); |
- workQueue.add(info); |
- } |
- |
- void runOverAllElements() { |
- if (compiler.disableTypeInference) return; |
- if (compiler.verbose) { |
- compiler.progress.reset(); |
- } |
- sortResolvedElements().forEach((Element element) { |
- if (compiler.shouldPrintProgress) { |
- compiler.log('Added $addedInGraph elements in inferencing graph.'); |
- compiler.progress.reset(); |
- } |
- // This also forces the creation of the [ElementTypeInformation] to ensure |
- // it is in the graph. |
- types.withMember(element, () => analyze(element, null)); |
- }); |
- compiler.log('Added $addedInGraph elements in inferencing graph.'); |
- |
- buildWorkQueue(); |
- refine(); |
- |
- // Try to infer element types of lists and compute their escape information. |
- types.allocatedLists.values.forEach((ListTypeInformation info) { |
- analyzeListAndEnqueue(info); |
- }); |
- |
- // Try to infer the key and value types for maps and compute the values' |
- // escape information. |
- types.allocatedMaps.values.forEach((MapTypeInformation info) { |
- analyzeMapAndEnqueue(info); |
- }); |
- |
- Set<FunctionElement> bailedOutOn = new Set<FunctionElement>(); |
- |
- // Trace closures to potentially infer argument types. |
- types.allocatedClosures.forEach((info) { |
- void trace(Iterable<FunctionElement> elements, |
- ClosureTracerVisitor tracer) { |
- tracer.run(); |
- if (!tracer.continueAnalyzing) { |
- elements.forEach((FunctionElement e) { |
- compiler.world.registerMightBePassedToApply(e); |
- if (_VERBOSE) print("traced closure $e as ${true} (bail)"); |
- e.functionSignature.forEachParameter((parameter) { |
- types.getInferredTypeOf(parameter).giveUp( |
- this, |
- clearAssignments: false); |
- }); |
- }); |
- bailedOutOn.addAll(elements); |
- return; |
- } |
- elements |
- .where((e) => !bailedOutOn.contains(e)) |
- .forEach((FunctionElement e) { |
- e.functionSignature.forEachParameter((parameter) { |
- var info = types.getInferredTypeOf(parameter); |
- info.maybeResume(); |
- workQueue.add(info); |
- }); |
- if (tracer.tracedType.mightBePassedToFunctionApply) { |
- compiler.world.registerMightBePassedToApply(e); |
- }; |
- if (_VERBOSE) { |
- print("traced closure $e as " |
- "${compiler.world.getMightBePassedToApply(e)}"); |
- } |
- }); |
- } |
- if (info is ClosureTypeInformation) { |
- Iterable<FunctionElement> elements = [info.element]; |
- trace(elements, new ClosureTracerVisitor(elements, info, this)); |
- } else if (info is CallSiteTypeInformation) { |
- // We only are interested in functions here, as other targets |
- // of this closure call are not a root to trace but an intermediate |
- // for some other function. |
- Iterable<FunctionElement> elements = info.callees |
- .where((e) => e.isFunction); |
- trace(elements, new ClosureTracerVisitor(elements, info, this)); |
- } else { |
- assert(info is ElementTypeInformation); |
- trace([info.element], |
- new StaticTearOffClosureTracerVisitor(info.element, info, this)); |
- } |
- }); |
- |
- // Reset all nodes that use lists/maps that have been inferred, as well |
- // as nodes that use elements fetched from these lists/maps. The |
- // workset for a new run of the analysis will be these nodes. |
- Set<TypeInformation> seenTypes = new Set<TypeInformation>(); |
- while (!workQueue.isEmpty) { |
- TypeInformation info = workQueue.remove(); |
- if (seenTypes.contains(info)) continue; |
- // If the node cannot be reset, we do not need to update its users either. |
- if (!info.reset(this)) continue; |
- seenTypes.add(info); |
- workQueue.addAll(info.users); |
- } |
- |
- workQueue.addAll(seenTypes); |
- refine(); |
- |
- if (_PRINT_SUMMARY) { |
- types.allocatedLists.values.forEach((ListTypeInformation info) { |
- print('${info.type} ' |
- 'for ${info.originalType.allocationNode} ' |
- 'at ${info.originalType.allocationElement} ' |
- 'after ${info.refineCount}'); |
- }); |
- types.allocatedMaps.values.forEach((MapTypeInformation info) { |
- print('${info.type} ' |
- 'for ${info.originalType.allocationNode} ' |
- 'at ${info.originalType.allocationElement} ' |
- 'after ${info.refineCount}'); |
- }); |
- types.allocatedClosures.forEach((TypeInformation info) { |
- if (info is ElementTypeInformation) { |
- print('${types.getInferredSignatureOf(info.element)} for ' |
- '${info.element}'); |
- } else if (info is ClosureTypeInformation) { |
- print('${types.getInferredSignatureOf(info.element)} for ' |
- '${info.element}'); |
- } else if (info is DynamicCallSiteTypeInformation) { |
- for (Element target in info.targets) { |
- if (target is FunctionElement) { |
- print('${types.getInferredSignatureOf(target)} for ${target}'); |
- } else { |
- print('${types.getInferredTypeOf(target).type} for ${target}'); |
- } |
- } |
- } else { |
- print('${info.type} for some unknown kind of closure'); |
- } |
- }); |
- analyzedElements.forEach((Element elem) { |
- TypeInformation type = types.getInferredTypeOf(elem); |
- print('${elem} :: ${type} from ${type.assignments} '); |
- }); |
- } |
- |
- compiler.log('Inferred $overallRefineCount types.'); |
- |
- processLoopInformation(); |
- } |
- |
- void analyze(Element element, ArgumentsTypes arguments) { |
- element = element.implementation; |
- if (analyzedElements.contains(element)) return; |
- analyzedElements.add(element); |
- |
- SimpleTypeInferrerVisitor visitor = |
- new SimpleTypeInferrerVisitor(element, compiler, this); |
- TypeInformation type; |
- compiler.withCurrentElement(element, () { |
- type = visitor.run(); |
- }); |
- addedInGraph++; |
- |
- if (element.isField) { |
- VariableElement fieldElement = element; |
- ast.Node node = fieldElement.node; |
- if (element.isFinal || element.isConst) { |
- // If [element] is final and has an initializer, we record |
- // the inferred type. |
- if (fieldElement.initializer != null) { |
- if (type is! ListTypeInformation && type is! MapTypeInformation) { |
- // For non-container types, the constant handler does |
- // constant folding that could give more precise results. |
- ConstantExpression constant = compiler.backend.constants |
- .getConstantForVariable(element); |
- if (constant != null) { |
- ConstantValue value = constant.value; |
- if (value.isFunction) { |
- FunctionConstantValue functionConstant = value; |
- type = types.allocateClosure(node, functionConstant.element); |
- } else { |
- // Although we might find a better type, we have to keep |
- // the old type around to ensure that we get a complete view |
- // of the type graph and do not drop any flow edges. |
- TypeMask refinedType = value.computeMask(compiler); |
- assert(TypeMask.assertIsNormalized(refinedType, classWorld)); |
- type = new NarrowTypeInformation(type, refinedType); |
- types.allocatedTypes.add(type); |
- } |
- } |
- } |
- recordType(element, type); |
- } else if (!element.isInstanceMember) { |
- recordType(element, types.nullType); |
- } |
- } else if (fieldElement.initializer == null) { |
- // Only update types of static fields if there is no |
- // assignment. Instance fields are dealt with in the constructor. |
- if (Elements.isStaticOrTopLevelField(element)) { |
- recordTypeOfNonFinalField(node, element, type); |
- } |
- } else { |
- recordTypeOfNonFinalField(node, element, type); |
- } |
- if (Elements.isStaticOrTopLevelField(element) && |
- fieldElement.initializer != null && |
- !element.isConst) { |
- var argument = fieldElement.initializer; |
- // TODO(13429): We could do better here by using the |
- // constant handler to figure out if it's a lazy field or not. |
- if (argument.asSend() != null || |
- (argument.asNewExpression() != null && !argument.isConst)) { |
- recordType(element, types.nullType); |
- } |
- } |
- } else { |
- recordReturnType(element, type); |
- } |
- } |
- |
- void processLoopInformation() { |
- allocatedCalls.forEach((info) { |
- if (!info.inLoop) return; |
- if (info is StaticCallSiteTypeInformation) { |
- compiler.world.addFunctionCalledInLoop(info.calledElement); |
- } else if (info.selector.mask != null && |
- !info.selector.mask.containsAll(compiler.world)) { |
- // For instance methods, we only register a selector called in a |
- // loop if it is a typed selector, to avoid marking too many |
- // methods as being called from within a loop. This cuts down |
- // on the code bloat. |
- info.targets.forEach(compiler.world.addFunctionCalledInLoop); |
- } |
- }); |
- } |
- |
- void refine() { |
- while (!workQueue.isEmpty) { |
- if (compiler.shouldPrintProgress) { |
- compiler.log('Inferred $overallRefineCount types.'); |
- compiler.progress.reset(); |
- } |
- TypeInformation info = workQueue.remove(); |
- TypeMask oldType = info.type; |
- TypeMask newType = info.refine(this); |
- // Check that refinement has not accidentially changed the type. |
- assert(oldType == info.type); |
- if (info.abandonInferencing) info.doNotEnqueue = true; |
- if ((info.type = newType) != oldType) { |
- overallRefineCount++; |
- info.refineCount++; |
- if (info.refineCount > MAX_CHANGE_COUNT) { |
- if (_ANOMALY_WARN) { |
- print("ANOMALY WARNING: max refinement reached for $info"); |
- } |
- info.giveUp(this); |
- info.type = info.refine(this); |
- info.doNotEnqueue = true; |
- } |
- workQueue.addAll(info.users); |
- if (info.hasStableType(this)) { |
- info.stabilize(this); |
- } |
- } |
- } |
- } |
- |
- void buildWorkQueue() { |
- workQueue.addAll(types.typeInformations.values); |
- workQueue.addAll(types.allocatedTypes); |
- workQueue.addAll(types.allocatedClosures); |
- workQueue.addAll(allocatedCalls); |
- } |
- |
- /** |
- * Update the assignments to parameters in the graph. [remove] tells |
- * wheter assignments must be added or removed. If [init] is false, |
- * parameters are added to the work queue. |
- */ |
- void updateParameterAssignments(TypeInformation caller, |
- Element callee, |
- ArgumentsTypes arguments, |
- Selector selector, |
- {bool remove, bool addToQueue: true}) { |
- if (callee.name == Compiler.NO_SUCH_METHOD) return; |
- if (callee.isField) { |
- if (selector.isSetter) { |
- ElementTypeInformation info = types.getInferredTypeOf(callee); |
- if (remove) { |
- info.removeAssignment(arguments.positional[0]); |
- } else { |
- info.addAssignment(arguments.positional[0]); |
- } |
- if (addToQueue) workQueue.add(info); |
- } |
- } else if (callee.isGetter) { |
- return; |
- } else if (selector != null && selector.isGetter) { |
- // We are tearing a function off and thus create a closure. |
- assert(callee.isFunction); |
- MemberTypeInformation info = types.getInferredTypeOf(callee); |
- if (remove) { |
- info.closurizedCount--; |
- } else { |
- info.closurizedCount++; |
- if (Elements.isStaticOrTopLevel(callee)) { |
- types.allocatedClosures.add(info); |
- } else { |
- // We add the call-site type information here so that we |
- // can benefit from further refinement of the selector. |
- types.allocatedClosures.add(caller); |
- } |
- FunctionElement function = callee.implementation; |
- FunctionSignature signature = function.functionSignature; |
- signature.forEachParameter((Element parameter) { |
- ParameterTypeInformation info = types.getInferredTypeOf(parameter); |
- info.tagAsTearOffClosureParameter(this); |
- if (addToQueue) workQueue.add(info); |
- }); |
- } |
- } else { |
- FunctionElement function = callee.implementation; |
- FunctionSignature signature = function.functionSignature; |
- int parameterIndex = 0; |
- bool visitingRequiredParameter = true; |
- signature.forEachParameter((Element parameter) { |
- if (signature.hasOptionalParameters && |
- parameter == signature.firstOptionalParameter) { |
- visitingRequiredParameter = false; |
- } |
- TypeInformation type = visitingRequiredParameter |
- ? arguments.positional[parameterIndex] |
- : signature.optionalParametersAreNamed |
- ? arguments.named[parameter.name] |
- : parameterIndex < arguments.positional.length |
- ? arguments.positional[parameterIndex] |
- : null; |
- if (type == null) type = getDefaultTypeOfParameter(parameter); |
- TypeInformation info = types.getInferredTypeOf(parameter); |
- if (remove) { |
- info.removeAssignment(type); |
- } else { |
- info.addAssignment(type); |
- } |
- parameterIndex++; |
- if (addToQueue) workQueue.add(info); |
- }); |
- } |
- } |
- |
- /** |
- * Sets the type of a parameter's default value to [type]. If the global |
- * mapping in [defaultTypeOfParameter] already contains a type, it must be |
- * a [PlaceholderTypeInformation], which will be replaced. All its uses are |
- * updated. |
- */ |
- void setDefaultTypeOfParameter(ParameterElement parameter, |
- TypeInformation type) { |
- assert(parameter.functionDeclaration.isImplementation); |
- TypeInformation existing = defaultTypeOfParameter[parameter]; |
- defaultTypeOfParameter[parameter] = type; |
- TypeInformation info = types.getInferredTypeOf(parameter); |
- if (existing != null && existing is PlaceholderTypeInformation) { |
- // Replace references to [existing] to use [type] instead. |
- if (parameter.functionDeclaration.isInstanceMember) { |
- ParameterAssignments assignments = info.assignments; |
- assignments.replace(existing, type); |
- type.addUser(info); |
- } else { |
- List<TypeInformation> assignments = info.assignments; |
- for (int i = 0; i < assignments.length; i++) { |
- if (assignments[i] == existing) { |
- assignments[i] = type; |
- type.addUser(info); |
- } |
- } |
- } |
- } else { |
- assert(existing == null); |
- } |
- } |
- |
- /** |
- * Returns the [TypeInformation] node for the default value of a parameter. |
- * If this is queried before it is set by [setDefaultTypeOfParameter], a |
- * [PlaceholderTypeInformation] is returned, which will later be replaced |
- * by the actual node when [setDefaultTypeOfParameter] is called. |
- * |
- * Invariant: After graph construction, no [PlaceholderTypeInformation] nodes |
- * should be present and a default type for each parameter should |
- * exist. |
- */ |
- TypeInformation getDefaultTypeOfParameter(Element parameter) { |
- return defaultTypeOfParameter.putIfAbsent(parameter, () { |
- return new PlaceholderTypeInformation(types.currentMember); |
- }); |
- } |
- |
- /** |
- * Helper to inspect the [TypeGraphInferrer]'s state. To be removed by |
- * TODO(johnniwinther) once synthetic parameters get their own default |
- * values. |
- */ |
- bool hasAlreadyComputedTypeOfParameterDefault(Element parameter) { |
- TypeInformation seen = defaultTypeOfParameter[parameter]; |
- return (seen != null && seen is! PlaceholderTypeInformation); |
- } |
- |
- TypeInformation typeOfElement(Element element) { |
- if (element is FunctionElement) return types.functionType; |
- return types.getInferredTypeOf(element); |
- } |
- |
- TypeInformation returnTypeOfElement(Element element) { |
- if (element is !FunctionElement) return types.dynamicType; |
- return types.getInferredTypeOf(element); |
- } |
- |
- void recordTypeOfFinalField(Spannable node, |
- Element analyzed, |
- Element element, |
- TypeInformation type) { |
- types.getInferredTypeOf(element).addAssignment(type); |
- } |
- |
- void recordTypeOfNonFinalField(Spannable node, |
- Element element, |
- TypeInformation type) { |
- types.getInferredTypeOf(element).addAssignment(type); |
- } |
- |
- void recordType(Element element, TypeInformation type) { |
- types.getInferredTypeOf(element).addAssignment(type); |
- } |
- |
- void recordReturnType(Element element, TypeInformation type) { |
- TypeInformation info = types.getInferredTypeOf(element); |
- if (element.name == '==') { |
- // Even if x.== doesn't return a bool, 'x == null' evaluates to 'false'. |
- info.addAssignment(types.boolType); |
- } |
- // TODO(ngeoffray): Clean up. We do these checks because |
- // [SimpleTypesInferrer] deals with two different inferrers. |
- if (type == null) return; |
- if (info.assignments.isEmpty) info.addAssignment(type); |
- } |
- |
- TypeInformation addReturnTypeFor(Element element, |
- TypeInformation unused, |
- TypeInformation newType) { |
- TypeInformation type = types.getInferredTypeOf(element); |
- // TODO(ngeoffray): Clean up. We do this check because |
- // [SimpleTypesInferrer] deals with two different inferrers. |
- if (element.isGenerativeConstructor) return type; |
- type.addAssignment(newType); |
- return type; |
- } |
- |
- TypeInformation registerCalledElement(Spannable node, |
- Selector selector, |
- Element caller, |
- Element callee, |
- ArgumentsTypes arguments, |
- SideEffects sideEffects, |
- bool inLoop) { |
- CallSiteTypeInformation info = new StaticCallSiteTypeInformation( |
- types.currentMember, node, caller, callee, selector, arguments, |
- inLoop); |
- info.addToGraph(this); |
- allocatedCalls.add(info); |
- updateSideEffects(sideEffects, selector, callee); |
- return info; |
- } |
- |
- TypeInformation registerCalledSelector(ast.Node node, |
- Selector selector, |
- TypeInformation receiverType, |
- Element caller, |
- ArgumentsTypes arguments, |
- SideEffects sideEffects, |
- bool inLoop) { |
- if (selector.isClosureCall) { |
- return registerCalledClosure( |
- node, selector, receiverType, caller, arguments, sideEffects, inLoop); |
- } |
- |
- compiler.world.allFunctions.filter(selector).forEach((callee) { |
- updateSideEffects(sideEffects, selector, callee); |
- }); |
- |
- CallSiteTypeInformation info = new DynamicCallSiteTypeInformation( |
- types.currentMember, node, caller, selector, receiverType, arguments, |
- inLoop); |
- |
- info.addToGraph(this); |
- allocatedCalls.add(info); |
- return info; |
- } |
- |
- TypeInformation registerCalledClosure(ast.Node node, |
- Selector selector, |
- TypeInformation closure, |
- Element caller, |
- ArgumentsTypes arguments, |
- SideEffects sideEffects, |
- bool inLoop) { |
- sideEffects.setDependsOnSomething(); |
- sideEffects.setAllSideEffects(); |
- CallSiteTypeInformation info = new ClosureCallSiteTypeInformation( |
- types.currentMember, node, caller, selector, closure, arguments, |
- inLoop); |
- info.addToGraph(this); |
- allocatedCalls.add(info); |
- return info; |
- } |
- |
- // Sorts the resolved elements by size. We do this for this inferrer |
- // to get the same results for [ListTracer] compared to the |
- // [SimpleTypesInferrer]. |
- Iterable<Element> sortResolvedElements() { |
- int max = 0; |
- Map<int, Setlet<Element>> methodSizes = new Map<int, Setlet<Element>>(); |
- compiler.enqueuer.resolution.resolvedElements.forEach((AstElement element) { |
- // TODO(ngeoffray): Not sure why the resolver would put a null |
- // mapping. |
- if (!compiler.enqueuer.resolution.hasBeenResolved(element)) return; |
- TreeElementMapping mapping = element.resolvedAst.elements; |
- element = element.implementation; |
- if (element.impliesType) return; |
- assert(invariant(element, |
- element.isField || |
- element.isFunction || |
- element.isGenerativeConstructor || |
- element.isGetter || |
- element.isSetter, |
- message: 'Unexpected element kind: ${element.kind}')); |
- if (element.isAbstract) return; |
- // Put the other operators in buckets by length, later to be added in |
- // length order. |
- int length = mapping.getSelectorCount(); |
- max = length > max ? length : max; |
- Setlet<Element> set = methodSizes.putIfAbsent( |
- length, () => new Setlet<Element>()); |
- set.add(element); |
- }); |
- |
- List<Element> result = <Element>[]; |
- for (int i = 0; i <= max; i++) { |
- Setlet<Element> set = methodSizes[i]; |
- if (set != null) result.addAll(set); |
- } |
- return result; |
- } |
- |
- void clear() { |
- allocatedCalls.clear(); |
- defaultTypeOfParameter.clear(); |
- types.typeInformations.values.forEach((info) => info.clear()); |
- types.allocatedTypes.clear(); |
- types.concreteTypes.clear(); |
- types.allocatedClosures.clear(); |
- analyzedElements.clear(); |
- generativeConstructorsExposingThis.clear(); |
- } |
- |
- Iterable<Element> getCallersOf(Element element) { |
- if (compiler.disableTypeInference) { |
- throw new UnsupportedError( |
- "Cannot query the type inferrer when type inference is disabled."); |
- } |
- MemberTypeInformation info = types.getInferredTypeOf(element); |
- return info.callers; |
- } |
- |
- /** |
- * Returns the type of [element] when being called with [selector]. |
- */ |
- TypeInformation typeOfElementWithSelector(Element element, |
- Selector selector) { |
- if (element.name == Compiler.NO_SUCH_METHOD && |
- selector.name != element.name) { |
- // An invocation can resolve to a [noSuchMethod], in which case |
- // we get the return type of [noSuchMethod]. |
- return returnTypeOfElement(element); |
- } else if (selector.isGetter) { |
- if (element.isFunction) { |
- // [functionType] is null if the inferrer did not run. |
- return types.functionType == null |
- ? types.dynamicType |
- : types.functionType; |
- } else if (element.isField) { |
- return typeOfElement(element); |
- } else if (Elements.isUnresolved(element)) { |
- return types.dynamicType; |
- } else { |
- assert(element.isGetter); |
- return returnTypeOfElement(element); |
- } |
- } else if (element.isGetter || element.isField) { |
- assert(selector.isCall || selector.isSetter); |
- return types.dynamicType; |
- } else { |
- return returnTypeOfElement(element); |
- } |
- } |
- |
- void recordCapturedLocalRead(Local local) {} |
- |
- void recordLocalUpdate(Local local, TypeInformation type) {} |
-} |
- |
-class TypeGraphInferrer implements TypesInferrer { |
- TypeGraphInferrerEngine inferrer; |
- final Compiler compiler; |
- TypeGraphInferrer(Compiler this.compiler); |
- |
- String get name => 'Graph inferrer'; |
- |
- void analyzeMain(Element main) { |
- inferrer = new TypeGraphInferrerEngine(compiler, main); |
- inferrer.runOverAllElements(); |
- } |
- |
- TypeMask getReturnTypeOfElement(Element element) { |
- if (compiler.disableTypeInference) return compiler.typesTask.dynamicType; |
- // Currently, closure calls return dynamic. |
- if (element is! FunctionElement) return compiler.typesTask.dynamicType; |
- return inferrer.types.getInferredTypeOf(element).type; |
- } |
- |
- TypeMask getTypeOfElement(Element element) { |
- if (compiler.disableTypeInference) return compiler.typesTask.dynamicType; |
- // The inferrer stores the return type for a function, so we have to |
- // be careful to not return it here. |
- if (element is FunctionElement) return compiler.typesTask.functionType; |
- return inferrer.types.getInferredTypeOf(element).type; |
- } |
- |
- TypeMask getTypeOfNode(Element owner, ast.Node node) { |
- if (compiler.disableTypeInference) return compiler.typesTask.dynamicType; |
- return inferrer.types.allocatedLists[node].type; |
- } |
- |
- bool isFixedArrayCheckedForGrowable(ast.Node node) { |
- if (compiler.disableTypeInference) return true; |
- ListTypeInformation info = inferrer.types.allocatedLists[node]; |
- return info.checksGrowable; |
- } |
- |
- TypeMask getTypeOfSelector(Selector selector) { |
- if (compiler.disableTypeInference) return compiler.typesTask.dynamicType; |
- // Bailout for closure calls. We're not tracking types of |
- // closures. |
- if (selector.isClosureCall) return compiler.typesTask.dynamicType; |
- if (selector.isSetter || selector.isIndexSet) { |
- return compiler.typesTask.dynamicType; |
- } |
- if (inferrer.returnsListElementType(selector)) { |
- ContainerTypeMask mask = selector.mask; |
- TypeMask elementType = mask.elementType; |
- return elementType == null ? compiler.typesTask.dynamicType : elementType; |
- } |
- if (inferrer.returnsMapValueType(selector)) { |
- MapTypeMask mask = selector.mask; |
- TypeMask valueType = mask.valueType; |
- return valueType == null ? compiler.typesTask.dynamicType |
- : valueType; |
- } |
- |
- TypeMask result = const TypeMask.nonNullEmpty(); |
- Iterable<Element> elements = compiler.world.allFunctions.filter(selector); |
- for (Element element in elements) { |
- TypeMask type = |
- inferrer.typeOfElementWithSelector(element, selector).type; |
- result = result.union(type, compiler.world); |
- } |
- return result; |
- } |
- |
- Iterable<Element> getCallersOf(Element element) { |
- if (compiler.disableTypeInference) { |
- throw new UnsupportedError( |
- "Cannot query the type inferrer when type inference is disabled."); |
- } |
- return inferrer.getCallersOf(element); |
- } |
- |
- bool isCalledOnce(Element element) { |
- if (compiler.disableTypeInference) return false; |
- MemberTypeInformation info = inferrer.types.getInferredTypeOf(element); |
- return info.isCalledOnce(); |
- } |
- |
- void clear() { |
- inferrer.clear(); |
- } |
-} |