Skip to content

Commit

Permalink
Address nits on build/scope.rs
Browse files Browse the repository at this point in the history
  • Loading branch information
nagisa committed Feb 4, 2016
1 parent ebf6341 commit 5638847
Showing 1 changed file with 141 additions and 101 deletions.
242 changes: 141 additions & 101 deletions src/librustc_mir/build/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,18 @@ pub struct Scope<'tcx> {
// A scope may only have one associated free, because:
// 1. We require a `free` to only be scheduled in the scope of `EXPR` in `box EXPR`;
// 2. It only makes sense to have it translated into the diverge-path.
//
// This kind of drop will be run *after* all the regular drops scheduled onto this scope,
// because drops may have dependencies on the allocated memory.
//
// This is expected to go away once `box EXPR` becomes a sugar for placement protocol and gets
// desugared in some earlier stage.
free: Option<FreeData<'tcx>>,
}

struct DropData<'tcx> {
value: Lvalue<'tcx>,
// NB: per-drop “cache” is necessary for the build_scope_drops function below.
/// The cached block for the cleanups-on-diverge path. This block contains code to run the
/// current drop and all the preceding drops (i.e. those having lower index in Drop’s
/// Scope drop array)
Expand Down Expand Up @@ -137,28 +144,30 @@ pub struct LoopScope {
}

