Skip to content

Commit c28a8ee

Browse files
committed
Auto merge of #1532 - divergentdave:thread-panic-payload, r=RalfJung
Move panic payload state from Machine to Thread This PR moves the panic payload storage from the `Machine` state to per-thread state. Prior to this change, if one thread panicked while another was still unwinding, Miri would fail with `thread 'rustc' panicked at 'the panic runtime should avoid double-panics', src/shims/panic.rs:51:9`. I ran into this issue while prototyping a round-robin scheduler, but it's also reachable with the current scheduler and contrived programs that use blocking API calls to cause thread switching during unwinding. I wrote a test case along those lines for this change.
2 parents 0a4ecfc + a6746ad commit c28a8ee

File tree

11 files changed

+176
-26
lines changed

11 files changed

+176
-26
lines changed

src/eval.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,6 @@ pub fn create_ecx<'mir, 'tcx: 'mir>(
193193
StackPopCleanup::None { cleanup: true },
194194
)?;
195195

196-
// Set the last_error to 0
197-
let errno_layout = ecx.machine.layouts.u32;
198-
let errno_place = ecx.allocate(errno_layout, MiriMemoryKind::Machine.into());
199-
ecx.write_scalar(Scalar::from_u32(0), errno_place.into())?;
200-
ecx.machine.last_error = Some(errno_place);
201-
202196
Ok((ecx, ret_place))
203197
}
204198

src/helpers.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -394,17 +394,33 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
394394
)
395395
}
396396

397+
/// Get last error variable as a place, lazily allocating thread-local storage for it if
398+
/// necessary.
399+
fn last_error_place(&mut self) -> InterpResult<'tcx, MPlaceTy<'tcx, Tag>> {
400+
let this = self.eval_context_mut();
401+
if let Some(errno_place) = this.active_thread_ref().last_error {
402+
Ok(errno_place)
403+
} else {
404+
// Allocate new place, set initial value to 0.
405+
let errno_layout = this.machine.layouts.u32;
406+
let errno_place = this.allocate(errno_layout, MiriMemoryKind::Machine.into());
407+
this.write_scalar(Scalar::from_u32(0), errno_place.into())?;
408+
this.active_thread_mut().last_error = Some(errno_place);
409+
Ok(errno_place)
410+
}
411+
}
412+
397413
/// Sets the last error variable.
398414
fn set_last_error(&mut self, scalar: Scalar<Tag>) -> InterpResult<'tcx> {
399415
let this = self.eval_context_mut();
400-
let errno_place = this.machine.last_error.unwrap();
416+
let errno_place = this.last_error_place()?;
401417
this.write_scalar(scalar, errno_place.into())
402418
}
403419

404420
/// Gets the last error variable.
405-
fn get_last_error(&self) -> InterpResult<'tcx, Scalar<Tag>> {
406-
let this = self.eval_context_ref();
407-
let errno_place = this.machine.last_error.unwrap();
421+
fn get_last_error(&mut self) -> InterpResult<'tcx, Scalar<Tag>> {
422+
let this = self.eval_context_mut();
423+
let errno_place = this.last_error_place()?;
408424
this.read_scalar(errno_place.into())?.check_init()
409425
}
410426

