|
| 1 | +//! A control-flow graph can be said to have “balanced flow” if the flow |
| 2 | +//! (execution count) of each node is equal to the sum of its in-edge flows, |
| 3 | +//! and also equal to the sum of its out-edge flows. |
| 4 | +//! |
| 5 | +//! Control-flow graphs typically have one or more nodes that don't satisfy the |
| 6 | +//! balanced-flow property, e.g.: |
| 7 | +//! - The start node has out-edges, but no in-edges. |
| 8 | +//! - Return nodes have in-edges, but no out-edges. |
| 9 | +//! - `Yield` nodes can have an out-flow that is less than their in-flow. |
| 10 | +//! - Inescapable loops cause the in-flow/out-flow relationship to break down. |
| 11 | +//! |
| 12 | +//! Balanced-flow graphs are nevertheless useful for analysis, so this module |
| 13 | +//! provides a wrapper type ([`BalancedFlowGraph`]) that imposes balanced flow |
| 14 | +//! on an underlying graph. This is done by non-destructively adding synthetic |
| 15 | +//! nodes and edges as necessary. |
| 16 | +
|
| 17 | +use rustc_data_structures::graph; |
| 18 | +use rustc_data_structures::graph::iterate::DepthFirstSearch; |
| 19 | +use rustc_data_structures::graph::reversed::ReversedGraph; |
| 20 | +use rustc_index::Idx; |
| 21 | +use rustc_index::bit_set::DenseBitSet; |
| 22 | + |
| 23 | +use crate::coverage::counters::iter_nodes::IterNodes; |
| 24 | + |
| 25 | +/// A view of an underlying graph that has been augmented to have “balanced flow”. |
| 26 | +/// This means that the flow (execution count) of each node is equal to the |
| 27 | +/// sum of its in-edge flows, and also equal to the sum of its out-edge flows. |
| 28 | +/// |
| 29 | +/// To achieve this, a synthetic "sink" node is non-destructively added to the |
| 30 | +/// graph, with synthetic in-edges from these nodes: |
| 31 | +/// - Any node that has no out-edges. |
| 32 | +/// - Any node that explicitly requires a sink edge, as indicated by a |
| 33 | +/// caller-supplied `force_sink_edge` function. |
| 34 | +/// - Any node that would otherwise be unable to reach the sink, because it is |
| 35 | +/// part of an inescapable loop. |
| 36 | +/// |
| 37 | +/// To make the graph fully balanced, there is also a synthetic edge from the |
| 38 | +/// sink node back to the start node. |
| 39 | +/// |
| 40 | +/// --- |
| 41 | +/// The benefit of having a balanced-flow graph is that it can be subsequently |
| 42 | +/// transformed in ways that are guaranteed to preserve balanced flow |
| 43 | +/// (e.g. merging nodes together), which is useful for discovering relationships |
| 44 | +/// between the node flows of different nodes in the graph. |
| 45 | +pub(crate) struct BalancedFlowGraph<G: graph::DirectedGraph> { |
| 46 | + graph: G, |
| 47 | + sink_edge_nodes: DenseBitSet<G::Node>, |
| 48 | + pub(crate) sink: G::Node, |
| 49 | +} |
| 50 | + |
| 51 | +impl<G: graph::DirectedGraph> BalancedFlowGraph<G> { |
| 52 | + /// Creates a balanced view of an underlying graph, by adding a synthetic |
| 53 | + /// sink node that has in-edges from nodes that need or request such an edge, |
| 54 | + /// and a single out-edge to the start node. |
| 55 | + /// |
| 56 | + /// Assumes that all nodes in the underlying graph are reachable from the |
| 57 | + /// start node. |
| 58 | + pub(crate) fn for_graph(graph: G, force_sink_edge: impl Fn(G::Node) -> bool) -> Self |
| 59 | + where |
| 60 | + G: graph::ControlFlowGraph, |
| 61 | + { |
| 62 | + let mut sink_edge_nodes = DenseBitSet::new_empty(graph.num_nodes()); |
| 63 | + let mut dfs = DepthFirstSearch::new(ReversedGraph::new(&graph)); |
| 64 | + |
| 65 | + // First, determine the set of nodes that explicitly request or require |
| 66 | + // an out-edge to the sink. |
| 67 | + for node in graph.iter_nodes() { |
| 68 | + if force_sink_edge(node) || graph.successors(node).next().is_none() { |
| 69 | + sink_edge_nodes.insert(node); |
| 70 | + dfs.push_start_node(node); |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + // Next, find all nodes that are currently not reverse-reachable from |
| 75 | + // `sink_edge_nodes`, and add them to the set as well. |
| 76 | + dfs.complete_search(); |
| 77 | + sink_edge_nodes.union_not(dfs.visited_set()); |
| 78 | + |
| 79 | + // The sink node is 1 higher than the highest real node. |
| 80 | + let sink = G::Node::new(graph.num_nodes()); |
| 81 | + |
| 82 | + BalancedFlowGraph { graph, sink_edge_nodes, sink } |
| 83 | + } |
| 84 | +} |
| 85 | + |
| 86 | +impl<G> graph::DirectedGraph for BalancedFlowGraph<G> |
| 87 | +where |
| 88 | + G: graph::DirectedGraph, |
| 89 | +{ |
| 90 | + type Node = G::Node; |
| 91 | + |
| 92 | + /// Returns the number of nodes in this balanced-flow graph, which is 1 |
| 93 | + /// more than the number of nodes in the underlying graph, to account for |
| 94 | + /// the synthetic sink node. |
| 95 | + fn num_nodes(&self) -> usize { |
| 96 | + // The sink node's index is already the size of the underlying graph, |
| 97 | + // so just add 1 to that instead. |
| 98 | + self.sink.index() + 1 |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | +impl<G> graph::StartNode for BalancedFlowGraph<G> |
| 103 | +where |
| 104 | + G: graph::StartNode, |
| 105 | +{ |
| 106 | + fn start_node(&self) -> Self::Node { |
| 107 | + self.graph.start_node() |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +impl<G> graph::Successors for BalancedFlowGraph<G> |
| 112 | +where |
| 113 | + G: graph::StartNode + graph::Successors, |
| 114 | +{ |
| 115 | + fn successors(&self, node: Self::Node) -> impl Iterator<Item = Self::Node> { |
| 116 | + let real_edges; |
| 117 | + let sink_edge; |
| 118 | + |
| 119 | + if node == self.sink { |
| 120 | + // The sink node has no real out-edges, and one synthetic out-edge |
| 121 | + // to the start node. |
| 122 | + real_edges = None; |
| 123 | + sink_edge = Some(self.graph.start_node()); |
| 124 | + } else { |
| 125 | + // Real nodes have their real out-edges, and possibly one synthetic |
| 126 | + // out-edge to the sink node. |
| 127 | + real_edges = Some(self.graph.successors(node)); |
| 128 | + sink_edge = self.sink_edge_nodes.contains(node).then_some(self.sink); |
| 129 | + } |
| 130 | + |
| 131 | + real_edges.into_iter().flatten().chain(sink_edge) |
| 132 | + } |
| 133 | +} |
0 commit comments