impl<'tcx> Scope<'tcx> {
/// Invalidate cached blocks in the scope. Should always be run for all inner scopes when a
/// drop is pushed into some scope enclosing a larger extent of code.
fn invalidate_cache(&mut self, only_free: bool) {
/// Invalidate all the cached blocks in the scope.
///
/// Should always be run for all inner scopes when a drop is pushed into some scope enclosing a
/// larger extent of code.
fn invalidate_cache(&mut self) {
for dropdata in &mut self.drops {
dropdata.cached_block = None;
}
if let Some(ref mut freedata) = self.free {
freedata.cached_block = None;
}
if !only_free {
for dropdata in &mut self.drops {
dropdata.cached_block = None;
}
}
}

/// Returns the cached block for this scope.
///
/// Precondition: the caches must be fully filled (i.e. diverge_cleanup is called) in order for
/// this method to work correctly.
fn cached_block(&self) -> Option<BasicBlock> {
if let Some(ref free_data) = self.free {
free_data.cached_block
if let Some(data) = self.drops.last() {
Some(data.cached_block.expect("drop cache is not filled"))
} else if let Some(ref data) = self.free {
Some(data.cached_block.expect("free cache is not filled"))
} else {
self.drops.last().iter().flat_map(|dd| dd.cached_block).next()
None
}
}
}
Expand Down Expand Up @@ -297,18 +306,17 @@ impl<'a,'tcx> Builder<'a,'tcx> {
}
for scope in self.scopes.iter_mut().rev() {
if scope.extent == extent {
// We only invalidate cached block of free here; all other drops’ cached blocks to
// not become invalid (this drop will branch into them).
scope.invalidate_cache(true);
// No need to invalidate any caches here. The just-scheduled drop will branch into
// the drop that comes before it in the vector.
scope.drops.push(DropData {
value: lvalue.clone(),
cached_block: None
});
return;
} else {
// We must invalidate all the cached_blocks leading up to the scope we’re
// looking for, because all of the blocks in the chain become incorrect.
scope.invalidate_cache(false)
// looking for, because all of the blocks in the chain will become incorrect.
scope.invalidate_cache()
}
}
self.hir.span_bug(span,
Expand All @@ -328,6 +336,9 @@ impl<'a,'tcx> Builder<'a,'tcx> {
for scope in self.scopes.iter_mut().rev() {
if scope.extent == extent {
assert!(scope.free.is_none(), "scope already has a scheduled free!");
// We also must invalidate the caches in the scope for which the free is scheduled
// because the drops must branch into the free we schedule here.
scope.invalidate_cache();
scope.free = Some(FreeData {
span: span,
value: value.clone(),
Expand All @@ -337,9 +348,9 @@ impl<'a,'tcx> Builder<'a,'tcx> {
return;
} else {
// We must invalidate all the cached_blocks leading up to the scope we’re looking
// for, because otherwise some/most of the blocks in the chain might become
// for, because otherwise some/most of the blocks in the chain will become
// incorrect.
scope.invalidate_cache(false);
scope.invalidate_cache();
}
}
self.hir.span_bug(span,
Expand All @@ -357,95 +368,20 @@ impl<'a,'tcx> Builder<'a,'tcx> {
if self.scopes.is_empty() {
return None;
}

let unit_tmp = self.get_unit_temp();
let Builder { ref mut scopes, ref mut cfg, ref mut hir, .. } = *self;

let tcx = hir.tcx();
let unit_temp = self.get_unit_temp();
let Builder { ref mut hir, ref mut cfg, ref mut scopes, .. } = *self;
let mut next_block = None;

// Given an array of scopes, we generate these from the outermost scope to the innermost
// one. Thus for array [S0, S1, S2] with corresponding cleanup blocks [B0, B1, B2], we will
// generate B0 <- B1 <- B2 in left-to-right order. Control flow of the generated blocks
// always ends up at a block with the Resume terminator.
//
// Similarly to the scopes, we translate drops so:
// * Scheduled free drop is executed first;
// * Drops are executed after the free drop in the decreasing order (decreasing as
// from higher index in drops array to lower index);
//
// NB: We do the building backwards here. We will first produce a block containing the
// Resume terminator (which is executed last for any given chain of cleanups) and then go
// on building the drops from the outermost one to the innermost one. Similar note applies
// to the drops within the scope too.
{
let iter = scopes.iter_mut().filter(|s| !s.drops.is_empty() || s.free.is_some());
for scope in iter {
// Try using the cached free drop if any…
if let Some(FreeData { cached_block: Some(cached_block), .. }) = scope.free {
next_block = Some(cached_block);
continue;
}
// otherwise look for the cached regular drop (fast path)…
if let Some(&DropData { cached_block: Some(cached_block), .. }) = scope.drops.last() {
next_block = Some(cached_block);
continue;
}
// otherwise build the blocks…
for drop_data in scope.drops.iter_mut() {
// skipping them if they’re already built…
if let Some(cached_block) = drop_data.cached_block {
next_block = Some(cached_block);
continue;
}
let block = cfg.start_new_cleanup_block();
let target = next_block.unwrap_or_else(|| {
let b = cfg.start_new_cleanup_block();
cfg.terminate(b, Terminator::Resume);
b
});
cfg.terminate(block, Terminator::Drop {
value: drop_data.value.clone(),
target: target,
unwind: None
});
drop_data.cached_block = Some(block);
next_block = Some(block);
}

if let Some(ref mut free_data) = scope.free {
// The free was not cached yet. It must be translated the last and will be executed
// first.
let free_func = tcx.lang_items.box_free_fn()
.expect("box_free language item is missing");
let substs = tcx.mk_substs(Substs::new(
VecPerParamSpace::new(vec![], vec![], vec![free_data.item_ty]),
VecPerParamSpace::new(vec![], vec![], vec![])
));
let block = cfg.start_new_cleanup_block();
let target = next_block.unwrap_or_else(|| {
let b = cfg.start_new_cleanup_block();
cfg.terminate(b, Terminator::Resume);
b
});
cfg.terminate(block, Terminator::Call {
func: Operand::Constant(Constant {
span: free_data.span,
ty: tcx.lookup_item_type(free_func).ty.subst(tcx, substs),
literal: Literal::Item {
def_id: free_func,
kind: ItemKind::Function,
substs: substs
}
}),
args: vec![Operand::Consume(free_data.value.clone())],
destination: Some((unit_tmp.clone(), target)),
cleanup: None
});
free_data.cached_block = Some(block);
next_block = Some(block);
}
}
for scope in scopes.iter_mut().filter(|s| !s.drops.is_empty() || s.free.is_some()) {
next_block = Some(build_diverge_scope(hir.tcx(),
cfg,
unit_temp.clone(),
scope,
next_block));
}
scopes.iter().rev().flat_map(|x| x.cached_block()).next()
}
Expand All @@ -462,6 +398,7 @@ impl<'a,'tcx> Builder<'a,'tcx> {
next_target.unit()
}


// Panicking
// =========
// FIXME: should be moved into their own module
Expand Down Expand Up @@ -596,3 +533,106 @@ fn build_scope_drops<'tcx>(mut block: BasicBlock,
}
block.unit()
}

fn build_diverge_scope<'tcx>(tcx: &ty::ctxt<'tcx>,
cfg: &mut CFG<'tcx>,
unit_temp: Lvalue<'tcx>,
scope: &mut Scope<'tcx>,
target: Option<BasicBlock>)
-> BasicBlock {
debug_assert!(!scope.drops.is_empty() || scope.free.is_some());

// First, we build the drops, iterating the drops array in reverse. We do that so that as soon
// as we find a `cached_block`, we know that we’re finished and don’t need to do anything else.
let mut previous = None;
let mut last_drop_block = None;
for drop_data in scope.drops.iter_mut().rev() {
if let Some(cached_block) = drop_data.cached_block {
if let Some((previous_block, previous_value)) = previous {
cfg.terminate(previous_block, Terminator::Drop {
value: previous_value,
target: cached_block,
unwind: None
});
return last_drop_block.unwrap();
} else {
return cached_block;
}
} else {
let block = cfg.start_new_cleanup_block();
drop_data.cached_block = Some(block);
if let Some((previous_block, previous_value)) = previous {
cfg.terminate(previous_block, Terminator::Drop {
value: previous_value,
target: block,
unwind: None
});
} else {
last_drop_block = Some(block);
}
previous = Some((block, drop_data.value.clone()));
}
}

// Prepare the end target for this chain.
let mut target = target.unwrap_or_else(||{
let b = cfg.start_new_cleanup_block();
cfg.terminate(b, Terminator::Resume);
b
});

// Then, build the free branching into the prepared target.
if let Some(ref mut free_data) = scope.free {
target = if let Some(cached_block) = free_data.cached_block {
cached_block
} else {
let t = build_free(tcx, cfg, unit_temp, free_data, target);
free_data.cached_block = Some(t);
t
}
};

if let Some((previous_block, previous_value)) = previous {
// Finally, branch into that just-built `target` from the `previous_block`.
cfg.terminate(previous_block, Terminator::Drop {
value: previous_value,
target: target,
unwind: None
});
last_drop_block.unwrap()
} else {
// If `previous.is_none()`, there were no drops in this scope – we return the
// target.
target
}
}

fn build_free<'tcx>(tcx: &ty::ctxt<'tcx>,
cfg: &mut CFG<'tcx>,
unit_temp: Lvalue<'tcx>,
data: &FreeData<'tcx>,
target: BasicBlock)
-> BasicBlock {
let free_func = tcx.lang_items.box_free_fn()
.expect("box_free language item is missing");
let substs = tcx.mk_substs(Substs::new(
VecPerParamSpace::new(vec![], vec![], vec![data.item_ty]),
VecPerParamSpace::new(vec![], vec![], vec![])
));
let block = cfg.start_new_cleanup_block();
cfg.terminate(block, Terminator::Call {
func: Operand::Constant(Constant {
span: data.span,
ty: tcx.lookup_item_type(free_func).ty.subst(tcx, substs),
literal: Literal::Item {
def_id: free_func,
kind: ItemKind::Function,
substs: substs
}
}),
args: vec![Operand::Consume(data.value.clone())],
destination: Some((unit_temp, target)),
cleanup: None
});
block
}

0 comments on commit 5638847

Please sign in to comment.