Skip to content

Commit aa1267f

Browse files
tmiaskocjgillot
authored andcommitted
Preprocess dominator tree to answer queries in O(1)
1 parent 6c64870 commit aa1267f

File tree

7 files changed

+137
-55
lines changed

7 files changed

+137
-55
lines changed

compiler/rustc_const_eval/src/transform/validate.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
155155
if self.unwind_edge_count <= 1 {
156156
return;
157157
}
158-
let doms = self.body.basic_blocks.dominators();
158+
let dom_tree = self.body.basic_blocks.dominator_tree();
159159
let mut post_contract_node = FxHashMap::default();
160160
// Reusing the allocation across invocations of the closure
161161
let mut dom_path = vec![];
@@ -164,7 +164,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
164164
if let Some(root) = post_contract_node.get(&bb) {
165165
break *root;
166166
}
167-
let parent = doms.immediate_dominator(bb).unwrap();
167+
let parent = dom_tree.immediate_dominator(bb).unwrap();
168168
dom_path.push(bb);
169169
if !self.body.basic_blocks[parent].is_cleanup {
170170
break bb;

compiler/rustc_data_structures/src/graph/dominators/mod.rs

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ rustc_index::newtype_index! {
2626
struct PreorderIndex {}
2727
}
2828

29-
pub fn dominators<G: ControlFlowGraph>(graph: G) -> Dominators<G::Node> {
29+
pub fn dominator_tree<G: ControlFlowGraph>(graph: G) -> DominatorTree<G::Node> {
3030
// compute the post order index (rank) for each node
3131
let mut post_order_rank = IndexVec::from_elem_n(0, graph.num_nodes());
3232

@@ -244,7 +244,7 @@ pub fn dominators<G: ControlFlowGraph>(graph: G) -> Dominators<G::Node> {
244244

245245
let start_node = graph.start_node();
246246
immediate_dominators[start_node] = None;
247-
Dominators { start_node, post_order_rank, immediate_dominators }
247+
DominatorTree { start_node, post_order_rank, immediate_dominators }
248248
}
249249

250250
/// Evaluate the link-eval virtual forest, providing the currently minimum semi
@@ -309,16 +309,18 @@ fn compress(
309309

310310
/// Tracks the list of dominators for each node.
311311
#[derive(Clone, Debug)]
312-
pub struct Dominators<N: Idx> {
312+
pub struct DominatorTree<N: Idx> {
313313
start_node: N,
314314
post_order_rank: IndexVec<N, usize>,
315315
// Even though we track only the immediate dominator of each node, it's
316316
// possible to get its full list of dominators by looking up the dominator
317317
// of each dominator. (See the `impl Iterator for Iter` definition).
318+
//
319+
// Note: immediate_dominators[root] is Some(root)!
318320
immediate_dominators: IndexVec<N, Option<N>>,
319321
}
320322

321-
impl<Node: Idx> Dominators<Node> {
323+
impl<Node: Idx> DominatorTree<Node> {
322324
/// Returns true if node is reachable from the start node.
323325
pub fn is_reachable(&self, node: Node) -> bool {
324326
node == self.start_node || self.immediate_dominators[node].is_some()
@@ -333,12 +335,7 @@ impl<Node: Idx> Dominators<Node> {
333335
/// See the `impl Iterator for Iter` definition to understand how this works.
334336
pub fn dominators(&self, node: Node) -> Iter<'_, Node> {
335337
assert!(self.is_reachable(node), "node {node:?} is not reachable");
336-
Iter { dominators: self, node: Some(node) }
337-
}
338-
339-
pub fn dominates(&self, dom: Node, node: Node) -> bool {
340-
// FIXME -- could be optimized by using post-order-rank
341-
self.dominators(node).any(|n| n == dom)
338+
Iter { dom_tree: self, node: Some(node) }
342339
}
343340

344341
/// Provide deterministic ordering of nodes such that, if any two nodes have a dominator
@@ -351,7 +348,7 @@ impl<Node: Idx> Dominators<Node> {
351348
}
352349

353350
pub struct Iter<'dom, Node: Idx> {
354-
dominators: &'dom Dominators<Node>,
351+
dom_tree: &'dom DominatorTree<Node>,
355352
node: Option<Node>,
356353
}
357354

@@ -360,10 +357,96 @@ impl<'dom, Node: Idx> Iterator for Iter<'dom, Node> {
360357

361358
fn next(&mut self) -> Option<Self::Item> {
362359
if let Some(node) = self.node {
363-
self.node = self.dominators.immediate_dominator(node);
360+
self.node = self.dom_tree.immediate_dominator(node);
364361
Some(node)
365362
} else {
366363
None
367364
}
368365
}
369366
}
367+
368+
#[derive(Clone, Debug)]
369+
pub struct Dominators<Node: Idx> {
370+
time: IndexVec<Node, Time>,
371+
}
372+
373+
/// Describes the number of vertices discovered at the time when processing of a particular vertex
374+
/// started and when it finished. Both values are zero for unreachable vertices.
375+
#[derive(Copy, Clone, Default, Debug)]
376+
struct Time {
377+
start: u32,
378+
finish: u32,
379+
}
380+
381+
impl<Node: Idx> Dominators<Node> {
382+
pub fn dummy() -> Self {
383+
Self { time: Default::default() }
384+
}
385+
386+
/// Returns true if `a` dominates `b`.
387+
///
388+
/// # Panics
389+
///
390+
/// Panics if `b` is unreachable.
391+
pub fn dominates(&self, a: Node, b: Node) -> bool {
392+
let a = self.time[a];
393+
let b = self.time[b];
394+
assert!(b.start != 0, "node {b:?} is not reachable");
395+
a.start <= b.start && b.finish <= a.finish
396+
}
397+
}
398+
399+
pub fn dominators<N: Idx>(tree: &DominatorTree<N>) -> Dominators<N> {
400+
let DominatorTree { start_node, ref immediate_dominators, post_order_rank: _ } = *tree;
401+
402+
// Transpose the dominator tree edges, so that child nodes of vertex v are stored in
403+
// node[edges[v].start..edges[y].end].
404+
let mut edges: IndexVec<N, std::ops::Range<u32>> =
405+
IndexVec::from_elem(0..0, immediate_dominators);
406+
for &idom in immediate_dominators.iter() {
407+
if let Some(idom) = idom {
408+
edges[idom].end += 1;
409+
}
410+
}
411+
let mut m = 0;
412+
for e in edges.iter_mut() {
413+
m += e.end;
414+
e.start = m;
415+
e.end = m;
416+
}
417+
let mut node = IndexVec::from_elem_n(Idx::new(0), m.try_into().unwrap());
418+
for (i, &idom) in immediate_dominators.iter_enumerated() {
419+
if let Some(idom) = idom {
420+
edges[idom].start -= 1;
421+
node[edges[idom].start] = i;
422+
}
423+
}
424+
425+
// Perform a depth-first search of the dominator tree. Record the number of vertices discovered
426+
// when vertex v is discovered first as time[v].start, and when its processing is finished as
427+
// time[v].finish.
428+
let mut time: IndexVec<N, Time> = IndexVec::from_elem(Time::default(), immediate_dominators);
429+
let mut stack = Vec::new();
430+
431+
let mut discovered = 1;
432+
stack.push(start_node);
433+
time[start_node].start = discovered;
434+
435+
while let Some(&i) = stack.last() {
436+
let e = &mut edges[i];
437+
if e.start == e.end {
438+
// Finish processing vertex i.
439+
time[i].finish = discovered;
440+
stack.pop();
441+
} else {
442+
let j = node[e.start];
443+
e.start += 1;
444+
// Start processing vertex j.
445+
discovered += 1;
446+
time[j].start = discovered;
447+
stack.push(j);
448+
}
449+
}
450+
451+
Dominators { time }
452+
}

compiler/rustc_data_structures/src/graph/dominators/tests.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use super::super::tests::TestGraph;
66
fn diamond() {
77
let graph = TestGraph::new(0, &[(0, 1), (0, 2), (1, 3), (2, 3)]);
88

9-
let dominators = dominators(&graph);
10-
let immediate_dominators = &dominators.immediate_dominators;
9+
let tree = dominator_tree(&graph);
10+
let immediate_dominators = &tree.immediate_dominators;
1111
assert_eq!(immediate_dominators[0], None);
1212
assert_eq!(immediate_dominators[1], Some(0));
1313
assert_eq!(immediate_dominators[2], Some(0));
@@ -22,8 +22,8 @@ fn paper() {
2222
&[(6, 5), (6, 4), (5, 1), (4, 2), (4, 3), (1, 2), (2, 3), (3, 2), (2, 1)],
2323
);
2424

25-
let dominators = dominators(&graph);
26-
let immediate_dominators = &dominators.immediate_dominators;
25+
let dom_tree = dominator_tree(&graph);
26+
let immediate_dominators = &dom_tree.immediate_dominators;
2727
assert_eq!(immediate_dominators[0], None); // <-- note that 0 is not in graph
2828
assert_eq!(immediate_dominators[1], Some(6));
2929
assert_eq!(immediate_dominators[2], Some(6));
@@ -41,15 +41,15 @@ fn paper_slt() {
4141
&[(1, 2), (1, 3), (2, 3), (2, 7), (3, 4), (3, 6), (4, 5), (5, 4), (6, 7), (7, 8), (8, 5)],
4242
);
4343

44-
dominators(&graph);
44+
dominator_tree(&graph);
4545
}
4646

4747
#[test]
4848
fn immediate_dominator() {
4949
let graph = TestGraph::new(1, &[(1, 2), (2, 3)]);
50-
let dominators = dominators(&graph);
51-
assert_eq!(dominators.immediate_dominator(0), None);
52-
assert_eq!(dominators.immediate_dominator(1), None);
53-
assert_eq!(dominators.immediate_dominator(2), Some(1));
54-
assert_eq!(dominators.immediate_dominator(3), Some(2));
50+
let tree = dominator_tree(&graph);
51+
assert_eq!(tree.immediate_dominator(0), None);
52+
assert_eq!(tree.immediate_dominator(1), None);
53+
assert_eq!(tree.immediate_dominator(2), Some(1));
54+
assert_eq!(tree.immediate_dominator(3), Some(2));
5555
}

compiler/rustc_middle/src/mir/basic_blocks.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::mir::{BasicBlock, BasicBlockData, Successors, Terminator, TerminatorK
33

44
use rustc_data_structures::fx::FxHashMap;
55
use rustc_data_structures::graph;
6+
use rustc_data_structures::graph::dominators::{dominator_tree, DominatorTree};
67
use rustc_data_structures::graph::dominators::{dominators, Dominators};
78
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
89
use rustc_data_structures::sync::OnceCell;
@@ -41,8 +42,12 @@ impl<'tcx> BasicBlocks<'tcx> {
4142
*self.cache.is_cyclic.get_or_init(|| graph::is_cyclic(self))
4243
}
4344

45+
pub fn dominator_tree(&self) -> DominatorTree<BasicBlock> {
46+
dominator_tree(&self)
47+
}
48+
4449
pub fn dominators(&self) -> Dominators<BasicBlock> {
45-
dominators(&self)
50+
dominators(&self.dominator_tree())
4651
}
4752

4853
/// Returns predecessors for each basic block.

compiler/rustc_mir_transform/src/coverage/graph.rs

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ use super::Error;
22

33
use itertools::Itertools;
44
use rustc_data_structures::fx::FxHashMap;
5-
use rustc_data_structures::graph::dominators::{self, Dominators};
5+
use rustc_data_structures::graph::dominators::{self, DominatorTree, Dominators};
66
use rustc_data_structures::graph::{self, GraphSuccessors, WithNumNodes, WithStartNode};
77
use rustc_index::bit_set::BitSet;
88
use rustc_index::{IndexSlice, IndexVec};
99
use rustc_middle::mir::coverage::*;
1010
use rustc_middle::mir::{self, BasicBlock, BasicBlockData, Terminator, TerminatorKind};
1111

12+
use std::cmp::Ordering;
1213
use std::ops::{Index, IndexMut};
1314

1415
const ID_SEPARATOR: &str = ",";
@@ -24,6 +25,7 @@ pub(super) struct CoverageGraph {
2425
bb_to_bcb: IndexVec<BasicBlock, Option<BasicCoverageBlock>>,
2526
pub successors: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
2627
pub predecessors: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
28+
dominator_tree: Option<DominatorTree<BasicCoverageBlock>>,
2729
dominators: Option<Dominators<BasicCoverageBlock>>,
2830
}
2931

@@ -66,9 +68,17 @@ impl CoverageGraph {
6668
}
6769
}
6870

69-
let mut basic_coverage_blocks =
70-
Self { bcbs, bb_to_bcb, successors, predecessors, dominators: None };
71-
let dominators = dominators::dominators(&basic_coverage_blocks);
71+
let mut basic_coverage_blocks = Self {
72+
bcbs,
73+
bb_to_bcb,
74+
successors,
75+
predecessors,
76+
dominator_tree: None,
77+
dominators: None,
78+
};
79+
let dominator_tree = dominators::dominator_tree(&basic_coverage_blocks);
80+
let dominators = dominators::dominators(&dominator_tree);
81+
basic_coverage_blocks.dominator_tree = Some(dominator_tree);
7282
basic_coverage_blocks.dominators = Some(dominators);
7383
basic_coverage_blocks
7484
}
@@ -212,8 +222,12 @@ impl CoverageGraph {
212222
}
213223

214224
#[inline(always)]
215-
pub fn dominators(&self) -> &Dominators<BasicCoverageBlock> {
216-
self.dominators.as_ref().unwrap()
225+
pub fn rank_partial_cmp(
226+
&self,
227+
a: BasicCoverageBlock,
228+
b: BasicCoverageBlock,
229+
) -> Option<Ordering> {
230+
self.dominator_tree.as_ref().unwrap().rank_partial_cmp(a, b)
217231
}
218232
}
219233

@@ -650,26 +664,6 @@ pub(super) fn find_loop_backedges(
650664
let mut backedges = IndexVec::from_elem_n(Vec::<BasicCoverageBlock>::new(), num_bcbs);
651665

652666
// Identify loops by their backedges.
653-
//
654-
// The computational complexity is bounded by: n(s) x d where `n` is the number of
655-
// `BasicCoverageBlock` nodes (the simplified/reduced representation of the CFG derived from the
656-
// MIR); `s` is the average number of successors per node (which is most likely less than 2, and
657-
// independent of the size of the function, so it can be treated as a constant);
658-
// and `d` is the average number of dominators per node.
659-
//
660-
// The average number of dominators depends on the size and complexity of the function, and
661-
// nodes near the start of the function's control flow graph typically have less dominators
662-
// than nodes near the end of the CFG. Without doing a detailed mathematical analysis, I
663-
// think the resulting complexity has the characteristics of O(n log n).
664-
//
665-
// The overall complexity appears to be comparable to many other MIR transform algorithms, and I
666-
// don't expect that this function is creating a performance hot spot, but if this becomes an
667-
// issue, there may be ways to optimize the `dominates` algorithm (as indicated by an
668-
// existing `FIXME` comment in that code), or possibly ways to optimize it's usage here, perhaps
669-
// by keeping track of results for visited `BasicCoverageBlock`s if they can be used to short
670-
// circuit downstream `dominates` checks.
671-
//
672-
// For now, that kind of optimization seems unnecessarily complicated.
673667
for (bcb, _) in basic_coverage_blocks.iter_enumerated() {
674668
for &successor in &basic_coverage_blocks.successors[bcb] {
675669
if basic_coverage_blocks.dominates(successor, bcb) {

compiler/rustc_mir_transform/src/coverage/spans.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
345345
// before the dominated equal spans). When later comparing two spans in
346346
// order, the first will either dominate the second, or they will have no
347347
// dominator relationship.
348-
self.basic_coverage_blocks.dominators().rank_partial_cmp(a.bcb, b.bcb)
348+
self.basic_coverage_blocks.rank_partial_cmp(a.bcb, b.bcb)
349349
}
350350
} else {
351351
// Sort hi() in reverse order so shorter spans are attempted after longer spans.

compiler/rustc_mir_transform/src/ctfe_limit.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//! (thus indicating there is a loop in the CFG), or whose terminator is a function call.
33
use crate::MirPass;
44

5-
use rustc_data_structures::graph::dominators::Dominators;
5+
use rustc_data_structures::graph::dominators::DominatorTree;
66
use rustc_middle::mir::{
77
BasicBlock, BasicBlockData, Body, Statement, StatementKind, TerminatorKind,
88
};
@@ -13,7 +13,7 @@ pub struct CtfeLimit;
1313
impl<'tcx> MirPass<'tcx> for CtfeLimit {
1414
#[instrument(skip(self, _tcx, body))]
1515
fn run_pass(&self, _tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
16-
let doms = body.basic_blocks.dominators();
16+
let doms = body.basic_blocks.dominator_tree();
1717
let indices: Vec<BasicBlock> = body
1818
.basic_blocks
1919
.iter_enumerated()
@@ -39,7 +39,7 @@ impl<'tcx> MirPass<'tcx> for CtfeLimit {
3939
}
4040

4141
fn has_back_edge(
42-
doms: &Dominators<BasicBlock>,
42+
doms: &DominatorTree<BasicBlock>,
4343
node: BasicBlock,
4444
node_data: &BasicBlockData<'_>,
4545
) -> bool {

0 commit comments

Comments
 (0)