// Copyright 2026 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Generic implementation of the "closest common dominator" algorithm for //! directed graphs. //! //! Generic implementation of the Common Dominator algorithm for directed //! graphs, using the Cooper-Harvey-Kennedy iterative algorithm. Loosely //! speaking the algorithm finds the "choke point" for a set of nodes S in a //! directed graph (going from the "entry" node to nodes in S), closest to S. //! //! Dominance: //! //! * A flow graph is a directed graph with a designated entry node. //! * A node z is said to dominate a node n if all paths from the entry node to //! n must go through z. Every node dominates itself, and the entry node //! dominates all nodes. //! * A node can have one or more dominators. //! * A node z strictly dominates n if z dominates n and z != n. //! * The immediate dominator of a node n is the dominator of n that doesn't //! strictly dominate any other strict dominators of n. Informally it is the //! "closest" choke point on all paths from the entry node to n. //! * Let S be a subset of the nodes in the graph. The intersection of the //! dominators of each node in S is the set of common dominators of S. //! * The closest common dominator of S is the common dominator of S that //! doesn't strictly dominate any other common dominator of S. Informally, it //! is the choke point closest to S such that all paths from the entry node to //! S must go through it. //! //! Dominator Tree: //! //! For any flow graph G there is a corresponding dominator tree defined as //! follows: //! * The nodes of the dominator tree are the same as the nodes of G //! * The root of the dominator tree is the entry node of G //! * In the dominator tree, the children of a node are the nodes it immediately //! dominates //! //! The closest common dominator of S is the Lowest Common Ancestor (LCA) //! of S in the graph's dominator tree. //! //! This implementation constructs the Dominator Tree by first determining //! the Immediate Dominator for every node (using the standard iterative //! algorithm), and then calculating the LCA for the set S. See: //! //! * //! * //! //! The running time is O(V+E+|S|*V)in the worst case, the space complexity is //! O(V+E), where V is the number of nodes and E is the number of edges. In //! practice the algorithm is fast and efficient for typical use cases because //! the number of nodes that dominate any given node is typically small, and //! the dominator tree is typically shallow. use std::collections::HashMap; use std::collections::HashSet; use std::hash::Hash; use std::iter; use std::rc::Rc; use futures::future::try_join_all; use indexmap::IndexMap; use indexmap::IndexSet; use itertools::Itertools as _; use thiserror::Error; /// An immutable directed graph with nodes of type N and a minimal interface for /// iterating over nodes and their adjacent nodes. #[derive(Clone, Eq, PartialEq, Debug)] pub struct SimpleDirectedGraph where N: Clone + Eq + Hash, { /// The adjacency map of the graph. Each key is a node, and the /// corresponding value is the set of adjacent nodes (i.e., the children of /// the key node). The adjacency map is in canonical form: for every /// u->v edge, there is an entry in adj with key v (even if v has no /// outgoing edges). adj: IndexMap>, } impl SimpleDirectedGraph where N: Clone + Eq + Hash, { /// Constructs a new SimpleDirectedGraph from a list of edges. pub fn new(edges: EI) -> Self where EI: IntoIterator, { let mut adj: IndexMap> = IndexMap::new(); for (parent, child) in edges { adj.entry(parent).or_default().insert(child.clone()); adj.entry(child).or_default(); } Self { adj } } /// Returns the nodes in this graph. pub fn nodes(&self) -> impl Iterator { self.adj.keys() } /// Returns the nodes in this graph. pub fn num_nodes(&self) -> usize { self.adj.len() } /// Returns the edges in this graph. pub fn edges(&self) -> impl Iterator { self.adj .iter() .flat_map(|(parent, adj_set)| adj_set.iter().map(move |child| (parent, child))) } /// Returns the adjacent nodes for the given node, or None if the node is /// not in the graph. pub fn adjacent_nodes(&self, node: &N) -> Option> { self.adj.get(node).map(|adj_set| adj_set.iter()) } /// Returns true if this graph contains the given node. pub fn contains_node(&self, node: &N) -> bool { self.adj.contains_key(node) } /// Returns a postorder traversal of the nodes in this graph starting from /// the given node. pub fn get_postorder<'a>(&'a self, start_node: &'a N) -> Vec<&'a N> { post_order(start_node, |&u| self.adjacent_nodes(u).unwrap()).collect_vec() } } /// A FlowGraph is a directed graph with a designated start node. /// /// Any node in the graph can be the start node. There are no reachability /// requirements whatsoever: some nodes may be unreachable from the start node, /// the start node could have incoming edges, the graph could be disconnected, /// etc. #[derive(Clone, Eq, PartialEq, Debug)] pub struct FlowGraph where N: Clone + Eq + Hash, { /// The graph. pub graph: SimpleDirectedGraph, /// The start node. pub start_node: N, } /// Calculates the dominators in a flow graph. Also has a method for finding the /// closest common dominator of a set of nodes. pub struct DominatorFinder<'a, N> { /// Map from nodes to integers in [0, N-1] range, in postorder (the start /// node has index N-1). node_to_id: HashMap<&'a N, InternalId>, /// The inverse of node_to_id. id_to_node: Vec<&'a N>, /// The immediate dominator for each node (by index). NOTE: the immediate /// dominator of the start node is itself. immediate_dominators: Vec, } /// Errors that can occur while finding dominators. #[derive(Debug, Error, PartialEq)] pub enum DominatorFinderError { /// The flow graph is invalid. #[error("The flow graph is invalid: some nodes are unreachable from the start node")] UnreachableNodesInFlowGraph, /// The target set is empty. #[error("Target set must not be empty")] EmptyTargetSet, /// The target set is invalid. #[error("Target set contains a node which is not in the flow graph")] UnknownNodeInTargetSet, } /// The dominator algorithm assigns consecutive numeric IDs to nodes, for /// efficiency reasons. We use this type alias for clarity. type InternalId = usize; impl<'a, N> DominatorFinder<'a, N> where N: Clone + Eq + Hash, { /// Constructs a new DominatorFinder. Returns an error if the flow graph is /// invalid: e.g. if some node is unreachable from the start node. pub fn calculate(flow_graph: &'a FlowGraph) -> Result { // Get postorder traversal of the graph starting from the start node. let postorder = flow_graph.graph.get_postorder(&flow_graph.start_node); if postorder.len() != flow_graph.graph.num_nodes() { return Err(DominatorFinderError::UnreachableNodesInFlowGraph); } // Map generic types to integer IDs let mut node_to_id = HashMap::new(); let mut id_to_node = Vec::new(); for (index, &node) in postorder.iter().enumerate() { id_to_node.push(node); node_to_id.insert(node, index); } // Build graph using internal IDs. let num_nodes = node_to_id.len(); let mut rev_adj = vec![vec![]; num_nodes]; for (u, v) in flow_graph.graph.edges() { rev_adj[node_to_id[v]].push(node_to_id[u]); } // Find the immediate dominators for each node using the Cooper-Harvey-Kennedy // iterative algorithm. let immediate_dominators = Self::calculate_immediate_dominators(&rev_adj); Ok(Self { node_to_id, id_to_node, immediate_dominators, }) } /// Returns a map from each node to its immediate dominator. NOTE: the /// immediate dominator of the start node is itself. pub fn get_immediate_dominators(&self) -> HashMap { self.immediate_dominators .iter() .enumerate() .map(|(index, &idom)| { ( self.id_to_node[index].clone(), self.id_to_node[idom].clone(), ) }) .collect() } /// Finds the closest common dominator for the given flow graph and set of /// nodes S (target_set). pub fn find_closest_common_dominator( &self, target_set: NI, ) -> Result where NI: IntoIterator, { // Convert generic target_set to internal IDs let target_ids: Vec = target_set .into_iter() .map(|node| match self.node_to_id.get(&node) { Some(id) => Ok(*id), None => Err(DominatorFinderError::UnknownNodeInTargetSet), }) .try_collect()?; if target_ids.is_empty() { return Err(DominatorFinderError::EmptyTargetSet); } // The closest common dominator of a set of nodes is the lowest common ancestor // of those nodes in the dominator tree. let closest_common_dominator = Self::find_lowest_common_ancestor(&target_ids, &self.immediate_dominators); // Map the internal ID back to generic type N. Ok(self.id_to_node[closest_common_dominator].clone()) } // Applies the Cooper-Harvey-Kennedy iterative algorithm to find the immediate // dominators for each node in the graph. // See http://www.hipersoft.rice.edu/grads/publications/dom14.pdf for details on how this function works. fn calculate_immediate_dominators(rev_adj: &[Vec]) -> Vec { // Step 1: Compute Dominators on Reverse Graph let num_nodes = rev_adj.len(); let start_node_id = num_nodes - 1; // We hold the immediate dominator for each node in the following vector, in // index position (the kth entry is the immediate dominator of the node with ID // k). We initialize the immediate dominator of every node to usize::MAX to // represent that those nodes are not processed yet. Once a node is // processed, its immediate dominator is guaranteed to be a valid node // index. let mut immediate_dominators: Vec = vec![usize::MAX; num_nodes]; // NOTE: technically speaking the immediate dominator is NOT defined for the // start node, but it is convenient for the algorithm to set it to itself; this // is consistent with the literature and specifically with // Cooper-Harvey-Kennedy. immediate_dominators[start_node_id] = start_node_id; loop { // Each iteration of the loop processes all nodes in reverse postorder, trying // to improve the immediate dominator for each node. The loop continues until we // have an iteration where no immediate dominator is changed. Note that the // entries in immediate_dominators are only guaranteed to be correct when the // loop terminates. let mut changed = false; // Iterate in reverse postorder, skipping the start node. for u in (0..start_node_id).rev() { let mut new_idom = usize::MAX; // Process predecessors (nodes that flow INTO u). let preds = &rev_adj[u]; for &p in preds { if immediate_dominators[p] == usize::MAX { // Skip predecessors that have not been processed yet. continue; } if new_idom == usize::MAX { // This is the first predecessor of u that has been processed so far. We use // it as the starting point for finding the new "improved" immediate // dominator for u. new_idom = p; } else { // "Intersect" the current new_idom with p's idom. new_idom = Self::intersect(new_idom, p, &immediate_dominators); } } if new_idom == usize::MAX { // None of the predecessors of u have been processed yet. That's fine, we will // try again of the next iteration of the outer loop. continue; } if immediate_dominators[u] != new_idom { // We "improved" the immediate dominator for u! immediate_dominators[u] = new_idom; changed = true; } } if !changed { // We reached the fixed point. We are done. break; } } // At this point we know the immediate dominator of every node, but we keep the // Option wrapper so that we can use the intersect function during // find_lowest_common_ancestor. immediate_dominators } // See http://www.hipersoft.rice.edu/grads/publications/dom14.pdf for details on how this function works. fn intersect( mut b1: InternalId, mut b2: InternalId, immediate_dominators: &[InternalId], ) -> InternalId { while b1 != b2 { while b1 < b2 { b1 = immediate_dominators[b1]; } while b2 < b1 { b2 = immediate_dominators[b2]; } } b1 } // See http://www.hipersoft.rice.edu/grads/publications/dom14.pdf for details on how this function works. fn find_lowest_common_ancestor( targets: &[InternalId], immediate_dominators: &[InternalId], ) -> InternalId { targets .iter() .copied() .reduce(|a, b| Self::intersect(a, b, immediate_dominators)) .expect("targets must not be empty") } } /// Errors that can occur while finding a dominator value (i.e. a dominator in a /// value flow graph). #[derive(Debug, Error, PartialEq)] pub enum FindDominatorValueError { /// An error occurred while computing the value of a node. #[error(transparent)] ValueFnError(E), /// An error occurred while finding the dominator. #[error(transparent)] DominatorFinderError(#[from] DominatorFinderError), } /// Helper struct for constructing a value flow graph. It caches the results /// of applying value_fn to nodes, and also keeps track of the mapping from /// values to nodes and nodes to values. pub struct ValueCache { /// The function that emits values. value_fn: VF, /// Maps nodes to their corresponding values. node_values: HashMap>, /// Maps values to the nodes that have that value. value_to_nodes: HashMap, Vec>, } impl ValueCache where N: Hash + Eq + Clone, V: Hash + Eq, VF: AsyncFn(&N) -> Result, { /// Creates a new ValueCache that uses the given function to get values. pub fn new(value_fn: VF) -> Self { Self { value_fn, node_values: HashMap::new(), value_to_nodes: HashMap::new(), } } /// Returns the value for the given node, computing it if it is not already /// cached. pub async fn get_value(&mut self, node: &N) -> Result, E> { let values = self.get_values([node]).await?; assert_eq!(values.len(), 1); Ok(values[0].clone()) } /// Returns the nodes that have the given value. pub fn get_nodes_for_value(&self, value: &Rc) -> Option<&Vec> { self.value_to_nodes.get(value) } /// Returns the value of the given nodes, computing them (if they are not /// already cached) asynchronously and concurrently. The result will have /// the same cardinality as `nodes`, and may contain duplicate values /// (e.g. if multiple nodes have the same value, or if /// there are repeated nodes). The order of the result is not specified /// (any order is possible). async fn get_values<'a, NI>(&mut self, nodes: NI) -> Result>, E> where N: 'a, NI: IntoIterator, { let mut values = vec![]; let mut futures = vec![]; // 1. Filter out nodes already in the map and create futures for new nodes. for node in nodes { match self.node_values.get(node) { Some(value) => { values.push(value.clone()); } None => { // This node is not cached, we will compute its value. futures.push(async { let value = (self.value_fn)(node).await?; Ok((node.clone(), Rc::new(value))) }); } } } // 2. Run all new futures concurrently let new_results: Vec<(N, Rc)> = try_join_all(futures).await?; // 3. Insert the new entries into the maps. for (node, value) in new_results { self.node_values.insert(node.clone(), value.clone()); self.value_to_nodes .entry(value.clone()) .or_default() .push(node); values.push(value); } Ok(values) } } impl FlowGraph where N: Clone + Eq + Hash, { /// Constructs a new FlowGraph. pub fn new(graph: SimpleDirectedGraph, start_node: N) -> Self { Self { graph, start_node } } /// Creates a flow graph of values from a flow graph of nodes. /// /// More precisely, let G be a FlowGraph of nodes with start node S. The /// value flow graph G' is a FlowGraph derived from G. Let v(g) be the /// result of applying value_fn to g. The nodes of G' are the set of values /// v(g), for all g in G. For each edge g1->g2 in G, there is a /// corresponding edge v(g1)->v(g2) in G'. The start node in G' is v(S). /// /// Returns an error if any value_fn invocation fails. pub fn create_value_flow_graph<'a, V>(&self, node_values: &'a HashMap) -> FlowGraph<&'a V> where V: Eq + Hash, { let mut edges = vec![]; let start_value = node_values.get(&self.start_node).expect("cached"); for (parent, children) in &self.graph.adj { let parent_value = node_values.get(parent).expect("cached"); for child in children { let child_value = node_values.get(child).expect("cached"); edges.push((parent_value, child_value)); } } FlowGraph::new(SimpleDirectedGraph::new(edges), start_value) } /// Constructs a value flow graph from the given flow graph and value /// function, and finds the closest common dominator value for the /// values of the final nodes. Returns an error if value_fn returns an /// error for any node in the flow graph. `final_nodes` must not be empty. pub async fn find_dominator_value<'a, NI, V, VF, E>( &'a self, final_nodes: NI, value_fn: VF, ) -> Result> where NI: IntoIterator, V: Hash + Eq + Clone, VF: AsyncFn(&N) -> Result, { let mut value_cache = ValueCache::new(value_fn); let value_rc = self .find_dominator_value_with_value_cache(final_nodes, &mut value_cache) .await?; Ok((*value_rc).clone()) } /// Constructs a value flow graph from the given flow graph and value_cache, /// and finds the closest common dominator value (in the value flow graph) /// of the values of the final nodes. /// /// Returns an error if final_nodes is empty or if value_cache returns an /// error. pub async fn find_dominator_value_with_value_cache<'a, NI, V, VF, E>( &'a self, final_nodes: NI, value_cache: &mut ValueCache, ) -> Result, FindDominatorValueError> where NI: IntoIterator, V: Hash + Eq, VF: AsyncFn(&N) -> Result, { // First compute the values of all final nodes asynchronously and concurrently. let final_values = value_cache .get_values(final_nodes) .await .map_err(|e| FindDominatorValueError::ValueFnError(e))?; match &*final_values { [] => { return Err(FindDominatorValueError::DominatorFinderError( DominatorFinderError::EmptyTargetSet, )); } [final_value] => { // Optimization: if all final nodes have the same value, that value is the // closest common dominator. There is no need to build the value flow graph. return Ok(final_value.clone()); } _ => {} } let start_value = value_cache .get_value(&self.start_node) .await .map_err(|err| FindDominatorValueError::ValueFnError(err))?; if final_values.contains(&start_value) { return Ok(Rc::clone(&start_value)); } // Compute all remaining values. value_cache .get_values(self.graph.nodes()) .await .map_err(|err| FindDominatorValueError::ValueFnError(err))?; // NOTE: at this point we could compare the cardinality of the value set versus // the number of nodes: if equal then we know that every node has a // different value, and it is tempting to conclude that the result should be // `start_value` (because the shape of the value flow graph is identical // to the shape of the original flow graph). That is not always correct, // consider this example with start node A and final nodes C and D: // // A(1) -> B(2) -> C(3) // \--> D(4) // // However, IF start node IS the closest common dominator of the original graph // (it is not in the example above) then the answer would be `start_value`; // so IF we knew that to be true we could skip building the value flow graph and // running the dominator algorithm in the value flow graph. let value_flow_graph = self.create_value_flow_graph(&value_cache.node_values); let dominator_finder = DominatorFinder::calculate(&value_flow_graph)?; let dominator_value = dominator_finder.find_closest_common_dominator(final_values.iter())?; Ok(Rc::clone(dominator_value)) } } /// Traverses nodes from `start_node` in post-order. fn post_order( start_node: T, mut neighbors_fn: impl FnMut(&T) -> NI, ) -> impl Iterator where T: Clone + Hash + Eq, NI: DoubleEndedIterator, { let mut stack = vec![(start_node, false)]; let mut visited: HashSet = HashSet::new(); iter::from_fn(move || { while let Some((node, processed)) = stack.pop() { if processed { // If we marked it as processed, it means its children // were already added to the stack and processed. return Some(node); } // Mark as visited so we don't start a new DFS from here if !visited.insert(node.clone()) { // The node is already visited, continue. continue; } let neighbors = neighbors_fn(&node); // Push the node back onto the stack with processed = true. // It will be popped and yielded AFTER its children. stack.push((node, true)); // Push the neighbors onto the stack with processed = false. The neighbors are // added in reverse order, so they are processed in the // original order. for neighbor in neighbors.rev() { if !visited.contains(&neighbor) { stack.push((neighbor, false)); } } } None }) } #[cfg(test)] mod tests { use maplit::hashmap; use pollster::FutureExt as _; use super::*; #[test] fn test_closest_common_dominator_split() -> Result<(), DominatorFinderError> { // /-> B \ // A -> D // \-> C / let flow_graph = FlowGraph::new( SimpleDirectedGraph::new([("A", "B"), ("A", "C"), ("B", "D"), ("C", "D")]), "A", ); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!(df.find_closest_common_dominator(["A"])?, "A"); assert_eq!(df.find_closest_common_dominator(["B"])?, "B"); assert_eq!(df.find_closest_common_dominator(["C"])?, "C"); assert_eq!(df.find_closest_common_dominator(["D"])?, "D"); assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "A"); assert_eq!(df.find_closest_common_dominator(["B", "D"])?, "A"); assert_eq!(df.find_closest_common_dominator(["B", "C", "D"])?, "A"); Ok(()) } #[test] fn test_closest_common_dominator_linear_chain() -> Result<(), DominatorFinderError> { // A -> B -> C -> D let flow_graph = FlowGraph::new( SimpleDirectedGraph::new([("A", "B"), ("B", "C"), ("C", "D")]), "A", ); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!(df.find_closest_common_dominator(["A"])?, "A"); assert_eq!(df.find_closest_common_dominator(["B"])?, "B"); assert_eq!(df.find_closest_common_dominator(["C"])?, "C"); assert_eq!(df.find_closest_common_dominator(["D"])?, "D"); assert_eq!(df.find_closest_common_dominator(["A", "B"])?, "A"); assert_eq!(df.find_closest_common_dominator(["A", "C"])?, "A"); assert_eq!(df.find_closest_common_dominator(["A", "D"])?, "A"); assert_eq!(df.find_closest_common_dominator(["B", "D"])?, "B"); assert_eq!(df.find_closest_common_dominator(["C", "D"])?, "C"); assert_eq!(df.find_closest_common_dominator(["A", "B", "C", "D"])?, "A"); Ok(()) } #[test] fn test_closest_common_dominator_classic_diamond() -> Result<(), DominatorFinderError> { // /-> B -\ // A -> D -> E // \-> C -/ let flow_graph = FlowGraph::new( SimpleDirectedGraph::new([("A", "B"), ("A", "C"), ("B", "D"), ("C", "D"), ("D", "E")]), "A", ); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "A"); assert_eq!(df.find_closest_common_dominator(["B", "E"])?, "A"); assert_eq!(df.find_closest_common_dominator(["D"])?, "D"); assert_eq!(df.find_closest_common_dominator(["D", "E"])?, "D"); assert_eq!(df.find_closest_common_dominator(["A", "D"])?, "A"); Ok(()) } #[test] fn test_closest_common_dominator_single_node() -> Result<(), DominatorFinderError> { // A let flow_graph = FlowGraph::new(SimpleDirectedGraph::new([("A", "A")]), "A"); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!(df.find_closest_common_dominator(["A"])?, "A"); Ok(()) } #[test] fn test_invalid_flowgraph() { // /-> E // A -> B // \-> F // ^ // | // C --> D --/ let flow_graph = FlowGraph::new( SimpleDirectedGraph::new([("A", "B"), ("B", "E"), ("B", "F"), ("C", "D"), ("D", "F")]), "A", ); assert_eq!( DominatorFinder::calculate(&flow_graph).err(), Some(DominatorFinderError::UnreachableNodesInFlowGraph) ); } #[test] fn test_closest_common_dominator_simple_cycle_with_entry() -> Result<(), DominatorFinderError> { // // A -> B -> C -> D // ^ | // | | // \--------/ let flow_graph = FlowGraph::new( SimpleDirectedGraph::new([("A", "B"), ("B", "C"), ("C", "D"), ("D", "B")]), "A", ); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!(df.find_closest_common_dominator(["A", "B"])?, "A"); assert_eq!(df.find_closest_common_dominator(["A", "C"])?, "A"); assert_eq!(df.find_closest_common_dominator(["A", "B", "C"])?, "A"); assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "B"); assert_eq!(df.find_closest_common_dominator(["B", "C", "D"])?, "B"); assert_eq!(df.find_closest_common_dominator(["A"])?, "A"); assert_eq!(df.find_closest_common_dominator(["B"])?, "B"); assert_eq!(df.find_closest_common_dominator(["C"])?, "C"); assert_eq!(df.find_closest_common_dominator(["D"])?, "D"); Ok(()) } #[test] fn test_closest_common_dominator_figure_eight_with_bridge() -> Result<(), DominatorFinderError> { // // A -> B -> C -> D -> E -> F -> G // ^ | ^ | // | | | | // \_______/ \_______/ let flow_graph = FlowGraph::new( SimpleDirectedGraph::new([ ("A", "B"), // entry ("B", "C"), ("C", "D"), ("D", "B"), // Loop 1 ("D", "E"), // Bridge ("E", "F"), ("F", "G"), ("G", "E"), // Loop 2 ]), "A", ); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "B"); assert_eq!(df.find_closest_common_dominator(["B", "D"])?, "B"); assert_eq!(df.find_closest_common_dominator(["B", "E"])?, "B"); assert_eq!(df.find_closest_common_dominator(["C", "E"])?, "C"); assert_eq!(df.find_closest_common_dominator(["C", "F"])?, "C"); assert_eq!(df.find_closest_common_dominator(["D", "E"])?, "D"); assert_eq!(df.find_closest_common_dominator(["D", "F"])?, "D"); assert_eq!(df.find_closest_common_dominator(["E", "G"])?, "E"); assert_eq!(df.find_closest_common_dominator(["F", "G"])?, "F"); Ok(()) } #[test] fn test_closest_common_dominator_figure_eight() -> Result<(), DominatorFinderError> { // // A -> B -> C --> D -> E -> F // ^ | ^ | // | | | | // \_______/ \_________/ let flow_graph = FlowGraph::new( SimpleDirectedGraph::new([ ("A", "B"), // entry ("B", "C"), ("C", "D"), ("D", "B"), // Loop 1 ("D", "E"), ("E", "F"), ("F", "D"), // Loop 2 ]), "A", ); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "B"); assert_eq!(df.find_closest_common_dominator(["B", "D"])?, "B"); assert_eq!(df.find_closest_common_dominator(["B", "E"])?, "B"); assert_eq!(df.find_closest_common_dominator(["C", "D"])?, "C"); assert_eq!(df.find_closest_common_dominator(["C", "E"])?, "C"); assert_eq!(df.find_closest_common_dominator(["C", "F"])?, "C"); assert_eq!(df.find_closest_common_dominator(["D", "E"])?, "D"); assert_eq!(df.find_closest_common_dominator(["D", "F"])?, "D"); assert_eq!(df.find_closest_common_dominator(["E", "F"])?, "E"); Ok(()) } #[test] fn test_closest_common_dominator_entry_cycle_dominance() -> Result<(), DominatorFinderError> { // A -> B -> C // ^ | // |----/ let flow_graph = FlowGraph::new( SimpleDirectedGraph::new([("A", "B"), ("B", "C"), ("C", "B")]), "A", ); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!(df.find_closest_common_dominator(["A", "B"])?, "A"); assert_eq!(df.find_closest_common_dominator(["A", "C"])?, "A"); assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "B"); assert_eq!(df.find_closest_common_dominator(["A", "B", "C"])?, "A"); Ok(()) } #[test] fn test_closest_common_dominator_nested_loops() -> Result<(), DominatorFinderError> { // /---> E // | | // | | // A -> B -> C <--/ // ^ | // | V // \----D let flow_graph = FlowGraph::new( SimpleDirectedGraph::new([ ("A", "B"), ("B", "C"), ("C", "D"), ("C", "E"), ("E", "C"), ("D", "B"), ]), "A", ); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!(df.find_closest_common_dominator(["A", "B"])?, "A"); assert_eq!(df.find_closest_common_dominator(["A", "C"])?, "A"); assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "B"); assert_eq!(df.find_closest_common_dominator(["B", "D"])?, "B"); assert_eq!(df.find_closest_common_dominator(["B", "E"])?, "B"); assert_eq!(df.find_closest_common_dominator(["C", "D"])?, "C"); assert_eq!(df.find_closest_common_dominator(["C", "E"])?, "C"); assert_eq!(df.find_closest_common_dominator(["D", "E"])?, "C"); assert_eq!(df.find_closest_common_dominator(["B", "C", "D"])?, "B"); assert_eq!(df.find_closest_common_dominator(["B", "C", "E"])?, "B"); assert_eq!(df.find_closest_common_dominator(["B", "D", "E"])?, "B"); assert_eq!(df.find_closest_common_dominator(["C", "D", "E"])?, "C"); assert_eq!(df.find_closest_common_dominator(["B", "C", "D", "E"])?, "B"); Ok(()) } #[test] fn test_irreducible_graph_cooper_harvey_kennedy_fig2() -> Result<(), DominatorFinderError> { // 5 // / \ // | | // V V // 4 3 // | | // V V // 1 <==> 2 let graph = SimpleDirectedGraph::new([(1, 2), (2, 1), (3, 2), (4, 1), (5, 4), (5, 3)]); let flow_graph = FlowGraph::new(graph, 5); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!( df.get_immediate_dominators(), HashMap::from([(1, 5), (2, 5), (3, 5), (4, 5), (5, 5),]) ); Ok(()) } #[test] fn test_irreducible_graph_cooper_harvey_kennedy_fig3() -> Result<(), DominatorFinderError> { // 6 // / \ // | | // v v // 5 4 -- // | | \ // v v v // 1 <=> 2 <=> 3 let graph = SimpleDirectedGraph::new([ (1, 2), (2, 1), (2, 3), (3, 2), (5, 1), (4, 2), (4, 3), (6, 5), (6, 4), ]); let flow_graph = FlowGraph::new(graph, 6); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!( df.get_immediate_dominators(), HashMap::from([(1, 6), (2, 6), (3, 6), (4, 6), (5, 6), (6, 6),]) ); assert_eq!(df.find_closest_common_dominator([2, 3])?, 6); Ok(()) } #[test] fn test_dominator_tree_with_three_levels() -> Result<(), DominatorFinderError> { // Graph taken from https://en.wikipedia.org/wiki/Dominator_(graph_theory) // 1 // | /---\ // v / \ // 2 <--\ \ // / \ \ \ // / \ \ \ // | | | | // v v | | // 3 4 | | // | | | | // \ v | v // --> 5 --/ 6 // let graph = SimpleDirectedGraph::new([(1, 2), (2, 3), (2, 4), (2, 6), (3, 5), (4, 5), (5, 2)]); let flow_graph = FlowGraph::new(graph, 1); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!( df.get_immediate_dominators(), HashMap::from([(1, 1), (2, 1), (3, 2), (4, 2), (5, 2), (6, 2),]) ); assert_eq!(df.find_closest_common_dominator([1, 6])?, 1); assert_eq!(df.find_closest_common_dominator([2, 3])?, 2); assert_eq!(df.find_closest_common_dominator([2, 4])?, 2); assert_eq!(df.find_closest_common_dominator([2, 5])?, 2); assert_eq!(df.find_closest_common_dominator([2, 6])?, 2); assert_eq!(df.find_closest_common_dominator([3, 4])?, 2); assert_eq!(df.find_closest_common_dominator([3, 5])?, 2); assert_eq!(df.find_closest_common_dominator([3, 6])?, 2); assert_eq!(df.find_closest_common_dominator([4, 5])?, 2); assert_eq!(df.find_closest_common_dominator([4, 6])?, 2); assert_eq!(df.find_closest_common_dominator([5, 6])?, 2); assert_eq!(df.find_closest_common_dominator([2, 3, 5])?, 2); assert_eq!(df.find_closest_common_dominator([3, 4, 5])?, 2); assert_eq!(df.find_closest_common_dominator([3, 4, 5, 6])?, 2); Ok(()) } #[test] fn test_big_graph_fig_18_3() -> Result<(), DominatorFinderError> { // Graph taken from Modern Compiler Implementation in Java, // by Appel and Palsberg, 2004 // // 1 // | // v // /--> 2 <--\ // | / \ | // | v v | // \- 3 4 -/ // /\ // / \ // v v // /--> 5 6 // / / \ / // / | || // / v vv // | /-> 8 7 // | | | | // | | v v // | \-- 9 11 // | | | // | v v // \--- 10 --> 12 // let graph = SimpleDirectedGraph::new([ (1, 2), (2, 3), (2, 4), (3, 2), (4, 2), (4, 5), (4, 6), (5, 7), (5, 8), (6, 7), (7, 11), (8, 9), (9, 8), (9, 10), (10, 5), (10, 12), (11, 12), ]); let flow_graph = FlowGraph::new(graph, 1); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!( df.get_immediate_dominators(), HashMap::from([ (1, 1), (2, 1), (3, 2), (4, 2), (5, 4), (6, 4), (7, 4), (8, 5), (9, 8), (10, 9), (11, 7), (12, 4) ]) ); assert_eq!(df.find_closest_common_dominator([6, 3])?, 2); assert_eq!(df.find_closest_common_dominator([11, 9, 12])?, 4); assert_eq!(df.find_closest_common_dominator([11, 9, 5])?, 4); assert_eq!(df.find_closest_common_dominator([11, 10])?, 4); assert_eq!(df.find_closest_common_dominator([10, 11, 12, 3, 6])?, 2); Ok(()) } #[test] fn test_big_graph_fig_19_8() -> Result<(), DominatorFinderError> { // Graph taken from Modern Compiler Implementation in Java, // by Appel and Palsberg, 2004 // // /----- A ----\ // | | // v v // /----> B ---\ /-> C --\ // | | | | | | // | v | | v | // | /-- D | \-- E | // | | | | | | // | | | | | | // | v | v v v // | F \--> G H // | |\ | / // | | \ v / // | | \ /---J / // | | X / // | | / \ / // | vv v | // | I K | // | \ / | // | \ / | // | vv v // \---- L ---------> M // let graph = SimpleDirectedGraph::new([ ("A", "B"), ("A", "C"), ("B", "D"), ("B", "G"), ("C", "E"), ("C", "H"), ("D", "F"), ("D", "G"), ("E", "C"), ("E", "H"), ("F", "I"), ("F", "K"), ("G", "J"), ("H", "M"), ("I", "L"), ("J", "I"), ("K", "L"), ("L", "B"), ("L", "M"), ]); let flow_graph = FlowGraph::new(graph, "A"); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!( df.get_immediate_dominators(), HashMap::from([ ("A", "A"), ("B", "A"), ("C", "A"), ("D", "B"), ("E", "C"), ("F", "D"), ("G", "B"), ("H", "C"), ("I", "B"), ("J", "G"), ("K", "F"), ("L", "B"), ("M", "A"), ]) ); assert_eq!(df.find_closest_common_dominator(["K", "L"])?, "B"); assert_eq!(df.find_closest_common_dominator(["K", "C"])?, "A"); assert_eq!(df.find_closest_common_dominator(["B", "G", "J"])?, "B"); Ok(()) } #[test] fn test_closest_common_dominator_tree() -> Result<(), DominatorFinderError> { // A -> B -> C // \ \-> D // \------> E let flow_graph = FlowGraph::new( SimpleDirectedGraph::new([("A", "B"), ("B", "C"), ("B", "D"), ("A", "E")]), "A", ); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "B"); assert_eq!(df.find_closest_common_dominator(["B", "E"])?, "A"); assert_eq!(df.find_closest_common_dominator(["C", "D"])?, "B"); assert_eq!(df.find_closest_common_dominator(["C", "E"])?, "A"); assert_eq!(df.find_closest_common_dominator(["B", "C", "D"])?, "B"); assert_eq!(df.find_closest_common_dominator(["C", "D", "E"])?, "A"); Ok(()) } #[test] fn test_closest_common_dominator_bypassing_path() -> Result<(), DominatorFinderError> { // A -> B -> C -> D // | ^ // v | // E -------------/ let flow_graph = FlowGraph::new( SimpleDirectedGraph::new([("A", "B"), ("B", "C"), ("C", "D"), ("A", "E"), ("E", "D")]), "A", ); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "B"); assert_eq!(df.find_closest_common_dominator(["B", "D"])?, "A"); assert_eq!(df.find_closest_common_dominator(["B", "E"])?, "A"); assert_eq!(df.find_closest_common_dominator(["C", "D"])?, "A"); assert_eq!(df.find_closest_common_dominator(["C", "E"])?, "A"); assert_eq!(df.find_closest_common_dominator(["D", "E"])?, "A"); assert_eq!(df.find_closest_common_dominator(["B", "C", "D"])?, "A"); assert_eq!(df.find_closest_common_dominator(["C", "D", "E"])?, "A"); Ok(()) } #[test] fn test_closest_common_dominator_self_loop_handling() -> Result<(), DominatorFinderError> { // A->A (Self loop), A->B let flow_graph = FlowGraph::new(SimpleDirectedGraph::new([("A", "A"), ("A", "B")]), "A"); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!(df.find_closest_common_dominator(["A"])?, "A"); Ok(()) } #[test] fn test_closest_common_dominator_multi_edge() -> Result<(), DominatorFinderError> { // Shape: A->B (x2), B->C. let flow_graph = FlowGraph::new( SimpleDirectedGraph::new([ ("A", "B"), ("A", "B"), // Duplicate edge ("B", "C"), ]), "A", ); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!(df.find_closest_common_dominator(["A"])?, "A"); assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "B"); Ok(()) } #[test] fn test_closest_common_dominator_invalid_target_set() -> Result<(), DominatorFinderError> { // A -> B let flow_graph = FlowGraph::new(SimpleDirectedGraph::new([("A", "B")]), "A"); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!( df.find_closest_common_dominator([]), Err(DominatorFinderError::EmptyTargetSet) ); Ok(()) } #[test] fn test_closest_common_dominator_repeated_node() -> Result<(), DominatorFinderError> { // A -> B let flow_graph = FlowGraph::new(SimpleDirectedGraph::new([("A", "B")]), "A"); let df = DominatorFinder::calculate(&flow_graph)?; assert_eq!(df.find_closest_common_dominator(["A", "B", "A", "B"])?, "A"); Ok(()) } #[test] fn test_simple_directed_graph_nodes() { let graph = SimpleDirectedGraph::new([("A", "B"), ("B", "C")]); let nodes = graph.nodes().copied().collect_vec(); assert_eq!(nodes, ["A", "B", "C"]); let graph = SimpleDirectedGraph::::new([]); let nodes = graph.nodes().cloned().collect_vec(); assert!(nodes.is_empty()); } #[test] fn test_simple_directed_graph_edges() { let graph = SimpleDirectedGraph::new([("A", "B"), ("B", "C"), ("A", "C")]); let edges = graph.edges().map(|(&u, &v)| (u, v)).collect_vec(); assert_eq!(edges, [("A", "B"), ("A", "C"), ("B", "C")]); let graph = SimpleDirectedGraph::::new([]); let edges = graph.edges().collect_vec(); assert!(edges.is_empty()); } #[test] fn test_simple_directed_graph_adjacent_nodes() { let graph = SimpleDirectedGraph::new([("A", "B"), ("A", "C"), ("B", "D")]); assert_eq!( graph.adjacent_nodes(&"A").unwrap().copied().collect_vec(), ["B", "C"] ); assert_eq!( graph.adjacent_nodes(&"B").unwrap().copied().collect_vec(), ["D"] ); assert!(graph.adjacent_nodes(&"C").unwrap().next().is_none()); assert!(graph.adjacent_nodes(&"Z").is_none()); } #[test] fn test_simple_directed_graph_contains_node() { let graph = SimpleDirectedGraph::new([("A", "B"), ("B", "C")]); assert!(graph.contains_node(&"A")); assert!(graph.contains_node(&"B")); assert!(graph.contains_node(&"C")); assert!(!graph.contains_node(&"D")); } #[test] fn test_simple_directed_graph_new() { let graph = SimpleDirectedGraph::new([("A", "B"), ("A", "C"), ("B", "C"), ("A", "B")]); let nodes = graph.nodes().copied().collect_vec(); assert_eq!(nodes, ["A", "B", "C"]); let edges = graph.edges().map(|(&u, &v)| (u, v)).collect_vec(); assert_eq!(edges, [("A", "B"), ("A", "C"), ("B", "C")]); let graph = SimpleDirectedGraph::new([("B", "C"), ("A", "B")]); let nodes = graph.nodes().copied().collect_vec(); assert_eq!(nodes, ["B", "C", "A"]); let edges = graph.edges().map(|(&u, &v)| (u, v)).collect_vec(); assert_eq!(edges, [("B", "C"), ("A", "B")]); } #[test] fn test_flow_graph_new() { let graph = SimpleDirectedGraph::new([("A", "B")]); let flow_graph = FlowGraph::new(graph.clone(), "A"); assert_eq!(flow_graph.graph, graph); assert_eq!(flow_graph.start_node, "A"); let flow_graph = FlowGraph::new(graph.clone(), "C"); assert_eq!(flow_graph.graph, graph); assert_eq!(flow_graph.start_node, "C"); } #[test] fn test_post_order() { // This graph: // o F // |\ // o | E // | o D // | o C // | o B // |/ // o A let neighbors = hashmap! { 'A' => vec![], 'B' => vec!['A'], 'C' => vec!['B'], 'D' => vec!['C'], 'E' => vec!['A'], 'F' => vec!['E', 'D'], }; let neighbors_fn = |node: &char| neighbors[node].iter().copied(); assert_eq!( post_order('F', neighbors_fn).collect_vec(), ['A', 'E', 'B', 'C', 'D', 'F'] ); assert_eq!(post_order('E', neighbors_fn).collect_vec(), ['A', 'E']); assert_eq!( post_order('D', neighbors_fn).collect_vec(), ['A', 'B', 'C', 'D'] ); assert_eq!(post_order('A', neighbors_fn).collect_vec(), ['A']); // This graph: // o I // |\ // | o H // | |\ // | | o G // | o | F // | | o E // o |/ D // | o C // o | B // |/ // o A let neighbors = hashmap! { 'A' => vec![], 'B' => vec!['A'], 'C' => vec!['A'], 'D' => vec!['B'], 'E' => vec!['C'], 'F' => vec!['C'], 'G' => vec!['E'], 'H' => vec!['F', 'G'], 'I' => vec!['D', 'H'], }; let neighbors_fn = |node: &char| neighbors[node].iter().copied(); assert_eq!( post_order('I', neighbors_fn).collect_vec(), ['A', 'B', 'D', 'C', 'F', 'E', 'G', 'H', 'I'] ); // This graph: // o I // |\ // | |\ // | | |\ // | | | o h (h > I) // | | |/| // | | o | G // | |/| o f // | o |/ e (e > I, G) // |/| o D // o |/ C // | o b (b > D) // |/ // o A let neighbors = hashmap! { 'A' => vec![], 'b' => vec!['A'], 'C' => vec!['A'], 'D' => vec!['b'], 'e' => vec!['C', 'b'], 'f' => vec!['D'], 'G' => vec!['e', 'D'], 'h' => vec!['G', 'f'], 'I' => vec!['C', 'e', 'G', 'h'], }; let neighbors_fn = |node: &char| neighbors[node].iter().copied(); assert_eq!( post_order('I', neighbors_fn).collect_vec(), ['A', 'C', 'b', 'e', 'D', 'G', 'f', 'h', 'I'] ); // This graph: // o G // |\ // | o F // o | E // | o D // |/ // o C // o B // o A let neighbors = hashmap! { 'A' => vec![], 'B' => vec!['A'], 'C' => vec!['B'], 'D' => vec!['C'], 'E' => vec!['C'], 'F' => vec!['D'], 'G' => vec!['E', 'F'], }; let neighbors_fn = |node: &char| neighbors[node].iter().copied(); assert_eq!( post_order('G', neighbors_fn).collect_vec(), ['A', 'B', 'C', 'E', 'D', 'F', 'G'] ); // This graph: // o G // |\ // o | F // o | E // | o D // |/ // o c (c > E, D) // o B // o A let neighbors = hashmap! { 'A' => vec![], 'B' => vec!['A'], 'c' => vec!['B'], 'D' => vec!['c'], 'E' => vec!['c'], 'F' => vec!['E'], 'G' => vec!['F', 'D'], }; let neighbors_fn = |node: &char| neighbors[node].iter().copied(); assert_eq!( post_order('G', neighbors_fn).collect_vec(), ['A', 'B', 'c', 'E', 'F', 'D', 'G'] ); // This graph: // o F // |\ // o | E // | o D // | | o C // | | | // | | o B // | |/ // |/ // o A let neighbors = hashmap! { 'A' => vec![], 'B' => vec!['A'], 'C' => vec!['B'], 'D' => vec!['A'], 'E' => vec!['A'], 'F' => vec!['E', 'D'], }; let neighbors_fn = |node: &char| neighbors[node].iter().copied(); assert_eq!( post_order('F', neighbors_fn).collect_vec(), ['A', 'E', 'D', 'F'] ); assert_eq!(post_order('C', neighbors_fn).collect_vec(), ['A', 'B', 'C']); // This graph: // o D // | \ // o | C // o B // o A let neighbors = hashmap! { 'A' => vec![], 'B' => vec!['A'], 'C' => vec![], 'D' => vec!['C', 'B'], }; let neighbors_fn = |node: &char| neighbors[node].iter().copied(); assert_eq!( post_order('D', neighbors_fn).collect_vec(), ['C', 'A', 'B', 'D'] ); // This graph: // o C // o B // o A (to C) let neighbors = hashmap! { 'A' => vec!['C'], 'B' => vec!['A'], 'C' => vec!['B'], }; let neighbors_fn = |node: &char| neighbors[node].iter().copied(); assert_eq!(post_order('C', neighbors_fn).collect_vec(), ['A', 'B', 'C']); assert_eq!(post_order('B', neighbors_fn).collect_vec(), ['C', 'A', 'B']); assert_eq!(post_order('A', neighbors_fn).collect_vec(), ['B', 'C', 'A']); } #[test] fn test_value_flow_graph_new() { // A(1) -> B(1) -> C(2) let simple_graph = SimpleDirectedGraph::new([("A", "B"), ("B", "C")]); let flow_graph = FlowGraph::new(simple_graph, "A"); let node_values = HashMap::from([("A", 1), ("B", 1), ("C", 2)]); let value_flow_graph = flow_graph.create_value_flow_graph(&node_values); let expected_value_edges = [(&1, &1), (&1, &2)]; let expected_flow_graph = FlowGraph::new(SimpleDirectedGraph::new(expected_value_edges), &1); assert_eq!(value_flow_graph, expected_flow_graph); } #[test] fn test_value_flow_graph_find_dominator_value() { // A(1) -> B(1) -> C(2) -> D(3) // \------------> E(3) let simple_graph = SimpleDirectedGraph::new([("A", "B"), ("B", "C"), ("C", "D"), ("B", "E")]); let flow_graph = FlowGraph::new(simple_graph, "A"); let value_fn = async |node: &&str| match *node { "A" | "B" => Ok(1), "C" => Ok(2), "D" | "E" => Ok(3), _ => Err("Unknown node".to_string()), }; // Value graph (* means node has a self-loop): // 1* -> 2 -> 3 // \ ^ // \--------| assert_eq!( flow_graph .find_dominator_value(&["D", "E"], value_fn) .block_on(), Ok(3) ); assert_eq!( flow_graph .find_dominator_value(&["C", "D"], value_fn) .block_on(), Ok(1) ); assert_eq!( flow_graph .find_dominator_value(&["B", "C"], value_fn) .block_on(), Ok(1) ); } #[test] fn test_find_dominator_value_with_distinct_values() { // A(1) -> B(2) -> C(3) -> D(4) // \------------> E(5) let simple_graph = SimpleDirectedGraph::new([("A", "B"), ("B", "C"), ("C", "D"), ("B", "E")]); let flow_graph = FlowGraph::new(simple_graph, "A"); let value_fn = async |node: &&str| match *node { "A" => Ok(1), "B" => Ok(2), "C" => Ok(3), "D" => Ok(4), "E" => Ok(5), _ => Err("Unknown node".to_string()), }; // Value graph: // 1 -> 2 -> 3 -> 4 // \------> 5 assert_eq!( flow_graph .find_dominator_value(&["D", "E"], value_fn) .block_on(), Ok(2) ); assert_eq!( flow_graph .find_dominator_value(&["C", "D"], value_fn) .block_on(), Ok(3) ); assert_eq!( flow_graph .find_dominator_value(&["B", "C"], value_fn) .block_on(), Ok(2) ); } #[test] fn test_find_dominator_value_with_invalid_flow_graph() { // Invalid flow graph: A(1) -> B(1), C(2) -> D(2) (C and D are not reachable // from A). let simple_graph = SimpleDirectedGraph::new([("A", "B"), ("C", "D")]); let flow_graph = FlowGraph::new(simple_graph, "A"); let value_fn = async |node: &&str| match *node { "A" | "B" => Ok(1), "C" | "D" => Ok(2), _ => Err("Unknown node".to_string()), }; // Todo: the flow_graph is invalid because C and D are not reachable from A, so // ideally find_dominator_value should return UnreachableNodesInFlowGraph, but // the optimizations in find_dominator_value currently cause it to // return the start value. The best way to fix this is to calculate (and store) // the post-order in FlowGraph::new, that way we could not possibly construct an // invalid flow graph. This is not a big concern in practice though. assert_eq!( flow_graph .find_dominator_value(&["B", "D"], value_fn) .block_on(), Ok(1) ); } #[test] fn test_find_dominator_value_with_unknown_node_in_target_set() { // Flow graph: A(1) -> B(2). let simple_graph = SimpleDirectedGraph::new([("A", "B")]); let flow_graph = FlowGraph::new(simple_graph, "A"); let value_fn = async |node: &&str| match *node { "A" => Ok(1), "B" => Ok(2), "X" => Ok(666), _ => Err("Unknown node".to_string()), }; assert_eq!( flow_graph .find_dominator_value(&["B", "X"], value_fn) .block_on(), Err(FindDominatorValueError::DominatorFinderError( DominatorFinderError::UnknownNodeInTargetSet )) ); } #[test] fn test_find_dominator_value_with_unknown_node() { // Flow graph: A(1) -> B(2). let simple_graph = SimpleDirectedGraph::new([("A", "B")]); let flow_graph = FlowGraph::new(simple_graph, "A"); let value_fn = async |node: &&str| match *node { "A" => Ok(1), "B" => Ok(2), _ => Err("Unknown node".to_string()), }; assert_eq!( flow_graph .find_dominator_value(&["B", "X"], value_fn) .block_on(), Err(FindDominatorValueError::ValueFnError( "Unknown node".to_string() )) ); } }