Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor GraphTraversal to avoid non-determinism #4098

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions crates/turbo-tasks/src/graph/control_flow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/// The control flow of visiting an edge during a graph traversal.
pub enum VisitControlFlow<Node, Abort = !> {
/// The traversal should continue on the outgoing edges of the given node.
Continue(Node),
/// The traversal should skip visiting the edges the given node.
Skip(Node),
/// The traversal should abort and return immediately.
Abort(Abort),
}

impl<Node, Abort> VisitControlFlow<Node, Abort> {
/// Map the continue and skip values of this control flow.
pub fn map_node<Map, Mapped>(self, mut map: Map) -> VisitControlFlow<Mapped, Abort>
where
Map: FnMut(Node) -> Mapped,
{
match self {
VisitControlFlow::Continue(node) => VisitControlFlow::Continue(map(node)),
VisitControlFlow::Skip(node) => VisitControlFlow::Skip(map(node)),
VisitControlFlow::Abort(abort) => VisitControlFlow::Abort(abort),
}
}

/// Map the abort value of this control flow.
pub fn map_abort<Map, Mapped>(self, mut map: Map) -> VisitControlFlow<Node, Mapped>
where
Map: FnMut(Abort) -> Mapped,
{
match self {
VisitControlFlow::Continue(node) => VisitControlFlow::Continue(node),
VisitControlFlow::Skip(node) => VisitControlFlow::Skip(node),
VisitControlFlow::Abort(abort) => VisitControlFlow::Abort(map(abort)),
}
}
}
226 changes: 0 additions & 226 deletions crates/turbo-tasks/src/graph/get_children.rs

This file was deleted.

73 changes: 70 additions & 3 deletions crates/turbo-tasks/src/graph/graph_store.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,77 @@
use std::collections::HashSet;

/// A graph store is a data structure that will be built up during a graph
/// traversal. It is used to store the results of the traversal.
pub trait GraphStore<T>: Default {
pub trait GraphStore<Node>: Default {
type Handle: Clone;

// TODO(alexkirsz) An `entry(parent_handle) -> Entry` API would be more
// TODO(alexkirsz) An `entry(from_handle) -> Entry` API would be more
// efficient, as right now we're getting the same key multiple times.
/// Inserts a node into the graph store, and returns a handle to it.
fn insert(&mut self, parent_handle: Option<Self::Handle>, node: T) -> (Self::Handle, &T);
///
/// If this method returns `None`, the node edges will not be visited.
fn insert(
&mut self,
from_handle: Option<Self::Handle>,
node: Node,
) -> Option<(Self::Handle, &Node)>;
}

/// A [`GraphStore`] wrapper that skips nodes that have already been
/// visited. This is necessary to avoid repeated work when traversing non-tree
/// graphs (i.e. where a node can have more than one incoming edge).
#[derive(Debug)]
pub struct SkipDuplicates<StoreImpl, Node>
where
StoreImpl: GraphStore<Node>,
{
store: StoreImpl,
visited: HashSet<Node>,
}

impl<StoreImpl, Node> Default for SkipDuplicates<StoreImpl, Node>
where
StoreImpl: GraphStore<Node>,
{
fn default() -> Self {
Self {
store: Default::default(),
visited: Default::default(),
}
}
}

impl<StoreImpl, Node> GraphStore<Node> for SkipDuplicates<StoreImpl, Node>
where
StoreImpl: GraphStore<Node>,
Node: Eq + std::hash::Hash + Clone,
{
type Handle = StoreImpl::Handle;

fn insert(
&mut self,
from_handle: Option<Self::Handle>,
node: Node,
) -> Option<(Self::Handle, &Node)> {
if !self.visited.contains(&node) {
self.visited.insert(node.clone());
self.store.insert(from_handle, node)
} else {
// Always insert the node into the store, even if we've already
// visited it. This is necessary to ensure that the store sees all
// edges.
self.store.insert(from_handle, node);
None
}
}
}

impl<StoreImpl, Node> SkipDuplicates<StoreImpl, Node>
where
StoreImpl: GraphStore<Node>,
{
/// Consumes the wrapper and returns the underlying store.
pub fn into_inner(self) -> StoreImpl {
self.store
}
}
Loading