| // 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 <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. |
| |
| //! ## 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 base; |
| use build; |
| use common; |
| use common::{Block, FunctionContext, NodeIdAndSpan, LandingPad}; |
| use datum::{Datum, Lvalue}; |
| use debuginfo::{DebugLoc, ToDebugLoc}; |
| use glue; |
| use middle::region; |
| use type_::Type; |
| use value::Value; |
| use rustc::ty::{Ty, TyCtxt}; |
| |
| use std::fmt; |
| use syntax::ast; |
| |
| 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<CleanupObj<'tcx>>, |
| |
| // The debug location any drop calls generated for this scope will be |
| // associated with. |
| debug_loc: DebugLoc, |
| |
| cached_early_exits: Vec<CachedEarlyExit>, |
| cached_landing_pad: Option<BasicBlockRef>, |
| } |
| |
| #[derive(Copy, Clone, Debug)] |
| pub struct CustomScopeIndex { |
| index: usize |
| } |
| |
| pub const EXIT_BREAK: usize = 0; |
| pub const EXIT_LOOP: usize = 1; |
| pub const EXIT_MAX: usize = 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) => { |
| write!(f, "LoopScopeKind({}, [", nid)?; |
| for blk in blks { |
| write!(f, "{:p}, ", blk)?; |
| } |
| write!(f, "])") |
| } |
| } |
| } |
| } |
| |
| #[derive(Copy, Clone, PartialEq, Debug)] |
| pub enum EarlyExitLabel { |
| UnwindExit(UnwindKind), |
| ReturnExit, |
| LoopExit(ast::NodeId, usize) |
| } |
| |
| #[derive(Copy, Clone, Debug)] |
| pub enum UnwindKind { |
| LandingPad, |
| CleanupPad(ValueRef), |
| } |
| |
| #[derive(Copy, Clone)] |
| pub struct CachedEarlyExit { |
| label: EarlyExitLabel, |
| cleanup_block: BasicBlockRef, |
| last_cleanup: usize, |
| } |
| |
| pub trait Cleanup<'tcx> { |
| fn must_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<Cleanup<'tcx>+'tcx>; |
| |
| #[derive(Copy, Clone, Debug)] |
| pub enum ScopeId { |
| AstScope(ast::NodeId), |
| CustomScope(CustomScopeIndex) |
| } |
| |
| #[derive(Copy, Clone, Debug)] |
| pub struct DropHint<K>(pub ast::NodeId, pub K); |
| |
| pub type DropHintDatum<'tcx> = DropHint<Datum<'tcx, Lvalue>>; |
| pub type DropHintValue = DropHint<ValueRef>; |
| |
| impl<K> DropHint<K> { |
| pub fn new(id: ast::NodeId, k: K) -> DropHint<K> { DropHint(id, k) } |
| } |
| |
| impl DropHint<ValueRef> { |
| pub fn value(&self) -> ValueRef { self.1 } |
| } |
| |
| pub trait DropHintMethods { |
| type ValueKind; |
| fn to_value(&self) -> Self::ValueKind; |
| } |
| impl<'tcx> DropHintMethods for DropHintDatum<'tcx> { |
| type ValueKind = DropHintValue; |
| fn to_value(&self) -> DropHintValue { DropHint(self.0, self.1.val) } |
| } |
| |
| 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(); |
| let region_maps = &self.ccx.tcx().region_maps; |
| if top_scope.is_some() { |
| assert!((region_maps |
| .opt_encl_scope(region_maps.node_extent(debug_loc.id)) |
| .map(|s|s.node_id(region_maps)) == top_scope) |
| || |
| (region_maps |
| .opt_encl_scope(region_maps.lookup_code_extent( |
| region::CodeExtentData::DestructionScope(debug_loc.id))) |
| .map(|s|s.node_id(region_maps)) == 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; |
| } |
| } |
| 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: usize) -> 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, Value(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>, |
| drop_hint: Option<DropHintDatum<'tcx>>) { |
| if !self.type_needs_drop(ty) { return; } |
| let drop_hint = drop_hint.map(|hint|hint.to_value()); |
| let drop = box DropValue { |
| is_immediate: false, |
| val: val, |
| ty: ty, |
| fill_on_drop: false, |
| skip_dtor: false, |
| drop_hint: drop_hint, |
| }; |
| |
| debug!("schedule_drop_mem({:?}, val={:?}, ty={:?}) fill_on_drop={} skip_dtor={}", |
| cleanup_scope, |
| Value(val), |
| ty, |
| drop.fill_on_drop, |
| drop.skip_dtor); |
| |
| self.schedule_clean(cleanup_scope, drop as CleanupObj); |
| } |
| |
| /// Schedules a (deep) drop and filling of `val`, which is a pointer to an instance of `ty` |
| fn schedule_drop_and_fill_mem(&self, |
| cleanup_scope: ScopeId, |
| val: ValueRef, |
| ty: Ty<'tcx>, |
| drop_hint: Option<DropHintDatum<'tcx>>) { |
| if !self.type_needs_drop(ty) { return; } |
| |
| let drop_hint = drop_hint.map(|datum|datum.to_value()); |
| let drop = box DropValue { |
| is_immediate: false, |
| val: val, |
| ty: ty, |
| fill_on_drop: true, |
| skip_dtor: false, |
| drop_hint: drop_hint, |
| }; |
| |
| debug!("schedule_drop_and_fill_mem({:?}, val={:?}, ty={:?}, |
| fill_on_drop={}, skip_dtor={}, has_drop_hint={})", |
| cleanup_scope, |
| Value(val), |
| ty, |
| drop.fill_on_drop, |
| drop.skip_dtor, |
| drop_hint.is_some()); |
| |
| self.schedule_clean(cleanup_scope, drop as CleanupObj); |
| } |
| |
| /// Issue #23611: Schedules a (deep) drop of the contents of |
| /// `val`, which is a pointer to an instance of struct/enum type |
| /// `ty`. The scheduled code handles extracting the discriminant |
| /// and dropping the contents associated with that variant |
| /// *without* executing any associated drop implementation. |
| fn schedule_drop_adt_contents(&self, |
| cleanup_scope: ScopeId, |
| val: ValueRef, |
| ty: Ty<'tcx>) { |
| // `if` below could be "!contents_needs_drop"; skipping drop |
| // is just an optimization, so sound to be conservative. |
| if !self.type_needs_drop(ty) { return; } |
| |
| let drop = box DropValue { |
| is_immediate: false, |
| val: val, |
| ty: ty, |
| fill_on_drop: false, |
| skip_dtor: true, |
| drop_hint: None, |
| }; |
| |
| debug!("schedule_drop_adt_contents({:?}, val={:?}, ty={:?}) fill_on_drop={} skip_dtor={}", |
| cleanup_scope, |
| Value(val), |
| ty, |
| drop.fill_on_drop, |
| drop.skip_dtor); |
| |
| 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 !self.type_needs_drop(ty) { return; } |
| let drop = Box::new(DropValue { |
| is_immediate: true, |
| val: val, |
| ty: ty, |
| fill_on_drop: false, |
| skip_dtor: false, |
| drop_hint: None, |
| }); |
| |
| debug!("schedule_drop_immediate({:?}, val={:?}, ty={:?}) fill_on_drop={} skip_dtor={}", |
| cleanup_scope, |
| Value(val), |
| ty, |
| drop.fill_on_drop, |
| drop.skip_dtor); |
| |
| 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, Value(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.cached_landing_pad = None; |
| return; |
| } else { |
| // will be adding a cleanup to some enclosing scope |
| scope.clear_cached_exits(); |
| } |
| } |
| |
| bug!("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.cached_landing_pad = None; |
| } |
| |
| /// 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 resume the exception that |
| /// caused the landing pad to be run. |
| 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<ast::NodeId> { |
| 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<usize> { |
| 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) -> usize { |
| 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<R, F>(&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!(); |
| let mut skip = 0; |
| |
| // 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(val) => { |
| // Generate a block that will resume unwinding to the |
| // calling function |
| let bcx = self.new_block("resume", None); |
| match val { |
| UnwindKind::LandingPad => { |
| let addr = self.landingpad_alloca.get() |
| .unwrap(); |
| let lp = build::Load(bcx, addr); |
| base::call_lifetime_end(bcx, addr); |
| base::trans_unwind_resume(bcx, lp); |
| } |
| UnwindKind::CleanupPad(_) => { |
| let pad = build::CleanupPad(bcx, None, &[]); |
| build::CleanupRet(bcx, pad, None); |
| } |
| } |
| prev_llbb = bcx.llbb; |
| break; |
| } |
| |
| ReturnExit => { |
| prev_llbb = self.get_llreturn(); |
| break |
| } |
| |
| LoopExit(id, _) => { |
| bug!("cannot exit from scope {}, not in scope", id); |
| } |
| } |
| } |
| |
| // Pop off the scope, since we may be generating |
| // unwinding code for it. |
| let top_scope = self.pop_scope(); |
| let cached_exit = top_scope.cached_early_exit(label); |
| popped_scopes.push(top_scope); |
| |
| // 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. |
| if let Some((exit, last_cleanup)) = cached_exit { |
| prev_llbb = exit; |
| skip = last_cleanup; |
| break; |
| } |
| |
| // 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. |
| let scope = popped_scopes.last().unwrap(); |
| match label { |
| UnwindExit(..) | ReturnExit => { } |
| LoopExit(id, exit) => { |
| if let Some(exit) = scope.kind.early_exit_block(id, exit) { |
| prev_llbb = exit; |
| break |
| } |
| } |
| } |
| } |
| |
| 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 let Some(mut scope) = popped_scopes.pop() { |
| if !scope.cleanups.is_empty() { |
| let name = scope.block_name("clean"); |
| debug!("generating cleanups for {}", name); |
| |
| let bcx_in = self.new_block(&name[..], None); |
| let exit_label = label.start(bcx_in); |
| let mut bcx_out = bcx_in; |
| let len = scope.cleanups.len(); |
| for cleanup in scope.cleanups.iter().rev().take(len - skip) { |
| bcx_out = cleanup.trans(bcx_out, scope.debug_loc); |
| } |
| skip = 0; |
| exit_label.branch(bcx_out, prev_llbb); |
| prev_llbb = bcx_in.llbb; |
| |
| scope.add_cached_early_exit(exit_label, prev_llbb, len); |
| } |
| 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(&name[..], None); |
| last_scope.cached_landing_pad = Some(pad_bcx.llbb); |
| } |
| } |
| }; |
| |
| let llpersonality = pad_bcx.fcx.eh_personality(); |
| |
| let val = if base::wants_msvc_seh(self.ccx.sess()) { |
| // A cleanup pad requires a personality function to be specified, so |
| // we do that here explicitly (happens implicitly below through |
| // creation of the landingpad instruction). We then create a |
| // cleanuppad instruction which has no filters to run cleanup on all |
| // exceptions. |
| build::SetPersonalityFn(pad_bcx, llpersonality); |
| let llretval = build::CleanupPad(pad_bcx, None, &[]); |
| UnwindKind::CleanupPad(llretval) |
| } else { |
| // 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 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); |
| |
| let addr = match self.landingpad_alloca.get() { |
| Some(addr) => addr, |
| None => { |
| let addr = base::alloca(pad_bcx, common::val_ty(llretval), |
| ""); |
| base::call_lifetime_start(pad_bcx, addr); |
| self.landingpad_alloca.set(Some(addr)); |
| addr |
| } |
| }; |
| build::Store(pad_bcx, llretval, addr); |
| UnwindKind::LandingPad |
| }; |
| |
| // Generate the cleanup block and branch to it. |
| let label = UnwindExit(val); |
| let cleanup_llbb = self.trans_cleanups_to_exit_scope(label); |
| label.branch(pad_bcx, cleanup_llbb); |
| |
| 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<(BasicBlockRef, usize)> { |
| self.cached_early_exits.iter().rev(). |
| find(|e| e.label == label). |
| map(|e| (e.cleanup_block, e.last_cleanup)) |
| } |
| |
| fn add_cached_early_exit(&mut self, |
| label: EarlyExitLabel, |
| blk: BasicBlockRef, |
| last_cleanup: usize) { |
| self.cached_early_exits.push( |
| CachedEarlyExit { label: label, |
| cleanup_block: blk, |
| last_cleanup: last_cleanup}); |
| } |
| |
| /// 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), |
| } |
| } |
| |
| /// Manipulate cleanup scope for call arguments. Conceptually, each |
| /// argument to a call is an lvalue, and performing the call moves each |
| /// of the arguments into a new rvalue (which gets cleaned up by the |
| /// callee). As an optimization, instead of actually performing all of |
| /// those moves, trans just manipulates the cleanup scope to obtain the |
| /// same effect. |
| pub fn drop_non_lifetime_clean(&mut self) { |
| self.cleanups.retain(|c| c.is_lifetime_end()); |
| self.clear_cached_exits(); |
| } |
| } |
| |
| 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: usize) -> Option<BasicBlockRef> { |
| match *self { |
| LoopScopeKind(i, ref exits) if id == i => Some(exits[exit].llbb), |
| _ => None, |
| } |
| } |
| } |
| |
| impl EarlyExitLabel { |
| /// Generates a branch going from `from_bcx` to `to_llbb` where `self` is |
| /// the exit label attached to the start of `from_bcx`. |
| /// |
| /// Transitions from an exit label to other exit labels depend on the type |
| /// of label. For example with MSVC exceptions unwind exit labels will use |
| /// the `cleanupret` instruction instead of the `br` instruction. |
| fn branch(&self, from_bcx: Block, to_llbb: BasicBlockRef) { |
| if let UnwindExit(UnwindKind::CleanupPad(pad)) = *self { |
| build::CleanupRet(from_bcx, pad, Some(to_llbb)); |
| } else { |
| build::Br(from_bcx, to_llbb, DebugLoc::None); |
| } |
| } |
| |
| /// Generates the necessary instructions at the start of `bcx` to prepare |
| /// for the same kind of early exit label that `self` is. |
| /// |
| /// This function will appropriately configure `bcx` based on the kind of |
| /// label this is. For UnwindExit labels, the `lpad` field of the block will |
| /// be set to `Some`, and for MSVC exceptions this function will generate a |
| /// `cleanuppad` instruction at the start of the block so it may be jumped |
| /// to in the future (e.g. so this block can be cached as an early exit). |
| /// |
| /// Returns a new label which will can be used to cache `bcx` in the list of |
| /// early exits. |
| fn start(&self, bcx: Block) -> EarlyExitLabel { |
| match *self { |
| UnwindExit(UnwindKind::CleanupPad(..)) => { |
| let pad = build::CleanupPad(bcx, None, &[]); |
| bcx.lpad.set(Some(bcx.fcx.lpad_arena.alloc(LandingPad::msvc(pad)))); |
| UnwindExit(UnwindKind::CleanupPad(pad)) |
| } |
| UnwindExit(UnwindKind::LandingPad) => { |
| bcx.lpad.set(Some(bcx.fcx.lpad_arena.alloc(LandingPad::gnu()))); |
| *self |
| } |
| label => label, |
| } |
| } |
| } |
| |
| impl PartialEq for UnwindKind { |
| fn eq(&self, val: &UnwindKind) -> bool { |
| match (*self, *val) { |
| (UnwindKind::LandingPad, UnwindKind::LandingPad) | |
| (UnwindKind::CleanupPad(..), UnwindKind::CleanupPad(..)) => true, |
| _ => false, |
| } |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////// |
| // Cleanup types |
| |
| #[derive(Copy, Clone)] |
| pub struct DropValue<'tcx> { |
| is_immediate: bool, |
| val: ValueRef, |
| ty: Ty<'tcx>, |
| fill_on_drop: bool, |
| skip_dtor: bool, |
| drop_hint: Option<DropHintValue>, |
| } |
| |
| impl<'tcx> Cleanup<'tcx> for DropValue<'tcx> { |
| fn must_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> { |
| let skip_dtor = self.skip_dtor; |
| let _icx = if skip_dtor { |
| base::push_ctxt("<DropValue as Cleanup>::trans skip_dtor=true") |
| } else { |
| base::push_ctxt("<DropValue as Cleanup>::trans skip_dtor=false") |
| }; |
| let bcx = if self.is_immediate { |
| glue::drop_ty_immediate(bcx, self.val, self.ty, debug_loc, self.skip_dtor) |
| } else { |
| glue::drop_ty_core(bcx, self.val, self.ty, debug_loc, self.skip_dtor, self.drop_hint) |
| }; |
| if self.fill_on_drop { |
| base::drop_done_fill_mem(bcx, self.val, self.ty); |
| } |
| bcx |
| } |
| } |
| |
| #[derive(Copy, Clone, Debug)] |
| pub enum Heap { |
| HeapExchange |
| } |
| |
| #[derive(Copy, Clone)] |
| 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 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, Clone)] |
| pub struct LifetimeEnd { |
| ptr: ValueRef, |
| } |
| |
| impl<'tcx> Cleanup<'tcx> for LifetimeEnd { |
| fn must_unwind(&self) -> bool { |
| false |
| } |
| |
| 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: TyCtxt, |
| id: ast::NodeId) |
| -> ScopeId { |
| match tcx.region_maps.temporary_scope(id) { |
| Some(scope) => { |
| let r = AstScope(scope.node_id(&tcx.region_maps)); |
| debug!("temporary_scope({}) = {:?}", id, r); |
| r |
| } |
| None => { |
| bug!("no temporary scope available for expr {}", id) |
| } |
| } |
| } |
| |
| pub fn var_scope(tcx: TyCtxt, |
| id: ast::NodeId) |
| -> ScopeId { |
| let r = AstScope(tcx.region_maps.var_scope(id).node_id(&tcx.region_maps)); |
| debug!("var_scope({}) = {:?}", id, r); |
| r |
| } |
| |
| /////////////////////////////////////////////////////////////////////////// |
| // 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: usize) -> 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>, |
| drop_hint: Option<DropHintDatum<'tcx>>); |
| fn schedule_drop_and_fill_mem(&self, |
| cleanup_scope: ScopeId, |
| val: ValueRef, |
| ty: Ty<'tcx>, |
| drop_hint: Option<DropHintDatum<'tcx>>); |
| fn schedule_drop_adt_contents(&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_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<ast::NodeId>; |
| fn top_nonempty_cleanup_scope(&self) -> Option<usize>; |
| 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) -> usize; |
| fn push_scope(&self, scope: CleanupScope<'blk, 'tcx>); |
| fn pop_scope(&self) -> CleanupScope<'blk, 'tcx>; |
| fn top_scope<R, F>(&self, f: F) -> R where F: FnOnce(&CleanupScope<'blk, 'tcx>) -> R; |
| } |