blob: 69cd2c4b99e852331e894abade1336482daaeeb1 [file] [log] [blame]
// Copyright (c) 2021, 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 'entity_data.dart';
import 'entity_data_info.dart';
import 'import_set.dart';
import 'work_queue.dart';
import '../common.dart';
import '../compiler.dart' show Compiler;
import '../elements/entities.dart';
import '../kernel/element_map.dart';
import '../world.dart' show KClosedWorld;
/// Manages the state of the [EntityData] model. Every class, member, constant,
/// etc, is wrapped in the deferred loading algorithm by an [EntityData] which
/// knows how to collect dependencies for a given [Entity].
class AlgorithmState {
final Compiler compiler;
final KernelToElementMap elementMap;
final KClosedWorld closedWorld;
final EntityDataRegistry registry;
final Map<EntityData, ImportSet> entityToSet = {};
final Map<EntityData, EntityDataInfo> entityDataToInfo = {};
final ImportSetLattice importSets;
final WorkQueue queue;
AlgorithmState._(this.closedWorld, this.elementMap, this.compiler,
this.importSets, this.registry)
: queue = WorkQueue(importSets, registry);
/// Factory function to create and initialize a [AlgorithmState].
factory AlgorithmState.create(
FunctionEntity main,
Compiler compiler,
KernelToElementMap elementMap,
KClosedWorld closedWorld,
ImportSetLattice importSets) {
var entityDataState = AlgorithmState._(
closedWorld, elementMap, compiler, importSets, EntityDataRegistry());
entityDataState.initialize(main);
return entityDataState;
}
/// Given an [EntityData], an [oldSet] and a [newSet], either ignore the
/// update, apply the update immediately if we can avoid unions, or apply the
/// update later if we cannot. For more detail on [oldSet] and [newSet],
/// please see the comment in deferred_load.dart.
void update(EntityData entityData, ImportSet oldSet, ImportSet newSet) {
ImportSet currentSet = entityToSet[entityData];
// If [currentSet] == [newSet], then currentSet must include all of newSet.
if (currentSet == newSet) return;
// Elements in the main output unit always remain there.
if (currentSet == importSets.mainSet) return;
// If [currentSet] == [oldSet], then we can safely update the
// [entityToSet] map for [entityData] to [newSet] in a single assignment.
// If not, then if we are supposed to update [entityData] recursively, we add
// it back to the queue so that we can re-enter [update] later after
// performing a union. If we aren't supposed to update recursively, we just
// perform the union inline.
if (currentSet == oldSet) {
// Continue recursively updating from [oldSet] to [newSet].
entityToSet[entityData] = newSet;
updateDependencies(entityData, oldSet, newSet);
} else if (entityData.needsRecursiveUpdate) {
assert(
// Invariant: we must mark main before we mark any deferred import.
newSet != importSets.mainSet || oldSet != null,
failedAt(
NO_LOCATION_SPANNABLE,
"Tried to assign to the main output unit, but it was assigned "
"to $currentSet."));
queue.addEntityData(entityData, newSet);
} else {
entityToSet[entityData] = importSets.union(currentSet, newSet);
}
}
/// Returns the [EntityDataInfo] associated with a given [EntityData].
/// Note: In the event of a cache miss, i.e. the first time we ever see a new
/// [EntityData], we will add all reachable deferred roots to the queue for
/// processing.
EntityDataInfo getInfo(EntityData data) {
// Check for cached [EntityDataInfo], otherwise create a new one and
// collect dependencies.
var info = entityDataToInfo[data];
if (info == null) {
var infoBuilder =
EntityDataInfoBuilder(closedWorld, elementMap, compiler, registry);
var visitor = EntityDataInfoVisitor(infoBuilder);
data.accept(visitor);
info = infoBuilder.info;
entityDataToInfo[data] = info;
// This is the first time we have seen this [EntityData] before so process
// all deferred roots.
info.deferredRoots.forEach((entity, imports) {
for (ImportEntity deferredImport in imports) {
queue.addEntityData(entity, importSets.singleton(deferredImport));
}
});
}
return info;
}
/// Updates the dependencies of a given [EntityData] from [oldSet] to
/// [newSet].
void updateDependencies(
EntityData entityData, ImportSet oldSet, ImportSet newSet) {
var info = getInfo(entityData);
// Process all direct dependencies.
for (var entity in info.directDependencies) {
update(entity, oldSet, newSet);
}
}
/// Initializes the [AlgorithmState] assuming that [main] is the main entry
/// point of a given program.
void initialize(FunctionEntity main) {
// Add `main` and their recursive dependencies to the main output unit.
// We do this upfront to avoid wasting time visiting these elements when
// analyzing deferred imports.
queue.addMember(main, importSets.mainSet);
// Also add "global" dependencies to the main output unit. These are
// things that the backend needs but cannot associate with a particular
// element. This set also contains elements for which we lack precise
// information.
for (MemberEntity element
in closedWorld.backendUsage.globalFunctionDependencies) {
queue.addMember(element, importSets.mainSet);
}
for (ClassEntity element
in closedWorld.backendUsage.globalClassDependencies) {
queue.addClass(element, importSets.mainSet);
}
// Empty queue.
while (queue.isNotEmpty) {
queue.processNextItem(this);
}
}
}