Skip to content

Commit 093381e

Browse files
committed
Improve blocking in GenMC mode, fix deadlock test (deadlock is not currently detected)
1 parent 2d73543 commit 093381e

File tree

12 files changed

+113
-359
lines changed

12 files changed

+113
-359
lines changed

genmc-sys/cpp/include/MiriInterface.hpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,7 @@ struct MiriGenmcShim : private GenMCDriver {
136136

137137
/**** Blocking instructions ****/
138138
/// Inform GenMC that the thread should be blocked.
139-
/// Note: this function is currently hardcoded for `AssumeType::User`, corresponding to user
140-
/// supplied assume statements. This can become a parameter once more types of assumes are
141-
/// added.
142-
void handle_assume_block(ThreadId thread_id);
139+
void handle_assume_block(ThreadId thread_id, AssumeType assume_type);
143140

144141
/**** Mutex handling ****/
145142
auto handle_mutex_lock(ThreadId thread_id, uint64_t address, uint64_t size) -> MutexLockResult;

genmc-sys/cpp/src/MiriInterface/EventHandling.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@
3232

3333
/**** Blocking instructions ****/
3434

35-
void MiriGenmcShim::handle_assume_block(ThreadId thread_id) {
35+
void MiriGenmcShim::handle_assume_block(ThreadId thread_id, AssumeType assume_type) {
3636
BUG_ON(getExec().getGraph().isThreadBlocked(thread_id));
37-
GenMCDriver::handleAssume(inc_pos(thread_id), AssumeType::User);
37+
GenMCDriver::handleAssume(inc_pos(thread_id), assume_type);
3838
}
3939

4040
/**** Memory access handling ****/

genmc-sys/cpp/src/MiriInterface/Mutex.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ auto MiriGenmcShim::handle_mutex_lock(ThreadId thread_id, uint64_t address, uint
8080
// We did not acquire the mutex, so we tell GenMC to block the thread until we can acquire
8181
// it. GenMC determines this based on the annotation we pass with the load further up in
8282
// this function, namely when that load will read a value other than `MUTEX_LOCKED`.
83-
GenMCDriver::handleAssume(inc_pos(thread_id), AssumeType::Spinloop);
83+
// NOTE: The blocking happens on the Miri side.
8484
}
8585
return MutexLockResultExt::ok(is_lock_acquired);
8686
}

genmc-sys/src/lib.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,13 @@ mod ffi {
316316
UMin = 10,
317317
}
318318

319+
#[derive(Debug)]
320+
enum AssumeType {
321+
User = 0,
322+
Barrier = 1,
323+
Spinloop = 2,
324+
}
325+
319326
// # Safety
320327
//
321328
// This block is unsafe to allow defining safe methods inside.
@@ -334,6 +341,7 @@ mod ffi {
334341
(This tells cxx that the enums defined above are already defined on the C++ side;
335342
it will emit assertions to ensure that the two definitions agree.) ****/
336343
type ActionKind;
344+
type AssumeType;
337345
type MemOrdering;
338346
type RMWBinOp;
339347
type SchedulePolicy;
@@ -441,7 +449,11 @@ mod ffi {
441449
/// Inform GenMC that the thread should be blocked.
442450
/// Note: this function is currently hardcoded for `AssumeType::User`, corresponding to user supplied assume statements.
443451
/// This can become a parameter once more types of assumes are added.
444-
fn handle_assume_block(self: Pin<&mut MiriGenmcShim>, thread_id: i32);
452+
fn handle_assume_block(
453+
self: Pin<&mut MiriGenmcShim>,
454+
thread_id: i32,
455+
assume_type: AssumeType,
456+
);
445457

446458
/**** Mutex handling ****/
447459
fn handle_mutex_lock(

src/concurrency/genmc/intercept.rs

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
1+
use genmc_sys::AssumeType;
12
use rustc_middle::ty;
23
use tracing::debug;
34

45
use crate::concurrency::genmc::MAX_ACCESS_SIZE;
56
use crate::concurrency::thread::EvalContextExt as _;
67
use crate::*;
78

9+
impl GenmcCtx {
10+
/// Handle a user thread getting blocked.
11+
/// This may happen due to an manual `assume` statement added by a user
12+
/// or added by some automated program transformation, e.g., for spinloops.
13+
fn handle_assume_block<'tcx>(
14+
&self,
15+
machine: &MiriMachine<'tcx>,
16+
assume_type: AssumeType,
17+
) -> InterpResult<'tcx> {
18+
debug!("GenMC: assume statement, blocking active thread.");
19+
self.handle
20+
.borrow_mut()
21+
.pin_mut()
22+
.handle_assume_block(self.active_thread_genmc_tid(machine), assume_type);
23+
interp_ok(())
24+
}
25+
}
26+
827
// Handling of code intercepted by Miri in GenMC mode, such as assume statement or `std::sync::Mutex`.
928

1029
impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
@@ -23,6 +42,36 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
2342
panic!("{} is a diagnostic item expected to have {} arguments", instance, N);
2443
}
2544

45+
/**** Blocking functionality ****/
46+
47+
/// Handle a thread getting blocked by a user assume (not an automatically generated assume).
48+
/// Unblocking this thread in the current execution will cause a panic.
49+
/// Miri does not provide GenMC with the annotations to determine when to unblock the thread, so it should never be unblocked.
50+
fn handle_user_assume_block(&mut self) -> InterpResult<'tcx> {
51+
let this = self.eval_context_mut();
52+
debug!(
53+
"GenMC: block thread {:?} due to failing assume statement.",
54+
this.machine.threads.active_thread()
55+
);
56+
assert!(this.machine.threads.active_thread_ref().is_enabled());
57+
// Block the thread on the GenMC side.
58+
let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap();
59+
genmc_ctx.handle_assume_block(&this.machine, AssumeType::User)?;
60+
// Block the thread on the Miri side.
61+
this.block_thread(
62+
BlockReason::Genmc,
63+
None,
64+
callback!(
65+
@capture<'tcx> {}
66+
|_this, unblock: UnblockKind| {
67+
assert_eq!(unblock, UnblockKind::Ready);
68+
unreachable!("GenMC should never unblock a thread blocked by an `assume`.");
69+
}
70+
),
71+
);
72+
interp_ok(())
73+
}
74+
2675
fn intercept_mutex_lock(&mut self, mutex: MPlaceTy<'tcx>) -> InterpResult<'tcx> {
2776
debug!("GenMC: handling Mutex::lock()");
2877
let this = self.eval_context_mut();
@@ -47,13 +96,15 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
4796
result.is_reset, result.is_lock_acquired
4897
);
4998
if result.is_lock_acquired {
99+
// NOTE: We don't write anything back to Miri's memory, the Mutex state is handled only by GenMC.
50100
return interp_ok(());
51101
}
52-
// If we did not acquire the mutex and GenMC did not tell us to reset and try again, we have a deadlock.
102+
// We either did not acquire the mutex, or GenMC informed us to reset and try the lock again later, so we block the current thread.
53103
if !result.is_reset {
54-
throw_machine_stop!(TerminationInfo::Deadlock);
104+
// GenMC expects us to block the thread if we did get a `reset`.
105+
genmc_ctx.handle_assume_block(&this.machine, AssumeType::Spinloop)?;
55106
}
56-
// NOTE: We don't write anything back to Miri's memory, the Mutex state is handled only by GenMC.
107+
// Block the thread on the Miri side.
57108
this.block_thread(
58109
crate::BlockReason::Genmc,
59110
None,
@@ -78,9 +129,11 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
78129
throw_ub_format!("{}", error.to_string_lossy());
79130
}
80131
assert!(!result.is_reset, "We should not get a reset on the second lock attempt.");
81-
// If we did not acquire the mutex and GenMC did not tell us to reset and try again, we have a deadlock.
132+
// If we did not acquire the mutex and GenMC did not tell us to reset and try again, we block this thread for the remainder of the current execution.
82133
if !result.is_lock_acquired {
83-
throw_machine_stop!(TerminationInfo::Deadlock);
134+
debug!("GenMC: Mutex::lock failed to acquire the Mutex twice, permanently blocking thread.");
135+
// This is equivalent to a user inserted `assume(false)`.
136+
this.handle_user_assume_block()?;
84137
}
85138
interp_ok(())
86139
}
@@ -116,7 +169,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
116169
"GenMC: Mutex::try_lock(): is_reset: {}, is_lock_acquired: {}",
117170
result.is_reset, result.is_lock_acquired
118171
);
119-
assert!(!result.is_reset);
172+
assert!(!result.is_reset, "GenMC returned 'reset' for a mutex lock multiple times.");
120173
// Write the return value of try_lock, i.e., whether we acquired the mutex.
121174
this.write_scalar(Scalar::from_bool(result.is_lock_acquired), dest)?;
122175
// NOTE: We don't write anything back to Miri's memory where the Mutex is located, that state is handled only by GenMC.
@@ -187,19 +240,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
187240
if condition_bool {
188241
return interp_ok(());
189242
}
190-
let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap();
191-
genmc_ctx.handle_assume_block(&this.machine)?;
192-
this.block_thread(
193-
BlockReason::Genmc,
194-
None,
195-
callback!(
196-
@capture<'tcx> {}
197-
|_this, unblock: UnblockKind| {
198-
assert_eq!(unblock, UnblockKind::Ready);
199-
unreachable!("GenMC should never unblock a thread blocked by an `assume`.");
200-
}
201-
),
202-
);
203-
interp_ok(())
243+
this.handle_user_assume_block()
204244
}
205245
}

