blob: 4685dfd1b8dd7646393abd5008ac1e2e74a24387 [file] [log] [blame]
use genmc_sys::AssumeType;
use rustc_middle::ty;
use tracing::debug;
use crate::concurrency::genmc::MAX_ACCESS_SIZE;
use crate::concurrency::thread::EvalContextExt as _;
use crate::*;
impl GenmcCtx {
/// Handle a user thread getting blocked.
/// This may happen due to an manual `assume` statement added by a user
/// or added by some automated program transformation, e.g., for spinloops.
fn handle_assume_block<'tcx>(
&self,
machine: &MiriMachine<'tcx>,
assume_type: AssumeType,
) -> InterpResult<'tcx> {
debug!("GenMC: assume statement, blocking active thread.");
self.handle
.borrow_mut()
.pin_mut()
.handle_assume_block(self.active_thread_genmc_tid(machine), assume_type);
interp_ok(())
}
}
// Handling of code intercepted by Miri in GenMC mode, such as assume statement or `std::sync::Mutex`.
impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// Small helper to get the arguments of an intercepted function call.
fn get_fn_args<const N: usize>(
&self,
instance: ty::Instance<'tcx>,
args: &[FnArg<'tcx>],
) -> InterpResult<'tcx, [OpTy<'tcx>; N]> {
let this = self.eval_context_ref();
let args = this.copy_fn_args(args); // FIXME: Should `InPlace` arguments be reset to uninit?
if let Ok(ops) = args.try_into() {
return interp_ok(ops);
}
panic!("{} is a diagnostic item expected to have {} arguments", instance, N);
}
/**** Blocking functionality ****/
/// Handle a thread getting blocked by a user assume (not an automatically generated assume).
/// Unblocking this thread in the current execution will cause a panic.
/// Miri does not provide GenMC with the annotations to determine when to unblock the thread, so it should never be unblocked.
fn handle_user_assume_block(&mut self) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
debug!(
"GenMC: block thread {:?} due to failing assume statement.",
this.machine.threads.active_thread()
);
assert!(this.machine.threads.active_thread_ref().is_enabled());
// Block the thread on the GenMC side.
let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap();
genmc_ctx.handle_assume_block(&this.machine, AssumeType::User)?;
// Block the thread on the Miri side.
this.block_thread(
BlockReason::Genmc,
None,
callback!(
@capture<'tcx> {}
|_this, unblock: UnblockKind| {
assert_eq!(unblock, UnblockKind::Ready);
unreachable!("GenMC should never unblock a thread blocked by an `assume`.");
}
),
);
interp_ok(())
}
fn intercept_mutex_lock(&mut self, mutex: MPlaceTy<'tcx>) -> InterpResult<'tcx> {
debug!("GenMC: handling Mutex::lock()");
let this = self.eval_context_mut();
let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap();
let size = mutex.layout.size.bytes();
assert!(
size <= MAX_ACCESS_SIZE,
"Mutex is larger than maximal size of a memory access supported by GenMC ({size} > {MAX_ACCESS_SIZE})"
);
let result = genmc_ctx.handle.borrow_mut().pin_mut().handle_mutex_lock(
genmc_ctx.active_thread_genmc_tid(&this.machine),
mutex.ptr().addr().bytes(),
size,
);
if let Some(error) = result.error.as_ref() {
// FIXME(genmc): improve error handling.
throw_ub_format!("{}", error.to_string_lossy());
}
if result.is_reset {
debug!("GenMC: Mutex::lock: Reset");
// GenMC informed us to reset and try the lock again later.
// We block the current thread until GenMC schedules it again.
this.block_thread(
crate::BlockReason::Genmc,
None,
crate::callback!(
@capture<'tcx> {
mutex: MPlaceTy<'tcx>,
}
|this, unblock: crate::UnblockKind| {
debug!("GenMC: Mutex::lock: unblocking callback called, attempting to lock the Mutex again.");
assert_eq!(unblock, crate::UnblockKind::Ready);
this.intercept_mutex_lock(mutex)?;
interp_ok(())
}
),
);
} else if result.is_lock_acquired {
debug!("GenMC: Mutex::lock successfully acquired the Mutex.");
} else {
debug!("GenMC: Mutex::lock failed to acquire the Mutex, permanently blocking thread.");
// NOTE: `handle_mutex_lock` already blocked the current thread on the GenMC side.
this.block_thread(
crate::BlockReason::Genmc,
None,
crate::callback!(
@capture<'tcx> {
mutex: MPlaceTy<'tcx>,
}
|_this, _unblock: crate::UnblockKind| {
unreachable!("A thread blocked on `Mutex::lock` should not be unblocked again.");
}
),
);
}
// NOTE: We don't write anything back to Miri's memory where the Mutex is located, that state is handled only by GenMC.
interp_ok(())
}
fn intercept_mutex_try_lock(
&mut self,
mutex: MPlaceTy<'tcx>,
dest: &crate::PlaceTy<'tcx>,
) -> InterpResult<'tcx> {
debug!("GenMC: handling Mutex::try_lock()");
let this = self.eval_context_mut();
let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap();
let size = mutex.layout.size.bytes();
assert!(
size <= MAX_ACCESS_SIZE,
"Mutex is larger than maximal size of a memory access supported by GenMC ({size} > {MAX_ACCESS_SIZE})"
);
let result = genmc_ctx.handle.borrow_mut().pin_mut().handle_mutex_try_lock(
genmc_ctx.active_thread_genmc_tid(&this.machine),
mutex.ptr().addr().bytes(),
size,
);
if let Some(error) = result.error.as_ref() {
// FIXME(genmc): improve error handling.
throw_ub_format!("{}", error.to_string_lossy());
}
debug!(
"GenMC: Mutex::try_lock(): is_reset: {}, is_lock_acquired: {}",
result.is_reset, result.is_lock_acquired
);
assert!(!result.is_reset, "GenMC returned 'reset' for a mutex try_lock.");
// Write the return value of try_lock, i.e., whether we acquired the mutex.
this.write_scalar(Scalar::from_bool(result.is_lock_acquired), dest)?;
// NOTE: We don't write anything back to Miri's memory where the Mutex is located, that state is handled only by GenMC.
interp_ok(())
}
fn intercept_mutex_unlock(&self, mutex: MPlaceTy<'tcx>) -> InterpResult<'tcx> {
debug!("GenMC: handling Mutex::unlock()");
let this = self.eval_context_ref();
let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap();
let result = genmc_ctx.handle.borrow_mut().pin_mut().handle_mutex_unlock(
genmc_ctx.active_thread_genmc_tid(&this.machine),
mutex.ptr().addr().bytes(),
mutex.layout.size.bytes(),
);
if let Some(error) = result.error.as_ref() {
// FIXME(genmc): improve error handling.
throw_ub_format!("{}", error.to_string_lossy());
}
// NOTE: We don't write anything back to Miri's memory where the Mutex is located, that state is handled only by GenMC.}
interp_ok(())
}
}
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// Given a `ty::Instance<'tcx>`, do any required special handling.
/// Returns true if this `instance` should be skipped (i.e., no MIR should be executed for it).
fn genmc_intercept_function(
&mut self,
instance: rustc_middle::ty::Instance<'tcx>,
args: &[rustc_const_eval::interpret::FnArg<'tcx, crate::Provenance>],
dest: &crate::PlaceTy<'tcx>,
) -> InterpResult<'tcx, bool> {
let this = self.eval_context_mut();
assert!(
this.machine.data_race.as_genmc_ref().is_some(),
"This function should only be called in GenMC mode."
);
// NOTE: When adding new intercepted functions here, they must also be added to `fn get_function_kind` in `concurrency/genmc/scheduling.rs`.
use rustc_span::sym;
if this.tcx.is_diagnostic_item(sym::sys_mutex_lock, instance.def_id()) {
let [mutex] = this.get_fn_args(instance, args)?;
let mutex = this.deref_pointer(&mutex)?;
this.intercept_mutex_lock(mutex)?;
} else if this.tcx.is_diagnostic_item(sym::sys_mutex_try_lock, instance.def_id()) {
let [mutex] = this.get_fn_args(instance, args)?;
let mutex = this.deref_pointer(&mutex)?;
this.intercept_mutex_try_lock(mutex, dest)?;
} else if this.tcx.is_diagnostic_item(sym::sys_mutex_unlock, instance.def_id()) {
let [mutex] = this.get_fn_args(instance, args)?;
let mutex = this.deref_pointer(&mutex)?;
this.intercept_mutex_unlock(mutex)?;
} else {
// Nothing to intercept.
return interp_ok(false);
}
interp_ok(true)
}
/// Handle an `assume` statement. This will tell GenMC to block the current thread if the `condition` is false.
/// Returns `true` if the current thread should be blocked in Miri too.
fn handle_genmc_verifier_assume(&mut self, condition: &OpTy<'tcx>) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let condition_bool = this.read_scalar(condition)?.to_bool()?;
debug!("GenMC: handle_genmc_verifier_assume, condition: {condition:?} = {condition_bool}");
if condition_bool {
return interp_ok(());
}
this.handle_user_assume_block()
}
}