Coverage Report

Created: 2026-10-31 07:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wasmtime/crates/environ/src/graphs/scc.rs
Line
Count
Source
1
//! Strongly-connected components.
2
//!
3
//! This module implements [Tarjan's algorithm] for finding strongly-connected
4
//! components.
5
//!
6
//! This algorithm takes `O(V+E)` time and uses `O(V+E)` space.
7
//!
8
//! Tarjan's algorithm is usually presented as a recursive algorithm, but we do
9
//! not trust the input and cannot recurse over it for fear of blowing the
10
//! stack. Therefore, this implementation is iterative.
11
//!
12
//! [Tarjan's algorithm]: https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
13
14
use super::*;
15
use crate::{
16
    EntityRef, EntitySet, PrimaryMap, SecondaryMap, non_max::NonMaxU32,
17
    packed_option::PackedOption, prelude::*,
18
};
19
use alloc::collections::BTreeSet;
20
use core::{
21
    cmp,
22
    fmt::{self, Debug},
23
    hash::Hash,
24
    iter,
25
    ops::Range,
26
};
27
28
/// A strongly-connected component.
29
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
30
pub struct Scc(u32);
31
crate::entity_impl!(Scc);
32
33
/// The set of strongly-connected components for a graph of `Node`s.
34
pub struct StronglyConnectedComponents<Node>
35
where
36
    Node: EntityRef,
37
{
38
    /// A map from a component to the range of `self.component_nodes` that make
39
    /// up that component's nodes.
40
    components: PrimaryMap<Scc, Range<u32>>,
41
42
    /// The data storage for the values of `self.components`.
43
    component_nodes: Vec<Node>,
44
}
45
46
impl<Node> Debug for StronglyConnectedComponents<Node>
47
where
48
    Node: EntityRef + Debug,
49
{
50
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51
        struct Map<'a, Node: EntityRef + Debug>(&'a StronglyConnectedComponents<Node>);
52
53
        impl<'a, Node> Debug for Map<'a, Node>
54
        where
55
            Node: EntityRef + Debug,
56
        {
57
0
            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> fmt::Result {
58
0
                f.debug_map().entries(self.0.iter()).finish()
59
0
            }
Unexecuted instantiation: <<wasmtime_environ::graphs::scc::StronglyConnectedComponents<_> as core::fmt::Debug>::fmt::Map<wasmtime::compile::OutputIndex> as core::fmt::Debug>::fmt
Unexecuted instantiation: <<wasmtime_environ::graphs::scc::StronglyConnectedComponents<_> as core::fmt::Debug>::fmt::Map<_> as core::fmt::Debug>::fmt
60
        }
61
62
0
        f.debug_struct("StronglyConnectedComponents")
63
0
            .field("components", &Map(self))
64
0
            .finish()
65
0
    }
Unexecuted instantiation: <wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime::compile::OutputIndex> as core::fmt::Debug>::fmt
Unexecuted instantiation: <wasmtime_environ::graphs::scc::StronglyConnectedComponents<_> as core::fmt::Debug>::fmt
66
}
67
68
impl<Node> StronglyConnectedComponents<Node>
69
where
70
    Node: EntityRef + Debug,
71
{
72
    /// Find the strongly-connected for the given graph.
73
58.7k
    pub fn new<G>(graph: G) -> Self
74
58.7k
    where
75
58.7k
        G: Graph<Node>,
76
    {
77
58.7k
        let nodes = graph.nodes();
78
79
        // The resulting components and their nodes.
80
58.7k
        let mut component_nodes = vec![];
81
58.7k
        let mut components = PrimaryMap::<Scc, Range<u32>>::new();
82
83
        // The DFS index counter.
84
58.7k
        let mut index = NonMaxU32::default();
85
86
        // The DFS index and the earliest on-stack node reachable from each
87
        // node.
88
58.7k
        let (min, max) = nodes.size_hint();
89
58.7k
        let capacity = max.unwrap_or_else(|| 2 * min);
Unexecuted instantiation: <wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime_fuzzing::generators::gc_ops::types::RecGroupNode>>::new::<&wasmtime_fuzzing::generators::gc_ops::types::DenseRecGroupGraph>::{closure#0}
Unexecuted instantiation: <wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime::compile::OutputIndex>>::new::<&wasmtime_environ::graphs::FilterNodes<wasmtime_environ::graphs::entity_graph::EntityGraph<wasmtime::compile::OutputIndex>, <wasmtime::compile::CompileInputs>::compile_with_inlining::{closure#3}>>::{closure#0}
Unexecuted instantiation: <wasmtime_environ::graphs::scc::StronglyConnectedComponents<_>>::new::<_>::{closure#0}
90
58.7k
        let mut indices = SecondaryMap::<Node, Option<NonMaxU32>>::with_capacity(capacity);
91
58.7k
        let mut lowlinks = SecondaryMap::<Node, Option<NonMaxU32>>::with_capacity(capacity);
92
93
        // The stack of nodes we are currently finding an SCC for. Not the same
94
        // as the DFS stack: we only pop from this stack once we find the root
95
        // of an SCC.
96
58.7k
        let mut stack = vec![];
97
58.7k
        let mut on_stack = EntitySet::<Node>::new();
98
99
58.7k
        let mut dfs = Dfs::new(nodes);
100
1.28M
        while let Some(event) = dfs.next(
101
1.28M
            &graph,
102
            // We have seen the node before if we have assigned it a DFS index.
103
843k
            |node| indices[node].is_some(),
<wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime_fuzzing::generators::gc_ops::types::RecGroupNode>>::new::<&wasmtime_fuzzing::generators::gc_ops::types::DenseRecGroupGraph>::{closure#1}
Line
Count
Source
103
11.7k
            |node| indices[node].is_some(),
<wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime::compile::OutputIndex>>::new::<&wasmtime_environ::graphs::FilterNodes<wasmtime_environ::graphs::entity_graph::EntityGraph<wasmtime::compile::OutputIndex>, <wasmtime::compile::CompileInputs>::compile_with_inlining::{closure#3}>>::{closure#1}
Line
Count
Source
103
831k
            |node| indices[node].is_some(),
Unexecuted instantiation: <wasmtime_environ::graphs::scc::StronglyConnectedComponents<_>>::new::<_>::{closure#1}
104
        ) {
105
1.23M
            match event {
106
532k
                DfsEvent::Pre(node) => {
107
532k
                    debug_assert!(indices[node].is_none());
108
532k
                    debug_assert!(lowlinks[node].is_none());
109
110
                    // Assign an index to this node.
111
532k
                    indices[node] = Some(index);
112
113
                    // Its current lowlink is itself. This will get updated to
114
                    // be accurate as we visit the node's successors.
115
532k
                    lowlinks[node] = Some(index);
116
117
                    // Increment the DFS counter.
118
532k
                    index = NonMaxU32::new(index.get() + 1).unwrap();
119
120
                    // Push the node onto the SCC stack.
121
532k
                    stack.push(node);
122
532k
                    let is_newly_on_stack = on_stack.insert(node);
123
532k
                    debug_assert!(is_newly_on_stack);
124
                }
125
126
165k
                DfsEvent::AfterEdge(node, succ) => {
127
165k
                    debug_assert!(indices[node].is_some());
128
165k
                    debug_assert!(lowlinks[node].is_some());
129
165k
                    debug_assert!(lowlinks[node] <= indices[node]);
130
165k
                    debug_assert!(indices[succ].is_some());
131
165k
                    debug_assert!(lowlinks[succ].is_some());
132
165k
                    debug_assert!(lowlinks[succ] <= indices[succ]);
133
134
                    // If the successor is still on the SCC stack, then it is
135
                    // part of the same SCC as this node, so propagate its
136
                    // lowlink to this node's lowlink.
137
165k
                    if on_stack.contains(succ) {
138
4.98k
                        lowlinks[node] =
139
4.98k
                            Some(cmp::min(lowlinks[node].unwrap(), lowlinks[succ].unwrap()));
140
160k
                    }
141
                }
142
143
532k
                DfsEvent::Post(node) => {
144
532k
                    debug_assert!(indices[node].is_some());
145
532k
                    debug_assert!(lowlinks[node].is_some());
146
147
                    // If this node's index is the same as its lowlink, then it
148
                    // is the root of a component. Pop this component's elements
149
                    // from the SCC stack and push them into our result data
150
                    // structures.
151
532k
                    if indices[node] == lowlinks[node] {
152
531k
                        let mut done = false;
153
531k
                        components.push(extend_with_range(
154
531k
                            &mut component_nodes,
155
1.06M
                            iter::from_fn(|| {
156
1.06M
                                if done {
157
531k
                                    return None;
158
532k
                                }
159
532k
                                let v = stack.pop().unwrap();
160
532k
                                let was_on_stack = on_stack.remove(v);
161
532k
                                debug_assert!(was_on_stack);
162
532k
                                if v == node {
163
531k
                                    done = true;
164
531k
                                }
165
532k
                                Some(v)
166
1.06M
                            }),
<wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime_fuzzing::generators::gc_ops::types::RecGroupNode>>::new::<&wasmtime_fuzzing::generators::gc_ops::types::DenseRecGroupGraph>::{closure#2}
Line
Count
Source
155
11.0k
                            iter::from_fn(|| {
156
11.0k
                                if done {
157
5.04k
                                    return None;
158
6.04k
                                }
159
6.04k
                                let v = stack.pop().unwrap();
160
6.04k
                                let was_on_stack = on_stack.remove(v);
161
6.04k
                                debug_assert!(was_on_stack);
162
6.04k
                                if v == node {
163
5.04k
                                    done = true;
164
5.04k
                                }
165
6.04k
                                Some(v)
166
11.0k
                            }),
<wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime::compile::OutputIndex>>::new::<&wasmtime_environ::graphs::FilterNodes<wasmtime_environ::graphs::entity_graph::EntityGraph<wasmtime::compile::OutputIndex>, <wasmtime::compile::CompileInputs>::compile_with_inlining::{closure#3}>>::{closure#2}
Line
Count
Source
155
1.05M
                            iter::from_fn(|| {
156
1.05M
                                if done {
157
526k
                                    return None;
158
526k
                                }
159
526k
                                let v = stack.pop().unwrap();
160
526k
                                let was_on_stack = on_stack.remove(v);
161
526k
                                debug_assert!(was_on_stack);
162
526k
                                if v == node {
163
526k
                                    done = true;
164
526k
                                }
165
526k
                                Some(v)
166
1.05M
                            }),
Unexecuted instantiation: <wasmtime_environ::graphs::scc::StronglyConnectedComponents<_>>::new::<_>::{closure#2}
167
                        ));
168
1.25k
                    }
169
                }
170
            }
171
        }
172
173
58.7k
        Self {
174
58.7k
            components,
175
58.7k
            component_nodes,
176
58.7k
        }
177
58.7k
    }
<wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime_fuzzing::generators::gc_ops::types::RecGroupNode>>::new::<&wasmtime_fuzzing::generators::gc_ops::types::DenseRecGroupGraph>
Line
Count
Source
73
2.01k
    pub fn new<G>(graph: G) -> Self
74
2.01k
    where
75
2.01k
        G: Graph<Node>,
76
    {
77
2.01k
        let nodes = graph.nodes();
78
79
        // The resulting components and their nodes.
80
2.01k
        let mut component_nodes = vec![];
81
2.01k
        let mut components = PrimaryMap::<Scc, Range<u32>>::new();
82
83
        // The DFS index counter.
84
2.01k
        let mut index = NonMaxU32::default();
85
86
        // The DFS index and the earliest on-stack node reachable from each
87
        // node.
88
2.01k
        let (min, max) = nodes.size_hint();
89
2.01k
        let capacity = max.unwrap_or_else(|| 2 * min);
90
2.01k
        let mut indices = SecondaryMap::<Node, Option<NonMaxU32>>::with_capacity(capacity);
91
2.01k
        let mut lowlinks = SecondaryMap::<Node, Option<NonMaxU32>>::with_capacity(capacity);
92
93
        // The stack of nodes we are currently finding an SCC for. Not the same
94
        // as the DFS stack: we only pop from this stack once we find the root
95
        // of an SCC.
96
2.01k
        let mut stack = vec![];
97
2.01k
        let mut on_stack = EntitySet::<Node>::new();
98
99
2.01k
        let mut dfs = Dfs::new(nodes);
100
17.8k
        while let Some(event) = dfs.next(
101
17.8k
            &graph,
102
            // We have seen the node before if we have assigned it a DFS index.
103
            |node| indices[node].is_some(),
104
        ) {
105
15.8k
            match event {
106
6.04k
                DfsEvent::Pre(node) => {
107
6.04k
                    debug_assert!(indices[node].is_none());
108
6.04k
                    debug_assert!(lowlinks[node].is_none());
109
110
                    // Assign an index to this node.
111
6.04k
                    indices[node] = Some(index);
112
113
                    // Its current lowlink is itself. This will get updated to
114
                    // be accurate as we visit the node's successors.
115
6.04k
                    lowlinks[node] = Some(index);
116
117
                    // Increment the DFS counter.
118
6.04k
                    index = NonMaxU32::new(index.get() + 1).unwrap();
119
120
                    // Push the node onto the SCC stack.
121
6.04k
                    stack.push(node);
122
6.04k
                    let is_newly_on_stack = on_stack.insert(node);
123
6.04k
                    debug_assert!(is_newly_on_stack);
124
                }
125
126
3.79k
                DfsEvent::AfterEdge(node, succ) => {
127
3.79k
                    debug_assert!(indices[node].is_some());
128
3.79k
                    debug_assert!(lowlinks[node].is_some());
129
3.79k
                    debug_assert!(lowlinks[node] <= indices[node]);
130
3.79k
                    debug_assert!(indices[succ].is_some());
131
3.79k
                    debug_assert!(lowlinks[succ].is_some());
132
3.79k
                    debug_assert!(lowlinks[succ] <= indices[succ]);
133
134
                    // If the successor is still on the SCC stack, then it is
135
                    // part of the same SCC as this node, so propagate its
136
                    // lowlink to this node's lowlink.
137
3.79k
                    if on_stack.contains(succ) {
138
2.20k
                        lowlinks[node] =
139
2.20k
                            Some(cmp::min(lowlinks[node].unwrap(), lowlinks[succ].unwrap()));
140
2.20k
                    }
141
                }
142
143
6.04k
                DfsEvent::Post(node) => {
144
6.04k
                    debug_assert!(indices[node].is_some());
145
6.04k
                    debug_assert!(lowlinks[node].is_some());
146
147
                    // If this node's index is the same as its lowlink, then it
148
                    // is the root of a component. Pop this component's elements
149
                    // from the SCC stack and push them into our result data
150
                    // structures.
151
6.04k
                    if indices[node] == lowlinks[node] {
152
5.04k
                        let mut done = false;
153
5.04k
                        components.push(extend_with_range(
154
5.04k
                            &mut component_nodes,
155
5.04k
                            iter::from_fn(|| {
156
                                if done {
157
                                    return None;
158
                                }
159
                                let v = stack.pop().unwrap();
160
                                let was_on_stack = on_stack.remove(v);
161
                                debug_assert!(was_on_stack);
162
                                if v == node {
163
                                    done = true;
164
                                }
165
                                Some(v)
166
                            }),
167
                        ));
168
1.00k
                    }
169
                }
170
            }
171
        }
172
173
2.01k
        Self {
174
2.01k
            components,
175
2.01k
            component_nodes,
176
2.01k
        }
177
2.01k
    }
<wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime::compile::OutputIndex>>::new::<&wasmtime_environ::graphs::FilterNodes<wasmtime_environ::graphs::entity_graph::EntityGraph<wasmtime::compile::OutputIndex>, <wasmtime::compile::CompileInputs>::compile_with_inlining::{closure#3}>>
Line
Count
Source
73
56.6k
    pub fn new<G>(graph: G) -> Self
74
56.6k
    where
75
56.6k
        G: Graph<Node>,
76
    {
77
56.6k
        let nodes = graph.nodes();
78
79
        // The resulting components and their nodes.
80
56.6k
        let mut component_nodes = vec![];
81
56.6k
        let mut components = PrimaryMap::<Scc, Range<u32>>::new();
82
83
        // The DFS index counter.
84
56.6k
        let mut index = NonMaxU32::default();
85
86
        // The DFS index and the earliest on-stack node reachable from each
87
        // node.
88
56.6k
        let (min, max) = nodes.size_hint();
89
56.6k
        let capacity = max.unwrap_or_else(|| 2 * min);
90
56.6k
        let mut indices = SecondaryMap::<Node, Option<NonMaxU32>>::with_capacity(capacity);
91
56.6k
        let mut lowlinks = SecondaryMap::<Node, Option<NonMaxU32>>::with_capacity(capacity);
92
93
        // The stack of nodes we are currently finding an SCC for. Not the same
94
        // as the DFS stack: we only pop from this stack once we find the root
95
        // of an SCC.
96
56.6k
        let mut stack = vec![];
97
56.6k
        let mut on_stack = EntitySet::<Node>::new();
98
99
56.6k
        let mut dfs = Dfs::new(nodes);
100
1.27M
        while let Some(event) = dfs.next(
101
1.27M
            &graph,
102
            // We have seen the node before if we have assigned it a DFS index.
103
            |node| indices[node].is_some(),
104
        ) {
105
1.21M
            match event {
106
526k
                DfsEvent::Pre(node) => {
107
526k
                    debug_assert!(indices[node].is_none());
108
526k
                    debug_assert!(lowlinks[node].is_none());
109
110
                    // Assign an index to this node.
111
526k
                    indices[node] = Some(index);
112
113
                    // Its current lowlink is itself. This will get updated to
114
                    // be accurate as we visit the node's successors.
115
526k
                    lowlinks[node] = Some(index);
116
117
                    // Increment the DFS counter.
118
526k
                    index = NonMaxU32::new(index.get() + 1).unwrap();
119
120
                    // Push the node onto the SCC stack.
121
526k
                    stack.push(node);
122
526k
                    let is_newly_on_stack = on_stack.insert(node);
123
526k
                    debug_assert!(is_newly_on_stack);
124
                }
125
126
161k
                DfsEvent::AfterEdge(node, succ) => {
127
161k
                    debug_assert!(indices[node].is_some());
128
161k
                    debug_assert!(lowlinks[node].is_some());
129
161k
                    debug_assert!(lowlinks[node] <= indices[node]);
130
161k
                    debug_assert!(indices[succ].is_some());
131
161k
                    debug_assert!(lowlinks[succ].is_some());
132
161k
                    debug_assert!(lowlinks[succ] <= indices[succ]);
133
134
                    // If the successor is still on the SCC stack, then it is
135
                    // part of the same SCC as this node, so propagate its
136
                    // lowlink to this node's lowlink.
137
161k
                    if on_stack.contains(succ) {
138
2.78k
                        lowlinks[node] =
139
2.78k
                            Some(cmp::min(lowlinks[node].unwrap(), lowlinks[succ].unwrap()));
140
158k
                    }
141
                }
142
143
526k
                DfsEvent::Post(node) => {
144
526k
                    debug_assert!(indices[node].is_some());
145
526k
                    debug_assert!(lowlinks[node].is_some());
146
147
                    // If this node's index is the same as its lowlink, then it
148
                    // is the root of a component. Pop this component's elements
149
                    // from the SCC stack and push them into our result data
150
                    // structures.
151
526k
                    if indices[node] == lowlinks[node] {
152
526k
                        let mut done = false;
153
526k
                        components.push(extend_with_range(
154
526k
                            &mut component_nodes,
155
526k
                            iter::from_fn(|| {
156
                                if done {
157
                                    return None;
158
                                }
159
                                let v = stack.pop().unwrap();
160
                                let was_on_stack = on_stack.remove(v);
161
                                debug_assert!(was_on_stack);
162
                                if v == node {
163
                                    done = true;
164
                                }
165
                                Some(v)
166
                            }),
167
                        ));
168
249
                    }
169
                }
170
            }
171
        }
172
173
56.6k
        Self {
174
56.6k
            components,
175
56.6k
            component_nodes,
176
56.6k
        }
177
56.6k
    }
Unexecuted instantiation: <wasmtime_environ::graphs::scc::StronglyConnectedComponents<_>>::new::<_>
178
179
    /// Get the number of components.
180
56.6k
    pub fn len(&self) -> usize {
181
56.6k
        self.components.len()
182
56.6k
    }
<wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime::compile::OutputIndex>>::len
Line
Count
Source
180
56.6k
    pub fn len(&self) -> usize {
181
56.6k
        self.components.len()
182
56.6k
    }
Unexecuted instantiation: <wasmtime_environ::graphs::scc::StronglyConnectedComponents<_>>::len
183
184
1.58M
    fn node_range(&self, range: Range<u32>) -> &[Node] {
185
1.58M
        let start = usize::try_from(range.start).unwrap();
186
1.58M
        let end = usize::try_from(range.end).unwrap();
187
1.58M
        &self.component_nodes[start..end]
188
1.58M
    }
<wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime_fuzzing::generators::gc_ops::types::RecGroupNode>>::node_range
Line
Count
Source
184
5.04k
    fn node_range(&self, range: Range<u32>) -> &[Node] {
185
5.04k
        let start = usize::try_from(range.start).unwrap();
186
5.04k
        let end = usize::try_from(range.end).unwrap();
187
5.04k
        &self.component_nodes[start..end]
188
5.04k
    }
<wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime::compile::OutputIndex>>::node_range
Line
Count
Source
184
1.57M
    fn node_range(&self, range: Range<u32>) -> &[Node] {
185
1.57M
        let start = usize::try_from(range.start).unwrap();
186
1.57M
        let end = usize::try_from(range.end).unwrap();
187
1.57M
        &self.component_nodes[start..end]
188
1.57M
    }
Unexecuted instantiation: <wasmtime_environ::graphs::scc::StronglyConnectedComponents<_>>::node_range
189
190
    /// Iterate over each strongly-connected component and the `Node`s that are
191
    /// members of it.
192
    ///
193
    /// Iteration happens in reverse-topological order (successors are visited
194
    /// before predecessors in the resulting SCC DAG).
195
115k
    pub fn iter(&self) -> impl ExactSizeIterator<Item = (Scc, &[Node])> + '_ {
196
115k
        self.components
197
115k
            .iter()
198
1.05M
            .map(|(component, range)| (component, self.node_range(range.clone())))
<wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime_fuzzing::generators::gc_ops::types::RecGroupNode>>::iter::{closure#0}
Line
Count
Source
198
5.04k
            .map(|(component, range)| (component, self.node_range(range.clone())))
<wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime::compile::OutputIndex>>::iter::{closure#0}
Line
Count
Source
198
1.05M
            .map(|(component, range)| (component, self.node_range(range.clone())))
Unexecuted instantiation: <wasmtime_environ::graphs::scc::StronglyConnectedComponents<_>>::iter::{closure#0}
199
115k
    }
<wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime_fuzzing::generators::gc_ops::types::RecGroupNode>>::iter
Line
Count
Source
195
2.01k
    pub fn iter(&self) -> impl ExactSizeIterator<Item = (Scc, &[Node])> + '_ {
196
2.01k
        self.components
197
2.01k
            .iter()
198
2.01k
            .map(|(component, range)| (component, self.node_range(range.clone())))
199
2.01k
    }
<wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime::compile::OutputIndex>>::iter
Line
Count
Source
195
113k
    pub fn iter(&self) -> impl ExactSizeIterator<Item = (Scc, &[Node])> + '_ {
196
113k
        self.components
197
113k
            .iter()
198
113k
            .map(|(component, range)| (component, self.node_range(range.clone())))
199
113k
    }
Unexecuted instantiation: <wasmtime_environ::graphs::scc::StronglyConnectedComponents<_>>::iter
200
201
    /// Iterate over each strongly-connected component.
202
    ///
203
    /// Iteration happens in reverse-topological order (successors are visited
204
    /// before predecessors in the resulting SCC DAG).
205
113k
    pub fn keys(&self) -> impl ExactSizeIterator<Item = Scc> {
206
113k
        self.components.keys()
207
113k
    }
<wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime::compile::OutputIndex>>::keys
Line
Count
Source
205
113k
    pub fn keys(&self) -> impl ExactSizeIterator<Item = Scc> {
206
113k
        self.components.keys()
207
113k
    }
Unexecuted instantiation: <wasmtime_environ::graphs::scc::StronglyConnectedComponents<_>>::keys
208
209
    /// Iterate over the `Node`s that make up each strongly-connected component.
210
    ///
211
    /// Iteration happens in reverse-topological order (successors are visited
212
    /// before predecessors in the resulting SCC DAG).
213
    #[cfg(test)]
214
    pub fn values(&self) -> impl ExactSizeIterator<Item = &[Node]> + '_ {
215
        self.components
216
            .values()
217
            .map(|range| self.node_range(range.clone()))
218
    }
219
220
    /// Get the `Node`s that make up the given strongly-connected component.
221
526k
    pub fn nodes(&self, component: Scc) -> &[Node] {
222
526k
        let range = self.components[component].clone();
223
526k
        self.node_range(range)
224
526k
    }
<wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime::compile::OutputIndex>>::nodes
Line
Count
Source
221
526k
    pub fn nodes(&self, component: Scc) -> &[Node] {
222
526k
        let range = self.components[component].clone();
223
526k
        self.node_range(range)
224
526k
    }
Unexecuted instantiation: <wasmtime_environ::graphs::scc::StronglyConnectedComponents<_>>::nodes
225
226
    /// Get this set of strongly-connected components' "evaporation".
227
    ///
228
    /// Given an input graph `G`, we can construct a new graph where the new
229
    /// graph's nodes are the strongly-connected components of `G` and there is
230
    /// an edge `scc(a) --> scc(b)` for every edge `a --> b` in the input graph
231
    /// `G` where `scc(a) != scc(b)` and `scc` is the function from a node to
232
    /// its strongly-connected component. This new graph is known as the
233
    /// "condensation" of `G`.
234
    ///
235
    /// In the following diagram, the solid lines are the input graph and the
236
    /// dotted lines show its condensation:
237
    ///
238
    /// ```text
239
    /// ...............
240
    /// :             :
241
    /// :    ,-----.  :
242
    /// :    |     |  :
243
    /// :    V     |  :
244
    /// :  +---+   |  :
245
    /// :  | a |---'  :
246
    /// :  +---+      :
247
    /// :    |        :
248
    /// :....|........:
249
    ///      |  :
250
    ///      |  :
251
    ///      |  :
252
    ///      |  V
253
    /// .....|..............       ....................
254
    /// :    |             :       :                  :
255
    /// :    V             :......>:                  :
256
    /// :  +---+    +---+  :       :  +---+    +---+  :
257
    /// :  | b |<-->| c |------------>| d |<-->| e |  :
258
    /// :  +---+    +---+  :       :  +---+    +---+  :
259
    /// :    |             :       :             |    :
260
    /// :....|.............:       :.............|....:
261
    ///      |  :                          :     |
262
    ///      |  :                          :     |
263
    ///      |  :                          :     |
264
    ///      |  V                          :     |
265
    /// .....|........................     :     |
266
    /// :    |                       :     :     |
267
    /// :    | ,----------------.    :     :     |
268
    /// :    | |                |    :<....:     |
269
    /// :    V |                V    :           |
270
    /// :   +---+    +---+    +---+  :           |
271
    /// :   | f |<---| g |<---| h |<-------------'
272
    /// :   +---+    +---+    +---+  :
273
    /// :                            :
274
    /// :............................:
275
    /// ```
276
    ///
277
    /// Note that a graph's condensation is always acyclic, because the
278
    /// strongly-connected components encapsulate and hide any cycles from the
279
    /// input graph.
280
    ///
281
    /// I am not aware of an existing name for the reverse (or transpose)
282
    /// condensation, where each of the condensation's edges `a --> b` are
283
    /// flipped into `b --> a`, so, at cfallin's brilliant suggestion, I have
284
    /// decided to call it an "evaporation".
285
    ///
286
    /// In the context of a call graph, the condensation allows us to derive a
287
    /// partial dependency ordering for bottom-up inlining:
288
    ///
289
    /// * An edge `scc({a,b,...}) --> scc({c,d,...})` means that the functions
290
    ///   `{c,d,...}` should be processed for inlining before functions
291
    ///   `{a,b,...}`, since some functions in `{a,b,...}` call some functions
292
    ///   in `{c,d,...}` and we might want to inline these calls but only after
293
    ///   `{c,d,...}` have already had their calls inlined.
294
    ///
295
    /// * Functions within an SCC are unordered (and we probably don't want to
296
    ///   inline between them at all, or only want to do so in a very limited
297
    ///   manner, since their members are mutually recursive).
298
    ///
299
    /// A call graph's evaporation, by flipping edges from pointing to an SCC's
300
    /// dependencies to pointing to its dependers, allows us to answer the
301
    /// question "given that I've finished processing calls in `scc({e,f,...})`
302
    /// for inlining, which other SCCs are now potentially ready for
303
    /// processing?".
304
56.6k
    pub fn evaporation<G>(&self, graph: G) -> Evaporation
305
56.6k
    where
306
56.6k
        G: Graph<Node>,
307
    {
308
56.6k
        log::trace!("Creating the evaporation of {self:#?}");
309
310
56.6k
        let node_to_component: SecondaryMap<Node, PackedOption<Scc>> = self
311
56.6k
            .iter()
312
526k
            .flat_map(|(c, nodes)| {
313
526k
                nodes
314
526k
                    .iter()
315
526k
                    .copied()
316
526k
                    .map(move |node| (node, PackedOption::from(Some(c))))
<wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime::compile::OutputIndex>>::evaporation::<&wasmtime_environ::graphs::FilterNodes<wasmtime_environ::graphs::entity_graph::EntityGraph<wasmtime::compile::OutputIndex>, <wasmtime::compile::CompileInputs>::compile_with_inlining::{closure#3}>>::{closure#0}::{closure#0}
Line
Count
Source
316
526k
                    .map(move |node| (node, PackedOption::from(Some(c))))
Unexecuted instantiation: <wasmtime_environ::graphs::scc::StronglyConnectedComponents<_>>::evaporation::<_>::{closure#0}::{closure#0}
317
526k
            })
<wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime::compile::OutputIndex>>::evaporation::<&wasmtime_environ::graphs::FilterNodes<wasmtime_environ::graphs::entity_graph::EntityGraph<wasmtime::compile::OutputIndex>, <wasmtime::compile::CompileInputs>::compile_with_inlining::{closure#3}>>::{closure#0}
Line
Count
Source
312
526k
            .flat_map(|(c, nodes)| {
313
526k
                nodes
314
526k
                    .iter()
315
526k
                    .copied()
316
526k
                    .map(move |node| (node, PackedOption::from(Some(c))))
317
526k
            })
Unexecuted instantiation: <wasmtime_environ::graphs::scc::StronglyConnectedComponents<_>>::evaporation::<_>::{closure#0}
318
56.6k
            .collect();
319
320
        // Create a set of all reverse dependencies. This set contains an entry
321
        // `(to, from)` for all `from --> to` edges in the SCC's condensation.
322
        //
323
        // Note that we use a `BTreeSet` so that the results are ordered, all
324
        // edges `a --> *` are contiguous for each component `a`, and we can
325
        // therefore pack them densely in the resulting `Evaporation`.
326
56.6k
        let mut reverse_edge_set = BTreeSet::<(Scc, Scc)>::new();
327
526k
        for (from_component, nodes) in self.iter() {
328
526k
            for node in nodes {
329
526k
                for succ in graph.successors(*node) {
330
161k
                    let to_component = node_to_component[succ].unwrap();
331
161k
                    if to_component == from_component {
332
2.78k
                        continue;
333
158k
                    }
334
158k
                    reverse_edge_set.insert((to_component, from_component));
335
                }
336
            }
337
        }
338
339
        // Convert the `reverse_edge_set` into our densely-packed
340
        // representation.
341
56.6k
        let mut reverse_edges =
342
56.6k
            SecondaryMap::<Scc, Range<u32>>::with_capacity(self.components.len());
343
56.6k
        let mut reverse_edge_elems = Vec::with_capacity(reverse_edge_set.len());
344
158k
        for (to_node, from_node) in reverse_edge_set {
345
158k
            let range = extend_with_range(&mut reverse_edge_elems, Some(from_node));
346
347
158k
            if reverse_edges[to_node] == Range::default() {
348
147k
                reverse_edges[to_node] = range;
349
147k
            } else {
350
11.5k
                debug_assert_eq!(reverse_edges[to_node].end, range.start);
351
11.5k
                reverse_edges[to_node].end = range.end;
352
            }
353
        }
354
355
56.6k
        let result = Evaporation {
356
56.6k
            reverse_edges,
357
56.6k
            reverse_edge_elems,
358
56.6k
        };
359
56.6k
        log::trace!("  -> {result:#?}");
360
56.6k
        result
361
56.6k
    }
<wasmtime_environ::graphs::scc::StronglyConnectedComponents<wasmtime::compile::OutputIndex>>::evaporation::<&wasmtime_environ::graphs::FilterNodes<wasmtime_environ::graphs::entity_graph::EntityGraph<wasmtime::compile::OutputIndex>, <wasmtime::compile::CompileInputs>::compile_with_inlining::{closure#3}>>
Line
Count
Source
304
56.6k
    pub fn evaporation<G>(&self, graph: G) -> Evaporation
305
56.6k
    where
306
56.6k
        G: Graph<Node>,
307
    {
308
56.6k
        log::trace!("Creating the evaporation of {self:#?}");
309
310
56.6k
        let node_to_component: SecondaryMap<Node, PackedOption<Scc>> = self
311
56.6k
            .iter()
312
56.6k
            .flat_map(|(c, nodes)| {
313
                nodes
314
                    .iter()
315
                    .copied()
316
                    .map(move |node| (node, PackedOption::from(Some(c))))
317
            })
318
56.6k
            .collect();
319
320
        // Create a set of all reverse dependencies. This set contains an entry
321
        // `(to, from)` for all `from --> to` edges in the SCC's condensation.
322
        //
323
        // Note that we use a `BTreeSet` so that the results are ordered, all
324
        // edges `a --> *` are contiguous for each component `a`, and we can
325
        // therefore pack them densely in the resulting `Evaporation`.
326
56.6k
        let mut reverse_edge_set = BTreeSet::<(Scc, Scc)>::new();
327
526k
        for (from_component, nodes) in self.iter() {
328
526k
            for node in nodes {
329
526k
                for succ in graph.successors(*node) {
330
161k
                    let to_component = node_to_component[succ].unwrap();
331
161k
                    if to_component == from_component {
332
2.78k
                        continue;
333
158k
                    }
334
158k
                    reverse_edge_set.insert((to_component, from_component));
335
                }
336
            }
337
        }
338
339
        // Convert the `reverse_edge_set` into our densely-packed
340
        // representation.
341
56.6k
        let mut reverse_edges =
342
56.6k
            SecondaryMap::<Scc, Range<u32>>::with_capacity(self.components.len());
343
56.6k
        let mut reverse_edge_elems = Vec::with_capacity(reverse_edge_set.len());
344
158k
        for (to_node, from_node) in reverse_edge_set {
345
158k
            let range = extend_with_range(&mut reverse_edge_elems, Some(from_node));
346
347
158k
            if reverse_edges[to_node] == Range::default() {
348
147k
                reverse_edges[to_node] = range;
349
147k
            } else {
350
11.5k
                debug_assert_eq!(reverse_edges[to_node].end, range.start);
351
11.5k
                reverse_edges[to_node].end = range.end;
352
            }
353
        }
354
355
56.6k
        let result = Evaporation {
356
56.6k
            reverse_edges,
357
56.6k
            reverse_edge_elems,
358
56.6k
        };
359
56.6k
        log::trace!("  -> {result:#?}");
360
56.6k
        result
361
56.6k
    }
Unexecuted instantiation: <wasmtime_environ::graphs::scc::StronglyConnectedComponents<_>>::evaporation::<_>
362
}
363
364
/// The "evaporation" of a graph and its strongly-connected components.
365
///
366
/// Created by `StronglyConnectedComponents::evaporation`; see that method's
367
/// documentation for more details.
368
pub struct Evaporation {
369
    reverse_edges: SecondaryMap<Scc, Range<u32>>,
370
    reverse_edge_elems: Vec<Scc>,
371
}
372
373
impl Debug for Evaporation {
374
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375
        struct Map<'a>(&'a Evaporation);
376
377
        impl<'a> Debug for Map<'a> {
378
0
            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
379
0
                f.debug_map()
380
0
                    .entries(
381
0
                        self.0
382
0
                            .reverse_edges
383
0
                            .keys()
384
0
                            .map(|k| (k, self.0.reverse_edges(k))),
385
                    )
386
0
                    .finish()
387
0
            }
388
        }
389
390
0
        f.debug_struct("Evaporation")
391
0
            .field("reverse_edges", &Map(self))
392
0
            .finish()
393
0
    }
394
}
395
396
impl Evaporation {
397
    /// Get the reverse dependencies of the given strongly-connected component.
398
    ///
399
    /// The result is all of the SCCs whose members have edges in the original
400
    /// graph to one or more of this SCC's members.
401
1.05M
    pub fn reverse_edges(&self, component: Scc) -> &[Scc] {
402
1.05M
        let Range { start, end } = self.reverse_edges[component].clone();
403
1.05M
        let start = usize::try_from(start).unwrap();
404
1.05M
        let end = usize::try_from(end).unwrap();
405
1.05M
        &self.reverse_edge_elems[start..end]
406
1.05M
    }
407
}
408
409
#[cfg(test)]
410
mod tests {
411
    use super::*;
412
413
    #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
414
    pub struct Node(u32);
415
    crate::entity_impl!(Node);
416
417
    #[derive(Debug)]
418
    struct Graph {
419
        edges: SecondaryMap<Node, Vec<Node>>,
420
    }
421
422
    impl Default for Graph {
423
        fn default() -> Self {
424
            let _ = env_logger::try_init();
425
            Self {
426
                edges: Default::default(),
427
            }
428
        }
429
    }
430
431
    impl Graph {
432
        fn define(&mut self, node: u32, edges: impl IntoIterator<Item = u32>) -> &mut Self {
433
            assert!(self.edges[Node::from_u32(node)].is_empty());
434
            self.edges[Node::from_u32(node)].extend(edges.into_iter().map(|v| Node::from_u32(v)));
435
            self
436
        }
437
    }
438
439
    impl super::Graph<Node> for Graph {
440
        type NodesIter<'a>
441
            = cranelift_entity::Keys<Node>
442
        where
443
            Self: 'a;
444
445
        fn nodes(&self) -> Self::NodesIter<'_> {
446
            self.edges.keys()
447
        }
448
449
        type SuccessorsIter<'a>
450
            = core::iter::Copied<core::slice::Iter<'a, Node>>
451
        where
452
            Self: 'a;
453
454
        fn successors(&self, node: Node) -> Self::SuccessorsIter<'_> {
455
            self.edges[node].iter().copied()
456
        }
457
    }
458
459
    fn components(graph: &Graph) -> Vec<Vec<u32>> {
460
        let components = StronglyConnectedComponents::new(graph);
461
        components
462
            .values()
463
            .map(|vs| vs.iter().map(|v| v.as_u32()).collect::<Vec<_>>())
464
            .collect()
465
    }
466
467
    #[test]
468
    fn test_empty() {
469
        let graph = Graph::default();
470
        assert!(components(&graph).is_empty());
471
    }
472
473
    #[test]
474
    fn test_single_node() {
475
        // +---+
476
        // | 0 |
477
        // +---+
478
        let mut graph = Graph::default();
479
        graph.define(0, []);
480
481
        assert_eq!(components(&graph), vec![vec![0]]);
482
    }
483
484
    #[test]
485
    fn test_single_node_cycle() {
486
        //   ,---.
487
        //   |   |
488
        //   V   |
489
        // +---+ |
490
        // | 0 |-'
491
        // +---+
492
        let mut graph = Graph::default();
493
        graph.define(0, [0]);
494
495
        assert_eq!(components(&graph), vec![vec![0]]);
496
    }
497
498
    #[test]
499
    fn test_disconnected_nodes() {
500
        // +---+     +---+
501
        // | 0 |     | 1 |
502
        // +---+     +---+
503
        let mut graph = Graph::default();
504
        graph.define(0, []);
505
        graph.define(1, []);
506
        assert_eq!(components(&graph), vec![vec![1], vec![0]]);
507
    }
508
509
    #[test]
510
    fn test_chained_nodes() {
511
        // +---+   +---+   +---+   +---+
512
        // | 0 |<--| 1 |<--| 2 |<--| 3 |
513
        // +---+   +---+   +---+   +---+
514
        let mut graph = Graph::default();
515
        graph.define(0, []);
516
        graph.define(1, [0]);
517
        graph.define(2, [1]);
518
        graph.define(3, [2]);
519
        assert_eq!(components(&graph), vec![vec![0], vec![1], vec![2], vec![3]]);
520
    }
521
522
    #[test]
523
    fn test_simple_multi_node_cycle() {
524
        //   ,-----------------------.
525
        //   |                       |
526
        //   |                       V
527
        // +---+   +---+   +---+   +---+
528
        // | 0 |<--| 1 |<--| 2 |<--| 3 |
529
        // +---+   +---+   +---+   +---+
530
        let mut graph = Graph::default();
531
        graph.define(0, [3]);
532
        graph.define(1, [0]);
533
        graph.define(2, [1]);
534
        graph.define(3, [2]);
535
        assert_eq!(components(&graph), vec![vec![0, 1, 2, 3]]);
536
    }
537
538
    #[test]
539
    fn test_complicated_multi_node_cycle() {
540
        //   ,---------------.
541
        //   |               |
542
        //   |               V
543
        // +---+   +---+   +---+   +---+   +---+
544
        // | 0 |<--| 1 |<--| 2 |<--| 3 |<--| 4 |
545
        // +---+   +---+   +---+   +---+   +---+
546
        //                   |               ^
547
        //                   |               |
548
        //                   `---------------'
549
        let mut graph = Graph::default();
550
        graph.define(0, [3]);
551
        graph.define(1, [0]);
552
        graph.define(2, [1, 4]);
553
        graph.define(3, [2]);
554
        graph.define(4, [3]);
555
        assert_eq!(components(&graph), vec![vec![0, 1, 2, 3, 4]]);
556
    }
557
558
    #[test]
559
    fn test_disconnected_cycles() {
560
        // +---+           +---+
561
        // | 0 |           | 1 |
562
        // +---+           +---+
563
        //   ^               ^
564
        //   |               |
565
        //   V               V
566
        // +---+           +---+
567
        // | 2 |           | 3 |
568
        // +---+           +---+
569
        let mut graph = Graph::default();
570
        graph.define(0, [2]);
571
        graph.define(1, [3]);
572
        graph.define(2, [0]);
573
        graph.define(3, [1]);
574
        assert_eq!(components(&graph), vec![vec![1, 3], vec![0, 2]]);
575
    }
576
577
    #[test]
578
    fn test_chain_of_cycles() {
579
        //   ,-----.
580
        //   |     |
581
        //   V     |
582
        // +---+   |
583
        // | 0 |---'
584
        // +---+
585
        //   |
586
        //   V
587
        // +---+    +---+
588
        // | 1 |<-->| 2 |
589
        // +---+    +---+
590
        //  |
591
        //  | ,----------------.
592
        //  | |                |
593
        //  V |                V
594
        // +---+    +---+    +---+
595
        // | 3 |<---| 4 |<---| 5 |
596
        // +---+    +---+    +---+
597
        let mut graph = Graph::default();
598
        graph.define(0, [0, 1]);
599
        graph.define(1, [2, 3]);
600
        graph.define(2, [1]);
601
        graph.define(3, [5]);
602
        graph.define(4, [3]);
603
        graph.define(5, [4]);
604
        assert_eq!(components(&graph), vec![vec![3, 4, 5], vec![1, 2], vec![0]]);
605
    }
606
607
    #[test]
608
    fn test_multiple_edges_to_same_component() {
609
        // +---+           +---+
610
        // | 0 |           | 1 |
611
        // +---+           +---+
612
        //   ^               ^
613
        //   |               |
614
        //   V               V
615
        // +---+           +---+
616
        // | 2 |           | 3 |
617
        // +---+           +---+
618
        //   |               |
619
        //   `------. ,------'
620
        //          | |
621
        //          V V
622
        //         +---+
623
        //         | 4 |
624
        //         +---+
625
        //           ^
626
        //           |
627
        //           V
628
        //         +---+
629
        //         | 5 |
630
        //         +---+
631
        let mut graph = Graph::default();
632
        graph.define(0, [2]);
633
        graph.define(1, [3]);
634
        graph.define(2, [0, 4]);
635
        graph.define(3, [1, 4]);
636
        graph.define(4, [5]);
637
        graph.define(5, [4]);
638
        assert_eq!(components(&graph), vec![vec![4, 5], vec![1, 3], vec![0, 2]]);
639
    }
640
641
    #[test]
642
    fn test_duplicate_edges() {
643
        // +---+           +---+
644
        // | 0 |           | 1 |
645
        // +---+           +---+
646
        //   ^               ^
647
        //   |               |
648
        //   V               V
649
        // +---+           +---+
650
        // | 2 |---------->| 3 |
651
        // +---+           +---+
652
        //   |               ^
653
        //   `---------------'
654
        let mut graph = Graph::default();
655
        graph.define(0, [2]);
656
        graph.define(1, [3]);
657
        graph.define(2, [0, 3, 3]);
658
        graph.define(3, [1]);
659
        assert_eq!(components(&graph), vec![vec![1, 3], vec![0, 2]]);
660
    }
661
}