blob: 4a012ace8e85a29af5f81329a7886eb401d83be5 [file]
// Copyright (c) 2025, 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:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart';
import 'package:kernel/library_index.dart';
import 'package:vm/metadata/procedure_attributes.dart';
import '../modules.dart';
import 'devirtualization_oracle.dart';
class DependenciesCollector {
final CoreTypes _coreTypes;
final ClosedWorldClassHierarchy _classHierarchy;
final DevirtualizionOracle _devirtualizionOracle;
final DeferredModuleLoadingMap _loadingMap;
final bool _assertsEnabled;
final Map<TreeNode, ProcedureAttributesMetadata> procedureAttributeMetadata;
late final _checkLibraryIsLoadedFromLoadId = _coreTypes.index.getProcedure(
'dart:_internal',
LibraryIndex.topLevel,
'checkLibraryIsLoadedFromLoadId',
);
late final _loadLibraryFromLoadId = _coreTypes.index.getProcedure(
'dart:_internal',
LibraryIndex.topLevel,
'loadLibraryFromLoadId',
);
late final _exportWasmFunction = _coreTypes.index.getTopLevelMember(
'dart:_internal',
'exportWasmFunction',
);
DependenciesCollector(
this.procedureAttributeMetadata,
this._coreTypes,
this._classHierarchy,
this._devirtualizionOracle,
this._loadingMap,
this._assertsEnabled,
);
/// Returns the set of constants referred to by the (possibly composed)
/// [constant].
DirectConstantDependencies directConstantDependencies(Constant constant) {
Reference? extraReference;
if (constant is InstanceConstant) {
extraReference = constant.classReference;
} else if (constant is TearOffConstant) {
extraReference = constant.targetReference;
} else {
// The classes needed for {List,Map,Set,Record}Constants are
// marked as @pragma('wasm:entry-point') and do not have to be explicitly
// modeled as dependencies (they land in the root unit).
}
final children = <Constant>{};
constant.visitChildren(_ConstantDependenciesCollector._(children));
return DirectConstantDependencies(children, extraReference);
}
DirectReferenceDependencies directReferenceDependencies(Reference reference) {
final TreeNode node = reference.node!;
final deps = DirectReferenceDependencies();
if (node is Class) {
_enqueueInstanceMembers(node, deps);
return deps;
}
final collector = _ReferenceDependenciesCollector._(
procedureAttributeMetadata,
_recognizeDeferredLoadingGuard,
_disableAllGuards,
_classHierarchy,
_devirtualizionOracle,
_assertsEnabled,
reference,
deps,
);
// We collect dependencies of [node] and therefore only have to visit
// AST elements that represent code (such as `FunctionNode`, `Initializer`).
if (node is Procedure) {
node.function.accept(collector);
return deps;
}
if (node is Constructor) {
node.function.accept(collector);
for (final init in node.initializers) {
init.accept(collector);
}
for (final field in node.enclosingClass.fields) {
if (field.isInstanceMember) {
field.initializer?.accept(collector);
}
}
collector.addReference(node.enclosingClass.reference);
return deps;
}
if (node is Field) {
if (node.isInstanceMember) {
// Instance field getters/setters have no dependencies: The field
// initializers are initialized at constructor invocation time not at
// field access time. The field itself doesn't have a storage location
// (like a static field).
assert(
node.getterReference == reference ||
node.hasSetter && node.setterReference == reference,
);
} else {
if (node.getterReference == reference) {
// A static getter may invoke the initializer and accesses the storage
// location of the field.
collector.addReference(node.fieldReference);
node.initializer?.accept(collector);
} else if (node.setterReference == reference) {
// A static setter only accesses the storage location of the field.
collector.addReference(node.fieldReference);
} else {
assert(node.fieldReference == reference);
// The field storage itself has no dependencies.
}
}
return deps;
}
throw UnsupportedError('Unexpected reference: $reference');
}
LibraryDependency? _recognizeDeferredLoadingGuard(StaticInvocation node) {
final target = node.target;
if (target != _loadLibraryFromLoadId &&
target != _checkLibraryIsLoadedFromLoadId) {
return null;
}
if (target == _loadLibraryFromLoadId && node.parent is! AwaitExpression) {
// Only a `await D.loadLibrary()` guarantees `D` is loaded.
// A `var future = D.loadLibrary()` doesn't guarantee `D` is loaded.
return null;
}
final args = node.arguments.positional;
final loadId = (args[0] as IntLiteral).value;
assert(loadId > 0); // 0 means root
return _loadingMap.loadIdToDeferredImport[loadId];
}
bool _disableAllGuards(StaticInvocation node) {
return node.target == _exportWasmFunction;
}
void _enqueueInstanceMembers(Class klass, DirectReferenceDependencies deps) {
final superReference = klass.superclass?.reference;
if (superReference != null) {
deps.references.add(superReference);
}
for (final m in klass.members) {
if (m.isInstanceMember && !m.isAbstract) {
if (m is Field) {
if (!_devirtualizionOracle.isAlwaysStaticallyDispatchedTo(
m.getterReference,
)) {
deps.references.add(m.getterReference);
}
if (m.hasSetter) {
if (!_devirtualizionOracle.isAlwaysStaticallyDispatchedTo(
m.setterReference!,
)) {
deps.references.add(m.setterReference!);
}
}
continue;
}
assert(m is Procedure);
if (!_devirtualizionOracle.isAlwaysStaticallyDispatchedTo(
m.reference,
)) {
deps.references.add(m.reference);
}
}
}
}
}
class _ConstantDependenciesCollector extends RecursiveVisitor {
final Set<Constant> _directChildren;
_ConstantDependenciesCollector._(this._directChildren);
@override
void defaultConstantReference(Constant node) {
if (node is IntConstant || node is DoubleConstant) {
// Integers and doubles don't have identity, they are compared by value.
return;
}
_directChildren.add(node);
}
}
class _ReferenceDependenciesCollector extends RecursiveVisitor {
late final Map<TreeNode, ProcedureAttributesMetadata>
_procedureAttributeMetadata;
final LibraryDependency? Function(StaticInvocation node)
_recognizeDeferredLoadingGuard;
final bool Function(StaticInvocation node) _disableAllGuards;
final DevirtualizionOracle _devirtualizionOracle;
final ClosedWorldClassHierarchy _classHierarchy;
final bool _assertsEnabled;
final Reference reference;
final DirectReferenceDependencies deps;
final List<LibraryDependency> _activeLoadGuards = [];
_ReferenceDependenciesCollector._(
this._procedureAttributeMetadata,
this._recognizeDeferredLoadingGuard,
this._disableAllGuards,
this._classHierarchy,
this._devirtualizionOracle,
this._assertsEnabled,
this.reference,
this.deps,
);
// ---------------------------------------------------------------------------
// Ensure all AST nodes are handled - in case future AST nodes are added, they
// may affect control flow or dependency collection, so we want to know about
// them by throwing here.
// ---------------------------------------------------------------------------
@override
void defaultExpression(Expression node) => throw UnimplementedError();
@override
void defaultStatement(Statement node) => throw UnimplementedError();
// ---------------------------------------------------------------------------
// Only node that needs dependency collection & load active load guard
// handling.
// ---------------------------------------------------------------------------
@override
void visitStaticInvocation(StaticInvocation node) {
if (_disableAllGuards(node)) {
// If a function looks like this:
// ```
// void foo() {
// ...
// D.baz();
// ...
// _exportWasmFunction(baz);
// ...
// }
//
// @pragma('wasm:weak-export')
// external ... baz(...);
// ```
// Then the intrinsifier will recognize `_exportWasmFunction(baz)`
// specially and export the `baz` function from the same module as
// `foo`. We therefore do not want `baz` to land in another module.
final saved = _activeLoadGuards.toList();
_activeLoadGuards.clear();
node.visitChildren(this);
addReference(node.targetReference);
_activeLoadGuards.addAll(saved);
return;
}
node.visitChildren(this);
addReference(node.targetReference);
if (_recognizeDeferredLoadingGuard(node) case var guard?) {
_activeLoadGuards.add(guard);
}
}
// ---------------------------------------------------------------------------
// AST Expressions & Statements that have merge points in them which need to
// save & restore active load guards.
// ---------------------------------------------------------------------------
@override
void visitAssertBlock(AssertBlock node) {
if (_assertsEnabled) {
node.visitChildren(this);
// Either the assert throws (in which case code after the assert is
// unreachable) or the load guards produced in the assert evaluation still
// hold.
}
}
@override
void visitAssertStatement(AssertStatement node) {
if (_assertsEnabled) {
node.visitChildren(this);
// Either the assert throws (in which case code after the assert is
// unreachable) or the load guards produced in the assert evaluation still
// hold.
}
}
@override
void visitLabeledStatement(LabeledStatement node) {
final saved = _activeLoadGuards.length;
node.body.accept(this);
_activeLoadGuards.length = saved;
}
@override
void visitWhileStatement(WhileStatement node) {
// We execute the condition at least once.
node.condition.accept(this);
final saved = _activeLoadGuards.length;
node.body.accept(this);
_activeLoadGuards.length = saved;
}
@override
void visitDoStatement(DoStatement node) {
// We execute the body & condition at least once.
//
// NOTE: If the body contains a `break` it will target a separate
// [LabeledStatement] which already handles re-setting guards.
node.body.accept(this);
node.condition.accept(this);
}
@override
void visitForStatement(ForStatement node) {
// We initialize the variables always.
for (final variable in node.variableInitializations) {
variable.accept(this);
}
// We alway execute the condition at least once.
node.condition?.accept(this);
final saved = _activeLoadGuards.length;
node.body.accept(this);
// If we perform updates then the body must have successfully been
// executed.
// (NOTE: break/continue are handled in kernel via lowering to
// [LabeledStatement]s which will save&restore guards)
for (final update in node.updates) {
update.accept(this);
}
_activeLoadGuards.length = saved;
}
@override
void visitForInStatement(ForInStatement node) {
node.iterable.accept(this);
final saved = _activeLoadGuards.length;
node.body.accept(this);
_activeLoadGuards.length = saved;
}
@override
void visitSwitchStatement(SwitchStatement node) {
node.expression.accept(this);
final saved = _activeLoadGuards.length;
for (final c in node.cases) {
for (final expression in c.expressions) {
expression.accept(this);
_activeLoadGuards.length = saved;
}
c.body.accept(this);
_activeLoadGuards.length = saved;
}
assert(_activeLoadGuards.length == saved);
}
@override
void visitIfStatement(IfStatement node) {
node.condition.accept(this);
final saved = _activeLoadGuards.length;
node.then.accept(this);
_activeLoadGuards.length = saved;
node.otherwise?.accept(this);
_activeLoadGuards.length = saved;
}
@override
void visitTryCatch(TryCatch node) {
final saved = _activeLoadGuards.length;
node.body.accept(this);
_activeLoadGuards.length = saved;
for (final c in node.catches) {
c.body.accept(this);
_activeLoadGuards.length = saved;
}
}
@override
void visitTryFinally(TryFinally node) {
final saved = _activeLoadGuards.length;
node.body.accept(this);
_activeLoadGuards.length = saved;
node.finalizer.accept(this);
// NOTE: Finalizer will always be executed and as such any load guard in it
// will continue to hold after the finally block.
}
@override
void visitLogicalExpression(LogicalExpression node) {
node.left.accept(this);
final saved = _activeLoadGuards.length;
node.right.accept(this);
_activeLoadGuards.length = saved;
}
@override
void visitConditionalExpression(ConditionalExpression node) {
node.condition.accept(this);
final saved = _activeLoadGuards.length;
node.then.accept(this);
_activeLoadGuards.length = saved;
node.otherwise.accept(this);
_activeLoadGuards.length = saved;
}
@override
void visitFunctionExpression(FunctionExpression node) {
final saved = _activeLoadGuards.length;
node.visitChildren(this);
_activeLoadGuards.length = saved;
}
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
final saved = _activeLoadGuards.length;
node.visitChildren(this);
_activeLoadGuards.length = saved;
}
@override
void visitBreakStatement(BreakStatement node) {
// Unreachable after [node].
}
@override
void visitContinueSwitchStatement(ContinueSwitchStatement node) {
// Unreachable after [node].
}
@override
void visitRethrow(Rethrow node) {
// Unreachable after [node].
}
@override
void visitThrow(Throw node) {
node.expression.accept(this);
// Unreachable after [node].
}
@override
void visitLoadLibrary(LoadLibrary node) =>
throw StateError('Should have been lowered by now');
@override
void visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) =>
throw StateError('Should have been lowered by now');
// ---------------------------------------------------------------------------
// Expressions that need to collect dependencies, but do not have control flow
// in them and therefore don't need load guard handling.
// ---------------------------------------------------------------------------
@override
void visitSuperPropertyGet(SuperPropertyGet node) {
// NOTE: Super calls are direct calls and as such don't need to call
// [addSelectorUse]/[addDynamicSelectorUse].
node.visitChildren(this);
_addSuperTargetReference(node.interfaceTarget, setter: false);
}
@override
void visitSuperPropertySet(SuperPropertySet node) {
// NOTE: Super calls are direct calls and as such don't need to call
// [addSelectorUse]/[addDynamicSelectorUse].
node.visitChildren(this);
_addSuperTargetReference(node.interfaceTarget, setter: true);
}
@override
void visitSuperMethodInvocation(SuperMethodInvocation node) {
// NOTE: Super calls are direct calls and as such don't need to call
// [addSelectorUse]/[addDynamicSelectorUse].
node.visitChildren(this);
_addSuperTargetReference(node.interfaceTarget, setter: false);
}
@override
void visitInstanceGet(InstanceGet node) {
node.visitChildren(this);
final target = _devirtualizionOracle.staticDispatchTargetForGet(node);
if (target != null) {
addReference(target);
} else {
addSelectorUse(node.interfaceTarget, getter: true);
}
}
@override
void visitInstanceSet(InstanceSet node) {
node.visitChildren(this);
final target = _devirtualizionOracle.staticDispatchTargetForSet(node);
if (target != null) {
addReference(target);
} else {
addSelectorUse(node.interfaceTarget, getter: false);
}
}
@override
void visitInstanceInvocation(InstanceInvocation node) {
node.visitChildren(this);
final target = _devirtualizionOracle.staticDispatchTargetForCall(node);
if (target != null) {
addReference(target);
} else {
addSelectorUse(node.interfaceTarget, getter: false);
}
}
@override
void visitInstanceTearOff(InstanceTearOff node) {
node.visitChildren(this);
// There's no [Reference] in pure Kernel AST to represent the tear-off of a
// method (**). So for the purpose of this code that works on pure Kernel
// AST and collects dependencies, the method and it's tear-off are one
// entity. We treat it as such by making any use of the method be a use of
// tear-off as well - and vice versa.
//
// (**) The dart2wasm backend code does use multiple [Reference]s to
// represent the same method, constructor etc - including tear-offs. Though
// this is in the backend.
addSelectorUse(node.interfaceTarget, getter: true);
addSelectorUse(node.interfaceTarget, getter: false);
}
@override
void visitDynamicGet(DynamicGet node) {
node.visitChildren(this);
addDynamicSelectorUse(node.name);
}
@override
void visitDynamicSet(DynamicSet node) {
node.visitChildren(this);
addDynamicSelectorUse(node.name);
}
@override
void visitDynamicInvocation(DynamicInvocation node) {
node.visitChildren(this);
addDynamicSelectorUse(node.name);
}
@override
void visitFunctionInvocation(FunctionInvocation node) {
// NOTE: [_Closure.call] is marked as `@pragma('wasm:entry-point')` and will
// therefore be considered a selector use by the root.
node.visitChildren(this);
}
@override
void visitStaticGet(StaticGet node) {
node.visitChildren(this);
addReference(node.targetReference);
}
@override
void visitStaticSet(StaticSet node) {
node.visitChildren(this);
addReference(node.targetReference);
}
@override
void visitConstructorInvocation(ConstructorInvocation node) {
node.visitChildren(this);
addReference(node.targetReference);
}
@override
void visitSuperInitializer(SuperInitializer node) {
node.visitChildren(this);
addReference(node.targetReference);
}
@override
void visitRedirectingInitializer(RedirectingInitializer node) {
node.visitChildren(this);
addReference(node.targetReference);
}
@override
void visitStaticTearOff(StaticTearOff node) {
node.visitChildren(this);
addReference(node.targetReference);
}
@override
void defaultDartType(DartType node) {
// Ignore: Dart2wasm doesn't defer RTI information atm.
}
@override
void visitSupertype(Supertype node) {
// Ignore: Dart2wasm doesn't defer RTI information atm.
}
@override
void visitNullLiteral(NullLiteral node) {
addConstant(NullConstant());
}
@override
void visitStringLiteral(StringLiteral node) {
addConstant(StringConstant(node.value));
}
@override
void visitBoolLiteral(BoolLiteral node) {
addConstant(BoolConstant(node.value));
}
@override
void visitIntLiteral(IntLiteral node) {
// Integers don't have identity, they are compared by value.
}
@override
void visitDoubleLiteral(DoubleLiteral node) {
// Doubles don't have identity, they are compared by value.
}
// The references needed by the codegen to handle
// {List,Map,Set,Record}Literals are all marked with
// @pragma('wasm:entry-point') and do not have to be explicitly
// modeled as dependencies (they land in the root unit).
@override
void visitConstantExpression(ConstantExpression node) {
final constant = node.constant;
if (constant is IntConstant || constant is DoubleConstant) {
// Integers and doubles don't have identity, they are compared by value.
return;
}
addConstant(constant);
}
void addReference(Reference used) {
if (_activeLoadGuards.isEmpty) {
if (deps.references.add(used)) {
deps.deferredReferences.remove(used);
}
return;
}
if (!deps.references.contains(used)) {
if (deps.deferredReferences[used] case final existingGuards?) {
existingGuards.add(_activeLoadGuards.last);
return;
}
deps.deferredReferences[used] = {_activeLoadGuards.last};
}
}
void addConstant(Constant used) {
if (_activeLoadGuards.isEmpty) {
if (deps.constants.add(used)) {
deps.deferredConstants.remove(used);
}
return;
}
if (!deps.constants.contains(used)) {
if (deps.deferredConstants[used] case final existingGuards?) {
existingGuards.add(_activeLoadGuards.last);
return;
}
deps.deferredConstants[used] = {_activeLoadGuards.last};
}
}
void _addSuperTargetReference(
Member interfaceTarget, {
required bool setter,
}) {
final member = _classHierarchy.getDispatchTarget(
(reference.asMember).enclosingClass!.superclass!,
interfaceTarget.name,
setter: setter,
)!;
if (setter) {
addReference(
member is Field ? member.setterReference! : member.reference,
);
} else {
addReference(member is Field ? member.getterReference : member.reference);
}
}
void addDynamicSelectorUse(Name used) {
if (_activeLoadGuards.isEmpty) {
if (deps.dynamicSelectors.add(used)) {
deps.deferredDynamicSelectors.remove(used);
}
return;
}
if (!deps.dynamicSelectors.contains(used)) {
(deps.deferredDynamicSelectors[used] ??= {}).add(_activeLoadGuards.last);
}
}
void addSelectorUse(Member member, {required bool getter}) {
final metadata = _procedureAttributeMetadata[member]!;
final selectorId = getter
? metadata.getterSelectorId
: metadata.methodOrSetterSelectorId;
if (_activeLoadGuards.isEmpty) {
if (deps.selectorIds.add(selectorId)) {
deps.deferredSelectorIds.remove(selectorId);
}
return;
}
if (!deps.selectorIds.contains(selectorId)) {
(deps.deferredSelectorIds[selectorId] ??= {}).add(_activeLoadGuards.last);
}
}
// ---------------------------------------------------------------------------
// Expressions & Statements that do not need special handling:
//
// * they don't introduce reference/constant/selector depencencies
// * they don't introduce control flow and as such: any load guard valid
// before the node is still valid after the node, any load guard activated
// in the children stays active after the node
// ---------------------------------------------------------------------------
@override
void visitExpressionStatement(ExpressionStatement node) =>
node.visitChildren(this);
@override
void visitBlock(Block node) => node.visitChildren(this);
@override
void visitEmptyStatement(EmptyStatement node) => node.visitChildren(this);
@override
void defaultVariable(Variable node) => node.visitChildren(this);
@override
void visitReturnStatement(ReturnStatement node) => node.visitChildren(this);
@override
void visitYieldStatement(YieldStatement node) => node.visitChildren(this);
@override
void visitVariableStatement(VariableStatement node) =>
node.visitChildren(this);
@override
void visitLet(Let node) => node.visitChildren(this);
@override
void visitAuxiliaryExpression(AuxiliaryExpression node) =>
throw UnimplementedError();
@override
void visitInvalidExpression(InvalidExpression node) =>
throw UnimplementedError();
@override
void visitVariableGet(VariableGet node) => node.visitChildren(this);
@override
void visitVariableSet(VariableSet node) => node.visitChildren(this);
@override
void visitFunctionTearOff(FunctionTearOff node) => node.visitChildren(this);
@override
void visitAbstractSuperPropertyGet(AbstractSuperPropertyGet node) =>
node.visitChildren(this);
@override
void visitAbstractSuperPropertySet(AbstractSuperPropertySet node) =>
node.visitChildren(this);
@override
void visitLocalFunctionInvocation(LocalFunctionInvocation node) =>
node.visitChildren(this);
@override
void visitInstanceGetterInvocation(InstanceGetterInvocation node) =>
node.visitChildren(this);
@override
void visitEqualsNull(EqualsNull node) => node.visitChildren(this);
@override
void visitEqualsCall(EqualsCall node) => node.visitChildren(this);
@override
void visitAbstractSuperMethodInvocation(AbstractSuperMethodInvocation node) =>
node.visitChildren(this);
@override
void visitRedirectingFactoryInvocation(RedirectingFactoryInvocation node) =>
node.visitChildren(this);
@override
void visitNot(Not node) => node.visitChildren(this);
@override
void visitNullCheck(NullCheck node) => node.visitChildren(this);
@override
void visitStringConcatenation(StringConcatenation node) =>
node.visitChildren(this);
@override
void visitListConcatenation(ListConcatenation node) =>
node.visitChildren(this);
@override
void visitSetConcatenation(SetConcatenation node) => node.visitChildren(this);
@override
void visitMapConcatenation(MapConcatenation node) => node.visitChildren(this);
@override
void visitInstanceCreation(InstanceCreation node) => node.visitChildren(this);
@override
void visitFileUriExpression(FileUriExpression node) =>
node.visitChildren(this);
@override
void visitIsExpression(IsExpression node) => node.visitChildren(this);
@override
void visitAsExpression(AsExpression node) => node.visitChildren(this);
@override
void visitSymbolLiteral(SymbolLiteral node) => node.visitChildren(this);
@override
void visitTypeLiteral(TypeLiteral node) => node.visitChildren(this);
@override
void visitThisExpression(ThisExpression node) => node.visitChildren(this);
@override
void visitListLiteral(ListLiteral node) => node.visitChildren(this);
@override
void visitSetLiteral(SetLiteral node) => node.visitChildren(this);
@override
void visitMapLiteral(MapLiteral node) => node.visitChildren(this);
@override
void visitRecordLiteral(RecordLiteral node) => node.visitChildren(this);
@override
void visitAwaitExpression(AwaitExpression node) => node.visitChildren(this);
@override
void visitBlockExpression(BlockExpression node) => node.visitChildren(this);
@override
void visitInstantiation(Instantiation node) => node.visitChildren(this);
@override
void visitTypedefTearOff(TypedefTearOff node) => node.visitChildren(this);
@override
void visitRecordIndexGet(RecordIndexGet node) => node.visitChildren(this);
@override
void visitRecordNameGet(RecordNameGet node) => node.visitChildren(this);
@override
void visitConstructorTearOff(ConstructorTearOff node) =>
node.visitChildren(this);
}
class DirectReferenceDependencies {
// The static dependencies.
final Set<Reference> references = {};
final Map<Reference, Set<LibraryDependency>> deferredReferences = {};
final Set<Constant> constants = {};
final Map<Constant, Set<LibraryDependency>> deferredConstants = {};
// The selectors used during calls.
final Set<int> selectorIds = {};
final Map<int, Set<LibraryDependency>> deferredSelectorIds = {};
final Set<Name> dynamicSelectors = {};
final Map<Name, Set<LibraryDependency>> deferredDynamicSelectors = {};
DirectReferenceDependencies();
bool get isEmpty =>
references.isEmpty &&
deferredReferences.isEmpty &&
constants.isEmpty &&
deferredConstants.isEmpty &&
selectorIds.isEmpty &&
deferredSelectorIds.isEmpty &&
dynamicSelectors.isEmpty &&
deferredDynamicSelectors.isEmpty;
}
class DirectConstantDependencies {
final Set<Constant> constants;
final Reference? reference;
DirectConstantDependencies(this.constants, this.reference);
bool get isEmpty => constants.isEmpty && reference == null;
}
/// Computes the roots for each deferred import.
ProgramPrefixUsages computePrefixRoots(
LibraryDependency programRootPrefix,
Set<Reference> programRoots,
Set<int> programSelectorRoots,
Map<Reference, DirectReferenceDependencies> directReferenceDependencies,
Map<Constant, DirectConstantDependencies> directConstantDependencies,
) {
final rootUsages = PrefixUsages(programRootPrefix);
rootUsages.references.addAll(programRoots);
rootUsages.selectorIds.addAll(programSelectorRoots);
final prefixRoots = <LibraryDependency, PrefixUsages>{
programRootPrefix: rootUsages,
};
directReferenceDependencies.forEach((_, deps) {
deps.deferredReferences.forEach((reference, imports) {
for (final import in imports) {
(prefixRoots[import] ??= PrefixUsages(
import,
)).references.add(reference);
}
});
deps.deferredConstants.forEach((constant, imports) {
for (final import in imports) {
(prefixRoots[import] ??= PrefixUsages(import)).constants.add(constant);
}
});
deps.deferredSelectorIds.forEach((selectorId, imports) {
for (final import in imports) {
(prefixRoots[import] ??= PrefixUsages(
import,
)).selectorIds.add(selectorId);
}
});
deps.deferredDynamicSelectors.forEach((name, imports) {
for (final import in imports) {
(prefixRoots[import] ??= PrefixUsages(import)).selectorNames.add(name);
}
});
});
return ProgramPrefixUsages(prefixRoots);
}
/// Maps each deferred library import to [PrefixUsages].
///
/// Depending on the usage, the [PrefixUsages] may only be the roots (i.e. the
/// ones accessed directly via `D.*` accesses) or it may be the transitive
/// closure of them or the transitive closure minus that of dominators.
class ProgramPrefixUsages {
final Map<LibraryDependency, PrefixUsages> usages;
ProgramPrefixUsages(this.usages);
}
class PrefixUsages {
final LibraryDependency prefix;
final Set<Reference> references = {};
final Set<Constant> constants = {};
final Set<int> selectorIds = {};
final Set<Name> selectorNames = {};
PrefixUsages(this.prefix);
}