src/machine.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,6 @@ pub struct Evaluator<'mir, 'tcx> {
236236
pub(crate) argv: Option<Scalar<Tag>>,
237237
pub(crate) cmd_line: Option<Scalar<Tag>>,
238238

239-
/// Last OS error location in memory. It is a 32-bit integer.
240-
pub(crate) last_error: Option<MPlaceTy<'tcx, Tag>>,
241-
242239
/// TLS state.
243240
pub(crate) tls: TlsData<'tcx>,
244241

@@ -252,11 +249,6 @@ pub struct Evaluator<'mir, 'tcx> {
252249
pub(crate) file_handler: shims::posix::FileHandler,
253250
pub(crate) dir_handler: shims::posix::DirHandler,
254251

255-
/// The temporary used for storing the argument of
256-
/// the call to `miri_start_panic` (the panic payload) when unwinding.
257-
/// This is pointer-sized, and matches the `Payload` type in `src/libpanic_unwind/miri.rs`.
258-
pub(crate) panic_payload: Option<Scalar<Tag>>,
259-
260252
/// The "time anchor" for this machine's monotone clock (for `Instant` simulation).
261253
pub(crate) time_anchor: Instant,
262254

@@ -285,13 +277,11 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
285277
argc: None,
286278
argv: None,
287279
cmd_line: None,
288-
last_error: None,
289280
tls: TlsData::default(),
290281
communicate,
291282
validate,
292283
file_handler: Default::default(),
293284
dir_handler: Default::default(),
294-
panic_payload: None,
295285
time_anchor: Instant::now(),
296286
layouts,
297287
threads: ThreadManager::default(),

src/shims/panic.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
4848
// Get the raw pointer stored in arg[0] (the panic payload).
4949
let &[payload] = check_arg_count(args)?;
5050
let payload = this.read_scalar(payload)?.check_init()?;
51+
let thread = this.active_thread_mut();
5152
assert!(
52-
this.machine.panic_payload.is_none(),
53+
thread.panic_payload.is_none(),
5354
"the panic runtime should avoid double-panics"
5455
);
55-
this.machine.panic_payload = Some(payload);
56+
thread.panic_payload = Some(payload);
5657

5758
// Jump to the unwind block to begin unwinding.
5859
this.unwind_to_block(unwind);
@@ -132,9 +133,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
132133
// We set the return value of `try` to 1, since there was a panic.
133134
this.write_scalar(Scalar::from_i32(1), catch_unwind.dest)?;
134135

135-
// `panic_payload` holds what was passed to `miri_start_panic`.
136+
// The Thread's `panic_payload` holds what was passed to `miri_start_panic`.
136137
// This is exactly the second argument we need to pass to `catch_fn`.
137-
let payload = this.machine.panic_payload.take().unwrap();
138+
let payload = this.active_thread_mut().panic_payload.take().unwrap();
138139

139140
// Push the `catch_fn` stackframe.
140141
let f_instance = this.memory.get_fn(catch_unwind.catch_fn)?.as_instance()?;

src/shims/posix/linux/foreign_items.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
2121
// errno
2222
"__errno_location" => {
2323
let &[] = check_arg_count(args)?;
24-
let errno_place = this.machine.last_error.unwrap();
24+
let errno_place = this.last_error_place()?;
2525
this.write_scalar(errno_place.to_ref().to_scalar()?, dest)?;
2626
}
2727

src/shims/posix/macos/foreign_items.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
2020
// errno
2121
"__error" => {
2222
let &[] = check_arg_count(args)?;
23-
let errno_place = this.machine.last_error.unwrap();
23+
let errno_place = this.last_error_place()?;
2424
this.write_scalar(errno_place.to_ref().to_scalar()?, dest)?;
2525
}
2626

src/thread.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,23 @@ enum ThreadJoinStatus {
106106
/// A thread.
107107
pub struct Thread<'mir, 'tcx> {
108108
state: ThreadState,
109+
109110
/// Name of the thread.
110111
thread_name: Option<Vec<u8>>,
112+
111113
/// The virtual call stack.
112114
stack: Vec<Frame<'mir, 'tcx, Tag, FrameData<'tcx>>>,
115+
113116
/// The join status.
114117
join_status: ThreadJoinStatus,
118+
119+
/// The temporary used for storing the argument of
120+
/// the call to `miri_start_panic` (the panic payload) when unwinding.
121+
/// This is pointer-sized, and matches the `Payload` type in `src/libpanic_unwind/miri.rs`.
122+
pub(crate) panic_payload: Option<Scalar<Tag>>,
123+
124+
/// Last OS error location in memory. It is a 32-bit integer.
125+
pub(crate) last_error: Option<MPlaceTy<'tcx, Tag>>,
115126
}
116127

117128
impl<'mir, 'tcx> Thread<'mir, 'tcx> {
@@ -150,6 +161,8 @@ impl<'mir, 'tcx> Default for Thread<'mir, 'tcx> {
150161
thread_name: None,
151162
stack: Vec::new(),
152163
join_status: ThreadJoinStatus::Joinable,
164+
panic_payload: None,
165+
last_error: None,
153166
}
154167
}
155168
}
@@ -568,6 +581,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
568581
this.machine.threads.get_active_thread_id()
569582
}
570583

584+
#[inline]
585+
fn active_thread_mut(&mut self) -> &mut Thread<'mir, 'tcx> {
586+
let this = self.eval_context_mut();
587+
this.machine.threads.active_thread_mut()
588+
}
589+
590+
#[inline]
591+
fn active_thread_ref(&self) -> &Thread<'mir, 'tcx> {
592+
let this = self.eval_context_ref();
593+
this.machine.threads.active_thread_ref()
594+
}
595+
571596
#[inline]
572597
fn get_total_thread_count(&self) -> usize {
573598
let this = self.eval_context_ref();

tests/run-pass/libc.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,24 @@ fn test_prctl_thread_name() {
212212
}
213213
}
214214

215+
/// Tests whether each thread has its own `__errno_location`.
216+
fn test_thread_local_errno() {
217+
#[cfg(not(target_os = "macos"))]
218+
use libc::__errno_location;
219+
#[cfg(target_os = "macos")]
220+
use libc::__error as __errno_location;
221+
222+
unsafe {
223+
*__errno_location() = 0xBEEF;
224+
std::thread::spawn(|| {
225+
assert_eq!(*__errno_location(), 0);
226+
*__errno_location() = 0xBAD1DEA;
227+
assert_eq!(*__errno_location(), 0xBAD1DEA);
228+
}).join().unwrap();
229+
assert_eq!(*__errno_location(), 0xBEEF);
230+
}
231+
}
232+
215233
fn main() {
216234
#[cfg(target_os = "linux")]
217235
test_posix_fadvise();
@@ -229,4 +247,6 @@ fn main() {
229247

230248
#[cfg(target_os = "linux")]
231249
test_prctl_thread_name();
250+
251+
test_thread_local_errno();
232252
}

tests/run-pass/libc.stderr

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
warning: thread support is experimental. For example, Miri does not detect data races yet.
2+
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// ignore-windows: Concurrency on Windows is not supported yet.
2+
3+
//! Cause a panic in one thread while another thread is unwinding. This checks
4+
//! that separate threads have their own panicking state.
5+
6+
use std::sync::{Arc, Condvar, Mutex};
7+
use std::thread::{spawn, JoinHandle};
8+
9+
struct BlockOnDrop(Option<JoinHandle<()>>);
10+
11+
impl BlockOnDrop {
12+
fn new(handle: JoinHandle<()>) -> BlockOnDrop {
13+
BlockOnDrop(Some(handle))
14+
}
15+
}
16+
17+
impl Drop for BlockOnDrop {
18+
fn drop(&mut self) {
19+
eprintln!("Thread 2 blocking on thread 1");
20+
let _ = self.0.take().unwrap().join();
21+
eprintln!("Thread 1 has exited");
22+
}
23+
}
24+
25+
fn main() {
26+
let t1_started_pair = Arc::new((Mutex::new(false), Condvar::new()));
27+
let t2_started_pair = Arc::new((Mutex::new(false), Condvar::new()));
28+
29+
let t1_continue_mutex = Arc::new(Mutex::new(()));
30+
let t1_continue_guard = t1_continue_mutex.lock();
31+
32+
let t1 = {
33+
let t1_started_pair = t1_started_pair.clone();
34+
let t1_continue_mutex = t1_continue_mutex.clone();
35+
spawn(move || {
36+
eprintln!("Thread 1 starting, will block on mutex");
37+
let (mutex, condvar) = &*t1_started_pair;
38+
*mutex.lock().unwrap() = true;
39+
condvar.notify_one();
40+
41+
drop(t1_continue_mutex.lock());
42+
panic!("panic in thread 1");
43+
})
44+
};
45+
46+
// Wait for thread 1 to signal it has started.
47+
let (t1_started_mutex, t1_started_condvar) = &*t1_started_pair;
48+
let mut t1_started_guard = t1_started_mutex.lock().unwrap();
49+
while !*t1_started_guard {
50+
t1_started_guard = t1_started_condvar.wait(t1_started_guard).unwrap();
51+
}
52+
eprintln!("Thread 1 reported it has started");
53+
// Thread 1 should now be blocked waiting on t1_continue_mutex.
54+
55+
let t2 = {
56+
let t2_started_pair = t2_started_pair.clone();
57+
let block_on_drop = BlockOnDrop::new(t1);
58+
spawn(move || {
59+
let _ = block_on_drop;
60+
61+
let (mutex, condvar) = &*t2_started_pair;
62+
*mutex.lock().unwrap() = true;
63+
condvar.notify_one();
64+
65+
panic!("panic in thread 2");
66+
})
67+
};
68+
69+
// Wait for thread 2 to signal it has started.
70+
let (t2_started_mutex, t2_started_condvar) = &*t2_started_pair;
71+
let mut t2_started_guard = t2_started_mutex.lock().unwrap();
72+
while !*t2_started_guard {
73+
t2_started_guard = t2_started_condvar.wait(t2_started_guard).unwrap();
74+
}
75+
eprintln!("Thread 2 reported it has started");
76+
// Thread 2 should now have already panicked and be in the middle of
77+
// unwinding. It should now be blocked on joining thread 1.
78+
79+
// Unlock t1_continue_mutex, and allow thread 1 to proceed.
80+
eprintln!("Unlocking mutex");
81+
drop(t1_continue_guard);
82+
// Thread 1 will panic the next time it is scheduled. This will test the
83+
// behavior of interest to this test, whether Miri properly handles
84+
// concurrent panics in two different threads.
85+
86+
// Block the main thread on waiting to join thread 2. Thread 2 should
87+
// already be blocked on joining thread 1, so thread 1 will be scheduled
88+
// to run next, as it is the only ready thread.
89+
assert!(t2.join().is_err());
90+
eprintln!("Thread 2 has exited");
91+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
warning: thread support is experimental. For example, Miri does not detect data races yet.
2+
3+
Thread 1 starting, will block on mutex
4+
Thread 1 reported it has started
5+
thread '<unnamed>' panicked at 'panic in thread 2', $DIR/concurrent-panic.rs:65:13
6+
Thread 2 blocking on thread 1
7+
Thread 2 reported it has started
8+
Unlocking mutex
9+
thread '<unnamed>' panicked at 'panic in thread 1', $DIR/concurrent-panic.rs:42:13
10+
Thread 1 has exited
11+
Thread 2 has exited

0 commit comments

Comments
 (0)