Auto merge of #2231 - DrMeepster:winfred, r=RalfJung
Windows thread support: Part 1 This PR adds support for threads on Windows.
This commit is contained in:
commit
46da748502
84 changed files with 921 additions and 225 deletions
|
|
@ -5,6 +5,8 @@
|
|||
#![feature(try_blocks)]
|
||||
#![feature(let_else)]
|
||||
#![feature(io_error_more)]
|
||||
#![feature(int_log)]
|
||||
#![feature(variant_count)]
|
||||
#![feature(yeet_expr)]
|
||||
#![feature(is_some_with)]
|
||||
#![feature(nonzero_ops)]
|
||||
|
|
|
|||
|
|
@ -421,6 +421,7 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
|
|||
) -> InterpResult<'tcx> {
|
||||
EnvVars::init(this, config)?;
|
||||
Evaluator::init_extern_statics(this)?;
|
||||
ThreadManager::init(this);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
|||
'mir: 'a,
|
||||
{
|
||||
#[cfg(windows)]
|
||||
pub fn u16vec_to_osstring<'tcx, 'a>(u16_vec: Vec<u16>) -> InterpResult<'tcx, OsString> {
|
||||
pub fn u16vec_to_osstring<'tcx>(u16_vec: Vec<u16>) -> InterpResult<'tcx, OsString> {
|
||||
Ok(OsString::from_wide(&u16_vec[..]))
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
|
|
|
|||
|
|
@ -197,12 +197,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
|||
fn nanosleep(
|
||||
&mut self,
|
||||
req_op: &OpTy<'tcx, Provenance>,
|
||||
_rem: &OpTy<'tcx, Provenance>,
|
||||
_rem: &OpTy<'tcx, Provenance>, // Signal handlers are not supported, so rem will never be written to.
|
||||
) -> InterpResult<'tcx, i32> {
|
||||
// Signal handlers are not supported, so rem will never be written to.
|
||||
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
this.assert_target_os_is_unix("nanosleep");
|
||||
this.check_no_isolation("`nanosleep`")?;
|
||||
|
||||
let duration = match this.read_timespec(&this.deref_operand(req_op)?)? {
|
||||
|
|
@ -233,4 +232,31 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
|||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn Sleep(&mut self, timeout: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
this.assert_target_os("windows", "Sleep");
|
||||
this.check_no_isolation("`Sleep`")?;
|
||||
|
||||
let timeout_ms = this.read_scalar(timeout)?.to_u32()?;
|
||||
|
||||
let duration = Duration::from_millis(timeout_ms.into());
|
||||
let timeout_time = Time::Monotonic(Instant::now().checked_add(duration).unwrap());
|
||||
|
||||
let active_thread = this.get_active_thread();
|
||||
this.block_thread(active_thread);
|
||||
|
||||
this.register_timeout_callback(
|
||||
active_thread,
|
||||
timeout_time,
|
||||
Box::new(move |ecx| {
|
||||
ecx.unblock_thread(active_thread);
|
||||
Ok(())
|
||||
}),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -229,25 +229,28 @@ impl<'tcx> TlsData<'tcx> {
|
|||
|
||||
impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
|
||||
trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
|
||||
/// Schedule TLS destructors for the main thread on Windows. The
|
||||
/// implementation assumes that we do not support concurrency on Windows
|
||||
/// yet.
|
||||
/// Schedule TLS destructors for Windows.
|
||||
/// On windows, TLS destructors are managed by std.
|
||||
fn schedule_windows_tls_dtors(&mut self) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
let active_thread = this.get_active_thread();
|
||||
assert_eq!(this.get_total_thread_count(), 1, "concurrency on Windows is not supported");
|
||||
|
||||
// Windows has a special magic linker section that is run on certain events.
|
||||
// Instead of searching for that section and supporting arbitrary hooks in there
|
||||
// (that would be basically https://github.com/rust-lang/miri/issues/450),
|
||||
// we specifically look up the static in libstd that we know is placed
|
||||
// in that section.
|
||||
let thread_callback = this
|
||||
.eval_path_scalar(&["std", "sys", "windows", "thread_local_key", "p_thread_callback"])?
|
||||
.to_pointer(this)?;
|
||||
let thread_callback =
|
||||
this.eval_windows("thread_local_key", "p_thread_callback")?.to_pointer(this)?;
|
||||
let thread_callback = this.get_ptr_fn(thread_callback)?.as_instance()?;
|
||||
|
||||
// FIXME: Technically, the reason should be `DLL_PROCESS_DETACH` when the main thread exits
|
||||
// but std treats both the same.
|
||||
let reason = this.eval_windows("c", "DLL_THREAD_DETACH")?;
|
||||
|
||||
// The signature of this function is `unsafe extern "system" fn(h: c::LPVOID, dwReason: c::DWORD, pv: c::LPVOID)`.
|
||||
let reason = this.eval_path_scalar(&["std", "sys", "windows", "c", "DLL_THREAD_DETACH"])?;
|
||||
// FIXME: `h` should be a handle to the current module and what `pv` should be is unknown
|
||||
// but both are ignored by std
|
||||
this.call_function(
|
||||
thread_callback,
|
||||
Abi::System { unwind: false },
|
||||
|
|
|
|||
|
|
@ -13,47 +13,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
|||
) -> InterpResult<'tcx, i32> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
// Create the new thread
|
||||
let new_thread_id = this.create_thread();
|
||||
|
||||
// Write the current thread-id, switch to the next thread later
|
||||
// to treat this write operation as occuring on the current thread.
|
||||
let thread_info_place = this.deref_operand(thread)?;
|
||||
this.write_scalar(
|
||||
Scalar::from_uint(new_thread_id.to_u32(), thread_info_place.layout.size),
|
||||
&thread_info_place.into(),
|
||||
)?;
|
||||
|
||||
// Read the function argument that will be sent to the new thread
|
||||
// before the thread starts executing since reading after the
|
||||
// context switch will incorrectly report a data-race.
|
||||
let fn_ptr = this.read_pointer(start_routine)?;
|
||||
let start_routine = this.read_pointer(start_routine)?;
|
||||
|
||||
let func_arg = this.read_immediate(arg)?;
|
||||
|
||||
// Finally switch to new thread so that we can push the first stackframe.
|
||||
// After this all accesses will be treated as occuring in the new thread.
|
||||
let old_thread_id = this.set_active_thread(new_thread_id);
|
||||
|
||||
// Perform the function pointer load in the new thread frame.
|
||||
let instance = this.get_ptr_fn(fn_ptr)?.as_instance()?;
|
||||
|
||||
// Note: the returned value is currently ignored (see the FIXME in
|
||||
// pthread_join below) because the Rust standard library does not use
|
||||
// it.
|
||||
let ret_place =
|
||||
this.allocate(this.layout_of(this.tcx.types.usize)?, MiriMemoryKind::Machine.into())?;
|
||||
|
||||
this.call_function(
|
||||
instance,
|
||||
this.start_thread(
|
||||
Some(thread_info_place),
|
||||
start_routine,
|
||||
Abi::C { unwind: false },
|
||||
&[*func_arg],
|
||||
Some(&ret_place.into()),
|
||||
StackPopCleanup::Root { cleanup: true },
|
||||
func_arg,
|
||||
this.layout_of(this.tcx.types.usize)?,
|
||||
)?;
|
||||
|
||||
// Restore the old active thread frame.
|
||||
this.set_active_thread(old_thread_id);
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +43,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
|||
}
|
||||
|
||||
let thread_id = this.read_scalar(thread)?.to_machine_usize(this)?;
|
||||
this.join_thread(thread_id.try_into().expect("thread ID should fit in u32"))?;
|
||||
this.join_thread_exclusive(thread_id.try_into().expect("thread ID should fit in u32"))?;
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
|
@ -79,7 +52,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
|||
let this = self.eval_context_mut();
|
||||
|
||||
let thread_id = this.read_scalar(thread)?.to_machine_usize(this)?;
|
||||
this.detach_thread(thread_id.try_into().expect("thread ID should fit in u32"))?;
|
||||
this.detach_thread(
|
||||
thread_id.try_into().expect("thread ID should fit in u32"),
|
||||
/*allow_terminated_joined*/ false,
|
||||
)?;
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ use rustc_target::spec::abi::Abi;
|
|||
use log::trace;
|
||||
|
||||
use crate::helpers::check_arg_count;
|
||||
use crate::shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle};
|
||||
use crate::*;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Dlsym {
|
||||
NtWriteFile,
|
||||
SetThreadDescription,
|
||||
}
|
||||
|
||||
impl Dlsym {
|
||||
|
|
@ -18,8 +20,8 @@ impl Dlsym {
|
|||
pub fn from_str<'tcx>(name: &str) -> InterpResult<'tcx, Option<Dlsym>> {
|
||||
Ok(match name {
|
||||
"GetSystemTimePreciseAsFileTime" => None,
|
||||
"SetThreadDescription" => None,
|
||||
"NtWriteFile" => Some(Dlsym::NtWriteFile),
|
||||
"SetThreadDescription" => Some(Dlsym::SetThreadDescription),
|
||||
_ => throw_unsup_format!("unsupported Windows dlsym: {}", name),
|
||||
})
|
||||
}
|
||||
|
|
@ -107,6 +109,23 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
|||
dest,
|
||||
)?;
|
||||
}
|
||||
Dlsym::SetThreadDescription => {
|
||||
let [handle, name] = check_arg_count(args)?;
|
||||
|
||||
let handle = this.read_scalar(handle)?.check_init()?;
|
||||
|
||||
let name = this.read_wide_str(this.read_pointer(name)?)?;
|
||||
|
||||
let thread = match Handle::from_scalar(handle, this)? {
|
||||
Some(Handle::Thread(thread)) => thread,
|
||||
Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => this.get_active_thread(),
|
||||
_ => this.invalid_handle("SetThreadDescription")?,
|
||||
};
|
||||
|
||||
this.set_thread_name_wide(thread, &name);
|
||||
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
}
|
||||
|
||||
trace!("{:?}", this.dump_place(**dest));
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ use rustc_target::spec::abi::Abi;
|
|||
|
||||
use crate::*;
|
||||
use shims::foreign_items::EmulateByNameResult;
|
||||
use shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle};
|
||||
use shims::windows::sync::EvalContextExt as _;
|
||||
use shims::windows::thread::EvalContextExt as _;
|
||||
|
||||
use smallvec::SmallVec;
|
||||
|
||||
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
|
||||
|
|
@ -219,6 +222,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
|||
let result = this.QueryPerformanceFrequency(lpFrequency)?;
|
||||
this.write_scalar(Scalar::from_i32(result), dest)?;
|
||||
}
|
||||
"Sleep" => {
|
||||
let [timeout] =
|
||||
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
|
||||
|
||||
this.Sleep(timeout)?;
|
||||
}
|
||||
|
||||
// Synchronization primitives
|
||||
"AcquireSRWLockExclusive" => {
|
||||
|
|
@ -314,12 +323,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
|||
// FIXME: we should set last_error, but to what?
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
"SwitchToThread" => {
|
||||
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
|
||||
// Note that once Miri supports concurrency, this will need to return a nonzero
|
||||
// value if this call does result in switching to another thread.
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
"GetStdHandle" => {
|
||||
let [which] =
|
||||
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
|
||||
|
|
@ -327,16 +330,42 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
|||
// We just make this the identity function, so we know later in `NtWriteFile` which
|
||||
// one it is. This is very fake, but libtest needs it so we cannot make it a
|
||||
// std-only shim.
|
||||
// FIXME: this should return real HANDLEs when io support is added
|
||||
this.write_scalar(Scalar::from_machine_isize(which.into(), this), dest)?;
|
||||
}
|
||||
|
||||
// Better error for attempts to create a thread
|
||||
"CreateThread" => {
|
||||
let [_, _, _, _, _, _] =
|
||||
"CloseHandle" => {
|
||||
let [handle] =
|
||||
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
|
||||
|
||||
this.handle_unsupported("can't create threads on Windows")?;
|
||||
return Ok(EmulateByNameResult::AlreadyJumped);
|
||||
this.CloseHandle(handle)?;
|
||||
|
||||
this.write_scalar(Scalar::from_u32(1), dest)?;
|
||||
}
|
||||
|
||||
// Threading
|
||||
"CreateThread" => {
|
||||
let [security, stacksize, start, arg, flags, thread] =
|
||||
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
|
||||
|
||||
let thread_id =
|
||||
this.CreateThread(security, stacksize, start, arg, flags, thread)?;
|
||||
|
||||
this.write_scalar(Handle::Thread(thread_id).to_scalar(this), dest)?;
|
||||
}
|
||||
"WaitForSingleObject" => {
|
||||
let [handle, timeout] =
|
||||
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
|
||||
|
||||
let ret = this.WaitForSingleObject(handle, timeout)?;
|
||||
this.write_scalar(Scalar::from_u32(ret), dest)?;
|
||||
}
|
||||
"GetCurrentThread" => {
|
||||
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
|
||||
|
||||
this.write_scalar(
|
||||
Handle::Pseudo(PseudoHandle::CurrentThread).to_scalar(this),
|
||||
dest,
|
||||
)?;
|
||||
}
|
||||
|
||||
// Incomplete shims that we "stub out" just to get pre-main initialization code to work.
|
||||
|
|
@ -344,6 +373,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
|||
"GetProcessHeap" if this.frame_in_std() => {
|
||||
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
|
||||
// Just fake a HANDLE
|
||||
// It's fine to not use the Handle type here because its a stub
|
||||
this.write_scalar(Scalar::from_machine_isize(1, this), dest)?;
|
||||
}
|
||||
"GetModuleHandleA" if this.frame_in_std() => {
|
||||
|
|
@ -374,45 +404,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
|||
// Any non zero value works for the stdlib. This is just used for stack overflows anyway.
|
||||
this.write_scalar(Scalar::from_u32(1), dest)?;
|
||||
}
|
||||
| "InitializeCriticalSection"
|
||||
| "EnterCriticalSection"
|
||||
| "LeaveCriticalSection"
|
||||
| "DeleteCriticalSection"
|
||||
if this.frame_in_std() =>
|
||||
{
|
||||
#[allow(non_snake_case)]
|
||||
let [_lpCriticalSection] =
|
||||
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
|
||||
assert_eq!(
|
||||
this.get_total_thread_count(),
|
||||
1,
|
||||
"concurrency on Windows is not supported"
|
||||
);
|
||||
// Nothing to do, not even a return value.
|
||||
// (Windows locks are reentrant, and we have only 1 thread,
|
||||
// so not doing any futher checks here is at least not incorrect.)
|
||||
}
|
||||
"TryEnterCriticalSection" if this.frame_in_std() => {
|
||||
#[allow(non_snake_case)]
|
||||
let [_lpCriticalSection] =
|
||||
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
|
||||
assert_eq!(
|
||||
this.get_total_thread_count(),
|
||||
1,
|
||||
"concurrency on Windows is not supported"
|
||||
);
|
||||
// There is only one thread, so this always succeeds and returns TRUE.
|
||||
this.write_scalar(Scalar::from_i32(1), dest)?;
|
||||
}
|
||||
"GetCurrentThread" if this.frame_in_std() => {
|
||||
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
|
||||
this.write_scalar(Scalar::from_machine_isize(1, this), dest)?;
|
||||
}
|
||||
"GetCurrentProcessId" if this.frame_in_std() => {
|
||||
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
|
||||
let result = this.GetCurrentProcessId()?;
|
||||
this.write_scalar(Scalar::from_u32(result), dest)?;
|
||||
}
|
||||
// this is only callable from std because we know that std ignores the return value
|
||||
"SwitchToThread" if this.frame_in_std() => {
|
||||
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
|
||||
|
||||
this.yield_active_thread();
|
||||
|
||||
// FIXME: this should return a nonzero value if this call does result in switching to another thread.
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
|
||||
_ => return Ok(EmulateByNameResult::NotSupported),
|
||||
}
|
||||
|
|
|
|||
167
src/shims/windows/handle.rs
Normal file
167
src/shims/windows/handle.rs
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
use rustc_target::abi::HasDataLayout;
|
||||
use std::mem::variant_count;
|
||||
|
||||
use crate::*;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum PseudoHandle {
|
||||
CurrentThread,
|
||||
}
|
||||
|
||||
/// Miri representation of a Windows `HANDLE`
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Handle {
|
||||
Null,
|
||||
Pseudo(PseudoHandle),
|
||||
Thread(ThreadId),
|
||||
}
|
||||
|
||||
impl PseudoHandle {
|
||||
const CURRENT_THREAD_VALUE: u32 = 0;
|
||||
|
||||
fn value(self) -> u32 {
|
||||
match self {
|
||||
Self::CurrentThread => Self::CURRENT_THREAD_VALUE,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_value(value: u32) -> Option<Self> {
|
||||
match value {
|
||||
Self::CURRENT_THREAD_VALUE => Some(Self::CurrentThread),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
const NULL_DISCRIMINANT: u32 = 0;
|
||||
const PSEUDO_DISCRIMINANT: u32 = 1;
|
||||
const THREAD_DISCRIMINANT: u32 = 2;
|
||||
|
||||
fn discriminant(self) -> u32 {
|
||||
match self {
|
||||
Self::Null => Self::NULL_DISCRIMINANT,
|
||||
Self::Pseudo(_) => Self::PSEUDO_DISCRIMINANT,
|
||||
Self::Thread(_) => Self::THREAD_DISCRIMINANT,
|
||||
}
|
||||
}
|
||||
|
||||
fn data(self) -> u32 {
|
||||
match self {
|
||||
Self::Null => 0,
|
||||
Self::Pseudo(pseudo_handle) => pseudo_handle.value(),
|
||||
Self::Thread(thread) => thread.to_u32(),
|
||||
}
|
||||
}
|
||||
|
||||
fn packed_disc_size() -> u32 {
|
||||
// ceil(log2(x)) is how many bits it takes to store x numbers
|
||||
let variant_count = variant_count::<Self>();
|
||||
|
||||
// however, std's ilog2 is floor(log2(x))
|
||||
let floor_log2 = variant_count.ilog2();
|
||||
|
||||
// we need to add one for non powers of two to compensate for the difference
|
||||
if variant_count.is_power_of_two() { floor_log2 } else { floor_log2 + 1 }
|
||||
}
|
||||
|
||||
/// Converts a handle into its machine representation.
|
||||
///
|
||||
/// The upper [`Self::packed_disc_size()`] bits are used to store a discriminant corresponding to the handle variant.
|
||||
/// The remaining bits are used for the variant's field.
|
||||
///
|
||||
/// None of this layout is guaranteed to applications by Windows or Miri.
|
||||
fn to_packed(self) -> u32 {
|
||||
let disc_size = Self::packed_disc_size();
|
||||
let data_size = u32::BITS - disc_size;
|
||||
|
||||
let discriminant = self.discriminant();
|
||||
let data = self.data();
|
||||
|
||||
// make sure the discriminant fits into `disc_size` bits
|
||||
assert!(discriminant < 2u32.pow(disc_size));
|
||||
|
||||
// make sure the data fits into `data_size` bits
|
||||
assert!(data < 2u32.pow(data_size));
|
||||
|
||||
// packs the data into the lower `data_size` bits
|
||||
// and packs the discriminant right above the data
|
||||
discriminant << data_size | data
|
||||
}
|
||||
|
||||
fn new(discriminant: u32, data: u32) -> Option<Self> {
|
||||
match discriminant {
|
||||
Self::NULL_DISCRIMINANT if data == 0 => Some(Self::Null),
|
||||
Self::PSEUDO_DISCRIMINANT => Some(Self::Pseudo(PseudoHandle::from_value(data)?)),
|
||||
Self::THREAD_DISCRIMINANT => Some(Self::Thread(data.into())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// see docs for `to_packed`
|
||||
fn from_packed(handle: u32) -> Option<Self> {
|
||||
let disc_size = Self::packed_disc_size();
|
||||
let data_size = u32::BITS - disc_size;
|
||||
|
||||
// the lower `data_size` bits of this mask are 1
|
||||
let data_mask = 2u32.pow(data_size) - 1;
|
||||
|
||||
// the discriminant is stored right above the lower `data_size` bits
|
||||
let discriminant = handle >> data_size;
|
||||
|
||||
// the data is stored in the lower `data_size` bits
|
||||
let data = handle & data_mask;
|
||||
|
||||
Self::new(discriminant, data)
|
||||
}
|
||||
|
||||
pub fn to_scalar(self, cx: &impl HasDataLayout) -> Scalar<Provenance> {
|
||||
// 64-bit handles are sign extended 32-bit handles
|
||||
// see https://docs.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication
|
||||
#[allow(clippy::cast_possible_wrap)] // we want it to wrap
|
||||
let signed_handle = self.to_packed() as i32;
|
||||
Scalar::from_machine_isize(signed_handle.into(), cx)
|
||||
}
|
||||
|
||||
pub fn from_scalar<'tcx>(
|
||||
handle: Scalar<Provenance>,
|
||||
cx: &impl HasDataLayout,
|
||||
) -> InterpResult<'tcx, Option<Self>> {
|
||||
let sign_extended_handle = handle.to_machine_isize(cx)?;
|
||||
|
||||
#[allow(clippy::cast_sign_loss)] // we want to lose the sign
|
||||
let handle = if let Ok(signed_handle) = i32::try_from(sign_extended_handle) {
|
||||
signed_handle as u32
|
||||
} else {
|
||||
// if a handle doesn't fit in an i32, it isn't valid.
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Ok(Self::from_packed(handle))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
|
||||
fn invalid_handle(&mut self, function_name: &str) -> InterpResult<'tcx, !> {
|
||||
throw_machine_stop!(TerminationInfo::Abort(format!(
|
||||
"invalid handle passed to `{function_name}`"
|
||||
)))
|
||||
}
|
||||
|
||||
fn CloseHandle(&mut self, handle_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
let handle = this.read_scalar(handle_op)?.check_init()?;
|
||||
|
||||
match Handle::from_scalar(handle, this)? {
|
||||
Some(Handle::Thread(thread)) =>
|
||||
this.detach_thread(thread, /*allow_terminated_joined*/ true)?,
|
||||
_ => this.invalid_handle("CloseHandle")?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
pub mod dlsym;
|
||||
pub mod foreign_items;
|
||||
|
||||
mod handle;
|
||||
mod sync;
|
||||
mod thread;
|
||||
|
|
|
|||
89
src/shims/windows/thread.rs
Normal file
89
src/shims/windows/thread.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
use rustc_middle::ty::layout::LayoutOf;
|
||||
use rustc_target::spec::abi::Abi;
|
||||
|
||||
use crate::*;
|
||||
use shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle};
|
||||
|
||||
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
|
||||
fn CreateThread(
|
||||
&mut self,
|
||||
security_op: &OpTy<'tcx, Provenance>,
|
||||
stacksize_op: &OpTy<'tcx, Provenance>,
|
||||
start_op: &OpTy<'tcx, Provenance>,
|
||||
arg_op: &OpTy<'tcx, Provenance>,
|
||||
flags_op: &OpTy<'tcx, Provenance>,
|
||||
thread_op: &OpTy<'tcx, Provenance>,
|
||||
) -> InterpResult<'tcx, ThreadId> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
let security = this.read_pointer(security_op)?;
|
||||
|
||||
// stacksize is ignored, but still needs to be a valid usize
|
||||
this.read_scalar(stacksize_op)?.to_machine_usize(this)?;
|
||||
|
||||
let start_routine = this.read_pointer(start_op)?;
|
||||
|
||||
let func_arg = this.read_immediate(arg_op)?;
|
||||
|
||||
let flags = this.read_scalar(flags_op)?.to_u32()?;
|
||||
|
||||
let thread = if this.ptr_is_null(this.read_pointer(thread_op)?)? {
|
||||
None
|
||||
} else {
|
||||
let thread_info_place = this.deref_operand(thread_op)?;
|
||||
Some(thread_info_place)
|
||||
};
|
||||
|
||||
let stack_size_param_is_a_reservation =
|
||||
this.eval_windows("c", "STACK_SIZE_PARAM_IS_A_RESERVATION")?.to_u32()?;
|
||||
|
||||
// We ignore the stack size, so we also ignore the
|
||||
// `STACK_SIZE_PARAM_IS_A_RESERVATION` flag.
|
||||
if flags != 0 && flags != stack_size_param_is_a_reservation {
|
||||
throw_unsup_format!("unsupported `dwCreationFlags` {} in `CreateThread`", flags)
|
||||
}
|
||||
|
||||
if !this.ptr_is_null(security)? {
|
||||
throw_unsup_format!("non-null `lpThreadAttributes` in `CreateThread`")
|
||||
}
|
||||
|
||||
this.start_thread(
|
||||
thread,
|
||||
start_routine,
|
||||
Abi::System { unwind: false },
|
||||
func_arg,
|
||||
this.layout_of(this.tcx.types.u32)?,
|
||||
)
|
||||
}
|
||||
|
||||
fn WaitForSingleObject(
|
||||
&mut self,
|
||||
handle_op: &OpTy<'tcx, Provenance>,
|
||||
timeout_op: &OpTy<'tcx, Provenance>,
|
||||
) -> InterpResult<'tcx, u32> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
let handle = this.read_scalar(handle_op)?.check_init()?;
|
||||
|
||||
let timeout = this.read_scalar(timeout_op)?.to_u32()?;
|
||||
|
||||
let thread = match Handle::from_scalar(handle, this)? {
|
||||
Some(Handle::Thread(thread)) => thread,
|
||||
// Unlike on posix, the outcome of joining the current thread is not documented.
|
||||
// On current Windows, it just deadlocks.
|
||||
Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => this.get_active_thread(),
|
||||
_ => this.invalid_handle("WaitForSingleObject")?,
|
||||
};
|
||||
|
||||
if timeout != this.eval_windows("c", "INFINITE")?.to_u32()? {
|
||||
throw_unsup_format!("`WaitForSingleObject` with non-infinite timeout");
|
||||
}
|
||||
|
||||
this.join_thread(thread)?;
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
150
src/thread.rs
150
src/thread.rs
|
|
@ -11,6 +11,8 @@ use rustc_data_structures::fx::FxHashMap;
|
|||
use rustc_hir::def_id::DefId;
|
||||
use rustc_index::vec::{Idx, IndexVec};
|
||||
use rustc_middle::mir::Mutability;
|
||||
use rustc_middle::ty::layout::TyAndLayout;
|
||||
use rustc_target::spec::abi::Abi;
|
||||
|
||||
use crate::concurrency::data_race;
|
||||
use crate::sync::SynchronizationState;
|
||||
|
|
@ -238,10 +240,7 @@ impl<'mir, 'tcx> Default for ThreadManager<'mir, 'tcx> {
|
|||
fn default() -> Self {
|
||||
let mut threads = IndexVec::new();
|
||||
// Create the main thread and add it to the list of threads.
|
||||
let mut main_thread = Thread::new("main");
|
||||
// The main thread can *not* be joined on.
|
||||
main_thread.join_status = ThreadJoinStatus::Detached;
|
||||
threads.push(main_thread);
|
||||
threads.push(Thread::new("main"));
|
||||
Self {
|
||||
active_thread: ThreadId::new(0),
|
||||
threads,
|
||||
|
|
@ -254,6 +253,13 @@ impl<'mir, 'tcx> Default for ThreadManager<'mir, 'tcx> {
|
|||
}
|
||||
|
||||
impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
|
||||
pub(crate) fn init(ecx: &mut MiriEvalContext<'mir, 'tcx>) {
|
||||
if ecx.tcx.sess.target.os.as_ref() != "windows" {
|
||||
// The main thread can *not* be joined on except on windows.
|
||||
ecx.machine.threads.threads[ThreadId::new(0)].join_status = ThreadJoinStatus::Detached;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if we have an allocation for the given thread local static for the
|
||||
/// active thread.
|
||||
fn get_thread_local_alloc_id(&self, def_id: DefId) -> Option<Pointer<Provenance>> {
|
||||
|
|
@ -348,10 +354,26 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
|
|||
|
||||
/// Mark the thread as detached, which means that no other thread will try
|
||||
/// to join it and the thread is responsible for cleaning up.
|
||||
fn detach_thread(&mut self, id: ThreadId) -> InterpResult<'tcx> {
|
||||
if self.threads[id].join_status != ThreadJoinStatus::Joinable {
|
||||
///
|
||||
/// `allow_terminated_joined` allows detaching joined threads that have already terminated.
|
||||
/// This matches Windows's behavior for `CloseHandle`.
|
||||
///
|
||||
/// See <https://docs.microsoft.com/en-us/windows/win32/procthread/thread-handles-and-identifiers>:
|
||||
/// > The handle is valid until closed, even after the thread it represents has been terminated.
|
||||
fn detach_thread(&mut self, id: ThreadId, allow_terminated_joined: bool) -> InterpResult<'tcx> {
|
||||
trace!("detaching {:?}", id);
|
||||
|
||||
let is_ub = if allow_terminated_joined && self.threads[id].state == ThreadState::Terminated
|
||||
{
|
||||
// "Detached" in particular means "not yet joined". Redundant detaching is still UB.
|
||||
self.threads[id].join_status == ThreadJoinStatus::Detached
|
||||
} else {
|
||||
self.threads[id].join_status != ThreadJoinStatus::Joinable
|
||||
};
|
||||
if is_ub {
|
||||
throw_ub_format!("trying to detach thread that was already detached or joined");
|
||||
}
|
||||
|
||||
self.threads[id].join_status = ThreadJoinStatus::Detached;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -362,18 +384,10 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
|
|||
joined_thread_id: ThreadId,
|
||||
data_race: Option<&mut data_race::GlobalState>,
|
||||
) -> InterpResult<'tcx> {
|
||||
if self.threads[joined_thread_id].join_status != ThreadJoinStatus::Joinable {
|
||||
throw_ub_format!("trying to join a detached or already joined thread");
|
||||
if self.threads[joined_thread_id].join_status == ThreadJoinStatus::Detached {
|
||||
throw_ub_format!("trying to join a detached thread");
|
||||
}
|
||||
if joined_thread_id == self.active_thread {
|
||||
throw_ub_format!("trying to join itself");
|
||||
}
|
||||
assert!(
|
||||
self.threads
|
||||
.iter()
|
||||
.all(|thread| thread.state != ThreadState::BlockedOnJoin(joined_thread_id)),
|
||||
"a joinable thread already has threads waiting for its termination"
|
||||
);
|
||||
|
||||
// Mark the joined thread as being joined so that we detect if other
|
||||
// threads try to join it.
|
||||
self.threads[joined_thread_id].join_status = ThreadJoinStatus::Joined;
|
||||
|
|
@ -394,6 +408,31 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Mark that the active thread tries to exclusively join the thread with `joined_thread_id`.
|
||||
/// If the thread is already joined by another thread, it will throw UB
|
||||
fn join_thread_exclusive(
|
||||
&mut self,
|
||||
joined_thread_id: ThreadId,
|
||||
data_race: Option<&mut data_race::GlobalState>,
|
||||
) -> InterpResult<'tcx> {
|
||||
if self.threads[joined_thread_id].join_status == ThreadJoinStatus::Joined {
|
||||
throw_ub_format!("trying to join an already joined thread");
|
||||
}
|
||||
|
||||
if joined_thread_id == self.active_thread {
|
||||
throw_ub_format!("trying to join itself");
|
||||
}
|
||||
|
||||
assert!(
|
||||
self.threads
|
||||
.iter()
|
||||
.all(|thread| thread.state != ThreadState::BlockedOnJoin(joined_thread_id)),
|
||||
"this thread already has threads waiting for its termination"
|
||||
);
|
||||
|
||||
self.join_thread(joined_thread_id, data_race)
|
||||
}
|
||||
|
||||
/// Set the name of the given thread.
|
||||
pub fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec<u8>) {
|
||||
self.threads[thread].thread_name = Some(new_thread_name);
|
||||
|
|
@ -624,9 +663,62 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn detach_thread(&mut self, thread_id: ThreadId) -> InterpResult<'tcx> {
|
||||
fn start_thread(
|
||||
&mut self,
|
||||
thread: Option<MPlaceTy<'tcx, Provenance>>,
|
||||
start_routine: Pointer<Option<Provenance>>,
|
||||
start_abi: Abi,
|
||||
func_arg: ImmTy<'tcx, Provenance>,
|
||||
ret_layout: TyAndLayout<'tcx>,
|
||||
) -> InterpResult<'tcx, ThreadId> {
|
||||
let this = self.eval_context_mut();
|
||||
this.machine.threads.detach_thread(thread_id)
|
||||
|
||||
// Create the new thread
|
||||
let new_thread_id = this.create_thread();
|
||||
|
||||
// Write the current thread-id, switch to the next thread later
|
||||
// to treat this write operation as occuring on the current thread.
|
||||
if let Some(thread_info_place) = thread {
|
||||
this.write_scalar(
|
||||
Scalar::from_uint(new_thread_id.to_u32(), thread_info_place.layout.size),
|
||||
&thread_info_place.into(),
|
||||
)?;
|
||||
}
|
||||
|
||||
// Finally switch to new thread so that we can push the first stackframe.
|
||||
// After this all accesses will be treated as occuring in the new thread.
|
||||
let old_thread_id = this.set_active_thread(new_thread_id);
|
||||
|
||||
// Perform the function pointer load in the new thread frame.
|
||||
let instance = this.get_ptr_fn(start_routine)?.as_instance()?;
|
||||
|
||||
// Note: the returned value is currently ignored (see the FIXME in
|
||||
// pthread_join in shims/unix/thread.rs) because the Rust standard library does not use
|
||||
// it.
|
||||
let ret_place = this.allocate(ret_layout, MiriMemoryKind::Machine.into())?;
|
||||
|
||||
this.call_function(
|
||||
instance,
|
||||
start_abi,
|
||||
&[*func_arg],
|
||||
Some(&ret_place.into()),
|
||||
StackPopCleanup::Root { cleanup: true },
|
||||
)?;
|
||||
|
||||
// Restore the old active thread frame.
|
||||
this.set_active_thread(old_thread_id);
|
||||
|
||||
Ok(new_thread_id)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn detach_thread(
|
||||
&mut self,
|
||||
thread_id: ThreadId,
|
||||
allow_terminated_joined: bool,
|
||||
) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
this.machine.threads.detach_thread(thread_id, allow_terminated_joined)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -636,6 +728,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn join_thread_exclusive(&mut self, joined_thread_id: ThreadId) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
this.machine
|
||||
.threads
|
||||
.join_thread_exclusive(joined_thread_id, this.machine.data_race.as_mut())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_active_thread(&mut self, thread_id: ThreadId) -> ThreadId {
|
||||
let this = self.eval_context_mut();
|
||||
|
|
@ -704,6 +805,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
|||
this.machine.threads.set_thread_name(thread, new_thread_name);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_thread_name_wide(&mut self, thread: ThreadId, new_thread_name: &[u16]) {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
// The Windows `GetThreadDescription` shim to get the thread name isn't implemented, so being lossy is okay.
|
||||
// This is only read by diagnostics, which already use `from_utf8_lossy`.
|
||||
this.machine
|
||||
.threads
|
||||
.set_thread_name(thread, String::from_utf16_lossy(new_thread_name).into_bytes());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_thread_name<'c>(&'c self, thread: ThreadId) -> &'c [u8]
|
||||
where
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@ignore-target-windows: No libc on Windows
|
||||
|
||||
//! The thread function must have exactly one argument.
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
error: Undefined Behavior: callee has fewer arguments than expected
|
||||
--> $DIR/too_few_args.rs:LL:CC
|
||||
--> $DIR/libc_pthread_create_too_few_args.rs:LL:CC
|
||||
|
|
||||
LL | panic!()
|
||||
| ^^^^^^^^ callee has fewer arguments than expected
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@ignore-target-windows: No libc on Windows
|
||||
|
||||
//! The thread function must have exactly one argument.
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
error: Undefined Behavior: callee has more arguments than expected
|
||||
--> $DIR/too_many_args.rs:LL:CC
|
||||
--> $DIR/libc_pthread_create_too_many_args.rs:LL:CC
|
||||
|
|
||||
LL | panic!()
|
||||
| ^^^^^^^^ callee has more arguments than expected
|
||||
|
|
@ -15,6 +15,6 @@ fn main() {
|
|||
// assert_eq!(libc::pthread_attr_init(&mut attr), 0); FIXME: this function is not yet implemented.
|
||||
assert_eq!(libc::pthread_create(&mut native, &attr, thread_start, ptr::null_mut()), 0);
|
||||
assert_eq!(libc::pthread_detach(native), 0);
|
||||
assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); //~ ERROR: Undefined Behavior: trying to join a detached or already joined thread
|
||||
assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); //~ ERROR: Undefined Behavior: trying to join a detached thread
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
error: Undefined Behavior: trying to join a detached or already joined thread
|
||||
error: Undefined Behavior: trying to join a detached thread
|
||||
--> $DIR/libc_pthread_join_detached.rs:LL:CC
|
||||
|
|
||||
LL | ... assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join a detached or already joined thread
|
||||
LL | assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join a detached thread
|
||||
|
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
|
|
|
|||
|
|
@ -15,6 +15,6 @@ fn main() {
|
|||
// assert_eq!(libc::pthread_attr_init(&mut attr), 0); FIXME: this function is not yet implemented.
|
||||
assert_eq!(libc::pthread_create(&mut native, &attr, thread_start, ptr::null_mut()), 0);
|
||||
assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
|
||||
assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); //~ ERROR: Undefined Behavior: trying to join a detached or already joined thread
|
||||
assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); //~ ERROR: Undefined Behavior: trying to join an already joined thread
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
error: Undefined Behavior: trying to join a detached or already joined thread
|
||||
error: Undefined Behavior: trying to join an already joined thread
|
||||
--> $DIR/libc_pthread_join_joined.rs:LL:CC
|
||||
|
|
||||
LL | ... assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join a detached or already joined thread
|
||||
LL | assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join an already joined thread
|
||||
|
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ fn main() {
|
|||
let thread_id: libc::pthread_t = unsafe { libc::pthread_self() };
|
||||
let handle = thread::spawn(move || {
|
||||
unsafe {
|
||||
assert_eq!(libc::pthread_join(thread_id, ptr::null_mut()), 0); //~ ERROR: Undefined Behavior: trying to join a detached or already joined thread
|
||||
assert_eq!(libc::pthread_join(thread_id, ptr::null_mut()), 0); //~ ERROR: Undefined Behavior: trying to join a detached thread
|
||||
}
|
||||
});
|
||||
thread::yield_now();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
error: Undefined Behavior: trying to join a detached or already joined thread
|
||||
error: Undefined Behavior: trying to join a detached thread
|
||||
--> $DIR/libc_pthread_join_main.rs:LL:CC
|
||||
|
|
||||
LL | ... assert_eq!(libc::pthread_join(thread_id, ptr::null_mut()), 0);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join a detached or already joined thread
|
||||
LL | assert_eq!(libc::pthread_join(thread_id, ptr::null_mut()), 0);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join a detached thread
|
||||
|
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ fn main() {
|
|||
let mut native_copy: libc::pthread_t = mem::zeroed();
|
||||
ptr::copy_nonoverlapping(&native, &mut native_copy, 1);
|
||||
let handle = thread::spawn(move || {
|
||||
assert_eq!(libc::pthread_join(native_copy, ptr::null_mut()), 0); //~ ERROR: Undefined Behavior: trying to join a detached or already joined thread
|
||||
assert_eq!(libc::pthread_join(native_copy, ptr::null_mut()), 0); //~ ERROR: Undefined Behavior: trying to join an already joined thread
|
||||
});
|
||||
assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
|
||||
handle.join().unwrap();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
error: Undefined Behavior: trying to join a detached or already joined thread
|
||||
error: Undefined Behavior: trying to join an already joined thread
|
||||
--> $DIR/libc_pthread_join_multiple.rs:LL:CC
|
||||
|
|
||||
LL | ... assert_eq!(libc::pthread_join(native_copy, ptr::null_mut()), 0);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join a detached or already joined thread
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join an already joined thread
|
||||
|
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
//@only-target-windows: Only Windows is not supported.
|
||||
|
||||
use std::thread;
|
||||
|
||||
//@error-pattern: can't create threads on Windows
|
||||
|
||||
fn main() {
|
||||
thread::spawn(|| {});
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
error: unsupported operation: can't create threads on Windows
|
||||
--> RUSTLIB/std/src/sys/PLATFORM/thread.rs:LL:CC
|
||||
|
|
||||
LL | let ret = c::CreateThread(
|
||||
| ___________________^
|
||||
LL | | ptr::null_mut(),
|
||||
LL | | stack,
|
||||
LL | | thread_start,
|
||||
... |
|
||||
LL | | ptr::null_mut(),
|
||||
LL | | );
|
||||
| |_________^ can't create threads on Windows
|
||||
|
|
||||
= help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
|
||||
= note: backtrace:
|
||||
= note: inside `std::sys::PLATFORM::thread::Thread::new` at RUSTLIB/std/src/sys/PLATFORM/thread.rs:LL:CC
|
||||
= note: inside `std::thread::Builder::spawn_unchecked_::<[closure@$DIR/thread-spawn.rs:LL:CC], ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
|
||||
= note: inside `std::thread::Builder::spawn_unchecked::<[closure@$DIR/thread-spawn.rs:LL:CC], ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
|
||||
= note: inside `std::thread::Builder::spawn::<[closure@$DIR/thread-spawn.rs:LL:CC], ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
|
||||
= note: inside `std::thread::spawn::<[closure@$DIR/thread-spawn.rs:LL:CC], ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
|
||||
note: inside `main` at $DIR/thread-spawn.rs:LL:CC
|
||||
--> $DIR/thread-spawn.rs:LL:CC
|
||||
|
|
||||
LL | thread::spawn(|| {});
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
//! Ensure that thread-local statics get deallocated when the thread dies.
|
||||
|
||||
#![feature(thread_local)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@ignore-target-windows: No libc on Windows
|
||||
|
||||
//@compile-flags: -Zmiri-disable-abi-check
|
||||
|
||||
//! Unwinding past the top frame of a stack is Undefined Behavior.
|
||||
|
|
|
|||
21
tests/fail/concurrency/windows_join_detached.rs
Normal file
21
tests/fail/concurrency/windows_join_detached.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//@only-target-windows: Uses win32 api functions
|
||||
//@error-pattern: Undefined Behavior: trying to join a detached thread
|
||||
|
||||
// Joining a detached thread is undefined behavior.
|
||||
|
||||
use std::os::windows::io::{AsRawHandle, RawHandle};
|
||||
use std::thread;
|
||||
|
||||
extern "system" {
|
||||
fn CloseHandle(handle: RawHandle) -> u32;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let thread = thread::spawn(|| ());
|
||||
|
||||
unsafe {
|
||||
assert_ne!(CloseHandle(thread.as_raw_handle()), 0);
|
||||
}
|
||||
|
||||
thread.join().unwrap();
|
||||
}
|
||||
22
tests/fail/concurrency/windows_join_detached.stderr
Normal file
22
tests/fail/concurrency/windows_join_detached.stderr
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
error: Undefined Behavior: trying to join a detached thread
|
||||
--> RUSTLIB/std/src/sys/PLATFORM/thread.rs:LL:CC
|
||||
|
|
||||
LL | let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle(), c::INFINITE) };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join a detached thread
|
||||
|
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
= note: backtrace:
|
||||
= note: inside `std::sys::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/PLATFORM/thread.rs:LL:CC
|
||||
= note: inside `std::thread::JoinInner::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
|
||||
= note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
|
||||
note: inside `main` at $DIR/windows_join_detached.rs:LL:CC
|
||||
--> $DIR/windows_join_detached.rs:LL:CC
|
||||
|
|
||||
LL | thread.join().unwrap();
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
28
tests/fail/concurrency/windows_join_main.rs
Normal file
28
tests/fail/concurrency/windows_join_main.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
//@only-target-windows: Uses win32 api functions
|
||||
// We are making scheduler assumptions here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
|
||||
// On windows, joining main is not UB, but it will block a thread forever.
|
||||
|
||||
use std::thread;
|
||||
|
||||
extern "system" {
|
||||
fn WaitForSingleObject(handle: isize, timeout: u32) -> u32;
|
||||
}
|
||||
|
||||
const INFINITE: u32 = u32::MAX;
|
||||
|
||||
// XXX HACK: This is how miri represents the handle for thread 0.
|
||||
// This value can be "legitimately" obtained by using `GetCurrentThread` with `DuplicateHandle`
|
||||
// but miri does not implement `DuplicateHandle` yet.
|
||||
const MAIN_THREAD: isize = (2i32 << 30) as isize;
|
||||
|
||||
fn main() {
|
||||
thread::spawn(|| {
|
||||
unsafe {
|
||||
assert_eq!(WaitForSingleObject(MAIN_THREAD, INFINITE), 0); //~ ERROR: deadlock: the evaluated program deadlocked
|
||||
}
|
||||
})
|
||||
.join()
|
||||
.unwrap();
|
||||
}
|
||||
13
tests/fail/concurrency/windows_join_main.stderr
Normal file
13
tests/fail/concurrency/windows_join_main.stderr
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
error: deadlock: the evaluated program deadlocked
|
||||
--> $DIR/windows_join_main.rs:LL:CC
|
||||
|
|
||||
LL | assert_eq!(WaitForSingleObject(MAIN_THREAD, INFINITE), 0);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program deadlocked
|
||||
|
|
||||
= note: inside closure at RUSTLIB/core/src/macros/mod.rs:LL:CC
|
||||
= note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
25
tests/fail/concurrency/windows_join_self.rs
Normal file
25
tests/fail/concurrency/windows_join_self.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
//@only-target-windows: Uses win32 api functions
|
||||
// We are making scheduler assumptions here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
|
||||
// On windows, a thread joining itself is not UB, but it will deadlock.
|
||||
|
||||
use std::thread;
|
||||
|
||||
extern "system" {
|
||||
fn GetCurrentThread() -> usize;
|
||||
fn WaitForSingleObject(handle: usize, timeout: u32) -> u32;
|
||||
}
|
||||
|
||||
const INFINITE: u32 = u32::MAX;
|
||||
|
||||
fn main() {
|
||||
thread::spawn(|| {
|
||||
unsafe {
|
||||
let native = GetCurrentThread();
|
||||
assert_eq!(WaitForSingleObject(native, INFINITE), 0); //~ ERROR: deadlock: the evaluated program deadlocked
|
||||
}
|
||||
})
|
||||
.join()
|
||||
.unwrap();
|
||||
}
|
||||
12
tests/fail/concurrency/windows_join_self.stderr
Normal file
12
tests/fail/concurrency/windows_join_self.stderr
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
error: deadlock: the evaluated program deadlocked
|
||||
--> $DIR/windows_join_self.rs:LL:CC
|
||||
|
|
||||
LL | assert_eq!(WaitForSingleObject(native, INFINITE), 0);
|
||||
| ^ the evaluated program deadlocked
|
||||
|
|
||||
= note: inside closure at $DIR/windows_join_self.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
|
||||
#![feature(new_uninit)]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
|
||||
#![feature(new_uninit)]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// We want to control preemption here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::thread::spawn;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// We want to control preemption here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// We want to control preemption here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// We want to control preemption here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::thread::spawn;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// We want to control preemption here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::thread::spawn;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// We want to control preemption here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// We want to control preemption here.
|
||||
//@compile-flags: -Zmiri-disable-isolation -Zmiri-preemption-rate=0
|
||||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
use std::mem;
|
||||
use std::thread::{sleep, spawn};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// We want to control preemption here.
|
||||
//@compile-flags: -Zmiri-disable-isolation -Zmiri-preemption-rate=0
|
||||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
use std::mem;
|
||||
use std::thread::{sleep, spawn};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// We want to control preemption here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
use std::thread::spawn;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// We want to control preemption here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
use std::thread::spawn;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
|
||||
|
||||
use std::ptr::null_mut;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// We want to control preemption here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
use std::thread::spawn;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// We want to control preemption here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
use std::thread::spawn;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
|
||||
|
||||
use std::ptr::null_mut;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// We want to control preemption here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
use std::thread::spawn;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// We want to control preemption here.
|
||||
//@compile-flags: -Zmiri-disable-isolation -Zmiri-preemption-rate=0
|
||||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
use std::sync::atomic::{fence, AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// We want to control preemption here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
use std::thread::spawn;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-disable-isolation -Zmir-opt-level=0 -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
|
||||
|
||||
// Note: mir-opt-level set to 0 to prevent the read of stack_var in thread 1
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// We want to control preemption here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
use std::thread::spawn;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
|
||||
|
||||
use std::ptr::null_mut;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-ignore-leaks
|
||||
|
||||
// https://plv.mpi-sws.org/scfix/paper.pdf
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// We want to control preemption here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
#![feature(core_intrinsics)]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// We want to control preemption here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
use std::sync::atomic::Ordering::*;
|
||||
use std::sync::atomic::{AtomicU16, AtomicU32};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-ignore-leaks -Zmiri-disable-stacked-borrows
|
||||
|
||||
// The following tests check whether our weak memory emulation produces
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@ignore-target-windows: Channels on Windows are not supported yet.
|
||||
//@compile-flags: -Zmiri-strict-provenance
|
||||
|
||||
use std::sync::mpsc::{channel, sync_channel};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
use std::panic::Location;
|
||||
use std::thread::spawn;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
|
||||
|
||||
use std::sync::atomic::{fence, AtomicUsize, Ordering};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-disable-data-race-detector
|
||||
|
||||
use std::thread::spawn;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
|
||||
use std::thread::spawn;
|
||||
|
||||
fn initialize() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-strict-provenance
|
||||
|
||||
use std::thread;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::thread;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@ignore-target-windows: Channels on Windows are not supported yet.
|
||||
// This specifically tests behavior *without* preemption.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-strict-provenance
|
||||
|
||||
//! The main purpose of this test is to check that if we take a pointer to
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@ignore-target-windows: TLS destructor order is different on Windows.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::thread;
|
||||
|
|
|
|||
191
tests/pass/concurrency/tls_lib_drop_windows.rs
Normal file
191
tests/pass/concurrency/tls_lib_drop_windows.rs
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
//@only-target-windows: TLS destructor order is different on Windows.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::thread;
|
||||
|
||||
struct TestCell {
|
||||
value: RefCell<u8>,
|
||||
}
|
||||
|
||||
impl Drop for TestCell {
|
||||
fn drop(&mut self) {
|
||||
for _ in 0..10 {
|
||||
thread::yield_now();
|
||||
}
|
||||
println!("Dropping: {} (should be before 'Continue main 1').", *self.value.borrow())
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static A: TestCell = TestCell { value: RefCell::new(0) };
|
||||
static A_CONST: TestCell = const { TestCell { value: RefCell::new(10) } };
|
||||
}
|
||||
|
||||
/// Check that destructors of the library thread locals are executed immediately
|
||||
/// after a thread terminates.
|
||||
fn check_destructors() {
|
||||
thread::spawn(|| {
|
||||
A.with(|f| {
|
||||
assert_eq!(*f.value.borrow(), 0);
|
||||
*f.value.borrow_mut() = 5;
|
||||
});
|
||||
A_CONST.with(|f| {
|
||||
assert_eq!(*f.value.borrow(), 10);
|
||||
*f.value.borrow_mut() = 15;
|
||||
});
|
||||
})
|
||||
.join()
|
||||
.unwrap();
|
||||
println!("Continue main 1.")
|
||||
}
|
||||
|
||||
struct JoinCell {
|
||||
value: RefCell<Option<thread::JoinHandle<u8>>>,
|
||||
}
|
||||
|
||||
impl Drop for JoinCell {
|
||||
fn drop(&mut self) {
|
||||
for _ in 0..10 {
|
||||
thread::yield_now();
|
||||
}
|
||||
let join_handle = self.value.borrow_mut().take().unwrap();
|
||||
println!("Joining: {} (should be before 'Continue main 2').", join_handle.join().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static B: JoinCell = JoinCell { value: RefCell::new(None) };
|
||||
}
|
||||
|
||||
/// Check that the destructor can be blocked joining another thread.
|
||||
fn check_blocking() {
|
||||
thread::spawn(|| {
|
||||
B.with(|f| {
|
||||
assert!(f.value.borrow().is_none());
|
||||
let handle = thread::spawn(|| 7);
|
||||
*f.value.borrow_mut() = Some(handle);
|
||||
});
|
||||
})
|
||||
.join()
|
||||
.unwrap();
|
||||
println!("Continue main 2.");
|
||||
// Preempt the main thread so that the destructor gets executed and can join
|
||||
// the thread.
|
||||
thread::yield_now();
|
||||
thread::yield_now();
|
||||
}
|
||||
|
||||
// This test tests that TLS destructors have run before the thread joins. The
|
||||
// test has no false positives (meaning: if the test fails, there's actually
|
||||
// an ordering problem). It may have false negatives, where the test passes but
|
||||
// join is not guaranteed to be after the TLS destructors. However, false
|
||||
// negatives should be exceedingly rare due to judicious use of
|
||||
// thread::yield_now and running the test several times.
|
||||
fn join_orders_after_tls_destructors() {
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
|
||||
// We emulate a synchronous MPSC rendezvous channel using only atomics and
|
||||
// thread::yield_now. We can't use std::mpsc as the implementation itself
|
||||
// may rely on thread locals.
|
||||
//
|
||||
// The basic state machine for an SPSC rendezvous channel is:
|
||||
// FRESH -> THREAD1_WAITING -> MAIN_THREAD_RENDEZVOUS
|
||||
// where the first transition is done by the “receiving” thread and the 2nd
|
||||
// transition is done by the “sending” thread.
|
||||
//
|
||||
// We add an additional state `THREAD2_LAUNCHED` between `FRESH` and
|
||||
// `THREAD1_WAITING` to block until all threads are actually running.
|
||||
//
|
||||
// A thread that joins on the “receiving” thread completion should never
|
||||
// observe the channel in the `THREAD1_WAITING` state. If this does occur,
|
||||
// we switch to the “poison” state `THREAD2_JOINED` and panic all around.
|
||||
// (This is equivalent to “sending” from an alternate producer thread.)
|
||||
const FRESH: u8 = 0;
|
||||
const THREAD2_LAUNCHED: u8 = 1;
|
||||
const THREAD1_WAITING: u8 = 2;
|
||||
const MAIN_THREAD_RENDEZVOUS: u8 = 3;
|
||||
const THREAD2_JOINED: u8 = 4;
|
||||
static SYNC_STATE: AtomicU8 = AtomicU8::new(FRESH);
|
||||
|
||||
for _ in 0..10 {
|
||||
SYNC_STATE.store(FRESH, Ordering::SeqCst);
|
||||
|
||||
let jh = thread::Builder::new()
|
||||
.name("thread1".into())
|
||||
.spawn(move || {
|
||||
struct TlDrop;
|
||||
|
||||
impl Drop for TlDrop {
|
||||
fn drop(&mut self) {
|
||||
let mut sync_state = SYNC_STATE.swap(THREAD1_WAITING, Ordering::SeqCst);
|
||||
loop {
|
||||
match sync_state {
|
||||
THREAD2_LAUNCHED | THREAD1_WAITING => thread::yield_now(),
|
||||
MAIN_THREAD_RENDEZVOUS => break,
|
||||
THREAD2_JOINED =>
|
||||
panic!(
|
||||
"Thread 1 still running after thread 2 joined on thread 1"
|
||||
),
|
||||
v => unreachable!("sync state: {}", v),
|
||||
}
|
||||
sync_state = SYNC_STATE.load(Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static TL_DROP: TlDrop = TlDrop;
|
||||
}
|
||||
|
||||
TL_DROP.with(|_| {});
|
||||
|
||||
loop {
|
||||
match SYNC_STATE.load(Ordering::SeqCst) {
|
||||
FRESH => thread::yield_now(),
|
||||
THREAD2_LAUNCHED => break,
|
||||
v => unreachable!("sync state: {}", v),
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let jh2 = thread::Builder::new()
|
||||
.name("thread2".into())
|
||||
.spawn(move || {
|
||||
assert_eq!(SYNC_STATE.swap(THREAD2_LAUNCHED, Ordering::SeqCst), FRESH);
|
||||
jh.join().unwrap();
|
||||
match SYNC_STATE.swap(THREAD2_JOINED, Ordering::SeqCst) {
|
||||
MAIN_THREAD_RENDEZVOUS => return,
|
||||
THREAD2_LAUNCHED | THREAD1_WAITING => {
|
||||
panic!("Thread 2 running after thread 1 join before main thread rendezvous")
|
||||
}
|
||||
v => unreachable!("sync state: {:?}", v),
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
loop {
|
||||
match SYNC_STATE.compare_exchange(
|
||||
THREAD1_WAITING,
|
||||
MAIN_THREAD_RENDEZVOUS,
|
||||
Ordering::SeqCst,
|
||||
Ordering::SeqCst,
|
||||
) {
|
||||
Ok(_) => break,
|
||||
Err(FRESH) => thread::yield_now(),
|
||||
Err(THREAD2_LAUNCHED) => thread::yield_now(),
|
||||
Err(THREAD2_JOINED) => {
|
||||
panic!("Main thread rendezvous after thread 2 joined thread 1")
|
||||
}
|
||||
v => unreachable!("sync state: {:?}", v),
|
||||
}
|
||||
}
|
||||
jh2.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
check_destructors();
|
||||
check_blocking();
|
||||
join_orders_after_tls_destructors();
|
||||
}
|
||||
5
tests/pass/concurrency/tls_lib_drop_windows.stdout
Normal file
5
tests/pass/concurrency/tls_lib_drop_windows.stdout
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
Dropping: 15 (should be before 'Continue main 1').
|
||||
Dropping: 5 (should be before 'Continue main 1').
|
||||
Continue main 1.
|
||||
Joining: 7 (should be before 'Continue main 2').
|
||||
Continue main 2.
|
||||
21
tests/pass/concurrency/windows_detach_terminated.rs
Normal file
21
tests/pass/concurrency/windows_detach_terminated.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//@only-target-windows: Uses win32 api functions
|
||||
// We are making scheduler assumptions here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
|
||||
use std::os::windows::io::IntoRawHandle;
|
||||
use std::thread;
|
||||
|
||||
extern "system" {
|
||||
fn CloseHandle(handle: usize) -> i32;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let thread = thread::spawn(|| {}).into_raw_handle() as usize;
|
||||
|
||||
// this yield ensures that `thread` is terminated by this point
|
||||
thread::yield_now();
|
||||
|
||||
unsafe {
|
||||
assert_ne!(CloseHandle(thread), 0);
|
||||
}
|
||||
}
|
||||
41
tests/pass/concurrency/windows_join_multiple.rs
Normal file
41
tests/pass/concurrency/windows_join_multiple.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
//@only-target-windows: Uses win32 api functions
|
||||
// We are making scheduler assumptions here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
|
||||
use std::os::windows::io::IntoRawHandle;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::thread;
|
||||
|
||||
extern "system" {
|
||||
fn WaitForSingleObject(handle: usize, timeout: u32) -> u32;
|
||||
}
|
||||
|
||||
const INFINITE: u32 = u32::MAX;
|
||||
|
||||
fn main() {
|
||||
static FLAG: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
let blocker = thread::spawn(|| {
|
||||
while !FLAG.load(Ordering::Relaxed) {
|
||||
thread::yield_now();
|
||||
}
|
||||
})
|
||||
.into_raw_handle() as usize;
|
||||
|
||||
let waiter = move || {
|
||||
unsafe {
|
||||
assert_eq!(WaitForSingleObject(blocker, INFINITE), 0);
|
||||
}
|
||||
};
|
||||
|
||||
let waiter1 = thread::spawn(waiter);
|
||||
let waiter2 = thread::spawn(waiter);
|
||||
|
||||
// this yield ensures `waiter1` & `waiter2` are blocked on `blocker` by this point
|
||||
thread::yield_now();
|
||||
|
||||
FLAG.store(true, Ordering::Relaxed);
|
||||
|
||||
waiter1.join().unwrap();
|
||||
waiter2.join().unwrap();
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@ignore-target-windows: Condvars on Windows are not supported yet.
|
||||
// We are making scheduler assumptions here.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ fn duration_sanity(diff: Duration) {
|
|||
assert!(diff.as_millis() < 500);
|
||||
}
|
||||
|
||||
// Sleeping on Windows is not supported yet.
|
||||
#[cfg(unix)]
|
||||
fn test_sleep() {
|
||||
let before = Instant::now();
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
|
|
@ -50,6 +48,5 @@ fn main() {
|
|||
assert_eq!(now2 - diff, now1);
|
||||
duration_sanity(diff);
|
||||
|
||||
#[cfg(unix)]
|
||||
test_sleep();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@ignore-target-windows: Channels on Windows are not supported yet.
|
||||
// FIXME: disallow preemption to work around https://github.com/rust-lang/rust/issues/55005
|
||||
//@compile-flags: -Zmiri-ignore-leaks -Zmiri-preemption-rate=0
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-ignore-leaks
|
||||
|
||||
// Tests operations not perfomable through C++'s atomic API
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-ignore-leaks
|
||||
|
||||
// Tests operations not perfomable through C++'s atomic API
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@ignore-target-windows: Concurrency on Windows is not supported yet.
|
||||
//@compile-flags: -Zmiri-ignore-leaks -Zmiri-preemption-rate=0
|
||||
|
||||
// Tests showing weak memory behaviours are exhibited. All tests
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue