Unify `return`, `break` and `continue` handling
diff --git a/src/librustc_mir/build/expr/stmt.rs b/src/librustc_mir/build/expr/stmt.rs
index 3c5eafb..cf3d877 100644
--- a/src/librustc_mir/build/expr/stmt.rs
+++ b/src/librustc_mir/build/expr/stmt.rs
@@ -1,4 +1,4 @@
-use crate::build::scope::BreakableScope;
+use crate::build::scope::BreakableTarget;
use crate::build::{BlockAnd, BlockAndExtension, BlockFrame, Builder};
use crate::hair::*;
use rustc::middle::region;
@@ -98,70 +98,13 @@
block.unit()
}
ExprKind::Continue { label } => {
- let BreakableScope {
- continue_block,
- region_scope,
- ..
- } = *this.find_breakable_scope(expr_span, label);
- let continue_block = continue_block
- .expect("Attempted to continue in non-continuable breakable block");
- this.exit_scope(
- expr_span,
- (region_scope, source_info),
- block,
- continue_block,
- );
- this.cfg.start_new_block().unit()
+ this.break_scope(block, None, BreakableTarget::Continue(label), source_info)
}
ExprKind::Break { label, value } => {
- let (break_block, region_scope, destination) = {
- let BreakableScope {
- break_block,
- region_scope,
- ref break_destination,
- ..
- } = *this.find_breakable_scope(expr_span, label);
- (break_block, region_scope, break_destination.clone())
- };
- if let Some(value) = value {
- debug!("stmt_expr Break val block_context.push(SubExpr) : {:?}", expr2);
- this.block_context.push(BlockFrame::SubExpr);
- unpack!(block = this.into(&destination, block, value));
- this.block_context.pop();
- } else {
- this.cfg.push_assign_unit(block, source_info, &destination)
- }
- this.exit_scope(expr_span, (region_scope, source_info), block, break_block);
- this.cfg.start_new_block().unit()
+ this.break_scope(block, value, BreakableTarget::Break(label), source_info)
}
ExprKind::Return { value } => {
- block = match value {
- Some(value) => {
- debug!("stmt_expr Return val block_context.push(SubExpr) : {:?}", expr2);
- this.block_context.push(BlockFrame::SubExpr);
- let result = unpack!(
- this.into(
- &Place::RETURN_PLACE,
- block,
- value
- )
- );
- this.block_context.pop();
- result
- }
- None => {
- this.cfg.push_assign_unit(
- block,
- source_info,
- &Place::RETURN_PLACE,
- );
- block
- }
- };
- let region_scope = this.region_scope_of_return_scope();
- let return_block = this.return_block();
- this.exit_scope(expr_span, (region_scope, source_info), block, return_block);
- this.cfg.start_new_block().unit()
+ this.break_scope(block, value, BreakableTarget::Return, source_info)
}
ExprKind::InlineAsm {
asm,
diff --git a/src/librustc_mir/build/mod.rs b/src/librustc_mir/build/mod.rs
index 0957dcbf..ff4841c 100644
--- a/src/librustc_mir/build/mod.rs
+++ b/src/librustc_mir/build/mod.rs
@@ -251,7 +251,7 @@
/// The current set of scopes, updated as we traverse;
/// see the `scope` module for more details.
- scopes: Vec<scope::Scope<'tcx>>,
+ scopes: scope::Scopes<'tcx>,
/// The block-context: each time we build the code within an hair::Block,
/// we push a frame here tracking whether we are building a statement or
@@ -274,10 +274,6 @@
/// The number of `push_unsafe_block` levels in scope.
push_unsafe_count: usize,
- /// The current set of breakables; see the `scope` module for more
- /// details.
- breakable_scopes: Vec<scope::BreakableScope<'tcx>>,
-
/// The vector of all scopes that we have created thus far;
/// we track this for debuginfo later.
source_scopes: IndexVec<SourceScope, SourceScopeData>,
@@ -714,7 +710,7 @@
fn_span: span,
arg_count,
is_generator,
- scopes: vec![],
+ scopes: Default::default(),
block_context: BlockContext::new(),
source_scopes: IndexVec::new(),
source_scope: OUTERMOST_SOURCE_SCOPE,
@@ -722,7 +718,6 @@
guard_context: vec![],
push_unsafe_count: 0,
unpushed_unsafe: safety,
- breakable_scopes: vec![],
local_decls: IndexVec::from_elem_n(
LocalDecl::new_return_place(return_ty, return_span),
1,
@@ -865,7 +860,11 @@
}
let body = self.hir.mirror(ast_body);
- self.into(&Place::RETURN_PLACE, block, body)
+ // `return_block` is called when we evaluate a `return` expression, so
+ // we just use `START_BLOCK` here.
+ self.in_breakable_scope(None, START_BLOCK, Place::RETURN_PLACE, |this| {
+ this.into(&Place::RETURN_PLACE, block, body)
+ })
}
fn get_unit_temp(&mut self) -> Place<'tcx> {
diff --git a/src/librustc_mir/build/scope.rs b/src/librustc_mir/build/scope.rs
index c5b5f25..32b6908 100644
--- a/src/librustc_mir/build/scope.rs
+++ b/src/librustc_mir/build/scope.rs
@@ -82,8 +82,8 @@
*/
-use crate::build::{BlockAnd, BlockAndExtension, Builder, CFG};
-use crate::hair::LintLevel;
+use crate::build::{BlockAnd, BlockAndExtension, BlockFrame, Builder, CFG};
+use crate::hair::{ExprRef, LintLevel};
use rustc::middle::region;
use rustc::ty::Ty;
use rustc::hir;
@@ -94,7 +94,7 @@
use std::mem;
#[derive(Debug)]
-pub struct Scope<'tcx> {
+struct Scope<'tcx> {
/// The source scope this scope was created in.
source_scope: SourceScope,
@@ -133,6 +133,13 @@
cached_unwind: CachedBlock,
}
+#[derive(Debug, Default)]
+pub struct Scopes<'tcx> {
+ scopes: Vec<Scope<'tcx>>,
+ /// The current set of breakable scopes. See module comment for more details.
+ breakable_scopes: Vec<BreakableScope<'tcx>>,
+}
+
#[derive(Debug)]
struct DropData<'tcx> {
/// span where drop obligation was incurred (typically where place was declared)
@@ -172,17 +179,25 @@
}
#[derive(Clone, Debug)]
-pub struct BreakableScope<'tcx> {
+struct BreakableScope<'tcx> {
/// Region scope of the loop
- pub region_scope: region::Scope,
+ region_scope: region::Scope,
/// Where the body of the loop begins. `None` if block
- pub continue_block: Option<BasicBlock>,
+ continue_block: Option<BasicBlock>,
/// Block to branch into when the loop or block terminates (either by being `break`-en out
/// from, or by having its condition to become false)
- pub break_block: BasicBlock,
+ break_block: BasicBlock,
/// The destination of the loop/block expression itself (i.e., where to put the result of a
/// `break` expression)
- pub break_destination: Place<'tcx>,
+ break_destination: Place<'tcx>,
+}
+
+/// The target of an expression that breaks out of a scope
+#[derive(Clone, Copy, Debug)]
+pub enum BreakableTarget {
+ Continue(region::Scope),
+ Break(region::Scope),
+ Return,
}
impl CachedBlock {
@@ -208,15 +223,6 @@
}
}
-impl DropKind {
- fn may_panic(&self) -> bool {
- match *self {
- DropKind::Value => true,
- DropKind::Storage => false
- }
- }
-}
-
impl<'tcx> Scope<'tcx> {
/// Invalidates all the cached blocks in the scope.
///
@@ -257,13 +263,111 @@
}
}
+impl<'tcx> Scopes<'tcx> {
+ fn len(&self) -> usize {
+ self.scopes.len()
+ }
+
+ fn push_scope(&mut self, region_scope: (region::Scope, SourceInfo), vis_scope: SourceScope) {
+ debug!("push_scope({:?})", region_scope);
+ self.scopes.push(Scope {
+ source_scope: vis_scope,
+ region_scope: region_scope.0,
+ region_scope_span: region_scope.1.span,
+ needs_cleanup: false,
+ drops: vec![],
+ cached_generator_drop: None,
+ cached_exits: Default::default(),
+ cached_unwind: CachedBlock::default(),
+ });
+ }
+
+ fn pop_scope(
+ &mut self,
+ region_scope: (region::Scope, SourceInfo),
+ ) -> (Scope<'tcx>, Option<BasicBlock>) {
+ let scope = self.scopes.pop().unwrap();
+ assert_eq!(scope.region_scope, region_scope.0);
+ let unwind_to = self.scopes.last()
+ .and_then(|next_scope| next_scope.cached_unwind.get(false));
+ (scope, unwind_to)
+ }
+
+ fn may_panic(&self, scope_count: usize) -> bool {
+ let len = self.len();
+ self.scopes[(len - scope_count)..].iter().any(|s| s.needs_cleanup)
+ }
+
+ /// Finds the breakable scope for a given label. This is used for
+ /// resolving `return`, `break` and `continue`.
+ fn find_breakable_scope(
+ &self,
+ span: Span,
+ target: BreakableTarget,
+ ) -> (BasicBlock, region::Scope, Option<Place<'tcx>>) {
+ let get_scope = |scope: region::Scope| {
+ // find the loop-scope by its `region::Scope`.
+ self.breakable_scopes.iter()
+ .rfind(|breakable_scope| breakable_scope.region_scope == scope)
+ .unwrap_or_else(|| span_bug!(span, "no enclosing breakable scope found"))
+ };
+ match target {
+ BreakableTarget::Return => {
+ let scope = &self.breakable_scopes[0];
+ if scope.break_destination != Place::RETURN_PLACE {
+ span_bug!(span, "`return` in item with no return scope");
+ }
+ (scope.break_block, scope.region_scope, Some(scope.break_destination.clone()))
+ }
+ BreakableTarget::Break(scope) => {
+ let scope = get_scope(scope);
+ (scope.break_block, scope.region_scope, Some(scope.break_destination.clone()))
+ }
+ BreakableTarget::Continue(scope) => {
+ let scope = get_scope(scope);
+ let continue_block = scope.continue_block
+ .unwrap_or_else(|| span_bug!(span, "missing `continue` block"));
+ (continue_block, scope.region_scope, None)
+ }
+ }
+ }
+
+ fn num_scopes_to(&self, region_scope: (region::Scope, SourceInfo), span: Span) -> usize {
+ let scope_count = 1 + self.scopes.iter().rev()
+ .position(|scope| scope.region_scope == region_scope.0)
+ .unwrap_or_else(|| {
+ span_bug!(span, "region_scope {:?} does not enclose", region_scope)
+ });
+ let len = self.len();
+ assert!(scope_count < len, "should not use `exit_scope` to pop ALL scopes");
+ scope_count
+ }
+
+ fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item=&mut Scope<'tcx>> + '_ {
+ self.scopes.iter_mut().rev()
+ }
+
+ fn top_scopes(&mut self, count: usize) -> impl DoubleEndedIterator<Item=&mut Scope<'tcx>> + '_ {
+ let len = self.len();
+ self.scopes[len - count..].iter_mut()
+ }
+
+ /// Returns the topmost active scope, which is known to be alive until
+ /// the next scope expression.
+ fn topmost(&self) -> region::Scope {
+ self.scopes.last().expect("topmost_scope: no scopes present").region_scope
+ }
+
+ fn source_info(&self, index: usize, span: Span) -> SourceInfo {
+ self.scopes[self.len() - index].source_info(span)
+ }
+}
+
impl<'a, 'tcx> Builder<'a, 'tcx> {
// Adding and removing scopes
// ==========================
- /// Start a breakable scope, which tracks where `continue` and `break`
- /// should branch to. See module comment for more details.
- ///
- /// Returns the might_break attribute of the BreakableScope used.
+ // Start a breakable scope, which tracks where `continue`, `break` and
+ // `return` should branch to.
pub fn in_breakable_scope<F, R>(&mut self,
loop_block: Option<BasicBlock>,
break_block: BasicBlock,
@@ -271,16 +375,16 @@
f: F) -> R
where F: FnOnce(&mut Builder<'a, 'tcx>) -> R
{
- let region_scope = self.topmost_scope();
+ let region_scope = self.scopes.topmost();
let scope = BreakableScope {
region_scope,
continue_block: loop_block,
break_block,
break_destination,
};
- self.breakable_scopes.push(scope);
+ self.scopes.breakable_scopes.push(scope);
let res = f(self);
- let breakable_scope = self.breakable_scopes.pop().unwrap();
+ let breakable_scope = self.scopes.breakable_scopes.pop().unwrap();
assert!(breakable_scope.region_scope == region_scope);
res
}
@@ -350,18 +454,7 @@
/// calls must be paired; using `in_scope` as a convenience
/// wrapper maybe preferable.
pub fn push_scope(&mut self, region_scope: (region::Scope, SourceInfo)) {
- debug!("push_scope({:?})", region_scope);
- let vis_scope = self.source_scope;
- self.scopes.push(Scope {
- source_scope: vis_scope,
- region_scope: region_scope.0,
- region_scope_span: region_scope.1.span,
- needs_cleanup: false,
- drops: vec![],
- cached_generator_drop: None,
- cached_exits: Default::default(),
- cached_unwind: CachedBlock::default(),
- });
+ self.scopes.push_scope(region_scope, self.source_scope);
}
/// Pops a scope, which should have region scope `region_scope`,
@@ -374,17 +467,11 @@
debug!("pop_scope({:?}, {:?})", region_scope, block);
// If we are emitting a `drop` statement, we need to have the cached
// diverge cleanup pads ready in case that drop panics.
- let may_panic =
- self.scopes.last().unwrap().drops.iter().any(|s| s.kind.may_panic());
- if may_panic {
+ if self.scopes.may_panic(1) {
self.diverge_cleanup();
}
- let scope = self.scopes.pop().unwrap();
- assert_eq!(scope.region_scope, region_scope.0);
-
- let unwind_to = self.scopes.last().and_then(|next_scope| {
- next_scope.cached_unwind.get(false)
- }).unwrap_or_else(|| self.resume_block());
+ let (scope, unwind_to) = self.scopes.pop_scope(region_scope);
+ let unwind_to = unwind_to.unwrap_or_else(|| self.resume_block());
unpack!(block = build_scope_drops(
&mut self.cfg,
@@ -399,6 +486,37 @@
block.unit()
}
+ pub fn break_scope(
+ &mut self,
+ mut block: BasicBlock,
+ value: Option<ExprRef<'tcx>>,
+ scope: BreakableTarget,
+ source_info: SourceInfo,
+ ) -> BlockAnd<()> {
+ let (mut target_block, region_scope, destination)
+ = self.scopes.find_breakable_scope(source_info.span, scope);
+ if let BreakableTarget::Return = scope {
+ // We call this now, rather than when we start lowering the
+ // function so that the return block doesn't precede the entire
+ // rest of the CFG. Some passes and LLVM prefer blocks to be in
+ // approximately CFG order.
+ target_block = self.return_block();
+ }
+ if let Some(destination) = destination {
+ if let Some(value) = value {
+ debug!("stmt_expr Break val block_context.push(SubExpr)");
+ self.block_context.push(BlockFrame::SubExpr);
+ unpack!(block = self.into(&destination, block, value));
+ self.block_context.pop();
+ } else {
+ self.cfg.push_assign_unit(block, source_info, &destination)
+ }
+ } else {
+ assert!(value.is_none(), "`return` and `break` should have a destination");
+ }
+ self.exit_scope(source_info.span, (region_scope, source_info), block, target_block);
+ self.cfg.start_new_block().unit()
+ }
/// Branch out of `block` to `target`, exiting all scopes up to
/// and including `region_scope`. This will insert whatever drops are
@@ -410,22 +528,16 @@
target: BasicBlock) {
debug!("exit_scope(region_scope={:?}, block={:?}, target={:?})",
region_scope, block, target);
- let scope_count = 1 + self.scopes.iter().rev()
- .position(|scope| scope.region_scope == region_scope.0)
- .unwrap_or_else(|| {
- span_bug!(span, "region_scope {:?} does not enclose", region_scope)
- });
- let len = self.scopes.len();
- assert!(scope_count < len, "should not use `exit_scope` to pop ALL scopes");
+ let scope_count = self.scopes.num_scopes_to(region_scope, span);
// If we are emitting a `drop` statement, we need to have the cached
// diverge cleanup pads ready in case that drop panics.
- let may_panic = self.scopes[(len - scope_count)..].iter().any(|s| s.needs_cleanup);
+ let may_panic = self.scopes.may_panic(scope_count);
if may_panic {
self.diverge_cleanup();
}
- let mut scopes = self.scopes[(len - scope_count - 1)..].iter_mut().rev();
+ let mut scopes = self.scopes.top_scopes(scope_count + 1).rev();
let mut scope = scopes.next().unwrap();
for next_scope in scopes {
if scope.drops.is_empty() {
@@ -466,9 +578,8 @@
scope = next_scope;
}
- let scope = &self.scopes[len - scope_count];
- self.cfg.terminate(block, scope.source_info(span),
- TerminatorKind::Goto { target });
+ let source_info = self.scopes.source_info(scope_count, span);
+ self.cfg.terminate(block, source_info, TerminatorKind::Goto { target });
}
/// Creates a path that performs all required cleanup for dropping a generator.
@@ -479,9 +590,9 @@
// Fill in the cache for unwinds
self.diverge_cleanup_gen(true);
- let src_info = self.scopes[0].source_info(self.fn_span);
+ let src_info = self.scopes.source_info(self.scopes.len(), self.fn_span);
let resume_block = self.resume_block();
- let mut scopes = self.scopes.iter_mut().rev().peekable();
+ let mut scopes = self.scopes.iter_mut().peekable();
let mut block = self.cfg.start_new_block();
let result = block;
@@ -547,22 +658,6 @@
scope
}
- // Finding scopes
- // ==============
- /// Finds the breakable scope for a given label. This is used for
- /// resolving `break` and `continue`.
- pub fn find_breakable_scope(&self,
- span: Span,
- label: region::Scope)
- -> &BreakableScope<'tcx> {
- // find the loop-scope with the correct id
- self.breakable_scopes.iter()
- .rev()
- .filter(|breakable_scope| breakable_scope.region_scope == label)
- .next()
- .unwrap_or_else(|| span_bug!(span, "no enclosing breakable scope found"))
- }
-
/// Given a span and the current source scope, make a SourceInfo.
pub fn source_info(&self, span: Span) -> SourceInfo {
SourceInfo {
@@ -571,25 +666,8 @@
}
}
- /// Returns the `region::Scope` of the scope which should be exited by a
- /// return.
- pub fn region_scope_of_return_scope(&self) -> region::Scope {
- // The outermost scope (`scopes[0]`) will be the `CallSiteScope`.
- // We want `scopes[1]`, which is the `ParameterScope`.
- assert!(self.scopes.len() >= 2);
- assert!(match self.scopes[1].region_scope.data {
- region::ScopeData::Arguments => true,
- _ => false,
- });
- self.scopes[1].region_scope
- }
-
- /// Returns the topmost active scope, which is known to be alive until
- /// the next scope expression.
- pub fn topmost_scope(&self) -> region::Scope {
- self.scopes.last().expect("topmost_scope: no scopes present").region_scope
- }
-
+ // Finding scopes
+ // ==============
/// Returns the scope that we should use as the lifetime of an
/// operand. Basically, an operand must live until it is consumed.
/// This is similar to, but not quite the same as, the temporary
@@ -620,20 +698,21 @@
None,
hir::BodyOwnerKind::Closure |
hir::BodyOwnerKind::Fn =>
- Some(self.topmost_scope()),
+ Some(self.scopes.topmost()),
}
}
// Schedule an abort block - this is used for some ABIs that cannot unwind
pub fn schedule_abort(&mut self) -> BasicBlock {
- self.scopes[0].needs_cleanup = true;
+ let source_info = self.scopes.source_info(self.scopes.len(), self.fn_span);
let abortblk = self.cfg.start_new_cleanup_block();
- let source_info = self.scopes[0].source_info(self.fn_span);
self.cfg.terminate(abortblk, source_info, TerminatorKind::Abort);
self.cached_resume_block = Some(abortblk);
abortblk
}
+ // Scheduling drops
+ // ================
pub fn schedule_drop_storage_and_value(
&mut self,
span: Span,
@@ -645,8 +724,6 @@
self.schedule_drop(span, region_scope, place, place_ty, DropKind::Value);
}
- // Scheduling drops
- // ================
/// Indicates that `place` should be dropped on exit from
/// `region_scope`.
///
@@ -679,7 +756,7 @@
}
}
- for scope in self.scopes.iter_mut().rev() {
+ for scope in self.scopes.iter_mut() {
let this_scope = scope.region_scope == region_scope;
// When building drops, we try to cache chains of drops in such a way so these drops
// could be reused by the drops which would branch into the cached (already built)
@@ -790,14 +867,15 @@
// Find the last cached block
debug!("diverge_cleanup_gen(self.scopes = {:?})", self.scopes);
- let (mut target, first_uncached) = if let Some(cached_index) = self.scopes.iter()
- .rposition(|scope| scope.cached_unwind.get(generator_drop).is_some()) {
- (self.scopes[cached_index].cached_unwind.get(generator_drop).unwrap(), cached_index + 1)
- } else {
- (self.resume_block(), 0)
- };
+ let cached_cleanup = self.scopes.iter_mut().enumerate()
+ .find_map(|(idx, ref scope)| {
+ let cached_block = scope.cached_unwind.get(generator_drop)?;
+ Some((cached_block, idx))
+ });
+ let (mut target, first_uncached) = cached_cleanup
+ .unwrap_or_else(|| (self.resume_block(), self.scopes.len()));
- for scope in self.scopes[first_uncached..].iter_mut() {
+ for scope in self.scopes.top_scopes(first_uncached) {
target = build_diverge_scope(&mut self.cfg, scope.region_scope_span,
scope, target, generator_drop, self.is_generator);
}
@@ -856,8 +934,8 @@
///
/// This is only needed for `match` arm scopes, because they have one
/// entrance per pattern, but only one exit.
- pub fn clear_top_scope(&mut self, region_scope: region::Scope) {
- let top_scope = self.scopes.last_mut().unwrap();
+ pub(crate) fn clear_top_scope(&mut self, region_scope: region::Scope) {
+ let top_scope = self.scopes.scopes.last_mut().unwrap();
assert_eq!(top_scope.region_scope, region_scope);
@@ -880,13 +958,13 @@
/// * There is only one exit for the arm scope
/// * The guard expression scope is too short, it ends just before the
/// boolean is tested.
- pub fn pop_variable(
+ pub(crate) fn pop_variable(
&mut self,
block: BasicBlock,
region_scope: region::Scope,
variable: Local,
) {
- let top_scope = self.scopes.last_mut().unwrap();
+ let top_scope = self.scopes.scopes.last_mut().unwrap();
assert_eq!(top_scope.region_scope, region_scope);
@@ -915,7 +993,6 @@
top_scope.invalidate_cache(true, self.is_generator, true);
}
-
}
/// Builds drops for pop_scope and exit_scope.