Skip to content

Commit 4c91bb9

Browse files
committed
introduce a VecGraph abstraction that cheaply stores graphs
This is perhaps better than the linked list approach I was using before. Lower memory overhead, Theta(N+E) storage. Does require a sort. =)
1 parent 4e85665 commit 4c91bb9

File tree

5 files changed

+197
-3
lines changed

5 files changed

+197
-3
lines changed

src/librustc_data_structures/graph/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub mod implementation;
55
pub mod iterate;
66
mod reference;
77
pub mod scc;
8+
pub mod vec_graph;
89

910
#[cfg(test)]
1011
mod test;
@@ -17,6 +18,10 @@ pub trait WithNumNodes: DirectedGraph {
1718
fn num_nodes(&self) -> usize;
1819
}
1920

21+
pub trait WithNumEdges: DirectedGraph {
22+
fn num_edges(&self) -> usize;
23+
}
24+
2025
pub trait WithSuccessors: DirectedGraph
2126
where
2227
Self: for<'graph> GraphSuccessors<'graph, Item = <Self as DirectedGraph>::Node>,

src/librustc_data_structures/graph/scc/mod.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
//! O(n) time.
55
66
use crate::fx::FxHashSet;
7-
use crate::graph::{DirectedGraph, WithNumNodes, WithSuccessors, GraphSuccessors};
7+
use crate::graph::{DirectedGraph, WithNumNodes, WithNumEdges, WithSuccessors, GraphSuccessors};
8+
use crate::graph::vec_graph::VecGraph;
89
use crate::indexed_vec::{Idx, IndexVec};
910
use std::ops::Range;
1011

@@ -58,6 +59,18 @@ impl<N: Idx, S: Idx> Sccs<N, S> {
5859
pub fn successors(&self, scc: S) -> &[S] {
5960
self.scc_data.successors(scc)
6061
}
62+
63+
/// Construct the reverse graph of the SCC graph.
64+
pub fn reverse(&self) -> VecGraph<S> {
65+
VecGraph::new(
66+
self.num_sccs(),
67+
self.all_sccs()
68+
.flat_map(|source| self.successors(source).iter().map(move |&target| {
69+
(target, source)
70+
}))
71+
.collect(),
72+
)
73+
}
6174
}
6275

6376
impl<N: Idx, S: Idx> DirectedGraph for Sccs<N, S> {
@@ -70,6 +83,12 @@ impl<N: Idx, S: Idx> WithNumNodes for Sccs<N, S> {
7083
}
7184
}
7285

86+
impl<N: Idx, S: Idx> WithNumEdges for Sccs<N, S> {
87+
fn num_edges(&self) -> usize {
88+
self.scc_data.all_successors.len()
89+
}
90+
}
91+
7392
impl<N: Idx, S: Idx> GraphSuccessors<'graph> for Sccs<N, S> {
7493
type Item = S;
7594

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
use crate::indexed_vec::{Idx, IndexVec};
2+
use crate::graph::{DirectedGraph, WithNumNodes, WithNumEdges, WithSuccessors, GraphSuccessors};
3+
4+
mod test;
5+
6+
pub struct VecGraph<N: Idx> {
7+
/// Maps from a given node to an index where the set of successors
8+
/// for that node starts. The index indexes into the `edges`
9+
/// vector. To find the range for a given node, we look up the
10+
/// start for that node and then the start for the next node
11+
/// (i.e., with an index 1 higher) and get the range between the
12+
/// two. This vector always has an extra entry so that this works
13+
/// even for the max element.
14+
node_starts: IndexVec<N, usize>,
15+
16+
edge_targets: Vec<N>,
17+
}
18+
19+
impl<N: Idx> VecGraph<N> {
20+
pub fn new(
21+
num_nodes: usize,
22+
mut edge_pairs: Vec<(N, N)>,
23+
) -> Self {
24+
// Sort the edges by the source -- this is important.
25+
edge_pairs.sort();
26+
27+
let num_edges = edge_pairs.len();
28+
29+
// Store the *target* of each edge into `edge_targets`
30+
let edge_targets: Vec<N> = edge_pairs.iter().map(|&(_, target)| target).collect();
31+
32+
// Create the *edge starts* array. We are iterating over over
33+
// the (sorted) edge pairs. We maintain the invariant that the
34+
// length of the `node_starts` arary is enough to store the
35+
// current source node -- so when we see that the source node
36+
// for an edge is greater than the current length, we grow the
37+
// edge-starts array by just enough.
38+
let mut node_starts = IndexVec::with_capacity(num_edges);
39+
for (index, &(source, _)) in edge_pairs.iter().enumerate() {
40+
// If we have a list like `[(0, x), (2, y)]`:
41+
//
42+
// - Start out with `node_starts` of `[]`
43+
// - Iterate to `(0, x)` at index 0:
44+
// - Push one entry because `node_starts.len()` (0) is <= the source (0)
45+
// - Leaving us with `node_starts` of `[0]`
46+
// - Iterate to `(2, y)` at index 1:
47+
// - Push one entry because `node_starts.len()` (1) is <= the source (2)
48+
// - Push one entry because `node_starts.len()` (2) is <= the source (2)
49+
// - Leaving us with `node_starts` of `[0, 1, 1]`
50+
// - Loop terminates
51+
while node_starts.len() <= source.index() {
52+
node_starts.push(index);
53+
}
54+
}
55+
56+
// Pad out the `node_starts` array so that it has `num_nodes +
57+
// 1` entries. Continuing our example above, if `num_nodes` is
58+
// be `3`, we would push one more index: `[0, 1, 1, 2]`.
59+
//
60+
// Interpretation of that vector:
61+
//
62+
// [0, 1, 1, 2]
63+
// ---- range for N=2
64+
// ---- range for N=1
65+
// ---- range for N=0
66+
while node_starts.len() <= num_nodes {
67+
node_starts.push(edge_targets.len());
68+
}
69+
70+
assert_eq!(node_starts.len(), num_nodes + 1);
71+
72+
Self { node_starts, edge_targets }
73+
}
74+
75+
/// Gets the successors for `source` as a slice.
76+
pub fn successors(&self, source: N) -> &[N] {
77+
let start_index = self.node_starts[source];
78+
let end_index = self.node_starts[source.plus(1)];
79+
&self.edge_targets[start_index..end_index]
80+
}
81+
}
82+
83+
impl<N: Idx> DirectedGraph for VecGraph<N> {
84+
type Node = N;
85+
}
86+
87+
impl<N: Idx> WithNumNodes for VecGraph<N> {
88+
fn num_nodes(&self) -> usize {
89+
self.node_starts.len() - 1
90+
}
91+
}
92+
93+
impl<N: Idx> WithNumEdges for VecGraph<N> {
94+
fn num_edges(&self) -> usize {
95+
self.edge_targets.len()
96+
}
97+
}
98+
99+
impl<N: Idx> GraphSuccessors<'graph> for VecGraph<N> {
100+
type Item = N;
101+
102+
type Iter = std::iter::Cloned<std::slice::Iter<'graph, N>>;
103+
}
104+
105+
impl<N: Idx> WithSuccessors for VecGraph<N> {
106+
fn successors<'graph>(
107+
&'graph self,
108+
node: N
109+
) -> <Self as GraphSuccessors<'graph>>::Iter {
110+
self.successors(node).iter().cloned()
111+
}
112+
}
113+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#![cfg(test)]
2+
3+
use super::*;
4+
5+
fn create_graph() -> VecGraph<usize> {
6+
// Create a simple graph
7+
//
8+
// 5
9+
// |
10+
// V
11+
// 0 --> 1 --> 2
12+
// |
13+
// v
14+
// 3 --> 4
15+
//
16+
// 6
17+
18+
VecGraph::new(
19+
7,
20+
vec![
21+
(0, 1),
22+
(1, 2),
23+
(1, 3),
24+
(3, 4),
25+
(5, 1),
26+
],
27+
)
28+
}
29+
30+
#[test]
31+
fn num_nodes() {
32+
let graph = create_graph();
33+
assert_eq!(graph.num_nodes(), 7);
34+
}
35+
36+
#[test]
37+
fn succesors() {
38+
let graph = create_graph();
39+
assert_eq!(graph.successors(0), &[1]);
40+
assert_eq!(graph.successors(1), &[2, 3]);
41+
assert_eq!(graph.successors(2), &[]);
42+
assert_eq!(graph.successors(3), &[4]);
43+
assert_eq!(graph.successors(4), &[]);
44+
assert_eq!(graph.successors(5), &[1]);
45+
assert_eq!(graph.successors(6), &[]);
46+
}

src/librustc_data_structures/indexed_vec.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ pub trait Idx: Copy + 'static + Ord + Debug + Hash {
1919
fn index(self) -> usize;
2020

2121
fn increment_by(&mut self, amount: usize) {
22-
let v = self.index() + amount;
23-
*self = Self::new(v);
22+
*self = self.plus(amount);
23+
}
24+
25+
fn plus(self, amount: usize) -> Self {
26+
Self::new(self.index() + amount)
2427
}
2528
}
2629

@@ -167,6 +170,14 @@ macro_rules! newtype_index {
167170
}
168171
}
169172

173+
impl std::ops::Add<usize> for $type {
174+
type Output = Self;
175+
176+
fn add(self, other: usize) -> Self {
177+
Self::new(self.index() + other)
178+
}
179+
}
180+
170181
impl Idx for $type {
171182
#[inline]
172183
fn new(value: usize) -> Self {

0 commit comments

Comments
 (0)