blob: 81e90ecee8075d44da89d222db56a95bf6ba28aa [file] [log] [blame] [edit]
// Copyright (c) 2026, 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/ast.dart';
import 'package:vm/dominators.dart' as dom;
import 'dependencies.dart';
/// Computes a dominator tree of deferred imports
///
/// If a deferred import A dominates deferred import B it means that A is
/// guaranteed to be loaded before B.
Dominators computeDominators(
LibraryDependency rootImport,
Set<Reference> roots,
Map<Reference, DirectReferenceDependencies> directReferenceDependencies,
Map<Constant, DirectConstantDependencies> directConstantDependencies,
) {
// Step 1) Create nodes of the graph
final root = Vertex(rootImport);
final veritices = <Object, Vertex>{rootImport: root};
final allDeferredImports = <LibraryDependency>{rootImport};
directReferenceDependencies.forEach((reference, deps) {
veritices[reference] = Vertex(reference);
deps.deferredReferences.forEach(
(_, imports) => allDeferredImports.addAll(imports),
);
deps.deferredConstants.forEach(
(_, imports) => allDeferredImports.addAll(imports),
);
});
directConstantDependencies.forEach((constant, deps) {
veritices[constant] = Vertex(constant);
});
for (final reference in roots) {
veritices[reference] ??= Vertex(reference);
}
for (final import in allDeferredImports) {
veritices[import] = Vertex(import);
}
// Step 2) Create edges of the graph
for (final reference in roots) {
root.successors.add(veritices[reference]!);
}
directReferenceDependencies.forEach((reference, deps) {
if (deps.isEmpty) return;
final from = veritices[reference]!;
deps.references.forEach((reference) {
from.successors.add(veritices[reference]!);
});
deps.deferredReferences.forEach((reference, imports) {
final referenceV = veritices[reference]!;
imports.forEach((import) {
final importV = veritices[import]!;
from.successors.add(importV);
importV.successors.add(referenceV);
});
});
deps.constants.forEach((constant) {
from.successors.add(veritices[constant]!);
});
deps.deferredConstants.forEach((constant, imports) {
final constantV = veritices[constant]!;
imports.forEach((import) {
final importV = veritices[import]!;
from.successors.add(importV);
importV.successors.add(constantV);
});
});
});
directConstantDependencies.forEach((constant, deps) {
if (deps.isEmpty) return;
final from = veritices[constant]!;
final reference = deps.reference;
if (reference != null) {
from.successors.add(veritices[reference]!);
}
deps.constants.forEach((constant) {
from.successors.add(veritices[constant]!);
});
});
// Step 3) Cleanup duplicate successors (the `successors` field is a `List`)
veritices.forEach((_, v) {
final allChildren = v.successors.toSet();
v.successors.clear();
v.successors.addAll(allChildren);
});
// Step 4) Compute dominance relationship in the graph.
dom.computeDominators(root);
// Step 5) Create [Dominators] object mapping prefixes to their dominator.
final doms = <LibraryDependency, DominatorNode<LibraryDependency>>{};
LibraryDependency? dominatorOf(LibraryDependency import) {
final importV = veritices[import]!;
Vertex? dom = importV.dominator;
while (dom != null && dom.object is! LibraryDependency) {
dom = dom.dominator;
}
if (dom != null) {
return dom.object as LibraryDependency;
} else {
assert(import == rootImport);
return null;
}
}
DominatorNode<LibraryDependency> dominatorNodeOf(LibraryDependency prefix) {
final existing = doms[prefix];
if (existing != null) return existing;
final dom = dominatorOf(prefix);
return doms[prefix] = DominatorNode<LibraryDependency>(
prefix,
dom != null ? dominatorNodeOf(dom) : null,
);
}
allDeferredImports.forEach(dominatorNodeOf);
return Dominators(doms[rootImport]!, doms);
}
SelectorDominators computeSelectorDominators(
Dominators dominators,
ProgramPrefixUsages prefixDominatorUsages,
) {
final selectorIdDominator = <int, DominatorNode<LibraryDependency>>{};
final selectorNameDominator = <Name, DominatorNode<LibraryDependency>>{};
dominators.root.visitDFS((_) {}, (node) {
final usages = prefixDominatorUsages.usages[node.prefix]!;
for (final selectorId in usages.selectorIds) {
final existing = selectorIdDominator[selectorId];
selectorIdDominator[selectorId] = existing == null
? node
: existing.commonDominator(node);
}
for (final name in usages.selectorNames) {
final existing = selectorNameDominator[name];
selectorNameDominator[name] = existing == null
? node
: existing.commonDominator(node);
}
});
return SelectorDominators(selectorIdDominator, selectorNameDominator);
}
/// Dominators of selectors.
///
/// This tells us which node in the dominator tree dominates all uses of a
/// selector.
class SelectorDominators {
final Map<int, DominatorNode<LibraryDependency>> selectorIds;
final Map<Name, DominatorNode<LibraryDependency>> selectorNames;
SelectorDominators(this.selectorIds, this.selectorNames);
void dump() {
print('Selector dominators:');
for (final MapEntry(:key, :value) in selectorIds.entries) {
print(' $key -> $value');
}
for (final MapEntry(:key, :value) in selectorNames.entries) {
print(' $key -> $value');
}
}
}
ClassDominators computeClassDominators(
Dominators dominators,
ProgramPrefixUsages prefixDominatorUsages,
) {
final classDominators = <Reference, DominatorNode<LibraryDependency>>{};
dominators.root.visitDFS((_) {}, (node) {
final usages = prefixDominatorUsages.usages[node.prefix]!;
for (final reference in usages.references) {
if (reference.node is! Class) continue;
final existing = classDominators[reference];
classDominators[reference] = existing == null
? node
: existing.commonDominator(node);
}
});
return ClassDominators(classDominators);
}
/// Dominators of classes.
///
/// This tells us which node in the dominator tree dominates all uses of a
/// class (a constructor invocation or constant will be a class use).
class ClassDominators {
final Map<Reference, DominatorNode<LibraryDependency>> classDominators;
ClassDominators(this.classDominators);
void dump() {
print('Class dominators:');
for (final MapEntry(:key, :value) in classDominators.entries) {
print(' $key -> $value');
}
}
}
/// Computes transitive usages of library prefixes minus that of their
/// dominators.
///
/// So if we have
///
/// Root
/// / \
/// D1 D2
/// /
/// D3
///
/// This walks down the tree in DFS order.
///
/// * transitive accesses via root prefix (i.e. program roots)
/// * transitive accesses via D1 prefix - excluding `Root` usages
/// * transitive accesses via D2 prefix - excluding `Root` usages
/// * transitive accesses via D3 prefix - excluding `D1` & `Root` usages
///
/// (the transitive accesses do not include deferred accesses)
ProgramPrefixUsages computeTransitiveDominatorUsages(
Dominators dominators,
ProgramPrefixUsages programRoots,
Map<Reference, DirectReferenceDependencies> directReferenceDependencies,
Map<Constant, DirectConstantDependencies> directConstantDependencies,
) {
final parentStack = <PrefixUsages>[];
final transitiveUsages = <LibraryDependency, PrefixUsages>{};
dominators.root.visitDFS(
(node) {
final prefixRoots = programRoots.usages[node.prefix]!;
final usages = scanTransitiveDepsExcludingParents(
parentStack,
prefixRoots,
directReferenceDependencies,
directConstantDependencies,
);
transitiveUsages[node.prefix] = usages;
parentStack.add(usages);
},
(node) {
parentStack.removeLast();
},
);
return ProgramPrefixUsages(transitiveUsages);
}
PrefixUsages scanTransitiveDepsExcludingParents(
List<PrefixUsages> parents,
PrefixUsages roots,
Map<Reference, DirectReferenceDependencies> directReferenceDependencies,
Map<Constant, DirectConstantDependencies> directConstantDependencies,
) {
final syncUsages = PrefixUsages(roots.prefix);
final worklistReferences = <Reference>[];
final worklistConstant = <Constant>[];
final enqueuedReferences = <Reference>{};
final enqueuedConstants = <Constant>{};
final enqueuedSelectorIds = <int>{};
final enqueuedSelectorNames = <Name>{};
void enqueueReference(Reference reference) {
if (enqueuedReferences.add(reference)) {
for (int i = 0; i < parents.length; ++i) {
if (parents[i].references.contains(reference)) return;
}
syncUsages.references.add(reference);
worklistReferences.add(reference);
}
}
void enqueueConstant(Constant constant) {
if (enqueuedConstants.add(constant)) {
for (int i = 0; i < parents.length; ++i) {
if (parents[i].constants.contains(constant)) return;
}
syncUsages.constants.add(constant);
worklistConstant.add(constant);
}
}
void enqueueSelectorId(int selectorId) {
if (enqueuedSelectorIds.add(selectorId)) {
for (int i = 0; i < parents.length; ++i) {
if (parents[i].selectorIds.contains(selectorId)) return;
}
syncUsages.selectorIds.add(selectorId);
}
}
void enqueueSelectorName(Name selectorName) {
if (enqueuedSelectorNames.add(selectorName)) {
for (int i = 0; i < parents.length; ++i) {
if (parents[i].selectorNames.contains(selectorName)) return;
}
syncUsages.selectorNames.add(selectorName);
}
}
for (final reference in roots.references) {
enqueueReference(reference);
}
for (final constant in roots.constants) {
enqueueConstant(constant);
}
for (final selectorId in roots.selectorIds) {
enqueueSelectorId(selectorId);
}
while (worklistReferences.isNotEmpty || worklistConstant.isNotEmpty) {
while (worklistReferences.isNotEmpty) {
final reference = worklistReferences.removeLast();
final deps = directReferenceDependencies[reference]!;
deps.references.forEach(enqueueReference);
deps.selectorIds.forEach(enqueueSelectorId);
deps.dynamicSelectors.forEach(enqueueSelectorName);
deps.constants.forEach(enqueueConstant);
}
while (worklistConstant.isNotEmpty) {
final constant = worklistConstant.removeLast();
final deps = directConstantDependencies[constant]!;
deps.constants.forEach(enqueueConstant);
final reference = deps.reference;
if (reference != null) enqueueReference(reference);
}
}
return syncUsages;
}
class Vertex extends dom.Vertex<Vertex> {
final Object object;
bool isLoadingRoot = true;
Vertex(this.object);
}
class Dominators {
late final DominatorNode<LibraryDependency> root;
final Map<LibraryDependency, DominatorNode<LibraryDependency>> _nodes;
Dominators(this.root, this._nodes);
late final List<DominatorNode<LibraryDependency>> allNodes = _nodes.values
.toList();
}
class DominatorNode<T> {
final T prefix;
final DominatorNode<T>? dominator;
final List<DominatorNode<T>> children = [];
final int depth;
DominatorNode(this.prefix, this.dominator)
: depth = dominator == null ? 0 : 1 + dominator.depth {
dominator?.children.add(this);
}
void visitDFS(
void Function(DominatorNode<T>) pre, [
void Function(DominatorNode<T>)? post,
]) {
pre(this);
for (final child in children) {
child.visitDFS(pre, post);
}
if (post != null) post(this);
}
bool strictlyDominates(DominatorNode<T> other) {
if (this == other) return false;
if (depth >= other.depth) return false;
var dom = other.dominator;
while (dom != null) {
if (dom == this) return true;
if (dom.depth == depth) return false;
dom = dom.dominator;
}
return false;
}
bool dominates(DominatorNode<T> other) {
return this == other || strictlyDominates(other);
}
DominatorNode<T> commonDominator(DominatorNode<T> right) {
var left = this;
if (left == right) return this;
final leftDepth = left.depth;
final rightDepth = right.depth;
if (leftDepth > rightDepth) {
for (int i = 0; i < (leftDepth - rightDepth); ++i) {
left = left.dominator!;
}
} else if (rightDepth > leftDepth) {
for (int i = 0; i < (rightDepth - leftDepth); ++i) {
right = right.dominator!;
}
}
while (left != right) {
left = left.dominator!;
right = right.dominator!;
}
return left;
}
void dump([int indent = 0]) {
print('${' ' * indent} $prefix');
for (final child in children) {
child.dump(indent + 2);
}
}
}