| // Copyright (c) 2012, 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 dart2js.enqueue; | |
| import 'dart:collection' show Queue; | |
| import 'common/tasks.dart' show CompilerTask; | |
| import 'common/work.dart' show WorkItem; | |
| import 'common.dart'; | |
| import 'common_elements.dart' show ElementEnvironment; | |
| import 'constants/values.dart'; | |
| import 'compiler.dart' show Compiler; | |
| import 'options.dart'; | |
| import 'elements/entities.dart'; | |
| import 'elements/types.dart'; | |
| import 'js_backend/enqueuer.dart'; | |
| import 'types/types.dart'; | |
| import 'universe/world_builder.dart'; | |
| import 'universe/use.dart' | |
| show | |
| ConstantUse, | |
| DynamicUse, | |
| StaticUse, | |
| StaticUseKind, | |
| TypeUse, | |
| TypeUseKind; | |
| import 'universe/world_impact.dart' | |
| show ImpactStrategy, ImpactUseCase, WorldImpact, WorldImpactVisitor; | |
| import 'util/enumset.dart'; | |
| import 'util/util.dart' show Setlet; | |
| import 'world.dart' show JClosedWorld; | |
| class EnqueueTask extends CompilerTask { | |
| ResolutionEnqueuer _resolution; | |
| CodegenEnqueuer codegenEnqueuerForTesting; | |
| final Compiler compiler; | |
| String get name => 'Enqueue'; | |
| EnqueueTask(Compiler compiler) | |
| : this.compiler = compiler, | |
| super(compiler.measurer); | |
| // TODO(johnniwinther): Remove the need for this. | |
| bool get hasResolution => _resolution != null; | |
| // TODO(johnniwinther): Remove the need for this. | |
| ResolutionEnqueuer get resolution { | |
| assert( | |
| _resolution != null, | |
| failedAt(NO_LOCATION_SPANNABLE, | |
| "ResolutionEnqueuer has not been created yet.")); | |
| return _resolution; | |
| } | |
| ResolutionEnqueuer createResolutionEnqueuer() { | |
| return _resolution ??= compiler.backend | |
| .createResolutionEnqueuer(this, compiler) | |
| ..onEmptyForTesting = compiler.onResolutionQueueEmptyForTesting; | |
| } | |
| Enqueuer createCodegenEnqueuer(JClosedWorld closedWorld, | |
| GlobalTypeInferenceResults globalInferenceResults) { | |
| Enqueuer enqueuer = compiler.backend.createCodegenEnqueuer( | |
| this, compiler, closedWorld, globalInferenceResults) | |
| ..onEmptyForTesting = compiler.onCodegenQueueEmptyForTesting; | |
| if (retainDataForTesting) { | |
| codegenEnqueuerForTesting = enqueuer; | |
| } | |
| return enqueuer; | |
| } | |
| } | |
| abstract class Enqueuer { | |
| WorldBuilder get worldBuilder; | |
| void open(ImpactStrategy impactStrategy, FunctionEntity mainMethod, | |
| Iterable<Uri> libraries); | |
| void close(); | |
| /// Returns [:true:] if this enqueuer is the resolution enqueuer. | |
| bool get isResolutionQueue; | |
| bool queueIsClosed; | |
| bool get queueIsEmpty; | |
| ImpactUseCase get impactUse; | |
| void forEach(void f(WorkItem work)); | |
| /// Apply the [worldImpact] to this enqueuer. If the [impactSource] is | |
| /// provided the impact strategy will remove it from the element impact cache, | |
| /// if it is no longer needed. | |
| void applyImpact(WorldImpact worldImpact, {var impactSource}); | |
| bool checkNoEnqueuedInvokedInstanceMethods( | |
| ElementEnvironment elementEnvironment); | |
| /// Check the enqueuer queue is empty or fail otherwise. | |
| void checkQueueIsEmpty(); | |
| void logSummary(void log(String message)); | |
| Iterable<MemberEntity> get processedEntities; | |
| Iterable<ClassEntity> get processedClasses; | |
| } | |
| abstract class EnqueuerListener { | |
| /// Called to instruct to the backend that [type] has been instantiated. | |
| void registerInstantiatedType(InterfaceType type, | |
| {bool isGlobal: false, bool nativeUsage: false}); | |
| /// Called to notify to the backend that a class is being instantiated. Any | |
| /// backend specific [WorldImpact] of this is returned. | |
| WorldImpact registerInstantiatedClass(ClassEntity cls); | |
| /// Called to notify to the backend that a class is implemented by an | |
| /// instantiated class. Any backend specific [WorldImpact] of this is | |
| /// returned. | |
| WorldImpact registerImplementedClass(ClassEntity cls); | |
| /// Called to register that a static function has been closurized. Any backend | |
| /// specific [WorldImpact] of this is returned. | |
| WorldImpact registerGetOfStaticFunction(); | |
| /// Called to register that [function] has been closurized. Any backend | |
| /// specific [WorldImpact] of this is returned. | |
| WorldImpact registerClosurizedMember(FunctionEntity function); | |
| /// Called to register that [element] is statically known to be used. Any | |
| /// backend specific [WorldImpact] of this is returned. | |
| WorldImpact registerUsedElement(MemberEntity member); | |
| /// Called to register that [value] is statically known to be used. Any | |
| /// backend specific [WorldImpact] of this is returned. | |
| WorldImpact registerUsedConstant(ConstantValue value); | |
| void onQueueOpen( | |
| Enqueuer enqueuer, FunctionEntity mainMethod, Iterable<Uri> libraries); | |
| /// Called when [enqueuer]'s queue is empty, but before it is closed. | |
| /// | |
| /// This is used, for example, by the JS backend to enqueue additional | |
| /// elements needed for reflection. [recentClasses] is a collection of | |
| /// all classes seen for the first time by the [enqueuer] since the last call | |
| /// to [onQueueEmpty]. | |
| /// | |
| /// A return value of `true` indicates that [recentClasses] has been | |
| /// processed and its elements do not need to be seen in the next round. When | |
| /// `false` is returned, [onQueueEmpty] will be called again once the | |
| /// resolution queue has drained and [recentClasses] will be a superset of the | |
| /// current value. | |
| /// | |
| /// There is no guarantee that a class is only present once in | |
| /// [recentClasses], but every class seen by the [enqueuer] will be present in | |
| /// [recentClasses] at least once. | |
| bool onQueueEmpty(Enqueuer enqueuer, Iterable<ClassEntity> recentClasses); | |
| /// Called when to the queue has been closed. | |
| void onQueueClosed(); | |
| /// Called after the queue has been emptied. | |
| void logSummary(void log(String message)); | |
| } | |
| abstract class EnqueuerImpl extends Enqueuer { | |
| CompilerTask get task; | |
| void checkClass(ClassEntity cls); | |
| void processStaticUse(StaticUse staticUse); | |
| void processTypeUse(TypeUse typeUse); | |
| void processDynamicUse(DynamicUse dynamicUse); | |
| void processConstantUse(ConstantUse constantUse); | |
| EnqueuerListener get listener; | |
| // TODO(johnniwinther): Initialize [_impactStrategy] to `null`. | |
| ImpactStrategy _impactStrategy = const ImpactStrategy(); | |
| ImpactStrategy get impactStrategy => _impactStrategy; | |
| void open(ImpactStrategy impactStrategy, FunctionEntity mainMethod, | |
| Iterable<Uri> libraries) { | |
| _impactStrategy = impactStrategy; | |
| listener.onQueueOpen(this, mainMethod, libraries); | |
| } | |
| void close() { | |
| // TODO(johnniwinther): Set [_impactStrategy] to `null` and [queueIsClosed] | |
| // to `true` here. | |
| _impactStrategy = const ImpactStrategy(); | |
| listener.onQueueClosed(); | |
| } | |
| /// Check enqueuer consistency after the queue has been closed. | |
| bool checkEnqueuerConsistency(ElementEnvironment elementEnvironment) { | |
| task.measure(() { | |
| // Run through the classes and see if we need to enqueue more methods. | |
| for (ClassEntity classElement | |
| in worldBuilder.directlyInstantiatedClasses) { | |
| for (ClassEntity currentClass = classElement; | |
| currentClass != null; | |
| currentClass = elementEnvironment.getSuperClass(currentClass)) { | |
| checkClass(currentClass); | |
| } | |
| } | |
| }); | |
| return true; | |
| } | |
| } | |
| /// [Enqueuer] which is specific to resolution. | |
| class ResolutionEnqueuer extends EnqueuerImpl { | |
| static const ImpactUseCase IMPACT_USE = | |
| const ImpactUseCase('ResolutionEnqueuer'); | |
| final CompilerTask task; | |
| final String name; | |
| final CompilerOptions _options; | |
| final EnqueuerListener listener; | |
| final Set<ClassEntity> _recentClasses = new Setlet<ClassEntity>(); | |
| bool _recentConstants = false; | |
| final ResolutionEnqueuerWorldBuilder _worldBuilder; | |
| final WorkItemBuilder _workItemBuilder; | |
| final DiagnosticReporter _reporter; | |
| bool queueIsClosed = false; | |
| WorldImpactVisitor _impactVisitor; | |
| final Queue<WorkItem> _queue = new Queue<WorkItem>(); | |
| // If not `null` this is called when the queue has been emptied. It allows for | |
| // applying additional impacts before re-emptying the queue. | |
| void Function() onEmptyForTesting; | |
| ResolutionEnqueuer(this.task, this._options, this._reporter, this.listener, | |
| this._worldBuilder, this._workItemBuilder, | |
| [this.name = 'resolution enqueuer']) { | |
| _impactVisitor = new EnqueuerImplImpactVisitor(this); | |
| } | |
| ResolutionWorldBuilder get worldBuilder => _worldBuilder; | |
| bool get queueIsEmpty => _queue.isEmpty; | |
| @override | |
| void checkQueueIsEmpty() { | |
| if (_queue.isNotEmpty) { | |
| failedAt(_queue.first.element, "$name queue is not empty."); | |
| } | |
| } | |
| Iterable<ClassEntity> get processedClasses => _worldBuilder.processedClasses; | |
| void applyImpact(WorldImpact worldImpact, {var impactSource}) { | |
| if (worldImpact.isEmpty) return; | |
| impactStrategy.visitImpact( | |
| impactSource, worldImpact, _impactVisitor, impactUse); | |
| } | |
| void _registerInstantiatedType(InterfaceType type, | |
| {ConstructorEntity constructor, | |
| bool nativeUsage: false, | |
| bool globalDependency: false, | |
| bool isRedirection: false}) { | |
| task.measure(() { | |
| _worldBuilder.registerTypeInstantiation(type, _applyClassUse, | |
| constructor: constructor, isRedirection: isRedirection); | |
| listener.registerInstantiatedType(type, | |
| isGlobal: globalDependency, nativeUsage: nativeUsage); | |
| }); | |
| } | |
| bool checkNoEnqueuedInvokedInstanceMethods( | |
| ElementEnvironment elementEnvironment) { | |
| return checkEnqueuerConsistency(elementEnvironment); | |
| } | |
| void checkClass(ClassEntity cls) { | |
| _worldBuilder.processClassMembers(cls, | |
| (MemberEntity member, EnumSet<MemberUse> useSet) { | |
| if (useSet.isNotEmpty) { | |
| _reporter.internalError(member, | |
| 'Unenqueued use of $member: ${useSet.iterable(MemberUse.values)}'); | |
| } | |
| }); | |
| } | |
| /// Callback for applying the use of a [member]. | |
| void _applyMemberUse(Entity member, EnumSet<MemberUse> useSet) { | |
| if (useSet.contains(MemberUse.NORMAL)) { | |
| _addToWorkList(member); | |
| } | |
| if (useSet.contains(MemberUse.CLOSURIZE_INSTANCE)) { | |
| _registerClosurizedMember(member); | |
| } | |
| if (useSet.contains(MemberUse.CLOSURIZE_STATIC)) { | |
| applyImpact(listener.registerGetOfStaticFunction(), | |
| impactSource: 'get of static function'); | |
| } | |
| } | |
| /// Callback for applying the use of a [cls]. | |
| void _applyClassUse(ClassEntity cls, EnumSet<ClassUse> useSet) { | |
| if (useSet.contains(ClassUse.INSTANTIATED)) { | |
| _recentClasses.add(cls); | |
| _worldBuilder.processClassMembers(cls, _applyMemberUse); | |
| // We only tell the backend once that [cls] was instantiated, so | |
| // any additional dependencies must be treated as global | |
| // dependencies. | |
| applyImpact(listener.registerInstantiatedClass(cls), | |
| impactSource: 'instantiated class'); | |
| } | |
| if (useSet.contains(ClassUse.IMPLEMENTED)) { | |
| applyImpact(listener.registerImplementedClass(cls), | |
| impactSource: 'implemented class'); | |
| } | |
| } | |
| void processDynamicUse(DynamicUse dynamicUse) { | |
| task.measure(() { | |
| _worldBuilder.registerDynamicUse(dynamicUse, _applyMemberUse); | |
| }); | |
| } | |
| void processConstantUse(ConstantUse constantUse) { | |
| task.measure(() { | |
| if (_worldBuilder.registerConstantUse(constantUse)) { | |
| applyImpact(listener.registerUsedConstant(constantUse.value), | |
| impactSource: 'constant use'); | |
| _recentConstants = true; | |
| } | |
| }); | |
| } | |
| void processStaticUse(StaticUse staticUse) { | |
| _worldBuilder.registerStaticUse(staticUse, _applyMemberUse); | |
| // TODO(johnniwinther): Add `ResolutionWorldBuilder.registerConstructorUse` | |
| // for these: | |
| switch (staticUse.kind) { | |
| case StaticUseKind.CONSTRUCTOR_INVOKE: | |
| case StaticUseKind.CONST_CONSTRUCTOR_INVOKE: | |
| _registerInstantiatedType(staticUse.type, | |
| constructor: staticUse.element, globalDependency: false); | |
| break; | |
| case StaticUseKind.REDIRECTION: | |
| _registerInstantiatedType(staticUse.type, | |
| constructor: staticUse.element, | |
| globalDependency: false, | |
| isRedirection: true); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| void processTypeUse(TypeUse typeUse) { | |
| DartType type = typeUse.type; | |
| switch (typeUse.kind) { | |
| case TypeUseKind.INSTANTIATION: | |
| _registerInstantiatedType(type, globalDependency: false); | |
| break; | |
| case TypeUseKind.NATIVE_INSTANTIATION: | |
| _registerInstantiatedType(type, | |
| nativeUsage: true, globalDependency: true); | |
| break; | |
| case TypeUseKind.IS_CHECK: | |
| case TypeUseKind.CATCH_TYPE: | |
| _registerIsCheck(type); | |
| break; | |
| case TypeUseKind.AS_CAST: | |
| if (!_options.omitAsCasts) { | |
| _registerIsCheck(type); | |
| } | |
| break; | |
| case TypeUseKind.IMPLICIT_CAST: | |
| if (_options.implicitDowncastCheckPolicy.isEmitted) { | |
| _registerIsCheck(type); | |
| } | |
| break; | |
| case TypeUseKind.PARAMETER_CHECK: | |
| if (_options.parameterCheckPolicy.isEmitted) { | |
| _registerIsCheck(type); | |
| } | |
| break; | |
| case TypeUseKind.TYPE_LITERAL: | |
| if (type is TypeVariableType) { | |
| _worldBuilder.registerTypeVariableTypeLiteral(type); | |
| } | |
| break; | |
| case TypeUseKind.RTI_VALUE: | |
| case TypeUseKind.TYPE_ARGUMENT: | |
| failedAt(CURRENT_ELEMENT_SPANNABLE, "Unexpected type use: $typeUse."); | |
| break; | |
| } | |
| } | |
| void _registerIsCheck(DartType type) { | |
| _worldBuilder.registerIsCheck(type); | |
| } | |
| void _registerClosurizedMember(MemberEntity element) { | |
| assert(element.isInstanceMember); | |
| applyImpact(listener.registerClosurizedMember(element), | |
| impactSource: 'closurized member'); | |
| _worldBuilder.registerClosurizedMember(element); | |
| } | |
| void _forEach(void f(WorkItem work)) { | |
| do { | |
| while (_queue.isNotEmpty) { | |
| // TODO(johnniwinther): Find an optimal process order. | |
| WorkItem work = _queue.removeLast(); | |
| if (!_worldBuilder.isMemberProcessed(work.element)) { | |
| f(work); | |
| _worldBuilder.registerProcessedMember(work.element); | |
| } | |
| } | |
| List<ClassEntity> recents = _recentClasses.toList(growable: false); | |
| _recentClasses.clear(); | |
| _recentConstants = false; | |
| if (!_onQueueEmpty(recents)) { | |
| _recentClasses.addAll(recents); | |
| } | |
| } while ( | |
| _queue.isNotEmpty || _recentClasses.isNotEmpty || _recentConstants); | |
| } | |
| void forEach(void f(WorkItem work)) { | |
| _forEach(f); | |
| if (onEmptyForTesting != null) { | |
| onEmptyForTesting(); | |
| _forEach(f); | |
| } | |
| } | |
| void logSummary(void log(String message)) { | |
| log('Resolved ${processedEntities.length} elements.'); | |
| listener.logSummary(log); | |
| } | |
| String toString() => 'Enqueuer($name)'; | |
| Iterable<MemberEntity> get processedEntities => | |
| _worldBuilder.processedMembers; | |
| ImpactUseCase get impactUse => IMPACT_USE; | |
| bool get isResolutionQueue => true; | |
| /// Registers [entity] as processed by the resolution enqueuer. Used only for | |
| /// testing. | |
| void registerProcessedElementInternal(MemberEntity entity) { | |
| _worldBuilder.registerProcessedMember(entity); | |
| } | |
| /// Create a [WorkItem] for [entity] and add it to the work list if it has not | |
| /// already been processed. | |
| void _addToWorkList(MemberEntity entity) { | |
| if (_worldBuilder.isMemberProcessed(entity)) return; | |
| WorkItem workItem = _workItemBuilder.createWorkItem(entity); | |
| if (workItem == null) return; | |
| if (queueIsClosed) { | |
| failedAt( | |
| entity, "Resolution work list is closed. Trying to add $entity."); | |
| } | |
| applyImpact(listener.registerUsedElement(entity), | |
| impactSource: 'used element'); | |
| _worldBuilder.registerUsedElement(entity); | |
| _queue.add(workItem); | |
| } | |
| /// [_onQueueEmpty] is called whenever the queue is drained. [recentClasses] | |
| /// contains the set of all classes seen for the first time since | |
| /// [_onQueueEmpty] was called last. A return value of [true] indicates that | |
| /// the [recentClasses] have been processed and may be cleared. If [false] is | |
| /// returned, [_onQueueEmpty] will be called once the queue is empty again (or | |
| /// still empty) and [recentClasses] will be a superset of the current value. | |
| bool _onQueueEmpty(Iterable<ClassEntity> recentClasses) { | |
| return listener.onQueueEmpty(this, recentClasses); | |
| } | |
| } | |
| class EnqueuerImplImpactVisitor implements WorldImpactVisitor { | |
| final EnqueuerImpl enqueuer; | |
| EnqueuerImplImpactVisitor(this.enqueuer); | |
| @override | |
| void visitDynamicUse(DynamicUse dynamicUse) { | |
| enqueuer.processDynamicUse(dynamicUse); | |
| } | |
| @override | |
| void visitStaticUse(StaticUse staticUse) { | |
| enqueuer.processStaticUse(staticUse); | |
| } | |
| @override | |
| void visitTypeUse(TypeUse typeUse) { | |
| enqueuer.processTypeUse(typeUse); | |
| } | |
| @override | |
| void visitConstantUse(ConstantUse constantUse) { | |
| enqueuer.processConstantUse(constantUse); | |
| } | |
| } | |
| /// Interface for creating work items for enqueued member entities. | |
| abstract class WorkItemBuilder { | |
| WorkItem createWorkItem(covariant MemberEntity entity); | |
| } |