Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ degree documented below):
- `solaris` / `illumos`: maintained by @devnexen. Supports the entire test suite.
- `freebsd`: maintained by @YohDeadfall and @LorrensP-2158466. Supports the entire test suite.
- `android`: **maintainer wanted**. Supports the entire test suite.
- `netbsd`: maintained by @joboet. Supports the entire test suite.
- For targets on other operating systems, Miri might fail before even reaching the `main` function.

However, even for targets that we do support, the degree of support for accessing platform APIs
Expand Down Expand Up @@ -662,6 +663,7 @@ Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows
* [`VecDeque::iter_mut` creating overlapping mutable references](https://github.com/rust-lang/rust/issues/74029)
* [Various standard library aliasing issues involving raw pointers](https://github.com/rust-lang/rust/pull/78602)
* [`<[T]>::copy_within` using a loan after invalidating it](https://github.com/rust-lang/rust/pull/85610)
* [`libc` had a wrong definition of `_lwp_park`, causing `std` to pass an immutable pointer because of coercion](https://github.com/rust-lang/libc/pull/5169)

## Scientific papers employing Miri

Expand Down
86 changes: 86 additions & 0 deletions src/concurrency/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,24 @@ struct FutexWaiter {
bitset: u32,
}

/// The set of threads that should wake immediately from `thread_park`.
#[derive(Default, Clone, Debug)]
pub struct ThreadTokens {
/// The set of thread tokens, each being associated with the vclock of the
/// thread that added the token to establish a happens-before edge between
/// `thread_unpark` and `thread_park`.
tokens: FxHashMap<ThreadId, Option<VClock>>,
}

/// The result of a `thread_park`.
pub enum ParkResult {
/// The token was already made available, and hence the thread should continue
/// running.
Already,
/// The thread was put to sleep.
Parked,
}

// Private extension trait for local helper methods
impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
pub(super) trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
Expand Down Expand Up @@ -830,4 +848,72 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {

interp_ok(woken)
}

/// Parks the current thread, to be unparked by `thread_unpark`.
///
/// If `thread_unpark` is called before `thread_park`, this call will return
/// `ParkResult::Already` and `callback` is never invoked. Otherwise, the
/// current thread is blocked, and `callback` will be invoked upon the next
/// call to `thread_unpark`.
fn thread_park(
&mut self,
deadline: Option<Deadline>,
callback: DynUnblockCallback<'tcx>,
) -> InterpResult<'tcx, ParkResult> {
let this = self.eval_context_mut();
let thread_id = this.active_thread();

if let Some(vclock) = this.machine.thread_tokens.tokens.remove(&thread_id) {
if let Some(vclock) = vclock {
this.acquire_clock(&vclock)?;
}

interp_ok(ParkResult::Already)
} else {
this.block_thread(BlockReason::Park, deadline,
callback!(
@capture<'tcx> {
callback: DynUnblockCallback<'tcx>,
}
|this, unblock: UnblockKind| {
match unblock {
UnblockKind::Ready => {
let thread = this.active_thread();
let token = this.machine.thread_tokens.tokens.remove(&thread).expect("
can only be unblocked by thread_unpark, which sets a token
");

if let Some(vclock) = token {
this.acquire_clock(&vclock)?;
}
},
UnblockKind::TimedOut => {}
}

callback.call(this, unblock)
}
));
interp_ok(ParkResult::Parked)
}
}

/// Unparks `thread`.
///
/// If `thread` is blocked through `thread_unpark`, then unblock it. Otherwise
/// add the threads token to the thread token set, so that the next `thread_park`
/// returns immediately.
fn thread_unpark(&mut self, thread: ThreadId) -> InterpResult<'tcx> {
let this = self.eval_context_mut();

let mut vclock = None;
this.release_clock(|c| vclock = Some(c.clone()))?;
this.machine.thread_tokens.tokens.insert(thread, vclock);

if this.machine.threads.thread_ref(thread).is_blocked_on(&BlockReason::Park) {
// Only unblock threads that are currently parked.
this.unblock_thread(thread, BlockReason::Park)?;
}

interp_ok(())
}
}
7 changes: 7 additions & 0 deletions src/concurrency/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ pub enum BlockReason {
RwLock,
/// Blocked on a Futex variable.
Futex,
/// Blocked by a thread park operation.
Park,
/// Blocked on an InitOnce.
InitOnce,
/// Blocked on epoll.
Expand Down Expand Up @@ -228,6 +230,11 @@ impl<'tcx> Thread<'tcx> {
self.state.is_enabled()
}

/// Return whether this thread is blocked for `reason`.
pub fn is_blocked_on(&self, reason: &BlockReason) -> bool {
self.state.is_blocked_on(reason)
}

/// Get the name of the current thread for display purposes; will include thread ID if not set.
fn thread_display_name(&self, id: ThreadId) -> String {
if let Some(ref thread_name) = self.thread_name {
Expand Down
20 changes: 20 additions & 0 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
interp_ok(())
}

/// Helper function used inside shims of foreign functions to check that an
/// alias name for a function is used on all `target_oses`, but not anywhere
/// else. It returns a hopefully helpful error message if this is not the case.
fn check_alias_used_on(
&self,
alias: &str,
target_oses: &[Os],
name: Symbol,
) -> InterpResult<'tcx> {
let target_os = &self.eval_context_ref().tcx.sess.target.os;
let in_list = target_oses.contains(target_os);
let alias_used = name.as_str() == alias;
match (alias_used, in_list) {
(true, true) => interp_ok(()),
(false, false) => interp_ok(()),
(true, false) => throw_unsup_format!("`{name}` is not supported on {target_os}"),
(false, true) => throw_unsup_format!("`{name}` is named `{alias}` on {target_os}"),
}
}

/// Helper function used inside the shims of foreign functions to assert that the target OS
/// is part of the UNIX family. It panics showing a message with the `name` of the foreign function
/// if this is not the case.
Expand Down
7 changes: 6 additions & 1 deletion src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use rustc_target::spec::{Arch, Os};
use crate::alloc_addresses::EvalContextExt;
use crate::concurrency::cpu_affinity::{self, CpuAffinityMask};
use crate::concurrency::data_race::{self, NaReadType, NaWriteType};
use crate::concurrency::sync::SyncObj;
use crate::concurrency::sync::{SyncObj, ThreadTokens};
use crate::concurrency::{
AllocDataRaceHandler, GenmcCtx, GenmcEvalContextExt as _, GlobalDataRaceHandler, weak_memory,
};
Expand Down Expand Up @@ -541,6 +541,9 @@ pub struct MiriMachine<'tcx> {
/// The set of threads.
pub(crate) threads: ThreadManager<'tcx>,

/// The list of threads that should wake immediately from a `thread_park`.
pub(crate) thread_tokens: ThreadTokens,

/// Handles blocking I/O and polling for completion.
pub(crate) blocking_io: BlockingIoManager,

Expand Down Expand Up @@ -774,6 +777,7 @@ impl<'tcx> MiriMachine<'tcx> {
dirs: Default::default(),
layouts,
threads,
thread_tokens: Default::default(),
thread_cpu_affinity,
blocking_io,
static_roots: Vec::new(),
Expand Down Expand Up @@ -1020,6 +1024,7 @@ impl VisitProvenance for MiriMachine<'_> {
#[rustfmt::skip]
let MiriMachine {
threads,
thread_tokens: _,
thread_cpu_affinity: _,
tls,
env_vars,
Expand Down
2 changes: 1 addition & 1 deletion src/provenance_gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ macro_rules! no_provenance {
)+
}
}
no_provenance!(i8 i16 i32 i64 isize u8 u16 u32 u64 usize bool ThreadId Deadline);
no_provenance!(i8 i16 i32 i64 isize u8 u16 u32 u64 usize bool ThreadId Deadline Instant);

impl VisitProvenance for &'static str {
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
Expand Down
26 changes: 26 additions & 0 deletions src/shims/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::time::{Duration, SystemTime};

use chrono::{DateTime, Datelike, Offset, Timelike, Utc};
use chrono_tz::Tz;
use rustc_middle::ty::ScalarInt;
use rustc_target::spec::Os;

use crate::*;
Expand Down Expand Up @@ -483,6 +484,31 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
})
}

/// Formats `duration` as a `timespec` and writes it to `tp`.
///
/// This will stop the machine if the duration is too large to fit in a
/// `timespec` (which should never happen for realistic durations).
fn write_timespec(&mut self, duration: Duration, tp: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
let this = self.eval_context_mut();

let sec_field = this.project_field_named(tp, "tv_sec")?;
let Some(secs) = i64::try_from(duration.as_secs())
.ok()
.and_then(|secs| ScalarInt::try_from_int(secs, sec_field.layout.size))
else {
throw_machine_stop!(TerminationInfo::Abort(
"tried to write a very large duration".to_owned()
));
};
this.write_scalar(secs, &sec_field)?;

let nsec_field = this.project_field_named(tp, "tv_nsec")?;
let nsecs = Scalar::from_uint(duration.subsec_nanos(), nsec_field.layout.size);
this.write_scalar(nsecs, &nsec_field)?;

interp_ok(())
}

/// Parse a `timeval` struct and return it as a [`Duration`]. It returns [`None`]
/// if the value in the `timeval` struct is invalid. Some libc functions will return
/// EINVAL in this case.
Expand Down
Loading