Move around code in cleanup for a more logical ordering, and fix comments
This commit is contained in:
parent
c7f8b0cd81
commit
b10d89a096
1 changed files with 60 additions and 184 deletions
|
@ -11,108 +11,12 @@
|
||||||
//! ## The Cleanup module
|
//! ## The Cleanup module
|
||||||
//!
|
//!
|
||||||
//! The cleanup module tracks what values need to be cleaned up as scopes
|
//! 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
|
//! are exited, either via panic or just normal control flow.
|
||||||
//! 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.
|
//! 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
|
//! Typically, when a scope is finished, we generate the cleanup code. This
|
||||||
//! each of its cleanups at that time. This corresponds to a normal exit
|
//! corresponds to a normal exit from a block (for example, an expression
|
||||||
//! from a block (for example, an expression completing evaluation
|
//! completing evaluation successfully without panic).
|
||||||
//! 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.
|
|
||||||
|
|
||||||
use llvm::{BasicBlockRef, ValueRef};
|
use llvm::{BasicBlockRef, ValueRef};
|
||||||
use base::{self, Lifetime};
|
use base::{self, Lifetime};
|
||||||
|
@ -131,9 +35,17 @@ pub struct CleanupScope<'tcx> {
|
||||||
pub landing_pad: Option<BasicBlockRef>,
|
pub landing_pad: Option<BasicBlockRef>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct CustomScopeIndex {
|
pub struct DropValue<'tcx> {
|
||||||
index: usize
|
val: ValueRef,
|
||||||
|
ty: Ty<'tcx>,
|
||||||
|
skip_dtor: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'tcx> DropValue<'tcx> {
|
||||||
|
fn trans<'blk>(&self, funclet: Option<&'blk Funclet>, bcx: &BlockAndBuilder<'blk, 'tcx>) {
|
||||||
|
glue::call_drop_glue(bcx, self.val, self.ty, self.skip_dtor, funclet)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
@ -142,6 +54,44 @@ enum UnwindKind {
|
||||||
CleanupPad(ValueRef),
|
CleanupPad(ValueRef),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UnwindKind {
|
||||||
|
/// Generates a branch going from `bcx` to `to_llbb` where `self` is
|
||||||
|
/// the exit label attached to the start of `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, bcx: &BlockAndBuilder, to_llbb: BasicBlockRef) {
|
||||||
|
match *self {
|
||||||
|
UnwindKind::CleanupPad(pad) => {
|
||||||
|
bcx.cleanup_ret(pad, Some(to_llbb));
|
||||||
|
}
|
||||||
|
UnwindKind::LandingPad => {
|
||||||
|
bcx.br(to_llbb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_funclet(&self, bcx: &BlockAndBuilder) -> Option<Funclet> {
|
||||||
|
match *self {
|
||||||
|
UnwindKind::CleanupPad(_) => {
|
||||||
|
let pad = bcx.cleanup_pad(None, &[]);
|
||||||
|
Funclet::msvc(pad)
|
||||||
|
},
|
||||||
|
UnwindKind::LandingPad => Funclet::gnu(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for UnwindKind {
|
||||||
|
fn eq(&self, label: &UnwindKind) -> bool {
|
||||||
|
match (*self, *label) {
|
||||||
|
(UnwindKind::LandingPad, UnwindKind::LandingPad) |
|
||||||
|
(UnwindKind::CleanupPad(..), UnwindKind::CleanupPad(..)) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
impl<'blk, 'tcx> FunctionContext<'blk, 'tcx> {
|
impl<'blk, 'tcx> FunctionContext<'blk, 'tcx> {
|
||||||
pub fn trans_scope(
|
pub fn trans_scope(
|
||||||
&self,
|
&self,
|
||||||
|
@ -186,9 +136,7 @@ impl<'blk, 'tcx> FunctionContext<'blk, 'tcx> {
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("schedule_drop_adt_contents(val={:?}, ty={:?}) skip_dtor={}",
|
debug!("schedule_drop_adt_contents(val={:?}, ty={:?}) skip_dtor={}",
|
||||||
Value(val),
|
Value(val), ty, drop.skip_dtor);
|
||||||
ty,
|
|
||||||
drop.skip_dtor);
|
|
||||||
|
|
||||||
Some(CleanupScope::new(self, drop))
|
Some(CleanupScope::new(self, drop))
|
||||||
}
|
}
|
||||||
|
@ -259,27 +207,9 @@ impl<'tcx> CleanupScope<'tcx> {
|
||||||
UnwindKind::LandingPad
|
UnwindKind::LandingPad
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate the cleanup block and branch to it.
|
|
||||||
let cleanup_llbb = CleanupScope::trans_cleanups_to_exit_scope(fcx, val, drop_val);
|
|
||||||
val.branch(&mut pad_bcx, cleanup_llbb);
|
|
||||||
|
|
||||||
return pad_bcx.llbb();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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.
|
|
||||||
fn trans_cleanups_to_exit_scope<'a>(
|
|
||||||
fcx: &FunctionContext<'a, 'tcx>,
|
|
||||||
label: UnwindKind,
|
|
||||||
drop_val: &DropValue<'tcx>
|
|
||||||
) -> BasicBlockRef {
|
|
||||||
debug!("trans_cleanups_to_exit_scope label={:?}`", label);
|
|
||||||
|
|
||||||
// Generate a block that will resume unwinding to the calling function
|
// Generate a block that will resume unwinding to the calling function
|
||||||
let bcx = fcx.build_new_block("resume");
|
let bcx = fcx.build_new_block("resume");
|
||||||
match label {
|
match val {
|
||||||
UnwindKind::LandingPad => {
|
UnwindKind::LandingPad => {
|
||||||
let addr = fcx.landingpad_alloca.get().unwrap();
|
let addr = fcx.landingpad_alloca.get().unwrap();
|
||||||
let lp = bcx.load(addr);
|
let lp = bcx.load(addr);
|
||||||
|
@ -299,68 +229,14 @@ impl<'tcx> CleanupScope<'tcx> {
|
||||||
let mut cleanup = fcx.build_new_block("clean_custom_");
|
let mut cleanup = fcx.build_new_block("clean_custom_");
|
||||||
|
|
||||||
// Insert cleanup instructions into the cleanup block
|
// Insert cleanup instructions into the cleanup block
|
||||||
drop_val.trans(label.get_funclet(&cleanup).as_ref(), &cleanup);
|
drop_val.trans(val.get_funclet(&cleanup).as_ref(), &cleanup);
|
||||||
|
|
||||||
// Insert instruction into cleanup block to branch to the exit
|
// Insert instruction into cleanup block to branch to the exit
|
||||||
label.branch(&mut cleanup, bcx.llbb());
|
val.branch(&mut cleanup, bcx.llbb());
|
||||||
|
|
||||||
debug!("trans_cleanups_to_exit_scope: llbb={:?}", cleanup.llbb());
|
// Branch into the cleanup block
|
||||||
|
val.branch(&mut pad_bcx, cleanup.llbb());
|
||||||
|
|
||||||
cleanup.llbb()
|
return pad_bcx.llbb();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UnwindKind {
|
|
||||||
/// Generates a branch going from `bcx` to `to_llbb` where `self` is
|
|
||||||
/// the exit label attached to the start of `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, bcx: &BlockAndBuilder, to_llbb: BasicBlockRef) {
|
|
||||||
match *self {
|
|
||||||
UnwindKind::CleanupPad(pad) => {
|
|
||||||
bcx.cleanup_ret(pad, Some(to_llbb));
|
|
||||||
}
|
|
||||||
UnwindKind::LandingPad => {
|
|
||||||
bcx.br(to_llbb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_funclet(&self, bcx: &BlockAndBuilder) -> Option<Funclet> {
|
|
||||||
match *self {
|
|
||||||
UnwindKind::CleanupPad(_) => {
|
|
||||||
let pad = bcx.cleanup_pad(None, &[]);
|
|
||||||
Funclet::msvc(pad)
|
|
||||||
},
|
|
||||||
UnwindKind::LandingPad => Funclet::gnu(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for UnwindKind {
|
|
||||||
fn eq(&self, label: &UnwindKind) -> bool {
|
|
||||||
match (*self, *label) {
|
|
||||||
(UnwindKind::LandingPad, UnwindKind::LandingPad) |
|
|
||||||
(UnwindKind::CleanupPad(..), UnwindKind::CleanupPad(..)) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
// Cleanup types
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct DropValue<'tcx> {
|
|
||||||
val: ValueRef,
|
|
||||||
ty: Ty<'tcx>,
|
|
||||||
skip_dtor: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'tcx> DropValue<'tcx> {
|
|
||||||
fn trans<'blk>(&self, funclet: Option<&'blk Funclet>, bcx: &BlockAndBuilder<'blk, 'tcx>) {
|
|
||||||
glue::call_drop_glue(bcx, self.val, self.ty, self.skip_dtor, funclet)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue