Skip to content

Commit 62bbf07

Browse files
committed
Auto merge of #60026 - Aaron1011:feature/miri-unwind, r=RalfJung,oli-obk
Add hooks for Miri panic unwinding This commits adds in some additional hooks to allow Miri to properly handle panic unwinding. None of this should have any impact on CTFE mode This supports rust-lang/miri#693
2 parents 5dda3ee + b4545a4 commit 62bbf07

File tree

16 files changed

+305
-112
lines changed

16 files changed

+305
-112
lines changed

src/libcore/intrinsics.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1348,6 +1348,11 @@ extern "rust-intrinsic" {
13481348
/// See documentation of `<*const T>::offset_from` for details.
13491349
#[cfg(not(bootstrap))]
13501350
pub fn ptr_offset_from<T>(ptr: *const T, base: *const T) -> isize;
1351+
1352+
/// Internal hook used by Miri to implement unwinding.
1353+
/// Perma-unstable: do not use
1354+
#[cfg(not(bootstrap))]
1355+
pub fn miri_start_panic(data: *mut (dyn crate::any::Any + crate::marker::Send)) -> !;
13511356
}
13521357

13531358
// Some functions are defined here because they accidentally got made

src/libpanic_unwind/lib.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ use core::raw;
3636
use core::panic::BoxMeUp;
3737

3838
cfg_if::cfg_if! {
39-
if #[cfg(target_os = "emscripten")] {
39+
if #[cfg(miri)] {
40+
#[path = "miri.rs"]
41+
mod imp;
42+
} else if #[cfg(target_os = "emscripten")] {
4043
#[path = "emcc.rs"]
4144
mod imp;
4245
} else if #[cfg(target_arch = "wasm32")] {

src/libpanic_unwind/miri.rs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use core::any::Any;
2+
use alloc::boxed::Box;
3+
4+
pub fn payload() -> *mut u8 {
5+
core::ptr::null_mut()
6+
}
7+
8+
pub unsafe fn panic(data: Box<dyn Any + Send>) -> ! {
9+
core::intrinsics::miri_start_panic(Box::into_raw(data))
10+
}
11+
12+
pub unsafe fn cleanup(ptr: *mut u8) -> Box<dyn Any + Send> {
13+
Box::from_raw(ptr)
14+
}
15+
16+
17+
// This is required by the compiler to exist (e.g., it's a lang item),
18+
// but is never used by Miri. Therefore, we just use a stub here
19+
#[lang = "eh_personality"]
20+
#[cfg(not(test))]
21+
fn rust_eh_personality() {
22+
unsafe { core::intrinsics::abort() }
23+
}

src/librustc_codegen_ssa/mir/block.rs

+15
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,21 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
528528
_ => FnAbi::new(&bx, sig, &extra_args)
529529
};
530530

