Skip to content

Commit 15483cc

Browse files
Annotate comments onto the LT algorithm
1 parent 3187480 commit 15483cc

File tree

1 file changed

+102
-2
lines changed
  • compiler/rustc_data_structures/src/graph/dominators

1 file changed

+102
-2
lines changed

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

+102-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
//!
33
//! Algorithm based on Loukas Georgiadis,
44
//! "Linear-Time Algorithms for Dominators and Related Problems",
5-
//! ftp://ftp.cs.princeton.edu/techreports/2005/737.pdf
5+
//! <ftp://ftp.cs.princeton.edu/techreports/2005/737.pdf>
6+
//!
7+
//! Additionally useful is the original Lengauer-Tarjan paper on this subject,
8+
//! "A Fast Algorithm for Finding Dominators in a Flowgraph"
9+
//! Thomas Lengauer and Robert Endre Tarjan.
10+
//! <https://www.cs.princeton.edu/courses/archive/spr03/cs423/download/dominators.pdf>
611
712
use super::ControlFlowGraph;
813
use rustc_index::vec::{Idx, IndexVec};
@@ -42,6 +47,14 @@ pub fn dominators<G: ControlFlowGraph>(graph: G) -> Dominators<G::Node> {
4247
real_to_pre_order[graph.start_node()] = Some(PreorderIndex::new(0));
4348
let mut post_order_idx = 0;
4449

50+
// Traverse the graph, collecting a number of things:
51+
//
52+
// * Preorder mapping (to it, and back to the actual ordering)
53+
// * Postorder mapping (used exclusively for rank_partial_cmp on the final product)
54+
// * Parents for each vertex in the preorder tree
55+
//
56+
// These are all done here rather than through one of the 'standard'
57+
// graph traversals to help make this fast.
4558
'recurse: while let Some(frame) = stack.last_mut() {
4659
while let Some(successor) = frame.iter.next() {
4760
if real_to_pre_order[successor].is_none() {
@@ -67,26 +80,95 @@ pub fn dominators<G: ControlFlowGraph>(graph: G) -> Dominators<G::Node> {
6780
let mut bucket = IndexVec::from_elem_n(vec![], reachable_vertices);
6881
let mut lastlinked = None;
6982

83+
// We loop over vertices in reverse preorder. This implements the pseudocode
84+
// of the simple Lengauer-Tarjan algorithm. A few key facts are noted here
85+
// which are helpful for understanding the code (full proofs and such are
86+
// found in various papers, including one cited at the top of this file).
87+
//
88+
// For each vertex w (which is not the root),
89+
// * semi[w] is a proper ancestor of the vertex w (i.e., semi[w] != w)
90+
// * idom[w] is an ancestor of semi[w] (i.e., idom[w] may equal semi[w])
91+
//
92+
// An immediate dominator of w (idom[w]) is a vertex v where v dominates w
93+
// and every other dominator of w dominates v. (Every vertex except the root has
94+
// a unique immediate dominator.)
95+
//
96+
// A semidominator for a given vertex w (semi[w]) is the vertex v with minimum
97+
// preorder number such that there exists a path from v to w in which all elements (other than w) have
98+
// preorder numbers greater than w (i.e., this path is not the tree path to
99+
// w).
70100
for w in (PreorderIndex::new(1)..PreorderIndex::new(reachable_vertices)).rev() {
71101
// Optimization: process buckets just once, at the start of the
72102
// iteration. Do not explicitly empty the bucket (even though it will
73103
// not be used again), to save some instructions.
104+
//
105+
// The bucket here contains the vertices whose semidominator is the
106+
// vertex w, which we are guaranteed to have found: all vertices who can
107+
// be semidominated by w must have a preorder number exceeding w, so
108+
// they have been placed in the bucket.
109+
//
110+
// We compute a partial set of immediate dominators here.
74111
let z = parent[w];
75112
for &v in bucket[z].iter() {
113+
// This uses the result of Lemma 5 from section 2 from the original
114+
// 1979 paper, to compute either the immediate or relative dominator
115+
// for a given vertex v.
116+
//
117+
// eval returns a vertex y, for which semi[y] is minimum among
118+
// vertices semi[v] +> y *> v. Note that semi[v] = z as we're in the
119+
// z bucket.
120+
//
121+
// Given such a vertex y, semi[y] <= semi[v] and idom[y] = idom[v].
122+
// If semi[y] = semi[v], though, idom[v] = semi[v].
123+
//
124+
// Using this, we can either set idom[v] to be:
125+
// * semi[v] (i.e. z), if semi[y] is z
126+
// * idom[y], otherwise
127+
//
128+
// We don't directly set to idom[y] though as it's not necessarily
129+
// known yet. The second preorder traversal will cleanup by updating
130+
// the idom for any that were missed in this pass.
76131
let y = eval(&mut parent, lastlinked, &semi, &mut label, v);
77132
idom[v] = if semi[y] < z { y } else { z };
78133
}
79134

135+
// This loop computes the semi[w] for w.
80136
semi[w] = w;
81137
for v in graph.predecessors(pre_order_to_real[w]) {
82138
let v = real_to_pre_order[v].unwrap();
139+
140+
// eval returns a vertex x from which semi[x] is minimum among
141+
// vertices semi[v] +> x *> v.
142+
//
143+
// From Lemma 4 from section 2, we know that the semidominator of a
144+
// vertex w is the minimum (by preorder number) vertex of the
145+
// following:
146+
//
147+
// * direct predecessors of w with preorder number less than w
148+
// * semidominators of u such that u > w and there exists (v, w)
149+
// such that u *> v
150+
//
151+
// This loop therefore identifies such a minima. Note that any
152+
// semidominator path to w must have all but the first vertex go
153+
// through vertices numbered greater than w, so the reverse preorder
154+
// traversal we are using guarantees that all of the information we
155+
// might need is available at this point.
156+
//
157+
// The eval call will give us semi[x], which is either:
158+
//
159+
// * v itself, if v has not yet been processed
160+
// * A possible 'best' semidominator for w.
83161
let x = eval(&mut parent, lastlinked, &semi, &mut label, v);
84162
semi[w] = std::cmp::min(semi[w], semi[x]);
85163
}
86-
// semi[w] is now semidominator(w).
164+
// semi[w] is now semidominator(w) and won't change any more.
87165

88166
// Optimization: Do not insert into buckets if parent[w] = semi[w], as
89167
// we then immediately know the idom.
168+
//
169+
// If we don't yet know the idom directly, then push this vertex into
170+
// our semidominator's bucket, where it will get processed at a later
171+
// stage to compute its immediate dominator.
90172
if parent[w] != semi[w] {
91173
bucket[semi[w]].push(w);
92174
} else {
@@ -97,6 +179,14 @@ pub fn dominators<G: ControlFlowGraph>(graph: G) -> Dominators<G::Node> {
97179
// processed elements; lastlinked represents the divider.
98180
lastlinked = Some(w);
99181
}
182+
183+
// Finalize the idoms for any that were not fully settable during initial
184+
// traversal.
185+
//
186+
// If idom[w] != semi[w] then we know that we've stored vertex y from above
187+
// into idom[w]. It is known to be our 'relative dominator', which means
188+
// that it's one of w's ancestors and has the same immediate dominator as w,
189+
// so use that idom.
100190
for w in PreorderIndex::new(1)..PreorderIndex::new(reachable_vertices) {
101191
if idom[w] != semi[w] {
102192
idom[w] = idom[idom[w]];
@@ -111,6 +201,16 @@ pub fn dominators<G: ControlFlowGraph>(graph: G) -> Dominators<G::Node> {
111201
Dominators { post_order_rank, immediate_dominators }
112202
}
113203

204+
/// Evaluate the link-eval virtual forest, providing the currently minimum semi
205+
/// value for the passed `node` (which may be itself).
206+
///
207+
/// This maintains that for every vertex v, `label[v]` is such that:
208+
///
209+
/// ```text
210+
/// semi[eval(v)] = min { semi[label[u]] | root_in_forest(v) +> u *> v }
211+
/// ```
212+
///
213+
/// where `+>` is a proper ancestor and `*>` is just an ancestor.
114214
#[inline]
115215
fn eval(
116216
ancestor: &mut IndexVec<PreorderIndex, PreorderIndex>,

0 commit comments

Comments
 (0)