Skip to content

Commit 7cdbc87

Browse files
committed
Auto merge of #69999 - RalfJung:miri-unwind, r=oli-obk
adjust Miri to needs of changed unwinding strategy As expected, #67502 broke unwinding in Miri. To fix it we have to adjust parts of the engine and the panic runtime, which this PR does. The Miri-side changes are in rust-lang/miri#1227. Cc @oli-obk @Aaron1011 @Mark-Simulacrum @Amanieu
2 parents 131772c + b450e1b commit 7cdbc87

File tree

8 files changed

+98
-50
lines changed

8 files changed

+98
-50
lines changed

src/libcore/intrinsics.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -1892,10 +1892,12 @@ extern "rust-intrinsic" {
18921892
pub fn ptr_offset_from<T>(ptr: *const T, base: *const T) -> isize;
18931893

18941894
/// Internal hook used by Miri to implement unwinding.
1895-
/// Compiles to a NOP during non-Miri codegen.
1895+
/// ICEs when encountered during non-Miri codegen.
18961896
///
1897-
/// Perma-unstable: do not use
1898-
pub fn miri_start_panic(data: *mut (dyn crate::any::Any + crate::marker::Send)) -> ();
1897+
/// The `payload` ptr here will be exactly the one `do_catch` gets passed by `try`.
1898+
///
1899+
/// Perma-unstable: do not use.
1900+
pub fn miri_start_panic(payload: *mut u8) -> !;
18991901
}
19001902

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

src/libpanic_unwind/lib.rs

+21-13
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
#![feature(raw)]
3131
#![panic_runtime]
3232
#![feature(panic_runtime)]
33+
// `real_imp` is unused with Miri, so silence warnings.
34+
#![cfg_attr(miri, allow(dead_code))]
3335

3436
use alloc::boxed::Box;
3537
use core::any::Any;
@@ -38,25 +40,38 @@ use core::panic::BoxMeUp;
3840
cfg_if::cfg_if! {
3941
if #[cfg(target_os = "emscripten")] {
4042
#[path = "emcc.rs"]
41-
mod imp;
43+
mod real_imp;
4244
} else if #[cfg(target_arch = "wasm32")] {
4345
#[path = "dummy.rs"]
44-
mod imp;
46+
mod real_imp;
4547
} else if #[cfg(target_os = "hermit")] {
4648
#[path = "hermit.rs"]
47-
mod imp;
49+
mod real_imp;
4850
} else if #[cfg(all(target_env = "msvc", target_arch = "aarch64"))] {
4951
#[path = "dummy.rs"]
50-
mod imp;
52+
mod real_imp;
5153
} else if #[cfg(target_env = "msvc")] {
5254
#[path = "seh.rs"]
53-
mod imp;
55+
mod real_imp;
5456
} else {
5557
// Rust runtime's startup objects depend on these symbols, so make them public.
5658
#[cfg(all(target_os="windows", target_arch = "x86", target_env="gnu"))]
57-
pub use imp::eh_frame_registry::*;
59+
pub use real_imp::eh_frame_registry::*;
5860
#[path = "gcc.rs"]
61+
mod real_imp;
62+
}
63+
}
64+
65+
cfg_if::cfg_if! {
66+
if #[cfg(miri)] {
67+
// Use the Miri runtime.
68+
// We still need to also load the normal runtime above, as rustc expects certain lang
69+
// items from there to be defined.
70+
#[path = "miri.rs"]
5971
mod imp;
72+
} else {
73+
// Use the real runtime.
74+
use real_imp as imp;
6075
}
6176
}
6277

@@ -81,12 +96,5 @@ pub unsafe extern "C" fn __rust_start_panic(payload: usize) -> u32 {
8196
let payload = payload as *mut &mut dyn BoxMeUp;
8297
let payload = (*payload).take_box();
8398

84-
// Miri panic support: cfg'd out of normal builds just to be sure.
85-
// When going through normal codegen, `miri_start_panic` is a NOP, so the
86-
// Miri-enabled sysroot still supports normal unwinding. But when executed in
87-
// Miri, this line initiates unwinding.
88-
#[cfg(miri)]
89-
core::intrinsics::miri_start_panic(payload);
90-
9199
imp::panic(Box::from_raw(payload))
92100
}

src/libpanic_unwind/miri.rs

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//! Unwinding panics for Miri.
2+
use alloc::boxed::Box;
3+
use core::any::Any;
4+
5+
// The type of the payload that the Miri engine propagates through unwinding for us.
6+
// Must be pointer-sized.
7+
type Payload = Box<Box<dyn Any + Send>>;
8+
9+
pub unsafe fn panic(payload: Box<dyn Any + Send>) -> u32 {
10+
// The payload we pass to `miri_start_panic` will be exactly the argument we get
11+
// in `cleanup` below. So we just box it up once, to get something pointer-sized.
12+
let payload_box: Payload = Box::new(payload);
13+
core::intrinsics::miri_start_panic(Box::into_raw(payload_box) as *mut u8)
14+
}
15+
16+
pub unsafe fn cleanup(payload_box: *mut u8) -> Box<dyn Any + Send> {
17+
// Recover the underlying `Box`.
18+
let payload_box: Payload = Box::from_raw(payload_box as *mut _);
19+
*payload_box
20+
}

