Skip to content

Commit

Permalink
Annotate comments onto the LT algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
Mark-Simulacrum committed Dec 7, 2021
1 parent 3187480 commit 15483cc
Showing 1 changed file with 102 additions and 2 deletions.
104 changes: 102 additions & 2 deletions compiler/rustc_data_structures/src/graph/dominators/mod.rs
Expand Up @@ -2,7 +2,12 @@
//!
//! Algorithm based on Loukas Georgiadis,
//! "Linear-Time Algorithms for Dominators and Related Problems",
//! ftp://ftp.cs.princeton.edu/techreports/2005/737.pdf
//! <ftp://ftp.cs.princeton.edu/techreports/2005/737.pdf>
//!
//! Additionally useful is the original Lengauer-Tarjan paper on this subject,
//! "A Fast Algorithm for Finding Dominators in a Flowgraph"
//! Thomas Lengauer and Robert Endre Tarjan.
//! <https://www.cs.princeton.edu/courses/archive/spr03/cs423/download/dominators.pdf>

use super::ControlFlowGraph;
use rustc_index::vec::{Idx, IndexVec};
Expand Down Expand Up @@ -42,6 +47,14 @@ pub fn dominators<G: ControlFlowGraph>(graph: G) -> Dominators<G::Node> {
real_to_pre_order[graph.start_node()] = Some(PreorderIndex::new(0));
let mut post_order_idx = 0;

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

// We loop over vertices in reverse preorder. This implements the pseudocode
// of the simple Lengauer-Tarjan algorithm. A few key facts are noted here
// which are helpful for understanding the code (full proofs and such are
// found in various papers, including one cited at the top of this file).
//
// For each vertex w (which is not the root),
// * semi[w] is a proper ancestor of the vertex w (i.e., semi[w] != w)
// * idom[w] is an ancestor of semi[w] (i.e., idom[w] may equal semi[w])
//
// An immediate dominator of w (idom[w]) is a vertex v where v dominates w
// and every other dominator of w dominates v. (Every vertex except the root has
// a unique immediate dominator.)
//
// A semidominator for a given vertex w (semi[w]) is the vertex v with minimum
// preorder number such that there exists a path from v to w in which all elements (other than w) have
// preorder numbers greater than w (i.e., this path is not the tree path to
// w).
for w in (PreorderIndex::new(1)..PreorderIndex::new(reachable_vertices)).rev() {
// Optimization: process buckets just once, at the start of the
// iteration. Do not explicitly empty the bucket (even though it will
// not be used again), to save some instructions.
//
// The bucket here contains the vertices whose semidominator is the
// vertex w, which we are guaranteed to have found: all vertices who can
// be semidominated by w must have a preorder number exceeding w, so
// they have been placed in the bucket.
//
// We compute a partial set of immediate dominators here.
let z = parent[w];
for &v in bucket[z].iter() {
// This uses the result of Lemma 5 from section 2 from the original
// 1979 paper, to compute either the immediate or relative dominator
// for a given vertex v.
//
// eval returns a vertex y, for which semi[y] is minimum among
// vertices semi[v] +> y *> v. Note that semi[v] = z as we're in the
// z bucket.
//
// Given such a vertex y, semi[y] <= semi[v] and idom[y] = idom[v].
// If semi[y] = semi[v], though, idom[v] = semi[v].
//
// Using this, we can either set idom[v] to be:
// * semi[v] (i.e. z), if semi[y] is z
// * idom[y], otherwise
//
// We don't directly set to idom[y] though as it's not necessarily
// known yet. The second preorder traversal will cleanup by updating
// the idom for any that were missed in this pass.
let y = eval(&mut parent, lastlinked, &semi, &mut label, v);
idom[v] = if semi[y] < z { y } else { z };
}

// This loop computes the semi[w] for w.
semi[w] = w;
for v in graph.predecessors(pre_order_to_real[w]) {
let v = real_to_pre_order[v].unwrap();

// eval returns a vertex x from which semi[x] is minimum among
// vertices semi[v] +> x *> v.
//
// From Lemma 4 from section 2, we know that the semidominator of a
// vertex w is the minimum (by preorder number) vertex of the
// following:
//
// * direct predecessors of w with preorder number less than w
// * semidominators of u such that u > w and there exists (v, w)
// such that u *> v
//
// This loop therefore identifies such a minima. Note that any
// semidominator path to w must have all but the first vertex go
// through vertices numbered greater than w, so the reverse preorder
// traversal we are using guarantees that all of the information we
// might need is available at this point.
//
// The eval call will give us semi[x], which is either:
//
// * v itself, if v has not yet been processed
// * A possible 'best' semidominator for w.
let x = eval(&mut parent, lastlinked, &semi, &mut label, v);
semi[w] = std::cmp::min(semi[w], semi[x]);
}
// semi[w] is now semidominator(w).
// semi[w] is now semidominator(w) and won't change any more.

// Optimization: Do not insert into buckets if parent[w] = semi[w], as
// we then immediately know the idom.
//
// If we don't yet know the idom directly, then push this vertex into
// our semidominator's bucket, where it will get processed at a later
// stage to compute its immediate dominator.
if parent[w] != semi[w] {
bucket[semi[w]].push(w);
} else {
Expand All @@ -97,6 +179,14 @@ pub fn dominators<G: ControlFlowGraph>(graph: G) -> Dominators<G::Node> {
// processed elements; lastlinked represents the divider.
lastlinked = Some(w);
}

// Finalize the idoms for any that were not fully settable during initial
// traversal.
//
// If idom[w] != semi[w] then we know that we've stored vertex y from above
// into idom[w]. It is known to be our 'relative dominator', which means
// that it's one of w's ancestors and has the same immediate dominator as w,
// so use that idom.
for w in PreorderIndex::new(1)..PreorderIndex::new(reachable_vertices) {
if idom[w] != semi[w] {
idom[w] = idom[idom[w]];
Expand All @@ -111,6 +201,16 @@ pub fn dominators<G: ControlFlowGraph>(graph: G) -> Dominators<G::Node> {
Dominators { post_order_rank, immediate_dominators }
}

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

0 comments on commit 15483cc

Please sign in to comment.