Skip to content

Mir predecessors cache invalidate on terminators #64841

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 0 additions & 73 deletions src/librustc/mir/cache.rs

This file was deleted.

90 changes: 74 additions & 16 deletions src/librustc/mir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ use rustc_data_structures::graph::dominators::{dominators, Dominators};
use rustc_data_structures::graph::{self, GraphPredecessors, GraphSuccessors};
use rustc_data_structures::indexed_vec::{Idx, IndexVec};
use rustc_data_structures::sync::Lrc;
use rustc_data_structures::sync::MappedReadGuard;
use rustc_macros::HashStable;
use rustc_serialize::{Encodable, Decodable};
use smallvec::SmallVec;
Expand All @@ -42,7 +41,6 @@ use syntax_pos::{Span, DUMMY_SP};

pub use crate::mir::interpret::AssertMessage;

mod cache;
pub mod interpret;
pub mod mono;
pub mod tcx;
Expand Down Expand Up @@ -162,7 +160,7 @@ pub struct Body<'tcx> {
pub span: Span,

/// A cache for various calculations.
cache: cache::Cache,
predecessors_cache: Option<IndexVec<BasicBlock, Vec<BasicBlock>>>,
}

impl<'tcx> Body<'tcx> {
Expand Down Expand Up @@ -200,7 +198,7 @@ impl<'tcx> Body<'tcx> {
__upvar_debuginfo_codegen_only_do_not_use,
spread_arg: None,
span,
cache: cache::Cache::new(),
predecessors_cache: None,
control_flow_destroyed,
}
}
Expand All @@ -212,26 +210,73 @@ impl<'tcx> Body<'tcx> {

#[inline]
pub fn basic_blocks_mut(&mut self) -> &mut IndexVec<BasicBlock, BasicBlockData<'tcx>> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should still invalidate, or better not exist at all anymore

self.cache.invalidate();
&mut self.basic_blocks
}

pub fn basic_block_terminator_opt_mut(&mut self, bb: BasicBlock) -> &mut Option<Terminator<'tcx>> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could just be called terminator_opt_mut

self.predecessors_cache = None;
&mut self.basic_blocks[bb].terminator
}

pub fn basic_block_terminator_mut(&mut self, bb: BasicBlock) -> &mut Terminator<'tcx> {
self.predecessors_cache = None;
/*
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was my first attempt at "smart" invalidation, though I doubted it quickly after writing it. I don't get the feeling that this will be faster than just wiping the whole cache since the recreation still has to re-walk everything. Maybe if we have a way to mark some basic blocks as "dirty" then the recreation could be faster. I'll either update this or remove it before moving the PR out of draft.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be doing smart invalidation for now. We can leave that to the user and make them choose the appropriate function for their use case (thus not invalidating so much)

let data = &mut self.basic_blocks[bb];
if let Some(cache) = self.predecessors_cache.as_mut() {
for successor in data.terminator().successors() {
let successor_vec = &mut cache[*successor];
for i in (0..successor_vec.len()).rev() {
if successor_vec[i] == bb {
successor_vec.swap_remove(i);
break;
}
}
}
}
*/

self.basic_blocks[bb].terminator_mut()
}

#[inline]
pub fn basic_blocks_and_local_decls_mut(
&mut self,
) -> (&mut IndexVec<BasicBlock, BasicBlockData<'tcx>>, &mut LocalDecls<'tcx>) {
self.cache.invalidate();
(&mut self.basic_blocks, &mut self.local_decls)
}

#[inline]
pub fn predecessors(&self) -> MappedReadGuard<'_, IndexVec<BasicBlock, Vec<BasicBlock>>> {
self.cache.predecessors(self)
pub fn unwrap_predecessors(&self) -> &IndexVec<BasicBlock, Vec<BasicBlock>> {
assert!(self.predecessors_cache.is_some());
self.predecessors_cache.as_ref().unwrap()
}

#[inline]
pub fn predecessors_for(&self, bb: BasicBlock) -> MappedReadGuard<'_, Vec<BasicBlock>> {
MappedReadGuard::map(self.predecessors(), |p| &p[bb])
pub fn predecessors(&mut self) -> &IndexVec<BasicBlock, Vec<BasicBlock>> {
if self.predecessors_cache.is_none() {
self.predecessors_cache = Some(self.calculate_predecessors())
}

self.predecessors_cache.as_ref().unwrap()
}

fn calculate_predecessors(&self) -> IndexVec<BasicBlock, Vec<BasicBlock>> {
let mut result = IndexVec::from_elem(vec![], self.basic_blocks());
for (bb, data) in self.basic_blocks().iter_enumerated() {
if let Some(ref term) = data.terminator {
for &tgt in term.successors() {
result[tgt].push(bb);
}
}
}

result
}

#[inline]
pub fn predecessors_for(&self, bb: BasicBlock) -> &[BasicBlock] {
// TODO(nashenas88) could this be predecessors sometimes too?
&self.unwrap_predecessors()[bb]
}

#[inline]
Expand Down Expand Up @@ -422,7 +467,7 @@ impl_stable_hash_for!(struct Body<'tcx> {
spread_arg,
control_flow_destroyed,
span,
cache
predecessors_cache
});

impl<'tcx> Index<BasicBlock> for Body<'tcx> {
Expand Down Expand Up @@ -1006,6 +1051,8 @@ impl BasicBlock {
}
}

CloneTypeFoldableAndLiftImpls!{ BasicBlock, }

///////////////////////////////////////////////////////////////////////////
// BasicBlockData and Terminator

Expand Down Expand Up @@ -1336,6 +1383,10 @@ impl<'tcx> BasicBlockData<'tcx> {
BasicBlockData { statements: vec![], terminator, is_cleanup: false }
}

pub fn terminator_opt(&self) -> &Option<Terminator<'tcx>> {
&self.terminator
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since I can't leave a comment outside of my changes...

I really wanted to make BasicBlockData::terminator private, but that's not possible because Cfg needs it in order to build up the BasicBlockData initially. Would it make sense to make terminator private and add a new method called something like terminator_for_cfg and terminator_for_cfg_mut that's intended only to be used by Cfg? This would help ensure that all other mutable access to terminator properly goes through Body so the cache can be properly maintained. In the current form, it would be very easy for someone to access &mut terminator directly without realizing it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea, seems fine to makeit private and create a BasicBlockData::new function that takes all values needed to construct a BasicBlockData

}

/// Accessor for terminator.
///
/// Terminator may not be None after construction of the basic block is complete. This accessor
Expand All @@ -1344,10 +1395,17 @@ impl<'tcx> BasicBlockData<'tcx> {
self.terminator.as_ref().expect("invalid terminator state")
}

pub fn terminator_mut(&mut self) -> &mut Terminator<'tcx> {
// This cannot be public since changing the terminator will break the predecessors cache in Body
// To do so outside of this module, use Body::basic_block_terminator_mut(BasicBlock)
fn terminator_mut(&mut self) -> &mut Terminator<'tcx> {
self.terminator.as_mut().expect("invalid terminator state")
}

// This can be public since changing the kind will not break the predecessors cache in Body
pub fn terminator_kind_mut(&mut self) -> &mut TerminatorKind<'tcx> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this safe to do? TerminatorKind doesn't affect successors, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TerminatorKind contains many BasicBlock ids, and if you change them, the successors change

&mut self.terminator_mut().kind
}

pub fn retain_statements<F>(&mut self, mut f: F)
where
F: FnMut(&mut Statement<'_>) -> bool,
Expand Down Expand Up @@ -2625,7 +2683,7 @@ impl<'tcx> graph::WithPredecessors for Body<'tcx> {
&self,
node: Self::Node,
) -> <Self as GraphPredecessors<'_>>::Iter {
self.predecessors_for(node).clone().into_iter()
self.predecessors_for(node).to_vec().into_iter()
}
}

Expand Down Expand Up @@ -2685,13 +2743,13 @@ impl Location {
}

// If we're in another block, then we want to check that block is a predecessor of `other`.
let mut queue: Vec<BasicBlock> = body.predecessors_for(other.block).clone();
let mut queue: Vec<BasicBlock> = body.predecessors_for(other.block).to_vec();
let mut visited = FxHashSet::default();

while let Some(block) = queue.pop() {
// If we haven't visited this block before, then make sure we visit it's predecessors.
if visited.insert(block) {
queue.append(&mut body.predecessors_for(block).clone());
queue.extend(body.predecessors_for(block).iter().cloned());
} else {
continue;
}
Expand Down Expand Up @@ -2964,7 +3022,7 @@ BraceStructTypeFoldableImpl! {
spread_arg,
control_flow_destroyed,
span,
cache,
predecessors_cache,
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/librustc/mir/traversal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ impl<'a, 'tcx> Iterator for Preorder<'a, 'tcx> {

let data = &self.body[idx];

if let Some(ref term) = data.terminator {
if let Some(ref term) = data.terminator_opt() {
self.worklist.extend(term.successors());
}

Expand Down Expand Up @@ -117,7 +117,7 @@ impl<'a, 'tcx> Postorder<'a, 'tcx> {

let data = &po.body[root];

if let Some(ref term) = data.terminator {
if let Some(ref term) = data.terminator_opt() {
po.visited.insert(root);
po.visit_stack.push((root, term.successors()));
po.traverse_successor();
Expand Down Expand Up @@ -186,7 +186,7 @@ impl<'a, 'tcx> Postorder<'a, 'tcx> {
};

if self.visited.insert(bb) {
if let Some(term) = &self.body[bb].terminator {
if let Some(term) = &self.body[bb].terminator_opt() {
self.visit_stack.push((bb, term.successors()));
}
}
Expand Down
1 change: 1 addition & 0 deletions src/librustc/mir/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,7 @@ macro_rules! make_mir_visitor {
fn visit_location(&mut self, body: & $($mutability)? Body<'tcx>, location: Location) {
let basic_block = & $($mutability)? body[location.block];
if basic_block.statements.len() == location.statement_index {
// TODO(nashenas88) how to ensure we clear the cache only in the mutable case...
if let Some(ref $($mutability)? terminator) = basic_block.terminator {
self.visit_terminator(terminator, location)
}
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_codegen_ssa/mir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ fn create_funclets<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(

let funclet;
let ret_llbb;
match mir[bb].terminator.as_ref().map(|t| &t.kind) {
match mir[bb].terminator_opt().as_ref().map(|t| &t.kind) {
// This is a basic block that we're aborting the program for,
// notably in an `extern` function. These basic blocks are inserted
// so that we assert that `extern` functions do indeed not panic,
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_mir/borrow_check/error_reporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
..
},
..
}) = bbd.terminator {
}) = bbd.terminator_opt() {
if let Some(source)
= BorrowedContentSource::from_call(func.ty(self.body, tcx), tcx)
{
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_mir/build/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl<'tcx> CFG<'tcx> {
source_info: SourceInfo,
kind: TerminatorKind<'tcx>) {
debug!("terminating block {:?} <- {:?}", block, kind);
debug_assert!(self.block_data(block).terminator.is_none(),
debug_assert!(self.block_data(block).terminator_opt().is_none(),
"terminate: block {:?}={:?} already has a terminator set",
block,
self.block_data(block));
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_mir/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
yield_ty: Option<Ty<'tcx>>)
-> Body<'tcx> {
for (index, block) in self.cfg.basic_blocks.iter().enumerate() {
if block.terminator.is_none() {
if block.terminator_opt().is_none() {
span_bug!(self.fn_span, "no terminator on block {:?}", index);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_mir/dataflow/impls/borrows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ fn precompute_borrows_out_of_scope<'tcx>(
// Add successor BBs to the work list, if necessary.
let bb_data = &body[bb];
assert!(hi == bb_data.statements.len());
for &succ_bb in bb_data.terminator.as_ref().unwrap().successors() {
for &succ_bb in bb_data.terminator().successors() {
visited.entry(succ_bb)
.and_modify(|lo| {
// `succ_bb` has been seen before. If it wasn't
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_mir/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ fn check_fn_for_unconditional_recursion(

let block = &basic_blocks[bb];

if let Some(ref terminator) = block.terminator {
if let Some(ref terminator) = block.terminator_opt() {
match terminator.kind {
TerminatorKind::Call { ref func, .. } => {
let func_ty = func.ty(body, tcx);
Expand Down
9 changes: 5 additions & 4 deletions src/librustc_mir/transform/add_call_guards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ impl AddCallGuards {

let cur_len = body.basic_blocks().len();

for block in body.basic_blocks_mut() {
match block.terminator {
for bb in body.basic_blocks().indices() {
let is_cleanup = body.basic_blocks()[bb].is_cleanup;
match body.basic_block_terminator_opt_mut(bb) {
Some(Terminator {
kind: TerminatorKind::Call {
destination: Some((_, ref mut destination)),
Expand All @@ -60,9 +61,9 @@ impl AddCallGuards {
// It's a critical edge, break it
let call_guard = BasicBlockData {
statements: vec![],
is_cleanup: block.is_cleanup,
is_cleanup: is_cleanup,
terminator: Some(Terminator {
source_info,
source_info: *source_info,
kind: TerminatorKind::Goto { target: *destination }
})
};
Expand Down
Loading