wasmtime/compile/
scc.rs

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