531+
// This should never be reachable at runtime:
532+
// We should only emit a call to this intrinsic in #[cfg(miri)] mode,
533+
// which means that we will never actually use the generate object files
534+
// (we will just be interpreting the MIR)
535+
//
536+
// Note that we still need to be able to codegen *something* for this intrisnic:
537+
// Miri currently uses Xargo to build a special libstd. As a side effect,
538+
// we generate normal object files for libstd - while these are never used,
539+
// we still need to be able to build them.
540+
if intrinsic == Some("miri_start_panic") {
541+
bx.abort();
542+
bx.unreachable();
543+
return;
544+
}
545+
531546
// Emit a panic or a no-op for `panic_if_uninhabited`.
532547
if intrinsic == Some("panic_if_uninhabited") {
533548
let ty = instance.unwrap().substs.type_at(0);

src/librustc_mir/const_eval.rs

+5-8
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
325325
args: &[OpTy<'tcx>],
326326
dest: Option<PlaceTy<'tcx>>,
327327
ret: Option<mir::BasicBlock>,
328+
_unwind: Option<mir::BasicBlock> // unwinding is not supported in consts
328329
) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>> {
329330
debug!("eval_fn_call: {:?}", instance);
330331
// Only check non-glue functions
@@ -336,7 +337,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
336337
// Some functions we support even if they are non-const -- but avoid testing
337338
// that for const fn! We certainly do *not* want to actually call the fn
338339
// though, so be sure we return here.
339-
return if ecx.hook_fn(instance, args, dest)? {
340+
return if ecx.hook_panic_fn(instance, args, dest)? {
340341
ecx.goto_block(ret)?; // fully evaluated and done
341342
Ok(None)
342343
} else {
@@ -374,7 +375,9 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
374375
span: Span,
375376
instance: ty::Instance<'tcx>,
376377
args: &[OpTy<'tcx>],
377-
dest: PlaceTy<'tcx>,
378+
dest: Option<PlaceTy<'tcx>>,
379+
_ret: Option<mir::BasicBlock>,
380+
_unwind: Option<mir::BasicBlock>
378381
) -> InterpResult<'tcx> {
379382
if ecx.emulate_intrinsic(span, instance, args, dest)? {
380383
return Ok(());
@@ -469,12 +472,6 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
469472
fn stack_push(_ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
470473
Ok(())
471474
}
472-
473-
/// Called immediately before a stack frame gets popped.
474-
#[inline(always)]
475-
fn stack_pop(_ecx: &mut InterpCx<'mir, 'tcx, Self>, _extra: ()) -> InterpResult<'tcx> {
476-
Ok(())
477-
}
478475
}
479476

480477
/// Extracts a field of a (variant of a) const.

src/librustc_mir/interpret/eval_context.rs

+123-57
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use rustc_data_structures::fx::FxHashMap;
2121

2222
use super::{
2323
Immediate, Operand, MemPlace, MPlaceTy, Place, PlaceTy, ScalarMaybeUndef,
24-
Memory, Machine
24+
Memory, Machine, StackPopInfo
2525
};
2626

2727
pub struct InterpCx<'mir, 'tcx, M: Machine<'mir, 'tcx>> {
@@ -60,6 +60,9 @@ pub struct Frame<'mir, 'tcx, Tag=(), Extra=()> {
6060
/// The span of the call site.
6161
pub span: source_map::Span,
6262

63+
/// Extra data for the machine.
64+
pub extra: Extra,
65+
6366
////////////////////////////////////////////////////////////////////////////////
6467
// Return place and locals
6568
////////////////////////////////////////////////////////////////////////////////
@@ -82,21 +85,22 @@ pub struct Frame<'mir, 'tcx, Tag=(), Extra=()> {
8285
////////////////////////////////////////////////////////////////////////////////
8386
/// The block that is currently executed (or will be executed after the above call stacks
8487
/// return).
85-
pub block: mir::BasicBlock,
88+
/// If this is `None`, we are unwinding and this function doesn't need any clean-up.
89+
/// Just continue the same as with `Resume`.
90+
pub block: Option<mir::BasicBlock>,
8691

8792
/// The index of the currently evaluated statement.
8893
pub stmt: usize,
89-
90-
/// Extra data for the machine.
91-
pub extra: Extra,
9294
}
9395

9496
#[derive(Clone, Eq, PartialEq, Debug)] // Miri debug-prints these
9597
pub enum StackPopCleanup {
9698
/// Jump to the next block in the caller, or cause UB if None (that's a function
9799
/// that may never return). Also store layout of return place so
98100
/// we can validate it at that layout.
99-
Goto(Option<mir::BasicBlock>),
101+
/// `ret` stores the block we jump to on a normal return, while 'unwind'
102+
/// stores the block used for cleanup during unwinding
103+
Goto { ret: Option<mir::BasicBlock>, unwind: Option<mir::BasicBlock> },
100104
/// Just do nohing: Used by Main and for the box_alloc hook in miri.
101105
/// `cleanup` says whether locals are deallocated. Static computation
102106
/// wants them leaked to intern what they need (and just throw away
@@ -489,7 +493,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
489493
let extra = M::stack_push(self)?;
490494
self.stack.push(Frame {
491495
body,
492-
block: mir::START_BLOCK,
496+
block: Some(mir::START_BLOCK),
493497
return_to_block,
494498
return_place,
495499
// empty local array, we fill it in below, after we are inside the stack frame and
@@ -547,60 +551,118 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
547551
}
548552
}
549553

550-
pub(super) fn pop_stack_frame(&mut self) -> InterpResult<'tcx> {
551-
info!("LEAVING({}) {}", self.cur_frame(), self.frame().instance);
554+
/// Pops the current frame from the stack, deallocating the
555+
/// memory for allocated locals.
556+
///
557+
/// If `unwinding` is `false`, then we are performing a normal return
558+
/// from a function. In this case, we jump back into the frame of the caller,
559+
/// and continue execution as normal.
560+
///
561+
/// If `unwinding` is `true`, then we are in the middle of a panic,
562+
/// and need to unwind this frame. In this case, we jump to the
563+
/// `cleanup` block for the function, which is responsible for running
564+
/// `Drop` impls for any locals that have been initialized at this point.
565+
/// The cleanup block ends with a special `Resume` terminator, which will
566+
/// cause us to continue unwinding.
567+
pub(super) fn pop_stack_frame(
568+
&mut self,
569+
unwinding: bool
570+
) -> InterpResult<'tcx> {
571+
info!("LEAVING({}) {} (unwinding = {})",
572+
self.cur_frame(), self.frame().instance, unwinding);
573+
574+
// Sanity check `unwinding`.
575+
assert_eq!(
576+
unwinding,
577+
match self.frame().block {
578+
None => true,
579+
Some(block) => self.body().basic_blocks()[block].is_cleanup
580+
}
581+
);
582+
552583
::log_settings::settings().indentation -= 1;
553584
let frame = self.stack.pop().expect(
554585
"tried to pop a stack frame, but there were none",
555586
);
556-
M::stack_pop(self, frame.extra)?;
557-
// Abort early if we do not want to clean up: We also avoid validation in that case,
587+
let stack_pop_info = M::stack_pop(self, frame.extra, unwinding)?;
588+
if let (false, StackPopInfo::StopUnwinding) = (unwinding, stack_pop_info) {
589+
bug!("Attempted to stop unwinding while there is no unwinding!");
590+
}
591+
592+
// Now where do we jump next?
593+
594+
// Determine if we leave this function normally or via unwinding.
595+
let cur_unwinding = if let StackPopInfo::StopUnwinding = stack_pop_info {
596+
false
597+
} else {
598+
unwinding
599+
};
600+
601+
// Usually we want to clean up (deallocate locals), but in a few rare cases we don't.
602+
// In that case, we return early. We also avoid validation in that case,
558603
// because this is CTFE and the final value will be thoroughly validated anyway.
559-
match frame.return_to_block {
560-
StackPopCleanup::Goto(_) => {},
561-
StackPopCleanup::None { cleanup } => {
562-
if !cleanup {
563-
assert!(self.stack.is_empty(), "only the topmost frame should ever be leaked");
564-
// Leak the locals, skip validation.
565-
return Ok(());
566-
}
567-
}
604+
let (cleanup, next_block) = match frame.return_to_block {
605+
StackPopCleanup::Goto { ret, unwind } => {
606+
(true, Some(if cur_unwinding { unwind } else { ret }))
607+
},
608+
StackPopCleanup::None { cleanup, .. } => (cleanup, None)
609+
};
610+
611+
if !cleanup {
612+
assert!(self.stack.is_empty(), "only the topmost frame should ever be leaked");
613+
assert!(next_block.is_none(), "tried to skip cleanup when we have a next block!");
614+
// Leak the locals, skip validation.
615+
return Ok(());
568616
}
569-
// Deallocate all locals that are backed by an allocation.
617+
618+
// Cleanup: deallocate all locals that are backed by an allocation.
570619
for local in frame.locals {
571620
self.deallocate_local(local.value)?;
572621
}
573-
// Validate the return value. Do this after deallocating so that we catch dangling
574-
// references.
575-
if let Some(return_place) = frame.return_place {
576-
if M::enforce_validity(self) {
577-
// Data got changed, better make sure it matches the type!
578-
// It is still possible that the return place held invalid data while
579-
// the function is running, but that's okay because nobody could have
580-
// accessed that same data from the "outside" to observe any broken
581-
// invariant -- that is, unless a function somehow has a ptr to
582-
// its return place... but the way MIR is currently generated, the
583-
// return place is always a local and then this cannot happen.
584-
self.validate_operand(
585-
self.place_to_op(return_place)?,
586-
vec![],
587-
None,
588-
)?;
589-
}
622+
623+
624+
trace!("StackPopCleanup: {:?} StackPopInfo: {:?} cur_unwinding = {:?}",
625+
frame.return_to_block, stack_pop_info, cur_unwinding);
626+
if cur_unwinding {
627+
// Follow the unwind edge.
628+
let unwind = next_block.expect("Encounted StackPopCleanup::None when unwinding!");
629+
let next_frame = self.frame_mut();
630+
// If `unwind` is `None`, we'll leave that function immediately again.
631+
next_frame.block = unwind;
632+
next_frame.stmt = 0;
590633
} else {
591-
// Uh, that shouldn't happen... the function did not intend to return
592-
throw_ub!(Unreachable)
593-
}
594-
// Jump to new block -- *after* validation so that the spans make more sense.
595-
match frame.return_to_block {
596-
StackPopCleanup::Goto(block) => {
597-
self.goto_block(block)?;
634+
// Follow the normal return edge.
635+
// Validate the return value. Do this after deallocating so that we catch dangling
636+
// references.
637+
if let Some(return_place) = frame.return_place {
638+
if M::enforce_validity(self) {
639+
// Data got changed, better make sure it matches the type!
640+
// It is still possible that the return place held invalid data while
641+
// the function is running, but that's okay because nobody could have
642+
// accessed that same data from the "outside" to observe any broken
643+
// invariant -- that is, unless a function somehow has a ptr to
644+
// its return place... but the way MIR is currently generated, the
645+
// return place is always a local and then this cannot happen.
646+
self.validate_operand(
647+
self.place_to_op(return_place)?,
648+
vec![],
649+
None,
650+
)?;
651+
}
652+
} else {
653+
// Uh, that shouldn't happen... the function did not intend to return
654+
throw_ub!(Unreachable);
655+
}
656+
657+
// Jump to new block -- *after* validation so that the spans make more sense.
658+
if let Some(ret) = next_block {
659+
self.goto_block(ret)?;
598660
}
599-
StackPopCleanup::None { .. } => {}
600661
}
601662

602663
if self.stack.len() > 0 {
603-
info!("CONTINUING({}) {}", self.cur_frame(), self.frame().instance);
664+
info!("CONTINUING({}) {} (unwinding = {})",
665+
self.cur_frame(), self.frame().instance, cur_unwinding);
604666
}
605667

606668
Ok(())
@@ -745,16 +807,20 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
745807
} else {
746808
last_span = Some(span);
747809
}
748-
let block = &body.basic_blocks()[block];
749-
let source_info = if stmt < block.statements.len() {
750-
block.statements[stmt].source_info
751-
} else {
752-
block.terminator().source_info
753-
};
754-
let lint_root = match body.source_scope_local_data {
755-
mir::ClearCrossCrate::Set(ref ivs) => Some(ivs[source_info.scope].lint_root),
756-
mir::ClearCrossCrate::Clear => None,
757-
};
810+
811+
let lint_root = block.and_then(|block| {
812+
let block = &body.basic_blocks()[block];
813+
let source_info = if stmt < block.statements.len() {
814+
block.statements[stmt].source_info
815+
} else {
816+
block.terminator().source_info
817+
};
818+
match body.source_scope_local_data {
819+
mir::ClearCrossCrate::Set(ref ivs) => Some(ivs[source_info.scope].lint_root),
820+
mir::ClearCrossCrate::Clear => None,
821+
}
822+
});
823+
758824
frames.push(FrameInfo { call_site: span, instance, lint_root });
759825
}
760826
trace!("generate stacktrace: {:#?}, {:?}", frames, explicit_span);

src/librustc_mir/interpret/intrinsics.rs

+10-3
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,17 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
9191
span: Span,
9292
instance: ty::Instance<'tcx>,
9393
args: &[OpTy<'tcx, M::PointerTag>],
94-
dest: PlaceTy<'tcx, M::PointerTag>,
94+
dest: Option<PlaceTy<'tcx, M::PointerTag>>,
9595
) -> InterpResult<'tcx, bool> {
9696
let substs = instance.substs;
9797

98+
// We currently do not handle any diverging intrinsics.
99+
let dest = match dest {
100+
Some(dest) => dest,
101+
None => return Ok(false)
102+
};
98103
let intrinsic_name = &*self.tcx.item_name(instance.def_id()).as_str();
104+
99105
match intrinsic_name {
100106
"caller_location" => {
101107
let topmost = span.ctxt().outer_expn().expansion_cause().unwrap_or(span);
@@ -347,9 +353,10 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
347353
Ok(true)
348354
}
349355

350-
/// "Intercept" a function call because we have something special to do for it.
356+
/// "Intercept" a function call to a panic-related function
357+
/// because we have something special to do for it.
351358
/// Returns `true` if an intercept happened.
352-
pub fn hook_fn(
359+
pub fn hook_panic_fn(
353360
&mut self,
354361
instance: ty::Instance<'tcx>,
355362
args: &[OpTy<'tcx, M::PointerTag>],

0 commit comments

Comments
 (0)