| // Copyright 2015 The Rust Project Developers. See the COPYRIGHT |
| // file at the top-level directory of this distribution and at |
| // http://rust-lang.org/COPYRIGHT. |
| // |
| // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| // option. This file may not be copied, modified, or distributed |
| // except according to those terms. |
| |
| /*! |
| Managing the scope stack. The scopes are tied to lexical scopes, so as |
| we descend the HAIR, we push a scope on the stack, build its |
| contents, and then pop it off. Every scope is named by a |
| `region::Scope`. |
| |
| ### SEME Regions |
| |
| When pushing a new scope, we record the current point in the graph (a |
| basic block); this marks the entry to the scope. We then generate more |
| stuff in the control-flow graph. Whenever the scope is exited, either |
| via a `break` or `return` or just by fallthrough, that marks an exit |
| from the scope. Each lexical scope thus corresponds to a single-entry, |
| multiple-exit (SEME) region in the control-flow graph. |
| |
| For now, we keep a mapping from each `region::Scope` to its |
| corresponding SEME region for later reference (see caveat in next |
| paragraph). This is because region scopes are tied to |
| them. Eventually, when we shift to non-lexical lifetimes, there should |
| be no need to remember this mapping. |
| |
| There is one additional wrinkle, actually, that I wanted to hide from |
| you but duty compels me to mention. In the course of building |
| matches, it sometimes happen that certain code (namely guards) gets |
| executed multiple times. This means that the scope lexical scope may |
| in fact correspond to multiple, disjoint SEME regions. So in fact our |
| mapping is from one scope to a vector of SEME regions. |
| |
| ### Drops |
| |
| The primary purpose for scopes is to insert drops: while building |
| the contents, we also accumulate places that need to be dropped upon |
| exit from each scope. This is done by calling `schedule_drop`. Once a |
| drop is scheduled, whenever we branch out we will insert drops of all |
| those places onto the outgoing edge. Note that we don't know the full |
| set of scheduled drops up front, and so whenever we exit from the |
| scope we only drop the values scheduled thus far. For example, consider |
| the scope S corresponding to this loop: |
| |
| ``` |
| # let cond = true; |
| loop { |
| let x = ..; |
| if cond { break; } |
| let y = ..; |
| } |
| ``` |
| |
| When processing the `let x`, we will add one drop to the scope for |
| `x`. The break will then insert a drop for `x`. When we process `let |
| y`, we will add another drop (in fact, to a subscope, but let's ignore |
| that for now); any later drops would also drop `y`. |
| |
| ### Early exit |
| |
| There are numerous "normal" ways to early exit a scope: `break`, |
| `continue`, `return` (panics are handled separately). Whenever an |
| early exit occurs, the method `exit_scope` is called. It is given the |
| current point in execution where the early exit occurs, as well as the |
| scope you want to branch to (note that all early exits from to some |
| other enclosing scope). `exit_scope` will record this exit point and |
| also add all drops. |
| |
| Panics are handled in a similar fashion, except that a panic always |
| returns out to the `DIVERGE_BLOCK`. To trigger a panic, simply call |
| `panic(p)` with the current point `p`. Or else you can call |
| `diverge_cleanup`, which will produce a block that you can branch to |
| which does the appropriate cleanup and then diverges. `panic(p)` |
| simply calls `diverge_cleanup()` and adds an edge from `p` to the |
| result. |
| |
| ### Loop scopes |
| |
| In addition to the normal scope stack, we track a loop scope stack |
| that contains only loops. It tracks where a `break` and `continue` |
| should go to. |
| |
| */ |
| |
| use build::{BlockAnd, BlockAndExtension, Builder, CFG}; |
| use hair::LintLevel; |
| use rustc::middle::region; |
| use rustc::ty::{Ty, TyCtxt}; |
| use rustc::hir; |
| use rustc::hir::def_id::LOCAL_CRATE; |
| use rustc::mir::*; |
| use syntax_pos::{Span}; |
| use rustc_data_structures::fx::FxHashMap; |
| |
| #[derive(Debug)] |
| pub struct Scope<'tcx> { |
| /// The source scope this scope was created in. |
| source_scope: SourceScope, |
| |
| /// the region span of this scope within source code. |
| region_scope: region::Scope, |
| |
| /// the span of that region_scope |
| region_scope_span: Span, |
| |
| /// Whether there's anything to do for the cleanup path, that is, |
| /// when unwinding through this scope. This includes destructors, |
| /// but not StorageDead statements, which don't get emitted at all |
| /// for unwinding, for several reasons: |
| /// * clang doesn't emit llvm.lifetime.end for C++ unwinding |
| /// * LLVM's memory dependency analysis can't handle it atm |
| /// * polluting the cleanup MIR with StorageDead creates |
| /// landing pads even though there's no actual destructors |
| /// * freeing up stack space has no effect during unwinding |
| needs_cleanup: bool, |
| |
| /// set of places to drop when exiting this scope. This starts |
| /// out empty but grows as variables are declared during the |
| /// building process. This is a stack, so we always drop from the |
| /// end of the vector (top of the stack) first. |
| drops: Vec<DropData<'tcx>>, |
| |
| /// The cache for drop chain on “normal” exit into a particular BasicBlock. |
| cached_exits: FxHashMap<(BasicBlock, region::Scope), BasicBlock>, |
| |
| /// The cache for drop chain on "generator drop" exit. |
| cached_generator_drop: Option<BasicBlock>, |
| |
| /// The cache for drop chain on "unwind" exit. |
| cached_unwind: CachedBlock, |
| } |
| |
| #[derive(Debug)] |
| struct DropData<'tcx> { |
| /// span where drop obligation was incurred (typically where place was declared) |
| span: Span, |
| |
| /// place to drop |
| location: Place<'tcx>, |
| |
| /// Whether this is a value Drop or a StorageDead. |
| kind: DropKind, |
| } |
| |
| #[derive(Debug, Default, Clone, Copy)] |
| pub(crate) struct CachedBlock { |
| /// 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) |
| unwind: Option<BasicBlock>, |
| |
| /// The cached block for unwinds during cleanups-on-generator-drop path |
| /// |
| /// This is split from the standard unwind path here to prevent drop |
| /// elaboration from creating drop flags that would have to be captured |
| /// by the generator. I'm not sure how important this optimization is, |
| /// but it is here. |
| generator_drop: Option<BasicBlock>, |
| } |
| |
| #[derive(Debug)] |
| pub(crate) enum DropKind { |
| Value { |
| cached_block: CachedBlock, |
| }, |
| Storage |
| } |
| |
| #[derive(Clone, Debug)] |
| pub struct BreakableScope<'tcx> { |
| /// Region scope of the loop |
| pub region_scope: region::Scope, |
| /// Where the body of the loop begins. `None` if block |
| pub 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, |
| /// The destination of the loop/block expression itself (i.e. where to put the result of a |
| /// `break` expression) |
| pub break_destination: Place<'tcx>, |
| } |
| |
| impl CachedBlock { |
| fn invalidate(&mut self) { |
| self.generator_drop = None; |
| self.unwind = None; |
| } |
| |
| fn get(&self, generator_drop: bool) -> Option<BasicBlock> { |
| if generator_drop { |
| self.generator_drop |
| } else { |
| self.unwind |
| } |
| } |
| |
| fn ref_mut(&mut self, generator_drop: bool) -> &mut Option<BasicBlock> { |
| if generator_drop { |
| &mut self.generator_drop |
| } else { |
| &mut self.unwind |
| } |
| } |
| } |
| |
| impl DropKind { |
| fn may_panic(&self) -> bool { |
| match *self { |
| DropKind::Value { .. } => true, |
| DropKind::Storage => false |
| } |
| } |
| } |
| |
| impl<'tcx> Scope<'tcx> { |
| /// 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. |
| /// |
| /// `storage_only` controls whether to invalidate only drop paths run `StorageDead`. |
| /// `this_scope_only` controls whether to invalidate only drop paths that refer to the current |
| /// top-of-scope (as opposed to dependent scopes). |
| fn invalidate_cache(&mut self, storage_only: bool, this_scope_only: bool) { |
| // FIXME: maybe do shared caching of `cached_exits` etc. to handle functions |
| // with lots of `try!`? |
| |
| // cached exits drop storage and refer to the top-of-scope |
| self.cached_exits.clear(); |
| |
| if !storage_only { |
| // the current generator drop and unwind ignore |
| // storage but refer to top-of-scope |
| self.cached_generator_drop = None; |
| self.cached_unwind.invalidate(); |
| } |
| |
| if !storage_only && !this_scope_only { |
| for dropdata in &mut self.drops { |
| if let DropKind::Value { ref mut cached_block } = dropdata.kind { |
| cached_block.invalidate(); |
| } |
| } |
| } |
| } |
| |
| /// Given a span and this scope's source scope, make a SourceInfo. |
| fn source_info(&self, span: Span) -> SourceInfo { |
| SourceInfo { |
| span, |
| scope: self.source_scope |
| } |
| } |
| } |
| |
| impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, '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. |
| pub fn in_breakable_scope<F, R>(&mut self, |
| loop_block: Option<BasicBlock>, |
| break_block: BasicBlock, |
| break_destination: Place<'tcx>, |
| f: F) -> R |
| where F: FnOnce(&mut Builder<'a, 'gcx, 'tcx>) -> R |
| { |
| let region_scope = self.topmost_scope(); |
| let scope = BreakableScope { |
| region_scope, |
| continue_block: loop_block, |
| break_block, |
| break_destination, |
| }; |
| self.breakable_scopes.push(scope); |
| let res = f(self); |
| let breakable_scope = self.breakable_scopes.pop().unwrap(); |
| assert!(breakable_scope.region_scope == region_scope); |
| res |
| } |
| |
| pub fn in_opt_scope<F, R>(&mut self, |
| opt_scope: Option<(region::Scope, SourceInfo)>, |
| mut block: BasicBlock, |
| f: F) |
| -> BlockAnd<R> |
| where F: FnOnce(&mut Builder<'a, 'gcx, 'tcx>) -> BlockAnd<R> |
| { |
| debug!("in_opt_scope(opt_scope={:?}, block={:?})", opt_scope, block); |
| if let Some(region_scope) = opt_scope { self.push_scope(region_scope); } |
| let rv = unpack!(block = f(self)); |
| if let Some(region_scope) = opt_scope { |
| unpack!(block = self.pop_scope(region_scope, block)); |
| } |
| debug!("in_scope: exiting opt_scope={:?} block={:?}", opt_scope, block); |
| block.and(rv) |
| } |
| |
| /// Convenience wrapper that pushes a scope and then executes `f` |
| /// to build its contents, popping the scope afterwards. |
| pub fn in_scope<F, R>(&mut self, |
| region_scope: (region::Scope, SourceInfo), |
| lint_level: LintLevel, |
| mut block: BasicBlock, |
| f: F) |
| -> BlockAnd<R> |
| where F: FnOnce(&mut Builder<'a, 'gcx, 'tcx>) -> BlockAnd<R> |
| { |
| debug!("in_scope(region_scope={:?}, block={:?})", region_scope, block); |
| let source_scope = self.source_scope; |
| let tcx = self.hir.tcx(); |
| if let LintLevel::Explicit(node_id) = lint_level { |
| let same_lint_scopes = tcx.dep_graph.with_ignore(|| { |
| let sets = tcx.lint_levels(LOCAL_CRATE); |
| let parent_hir_id = |
| tcx.hir.definitions().node_to_hir_id( |
| self.source_scope_local_data[source_scope].lint_root |
| ); |
| let current_hir_id = |
| tcx.hir.definitions().node_to_hir_id(node_id); |
| sets.lint_level_set(parent_hir_id) == |
| sets.lint_level_set(current_hir_id) |
| }); |
| |
| if !same_lint_scopes { |
| self.source_scope = |
| self.new_source_scope(region_scope.1.span, lint_level, |
| None); |
| } |
| } |
| self.push_scope(region_scope); |
| let rv = unpack!(block = f(self)); |
| unpack!(block = self.pop_scope(region_scope, block)); |
| self.source_scope = source_scope; |
| debug!("in_scope: exiting region_scope={:?} block={:?}", region_scope, block); |
| block.and(rv) |
| } |
| |
| /// Push a scope onto the stack. You can then build code in this |
| /// scope and call `pop_scope` afterwards. Note that these two |
| /// 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(), |
| }); |
| } |
| |
| /// Pops a scope, which should have region scope `region_scope`, |
| /// adding any drops onto the end of `block` that are needed. |
| /// This must match 1-to-1 with `push_scope`. |
| pub fn pop_scope(&mut self, |
| region_scope: (region::Scope, SourceInfo), |
| mut block: BasicBlock) |
| -> BlockAnd<()> { |
| 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 { |
| self.diverge_cleanup(); |
| } |
| let scope = self.scopes.pop().unwrap(); |
| assert_eq!(scope.region_scope, region_scope.0); |
| |
| self.cfg.push_end_region(self.hir.tcx(), block, region_scope.1, scope.region_scope); |
| let resume_block = self.resume_block(); |
| unpack!(block = build_scope_drops(&mut self.cfg, |
| resume_block, |
| &scope, |
| &self.scopes, |
| block, |
| self.arg_count, |
| false)); |
| |
| block.unit() |
| } |
| |
| |
| /// Branch out of `block` to `target`, exiting all scopes up to |
| /// and including `region_scope`. This will insert whatever drops are |
| /// needed, as well as tracking this exit for the SEME region. See |
| /// module comment for details. |
| pub fn exit_scope(&mut self, |
| span: Span, |
| region_scope: (region::Scope, SourceInfo), |
| mut block: BasicBlock, |
| 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"); |
| |
| // 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.drops.iter().any(|s| s.kind.may_panic())); |
| if may_panic { |
| self.diverge_cleanup(); |
| } |
| |
| { |
| let resume_block = self.resume_block(); |
| let mut rest = &mut self.scopes[(len - scope_count)..]; |
| while let Some((scope, rest_)) = {rest}.split_last_mut() { |
| rest = rest_; |
| block = if let Some(&e) = scope.cached_exits.get(&(target, region_scope.0)) { |
| self.cfg.terminate(block, scope.source_info(span), |
| TerminatorKind::Goto { target: e }); |
| return; |
| } else { |
| let b = self.cfg.start_new_block(); |
| self.cfg.terminate(block, scope.source_info(span), |
| TerminatorKind::Goto { target: b }); |
| scope.cached_exits.insert((target, region_scope.0), b); |
| b |
| }; |
| |
| // End all regions for scopes out of which we are breaking. |
| self.cfg.push_end_region(self.hir.tcx(), block, region_scope.1, scope.region_scope); |
| |
| unpack!(block = build_scope_drops(&mut self.cfg, |
| resume_block, |
| scope, |
| rest, |
| block, |
| self.arg_count, |
| false)); |
| } |
| } |
| let scope = &self.scopes[len - scope_count]; |
| self.cfg.terminate(block, scope.source_info(span), |
| TerminatorKind::Goto { target: target }); |
| } |
| |
| /// Creates a path that performs all required cleanup for dropping a generator. |
| /// |
| /// This path terminates in GeneratorDrop. Returns the start of the path. |
| /// None indicates there’s no cleanup to do at this point. |
| pub fn generator_drop_cleanup(&mut self) -> Option<BasicBlock> { |
| if !self.scopes.iter().any(|scope| scope.needs_cleanup) { |
| return None; |
| } |
| |
| // Fill in the cache |
| self.diverge_cleanup_gen(true); |
| |
| let src_info = self.scopes[0].source_info(self.fn_span); |
| let mut block = self.cfg.start_new_block(); |
| let result = block; |
| let resume_block = self.resume_block(); |
| let mut rest = &mut self.scopes[..]; |
| |
| while let Some((scope, rest_)) = {rest}.split_last_mut() { |
| rest = rest_; |
| if !scope.needs_cleanup { |
| continue; |
| } |
| block = if let Some(b) = scope.cached_generator_drop { |
| self.cfg.terminate(block, src_info, |
| TerminatorKind::Goto { target: b }); |
| return Some(result); |
| } else { |
| let b = self.cfg.start_new_block(); |
| scope.cached_generator_drop = Some(b); |
| self.cfg.terminate(block, src_info, |
| TerminatorKind::Goto { target: b }); |
| b |
| }; |
| |
| // End all regions for scopes out of which we are breaking. |
| self.cfg.push_end_region(self.hir.tcx(), block, src_info, scope.region_scope); |
| |
| unpack!(block = build_scope_drops(&mut self.cfg, |
| resume_block, |
| scope, |
| rest, |
| block, |
| self.arg_count, |
| true)); |
| } |
| |
| self.cfg.terminate(block, src_info, TerminatorKind::GeneratorDrop); |
| |
| Some(result) |
| } |
| |
| /// Creates a new source scope, nested in the current one. |
| pub fn new_source_scope(&mut self, |
| span: Span, |
| lint_level: LintLevel, |
| safety: Option<Safety>) -> SourceScope { |
| let parent = self.source_scope; |
| debug!("new_source_scope({:?}, {:?}, {:?}) - parent({:?})={:?}", |
| span, lint_level, safety, |
| parent, self.source_scope_local_data.get(parent)); |
| let scope = self.source_scopes.push(SourceScopeData { |
| span, |
| parent_scope: Some(parent), |
| }); |
| let scope_local_data = SourceScopeLocalData { |
| lint_root: if let LintLevel::Explicit(lint_root) = lint_level { |
| lint_root |
| } else { |
| self.source_scope_local_data[parent].lint_root |
| }, |
| safety: safety.unwrap_or_else(|| { |
| self.source_scope_local_data[parent].safety |
| }) |
| }; |
| self.source_scope_local_data.push(scope_local_data); |
| 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 { |
| span, |
| scope: self.source_scope |
| } |
| } |
| |
| /// 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 |
| } |
| |
| /// 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 |
| /// scope (which can be larger or smaller). |
| /// |
| /// Consider: |
| /// |
| /// let x = foo(bar(X, Y)); |
| /// |
| /// We wish to pop the storage for X and Y after `bar()` is |
| /// called, not after the whole `let` is completed. |
| /// |
| /// As another example, if the second argument diverges: |
| /// |
| /// foo(Box::new(2), panic!()) |
| /// |
| /// We would allocate the box but then free it on the unwinding |
| /// path; we would also emit a free on the 'success' path from |
| /// panic, but that will turn out to be removed as dead-code. |
| /// |
| /// When building statics/constants, returns `None` since |
| /// intermediate values do not have to be dropped in that case. |
| pub fn local_scope(&self) -> Option<region::Scope> { |
| match self.hir.body_owner_kind { |
| hir::BodyOwnerKind::Const | |
| hir::BodyOwnerKind::Static(_) => |
| // No need to free storage in this context. |
| None, |
| hir::BodyOwnerKind::Fn => |
| Some(self.topmost_scope()), |
| } |
| } |
| |
| // 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 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 |
| } |
| |
| pub fn schedule_drop_storage_and_value( |
| &mut self, |
| span: Span, |
| region_scope: region::Scope, |
| place: &Place<'tcx>, |
| place_ty: Ty<'tcx>, |
| ) { |
| self.schedule_drop( |
| span, region_scope, place, place_ty, |
| DropKind::Storage, |
| ); |
| self.schedule_drop( |
| span, region_scope, place, place_ty, |
| DropKind::Value { |
| cached_block: CachedBlock::default(), |
| }, |
| ); |
| } |
| |
| // Scheduling drops |
| // ================ |
| /// Indicates that `place` should be dropped on exit from |
| /// `region_scope`. |
| /// |
| /// When called with `DropKind::Storage`, `place` should be a local |
| /// with an index higher than the current `self.arg_count`. |
| pub fn schedule_drop( |
| &mut self, |
| span: Span, |
| region_scope: region::Scope, |
| place: &Place<'tcx>, |
| place_ty: Ty<'tcx>, |
| drop_kind: DropKind, |
| ) { |
| let needs_drop = self.hir.needs_drop(place_ty); |
| match drop_kind { |
| DropKind::Value { .. } => if !needs_drop { return }, |
| DropKind::Storage => { |
| match *place { |
| Place::Local(index) => if index.index() <= self.arg_count { |
| span_bug!( |
| span, "`schedule_drop` called with index {} and arg_count {}", |
| index.index(), |
| self.arg_count, |
| ) |
| }, |
| _ => span_bug!( |
| span, "`schedule_drop` called with non-`Local` place {:?}", place |
| ), |
| } |
| } |
| } |
| |
| for scope in self.scopes.iter_mut().rev() { |
| 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) |
| // blocks. This, however, means that whenever we add a drop into a scope which already |
| // had some blocks built (and thus, cached) for it, we must invalidate all caches which |
| // might branch into the scope which had a drop just added to it. This is necessary, |
| // because otherwise some other code might use the cache to branch into already built |
| // chain of drops, essentially ignoring the newly added drop. |
| // |
| // For example consider there’s two scopes with a drop in each. These are built and |
| // thus the caches are filled: |
| // |
| // +--------------------------------------------------------+ |
| // | +---------------------------------+ | |
| // | | +--------+ +-------------+ | +---------------+ | |
| // | | | return | <-+ | drop(outer) | <-+ | drop(middle) | | |
| // | | +--------+ +-------------+ | +---------------+ | |
| // | +------------|outer_scope cache|--+ | |
| // +------------------------------|middle_scope cache|------+ |
| // |
| // Now, a new, inner-most scope is added along with a new drop into both inner-most and |
| // outer-most scopes: |
| // |
| // +------------------------------------------------------------+ |
| // | +----------------------------------+ | |
| // | | +--------+ +-------------+ | +---------------+ | +-------------+ |
| // | | | return | <+ | drop(new) | <-+ | drop(middle) | <--+| drop(inner) | |
| // | | +--------+ | | drop(outer) | | +---------------+ | +-------------+ |
| // | | +-+ +-------------+ | | |
| // | +---|invalid outer_scope cache|----+ | |
| // +----=----------------|invalid middle_scope cache|-----------+ |
| // |
| // If, when adding `drop(new)` we do not invalidate the cached blocks for both |
| // outer_scope and middle_scope, then, when building drops for the inner (right-most) |
| // scope, the old, cached blocks, without `drop(new)` will get used, producing the |
| // wrong results. |
| // |
| // The cache and its invalidation for unwind branch is somewhat special. The cache is |
| // per-drop, rather than per scope, which has a several different implications. Adding |
| // a new drop into a scope will not invalidate cached blocks of the prior drops in the |
| // scope. That is true, because none of the already existing drops will have an edge |
| // into a block with the newly added drop. |
| // |
| // Note that this code iterates scopes from the inner-most to the outer-most, |
| // invalidating caches of each scope visited. This way bare minimum of the |
| // caches gets invalidated. i.e. if a new drop is added into the middle scope, the |
| // cache of outer scpoe stays intact. |
| scope.invalidate_cache(!needs_drop, this_scope); |
| if this_scope { |
| if let DropKind::Value { .. } = drop_kind { |
| scope.needs_cleanup = true; |
| } |
| |
| let region_scope_span = region_scope.span(self.hir.tcx(), |
| &self.hir.region_scope_tree); |
| // Attribute scope exit drops to scope's closing brace. |
| let scope_end = self.hir.tcx().sess.source_map().end_point(region_scope_span); |
| |
| scope.drops.push(DropData { |
| span: scope_end, |
| location: place.clone(), |
| kind: drop_kind |
| }); |
| return; |
| } |
| } |
| span_bug!(span, "region scope {:?} not in scope to drop {:?}", region_scope, place); |
| } |
| |
| // Other |
| // ===== |
| /// Creates a path that performs all required cleanup for unwinding. |
| /// |
| /// This path terminates in Resume. Returns the start of the path. |
| /// See module comment for more details. None indicates there’s no |
| /// cleanup to do at this point. |
| pub fn diverge_cleanup(&mut self) -> BasicBlock { |
| self.diverge_cleanup_gen(false) |
| } |
| |
| fn resume_block(&mut self) -> BasicBlock { |
| if let Some(target) = self.cached_resume_block { |
| target |
| } else { |
| let resumeblk = self.cfg.start_new_cleanup_block(); |
| self.cfg.terminate(resumeblk, |
| SourceInfo { |
| scope: OUTERMOST_SOURCE_SCOPE, |
| span: self.fn_span |
| }, |
| TerminatorKind::Resume); |
| self.cached_resume_block = Some(resumeblk); |
| resumeblk |
| } |
| } |
| |
| fn diverge_cleanup_gen(&mut self, generator_drop: bool) -> BasicBlock { |
| // To start, create the resume terminator. |
| let mut target = self.resume_block(); |
| |
| let Builder { ref mut cfg, ref mut scopes, .. } = *self; |
| |
| // Build up the drops in **reverse** order. The end result will |
| // look like: |
| // |
| // scopes[n] -> scopes[n-1] -> ... -> scopes[0] |
| // |
| // However, we build this in **reverse order**. That is, we |
| // process scopes[0], then scopes[1], etc, pointing each one at |
| // the result generates from the one before. Along the way, we |
| // store caches. If everything is cached, we'll just walk right |
| // to left reading the cached results but never created anything. |
| |
| if scopes.iter().any(|scope| scope.needs_cleanup) { |
| for scope in scopes.iter_mut() { |
| target = build_diverge_scope(self.hir.tcx(), cfg, scope.region_scope_span, |
| scope, target, generator_drop); |
| } |
| } |
| |
| target |
| } |
| |
| /// Utility function for *non*-scope code to build their own drops |
| pub fn build_drop(&mut self, |
| block: BasicBlock, |
| span: Span, |
| location: Place<'tcx>, |
| ty: Ty<'tcx>) -> BlockAnd<()> { |
| if !self.hir.needs_drop(ty) { |
| return block.unit(); |
| } |
| let source_info = self.source_info(span); |
| let next_target = self.cfg.start_new_block(); |
| let diverge_target = self.diverge_cleanup(); |
| self.cfg.terminate(block, source_info, |
| TerminatorKind::Drop { |
| location, |
| target: next_target, |
| unwind: Some(diverge_target), |
| }); |
| next_target.unit() |
| } |
| |
| /// Utility function for *non*-scope code to build their own drops |
| pub fn build_drop_and_replace(&mut self, |
| block: BasicBlock, |
| span: Span, |
| location: Place<'tcx>, |
| value: Operand<'tcx>) -> BlockAnd<()> { |
| let source_info = self.source_info(span); |
| let next_target = self.cfg.start_new_block(); |
| let diverge_target = self.diverge_cleanup(); |
| self.cfg.terminate(block, source_info, |
| TerminatorKind::DropAndReplace { |
| location, |
| value, |
| target: next_target, |
| unwind: Some(diverge_target), |
| }); |
| next_target.unit() |
| } |
| |
| /// Create an Assert terminator and return the success block. |
| /// If the boolean condition operand is not the expected value, |
| /// a runtime panic will be caused with the given message. |
| pub fn assert(&mut self, block: BasicBlock, |
| cond: Operand<'tcx>, |
| expected: bool, |
| msg: AssertMessage<'tcx>, |
| span: Span) |
| -> BasicBlock { |
| let source_info = self.source_info(span); |
| |
| let success_block = self.cfg.start_new_block(); |
| let cleanup = self.diverge_cleanup(); |
| |
| self.cfg.terminate(block, source_info, |
| TerminatorKind::Assert { |
| cond, |
| expected, |
| msg, |
| target: success_block, |
| cleanup: Some(cleanup), |
| }); |
| |
| success_block |
| } |
| } |
| |
| /// Builds drops for pop_scope and exit_scope. |
| fn build_scope_drops<'tcx>(cfg: &mut CFG<'tcx>, |
| resume_block: BasicBlock, |
| scope: &Scope<'tcx>, |
| earlier_scopes: &[Scope<'tcx>], |
| mut block: BasicBlock, |
| arg_count: usize, |
| generator_drop: bool) |
| -> BlockAnd<()> { |
| debug!("build_scope_drops({:?} -> {:?})", block, scope); |
| let mut iter = scope.drops.iter().rev(); |
| while let Some(drop_data) = iter.next() { |
| let source_info = scope.source_info(drop_data.span); |
| match drop_data.kind { |
| DropKind::Value { .. } => { |
| // Try to find the next block with its cached block for us to |
| // diverge into, either a previous block in this current scope or |
| // the top of the previous scope. |
| // |
| // If it wasn't for EndRegion, we could just chain all the DropData |
| // together and pick the first DropKind::Value. Please do that |
| // when we replace EndRegion with NLL. |
| let on_diverge = iter.clone().filter_map(|dd| { |
| match dd.kind { |
| DropKind::Value { cached_block } => Some(cached_block), |
| DropKind::Storage => None |
| } |
| }).next().or_else(|| { |
| if earlier_scopes.iter().any(|scope| scope.needs_cleanup) { |
| // If *any* scope requires cleanup code to be run, |
| // we must use the cached unwind from the *topmost* |
| // scope, to ensure all EndRegions from surrounding |
| // scopes are executed before the drop code runs. |
| Some(earlier_scopes.last().unwrap().cached_unwind) |
| } else { |
| // We don't need any further cleanup, so return None |
| // to avoid creating a landing pad. We can skip |
| // EndRegions because all local regions end anyway |
| // when the function unwinds. |
| // |
| // This is an important optimization because LLVM is |
| // terrible at optimizing landing pads. FIXME: I think |
| // it would be cleaner and better to do this optimization |
| // in SimplifyCfg instead of here. |
| None |
| } |
| }); |
| |
| let on_diverge = on_diverge.map(|cached_block| { |
| cached_block.get(generator_drop).unwrap_or_else(|| { |
| span_bug!(drop_data.span, "cached block not present?") |
| }) |
| }); |
| |
| let next = cfg.start_new_block(); |
| cfg.terminate(block, source_info, TerminatorKind::Drop { |
| location: drop_data.location.clone(), |
| target: next, |
| unwind: Some(on_diverge.unwrap_or(resume_block)) |
| }); |
| block = next; |
| } |
| DropKind::Storage => { |
| // We do not need to emit StorageDead for generator drops |
| if generator_drop { |
| continue |
| } |
| |
| // Drop the storage for both value and storage drops. |
| // Only temps and vars need their storage dead. |
| match drop_data.location { |
| Place::Local(index) if index.index() > arg_count => { |
| cfg.push(block, Statement { |
| source_info, |
| kind: StatementKind::StorageDead(index) |
| }); |
| } |
| _ => unreachable!(), |
| } |
| } |
| } |
| } |
| block.unit() |
| } |
| |
| fn build_diverge_scope<'a, 'gcx, 'tcx>(tcx: TyCtxt<'a, 'gcx, 'tcx>, |
| cfg: &mut CFG<'tcx>, |
| span: Span, |
| scope: &mut Scope<'tcx>, |
| mut target: BasicBlock, |
| generator_drop: bool) |
| -> BasicBlock |
| { |
| // Build up the drops in **reverse** order. The end result will |
| // look like: |
| // |
| // [EndRegion Block] -> [drops[n]] -...-> [drops[0]] -> [Free] -> [target] |
| // | | |
| // +---------------------------------------------------------+ |
| // code for scope |
| // |
| // The code in this function reads from right to left. At each |
| // point, we check for cached blocks representing the |
| // remainder. If everything is cached, we'll just walk right to |
| // left reading the cached results but never create anything. |
| |
| let source_scope = scope.source_scope; |
| let source_info = |span| SourceInfo { |
| span, |
| scope: source_scope |
| }; |
| |
| // Next, build up the drops. Here we iterate the vector in |
| // *forward* order, so that we generate drops[0] first (right to |
| // left in diagram above). |
| for (j, drop_data) in scope.drops.iter_mut().enumerate() { |
| debug!("build_diverge_scope drop_data[{}]: {:?}", j, drop_data); |
| // Only full value drops are emitted in the diverging path, |
| // not StorageDead. |
| // |
| // Note: This may not actually be what we desire (are we |
| // "freeing" stack storage as we unwind, or merely observing a |
| // frozen stack)? In particular, the intent may have been to |
| // match the behavior of clang, but on inspection eddyb says |
| // this is not what clang does. |
| let cached_block = match drop_data.kind { |
| DropKind::Value { ref mut cached_block } => cached_block.ref_mut(generator_drop), |
| DropKind::Storage => continue |
| }; |
| target = if let Some(cached_block) = *cached_block { |
| cached_block |
| } else { |
| let block = cfg.start_new_cleanup_block(); |
| cfg.terminate(block, source_info(drop_data.span), |
| TerminatorKind::Drop { |
| location: drop_data.location.clone(), |
| target, |
| unwind: None |
| }); |
| *cached_block = Some(block); |
| block |
| }; |
| } |
| |
| // Finally, push the EndRegion block, used by mir-borrowck, and set |
| // `cached_unwind` to point to it (Block becomes trivial goto after |
| // pass that removes all EndRegions). |
| target = { |
| let cached_block = scope.cached_unwind.ref_mut(generator_drop); |
| if let Some(cached_block) = *cached_block { |
| cached_block |
| } else { |
| let block = cfg.start_new_cleanup_block(); |
| cfg.push_end_region(tcx, block, source_info(span), scope.region_scope); |
| cfg.terminate(block, source_info(span), TerminatorKind::Goto { target: target }); |
| *cached_block = Some(block); |
| block |
| } |
| }; |
| |
| debug!("build_diverge_scope({:?}, {:?}) = {:?}", scope, span, target); |
| |
| target |
| } |