| // Copyright (c) 2017, 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. | |
| import 'package:collection/collection.dart'; | |
| import 'package:kernel/ast.dart' as ir; | |
| import 'package:kernel/type_environment.dart' as ir; | |
| import '../../compiler_api.dart' as api; | |
| import '../closure.dart'; | |
| import '../common.dart'; | |
| import '../common/elements.dart'; | |
| import '../common/metrics.dart'; | |
| import '../common/names.dart'; | |
| import '../compiler.dart'; | |
| import '../constants/values.dart'; | |
| import '../elements/entities.dart'; | |
| import '../elements/names.dart'; | |
| import '../elements/types.dart'; | |
| import '../js_backend/inferred_data.dart'; | |
| import '../js_backend/no_such_method_registry.dart'; | |
| import '../js_model/element_map.dart'; | |
| import '../js_model/elements.dart'; | |
| import '../js_model/js_world.dart'; | |
| import '../js_model/locals.dart'; | |
| import '../native/behavior.dart'; | |
| import '../options.dart'; | |
| import '../serialization/serialization.dart'; | |
| import '../universe/call_structure.dart'; | |
| import '../universe/member_hierarchy.dart'; | |
| import '../universe/selector.dart'; | |
| import '../universe/side_effects.dart'; | |
| import 'abstract_value_domain.dart'; | |
| import 'builder.dart'; | |
| import 'closure_tracer.dart'; | |
| import 'debug.dart' as debug; | |
| import 'list_tracer.dart'; | |
| import 'locals_handler.dart'; | |
| import 'map_tracer.dart'; | |
| import 'record_tracer.dart'; | |
| import 'set_tracer.dart'; | |
| import 'type_graph_dump.dart'; | |
| import 'type_graph_nodes.dart'; | |
| import 'type_system.dart'; | |
| import 'types.dart'; | |
| import 'work_queue.dart'; | |
| /// 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 InferrerEngine { | |
| /// A set of selector names that [List] implements, that we know return their | |
| /// element type. | |
| final Set<Selector> returnsListElementTypeSet = <Selector>{ | |
| Selector.getter(const PublicName('first')), | |
| Selector.getter(const PublicName('last')), | |
| Selector.getter(const PublicName('single')), | |
| Selector.call(const PublicName('singleWhere'), CallStructure.oneArg), | |
| Selector.call(const PublicName('elementAt'), CallStructure.oneArg), | |
| Selector.index(), | |
| Selector.call(const PublicName('removeAt'), CallStructure.oneArg), | |
| Selector.call(const PublicName('removeLast'), CallStructure.noArgs), | |
| }; | |
| /// The [JClosedWorld] on which inference reasoning is based. | |
| final JClosedWorld closedWorld; | |
| final TypeSystem types; | |
| final Map<ir.TreeNode, TypeInformation> concreteTypes = {}; | |
| final GlobalLocalsMap globalLocalsMap; | |
| final InferredDataBuilder inferredDataBuilder; | |
| final FunctionEntity mainElement; | |
| final Map<Local, TypeInformation> _defaultTypeOfParameter = {}; | |
| final WorkQueue _workQueue = WorkQueue(); | |
| late final InferrerEngineMetrics metrics = InferrerEngineMetrics( | |
| closedWorld.abstractValueDomain.metrics, | |
| ); | |
| final Set<MemberEntity> _analyzedElements = {}; | |
| /// 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. | |
| /// TODO(natebiggs): This value is needed right now because some types | |
| /// do not converge. See https://github.com/dart-lang/sdk/issues/50626 | |
| /// | |
| /// Note: Due to the encoding to track refine count for a given node this | |
| /// value must be less than 2^(64-N) where N is the number of values in the | |
| /// TypeInformation flags enum. | |
| static const int _maxChangeCount = 1000; | |
| int _overallRefineCount = 0; | |
| int _addedInGraph = 0; | |
| final CompilerOptions _options; | |
| final Progress _progress; | |
| final DiagnosticReporter _reporter; | |
| final api.CompilerOutput _compilerOutput; | |
| final Set<ConstructorEntity> _generativeConstructorsExposingThis = | |
| <ConstructorEntity>{}; | |
| /// Data computed internally within elements, like the type-mask of a send a | |
| /// list allocation, or a for-in loop. | |
| final Map<MemberEntity, KernelGlobalTypeInferenceElementData> _memberData = | |
| <MemberEntity, KernelGlobalTypeInferenceElementData>{}; | |
| ElementEnvironment get _elementEnvironment => closedWorld.elementEnvironment; | |
| AbstractValueDomain get abstractValueDomain => | |
| closedWorld.abstractValueDomain; | |
| CommonElements get commonElements => closedWorld.commonElements; | |
| NoSuchMethodData get noSuchMethodData => closedWorld.noSuchMethodData; | |
| final MemberHierarchyBuilder memberHierarchyBuilder; | |
| Set<MemberEntity>? _initializedVirtualMembers = {}; | |
| InferrerEngine( | |
| this._options, | |
| this._progress, | |
| this._reporter, | |
| this._compilerOutput, | |
| this.closedWorld, | |
| this.mainElement, | |
| this.globalLocalsMap, | |
| this.inferredDataBuilder, | |
| ) : types = TypeSystem( | |
| closedWorld, | |
| KernelTypeSystemStrategy(closedWorld, globalLocalsMap), | |
| ), | |
| memberHierarchyBuilder = MemberHierarchyBuilder(closedWorld), | |
| // Ensure `_MAX_CHANGE_COUNT` conforms to TypeInformation flag encoding. | |
| assert(_maxChangeCount.bitLength < 64 - TypeInformation.numTypeInfoFlags); | |
| /// Applies [f] to all elements in the universe that match [selector] and | |
| /// [mask]. If [f] returns false, aborts the iteration. | |
| void forEachElementMatching( | |
| Selector selector, | |
| AbstractValue? mask, | |
| bool Function(MemberEntity element) f, | |
| ) { | |
| final targets = memberHierarchyBuilder.rootsForCall(mask, selector); | |
| for (final target in targets) { | |
| memberHierarchyBuilder.forEachTargetMember( | |
| target, | |
| (member) => member.isAbstract || f(member), | |
| ); | |
| } | |
| } | |
| // TODO(johnniwinther): Make this private again. | |
| KernelGlobalTypeInferenceElementData dataOfMember(MemberEntity element) => | |
| _memberData[element] ??= KernelGlobalTypeInferenceElementData(); | |
| /// Update [sideEffects] with the side effects of [callee] being | |
| /// called with [selector]. | |
| void _updateSideEffects( | |
| SideEffectsBuilder sideEffectsBuilder, | |
| Selector? selector, | |
| MemberEntity callee, | |
| ) { | |
| if (callee is FieldEntity) { | |
| if (callee.isInstanceMember) { | |
| if (selector!.isSetter) { | |
| sideEffectsBuilder.setChangesInstanceProperty(); | |
| } else if (selector.isGetter) { | |
| sideEffectsBuilder.setDependsOnInstancePropertyStore(); | |
| } else { | |
| sideEffectsBuilder.setAllSideEffectsAndDependsOnSomething(); | |
| } | |
| } else { | |
| if (selector!.isSetter) { | |
| sideEffectsBuilder.setChangesStaticProperty(); | |
| } else if (selector.isGetter) { | |
| sideEffectsBuilder.setDependsOnStaticPropertyStore(); | |
| } else { | |
| sideEffectsBuilder.setAllSideEffectsAndDependsOnSomething(); | |
| } | |
| } | |
| } else if (callee.isGetter && !selector!.isGetter) { | |
| sideEffectsBuilder.setAllSideEffectsAndDependsOnSomething(); | |
| } else { | |
| sideEffectsBuilder.addInput( | |
| inferredDataBuilder.getSideEffectsBuilder(callee as FunctionEntity), | |
| ); | |
| } | |
| } | |
| /// Returns the type for [nativeBehavior]. See documentation on | |
| /// [NativeBehavior]. | |
| TypeInformation typeOfNativeBehavior(NativeBehavior? nativeBehavior) { | |
| if (nativeBehavior == null) return types.dynamicType; | |
| List<Object> typesReturned = nativeBehavior.typesReturned; | |
| if (typesReturned.isEmpty) return types.dynamicType; | |
| TypeInformation? returnType; | |
| for (var type in typesReturned) { | |
| TypeInformation? mappedType; | |
| if (type == SpecialType.jsObject) { | |
| mappedType = types.nonNullExact(commonElements.objectClass); | |
| } else if (type == commonElements.stringType) { | |
| mappedType = types.stringType; | |
| } else if (type == commonElements.intType) { | |
| mappedType = types.intType; | |
| } else if (type == commonElements.numType || | |
| type == commonElements.doubleType) { | |
| // Note: the backend double class is specifically for non-integer | |
| // doubles, and a native behavior returning 'double' does not guarantee | |
| // a non-integer return type, so we return the number type for those. | |
| mappedType = types.numType; | |
| } else if (type == commonElements.boolType) { | |
| mappedType = types.boolType; | |
| } else if (type == commonElements.nullType) { | |
| mappedType = types.nullType; | |
| } else if (type is VoidType) { | |
| mappedType = types.nullType; | |
| } else if (type is DynamicType) { | |
| return types.dynamicType; | |
| } else if (type is InterfaceType) { | |
| mappedType = types.nonNullSubtype(type.element); | |
| } else { | |
| mappedType = types.dynamicType; | |
| } | |
| returnType = types.computeLUB(returnType, mappedType); | |
| if (returnType == types.dynamicType) { | |
| break; | |
| } | |
| } | |
| return returnType!; | |
| } | |
| void updateSelectorInMember( | |
| MemberEntity owner, | |
| CallType callType, | |
| ir.TreeNode node, | |
| Selector? selector, | |
| AbstractValue? mask, | |
| ) { | |
| final data = dataOfMember(owner); | |
| assert(validCallType(callType, node)); | |
| switch (callType) { | |
| case CallType.access: | |
| data.setReceiverTypeMask(node, mask); | |
| break; | |
| case CallType.forIn: | |
| if (selector == Selectors.iterator) { | |
| data.setIteratorTypeMask(node as ir.ForInStatement, mask); | |
| } else if (selector == Selectors.current) { | |
| data.setCurrentTypeMask(node as ir.ForInStatement, mask); | |
| } else { | |
| assert(selector == Selectors.moveNext); | |
| data.setMoveNextTypeMask(node as ir.ForInStatement, mask); | |
| } | |
| break; | |
| } | |
| } | |
| bool checkIfExposesThis(ConstructorEntity element) { | |
| return _generativeConstructorsExposingThis.contains(element); | |
| } | |
| void recordExposesThis(ConstructorEntity element, bool exposesThis) { | |
| if (exposesThis) { | |
| _generativeConstructorsExposingThis.add(element); | |
| } | |
| } | |
| bool returnsListElementType(Selector selector, AbstractValue mask) { | |
| return abstractValueDomain.isContainer(mask) && | |
| returnsListElementTypeSet.contains(selector); | |
| } | |
| bool returnsMapValueType(Selector selector, AbstractValue mask) { | |
| return abstractValueDomain.isMap(mask) && selector.isIndex; | |
| } | |
| void analyzeListAndEnqueue(ListTypeInformation info) { | |
| if (info.analyzed) return; | |
| info.analyzed = true; | |
| ListTracerVisitor tracer = ListTracerVisitor(info, this); | |
| bool succeeded = tracer.run(); | |
| if (!succeeded) return; | |
| info.bailedOut = false; | |
| info.elementType.inferred = true; | |
| tracer.inputs.forEach(info.elementType.addInput); | |
| // Enqueue the list for later refinement | |
| _workQueue.add(info); | |
| _workQueue.add(info.elementType); | |
| } | |
| void analyzeSetAndEnqueue(SetTypeInformation info) { | |
| if (info.analyzed) return; | |
| info.analyzed = true; | |
| SetTracerVisitor tracer = SetTracerVisitor(info, this); | |
| bool succeeded = tracer.run(); | |
| if (!succeeded) return; | |
| info.bailedOut = false; | |
| info.elementType.inferred = true; | |
| tracer.inputs.forEach(info.elementType.addInput); | |
| // Enqueue the set for later refinement. | |
| _workQueue.add(info); | |
| _workQueue.add(info.elementType); | |
| } | |
| void analyzeMapAndEnqueue(MapTypeInformation info) { | |
| if (info.analyzed) return; | |
| info.analyzed = true; | |
| MapTracerVisitor tracer = MapTracerVisitor(info, this); | |
| bool succeeded = tracer.run(); | |
| if (!succeeded) return; | |
| info.bailedOut = false; | |
| for (int i = 0; i < tracer.keyInputs.length; ++i) { | |
| final newType = info.addEntryInput( | |
| abstractValueDomain, | |
| tracer.keyInputs[i], | |
| tracer.valueInputs[i], | |
| ); | |
| if (newType != null) _workQueue.add(newType); | |
| } | |
| for (final map in tracer.mapInputs) { | |
| _workQueue.addAll(info.addMapInput(abstractValueDomain, map)); | |
| } | |
| info.markAsInferred(); | |
| _workQueue.add(info.keyType); | |
| _workQueue.add(info.valueType); | |
| _workQueue.addAll(info.typeInfoMap.values); | |
| _workQueue.add(info); | |
| } | |
| void analyzeRecordAndEnqueue(RecordTypeInformation info) { | |
| if (info.analyzed) return; | |
| info.analyzed = true; | |
| RecordTracerVisitor tracer = RecordTracerVisitor(info, this); | |
| bool succeeded = tracer.run(); | |
| if (!succeeded) return; | |
| info.bailedOut = false; | |
| _workQueue.add(info); | |
| } | |
| void runOverAllElements() { | |
| metrics.time.measure(_runOverAllElements); | |
| } | |
| void _runOverAllElements() { | |
| _initMemberHierarchy(); | |
| metrics.analyze.measure(_analyzeAllElements); | |
| final dump = debug.printGraph ? TypeGraphDump(_compilerOutput, this) : null; | |
| dump?.beforeAnalysis(); | |
| _buildWorkQueue(); | |
| metrics.refine1.measure(_refine); | |
| // Process the refined targets of calls labeling closureized members. We do | |
| // not need to mark targets as called in this pass because we don't use this | |
| // information in global inference. | |
| _processCalledTargets(shouldMarkCalled: false); | |
| metrics.trace.measure(() { | |
| // Try to infer element types of lists and compute their escape information. | |
| for (var info in types.allocatedLists.values) { | |
| analyzeListAndEnqueue(info); | |
| } | |
| // Try to infer element types of sets and compute their escape information. | |
| for (var info in types.allocatedSets.values) { | |
| analyzeSetAndEnqueue(info); | |
| } | |
| // Try to infer the key and value types for maps and compute the values' | |
| // escape information. | |
| for (var info in types.allocatedMaps.values) { | |
| analyzeMapAndEnqueue(info); | |
| } | |
| Set<FunctionEntity> bailedOutOn = <FunctionEntity>{}; | |
| // Trace closures to potentially infer argument types. | |
| for (var info in types.allocatedClosures) { | |
| void trace( | |
| Iterable<FunctionEntity> elements, | |
| ClosureTracerVisitor tracer, | |
| ) { | |
| tracer.run(); | |
| if (!tracer.continueAnalyzing) { | |
| for (var element in elements) { | |
| inferredDataBuilder.registerMightBePassedToApply(element); | |
| if (debug.verbose) { | |
| print("traced closure $element as ${true} (bail)"); | |
| } | |
| types.strategy.forEachParameter(element, (Local parameter) { | |
| types | |
| .getInferredTypeOfParameter(parameter) | |
| .giveUp(this, clearInputs: false); | |
| }); | |
| } | |
| bailedOutOn.addAll(elements); | |
| return; | |
| } | |
| elements.where((e) => !bailedOutOn.contains(e)).forEach(( | |
| FunctionEntity element, | |
| ) { | |
| types.strategy.forEachParameter(element, (Local parameter) { | |
| ParameterTypeInformation info = types.getInferredTypeOfParameter( | |
| parameter, | |
| ); | |
| info.maybeResume(); | |
| _workQueue.add(info); | |
| }); | |
| if (tracer.tracedType.mightBePassedToFunctionApply) { | |
| inferredDataBuilder.registerMightBePassedToApply(element); | |
| } | |
| if (debug.verbose) { | |
| print( | |
| "traced closure $element as " | |
| "${inferredDataBuilder.getCurrentlyKnownMightBePassedToApply(element)}", | |
| ); | |
| } | |
| }); | |
| } | |
| if (info is ClosureTypeInformation) { | |
| Iterable<FunctionEntity> elements = [info.closure]; | |
| trace(elements, ClosureTracerVisitor(elements, info, this)); | |
| } else if (info is CallSiteTypeInformation) { | |
| final selector = info.selector; | |
| List<FunctionEntity> elements; | |
| if (info is StaticCallSiteTypeInformation) { | |
| if (selector != null && selector.isCall) { | |
| // This is a constructor call to a class with a call method. So we | |
| // need to trace the call method here. | |
| final calledElement = info.calledElement; | |
| assert( | |
| calledElement is ConstructorEntity && | |
| calledElement.isGenerativeConstructor, | |
| ); | |
| final cls = calledElement.enclosingClass!; | |
| final callMethod = _lookupCallMethod(cls)!; | |
| elements = [callMethod]; | |
| } else { | |
| elements = [info.calledElement as FunctionEntity]; | |
| } | |
| } else if (info is DynamicCallSiteTypeInformation) { | |
| // 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. | |
| elements = []; | |
| bool processTarget(MemberEntity entity) { | |
| if (entity.isFunction && !entity.isAbstract) { | |
| elements.add(entity as FunctionEntity); | |
| } | |
| return true; | |
| } | |
| info.forEachConcreteTarget(memberHierarchyBuilder, processTarget); | |
| } else { | |
| elements = const []; | |
| } | |
| trace(elements, ClosureTracerVisitor(elements, info, this)); | |
| } else if (info is MemberTypeInformation) { | |
| final member = info.member as FunctionEntity; | |
| trace([ | |
| member, | |
| ], StaticTearOffClosureTracerVisitor(member, info, this)); | |
| } else if (info is ParameterTypeInformation) { | |
| failedAt( | |
| noLocationSpannable, | |
| 'Unexpected closure allocation info $info', | |
| ); | |
| } | |
| } | |
| }); | |
| dump?.beforeTracing(); | |
| // 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 = <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); | |
| metrics.refine2.measure(_refine); | |
| // Process the refined targets of calls labeling closureized members and | |
| // marking targeted members as called. | |
| _processCalledTargets(shouldMarkCalled: true); | |
| if (debug.printSummary) { | |
| void printSorted(Iterable<String> toSort) { | |
| print(toSort.sorted((a, b) => a.compareTo(b)).join('\n')); | |
| } | |
| printSorted( | |
| types.allocatedLists.values.map((info) { | |
| return '${info.type} ' | |
| 'for ${abstractValueDomain.getAllocationNode(info.originalType)} ' | |
| 'at ${abstractValueDomain.getAllocationElement(info.originalType)}' | |
| 'after ${info.refineCount}'; | |
| }), | |
| ); | |
| printSorted( | |
| types.allocatedSets.values.map((info) { | |
| return ('${info.type} ' | |
| 'for ${abstractValueDomain.getAllocationNode(info.originalType)} ' | |
| 'at ${abstractValueDomain.getAllocationElement(info.originalType)} ' | |
| 'after ${info.refineCount}'); | |
| }), | |
| ); | |
| printSorted( | |
| types.allocatedMaps.values.map((info) { | |
| return '${info.type} ' | |
| 'for ${abstractValueDomain.getAllocationNode(info.originalType)} ' | |
| 'at ${abstractValueDomain.getAllocationElement(info.originalType)}' | |
| 'after ${info.refineCount}'; | |
| }), | |
| ); | |
| printSorted( | |
| types.allocatedClosures.map((TypeInformation info) { | |
| if (info is ElementTypeInformation) { | |
| return '${info.getInferredSignature(types)} for ' | |
| '${info.debugName}'; | |
| } else if (info is ClosureTypeInformation) { | |
| return '${info.getInferredSignature(types)} for ' | |
| '${info.debugName}'; | |
| } else if (info is DynamicCallSiteTypeInformation) { | |
| var str = ''; | |
| if (info.hasClosureCallTargets) { | |
| str += '<Closure.call>'; | |
| } | |
| info.forEachConcreteTarget(memberHierarchyBuilder, (member) { | |
| if (member is FunctionEntity) { | |
| str += | |
| '${types.getInferredSignatureOfMethod(member)} ' | |
| 'for $member'; | |
| } else { | |
| str += | |
| '${types.getInferredTypeOfMember(member).type} ' | |
| 'for $member'; | |
| } | |
| return true; | |
| }); | |
| return str; | |
| } else if (info is StaticCallSiteTypeInformation) { | |
| final cls = info.calledElement.enclosingClass!; | |
| final callMethod = _lookupCallMethod(cls)!; | |
| return '${types.getInferredSignatureOfMethod(callMethod)} for $cls'; | |
| } else { | |
| return '${info.type} for some unknown kind of closure'; | |
| } | |
| }), | |
| ); | |
| printSorted( | |
| _analyzedElements.map((MemberEntity elem) { | |
| TypeInformation type = types.getInferredTypeOfMember(elem); | |
| return '$elem :: $type from ${type.inputs} '; | |
| }), | |
| ); | |
| } | |
| dump?.afterAnalysis(); | |
| metrics.overallRefineCount.add(_overallRefineCount); | |
| _reporter.log('Inferred $_overallRefineCount types.'); | |
| _processLoopInformation(); | |
| } | |
| /// Call [analyze] for all live members. | |
| void _analyzeAllElements() { | |
| _progress.startPhase(); | |
| final toProcess = { | |
| ...closedWorld.processedMembers, | |
| ...closedWorld.liveAbstractInstanceMembers, | |
| }; | |
| for (var member in toProcess) { | |
| _progress.showProgress( | |
| 'Added ', | |
| _addedInGraph, | |
| ' elements in inferencing graph.', | |
| ); | |
| // This also forces the creation of the [ElementTypeInformation] to ensure | |
| // it is in the graph. | |
| types.withMember(member, () => analyze(member)); | |
| } | |
| metrics.elementsInGraph.add(_addedInGraph); | |
| _reporter.log('Added $_addedInGraph elements in inferencing graph.'); | |
| metrics.allTypesCount.add(types.allTypes.length); | |
| } | |
| void _initMemberHierarchy() { | |
| metrics.memberHierarchy.measure( | |
| () => memberHierarchyBuilder.init(_initializeOverrideEdges), | |
| ); | |
| // Once the hierarchy is set up we will not need to initialize new | |
| // virtual members and can clear the initialization cache. | |
| _initializedVirtualMembers = null; | |
| } | |
| /// Returns the body node for [member]. | |
| ir.Node? _computeMemberBody(MemberEntity member) { | |
| MemberDefinition definition = closedWorld.elementMap.getMemberDefinition( | |
| member, | |
| ); | |
| switch (definition.kind) { | |
| case MemberKind.regular: | |
| final node = definition.node; | |
| if (node is ir.Field) { | |
| return getFieldInitializer( | |
| closedWorld.elementMap, | |
| member as FieldEntity, | |
| ); | |
| } else if (node is ir.Procedure) { | |
| return node.function; | |
| } | |
| break; | |
| case MemberKind.constructor: | |
| return definition.node; | |
| case MemberKind.constructorBody: | |
| final node = definition.node; | |
| if (node is ir.Constructor) { | |
| return node.function; | |
| } else if (node is ir.Procedure) { | |
| return node.function; | |
| } | |
| break; | |
| case MemberKind.closureCall: | |
| final node = definition.node as ir.LocalFunction; | |
| return node.function; | |
| case MemberKind.closureField: | |
| case MemberKind.signature: | |
| case MemberKind.generatorBody: | |
| case MemberKind.recordGetter: | |
| case MemberKind.parameterStub: | |
| break; | |
| } | |
| failedAt(member, 'Unexpected member definition: $definition.'); | |
| } | |
| /// Returns the `call` method on [cls] or the `noSuchMethod` if [cls] doesn't | |
| /// implement `call`. | |
| FunctionEntity? _lookupCallMethod(ClassEntity cls) { | |
| var function = _elementEnvironment.lookupClassMember( | |
| cls, | |
| Names.call, | |
| ) as FunctionEntity?; | |
| if (function == null || function.isAbstract) { | |
| function = _elementEnvironment.lookupClassMember( | |
| cls, | |
| Names.noSuchMethod_, | |
| ) as FunctionEntity?; | |
| } | |
| return function; | |
| } | |
| void analyze(MemberEntity element) { | |
| if (_analyzedElements.contains(element)) return; | |
| _analyzedElements.add(element); | |
| final body = _computeMemberBody(element); | |
| TypeInformation? type; | |
| _reporter.withCurrentElement(element, () { | |
| type = _computeMemberTypeInformation(element, body); | |
| }); | |
| _addedInGraph++; | |
| if (element is FieldEntity) { | |
| final field = element; | |
| if (!field.isAssignable) { | |
| // If [element] is final and has an initializer, we record | |
| // the inferred type. | |
| if (body != null) { | |
| if (type is! ListTypeInformation && type is! MapTypeInformation) { | |
| // For non-container types, the constant handler does | |
| // constant folding that could give more precise results. | |
| final value = _getFieldConstant(field as JField); | |
| if (value != null) { | |
| if (value is FunctionConstantValue) { | |
| type = types.allocateClosure(value.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. | |
| AbstractValue refinedType = abstractValueDomain | |
| .computeAbstractValueForConstant(value); | |
| type = NarrowTypeInformation( | |
| abstractValueDomain, | |
| type!, | |
| refinedType, | |
| ); | |
| types.allocatedTypes.add(type!); | |
| } | |
| } | |
| } | |
| recordTypeOfField(field, type!); | |
| } else if (!element.isInstanceMember) { | |
| recordTypeOfField(field, types.nullType); | |
| } | |
| } else if (body == null) { | |
| // Only update types of static fields if there is no | |
| // assignment. Instance fields are dealt with in the constructor. | |
| if (element.isStatic || element.isTopLevel) { | |
| recordTypeOfField(field, type!); | |
| } | |
| } else { | |
| recordTypeOfField(field, type!); | |
| } | |
| if ((element.isStatic || element.isTopLevel) && | |
| body != null && | |
| !element.isConst) { | |
| if (_isFieldInitializerPotentiallyNull(element, body)) { | |
| recordTypeOfField(field, types.nullType); | |
| } | |
| } | |
| } else { | |
| final method = element as FunctionEntity; | |
| // Abstract methods don't have a body so they won't have a return type. | |
| if (!method.isAbstract) recordReturnType(method, type!); | |
| } | |
| } | |
| /// Visits [body] to compute the [TypeInformation] node for [member]. | |
| TypeInformation _computeMemberTypeInformation( | |
| MemberEntity member, | |
| ir.Node? body, | |
| ) { | |
| final node = closedWorld.elementMap.getMemberContextNode(member); | |
| KernelTypeGraphBuilder visitor = KernelTypeGraphBuilder( | |
| _options, | |
| closedWorld, | |
| this, | |
| member, | |
| body, | |
| globalLocalsMap.getLocalsMap(member), | |
| node != null | |
| ? ir.StaticTypeContext( | |
| node, | |
| closedWorld.elementMap.typeEnvironment, | |
| cache: ir.StaticTypeCacheImpl(), | |
| ) | |
| : null, | |
| memberHierarchyBuilder, | |
| ); | |
| return visitor.run(); | |
| } | |
| /// Returns `true` if the [initializer] of the non-const static or top-level | |
| /// [field] is potentially `null`. | |
| bool _isFieldInitializerPotentiallyNull( | |
| FieldEntity field, | |
| ir.Node initializer, | |
| ) { | |
| // TODO(13429): We could do better here by using the | |
| // constant handler to figure out if it's a lazy field or not. | |
| // TODO(johnniwinther): Implement the ad-hoc check in ast inferrer? This | |
| // mimics that ast inferrer which return `true` for [ast.Send] and | |
| // non-const [ast.NewExpression]. | |
| if (initializer is ir.InstanceInvocation || | |
| initializer is ir.InstanceGetterInvocation || | |
| initializer is ir.DynamicInvocation || | |
| initializer is ir.FunctionInvocation || | |
| initializer is ir.LocalFunctionInvocation || | |
| initializer is ir.EqualsNull || | |
| initializer is ir.EqualsCall || | |
| initializer is ir.InstanceGet || | |
| initializer is ir.DynamicGet || | |
| initializer is ir.InstanceTearOff || | |
| initializer is ir.FunctionTearOff || | |
| initializer is ir.InstanceSet || | |
| initializer is ir.DynamicSet || | |
| initializer is ir.StaticInvocation || | |
| initializer is ir.StaticGet || | |
| initializer is ir.StaticTearOff || | |
| initializer is ir.StaticSet || | |
| initializer is ir.Let || | |
| initializer is ir.ConstructorInvocation && !initializer.isConst) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| /// Returns the [ConstantValue] for the initial value of [field], or | |
| /// `null` if the initializer is not a constant value. | |
| ConstantValue? _getFieldConstant(JField field) { | |
| return closedWorld.fieldAnalysis.getFieldData(field).initialValue; | |
| } | |
| /// Returns `true` if [cls] has a 'call' method. | |
| bool _hasCallType(ClassEntity cls) { | |
| return closedWorld.dartTypes.getCallType( | |
| closedWorld.elementEnvironment.getThisType(cls), | |
| ) != | |
| null; | |
| } | |
| void _processLoopInformation() { | |
| for (var info in types.allocatedCalls) { | |
| if (!info.inLoop) continue; | |
| // We can't compute the callees of closures, no new information to add. | |
| if (info is ClosureCallSiteTypeInformation) { | |
| continue; | |
| } | |
| if (info is StaticCallSiteTypeInformation) { | |
| MemberEntity member = info.calledElement; | |
| inferredDataBuilder.addFunctionCalledInLoop(member); | |
| } else if (info is DynamicCallSiteTypeInformation && | |
| info.mask != null && | |
| abstractValueDomain.containsAll(info.mask!).isDefinitelyFalse) { | |
| // 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.forEachConcreteTarget(memberHierarchyBuilder, ( | |
| MemberEntity element, | |
| ) { | |
| inferredDataBuilder.addFunctionCalledInLoop(element); | |
| return true; | |
| }); | |
| } | |
| } | |
| } | |
| void _refine() { | |
| _progress.startPhase(); | |
| while (!_workQueue.isEmpty) { | |
| _progress.showProgress('Inferred ', _overallRefineCount, ' types.'); | |
| TypeInformation info = _workQueue.remove(); | |
| AbstractValue oldType = info.type; | |
| // In order to ensure that types are always getting wider we union the old | |
| // and new refined types. This ensures that we do not get caught in any | |
| // refinement loops that can arise from horizontal or downward | |
| // (i.e. narrowing) moves in the type lattice. | |
| // | |
| // This tends to produce comparable results to not doing the union and | |
| // often with fewer refine steps since the natural progression of these | |
| // refines is to take us up the lattice anyway. | |
| AbstractValue newType = abstractValueDomain.union( | |
| oldType, | |
| info.refine(this), | |
| ); | |
| // Check that refinement has not accidentally changed the type. | |
| assert(oldType == info.type); | |
| if (info.abandonInferencing) info.doNotEnqueue = true; | |
| if ((info.type = newType) != oldType) { | |
| _overallRefineCount++; | |
| info.incrementRefineCount(); | |
| if (info.refineCount > _maxChangeCount) { | |
| metrics.exceededMaxChangeCount.add(); | |
| if (debug.anomalyWarn) { | |
| 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.orderedTypeInformations); | |
| _workQueue.addAll(types.allocatedTypes); | |
| _workQueue.addAll(types.allocatedClosures); | |
| _workQueue.addAll(types.allocatedCalls); | |
| } | |
| /// Update the inputs to parameters in the graph. [remove] tells whether | |
| /// inputs must be added or removed. If [addToQueue] is `true`, parameters are | |
| /// added to the work queue. Returns `true` if the call requires [callee] to | |
| /// be closurized. If [virtualCall] is `true` inputs are added and removed | |
| /// from the virtual types for [callee]. | |
| bool updateParameterInputs( | |
| TypeInformation callSiteType, | |
| MemberEntity callee, | |
| ArgumentsTypes? arguments, | |
| Selector? selector, { | |
| required bool remove, | |
| required bool addToQueue, | |
| bool virtualCall = false, | |
| }) { | |
| if (callee.name == Identifiers.noSuchMethod_) return false; | |
| if (callee is FieldEntity) { | |
| if (selector!.isSetter) { | |
| ElementTypeInformation info = types.getInferredTypeOfMember( | |
| callee, | |
| virtual: virtualCall, | |
| ); | |
| if (remove) { | |
| info.removeInput(arguments!.positional[0]); | |
| } else { | |
| info.addInput(arguments!.positional[0]); | |
| } | |
| if (addToQueue) _workQueue.add(info); | |
| } | |
| } else if (callee.isGetter) { | |
| return false; | |
| } else if (selector != null && selector.isGetter) { | |
| // We are tearing a function off and thus create a closure. | |
| assert(callee.isFunction); | |
| final memberInfo = types.getInferredTypeOfMember( | |
| callee, | |
| virtual: virtualCall, | |
| ); | |
| _markForClosurization( | |
| memberInfo, | |
| callSiteType, | |
| remove: remove, | |
| addToQueue: addToQueue, | |
| isVirtualCall: virtualCall, | |
| ); | |
| return true; | |
| } else { | |
| final method = callee as FunctionEntity; | |
| ParameterStructure parameterStructure = method.parameterStructure; | |
| int parameterIndex = 0; | |
| final localArguments = arguments!; | |
| types.strategy.forEachParameter(callee, (Local parameter) { | |
| TypeInformation? type; | |
| if (parameterIndex < parameterStructure.requiredPositionalParameters) { | |
| type = localArguments.positional[parameterIndex]; | |
| } else if (parameterStructure.namedParameters.isNotEmpty) { | |
| type = localArguments.named[parameter.name]; | |
| } else if (parameterIndex < localArguments.positional.length) { | |
| type = localArguments.positional[parameterIndex]; | |
| } | |
| type ??= getDefaultTypeOfParameter(parameter); | |
| TypeInformation info = types.getInferredTypeOfParameter( | |
| parameter, | |
| isVirtual: virtualCall, | |
| ); | |
| if (remove) { | |
| info.removeInput(type); | |
| } else { | |
| info.addInput(type); | |
| } | |
| parameterIndex++; | |
| if (addToQueue) _workQueue.add(info); | |
| }); | |
| } | |
| return false; | |
| } | |
| /// Adds edges between the virtual type information for [parent] and | |
| /// [override] based on the type of each member. If the virtual type | |
| /// information for either does not exist yet, create it and initialize | |
| /// it by adding edges to the concrete type information for that member. | |
| /// Passed to [MemberHierarchyBuilder.init] to be called as it discovers | |
| /// overrides. | |
| /// | |
| /// Possible override configurations (parent/override): | |
| /// - field/getter | |
| /// - field/setter | |
| /// - field/field | |
| /// - getter/getter | |
| /// - getter/field | |
| /// - setter/setter | |
| /// - setter/field | |
| /// - method/method | |
| void _initializeOverrideEdges(MemberEntity parent, MemberEntity override) { | |
| // Skip adding edges for Object.noSuchMethod since it cannot virtually | |
| // dispatch to other implementations of NSM. However, user-defined NSM | |
| // methods do need to handle virtual dispatch. | |
| if (parent == commonElements.objectNoSuchMethod) return; | |
| final parentType = _getAndSetupVirtualMember(parent); | |
| final overrideType = _getAndSetupVirtualMember(override); | |
| _addOverrideTypeInputs(parent, override, parentType, overrideType); | |
| } | |
| void _setupVirtualCall( | |
| MemberTypeInformation virtualCallType, | |
| MemberEntity member, | |
| ) { | |
| if (member is FieldEntity || member.isGetter) { | |
| final realMember = types.getInferredTypeOfMember(member); | |
| virtualCallType.addInput(realMember); | |
| if (member.isAssignable) { | |
| realMember.addInput(virtualCallType); | |
| } | |
| } else { | |
| assert(member.isSetter || member.isFunction); | |
| types.strategy.forEachParameter(member as FunctionEntity, ( | |
| Local parameter, | |
| ) { | |
| final virtualParamInfo = types.getInferredTypeOfParameter( | |
| parameter, | |
| isVirtual: true, | |
| ); | |
| final realParamInfo = types.getInferredTypeOfParameter(parameter); | |
| realParamInfo.addInput(virtualParamInfo); | |
| assert(virtualParamInfo.users.first == realParamInfo); | |
| }); | |
| if (member.isFunction) { | |
| virtualCallType.addInput(types.getInferredTypeOfMember(member)); | |
| } | |
| } | |
| } | |
| void _addOverrideParameterEdges( | |
| FunctionEntity parent, | |
| FunctionEntity override, | |
| ) { | |
| final ParameterStructure parameterStructure = parent.parameterStructure; | |
| int parameterIndex = 0; | |
| // Collect the parent parameter type infos. | |
| final List<TypeInformation> positional = []; | |
| final Map<String, TypeInformation> named = {}; | |
| types.strategy.forEachParameter(parent, (Local parameter) { | |
| TypeInformation type = types.getInferredTypeOfParameter( | |
| parameter, | |
| isVirtual: true, | |
| ); | |
| if (parameterIndex < parameterStructure.requiredPositionalParameters) { | |
| positional.add(type); | |
| } else if (parameterStructure.namedParameters.isNotEmpty) { | |
| named[parameter.name!] = type; | |
| } else if (parameterIndex < parameterStructure.positionalParameters) { | |
| positional.add(type); | |
| } | |
| parameterIndex++; | |
| }); | |
| parameterIndex = 0; | |
| // Add the parent parameter type infos as inputs to the override's | |
| // parameters. | |
| types.strategy.forEachParameter(override, (Local parameter) { | |
| TypeInformation? parentParamInfo; | |
| if (parameterIndex < parameterStructure.requiredPositionalParameters) { | |
| parentParamInfo = positional[parameterIndex]; | |
| } else if (parameterStructure.namedParameters.isNotEmpty) { | |
| parentParamInfo = named[parameter.name]; | |
| } else if (parameterIndex < positional.length) { | |
| parentParamInfo = positional[parameterIndex]; | |
| } | |
| TypeInformation overrideParamInfo = types.getInferredTypeOfParameter( | |
| parameter, | |
| isVirtual: true, | |
| ); | |
| if (parentParamInfo != null) { | |
| overrideParamInfo.addInput(parentParamInfo); | |
| } | |
| // An override can have extra parameters, make required parameters | |
| // optional, or have a different default values. Make the default value an | |
| // input to cover these cases. The default value is not used when every | |
| // call site passes an argument in an optional position, but there is not | |
| // a convenient test for this, so this causes a conservative inclusion of | |
| // the default value when sometimes not strictly necessary. | |
| final ParameterStructure overrideStructure = override.parameterStructure; | |
| final bool isOptional; | |
| if (parameterIndex < overrideStructure.requiredPositionalParameters) { | |
| isOptional = false; | |
| } else if (overrideStructure.namedParameters.isNotEmpty) { | |
| isOptional = !overrideStructure.requiredNamedParameters.contains( | |
| parameter.name, | |
| ); | |
| } else { | |
| isOptional = true; // Optional positional. | |
| } | |
| if (parentParamInfo == null || isOptional) { | |
| overrideParamInfo.addInput(getDefaultTypeOfParameter(parameter)); | |
| } | |
| parameterIndex++; | |
| }); | |
| } | |
| MemberTypeInformation _getAndSetupVirtualMember(MemberEntity member) { | |
| final memberType = types.getInferredTypeOfMember(member, virtual: true); | |
| if (_initializedVirtualMembers!.add(member)) { | |
| _setupVirtualCall(memberType, member); | |
| } | |
| return memberType; | |
| } | |
| void _addOverrideTypeInputs( | |
| MemberEntity parent, | |
| MemberEntity override, | |
| MemberTypeInformation parentType, | |
| MemberTypeInformation overrideType, | |
| ) { | |
| if (parent is FieldEntity) { | |
| if (override.isGetter) { | |
| parentType.addInput(overrideType); | |
| } else if (override.isSetter) { | |
| types.strategy.forEachParameter(override as FunctionEntity, ( | |
| Local parameter, | |
| ) { | |
| final paramInfo = types.getInferredTypeOfParameter( | |
| parameter, | |
| isVirtual: true, | |
| ); | |
| paramInfo.addInput(parentType); | |
| }); | |
| } else { | |
| assert(override is FieldEntity); | |
| parentType.addInput(overrideType); | |
| if (parent.isAssignable) { | |
| // Parent has an implicit setter so the types of set values need to | |
| // flow into the override. | |
| overrideType.addInput(parentType); | |
| } | |
| } | |
| } else if (parent.isGetter) { | |
| assert(override.isGetter || override is FieldEntity); | |
| parentType.addInput(overrideType); | |
| } else if (parent.isSetter) { | |
| if (override.isSetter) { | |
| _addOverrideParameterEdges( | |
| parent as FunctionEntity, | |
| override as FunctionEntity, | |
| ); | |
| } else { | |
| assert(override is FieldEntity); | |
| types.strategy.forEachParameter(parent as FunctionEntity, ( | |
| Local parameter, | |
| ) { | |
| final paramInfo = types.getInferredTypeOfParameter( | |
| parameter, | |
| isVirtual: true, | |
| ); | |
| overrideType.addInput(paramInfo); | |
| }); | |
| } | |
| } else { | |
| assert(parent.isFunction && override.isFunction); | |
| parentType.addInput(overrideType); | |
| _addOverrideParameterEdges( | |
| parent as FunctionEntity, | |
| override as FunctionEntity, | |
| ); | |
| } | |
| } | |
| void _markForClosurization( | |
| MemberTypeInformation memberInfo, | |
| TypeInformation callSiteType, { | |
| required bool remove, | |
| required bool addToQueue, | |
| required bool isVirtualCall, | |
| }) { | |
| final member = memberInfo.member; | |
| if (remove) { | |
| memberInfo.closurizedCount--; | |
| } else { | |
| memberInfo.closurizedCount++; | |
| if (member.isStatic || member.isTopLevel) { | |
| types.allocatedClosures.add(memberInfo); | |
| } else { | |
| // We add the call-site type information here so that we | |
| // can benefit from further refinement of the selector. | |
| types.allocatedClosures.add(callSiteType); | |
| } | |
| types.strategy.forEachParameter(member as FunctionEntity, ( | |
| Local parameter, | |
| ) { | |
| ParameterTypeInformation info = types.getInferredTypeOfParameter( | |
| parameter, | |
| isVirtual: isVirtualCall, | |
| ); | |
| info.tagAsTearOffClosureParameter(this); | |
| if (addToQueue) _workQueue.add(info); | |
| }); | |
| } | |
| } | |
| /// Iterate through reachable members for the given target. Label relevant | |
| /// members as needing closurization if necessary. If [shouldMarkCalled] then | |
| /// also mark reachable members as called. | |
| void _processDynamicTarget( | |
| DynamicCallTarget target, | |
| DynamicCallSiteTypeInformation callSiteType, { | |
| required bool shouldMarkCalled, | |
| }) { | |
| final virtualType = types.getInferredTypeOfMember( | |
| target.member, | |
| virtual: true, | |
| ); | |
| final needsClosurization = virtualType.closurizedCount > 0; | |
| // There is nothing to do so no need to iterate over target members. | |
| if (!needsClosurization && !shouldMarkCalled) return; | |
| bool handleTarget(MemberEntity member) { | |
| if (!member.isAbstract) { | |
| MemberTypeInformation info = types.getInferredTypeOfMember(member); | |
| if (shouldMarkCalled) info.markCalled(); | |
| if (needsClosurization) { | |
| _markForClosurization( | |
| info, | |
| callSiteType, | |
| remove: false, | |
| addToQueue: false, | |
| isVirtualCall: target.isVirtual, | |
| ); | |
| } | |
| } | |
| return true; | |
| } | |
| memberHierarchyBuilder.forEachTargetMember(target, handleTarget); | |
| } | |
| /// Update call information for targets of calls. | |
| /// | |
| /// Marks targets of dynamic calls that need closuraization. If [shouldMarkCalled] | |
| /// is true any members targeted by a dynamic or static call is also marked | |
| /// as being called. | |
| void _processCalledTargets({required bool shouldMarkCalled}) { | |
| for (final call in types.allocatedCalls) { | |
| if (call is DynamicCallSiteTypeInformation) { | |
| for (final target in call.targets) { | |
| _processDynamicTarget( | |
| target, | |
| call, | |
| shouldMarkCalled: shouldMarkCalled, | |
| ); | |
| } | |
| } else if (shouldMarkCalled && call is StaticCallSiteTypeInformation) { | |
| types.getInferredTypeOfMember(call.calledElement).markCalled(); | |
| } | |
| } | |
| } | |
| /// 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(Local parameter, TypeInformation type) { | |
| final existing = _defaultTypeOfParameter[parameter]; | |
| _defaultTypeOfParameter[parameter] = type; | |
| TypeInformation info = types.getInferredTypeOfParameter(parameter); | |
| if (existing != null && existing is PlaceholderTypeInformation) { | |
| TypeInformation virtualInfo = types.getInferredTypeOfParameter( | |
| parameter, | |
| isVirtual: true, | |
| ); | |
| // Replace references to [existing] to use [type] instead. | |
| info.inputs.replace(existing, type); | |
| virtualInfo.inputs.replace(existing, type); | |
| // Also forward all users. | |
| type.addUsersOf(existing); | |
| } 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(Local parameter) { | |
| return _defaultTypeOfParameter.putIfAbsent(parameter, () { | |
| return PlaceholderTypeInformation( | |
| abstractValueDomain, | |
| types.currentMember, | |
| ); | |
| }); | |
| } | |
| /// Returns the type of [element]. | |
| ParameterTypeInformation typeOfParameter(Local element) { | |
| return types.getInferredTypeOfParameter(element); | |
| } | |
| MemberTypeInformation _inferredTypeOfMember( | |
| MemberEntity element, { | |
| required bool isVirtual, | |
| }) { | |
| return types.getInferredTypeOfMember(element, virtual: isVirtual); | |
| } | |
| MemberTypeInformation inferredTypeOfTarget(DynamicCallTarget target) { | |
| return _inferredTypeOfMember(target.member, isVirtual: target.isVirtual); | |
| } | |
| /// Returns the type of [element]. | |
| TypeInformation typeOfMember(MemberEntity element, {bool isVirtual = false}) { | |
| if (element is FunctionEntity) return types.functionType; | |
| return _inferredTypeOfMember(element, isVirtual: isVirtual); | |
| } | |
| /// Returns the return type of [element]. | |
| TypeInformation returnTypeOfMember( | |
| MemberEntity element, { | |
| bool isVirtual = false, | |
| }) { | |
| if (element is! FunctionEntity) return types.dynamicType; | |
| return _inferredTypeOfMember(element, isVirtual: isVirtual); | |
| } | |
| /// Records that [element] is of type [type]. | |
| void recordTypeOfField(FieldEntity element, TypeInformation type) { | |
| types.getInferredTypeOfMember(element).addInput(type); | |
| } | |
| /// Records that the return type [element] is of type [type]. | |
| void recordReturnType(FunctionEntity element, TypeInformation type) { | |
| TypeInformation info = types.getInferredTypeOfMember(element); | |
| if (element.name == '==') { | |
| // Even if x.== doesn't return a bool, 'x == null' evaluates to 'false'. | |
| info.addInput(types.boolType); | |
| } | |
| if (info.inputs.isEmpty) info.addInput(type); | |
| } | |
| /// Notifies to the inferrer that [analyzedElement] can have return type | |
| /// [newType]. [currentType] is the type the inference has currently found. | |
| /// | |
| /// Returns the new type for [analyzedElement]. | |
| TypeInformation addReturnTypeForMethod( | |
| FunctionEntity element, | |
| TypeInformation newType, | |
| ) { | |
| TypeInformation type = types.getInferredTypeOfMember(element); | |
| // TODO(ngeoffray): Clean up. We do this check because | |
| // [SimpleTypesInferrer] deals with two different inferrers. | |
| if (element is ConstructorEntity && element.isGenerativeConstructor) { | |
| return type; | |
| } | |
| type.addInput(newType); | |
| return type; | |
| } | |
| /// Registers that [caller] calls [callee] at location [node], with | |
| /// [selector], and [arguments]. Note that [selector] is null for forwarding | |
| /// constructors. | |
| /// | |
| /// [sideEffectsBuilder] will be updated to incorporate [callee]'s side | |
| /// effects. | |
| /// | |
| /// [inLoop] tells whether the call happens in a loop. | |
| TypeInformation registerCalledMember( | |
| ir.Node node, | |
| Selector? selector, | |
| MemberEntity caller, | |
| MemberEntity callee, | |
| ArgumentsTypes? arguments, | |
| SideEffectsBuilder sideEffectsBuilder, | |
| bool inLoop, | |
| ) { | |
| CallSiteTypeInformation info = StaticCallSiteTypeInformation( | |
| abstractValueDomain, | |
| types.currentMember, | |
| node, | |
| caller, | |
| callee, | |
| selector, | |
| arguments, | |
| inLoop, | |
| ); | |
| // If this class has a 'call' method then we have essentially created a | |
| // closure here. Register it as such so that it is traced. | |
| // Note: we exclude factory constructors because they don't always create an | |
| // instance of the type. They are static methods that delegate to some other | |
| // generative constructor to do the actual creation of the object. | |
| if (selector != null && | |
| selector.isCall && | |
| callee is ConstructorEntity && | |
| callee.isGenerativeConstructor) { | |
| ClassEntity cls = callee.enclosingClass; | |
| if (_hasCallType(cls)) { | |
| types.allocatedClosures.add(info); | |
| } | |
| } | |
| info.addToGraph(this); | |
| types.allocatedCalls.add(info); | |
| _updateSideEffects(sideEffectsBuilder, selector, callee); | |
| return info; | |
| } | |
| /// Registers that [caller] calls [selector] with [receiverType] as receiver, | |
| /// and [arguments]. | |
| /// | |
| /// [sideEffectsBuilder] will be updated to incorporate the potential callees' | |
| /// side effects. | |
| /// | |
| /// [inLoop] tells whether the call happens in a loop. | |
| TypeInformation registerCalledSelector( | |
| CallType callType, | |
| ir.Node node, | |
| Selector selector, | |
| AbstractValue? mask, | |
| TypeInformation receiverType, | |
| MemberEntity caller, | |
| ArgumentsTypes? arguments, | |
| SideEffectsBuilder sideEffectsBuilder, { | |
| required bool inLoop, | |
| required bool isConditional, | |
| }) { | |
| if (selector.isMaybeClosureCall && node is! ir.InstanceGetterInvocation) { | |
| // If node is an InstanceGetterInvocation then the target is an invocation | |
| // of a closure retreived from an instance member (field or getter) rather | |
| // than a method invocation. | |
| return registerCalledClosure( | |
| node, | |
| selector, | |
| receiverType, | |
| caller, | |
| arguments, | |
| sideEffectsBuilder, | |
| inLoop: inLoop, | |
| ); | |
| } | |
| if (closedWorld.includesClosureCall(selector, mask)) { | |
| sideEffectsBuilder.setAllSideEffectsAndDependsOnSomething(); | |
| } | |
| forEachElementMatching(selector, mask, (element) { | |
| _updateSideEffects(sideEffectsBuilder, selector, element); | |
| return true; | |
| }); | |
| CallSiteTypeInformation info = DynamicCallSiteTypeInformation( | |
| abstractValueDomain, | |
| types.currentMember, | |
| callType, | |
| node, | |
| caller, | |
| selector, | |
| mask, | |
| receiverType, | |
| arguments, | |
| inLoop, | |
| isConditional, | |
| ); | |
| info.addToGraph(this); | |
| types.allocatedCalls.add(info); | |
| return info; | |
| } | |
| /// Registers a call to await with an expression of type [argumentType] as | |
| /// argument. | |
| TypeInformation registerAwait( | |
| ir.AwaitExpression node, | |
| TypeInformation argument, | |
| ) { | |
| AwaitTypeInformation info = AwaitTypeInformation( | |
| abstractValueDomain, | |
| types.currentMember!, | |
| node, | |
| ); | |
| info.addInput(argument); | |
| types.allocatedTypes.add(info); | |
| return info; | |
| } | |
| /// Registers a call to yield with an expression of type [argumentType] as | |
| /// argument. | |
| TypeInformation registerYield(ir.Node node, TypeInformation argument) { | |
| YieldTypeInformation info = YieldTypeInformation( | |
| abstractValueDomain, | |
| types.currentMember, | |
| node, | |
| ); | |
| info.addInput(argument); | |
| types.allocatedTypes.add(info); | |
| return info; | |
| } | |
| /// Registers that [caller] calls [closure] with [arguments]. | |
| /// | |
| /// [sideEffectsBuilder] will be updated to incorporate the potential callees' | |
| /// side effects. | |
| /// | |
| /// [inLoop] tells whether the call happens in a loop. | |
| TypeInformation registerCalledClosure( | |
| ir.Node node, | |
| Selector selector, | |
| TypeInformation closure, | |
| MemberEntity caller, | |
| ArgumentsTypes? arguments, | |
| SideEffectsBuilder sideEffectsBuilder, { | |
| required bool inLoop, | |
| }) { | |
| sideEffectsBuilder.setAllSideEffectsAndDependsOnSomething(); | |
| CallSiteTypeInformation info = ClosureCallSiteTypeInformation( | |
| abstractValueDomain, | |
| types.currentMember, | |
| node, | |
| caller, | |
| selector, | |
| closure, | |
| arguments, | |
| inLoop, | |
| ); | |
| info.addToGraph(this); | |
| types.allocatedCalls.add(info); | |
| return info; | |
| } | |
| void clear() { | |
| if (retainDataForTesting) return; | |
| void cleanup(TypeInformation info) { | |
| info.cleanup(); | |
| } | |
| types.allocatedCalls.forEach(cleanup); | |
| types.allocatedCalls.clear(); | |
| _defaultTypeOfParameter.clear(); | |
| types.parameterTypeInformations.values.forEach(cleanup); | |
| types.memberTypeInformations.values.forEach(cleanup); | |
| types.allocatedTypes.forEach(cleanup); | |
| types.allocatedTypes.clear(); | |
| types.concreteTypes.clear(); | |
| types.allocatedClosures.forEach(cleanup); | |
| types.allocatedClosures.clear(); | |
| _analyzedElements.clear(); | |
| _generativeConstructorsExposingThis.clear(); | |
| types.allocatedMaps.values.forEach(cleanup); | |
| types.allocatedSets.values.forEach(cleanup); | |
| types.allocatedLists.values.forEach(cleanup); | |
| _memberData.clear(); | |
| } | |
| Map<MemberEntity, Set<MemberEntity>>? _cachedCallersOfForTesting; | |
| Iterable<MemberEntity>? getCallersOfForTesting(MemberEntity element) { | |
| if (_cachedCallersOfForTesting == null) { | |
| final callers = _cachedCallersOfForTesting = {}; | |
| for (final callSite in types.allocatedCalls) { | |
| if (callSite is StaticCallSiteTypeInformation) { | |
| (callers[callSite.calledElement] ??= {}).add(callSite.caller); | |
| } else if (callSite is DynamicCallSiteTypeInformation) { | |
| callSite.forEachConcreteTarget(memberHierarchyBuilder, (member) { | |
| (callers[member] ??= {}).add(callSite.caller); | |
| return true; | |
| }); | |
| } | |
| } | |
| } | |
| return _cachedCallersOfForTesting![element]; | |
| } | |
| /// Returns the type of [element] when being called with [selector]. | |
| TypeInformation typeOfMemberWithSelector( | |
| MemberEntity element, | |
| Selector? selector, { | |
| required bool isVirtual, | |
| }) { | |
| if (element.name == Identifiers.noSuchMethod_ && | |
| selector!.name != element.name) { | |
| // An invocation can resolve to a [noSuchMethod], in which case | |
| // we get the return type of [noSuchMethod]. | |
| return returnTypeOfMember(element); | |
| } else if (selector!.isGetter) { | |
| if (element.isFunction) { | |
| return types.functionType; | |
| } else if (element is FieldEntity) { | |
| return typeOfMember(element, isVirtual: isVirtual); | |
| } else if (element.isGetter) { | |
| return returnTypeOfMember(element, isVirtual: isVirtual); | |
| } else { | |
| assert(false, failedAt(element, "Unexpected member $element")); | |
| return types.dynamicType; | |
| } | |
| } else if (element.isGetter || element is FieldEntity) { | |
| assert(selector.isCall || selector.isSetter); | |
| return types.dynamicType; | |
| } else { | |
| return returnTypeOfMember(element, isVirtual: isVirtual); | |
| } | |
| } | |
| /// Returns true if global optimizations such as type inferencing can apply to | |
| /// the field [element]. | |
| /// | |
| /// One category of elements that do not apply is runtime helpers that the | |
| /// backend calls, but the optimizations don't see those calls. | |
| bool canFieldBeUsedForGlobalOptimizations(FieldEntity element) { | |
| if (closedWorld.backendUsage.isFieldUsedByBackend(element)) { | |
| return false; | |
| } | |
| if ((element.isTopLevel || element.isStatic) && !element.isAssignable) { | |
| return true; | |
| } | |
| return true; | |
| } | |
| /// Returns true if global optimizations such as type inferencing can apply to | |
| /// the parameter [element]. | |
| /// | |
| /// One category of elements that do not apply is runtime helpers that the | |
| /// backend calls, but the optimizations don't see those calls. | |
| bool canFunctionParametersBeUsedForGlobalOptimizations( | |
| FunctionEntity function, | |
| ) { | |
| return !closedWorld.backendUsage.isFunctionUsedByBackend(function); | |
| } | |
| /// Returns `true` if inference of parameter types is disabled for [member]. | |
| bool assumeDynamic(MemberEntity member) { | |
| return closedWorld.annotationsData.hasAssumeDynamic(member); | |
| } | |
| } | |
| class InferrerEngineMetrics extends MetricsBase { | |
| final time = DurationMetric('time'); | |
| final analyze = DurationMetric('time.analyze'); | |
| final memberHierarchy = DurationMetric('time.memberHierarchy'); | |
| final refine1 = DurationMetric('time.refine1'); | |
| final trace = DurationMetric('time.trace'); | |
| final refine2 = DurationMetric('time.refine2'); | |
| final elementsInGraph = CountMetric('count.elementsInGraph'); | |
| final allTypesCount = CountMetric('count.allTypes'); | |
| final exceededMaxChangeCount = CountMetric('count.exceededMaxChange'); | |
| final overallRefineCount = CountMetric('count.overallRefines'); | |
| InferrerEngineMetrics(Metrics subMetrics) { | |
| primary = [time, ...subMetrics.primary]; | |
| secondary = [ | |
| analyze, | |
| memberHierarchy, | |
| refine1, | |
| trace, | |
| refine2, | |
| elementsInGraph, | |
| allTypesCount, | |
| exceededMaxChangeCount, | |
| overallRefineCount, | |
| ...subMetrics.secondary, | |
| ]; | |
| } | |
| } | |
| class KernelTypeSystemStrategy implements TypeSystemStrategy { | |
| final JClosedWorld _closedWorld; | |
| final GlobalLocalsMap _globalLocalsMap; | |
| KernelTypeSystemStrategy(this._closedWorld, this._globalLocalsMap); | |
| JElementEnvironment get _elementEnvironment => | |
| _closedWorld.elementEnvironment; | |
| @override | |
| bool checkClassEntity(ClassEntity cls) => true; | |
| @override | |
| bool checkMapNode(ir.Node node) => true; | |
| @override | |
| bool checkSetNode(ir.Node node) => true; | |
| @override | |
| bool checkListNode(ir.Node node) => true; | |
| @override | |
| bool checkLoopPhiNode(ir.Node node) => true; | |
| @override | |
| bool checkPhiNode(ir.Node? node) => | |
| node == null || node is ir.TryCatch || node is ir.TryFinally; | |
| @override | |
| void forEachParameter( | |
| FunctionEntity function, | |
| void Function(Local parameter) f, | |
| ) { | |
| forEachOrderedParameterAsLocal( | |
| _globalLocalsMap, | |
| _closedWorld.elementMap, | |
| function, | |
| (Local parameter, {required bool isElided}) { | |
| f(parameter); | |
| }, | |
| ); | |
| } | |
| @override | |
| ParameterTypeInformation createParameterTypeInformation( | |
| AbstractValueDomain abstractValueDomain, | |
| covariant JLocal parameter, | |
| TypeSystem types, { | |
| required bool isVirtual, | |
| }) { | |
| MemberEntity context = parameter.memberContext; | |
| KernelToLocalsMap localsMap = _globalLocalsMap.getLocalsMap(context); | |
| ir.FunctionNode functionNode = localsMap.getFunctionNodeForParameter( | |
| parameter, | |
| ); | |
| DartType type = localsMap.getLocalType(_closedWorld.elementMap, parameter); | |
| late final MemberEntity member; | |
| bool isClosure = false; | |
| final parent = functionNode.parent; | |
| if (parent is ir.Member) { | |
| member = _closedWorld.elementMap.getMember(parent); | |
| } else if (parent is ir.LocalFunction) { | |
| ClosureRepresentationInfo info = _closedWorld.closureDataLookup | |
| .getClosureInfo(parent); | |
| member = info.callMethod!; | |
| isClosure = true; | |
| } | |
| MemberTypeInformation memberTypeInformation = types.getInferredTypeOfMember( | |
| member, | |
| ); | |
| if (isClosure) { | |
| return ParameterTypeInformation.localFunction( | |
| abstractValueDomain, | |
| memberTypeInformation, | |
| parameter, | |
| type, | |
| member as FunctionEntity, | |
| ); | |
| } else if (member.isInstanceMember) { | |
| return ParameterTypeInformation.instanceMember( | |
| abstractValueDomain, | |
| memberTypeInformation, | |
| parameter, | |
| type, | |
| member as FunctionEntity, | |
| ParameterInputs.instanceMember(), | |
| isVirtual: isVirtual, | |
| ); | |
| } else { | |
| return ParameterTypeInformation.static( | |
| abstractValueDomain, | |
| memberTypeInformation, | |
| parameter, | |
| type, | |
| member as FunctionEntity, | |
| ); | |
| } | |
| } | |
| @override | |
| MemberTypeInformation createMemberTypeInformation( | |
| AbstractValueDomain abstractValueDomain, | |
| MemberEntity member, | |
| ) { | |
| if (member is FieldEntity) { | |
| final field = member; | |
| DartType type = _elementEnvironment.getFieldType(field); | |
| return FieldTypeInformation(abstractValueDomain, field, type); | |
| } else if (member.isGetter) { | |
| final getter = member as FunctionEntity; | |
| DartType type = _elementEnvironment.getFunctionType(getter); | |
| return GetterTypeInformation( | |
| abstractValueDomain, | |
| getter, | |
| type as FunctionType, | |
| ); | |
| } else if (member.isSetter) { | |
| final setter = member as FunctionEntity; | |
| return SetterTypeInformation(abstractValueDomain, setter); | |
| } else if (member.isFunction) { | |
| final method = member as FunctionEntity; | |
| DartType type = _elementEnvironment.getFunctionType(method); | |
| return MethodTypeInformation( | |
| abstractValueDomain, | |
| method, | |
| type as FunctionType, | |
| ); | |
| } else { | |
| final constructor = member as ConstructorEntity; | |
| if (constructor.isFactoryConstructor) { | |
| DartType type = _elementEnvironment.getFunctionType(constructor); | |
| return FactoryConstructorTypeInformation( | |
| abstractValueDomain, | |
| constructor, | |
| type as FunctionType, | |
| ); | |
| } else { | |
| return GenerativeConstructorTypeInformation( | |
| abstractValueDomain, | |
| constructor, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| class KernelGlobalTypeInferenceElementData | |
| implements GlobalTypeInferenceElementData { | |
| /// Tag used for identifying serialized [GlobalTypeInferenceElementData] | |
| /// objects in a debugging data stream. | |
| static const String tag = 'global-type-inference-element-data'; | |
| // TODO(natebiggs): Can we remove nulls from these maps. | |
| Map<ir.TreeNode, AbstractValue?>? _receiverMap; | |
| Map<ir.ForInStatement, AbstractValue?>? _iteratorMap; | |
| Map<ir.ForInStatement, AbstractValue?>? _currentMap; | |
| Map<ir.ForInStatement, AbstractValue?>? _moveNextMap; | |
| KernelGlobalTypeInferenceElementData(); | |
| KernelGlobalTypeInferenceElementData.internal( | |
| this._receiverMap, | |
| this._iteratorMap, | |
| this._currentMap, | |
| this._moveNextMap, | |
| ); | |
| /// Deserializes a [GlobalTypeInferenceElementData] object from [source]. | |
| factory KernelGlobalTypeInferenceElementData.readFromDataSource( | |
| DataSourceReader source, | |
| ir.Member? context, | |
| AbstractValueDomain abstractValueDomain, | |
| ) { | |
| return source.inMemberContext(context, () { | |
| source.begin(tag); | |
| Map<ir.TreeNode, AbstractValue>? sendMap = source | |
| .readTreeNodeMapInContextOrNull( | |
| () => abstractValueDomain.readAbstractValueFromDataSource(source), | |
| ); | |
| Map<ir.ForInStatement, AbstractValue>? iteratorMap = source | |
| .readTreeNodeMapInContextOrNull( | |
| () => abstractValueDomain.readAbstractValueFromDataSource(source), | |
| ); | |
| Map<ir.ForInStatement, AbstractValue>? currentMap = source | |
| .readTreeNodeMapInContextOrNull( | |
| () => abstractValueDomain.readAbstractValueFromDataSource(source), | |
| ); | |
| Map<ir.ForInStatement, AbstractValue>? moveNextMap = source | |
| .readTreeNodeMapInContextOrNull( | |
| () => abstractValueDomain.readAbstractValueFromDataSource(source), | |
| ); | |
| source.end(tag); | |
| return KernelGlobalTypeInferenceElementData.internal( | |
| sendMap, | |
| iteratorMap, | |
| currentMap, | |
| moveNextMap, | |
| ); | |
| }); | |
| } | |
| @override | |
| void writeToDataSink( | |
| DataSinkWriter sink, | |
| ir.Member? context, | |
| AbstractValueDomain abstractValueDomain, | |
| ) { | |
| sink.inMemberContext(context, () { | |
| sink.begin(tag); | |
| sink.writeTreeNodeMapInContextOrNull( | |
| _receiverMap, | |
| (AbstractValue? value) => | |
| abstractValueDomain.writeAbstractValueToDataSink(sink, value), | |
| ); | |
| sink.writeTreeNodeMapInContextOrNull( | |
| _iteratorMap, | |
| (AbstractValue? value) => | |
| abstractValueDomain.writeAbstractValueToDataSink(sink, value), | |
| ); | |
| sink.writeTreeNodeMapInContextOrNull( | |
| _currentMap, | |
| (AbstractValue? value) => | |
| abstractValueDomain.writeAbstractValueToDataSink(sink, value), | |
| ); | |
| sink.writeTreeNodeMapInContextOrNull( | |
| _moveNextMap, | |
| (AbstractValue? value) => | |
| abstractValueDomain.writeAbstractValueToDataSink(sink, value), | |
| ); | |
| sink.end(tag); | |
| }); | |
| } | |
| @override | |
| GlobalTypeInferenceElementData? compress() { | |
| final receiverMap = _receiverMap; | |
| if (receiverMap != null) { | |
| receiverMap.removeWhere(_mapsToNull); | |
| if (receiverMap.isEmpty) { | |
| _receiverMap = null; | |
| } | |
| } | |
| final iteratorMap = _iteratorMap; | |
| if (iteratorMap != null) { | |
| iteratorMap.removeWhere(_mapsToNull); | |
| if (iteratorMap.isEmpty) { | |
| _iteratorMap = null; | |
| } | |
| } | |
| final currentMap = _currentMap; | |
| if (currentMap != null) { | |
| currentMap.removeWhere(_mapsToNull); | |
| if (currentMap.isEmpty) { | |
| _currentMap = null; | |
| } | |
| } | |
| final moveNextMap = _moveNextMap; | |
| if (moveNextMap != null) { | |
| moveNextMap.removeWhere(_mapsToNull); | |
| if (moveNextMap.isEmpty) { | |
| _moveNextMap = null; | |
| } | |
| } | |
| if (_receiverMap == null && | |
| _iteratorMap == null && | |
| _currentMap == null && | |
| _moveNextMap == null) { | |
| return null; | |
| } | |
| return this; | |
| } | |
| @override | |
| AbstractValue? typeOfReceiver(ir.TreeNode node) { | |
| if (_receiverMap == null) return null; | |
| return _receiverMap![node]; | |
| } | |
| void setCurrentTypeMask(ir.ForInStatement node, AbstractValue? mask) { | |
| final currentMap = _currentMap ??= <ir.ForInStatement, AbstractValue?>{}; | |
| currentMap[node] = mask; | |
| } | |
| void setMoveNextTypeMask(ir.ForInStatement node, AbstractValue? mask) { | |
| final moveNextMap = _moveNextMap ??= <ir.ForInStatement, AbstractValue?>{}; | |
| moveNextMap[node] = mask; | |
| } | |
| void setIteratorTypeMask(ir.ForInStatement node, AbstractValue? mask) { | |
| final iteratorMap = _iteratorMap ??= <ir.ForInStatement, AbstractValue?>{}; | |
| iteratorMap[node] = mask; | |
| } | |
| @override | |
| AbstractValue? typeOfIteratorCurrent(covariant ir.ForInStatement node) { | |
| if (_currentMap == null) return null; | |
| return _currentMap![node]; | |
| } | |
| @override | |
| AbstractValue? typeOfIteratorMoveNext(covariant ir.ForInStatement node) { | |
| if (_moveNextMap == null) return null; | |
| return _moveNextMap![node]; | |
| } | |
| @override | |
| AbstractValue? typeOfIterator(covariant ir.ForInStatement node) { | |
| if (_iteratorMap == null) return null; | |
| return _iteratorMap![node]; | |
| } | |
| void setReceiverTypeMask(ir.TreeNode node, AbstractValue? mask) { | |
| final receiverMap = _receiverMap ??= <ir.TreeNode, AbstractValue?>{}; | |
| receiverMap[node] = mask; | |
| } | |
| } | |
| bool _mapsToNull(ir.TreeNode node, AbstractValue? value) => value == null; |