avoid the loop in unwinding stack popping
This commit is contained in:
parent
499eb5d831
commit
01c11f9fb5
5 changed files with 89 additions and 111 deletions
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue