1
Fork 0

avoid the loop in unwinding stack popping

This commit is contained in:
Ralf Jung 2019-10-20 18:51:25 +02:00 committed by Aaron Hill
parent 499eb5d831
commit 01c11f9fb5
No known key found for this signature in database
GPG key ID: B4087E510E98B164
5 changed files with 89 additions and 111 deletions

View file

@ -60,6 +60,9 @@ pub struct Frame<'mir, 'tcx, Tag=(), Extra=()> {
/// The span of the call site. /// The span of the call site.
pub span: source_map::Span, pub span: source_map::Span,
/// Extra data for the machine.
pub extra: Extra,
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Return place and locals // Return place and locals
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -82,13 +85,12 @@ pub struct Frame<'mir, 'tcx, Tag=(), Extra=()> {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// The block that is currently executed (or will be executed after the above call stacks /// The block that is currently executed (or will be executed after the above call stacks
/// return). /// return).
pub block: mir::BasicBlock, /// If this is `None`, we are unwinding and this function doesn't need any clean-up.
/// Just continue the same as with
pub block: Option<mir::BasicBlock>,
/// The index of the currently evaluated statement. /// The index of the currently evaluated statement.
pub stmt: usize, pub stmt: usize,
/// Extra data for the machine.
pub extra: Extra,
} }
#[derive(Clone, Eq, PartialEq, Debug)] // Miri debug-prints these #[derive(Clone, Eq, PartialEq, Debug)] // Miri debug-prints these
@ -491,7 +493,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let extra = M::stack_push(self)?; let extra = M::stack_push(self)?;
self.stack.push(Frame { self.stack.push(Frame {
body, body,
block: mir::START_BLOCK, block: Some(mir::START_BLOCK),
return_to_block, return_to_block,
return_place, return_place,
// empty local array, we fill it in below, after we are inside the stack frame and // empty local array, we fill it in below, after we are inside the stack frame and
@ -549,51 +551,75 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
} }
} }
pub(super) fn pop_stack_frame_internal( pub(super) fn pop_stack_frame(
&mut self, &mut self,
unwinding: bool unwinding: bool
) -> InterpResult<'tcx, (StackPopCleanup, StackPopInfo)> { ) -> InterpResult<'tcx> {
info!("LEAVING({}) {} (unwinding = {})", info!("LEAVING({}) {} (unwinding = {})",
self.cur_frame(), self.frame().instance, unwinding); self.cur_frame(), self.frame().instance, unwinding);
// Sanity check `unwinding`.
assert_eq!(
unwinding,
match self.frame().block {
None => true,
Some(block) => self.body().basic_blocks()[block].is_cleanup
}
);
::log_settings::settings().indentation -= 1; ::log_settings::settings().indentation -= 1;
let frame = self.stack.pop().expect( let frame = self.stack.pop().expect(
"tried to pop a stack frame, but there were none", "tried to pop a stack frame, but there were none",
); );
let stack_pop_info = M::stack_pop(self, frame.extra)?; let stack_pop_info = M::stack_pop(self, frame.extra)?;
match (unwinding, stack_pop_info) {
// Abort early if we do not want to clean up: We also avoid validation in that case, (true, StackPopInfo::StartUnwinding) =>
// because this is CTFE and the final value will be thoroughly validated anyway. bug!("Attempted to start unwinding while already unwinding!"),
match frame.return_to_block { (false, StackPopInfo::StopUnwinding) =>
StackPopCleanup::Goto{ .. } => {}, bug!("Attempted to stop unwinding while there is no unwinding!"),
StackPopCleanup::None { cleanup, .. } => { _ => {}
assert!(!unwinding, "Encountered StackPopCleanup::None while unwinding");
if !cleanup {
assert!(self.stack.is_empty(), "only the topmost frame should ever be leaked");
// Leak the locals, skip validation.
return Ok((frame.return_to_block, stack_pop_info));
}
}
} }
// Deallocate all locals that are backed by an allocation.
// Usually we want to clean up (deallocate locals), but in a few rare cases we don't.
// In that case, we return early. We also avoid validation in that case,
// because this is CTFE and the final value will be thoroughly validated anyway.
let cleanup = unwinding || match frame.return_to_block {
StackPopCleanup::Goto{ .. } => true,
StackPopCleanup::None { cleanup, .. } => {
cleanup
}
};
if !cleanup {
assert!(self.stack.is_empty(), "only the topmost frame should ever be leaked");
// Leak the locals, skip validation.
return Ok(());
}
// Cleanup: deallocate all locals that are backed by an allocation.
for local in frame.locals { for local in frame.locals {
self.deallocate_local(local.value)?; self.deallocate_local(local.value)?;
} }
// If we're popping frames due to unwinding, and we didn't just exit // Now where do we jump next?
// unwinding, we skip a bunch of validation and cleanup logic (including
// jumping to the regular return block specified in the StackPopCleanup) // Determine if we leave this function normally or via unwinding.
let cur_unwinding = unwinding && stack_pop_info != StackPopInfo::StopUnwinding; let cur_unwinding = unwinding && stack_pop_info != StackPopInfo::StopUnwinding;
trace!("StackPopCleanup: {:?} StackPopInfo: {:?} cur_unwinding = {:?}",
info!("StackPopCleanup: {:?} StackPopInfo: {:?} cur_unwinding = {:?}",
frame.return_to_block, stack_pop_info, cur_unwinding); frame.return_to_block, stack_pop_info, cur_unwinding);
if cur_unwinding {
// Follow the unwind edge.
// When we're popping a stack frame for unwinding purposes, match frame.return_to_block {
// we don't care at all about returning-related stuff (i.e. return_place StackPopCleanup::Goto { unwind, .. } => {
// and return_to_block), because we're not performing a return from this frame. let next_frame = self.frame_mut();
if !cur_unwinding { // If `unwind` is `None`, we'll leave that function immediately again.
next_frame.block = unwind;
next_frame.stmt = 0;
},
StackPopCleanup::None { .. } =>
bug!("Encountered StackPopCleanup::None while unwinding"),
}
} else {
// Follow the normal return edge.
// Validate the return value. Do this after deallocating so that we catch dangling // Validate the return value. Do this after deallocating so that we catch dangling
// references. // references.
if let Some(return_place) = frame.return_place { if let Some(return_place) = frame.return_place {
@ -625,70 +651,9 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
} }
} }
Ok((frame.return_to_block, stack_pop_info))
}
pub(super) fn pop_stack_frame(&mut self, unwinding: bool) -> InterpResult<'tcx> {
let (mut cleanup, mut stack_pop_info) = self.pop_stack_frame_internal(unwinding)?;
// There are two cases where we want to unwind the stack:
// * The caller explicitly told us (i.e. we hit a Resume terminator)
// * The machine indicated that we've just started unwinding (i.e.
// a panic has just occured)
if unwinding || stack_pop_info == StackPopInfo::StartUnwinding {
trace!("unwinding: starting stack unwind...");
// Overwrite our current stack_pop_info, so that the check
// below doesn't fail.
stack_pop_info = StackPopInfo::Normal;
// There are three posible ways that we can exit the loop:
// 1) We find an unwind block - we jump to it to allow cleanup
// to occur for that frame
// 2) pop_stack_frame_internal reports that we're no longer unwinding
// - this means that the panic has been caught, and that execution
// should continue as normal
// 3) We pop all of our frames off the stack - this should never happen.
while !self.stack.is_empty() {
match stack_pop_info {
// We tried to start unwinding while we were already
// unwinding. Note that this **is not** the same thing
// as a double panic, which will be intercepted by
// libcore/libstd before we actually attempt to unwind.
StackPopInfo::StartUnwinding => {
throw_ub_format!("Attempted to start unwinding while already unwinding!");
},
StackPopInfo::StopUnwinding => {
trace!("unwinding: no longer unwinding!");
break;
}
StackPopInfo::Normal => {}
}
match cleanup {
StackPopCleanup::Goto { unwind, .. } if unwind.is_some() => {
info!("unwind: found cleanup block {:?}", unwind);
self.goto_block(unwind)?;
break;
},
_ => {}
}
info!("unwinding: popping frame!");
let res = self.pop_stack_frame_internal(true)?;
cleanup = res.0;
stack_pop_info = res.1;
}
if self.stack.is_empty() {
// We should never get here:
// The 'start_fn' lang item should always install a panic handler
throw_ub!(Unreachable);
}
}
if self.stack.len() > 0 { if self.stack.len() > 0 {
info!("CONTINUING({}) {}", self.cur_frame(), self.frame().instance); info!("CONTINUING({}) {} (unwinding = {})",
self.cur_frame(), self.frame().instance, cur_unwinding);
} }
Ok(()) Ok(())
@ -833,16 +798,20 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
} else { } else {
last_span = Some(span); last_span = Some(span);
} }
let block = &body.basic_blocks()[block];
let source_info = if stmt < block.statements.len() { let lint_root = block.and_then(|block| {
block.statements[stmt].source_info let block = &body.basic_blocks()[block];
} else { let source_info = if stmt < block.statements.len() {
block.terminator().source_info block.statements[stmt].source_info
}; } else {
let lint_root = match body.source_scope_local_data { block.terminator().source_info
mir::ClearCrossCrate::Set(ref ivs) => Some(ivs[source_info.scope].lint_root), };
mir::ClearCrossCrate::Clear => None, match body.source_scope_local_data {
}; mir::ClearCrossCrate::Set(ref ivs) => Some(ivs[source_info.scope].lint_root),
mir::ClearCrossCrate::Clear => None,
}
});
frames.push(FrameInfo { call_site: span, instance, lint_root }); frames.push(FrameInfo { call_site: span, instance, lint_root });
} }
trace!("generate stacktrace: {:#?}, {:?}", frames, explicit_span); trace!("generate stacktrace: {:#?}, {:?}", frames, explicit_span);

View file

@ -18,7 +18,7 @@ use super::{
/// Data returned by Machine::stack_pop, /// Data returned by Machine::stack_pop,
/// to provide further control over the popping of the stack frame /// to provide further control over the popping of the stack frame
#[derive(Eq, PartialEq, Debug)] #[derive(Eq, PartialEq, Debug, Copy, Clone)]
pub enum StackPopInfo { pub enum StackPopInfo {
/// Indicates that we have just started unwinding /// Indicates that we have just started unwinding
/// as the result of panic /// as the result of panic

View file

@ -326,7 +326,7 @@ struct FrameSnapshot<'a, 'tcx> {
return_to_block: &'a StackPopCleanup, return_to_block: &'a StackPopCleanup,
return_place: Option<Place<(), AllocIdSnapshot<'a>>>, return_place: Option<Place<(), AllocIdSnapshot<'a>>>,
locals: IndexVec<mir::Local, LocalValue<(), AllocIdSnapshot<'a>>>, locals: IndexVec<mir::Local, LocalValue<(), AllocIdSnapshot<'a>>>,
block: &'a mir::BasicBlock, block: Option<mir::BasicBlock>,
stmt: usize, stmt: usize,
} }
@ -364,7 +364,7 @@ impl<'a, 'mir, 'tcx, Ctx> Snapshot<'a, Ctx> for &'a Frame<'mir, 'tcx>
instance: *instance, instance: *instance,
span: *span, span: *span,
return_to_block, return_to_block,
block, block: *block,
stmt: *stmt, stmt: *stmt,
return_place: return_place.map(|r| r.snapshot(ctx)), return_place: return_place.map(|r| r.snapshot(ctx)),
locals: locals.iter().map(|local| local.snapshot(ctx)).collect(), locals: locals.iter().map(|local| local.snapshot(ctx)).collect(),

View file

@ -49,7 +49,16 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
return Ok(false); return Ok(false);
} }
let block = self.frame().block; let block = match self.frame().block {
Some(block) => block,
None => {
// We are unwinding and this fn has no cleanup code.
// Just go on unwinding.
trace!("unwinding: skipping frame");
self.pop_stack_frame(/* unwinding */ true)?;
return Ok(true)
}
};
let stmt_id = self.frame().stmt; let stmt_id = self.frame().stmt;
let body = self.body(); let body = self.body();
let basic_block = &body.basic_blocks()[block]; let basic_block = &body.basic_blocks()[block];

View file

@ -15,7 +15,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
#[inline] #[inline]
pub fn goto_block(&mut self, target: Option<mir::BasicBlock>) -> InterpResult<'tcx> { pub fn goto_block(&mut self, target: Option<mir::BasicBlock>) -> InterpResult<'tcx> {
if let Some(target) = target { if let Some(target) = target {
self.frame_mut().block = target; self.frame_mut().block = Some(target);
self.frame_mut().stmt = 0; self.frame_mut().stmt = 0;
Ok(()) Ok(())
} else { } else {