use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def_id::DefId;
use rustc_index::vec::{Idx, IndexVec};
-use rustc_middle::{
- middle::codegen_fn_attrs::CodegenFnAttrFlags,
- mir,
- ty::{self, Instance},
-};
use crate::sync::SynchronizationState;
use crate::*;
/// responsibility of the synchronization primitives to track threads that
/// are blocked by them.
BlockedOnSync,
- /// The thread has terminated its execution (we do not delete terminated
- /// threads).
+ /// The thread has terminated its execution. We do not delete terminated
+ /// threads (FIXME: why?).
Terminated,
}
}
}
+/// A specific moment in time.
#[derive(Debug)]
pub enum Time {
Monotonic(Instant),
/// Change the active thread to some enabled thread.
fn yield_active_thread(&mut self) {
+ // We do not yield immediately, as swapping out the current stack while executing a MIR statement
+ // could lead to all sorts of confusion.
+ // We should only switch stacks between steps.
self.yield_active_thread = true;
}
/// Register the given `callback` to be called once the `call_time` passes.
+ ///
+ /// The callback will be called with `thread` being the active thread, and
+ /// the callback may not change the active thread.
fn register_timeout_callback(
&mut self,
thread: ThreadId,
//
// Documentation:
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_timedwait.html#
- if let Some(sleep_time) =
- self.timeout_callbacks.values().map(|info| info.call_time.get_wait_time()).min()
- {
- if sleep_time == Duration::new(0, 0) {
- return Ok(SchedulingAction::ExecuteTimeoutCallback);
- }
+ let potential_sleep_time =
+ self.timeout_callbacks.values().map(|info| info.call_time.get_wait_time()).min();
+ if potential_sleep_time == Some(Duration::new(0, 0)) {
+ return Ok(SchedulingAction::ExecuteTimeoutCallback);
}
// No callbacks scheduled, pick a regular thread to execute.
if self.threads[self.active_thread].state == ThreadState::Enabled
// We have not found a thread to execute.
if self.threads.iter().all(|thread| thread.state == ThreadState::Terminated) {
unreachable!("all threads terminated without the main thread terminating?!");
- } else if let Some(sleep_time) =
- self.timeout_callbacks.values().map(|info| info.call_time.get_wait_time()).min()
- {
+ } else if let Some(sleep_time) = potential_sleep_time {
// All threads are currently blocked, but we have unexecuted
// timeout_callbacks, which may unblock some of the threads. Hence,
// sleep until the first callback.
// Public interface to thread management.
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
- /// A workaround for thread-local statics until
- /// https://github.com/rust-lang/rust/issues/70685 is fixed: change the
- /// thread-local allocation id with a freshly generated allocation id for
- /// the currently active thread.
- fn remap_thread_local_alloc_ids(
- &self,
- val: &mut mir::interpret::ConstValue<'tcx>,
- ) -> InterpResult<'tcx> {
- let this = self.eval_context_ref();
- match *val {
- mir::interpret::ConstValue::Scalar(Scalar::Ptr(ref mut ptr)) => {
- let alloc_id = ptr.alloc_id;
- let alloc = this.tcx.get_global_alloc(alloc_id);
- let tcx = this.tcx;
- let is_thread_local = |def_id| {
- tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::THREAD_LOCAL)
- };
- match alloc {
- Some(GlobalAlloc::Static(def_id)) if is_thread_local(def_id) => {
- ptr.alloc_id = this.get_or_create_thread_local_alloc_id(def_id)?;
- }
- _ => {}
- }
- }
- _ => {
- // FIXME: Handling only `Scalar` seems to work for now, but at
- // least in principle thread-locals could be in any constant, so
- // we should also consider other cases. However, once
- // https://github.com/rust-lang/rust/issues/70685 gets fixed,
- // this code will have to be rewritten anyway.
- }
- }
- Ok(())
- }
-
/// Get a thread-specific allocation id for the given thread-local static.
/// If needed, allocate a new one.
- ///
- /// FIXME: This method should be replaced as soon as
- /// https://github.com/rust-lang/rust/issues/70685 gets fixed.
fn get_or_create_thread_local_alloc_id(&self, def_id: DefId) -> InterpResult<'tcx, AllocId> {
let this = self.eval_context_ref();
let tcx = this.tcx;
// We need to allocate a thread-specific allocation id for this
// thread-local static.
//
- // At first, we invoke the `const_eval_raw` query and extract the
- // allocation from it. Unfortunately, we have to duplicate the code
- // from `Memory::get_global_alloc` that does this.
- //
+ // At first, we compute the initial value for this static.
// Then we store the retrieved allocation back into the `alloc_map`
// to get a fresh allocation id, which we can use as a
// thread-specific allocation id for the thread-local static.
+ // On first access to that allocation, it will be copied over to the machine memory.
if tcx.is_foreign_item(def_id) {
throw_unsup_format!("foreign thread-local statics are not supported");
}
- // Invoke the `const_eval_raw` query.
- let instance = Instance::mono(tcx.tcx, def_id);
- let gid = GlobalId { instance, promoted: None };
- let raw_const =
- tcx.const_eval_raw(ty::ParamEnv::reveal_all().and(gid)).map_err(|err| {
- // no need to report anything, the const_eval call takes care of that
- // for statics
- assert!(tcx.is_static(def_id));
- err
- })?;
- let id = raw_const.alloc_id;
- // Extract the allocation from the query result.
- let allocation = tcx.global_alloc(id).unwrap_memory();
+ let allocation = interpret::get_static(*tcx, def_id)?;
// Create a new allocation id for the same allocation in this hacky
// way. Internally, `alloc_map` deduplicates allocations, but this
// is fine because Miri will make a copy before a first mutable
}
#[inline]
- fn create_thread(&mut self) -> InterpResult<'tcx, ThreadId> {
+ fn create_thread(&mut self) -> ThreadId {
let this = self.eval_context_mut();
- Ok(this.machine.threads.create_thread())
+ this.machine.threads.create_thread()
}
#[inline]
}
#[inline]
- fn set_active_thread(&mut self, thread_id: ThreadId) -> InterpResult<'tcx, ThreadId> {
+ fn set_active_thread(&mut self, thread_id: ThreadId) -> ThreadId {
let this = self.eval_context_mut();
- Ok(this.machine.threads.set_active_thread_id(thread_id))
+ this.machine.threads.set_active_thread_id(thread_id)
}
#[inline]
- fn get_active_thread(&self) -> InterpResult<'tcx, ThreadId> {
+ fn get_active_thread(&self) -> ThreadId {
let this = self.eval_context_ref();
- Ok(this.machine.threads.get_active_thread_id())
+ this.machine.threads.get_active_thread_id()
}
#[inline]
- fn get_total_thread_count(&self) -> InterpResult<'tcx, usize> {
+ fn get_total_thread_count(&self) -> usize {
let this = self.eval_context_ref();
- Ok(this.machine.threads.get_total_thread_count())
+ this.machine.threads.get_total_thread_count()
}
#[inline]
- fn has_terminated(&self, thread_id: ThreadId) -> InterpResult<'tcx, bool> {
+ fn has_terminated(&self, thread_id: ThreadId) -> bool {
let this = self.eval_context_ref();
- Ok(this.machine.threads.has_terminated(thread_id))
+ this.machine.threads.has_terminated(thread_id)
}
#[inline]
- fn enable_thread(&mut self, thread_id: ThreadId) -> InterpResult<'tcx> {
+ fn enable_thread(&mut self, thread_id: ThreadId) {
let this = self.eval_context_mut();
this.machine.threads.enable_thread(thread_id);
- Ok(())
}
#[inline]
}
#[inline]
- fn set_active_thread_name(&mut self, new_thread_name: Vec<u8>) -> InterpResult<'tcx, ()> {
+ fn set_active_thread_name(&mut self, new_thread_name: Vec<u8>) {
let this = self.eval_context_mut();
- Ok(this.machine.threads.set_thread_name(new_thread_name))
+ this.machine.threads.set_thread_name(new_thread_name);
}
#[inline]
- fn get_active_thread_name<'c>(&'c self) -> InterpResult<'tcx, &'c [u8]>
+ fn get_active_thread_name<'c>(&'c self) -> &'c [u8]
where
'mir: 'c,
{
let this = self.eval_context_ref();
- Ok(this.machine.threads.get_thread_name())
+ this.machine.threads.get_thread_name()
}
#[inline]
- fn block_thread(&mut self, thread: ThreadId) -> InterpResult<'tcx> {
+ fn block_thread(&mut self, thread: ThreadId) {
let this = self.eval_context_mut();
- Ok(this.machine.threads.block_thread(thread))
+ this.machine.threads.block_thread(thread);
}
#[inline]
- fn unblock_thread(&mut self, thread: ThreadId) -> InterpResult<'tcx> {
+ fn unblock_thread(&mut self, thread: ThreadId) {
let this = self.eval_context_mut();
- Ok(this.machine.threads.unblock_thread(thread))
+ this.machine.threads.unblock_thread(thread);
}
#[inline]
- fn yield_active_thread(&mut self) -> InterpResult<'tcx> {
+ fn yield_active_thread(&mut self) {
let this = self.eval_context_mut();
this.machine.threads.yield_active_thread();
- Ok(())
}
#[inline]
thread: ThreadId,
call_time: Time,
callback: TimeoutCallback<'mir, 'tcx>,
- ) -> InterpResult<'tcx> {
+ ) {
let this = self.eval_context_mut();
this.machine.threads.register_timeout_callback(thread, call_time, callback);
- Ok(())
}
#[inline]
- fn unregister_timeout_callback_if_exists(&mut self, thread: ThreadId) -> InterpResult<'tcx> {
+ fn unregister_timeout_callback_if_exists(&mut self, thread: ThreadId) {
let this = self.eval_context_mut();
this.machine.threads.unregister_timeout_callback_if_exists(thread);
- Ok(())
}
/// Execute a timeout callback on the callback's thread.
let this = self.eval_context_mut();
let (thread, callback) =
this.machine.threads.get_ready_callback().expect("no callback found");
- let old_thread = this.set_active_thread(thread)?;
+ // This back-and-forth with `set_active_thread` is here because of two
+ // design decisions:
+ // 1. Make the caller and not the callback responsible for changing
+ // thread.
+ // 2. Make the scheduler the only place that can change the active
+ // thread.
+ let old_thread = this.set_active_thread(thread);
callback(this)?;
- this.set_active_thread(old_thread)?;
+ this.set_active_thread(old_thread);
Ok(())
}