Auto merge of #111673 - cjgillot:dominator-preprocess, r=cjgillot,tmiasko
Preprocess and cache dominator tree Preprocessing dominators has a very strong effect for https://github.com/rust-lang/rust/pull/111344. That pass checks that assignments dominate their uses repeatedly. Using the unprocessed dominator tree caused a quadratic runtime (number of bbs x depth of the dominator tree). This PR also caches the dominator tree and the pre-processed dominators in the MIR cfg cache. Rebase of https://github.com/rust-lang/rust/pull/107157 cc `@tmiasko`
This commit is contained in:
commit
97d328012b
9 changed files with 107 additions and 51 deletions
|
@ -9,6 +9,7 @@ use rustc_index::{IndexSlice, IndexVec};
|
|||
use rustc_middle::mir::coverage::*;
|
||||
use rustc_middle::mir::{self, BasicBlock, BasicBlockData, Terminator, TerminatorKind};
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
const ID_SEPARATOR: &str = ",";
|
||||
|
@ -212,8 +213,12 @@ impl CoverageGraph {
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn dominators(&self) -> &Dominators<BasicCoverageBlock> {
|
||||
self.dominators.as_ref().unwrap()
|
||||
pub fn rank_partial_cmp(
|
||||
&self,
|
||||
a: BasicCoverageBlock,
|
||||
b: BasicCoverageBlock,
|
||||
) -> Option<Ordering> {
|
||||
self.dominators.as_ref().unwrap().rank_partial_cmp(a, b)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -650,26 +655,6 @@ pub(super) fn find_loop_backedges(
|
|||
let mut backedges = IndexVec::from_elem_n(Vec::<BasicCoverageBlock>::new(), num_bcbs);
|
||||
|
||||
// Identify loops by their backedges.
|
||||
//
|
||||
// The computational complexity is bounded by: n(s) x d where `n` is the number of
|
||||
// `BasicCoverageBlock` nodes (the simplified/reduced representation of the CFG derived from the
|
||||
// MIR); `s` is the average number of successors per node (which is most likely less than 2, and
|
||||
// independent of the size of the function, so it can be treated as a constant);
|
||||
// and `d` is the average number of dominators per node.
|
||||
//
|
||||
// The average number of dominators depends on the size and complexity of the function, and
|
||||
// nodes near the start of the function's control flow graph typically have less dominators
|
||||
// than nodes near the end of the CFG. Without doing a detailed mathematical analysis, I
|
||||
// think the resulting complexity has the characteristics of O(n log n).
|
||||
//
|
||||
// The overall complexity appears to be comparable to many other MIR transform algorithms, and I
|
||||
// don't expect that this function is creating a performance hot spot, but if this becomes an
|
||||
// issue, there may be ways to optimize the `dominates` algorithm (as indicated by an
|
||||
// existing `FIXME` comment in that code), or possibly ways to optimize it's usage here, perhaps
|
||||
// by keeping track of results for visited `BasicCoverageBlock`s if they can be used to short
|
||||
// circuit downstream `dominates` checks.
|
||||
//
|
||||
// For now, that kind of optimization seems unnecessarily complicated.
|
||||
for (bcb, _) in basic_coverage_blocks.iter_enumerated() {
|
||||
for &successor in &basic_coverage_blocks.successors[bcb] {
|
||||
if basic_coverage_blocks.dominates(successor, bcb) {
|
||||
|
|
|
@ -344,7 +344,7 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
|
|||
// before the dominated equal spans). When later comparing two spans in
|
||||
// order, the first will either dominate the second, or they will have no
|
||||
// dominator relationship.
|
||||
self.basic_coverage_blocks.dominators().rank_partial_cmp(a.bcb, b.bcb)
|
||||
self.basic_coverage_blocks.rank_partial_cmp(a.bcb, b.bcb)
|
||||
}
|
||||
} else {
|
||||
// Sort hi() in reverse order so shorter spans are attempted after longer spans.
|
||||
|
|
|
@ -47,7 +47,7 @@ fn has_back_edge(
|
|||
return false;
|
||||
}
|
||||
// Check if any of the dominators of the node are also the node's successor.
|
||||
doms.dominators(node).any(|dom| node_data.terminator().successors().any(|succ| succ == dom))
|
||||
node_data.terminator().successors().any(|succ| doms.dominates(succ, node))
|
||||
}
|
||||
|
||||
fn insert_counter(basic_block_data: &mut BasicBlockData<'_>) {
|
||||
|
|
|
@ -31,11 +31,11 @@ pub struct SsaLocals {
|
|||
/// We often encounter MIR bodies with 1 or 2 basic blocks. In those cases, it's unnecessary to
|
||||
/// actually compute dominators, we can just compare block indices because bb0 is always the first
|
||||
/// block, and in any body all other blocks are always dominated by bb0.
|
||||
struct SmallDominators {
|
||||
inner: Option<Dominators<BasicBlock>>,
|
||||
struct SmallDominators<'a> {
|
||||
inner: Option<&'a Dominators<BasicBlock>>,
|
||||
}
|
||||
|
||||
impl SmallDominators {
|
||||
impl SmallDominators<'_> {
|
||||
fn dominates(&self, first: Location, second: Location) -> bool {
|
||||
if first.block == second.block {
|
||||
first.statement_index <= second.statement_index
|
||||
|
@ -198,14 +198,14 @@ enum LocationExtended {
|
|||
Arg,
|
||||
}
|
||||
|
||||
struct SsaVisitor {
|
||||
dominators: SmallDominators,
|
||||
struct SsaVisitor<'a> {
|
||||
dominators: SmallDominators<'a>,
|
||||
assignments: IndexVec<Local, Set1<LocationExtended>>,
|
||||
assignment_order: Vec<Local>,
|
||||
direct_uses: IndexVec<Local, u32>,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for SsaVisitor {
|
||||
impl<'tcx> Visitor<'tcx> for SsaVisitor<'_> {
|
||||
fn visit_local(&mut self, local: Local, ctxt: PlaceContext, loc: Location) {
|
||||
match ctxt {
|
||||
PlaceContext::MutatingUse(MutatingUseContext::Projection)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue