// 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/codegen.dart'; 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 'inferrer/types.dart'; import 'js_backend/backend.dart' show CodegenInputs; import 'js_backend/enqueuer.dart'; import 'universe/member_usage.dart'; import 'universe/resolution_world_builder.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; @override 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, CodegenInputs codegenInputs, CodegenResults codegenResults) { Enqueuer enqueuer = compiler.backend.createCodegenEnqueuer(this, compiler, closedWorld, globalInferenceResults, codegenInputs, codegenResults) ..onEmptyForTesting = compiler.onCodegenQueueEmptyForTesting; if (retainDataForTesting) { codegenEnqueuerForTesting = enqueuer; } return enqueuer; } } abstract class Enqueuer { /// If `true` the checking for unenqueued members is skipped. The current /// implementation registers parameter usages as a side-effect so unit /// testing of member usage we need to test both with and without the /// enqueuer check. // TODO(johnniwinther): [checkEnqueuerConsistency] should not have // side-effects. static bool skipEnqueuerCheckForTesting = false; WorldBuilder get worldBuilder; void open(ImpactStrategy impactStrategy, FunctionEntity mainMethod, Iterable 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 get processedEntities; Iterable 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 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 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; @override void open(ImpactStrategy impactStrategy, FunctionEntity mainMethod, Iterable libraries) { _impactStrategy = impactStrategy; listener.onQueueOpen(this, mainMethod, libraries); } @override 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.measureSubtask('resolution.check', () { // 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'); @override final CompilerTask task; final String name; final CompilerOptions _options; @override final EnqueuerListener listener; final Set _recentClasses = new Setlet(); bool _recentConstants = false; final ResolutionEnqueuerWorldBuilder _worldBuilder; final WorkItemBuilder _workItemBuilder; final DiagnosticReporter _reporter; @override bool queueIsClosed = false; WorldImpactVisitor _impactVisitor; final Queue _queue = new Queue(); // 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); } @override ResolutionWorldBuilder get worldBuilder => _worldBuilder; @override bool get queueIsEmpty => _queue.isEmpty; @override void checkQueueIsEmpty() { if (_queue.isNotEmpty) { failedAt(_queue.first.element, "$name queue is not empty."); } } @override Iterable get processedClasses => _worldBuilder.processedClasses; @override 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}) { task.measureSubtask('resolution.typeUse', () { _worldBuilder.registerTypeInstantiation(type, _applyClassUse, constructor: constructor); listener.registerInstantiatedType(type, isGlobal: globalDependency, nativeUsage: nativeUsage); }); } @override bool checkNoEnqueuedInvokedInstanceMethods( ElementEnvironment elementEnvironment) { if (Enqueuer.skipEnqueuerCheckForTesting) return true; return checkEnqueuerConsistency(elementEnvironment); } @override void checkClass(ClassEntity cls) { _worldBuilder.processClassMembers(cls, (MemberEntity member, EnumSet useSet) { if (useSet.isNotEmpty) { _reporter.internalError(member, 'Unenqueued use of $member: ${useSet.iterable(MemberUse.values)}'); } }, checkEnqueuerConsistency: true); } /// Callback for applying the use of a [member]. void _applyMemberUse(Entity member, EnumSet 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 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'); } } @override void processDynamicUse(DynamicUse dynamicUse) { task.measureSubtask('resolution.dynamicUse', () { _worldBuilder.registerDynamicUse(dynamicUse, _applyMemberUse); }); } @override void processConstantUse(ConstantUse constantUse) { task.measureSubtask('resolution.constantUse', () { if (_worldBuilder.registerConstantUse(constantUse)) { applyImpact(listener.registerUsedConstant(constantUse.value), impactSource: 'constant use'); _recentConstants = true; } }); } @override void processStaticUse(StaticUse staticUse) { task.measureSubtask('resolution.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; default: break; } }); } @override void processTypeUse(TypeUse typeUse) { DartType type = typeUse.type; switch (typeUse.kind) { case TypeUseKind.INSTANTIATION: case TypeUseKind.CONST_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 recents = _recentClasses.toList(growable: false); _recentClasses.clear(); _recentConstants = false; if (!_onQueueEmpty(recents)) { _recentClasses.addAll(recents); } } while ( _queue.isNotEmpty || _recentClasses.isNotEmpty || _recentConstants); } @override void forEach(void f(WorkItem work)) { _forEach(f); if (onEmptyForTesting != null) { onEmptyForTesting(); _forEach(f); } } @override void logSummary(void log(String message)) { log('Resolved ${processedEntities.length} elements.'); listener.logSummary(log); } @override String toString() => 'Enqueuer($name)'; @override Iterable get processedEntities => _worldBuilder.processedMembers; @override ImpactUseCase get impactUse => IMPACT_USE; @override 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 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); }