src/librustc/mir/interpret/value.rs

+26
Original file line numberDiff line numberDiff line change
@@ -272,11 +272,13 @@ impl<'tcx, Tag> Scalar<Tag> {
272272

273273
#[inline]
274274
pub fn from_bool(b: bool) -> Self {
275+
// Guaranteed to be truncated and does not need sign extension.
275276
Scalar::Raw { data: b as u128, size: 1 }
276277
}
277278

278279
#[inline]
279280
pub fn from_char(c: char) -> Self {
281+
// Guaranteed to be truncated and does not need sign extension.
280282
Scalar::Raw { data: c as u128, size: 4 }
281283
}
282284

@@ -299,21 +301,25 @@ impl<'tcx, Tag> Scalar<Tag> {
299301

300302
#[inline]
301303
pub fn from_u8(i: u8) -> Self {
304+
// Guaranteed to be truncated and does not need sign extension.
302305
Scalar::Raw { data: i as u128, size: 1 }
303306
}
304307

305308
#[inline]
306309
pub fn from_u16(i: u16) -> Self {
310+
// Guaranteed to be truncated and does not need sign extension.
307311
Scalar::Raw { data: i as u128, size: 2 }
308312
}
309313

310314
#[inline]
311315
pub fn from_u32(i: u32) -> Self {
316+
// Guaranteed to be truncated and does not need sign extension.
312317
Scalar::Raw { data: i as u128, size: 4 }
313318
}
314319

315320
#[inline]
316321
pub fn from_u64(i: u64) -> Self {
322+
// Guaranteed to be truncated and does not need sign extension.
317323
Scalar::Raw { data: i as u128, size: 8 }
318324
}
319325

@@ -341,6 +347,26 @@ impl<'tcx, Tag> Scalar<Tag> {
341347
.unwrap_or_else(|| bug!("Signed value {:#x} does not fit in {} bits", i, size.bits()))
342348
}
343349

350+
#[inline]
351+
pub fn from_i8(i: i8) -> Self {
352+
Self::from_int(i, Size::from_bits(8))
353+
}
354+
355+
#[inline]
356+
pub fn from_i16(i: i16) -> Self {
357+
Self::from_int(i, Size::from_bits(16))
358+
}
359+
360+
#[inline]
361+
pub fn from_i32(i: i32) -> Self {
362+
Self::from_int(i, Size::from_bits(32))
363+
}
364+
365+
#[inline]
366+
pub fn from_i64(i: i64) -> Self {
367+
Self::from_int(i, Size::from_bits(64))
368+
}
369+
344370
#[inline]
345371
pub fn from_machine_isize(i: i64, cx: &impl HasDataLayout) -> Self {
346372
Self::from_int(i, cx.data_layout().pointer_size)

src/librustc_mir/interpret/eval_context.rs

+12-19
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use rustc_span::source_map::{self, Span, DUMMY_SP};
2121

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

2727
pub struct InterpCx<'mir, 'tcx, M: Machine<'mir, 'tcx>> {
@@ -623,31 +623,24 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
623623

624624
::log_settings::settings().indentation -= 1;
625625
let frame = self.stack.pop().expect("tried to pop a stack frame, but there were none");
626-
let stack_pop_info = M::stack_pop(self, frame.extra, unwinding)?;
627-
if let (false, StackPopInfo::StopUnwinding) = (unwinding, stack_pop_info) {
628-
bug!("Attempted to stop unwinding while there is no unwinding!");
629-
}
630626

631627
// Now where do we jump next?
632628

633-
// Determine if we leave this function normally or via unwinding.
634-
let cur_unwinding =
635-
if let StackPopInfo::StopUnwinding = stack_pop_info { false } else { unwinding };
636-
637629
// Usually we want to clean up (deallocate locals), but in a few rare cases we don't.
638630
// In that case, we return early. We also avoid validation in that case,
639631
// because this is CTFE and the final value will be thoroughly validated anyway.
640632
let (cleanup, next_block) = match frame.return_to_block {
641633
StackPopCleanup::Goto { ret, unwind } => {
642-
(true, Some(if cur_unwinding { unwind } else { ret }))
634+
(true, Some(if unwinding { unwind } else { ret }))
643635
}
644636
StackPopCleanup::None { cleanup, .. } => (cleanup, None),
645637
};
646638

647639
if !cleanup {
648640
assert!(self.stack.is_empty(), "only the topmost frame should ever be leaked");
649641
assert!(next_block.is_none(), "tried to skip cleanup when we have a next block!");
650-
// Leak the locals, skip validation.
642+
assert!(!unwinding, "tried to skip cleanup during unwinding");
643+
// Leak the locals, skip validation, skip machine hook.
651644
return Ok(());
652645
}
653646

@@ -656,13 +649,13 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
656649
self.deallocate_local(local.value)?;
657650
}
658651

659-
trace!(
660-
"StackPopCleanup: {:?} StackPopInfo: {:?} cur_unwinding = {:?}",
661-
frame.return_to_block,
662-
stack_pop_info,
663-
cur_unwinding
664-
);
665-
if cur_unwinding {
652+
if M::stack_pop(self, frame.extra, unwinding)? == StackPopJump::NoJump {
653+
// The hook already did everything.
654+
// We want to skip the `info!` below, hence early return.
655+
return Ok(());
656+
}
657+
// Normal return.
658+
if unwinding {
666659
// Follow the unwind edge.
667660
let unwind = next_block.expect("Encountered StackPopCleanup::None when unwinding!");
668661
self.unwind_to_block(unwind);
@@ -697,7 +690,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
697690
"CONTINUING({}) {} (unwinding = {})",
698691
self.cur_frame(),
699692
self.frame().instance,
700-
cur_unwinding
693+
unwinding
701694
);
702695
}
703696

src/librustc_mir/interpret/machine.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@ use super::{
1717
/// Data returned by Machine::stack_pop,
1818
/// to provide further control over the popping of the stack frame
1919
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
20-
pub enum StackPopInfo {
20+
pub enum StackPopJump {
2121
/// Indicates that no special handling should be
2222
/// done - we'll either return normally or unwind
2323
/// based on the terminator for the function
2424
/// we're leaving.
2525
Normal,
2626

27-
/// Indicates that we should stop unwinding,
28-
/// as we've reached a catch frame
29-
StopUnwinding,
27+
/// Indicates that we should *not* jump to the return/unwind address, as the callback already
28+
/// took care of everything.
29+
NoJump,
3030
}
3131

3232
/// Whether this kind of memory is allowed to leak
@@ -276,9 +276,9 @@ pub trait Machine<'mir, 'tcx>: Sized {
276276
_ecx: &mut InterpCx<'mir, 'tcx, Self>,
277277
_extra: Self::FrameExtra,
278278
_unwinding: bool,
279-
) -> InterpResult<'tcx, StackPopInfo> {
279+
) -> InterpResult<'tcx, StackPopJump> {
280280
// By default, we do not support unwinding from panics
281-
Ok(StackPopInfo::Normal)
281+
Ok(StackPopJump::Normal)
282282
}
283283

284284
fn int_to_ptr(

src/librustc_mir/interpret/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub use self::place::{MPlaceTy, MemPlace, MemPlaceMeta, Place, PlaceTy};
2424

2525
pub use self::memory::{AllocCheck, FnVal, Memory, MemoryKind};
2626

27-
pub use self::machine::{AllocMap, Machine, MayLeak, StackPopInfo};
27+
pub use self::machine::{AllocMap, Machine, MayLeak, StackPopJump};
2828

2929
pub use self::operand::{ImmTy, Immediate, OpTy, Operand, ScalarMaybeUndef};
3030

src/libstd/panicking.rs

+7-8
Original file line numberDiff line numberDiff line change
@@ -251,21 +251,20 @@ pub unsafe fn r#try<R, F: FnOnce() -> R>(f: F) -> Result<R, Box<dyn Any + Send>>
251251
//
252252
// We go through a transition where:
253253
//
254-
// * First, we set the data to be the closure that we're going to call.
254+
// * First, we set the data field `f` to be the argumentless closure that we're going to call.
255255
// * When we make the function call, the `do_call` function below, we take
256-
// ownership of the function pointer. At this point the `Data` union is
256+
// ownership of the function pointer. At this point the `data` union is
257257
// entirely uninitialized.
258258
// * If the closure successfully returns, we write the return value into the
259-
// data's return slot. Note that `ptr::write` is used as it's overwriting
260-
// uninitialized data.
259+
// data's return slot (field `r`).
260+
// * If the closure panics (`do_catch` below), we write the panic payload into field `p`.
261261
// * Finally, when we come back out of the `try` intrinsic we're
262262
// in one of two states:
263263
//
264264
// 1. The closure didn't panic, in which case the return value was
265-
// filled in. We move it out of `data` and return it.
266-
// 2. The closure panicked, in which case the return value wasn't
267-
// filled in. In this case the entire `data` union is invalid, so
268-
// there is no need to drop anything.
265+
// filled in. We move it out of `data.r` and return it.
266+
// 2. The closure panicked, in which case the panic payload was
267+
// filled in. We move it out of `data.p` and return it.
269268
//
270269
// Once we stack all that together we should have the "most efficient'
271270
// method of calling a catch panic whilst juggling ownership.

0 commit comments

Comments
 (0)