// Copyright 2013-2014 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 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! ## The Cleanup module //! //! The cleanup module tracks what values need to be cleaned up as scopes //! are exited, either via panic or just normal control flow. The basic //! idea is that the function context maintains a stack of cleanup scopes //! that are pushed/popped as we traverse the AST tree. There is typically //! at least one cleanup scope per AST node; some AST nodes may introduce //! additional temporary scopes. //! //! Cleanup items can be scheduled into any of the scopes on the stack. //! Typically, when a scope is popped, we will also generate the code for //! each of its cleanups at that time. This corresponds to a normal exit //! from a block (for example, an expression completing evaluation //! successfully without panic). However, it is also possible to pop a //! block *without* executing its cleanups; this is typically used to //! guard intermediate values that must be cleaned up on panic, but not //! if everything goes right. See the section on custom scopes below for //! more details. //! //! Cleanup scopes come in three kinds: //! //! - **AST scopes:** each AST node in a function body has a corresponding //! AST scope. We push the AST scope when we start generate code for an AST //! node and pop it once the AST node has been fully generated. //! - **Loop scopes:** loops have an additional cleanup scope. Cleanups are //! never scheduled into loop scopes; instead, they are used to record the //! basic blocks that we should branch to when a `continue` or `break` statement //! is encountered. //! - **Custom scopes:** custom scopes are typically used to ensure cleanup //! of intermediate values. //! //! ### When to schedule cleanup //! //! Although the cleanup system is intended to *feel* fairly declarative, //! it's still important to time calls to `schedule_clean()` correctly. //! Basically, you should not schedule cleanup for memory until it has //! been initialized, because if an unwind should occur before the memory //! is fully initialized, then the cleanup will run and try to free or //! drop uninitialized memory. If the initialization itself produces //! byproducts that need to be freed, then you should use temporary custom //! scopes to ensure that those byproducts will get freed on unwind. For //! example, an expression like `box foo()` will first allocate a box in the //! heap and then call `foo()` -- if `foo()` should panic, this box needs //! to be *shallowly* freed. //! //! ### Long-distance jumps //! //! In addition to popping a scope, which corresponds to normal control //! flow exiting the scope, we may also *jump out* of a scope into some //! earlier scope on the stack. This can occur in response to a `return`, //! `break`, or `continue` statement, but also in response to panic. In //! any of these cases, we will generate a series of cleanup blocks for //! each of the scopes that is exited. So, if the stack contains scopes A //! ... Z, and we break out of a loop whose corresponding cleanup scope is //! X, we would generate cleanup blocks for the cleanups in X, Y, and Z. //! After cleanup is done we would branch to the exit point for scope X. //! But if panic should occur, we would generate cleanups for all the //! scopes from A to Z and then resume the unwind process afterwards. //! //! To avoid generating tons of code, we cache the cleanup blocks that we //! create for breaks, returns, unwinds, and other jumps. Whenever a new //! cleanup is scheduled, though, we must clear these cached blocks. A //! possible improvement would be to keep the cached blocks but simply //! generate a new block which performs the additional cleanup and then //! branches to the existing cached blocks. //! //! ### AST and loop cleanup scopes //! //! AST cleanup scopes are pushed when we begin and end processing an AST //! node. They are used to house cleanups related to rvalue temporary that //! get referenced (e.g., due to an expression like `&Foo()`). Whenever an //! AST scope is popped, we always trans all the cleanups, adding the cleanup //! code after the postdominator of the AST node. //! //! AST nodes that represent breakable loops also push a loop scope; the //! loop scope never has any actual cleanups, it's just used to point to //! the basic blocks where control should flow after a "continue" or //! "break" statement. Popping a loop scope never generates code. //! //! ### Custom cleanup scopes //! //! Custom cleanup scopes are used for a variety of purposes. The most //! common though is to handle temporary byproducts, where cleanup only //! needs to occur on panic. The general strategy is to push a custom //! cleanup scope, schedule *shallow* cleanups into the custom scope, and //! then pop the custom scope (without transing the cleanups) when //! execution succeeds normally. This way the cleanups are only trans'd on //! unwind, and only up until the point where execution succeeded, at //! which time the complete value should be stored in an lvalue or some //! other place where normal cleanup applies. //! //! To spell it out, here is an example. Imagine an expression `box expr`. //! We would basically: //! //! 1. Push a custom cleanup scope C. //! 2. Allocate the box. //! 3. Schedule a shallow free in the scope C. //! 4. Trans `expr` into the box. //! 5. Pop the scope C. //! 6. Return the box as an rvalue. //! //! This way, if a panic occurs while transing `expr`, the custom //! cleanup scope C is pushed and hence the box will be freed. The trans //! code for `expr` itself is responsible for freeing any other byproducts //! that may be in play. pub use self::ScopeId::*; pub use self::CleanupScopeKind::*; pub use self::EarlyExitLabel::*; pub use self::Heap::*; use llvm::{BasicBlockRef, ValueRef}; use trans::base; use trans::build; use trans::callee; use trans::common; use trans::common::{Block, FunctionContext, ExprId, NodeIdAndSpan}; use trans::debuginfo::{DebugLoc, ToDebugLoc}; use trans::glue; use middle::region; use trans::type_::Type; use middle::ty::{self, Ty}; use std::fmt; use syntax::ast; use util::ppaux::Repr; pub struct CleanupScope<'blk, 'tcx: 'blk> { // The id of this cleanup scope. If the id is None, // this is a *temporary scope* that is pushed during trans to // cleanup miscellaneous garbage that trans may generate whose // lifetime is a subset of some expression. See module doc for // more details. kind: CleanupScopeKind<'blk, 'tcx>, // Cleanups to run upon scope exit. cleanups: Vec>, // The debug location any drop calls generated for this scope will be // associated with. debug_loc: DebugLoc, cached_early_exits: Vec, cached_landing_pad: Option, } #[derive(Copy, Debug)] pub struct CustomScopeIndex { index: uint } pub const EXIT_BREAK: uint = 0; pub const EXIT_LOOP: uint = 1; pub const EXIT_MAX: uint = 2; pub enum CleanupScopeKind<'blk, 'tcx: 'blk> { CustomScopeKind, AstScopeKind(ast::NodeId), LoopScopeKind(ast::NodeId, [Block<'blk, 'tcx>; EXIT_MAX]) } impl<'blk, 'tcx: 'blk> fmt::Debug for CleanupScopeKind<'blk, 'tcx> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { CustomScopeKind => write!(f, "CustomScopeKind"), AstScopeKind(nid) => write!(f, "AstScopeKind({})", nid), LoopScopeKind(nid, ref blks) => { try!(write!(f, "LoopScopeKind({}, [", nid)); for blk in blks { try!(write!(f, "{:p}, ", blk)); } write!(f, "])") } } } } #[derive(Copy, PartialEq, Debug)] pub enum EarlyExitLabel { UnwindExit, ReturnExit, LoopExit(ast::NodeId, uint) } #[derive(Copy)] pub struct CachedEarlyExit { label: EarlyExitLabel, cleanup_block: BasicBlockRef, } pub trait Cleanup<'tcx> { fn must_unwind(&self) -> bool; fn clean_on_unwind(&self) -> bool; fn is_lifetime_end(&self) -> bool; fn trans<'blk>(&self, bcx: Block<'blk, 'tcx>, debug_loc: DebugLoc) -> Block<'blk, 'tcx>; } pub type CleanupObj<'tcx> = Box+'tcx>; #[derive(Copy, Debug)] pub enum ScopeId { AstScope(ast::NodeId), CustomScope(CustomScopeIndex) } impl<'blk, 'tcx> CleanupMethods<'blk, 'tcx> for FunctionContext<'blk, 'tcx> { /// Invoked when we start to trans the code contained within a new cleanup scope. fn push_ast_cleanup_scope(&self, debug_loc: NodeIdAndSpan) { debug!("push_ast_cleanup_scope({})", self.ccx.tcx().map.node_to_string(debug_loc.id)); // FIXME(#2202) -- currently closure bodies have a parent // region, which messes up the assertion below, since there // are no cleanup scopes on the stack at the start of // trans'ing a closure body. I think though that this should // eventually be fixed by closure bodies not having a parent // region, though that's a touch unclear, and it might also be // better just to narrow this assertion more (i.e., by // excluding id's that correspond to closure bodies only). For // now we just say that if there is already an AST scope on the stack, // this new AST scope had better be its immediate child. let top_scope = self.top_ast_scope(); if top_scope.is_some() { assert!((self.ccx .tcx() .region_maps .opt_encl_scope(region::CodeExtent::from_node_id(debug_loc.id)) .map(|s|s.node_id()) == top_scope) || (self.ccx .tcx() .region_maps .opt_encl_scope(region::CodeExtent::DestructionScope(debug_loc.id)) .map(|s|s.node_id()) == top_scope)); } self.push_scope(CleanupScope::new(AstScopeKind(debug_loc.id), debug_loc.debug_loc())); } fn push_loop_cleanup_scope(&self, id: ast::NodeId, exits: [Block<'blk, 'tcx>; EXIT_MAX]) { debug!("push_loop_cleanup_scope({})", self.ccx.tcx().map.node_to_string(id)); assert_eq!(Some(id), self.top_ast_scope()); // Just copy the debuginfo source location from the enclosing scope let debug_loc = self.scopes .borrow() .last() .unwrap() .debug_loc; self.push_scope(CleanupScope::new(LoopScopeKind(id, exits), debug_loc)); } fn push_custom_cleanup_scope(&self) -> CustomScopeIndex { let index = self.scopes_len(); debug!("push_custom_cleanup_scope(): {}", index); // Just copy the debuginfo source location from the enclosing scope let debug_loc = self.scopes .borrow() .last() .map(|opt_scope| opt_scope.debug_loc) .unwrap_or(DebugLoc::None); self.push_scope(CleanupScope::new(CustomScopeKind, debug_loc)); CustomScopeIndex { index: index } } fn push_custom_cleanup_scope_with_debug_loc(&self, debug_loc: NodeIdAndSpan) -> CustomScopeIndex { let index = self.scopes_len(); debug!("push_custom_cleanup_scope(): {}", index); self.push_scope(CleanupScope::new(CustomScopeKind, debug_loc.debug_loc())); CustomScopeIndex { index: index } } /// Removes the cleanup scope for id `cleanup_scope`, which must be at the top of the cleanup /// stack, and generates the code to do its cleanups for normal exit. fn pop_and_trans_ast_cleanup_scope(&self, bcx: Block<'blk, 'tcx>, cleanup_scope: ast::NodeId) -> Block<'blk, 'tcx> { debug!("pop_and_trans_ast_cleanup_scope({})", self.ccx.tcx().map.node_to_string(cleanup_scope)); assert!(self.top_scope(|s| s.kind.is_ast_with_id(cleanup_scope))); let scope = self.pop_scope(); self.trans_scope_cleanups(bcx, &scope) } /// Removes the loop cleanup scope for id `cleanup_scope`, which must be at the top of the /// cleanup stack. Does not generate any cleanup code, since loop scopes should exit by /// branching to a block generated by `normal_exit_block`. fn pop_loop_cleanup_scope(&self, cleanup_scope: ast::NodeId) { debug!("pop_loop_cleanup_scope({})", self.ccx.tcx().map.node_to_string(cleanup_scope)); assert!(self.top_scope(|s| s.kind.is_loop_with_id(cleanup_scope))); let _ = self.pop_scope(); } /// Removes the top cleanup scope from the stack without executing its cleanups. The top /// cleanup scope must be the temporary scope `custom_scope`. fn pop_custom_cleanup_scope(&self, custom_scope: CustomScopeIndex) { debug!("pop_custom_cleanup_scope({})", custom_scope.index); assert!(self.is_valid_to_pop_custom_scope(custom_scope)); let _ = self.pop_scope(); } /// Removes the top cleanup scope from the stack, which must be a temporary scope, and /// generates the code to do its cleanups for normal exit. fn pop_and_trans_custom_cleanup_scope(&self, bcx: Block<'blk, 'tcx>, custom_scope: CustomScopeIndex) -> Block<'blk, 'tcx> { debug!("pop_and_trans_custom_cleanup_scope({:?})", custom_scope); assert!(self.is_valid_to_pop_custom_scope(custom_scope)); let scope = self.pop_scope(); self.trans_scope_cleanups(bcx, &scope) } /// Returns the id of the top-most loop scope fn top_loop_scope(&self) -> ast::NodeId { for scope in self.scopes.borrow().iter().rev() { if let LoopScopeKind(id, _) = scope.kind { return id; } } self.ccx.sess().bug("no loop scope found"); } /// Returns a block to branch to which will perform all pending cleanups and then /// break/continue (depending on `exit`) out of the loop with id `cleanup_scope` fn normal_exit_block(&'blk self, cleanup_scope: ast::NodeId, exit: uint) -> BasicBlockRef { self.trans_cleanups_to_exit_scope(LoopExit(cleanup_scope, exit)) } /// Returns a block to branch to which will perform all pending cleanups and then return from /// this function fn return_exit_block(&'blk self) -> BasicBlockRef { self.trans_cleanups_to_exit_scope(ReturnExit) } fn schedule_lifetime_end(&self, cleanup_scope: ScopeId, val: ValueRef) { let drop = box LifetimeEnd { ptr: val, }; debug!("schedule_lifetime_end({:?}, val={})", cleanup_scope, self.ccx.tn().val_to_string(val)); self.schedule_clean(cleanup_scope, drop as CleanupObj); } /// Schedules a (deep) drop of `val`, which is a pointer to an instance of `ty` fn schedule_drop_mem(&self, cleanup_scope: ScopeId, val: ValueRef, ty: Ty<'tcx>) { if !common::type_needs_drop(self.ccx.tcx(), ty) { return; } let drop = box DropValue { is_immediate: false, must_unwind: common::type_needs_unwind_cleanup(self.ccx, ty), val: val, ty: ty, zero: false }; debug!("schedule_drop_mem({:?}, val={}, ty={})", cleanup_scope, self.ccx.tn().val_to_string(val), ty.repr(self.ccx.tcx())); self.schedule_clean(cleanup_scope, drop as CleanupObj); } /// Schedules a (deep) drop and zero-ing of `val`, which is a pointer to an instance of `ty` fn schedule_drop_and_zero_mem(&self, cleanup_scope: ScopeId, val: ValueRef, ty: Ty<'tcx>) { if !common::type_needs_drop(self.ccx.tcx(), ty) { return; } let drop = box DropValue { is_immediate: false, must_unwind: common::type_needs_unwind_cleanup(self.ccx, ty), val: val, ty: ty, zero: true }; debug!("schedule_drop_and_zero_mem({:?}, val={}, ty={}, zero={})", cleanup_scope, self.ccx.tn().val_to_string(val), ty.repr(self.ccx.tcx()), true); self.schedule_clean(cleanup_scope, drop as CleanupObj); } /// Schedules a (deep) drop of `val`, which is an instance of `ty` fn schedule_drop_immediate(&self, cleanup_scope: ScopeId, val: ValueRef, ty: Ty<'tcx>) { if !common::type_needs_drop(self.ccx.tcx(), ty) { return; } let drop = box DropValue { is_immediate: true, must_unwind: common::type_needs_unwind_cleanup(self.ccx, ty), val: val, ty: ty, zero: false }; debug!("schedule_drop_immediate({:?}, val={}, ty={:?})", cleanup_scope, self.ccx.tn().val_to_string(val), ty.repr(self.ccx.tcx())); self.schedule_clean(cleanup_scope, drop as CleanupObj); } /// Schedules a call to `free(val)`. Note that this is a shallow operation. fn schedule_free_value(&self, cleanup_scope: ScopeId, val: ValueRef, heap: Heap, content_ty: Ty<'tcx>) { let drop = box FreeValue { ptr: val, heap: heap, content_ty: content_ty }; debug!("schedule_free_value({:?}, val={}, heap={:?})", cleanup_scope, self.ccx.tn().val_to_string(val), heap); self.schedule_clean(cleanup_scope, drop as CleanupObj); } /// Schedules a call to `free(val)`. Note that this is a shallow operation. fn schedule_free_slice(&self, cleanup_scope: ScopeId, val: ValueRef, size: ValueRef, align: ValueRef, heap: Heap) { let drop = box FreeSlice { ptr: val, size: size, align: align, heap: heap }; debug!("schedule_free_slice({:?}, val={}, heap={:?})", cleanup_scope, self.ccx.tn().val_to_string(val), heap); self.schedule_clean(cleanup_scope, drop as CleanupObj); } fn schedule_clean(&self, cleanup_scope: ScopeId, cleanup: CleanupObj<'tcx>) { match cleanup_scope { AstScope(id) => self.schedule_clean_in_ast_scope(id, cleanup), CustomScope(id) => self.schedule_clean_in_custom_scope(id, cleanup), } } /// Schedules a cleanup to occur upon exit from `cleanup_scope`. If `cleanup_scope` is not /// provided, then the cleanup is scheduled in the topmost scope, which must be a temporary /// scope. fn schedule_clean_in_ast_scope(&self, cleanup_scope: ast::NodeId, cleanup: CleanupObj<'tcx>) { debug!("schedule_clean_in_ast_scope(cleanup_scope={})", cleanup_scope); for scope in self.scopes.borrow_mut().iter_mut().rev() { if scope.kind.is_ast_with_id(cleanup_scope) { scope.cleanups.push(cleanup); scope.clear_cached_exits(); return; } else { // will be adding a cleanup to some enclosing scope scope.clear_cached_exits(); } } self.ccx.sess().bug( &format!("no cleanup scope {} found", self.ccx.tcx().map.node_to_string(cleanup_scope))[]); } /// Schedules a cleanup to occur in the top-most scope, which must be a temporary scope. fn schedule_clean_in_custom_scope(&self, custom_scope: CustomScopeIndex, cleanup: CleanupObj<'tcx>) { debug!("schedule_clean_in_custom_scope(custom_scope={})", custom_scope.index); assert!(self.is_valid_custom_scope(custom_scope)); let mut scopes = self.scopes.borrow_mut(); let scope = &mut (*scopes)[custom_scope.index]; scope.cleanups.push(cleanup); scope.clear_cached_exits(); } /// Returns true if there are pending cleanups that should execute on panic. fn needs_invoke(&self) -> bool { self.scopes.borrow().iter().rev().any(|s| s.needs_invoke()) } /// Returns a basic block to branch to in the event of a panic. This block will run the panic /// cleanups and eventually invoke the LLVM `Resume` instruction. fn get_landing_pad(&'blk self) -> BasicBlockRef { let _icx = base::push_ctxt("get_landing_pad"); debug!("get_landing_pad"); let orig_scopes_len = self.scopes_len(); assert!(orig_scopes_len > 0); // Remove any scopes that do not have cleanups on panic: let mut popped_scopes = vec!(); while !self.top_scope(|s| s.needs_invoke()) { debug!("top scope does not need invoke"); popped_scopes.push(self.pop_scope()); } // Check for an existing landing pad in the new topmost scope: let llbb = self.get_or_create_landing_pad(); // Push the scopes we removed back on: loop { match popped_scopes.pop() { Some(scope) => self.push_scope(scope), None => break } } assert_eq!(self.scopes_len(), orig_scopes_len); return llbb; } } impl<'blk, 'tcx> CleanupHelperMethods<'blk, 'tcx> for FunctionContext<'blk, 'tcx> { /// Returns the id of the current top-most AST scope, if any. fn top_ast_scope(&self) -> Option { for scope in self.scopes.borrow().iter().rev() { match scope.kind { CustomScopeKind | LoopScopeKind(..) => {} AstScopeKind(i) => { return Some(i); } } } None } fn top_nonempty_cleanup_scope(&self) -> Option { self.scopes.borrow().iter().rev().position(|s| !s.cleanups.is_empty()) } fn is_valid_to_pop_custom_scope(&self, custom_scope: CustomScopeIndex) -> bool { self.is_valid_custom_scope(custom_scope) && custom_scope.index == self.scopes.borrow().len() - 1 } fn is_valid_custom_scope(&self, custom_scope: CustomScopeIndex) -> bool { let scopes = self.scopes.borrow(); custom_scope.index < scopes.len() && (*scopes)[custom_scope.index].kind.is_temp() } /// Generates the cleanups for `scope` into `bcx` fn trans_scope_cleanups(&self, // cannot borrow self, will recurse bcx: Block<'blk, 'tcx>, scope: &CleanupScope<'blk, 'tcx>) -> Block<'blk, 'tcx> { let mut bcx = bcx; if !bcx.unreachable.get() { for cleanup in scope.cleanups.iter().rev() { bcx = cleanup.trans(bcx, scope.debug_loc); } } bcx } fn scopes_len(&self) -> uint { self.scopes.borrow().len() } fn push_scope(&self, scope: CleanupScope<'blk, 'tcx>) { self.scopes.borrow_mut().push(scope) } fn pop_scope(&self) -> CleanupScope<'blk, 'tcx> { debug!("popping cleanup scope {}, {} scopes remaining", self.top_scope(|s| s.block_name("")), self.scopes_len() - 1); self.scopes.borrow_mut().pop().unwrap() } fn top_scope(&self, f: F) -> R where F: FnOnce(&CleanupScope<'blk, 'tcx>) -> R { f(self.scopes.borrow().last().unwrap()) } /// Used when the caller wishes to jump to an early exit, such as a return, break, continue, or /// unwind. This function will generate all cleanups between the top of the stack and the exit /// `label` and return a basic block that the caller can branch to. /// /// For example, if the current stack of cleanups were as follows: /// /// AST 22 /// Custom 1 /// AST 23 /// Loop 23 /// Custom 2 /// AST 24 /// /// and the `label` specifies a break from `Loop 23`, then this function would generate a /// series of basic blocks as follows: /// /// Cleanup(AST 24) -> Cleanup(Custom 2) -> break_blk /// /// where `break_blk` is the block specified in `Loop 23` as the target for breaks. The return /// value would be the first basic block in that sequence (`Cleanup(AST 24)`). The caller could /// then branch to `Cleanup(AST 24)` and it will perform all cleanups and finally branch to the /// `break_blk`. fn trans_cleanups_to_exit_scope(&'blk self, label: EarlyExitLabel) -> BasicBlockRef { debug!("trans_cleanups_to_exit_scope label={:?} scopes={}", label, self.scopes_len()); let orig_scopes_len = self.scopes_len(); let mut prev_llbb; let mut popped_scopes = vec!(); // First we pop off all the cleanup stacks that are // traversed until the exit is reached, pushing them // onto the side vector `popped_scopes`. No code is // generated at this time. // // So, continuing the example from above, we would wind up // with a `popped_scopes` vector of `[AST 24, Custom 2]`. // (Presuming that there are no cached exits) loop { if self.scopes_len() == 0 { match label { UnwindExit => { // Generate a block that will `Resume`. let prev_bcx = self.new_block(true, "resume", None); let personality = self.personality.get().expect( "create_landing_pad() should have set this"); build::Resume(prev_bcx, build::Load(prev_bcx, personality)); prev_llbb = prev_bcx.llbb; break; } ReturnExit => { prev_llbb = self.get_llreturn(); break; } LoopExit(id, _) => { self.ccx.sess().bug(&format!( "cannot exit from scope {}, \ not in scope", id)[]); } } } // Check if we have already cached the unwinding of this // scope for this label. If so, we can stop popping scopes // and branch to the cached label, since it contains the // cleanups for any subsequent scopes. match self.top_scope(|s| s.cached_early_exit(label)) { Some(cleanup_block) => { prev_llbb = cleanup_block; break; } None => { } } // Pop off the scope, since we will be generating // unwinding code for it. If we are searching for a loop exit, // and this scope is that loop, then stop popping and set // `prev_llbb` to the appropriate exit block from the loop. popped_scopes.push(self.pop_scope()); let scope = popped_scopes.last().unwrap(); match label { UnwindExit | ReturnExit => { } LoopExit(id, exit) => { match scope.kind.early_exit_block(id, exit) { Some(exitllbb) => { prev_llbb = exitllbb; break; } None => { } } } } } debug!("trans_cleanups_to_exit_scope: popped {} scopes", popped_scopes.len()); // Now push the popped scopes back on. As we go, // we track in `prev_llbb` the exit to which this scope // should branch when it's done. // // So, continuing with our example, we will start out with // `prev_llbb` being set to `break_blk` (or possibly a cached // early exit). We will then pop the scopes from `popped_scopes` // and generate a basic block for each one, prepending it in the // series and updating `prev_llbb`. So we begin by popping `Custom 2` // and generating `Cleanup(Custom 2)`. We make `Cleanup(Custom 2)` // branch to `prev_llbb == break_blk`, giving us a sequence like: // // Cleanup(Custom 2) -> prev_llbb // // We then pop `AST 24` and repeat the process, giving us the sequence: // // Cleanup(AST 24) -> Cleanup(Custom 2) -> prev_llbb // // At this point, `popped_scopes` is empty, and so the final block // that we return to the user is `Cleanup(AST 24)`. while !popped_scopes.is_empty() { let mut scope = popped_scopes.pop().unwrap(); if scope.cleanups.iter().any(|c| cleanup_is_suitable_for(&**c, label)) { let name = scope.block_name("clean"); debug!("generating cleanups for {}", name); let bcx_in = self.new_block(label.is_unwind(), &name[], None); let mut bcx_out = bcx_in; for cleanup in scope.cleanups.iter().rev() { if cleanup_is_suitable_for(&**cleanup, label) { bcx_out = cleanup.trans(bcx_out, scope.debug_loc); } } build::Br(bcx_out, prev_llbb, DebugLoc::None); prev_llbb = bcx_in.llbb; } else { debug!("no suitable cleanups in {}", scope.block_name("clean")); } scope.add_cached_early_exit(label, prev_llbb); self.push_scope(scope); } debug!("trans_cleanups_to_exit_scope: prev_llbb={:?}", prev_llbb); assert_eq!(self.scopes_len(), orig_scopes_len); prev_llbb } /// Creates a landing pad for the top scope, if one does not exist. The landing pad will /// perform all cleanups necessary for an unwind and then `resume` to continue error /// propagation: /// /// landing_pad -> ... cleanups ... -> [resume] /// /// (The cleanups and resume instruction are created by `trans_cleanups_to_exit_scope()`, not /// in this function itself.) fn get_or_create_landing_pad(&'blk self) -> BasicBlockRef { let pad_bcx; debug!("get_or_create_landing_pad"); // Check if a landing pad block exists; if not, create one. { let mut scopes = self.scopes.borrow_mut(); let last_scope = scopes.last_mut().unwrap(); match last_scope.cached_landing_pad { Some(llbb) => { return llbb; } None => { let name = last_scope.block_name("unwind"); pad_bcx = self.new_block(true, &name[], None); last_scope.cached_landing_pad = Some(pad_bcx.llbb); } } } // The landing pad return type (the type being propagated). Not sure what // this represents but it's determined by the personality function and // this is what the EH proposal example uses. let llretty = Type::struct_(self.ccx, &[Type::i8p(self.ccx), Type::i32(self.ccx)], false); // The exception handling personality function. // // If our compilation unit has the `eh_personality` lang item somewhere // within it, then we just need to translate that. Otherwise, we're // building an rlib which will depend on some upstream implementation of // this function, so we just codegen a generic reference to it. We don't // specify any of the types for the function, we just make it a symbol // that LLVM can later use. let llpersonality = match pad_bcx.tcx().lang_items.eh_personality() { Some(def_id) => { callee::trans_fn_ref(pad_bcx.ccx(), def_id, ExprId(0), pad_bcx.fcx.param_substs).val } None => { let mut personality = self.ccx.eh_personality().borrow_mut(); match *personality { Some(llpersonality) => llpersonality, None => { let fty = Type::variadic_func(&[], &Type::i32(self.ccx)); let f = base::decl_cdecl_fn(self.ccx, "rust_eh_personality", fty, self.ccx.tcx().types.i32); *personality = Some(f); f } } } }; // The only landing pad clause will be 'cleanup' let llretval = build::LandingPad(pad_bcx, llretty, llpersonality, 1); // The landing pad block is a cleanup build::SetCleanup(pad_bcx, llretval); // We store the retval in a function-central alloca, so that calls to // Resume can find it. match self.personality.get() { Some(addr) => { build::Store(pad_bcx, llretval, addr); } None => { let addr = base::alloca(pad_bcx, common::val_ty(llretval), ""); self.personality.set(Some(addr)); build::Store(pad_bcx, llretval, addr); } } // Generate the cleanup block and branch to it. let cleanup_llbb = self.trans_cleanups_to_exit_scope(UnwindExit); build::Br(pad_bcx, cleanup_llbb, DebugLoc::None); return pad_bcx.llbb; } } impl<'blk, 'tcx> CleanupScope<'blk, 'tcx> { fn new(kind: CleanupScopeKind<'blk, 'tcx>, debug_loc: DebugLoc) -> CleanupScope<'blk, 'tcx> { CleanupScope { kind: kind, debug_loc: debug_loc, cleanups: vec!(), cached_early_exits: vec!(), cached_landing_pad: None, } } fn clear_cached_exits(&mut self) { self.cached_early_exits = vec!(); self.cached_landing_pad = None; } fn cached_early_exit(&self, label: EarlyExitLabel) -> Option { self.cached_early_exits.iter(). find(|e| e.label == label). map(|e| e.cleanup_block) } fn add_cached_early_exit(&mut self, label: EarlyExitLabel, blk: BasicBlockRef) { self.cached_early_exits.push( CachedEarlyExit { label: label, cleanup_block: blk }); } /// True if this scope has cleanups that need unwinding fn needs_invoke(&self) -> bool { self.cached_landing_pad.is_some() || self.cleanups.iter().any(|c| c.must_unwind()) } /// Returns a suitable name to use for the basic block that handles this cleanup scope fn block_name(&self, prefix: &str) -> String { match self.kind { CustomScopeKind => format!("{}_custom_", prefix), AstScopeKind(id) => format!("{}_ast_{}_", prefix, id), LoopScopeKind(id, _) => format!("{}_loop_{}_", prefix, id), } } pub fn drop_non_lifetime_clean(&mut self) { self.cleanups.retain(|c| c.is_lifetime_end()); } } impl<'blk, 'tcx> CleanupScopeKind<'blk, 'tcx> { fn is_temp(&self) -> bool { match *self { CustomScopeKind => true, LoopScopeKind(..) | AstScopeKind(..) => false, } } fn is_ast_with_id(&self, id: ast::NodeId) -> bool { match *self { CustomScopeKind | LoopScopeKind(..) => false, AstScopeKind(i) => i == id } } fn is_loop_with_id(&self, id: ast::NodeId) -> bool { match *self { CustomScopeKind | AstScopeKind(..) => false, LoopScopeKind(i, _) => i == id } } /// If this is a loop scope with id `id`, return the early exit block `exit`, else `None` fn early_exit_block(&self, id: ast::NodeId, exit: uint) -> Option { match *self { LoopScopeKind(i, ref exits) if id == i => Some(exits[exit].llbb), _ => None, } } } impl EarlyExitLabel { fn is_unwind(&self) -> bool { match *self { UnwindExit => true, _ => false } } } /////////////////////////////////////////////////////////////////////////// // Cleanup types #[derive(Copy)] pub struct DropValue<'tcx> { is_immediate: bool, must_unwind: bool, val: ValueRef, ty: Ty<'tcx>, zero: bool } impl<'tcx> Cleanup<'tcx> for DropValue<'tcx> { fn must_unwind(&self) -> bool { self.must_unwind } fn clean_on_unwind(&self) -> bool { self.must_unwind } fn is_lifetime_end(&self) -> bool { false } fn trans<'blk>(&self, bcx: Block<'blk, 'tcx>, debug_loc: DebugLoc) -> Block<'blk, 'tcx> { let bcx = if self.is_immediate { glue::drop_ty_immediate(bcx, self.val, self.ty, debug_loc) } else { glue::drop_ty(bcx, self.val, self.ty, debug_loc) }; if self.zero { base::zero_mem(bcx, self.val, self.ty); } bcx } } #[derive(Copy, Debug)] pub enum Heap { HeapExchange } #[derive(Copy)] pub struct FreeValue<'tcx> { ptr: ValueRef, heap: Heap, content_ty: Ty<'tcx> } impl<'tcx> Cleanup<'tcx> for FreeValue<'tcx> { fn must_unwind(&self) -> bool { true } fn clean_on_unwind(&self) -> bool { true } fn is_lifetime_end(&self) -> bool { false } fn trans<'blk>(&self, bcx: Block<'blk, 'tcx>, debug_loc: DebugLoc) -> Block<'blk, 'tcx> { match self.heap { HeapExchange => { glue::trans_exchange_free_ty(bcx, self.ptr, self.content_ty, debug_loc) } } } } #[derive(Copy)] pub struct FreeSlice { ptr: ValueRef, size: ValueRef, align: ValueRef, heap: Heap, } impl<'tcx> Cleanup<'tcx> for FreeSlice { fn must_unwind(&self) -> bool { true } fn clean_on_unwind(&self) -> bool { true } fn is_lifetime_end(&self) -> bool { false } fn trans<'blk>(&self, bcx: Block<'blk, 'tcx>, debug_loc: DebugLoc) -> Block<'blk, 'tcx> { match self.heap { HeapExchange => { glue::trans_exchange_free_dyn(bcx, self.ptr, self.size, self.align, debug_loc) } } } } #[derive(Copy)] pub struct LifetimeEnd { ptr: ValueRef, } impl<'tcx> Cleanup<'tcx> for LifetimeEnd { fn must_unwind(&self) -> bool { false } fn clean_on_unwind(&self) -> bool { true } fn is_lifetime_end(&self) -> bool { true } fn trans<'blk>(&self, bcx: Block<'blk, 'tcx>, debug_loc: DebugLoc) -> Block<'blk, 'tcx> { debug_loc.apply(bcx.fcx); base::call_lifetime_end(bcx, self.ptr); bcx } } pub fn temporary_scope(tcx: &ty::ctxt, id: ast::NodeId) -> ScopeId { match tcx.region_maps.temporary_scope(id) { Some(scope) => { let r = AstScope(scope.node_id()); debug!("temporary_scope({}) = {:?}", id, r); r } None => { tcx.sess.bug(&format!("no temporary scope available for expr {}", id)[]) } } } pub fn var_scope(tcx: &ty::ctxt, id: ast::NodeId) -> ScopeId { let r = AstScope(tcx.region_maps.var_scope(id).node_id()); debug!("var_scope({}) = {:?}", id, r); r } fn cleanup_is_suitable_for(c: &Cleanup, label: EarlyExitLabel) -> bool { !label.is_unwind() || c.clean_on_unwind() } /////////////////////////////////////////////////////////////////////////// // These traits just exist to put the methods into this file. pub trait CleanupMethods<'blk, 'tcx> { fn push_ast_cleanup_scope(&self, id: NodeIdAndSpan); fn push_loop_cleanup_scope(&self, id: ast::NodeId, exits: [Block<'blk, 'tcx>; EXIT_MAX]); fn push_custom_cleanup_scope(&self) -> CustomScopeIndex; fn push_custom_cleanup_scope_with_debug_loc(&self, debug_loc: NodeIdAndSpan) -> CustomScopeIndex; fn pop_and_trans_ast_cleanup_scope(&self, bcx: Block<'blk, 'tcx>, cleanup_scope: ast::NodeId) -> Block<'blk, 'tcx>; fn pop_loop_cleanup_scope(&self, cleanup_scope: ast::NodeId); fn pop_custom_cleanup_scope(&self, custom_scope: CustomScopeIndex); fn pop_and_trans_custom_cleanup_scope(&self, bcx: Block<'blk, 'tcx>, custom_scope: CustomScopeIndex) -> Block<'blk, 'tcx>; fn top_loop_scope(&self) -> ast::NodeId; fn normal_exit_block(&'blk self, cleanup_scope: ast::NodeId, exit: uint) -> BasicBlockRef; fn return_exit_block(&'blk self) -> BasicBlockRef; fn schedule_lifetime_end(&self, cleanup_scope: ScopeId, val: ValueRef); fn schedule_drop_mem(&self, cleanup_scope: ScopeId, val: ValueRef, ty: Ty<'tcx>); fn schedule_drop_and_zero_mem(&self, cleanup_scope: ScopeId, val: ValueRef, ty: Ty<'tcx>); fn schedule_drop_immediate(&self, cleanup_scope: ScopeId, val: ValueRef, ty: Ty<'tcx>); fn schedule_free_value(&self, cleanup_scope: ScopeId, val: ValueRef, heap: Heap, content_ty: Ty<'tcx>); fn schedule_free_slice(&self, cleanup_scope: ScopeId, val: ValueRef, size: ValueRef, align: ValueRef, heap: Heap); fn schedule_clean(&self, cleanup_scope: ScopeId, cleanup: CleanupObj<'tcx>); fn schedule_clean_in_ast_scope(&self, cleanup_scope: ast::NodeId, cleanup: CleanupObj<'tcx>); fn schedule_clean_in_custom_scope(&self, custom_scope: CustomScopeIndex, cleanup: CleanupObj<'tcx>); fn needs_invoke(&self) -> bool; fn get_landing_pad(&'blk self) -> BasicBlockRef; } trait CleanupHelperMethods<'blk, 'tcx> { fn top_ast_scope(&self) -> Option; fn top_nonempty_cleanup_scope(&self) -> Option; fn is_valid_to_pop_custom_scope(&self, custom_scope: CustomScopeIndex) -> bool; fn is_valid_custom_scope(&self, custom_scope: CustomScopeIndex) -> bool; fn trans_scope_cleanups(&self, bcx: Block<'blk, 'tcx>, scope: &CleanupScope<'blk, 'tcx>) -> Block<'blk, 'tcx>; fn trans_cleanups_to_exit_scope(&'blk self, label: EarlyExitLabel) -> BasicBlockRef; fn get_or_create_landing_pad(&'blk self) -> BasicBlockRef; fn scopes_len(&self) -> uint; fn push_scope(&self, scope: CleanupScope<'blk, 'tcx>); fn pop_scope(&self) -> CleanupScope<'blk, 'tcx>; fn top_scope(&self, f: F) -> R where F: FnOnce(&CleanupScope<'blk, 'tcx>) -> R; }