9394b30b77
Bug: b/286184681 Change-Id: I903528c4adfbc576644aec7541903df6b9633e26 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/325523 Reviewed-by: Jonas Termansen <sortie@google.com> Reviewed-by: Samuel Rawlins <srawlins@google.com> Commit-Queue: Alexander Thomas <athom@google.com>
245 lines
7.9 KiB
Dart
245 lines
7.9 KiB
Dart
// Copyright (c) 2017, 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 fasta.graph;
|
|
|
|
import 'dart:math';
|
|
|
|
import '../ast.dart';
|
|
|
|
abstract class Graph<T> {
|
|
Iterable<T> get vertices;
|
|
|
|
Iterable<T> neighborsOf(T vertex);
|
|
}
|
|
|
|
/// [Graph] implementation using a collection of [Library] nodes as the graph
|
|
/// vertices and using library dependencies to compute neighbors.
|
|
///
|
|
/// If [coreLibrary] is provided, it will be included in the neighbor of all
|
|
/// vertices. Otherwise, `dart:core` will only be neighboring libraries that
|
|
/// explicitly dependent on it.
|
|
class LibraryGraph implements Graph<Library> {
|
|
final Iterable<Library> libraries;
|
|
final Library? coreLibrary;
|
|
|
|
LibraryGraph(this.libraries, {this.coreLibrary});
|
|
|
|
@override
|
|
Iterable<Library> get vertices => libraries;
|
|
|
|
@override
|
|
Iterable<Library> neighborsOf(Library library) sync* {
|
|
if (coreLibrary != null && library != coreLibrary) {
|
|
yield coreLibrary!;
|
|
}
|
|
for (LibraryDependency dependency in library.dependencies) {
|
|
yield dependency.targetLibrary;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Computes the strongly connected components of [graph].
|
|
///
|
|
/// This implementation is based on [Dijkstra's path-based strong component
|
|
/// algorithm]
|
|
/// (https://en.wikipedia.org/wiki/Path-based_strong_component_algorithm#Description).
|
|
List<List<T>> computeStrongComponents<T>(Graph<T> graph) {
|
|
List<List<T>> result = <List<T>>[];
|
|
int count = 0;
|
|
Map<T, int> preorderNumbers = <T, int>{};
|
|
List<T> unassigned = <T>[];
|
|
List<T> candidates = <T>[];
|
|
Set<T> assigned = new Set<T>();
|
|
|
|
void recursivelySearch(T vertex) {
|
|
// Step 1: Set the preorder number of [vertex] to [count], and increment
|
|
// [count].
|
|
preorderNumbers[vertex] = count++;
|
|
|
|
// Step 2: Push [vertex] onto [unassigned] and also onto [candidates].
|
|
unassigned.add(vertex);
|
|
candidates.add(vertex);
|
|
|
|
// Step 3: For each edge from [vertex] to a neighboring vertex [neighbor]:
|
|
for (T neighbor in graph.neighborsOf(vertex)) {
|
|
int? neighborPreorderNumber = preorderNumbers[neighbor];
|
|
if (neighborPreorderNumber == null) {
|
|
// If the preorder number of [neighbor] has not yet been assigned,
|
|
// recursively search [neighbor];
|
|
recursivelySearch(neighbor);
|
|
} else if (!assigned.contains(neighbor)) {
|
|
// Otherwise, if [neighbor] has not yet been assigned to a strongly
|
|
// connected component:
|
|
//
|
|
// * Repeatedly pop vertices from [candidates] until the top element of
|
|
// [candidates] has a preorder number less than or equal to the
|
|
// preorder number of [neighbor].
|
|
while (preorderNumbers[candidates.last]! > neighborPreorderNumber) {
|
|
candidates.removeLast();
|
|
}
|
|
}
|
|
}
|
|
// Step 4: If [vertex] is the top element of [candidates]:
|
|
if (candidates.last == vertex) {
|
|
// Pop vertices from [unassigned] until [vertex] has been popped, and
|
|
// assign the popped vertices to a new component.
|
|
List<T> component = <T>[];
|
|
while (true) {
|
|
T top = unassigned.removeLast();
|
|
component.add(top);
|
|
assigned.add(top);
|
|
if (top == vertex) break;
|
|
}
|
|
result.add(component);
|
|
|
|
// Pop [vertex] from [candidates].
|
|
candidates.removeLast();
|
|
}
|
|
}
|
|
|
|
for (T vertex in graph.vertices) {
|
|
if (preorderNumbers[vertex] == null) {
|
|
recursivelySearch(vertex);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// A [Graph] using strongly connected components, as computed by
|
|
/// [computeStrongComponents], as vertices. Neighbors are computed using the
|
|
/// neighbors of the provided [subgraph] which was used to compute the strongly
|
|
/// connected components.
|
|
class StrongComponentGraph<T> implements Graph<List<T>> {
|
|
final Graph<T> subgraph;
|
|
final List<List<T>> components;
|
|
final Map<T, List<T>> _elementToComponentMap = {};
|
|
final Map<List<T>, Set<List<T>>> _neighborsMap = {};
|
|
|
|
StrongComponentGraph(this.subgraph, this.components) {
|
|
for (List<T> component in components) {
|
|
for (T element in component) {
|
|
_elementToComponentMap[element] = component;
|
|
}
|
|
}
|
|
}
|
|
|
|
Set<List<T>> _computeNeighborsOf(List<T> component) {
|
|
Set<List<T>> neighbors = {};
|
|
for (T element in component) {
|
|
for (T neighborElement in subgraph.neighborsOf(element)) {
|
|
List<T> neighborComponent = _elementToComponentMap[neighborElement]!;
|
|
if (component != neighborComponent) {
|
|
neighbors.add(neighborComponent);
|
|
}
|
|
}
|
|
}
|
|
return neighbors;
|
|
}
|
|
|
|
@override
|
|
Iterable<List<T>> neighborsOf(List<T> vertex) {
|
|
return _neighborsMap[vertex] ??= _computeNeighborsOf(vertex);
|
|
}
|
|
|
|
@override
|
|
Iterable<List<T>> get vertices => components;
|
|
}
|
|
|
|
const int cyclicMarker = -1;
|
|
|
|
int _topologicalSortInternal<T>(
|
|
Graph<T> graph, TopologicalSortResult<T> result, T vertex) {
|
|
int? index = result.indexMap[vertex];
|
|
if (index == null) {
|
|
result.indexMap[vertex] = cyclicMarker;
|
|
int index = 0;
|
|
for (T neighbor in graph.neighborsOf(vertex)) {
|
|
int neighborIndex = _topologicalSortInternal(graph, result, neighbor);
|
|
if (neighborIndex == cyclicMarker) {
|
|
result.cyclicVertices.add(vertex);
|
|
return cyclicMarker;
|
|
} else {
|
|
index = max(index, neighborIndex + 1);
|
|
}
|
|
}
|
|
result.sortedVertices.add(vertex);
|
|
if (index >= result.layers.length) {
|
|
assert(index == result.layers.length);
|
|
result.layers.add([vertex]);
|
|
} else {
|
|
result.layers[index].add(vertex);
|
|
}
|
|
return result.indexMap[vertex] = index;
|
|
}
|
|
return index;
|
|
}
|
|
|
|
/// Perform a topological sorting of the vertices in [graph], returning a
|
|
/// [TopologicalSortResult] object with the result.
|
|
TopologicalSortResult<T> topologicalSort<T>(Graph<T> graph) {
|
|
TopologicalSortResult<T> result = new TopologicalSortResult();
|
|
|
|
for (T vertex in graph.vertices) {
|
|
_topologicalSortInternal(graph, result, vertex);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// The result of computing the [topologicalSort] on a [Graph].
|
|
class TopologicalSortResult<T> {
|
|
/// The non-cyclic vertices of the graph sorted in topological order.
|
|
final List<T> sortedVertices = [];
|
|
|
|
/// The cyclic vertices of graph, including vertices that have a path to
|
|
/// a vertex.
|
|
final List<T> cyclicVertices = [];
|
|
|
|
/// The topological index of all non-cyclic vertices of the graph.
|
|
///
|
|
/// The "topological index" of a vertex is the length of the longest path
|
|
/// through neighbors. For vertices with no neighbors, the index is 0.
|
|
/// For any other vertex, it is 1 plus max of the index of its neighbors.
|
|
final Map<T, int> indexMap = {};
|
|
|
|
/// The non-cyclic vertices in layers according to their topological index.
|
|
/// That is, `layers[i]` contain the list of vertices with index `i`.
|
|
final List<List<T>> layers = [];
|
|
}
|
|
|
|
/// Find vertices in [graph] that either is in [vertices], has a neighbor in
|
|
/// [vertices], has a neighbor of a neighbor (etc) in [vertices].
|
|
Set<T> calculateTransitiveDependenciesOf<T>(Graph<T> graph, Set<T> vertices) {
|
|
// Compute direct dependencies for each vertex (the reverse of the
|
|
// edges returned by `graph.neighborsOf`).
|
|
Map<T, Set<T>> directDependencies = <T, Set<T>>{};
|
|
List<T> workList = [];
|
|
{
|
|
for (T vertex in graph.vertices) {
|
|
if (vertices.contains(vertex)) workList.add(vertex);
|
|
for (T neighbor in graph.neighborsOf(vertex)) {
|
|
(directDependencies[neighbor] ??= new Set<T>()).add(vertex);
|
|
if (vertices.contains(neighbor)) workList.add(vertex);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Collect and remove all dependencies.
|
|
Set<T> left = new Set<T>.of(graph.vertices);
|
|
Set<T> transitive = {};
|
|
while (workList.isNotEmpty) {
|
|
T removed = workList.removeLast();
|
|
if (left.remove(removed)) {
|
|
Set<T>? s = directDependencies[removed];
|
|
if (s != null) {
|
|
// [s] is null for leaves.
|
|
workList.addAll(s);
|
|
}
|
|
transitive.add(removed);
|
|
}
|
|
}
|
|
return transitive;
|
|
}
|