src/concurrency/genmc/mod.rs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -870,20 +870,6 @@ impl GenmcCtx {
870870
};
871871
interp_ok((old_value_scalar, new_value_scalar))
872872
}
873-
874-
/**** Blocking functionality ****/
875-
876-
/// Handle a user thread getting blocked.
877-
/// This may happen due to an manual `assume` statement added by a user
878-
/// or added by some automated program transformation, e.g., for spinloops.
879-
fn handle_assume_block<'tcx>(&self, machine: &MiriMachine<'tcx>) -> InterpResult<'tcx> {
880-
debug!("GenMC: assume statement, blocking active thread.");
881-
self.handle
882-
.borrow_mut()
883-
.pin_mut()
884-
.handle_assume_block(self.active_thread_genmc_tid(machine));
885-
interp_ok(())
886-
}
887873
}
888874

889875
impl VisitProvenance for GenmcCtx {

src/concurrency/thread.rs

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -708,21 +708,31 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> {
708708
let this = self.eval_context_mut();
709709

710710
// In GenMC mode, we let GenMC do the scheduling.
711-
if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() {
712-
let Some(next_thread_id) = genmc_ctx.schedule_thread(this)? else {
713-
return interp_ok(SchedulingAction::ExecuteStep);
714-
};
715-
// If a thread is blocked on GenMC, we have to implicitly unblock it when it gets scheduled again.
716-
if this.machine.threads.threads[next_thread_id].state.is_blocked_on(BlockReason::Genmc)
717-
{
718-
info!("GenMC: scheduling blocked thread {next_thread_id:?}, so we unblock it now.");
719-
this.unblock_thread(next_thread_id, BlockReason::Genmc)?;
711+
if this.machine.data_race.as_genmc_ref().is_some() {
712+
loop {
713+
let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap();
714+
let Some(next_thread_id) = genmc_ctx.schedule_thread(this)? else {
715+
return interp_ok(SchedulingAction::ExecuteStep);
716+
};
717+
// If a thread is blocked on GenMC, we have to implicitly unblock it when it gets scheduled again.
718+
if this.machine.threads.threads[next_thread_id]
719+
.state
720+
.is_blocked_on(BlockReason::Genmc)
721+
{
722+
info!(
723+
"GenMC: scheduling blocked thread {next_thread_id:?}, so we unblock it now."
724+
);
725+
this.unblock_thread(next_thread_id, BlockReason::Genmc)?;
726+
}
727+
// Set the new active thread.
728+
let thread_manager = &mut this.machine.threads;
729+
thread_manager.active_thread = next_thread_id;
730+
// A thread blocked by GenMC may get blocked again during the unblocking callback.
731+
// In that case, we need to ask for a different thread to run next.
732+
if thread_manager.threads[thread_manager.active_thread].state.is_enabled() {
733+
return interp_ok(SchedulingAction::ExecuteStep);
734+
}
720735
}
721-
// Set the new active thread.
722-
let thread_manager = &mut this.machine.threads;
723-
thread_manager.active_thread = next_thread_id;
724-
assert!(thread_manager.threads[thread_manager.active_thread].state.is_enabled());
725-
return interp_ok(SchedulingAction::ExecuteStep);
726736
}
727737

728738
// We are not in GenMC mode, so we control the scheduling.

tests/genmc/fail/intercept/mutex_deadlock.stderr

Lines changed: 0 additions & 79 deletions
This file was deleted.

tests/genmc/fail/intercept/mutex_deadlock_std.rs

Lines changed: 0 additions & 29 deletions
This file was deleted.

0 commit comments

Comments
 (0)