From b6fc2fc82a90ee1deb4a65c811764c610155b6b9 Mon Sep 17 00:00:00 2001 From: DrMeepster <19316085+DrMeepster@users.noreply.github.com> Date: Wed, 25 May 2022 16:28:37 -0700 Subject: [PATCH 1/5] basic theading --- src/lib.rs | 2 + src/shims/tls.rs | 16 +- src/shims/unix/thread.rs | 43 +---- src/shims/windows/dlsym.rs | 20 ++- src/shims/windows/foreign_items.rs | 102 ++++++----- src/shims/windows/handle.rs | 162 ++++++++++++++++++ src/shims/windows/mod.rs | 2 + src/shims/windows/thread.rs | 84 +++++++++ src/thread.rs | 113 +++++++++--- ...rs => libc_pthread_create_too_few_args.rs} | 2 +- ...> libc_pthread_create_too_few_args.stderr} | 2 +- ...s => libc_pthread_create_too_many_args.rs} | 2 +- ... libc_pthread_create_too_many_args.stderr} | 2 +- .../concurrency/libc_pthread_join_detached.rs | 2 +- .../libc_pthread_join_detached.stderr | 2 +- tests/fail/concurrency/thread-spawn.rs | 9 - tests/fail/concurrency/thread-spawn.stderr | 30 ---- .../thread_local_static_dealloc.rs | 2 - tests/fail/concurrency/unwind_top_of_stack.rs | 3 +- tests/fail/data_race/alloc_read_race.rs | 1 - tests/fail/data_race/alloc_write_race.rs | 1 - .../data_race/atomic_read_na_write_race1.rs | 1 - .../data_race/atomic_read_na_write_race2.rs | 1 - .../data_race/atomic_write_na_read_race1.rs | 1 - .../data_race/atomic_write_na_read_race2.rs | 1 - .../data_race/atomic_write_na_write_race1.rs | 1 - .../data_race/atomic_write_na_write_race2.rs | 1 - .../data_race/dangling_thread_async_race.rs | 1 - tests/fail/data_race/dangling_thread_race.rs | 1 - tests/fail/data_race/dealloc_read_race1.rs | 1 - tests/fail/data_race/dealloc_read_race2.rs | 1 - .../fail/data_race/dealloc_read_race_stack.rs | 1 - tests/fail/data_race/dealloc_write_race1.rs | 1 - tests/fail/data_race/dealloc_write_race2.rs | 1 - .../data_race/dealloc_write_race_stack.rs | 1 - .../data_race/enable_after_join_to_main.rs | 1 - tests/fail/data_race/fence_after_load.rs | 1 - tests/fail/data_race/read_write_race.rs | 1 - tests/fail/data_race/read_write_race_stack.rs | 1 - tests/fail/data_race/relax_acquire_race.rs | 1 - tests/fail/data_race/release_seq_race.rs | 1 - .../data_race/release_seq_race_same_thread.rs | 1 - tests/fail/data_race/rmw_race.rs | 1 - tests/fail/data_race/write_write_race.rs | 1 - .../fail/data_race/write_write_race_stack.rs | 1 - tests/fail/should-pass/cpp20_rwc_syncs.rs | 1 - tests/fail/weak_memory/racing_mixed_size.rs | 1 - .../weak_memory/racing_mixed_size_read.rs | 1 - tests/pass/0weak_memory_consistency.rs | 1 - tests/pass/concurrency/channels.rs | 2 +- .../concurrency/concurrent_caller_location.rs | 2 - tests/pass/concurrency/data_race.rs | 1 - .../concurrency/disable_data_race_detector.rs | 1 - tests/pass/concurrency/issue1643.rs | 2 - tests/pass/concurrency/simple.rs | 1 - tests/pass/concurrency/spin_loop.rs | 1 - .../pass/concurrency/spin_loops_nopreempt.rs | 2 +- tests/pass/concurrency/thread_locals.rs | 1 - tests/pass/concurrency/tls_lib_drop.rs | 2 - tests/pass/panic/concurrent-panic.rs | 2 +- tests/pass/shims/time.rs | 3 - tests/pass/threadleak_ignored.rs | 2 +- tests/pass/weak_memory/extra_cpp.rs | 1 - tests/pass/weak_memory/extra_cpp_unsafe.rs | 1 - tests/pass/weak_memory/weak.rs | 1 - 65 files changed, 448 insertions(+), 207 deletions(-) create mode 100644 src/shims/windows/handle.rs create mode 100644 src/shims/windows/thread.rs rename tests/fail/concurrency/{too_few_args.rs => libc_pthread_create_too_few_args.rs} (92%) rename tests/fail/concurrency/{too_few_args.stderr => libc_pthread_create_too_few_args.stderr} (92%) rename tests/fail/concurrency/{too_many_args.rs => libc_pthread_create_too_many_args.rs} (92%) rename tests/fail/concurrency/{too_many_args.stderr => libc_pthread_create_too_many_args.stderr} (92%) delete mode 100644 tests/fail/concurrency/thread-spawn.rs delete mode 100644 tests/fail/concurrency/thread-spawn.stderr diff --git a/src/lib.rs b/src/lib.rs index ba337f2831..73c96b2e59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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)] diff --git a/src/shims/tls.rs b/src/shims/tls.rs index 633e0322bb..8627d9a044 100644 --- a/src/shims/tls.rs +++ b/src/shims/tls.rs @@ -229,25 +229,25 @@ 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()?; + // Technically, the reason should be `DLL_PROCESS_DETACH` when the main thread exits but std ignores it. + 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"])?; this.call_function( thread_callback, Abi::System { unwind: false }, diff --git a/src/shims/unix/thread.rs b/src/shims/unix/thread.rs index 0df70543fa..094183b3e7 100644 --- a/src/shims/unix/thread.rs +++ b/src/shims/unix/thread.rs @@ -13,47 +13,14 @@ 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 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), + start_routine, Abi::C { unwind: false }, - &[*func_arg], - Some(&ret_place.into()), - StackPopCleanup::Root { cleanup: true }, + arg, + this.layout_of(this.tcx.types.usize)?, )?; - // Restore the old active thread frame. - this.set_active_thread(old_thread_id); - Ok(0) } diff --git a/src/shims/windows/dlsym.rs b/src/shims/windows/dlsym.rs index eab5f99c87..f18e27d38c 100644 --- a/src/shims/windows/dlsym.rs +++ b/src/shims/windows/dlsym.rs @@ -5,11 +5,13 @@ use rustc_target::spec::abi::Abi; use log::trace; use crate::helpers::check_arg_count; +use crate::shims::windows::handle::Handle; 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> { 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,22 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx dest, )?; } + Dlsym::SetThreadDescription => { + let [handle, name] = check_arg_count(args)?; + + let name = this.read_wide_str(this.read_pointer(name)?)?; + + let thread = + match Handle::from_scalar(this.read_scalar(handle)?.check_init()?, this)? { + Some(Handle::Thread(thread)) => thread, + Some(Handle::CurrentThread) => this.get_active_thread(), + _ => throw_ub_format!("invalid handle"), + }; + + this.set_thread_name_wide(thread, name); + + this.write_null(dest)?; + } } trace!("{:?}", this.dump_place(**dest)); diff --git a/src/shims/windows/foreign_items.rs b/src/shims/windows/foreign_items.rs index 6520609b76..00a80f8697 100644 --- a/src/shims/windows/foreign_items.rs +++ b/src/shims/windows/foreign_items.rs @@ -1,12 +1,17 @@ use std::iter; +use std::time::{Duration, Instant}; use rustc_span::Symbol; use rustc_target::abi::Size; use rustc_target::spec::abi::Abi; +use crate::thread::Time; use crate::*; use shims::foreign_items::EmulateByNameResult; +use shims::windows::handle::{EvalContextExt as _, Handle}; 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 +224,29 @@ 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.check_no_isolation("`Sleep`")?; + + let timeout_ms = this.read_scalar(timeout)?.to_u32()?; + + let duration = Duration::from_millis(timeout_ms as u64); + 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(()) + }), + ); + } // Synchronization primitives "AcquireSRWLockExclusive" => { @@ -314,10 +342,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx // FIXME: we should set last_error, but to what? this.write_null(dest)?; } - "SwitchToThread" => { + // 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)?; - // 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.yield_active_thread(); + + // FIXME: this should return a nonzero value if this call does result in switching to another thread. this.write_null(dest)?; } "GetStdHandle" => { @@ -329,14 +360,37 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx // std-only shim. this.write_scalar(Scalar::from_machine_isize(which.into(), this), dest)?; } + "CloseHandle" => { + let [handle] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; - // Better error for attempts to create a thread + this.CloseHandle(handle)?; + + this.write_scalar(Scalar::from_u32(1), dest)?; + } + + // Threading "CreateThread" => { - let [_, _, _, _, _, _] = + let [security, stacksize, start, arg, flags, thread] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; - this.handle_unsupported("can't create threads on Windows")?; - return Ok(EmulateByNameResult::AlreadyJumped); + 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)?; + + this.WaitForSingleObject(handle, timeout)?; + + this.write_scalar(Scalar::from_u32(0), dest)?; + } + "GetCurrentThread" => { + let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + + this.write_scalar(Handle::CurrentThread.to_scalar(this), dest)?; } // Incomplete shims that we "stub out" just to get pre-main initialization code to work. @@ -374,40 +428,6 @@ 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()?; diff --git a/src/shims/windows/handle.rs b/src/shims/windows/handle.rs new file mode 100644 index 0000000000..ff64a958da --- /dev/null +++ b/src/shims/windows/handle.rs @@ -0,0 +1,162 @@ +use rustc_target::abi::HasDataLayout; +use std::mem::variant_count; + +use crate::*; + +/// A Windows `HANDLE` that represents a resource instead of being null or a pseudohandle. +/// +/// This is a seperate type from [`Handle`] to simplify the packing and unpacking code. +#[derive(Clone, Copy)] +enum RealHandle { + Thread(ThreadId), +} + +impl RealHandle { + const USABLE_BITS: u32 = 31; + + const THREAD_DISCRIMINANT: u32 = 1; + + fn discriminant(self) -> u32 { + match self { + // can't use zero here because all zero handle is invalid + Self::Thread(_) => Self::THREAD_DISCRIMINANT, + } + } + + fn data(self) -> u32 { + match self { + Self::Thread(thread) => thread.to_u32(), + } + } + + fn packed_disc_size() -> u32 { + // log2(x) + 1 is how many bits it takes to store x + // because the discriminants start at 1, the variant count is equal to the highest discriminant + variant_count::().ilog2() + 1 + } + + /// This function packs the discriminant and data values into a 31-bit space. + /// None of this layout is guaranteed to applications by Windows or Miri. + /// The sign bit is not used to avoid overlapping any pseudo-handles. + fn to_packed(self) -> i32 { + let disc_size = Self::packed_disc_size(); + let data_size = Self::USABLE_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) as i32 + } + + fn new(discriminant: u32, data: u32) -> Option { + match discriminant { + Self::THREAD_DISCRIMINANT => Some(Self::Thread(data.into())), + _ => None, + } + } + + /// see docs for `to_packed` + fn from_packed(handle: i32) -> Option { + let handle_bits = handle as u32; + + let disc_size = Self::packed_disc_size(); + let data_size = Self::USABLE_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_bits >> data_size; + + // the data is stored in the lower `data_size` bits + let data = handle_bits & data_mask; + + Self::new(discriminant, data) + } +} + +/// Miri representation of a Windows `HANDLE` +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Handle { + Null, // = 0 + + // pseudo-handles + // The lowest real windows pseudo-handle is -6, so miri pseduo-handles start at -7 to break code hardcoding these values + CurrentThread, // = -7 + + // real handles + Thread(ThreadId), +} + +impl Handle { + const CURRENT_THREAD_VALUE: i32 = -7; + + fn to_packed(self) -> i32 { + match self { + Self::Null => 0, + Self::CurrentThread => Self::CURRENT_THREAD_VALUE, + Self::Thread(thread) => RealHandle::Thread(thread).to_packed(), + } + } + + pub fn to_scalar(self, cx: &impl HasDataLayout) -> Scalar { + // 64-bit handles are sign extended 32-bit handles + // see https://docs.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication + let handle = self.to_packed().into(); + + Scalar::from_machine_isize(handle, cx) + } + + fn from_packed(handle: i64) -> Option { + let current_thread_val = Self::CURRENT_THREAD_VALUE as i64; + + if handle == 0 { + Some(Self::Null) + } else if handle == current_thread_val { + Some(Self::CurrentThread) + } else if let Ok(handle) = handle.try_into() { + match RealHandle::from_packed(handle)? { + RealHandle::Thread(id) => Some(Self::Thread(id)), + } + } else { + // if a handle doesn't fit in an i32, it isn't valid. + None + } + } + + pub fn from_scalar<'tcx>( + handle: Scalar, + cx: &impl HasDataLayout, + ) -> InterpResult<'tcx, Option> { + let handle = handle.to_machine_isize(cx)?; + + 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 CloseHandle(&mut self, handle_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + match Handle::from_scalar(this.read_scalar(handle_op)?.check_init()?, this)? { + Some(Handle::Thread(thread)) => this.detach_thread(thread)?, + _ => + throw_machine_stop!(TerminationInfo::Abort( + "invalid handle passed to `CloseHandle`".into() + )), + }; + + Ok(()) + } +} diff --git a/src/shims/windows/mod.rs b/src/shims/windows/mod.rs index 668d69966b..40fe71b2db 100644 --- a/src/shims/windows/mod.rs +++ b/src/shims/windows/mod.rs @@ -1,4 +1,6 @@ pub mod dlsym; pub mod foreign_items; +mod handle; mod sync; +mod thread; diff --git a/src/shims/windows/thread.rs b/src/shims/windows/thread.rs new file mode 100644 index 0000000000..66cd9dd034 --- /dev/null +++ b/src/shims/windows/thread.rs @@ -0,0 +1,84 @@ +use std::time::{Duration, Instant}; + +use rustc_middle::ty::layout::LayoutOf; +use rustc_target::spec::abi::Abi; + +use crate::thread::Time; +use crate::*; +use shims::windows::handle::Handle; + +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(); + + if !this.ptr_is_null(this.read_pointer(security_op)?)? { + throw_unsup_format!("non-null `lpThreadAttributes` in `CreateThread`") + } + + // stacksize is ignored, but still needs to be a valid usize + let _ = this.read_scalar(stacksize_op)?.to_machine_usize(this)?; + + let flags = this.read_scalar(flags_op)?.to_u32()?; + + let stack_size_param_is_a_reservation = + this.eval_windows("c", "STACK_SIZE_PARAM_IS_A_RESERVATION")?.to_u32()?; + + if flags != 0 && flags != stack_size_param_is_a_reservation { + throw_unsup_format!("unsupported `dwCreationFlags` {} in `CreateThread`", flags) + } + + let thread = + if this.ptr_is_null(this.read_pointer(thread_op)?)? { None } else { Some(thread_op) }; + + this.start_thread( + thread, + start_op, + Abi::System { unwind: false }, + arg_op, + this.layout_of(this.tcx.types.u32)?, + ) + } + + fn WaitForSingleObject( + &mut self, + handle: &OpTy<'tcx, Provenance>, + timeout: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let thread = match Handle::from_scalar(this.read_scalar(handle)?.check_init()?, this)? { + Some(Handle::Thread(thread)) => thread, + Some(Handle::CurrentThread) => throw_ub_format!("trying to wait on itself"), + _ => throw_ub_format!("invalid handle"), + }; + + if this.read_scalar(timeout)?.to_u32()? != this.eval_windows("c", "INFINITE")?.to_u32()? { + this.check_no_isolation("`WaitForSingleObject` with non-infinite timeout")?; + } + + let timeout_ms = this.read_scalar(timeout)?.to_u32()?; + + let timeout_time = if timeout_ms == this.eval_windows("c", "INFINITE")?.to_u32()? { + None + } else { + let duration = Duration::from_millis(timeout_ms as u64); + + Some(Time::Monotonic(Instant::now().checked_add(duration).unwrap())) + }; + + this.wait_on_thread(timeout_time, thread)?; + + Ok(()) + } +} diff --git a/src/thread.rs b/src/thread.rs index 6f394fa42f..fa70dcfa2a 100644 --- a/src/thread.rs +++ b/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; @@ -179,7 +181,7 @@ impl<'mir, 'tcx> Thread<'mir, 'tcx> { } /// A specific moment in time. -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub enum Time { Monotonic(Instant), RealTime(SystemTime), @@ -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> { @@ -348,10 +354,23 @@ 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`. + 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 + { + 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 +381,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 joined_thread_id == self.active_thread { - throw_ub_format!("trying to join itself"); + if self.threads[joined_thread_id].join_status == ThreadJoinStatus::Detached { + throw_ub_format!("trying to join a detached thread"); } - 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; @@ -624,9 +635,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>, + start_routine: Pointer>, + start_abi: Abi, + func_arg: ImmTy<'tcx, Provenance>, + ret_layout: TyAndLayout<'tcx>, + ) -> InterpResult<'tcx, ThreadId> { + 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. + 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) + this.machine.threads.detach_thread(thread_id, allow_terminated_joined) } #[inline] @@ -704,6 +768,15 @@ 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: Vec) { + let this = self.eval_context_mut(); + this.machine.threads.set_thread_name( + thread, + new_thread_name.into_iter().flat_map(u16::to_ne_bytes).collect(), + ); + } + #[inline] fn get_thread_name<'c>(&'c self, thread: ThreadId) -> &'c [u8] where diff --git a/tests/fail/concurrency/too_few_args.rs b/tests/fail/concurrency/libc_pthread_create_too_few_args.rs similarity index 92% rename from tests/fail/concurrency/too_few_args.rs rename to tests/fail/concurrency/libc_pthread_create_too_few_args.rs index 43c7c74d41..e1d3704af7 100644 --- a/tests/fail/concurrency/too_few_args.rs +++ b/tests/fail/concurrency/libc_pthread_create_too_few_args.rs @@ -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. diff --git a/tests/fail/concurrency/too_few_args.stderr b/tests/fail/concurrency/libc_pthread_create_too_few_args.stderr similarity index 92% rename from tests/fail/concurrency/too_few_args.stderr rename to tests/fail/concurrency/libc_pthread_create_too_few_args.stderr index c1eb4d8cb6..2304b42b2c 100644 --- a/tests/fail/concurrency/too_few_args.stderr +++ b/tests/fail/concurrency/libc_pthread_create_too_few_args.stderr @@ -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 diff --git a/tests/fail/concurrency/too_many_args.rs b/tests/fail/concurrency/libc_pthread_create_too_many_args.rs similarity index 92% rename from tests/fail/concurrency/too_many_args.rs rename to tests/fail/concurrency/libc_pthread_create_too_many_args.rs index d660037ca6..7408634db5 100644 --- a/tests/fail/concurrency/too_many_args.rs +++ b/tests/fail/concurrency/libc_pthread_create_too_many_args.rs @@ -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. diff --git a/tests/fail/concurrency/too_many_args.stderr b/tests/fail/concurrency/libc_pthread_create_too_many_args.stderr similarity index 92% rename from tests/fail/concurrency/too_many_args.stderr rename to tests/fail/concurrency/libc_pthread_create_too_many_args.stderr index 42a96ae626..49c7f57997 100644 --- a/tests/fail/concurrency/too_many_args.stderr +++ b/tests/fail/concurrency/libc_pthread_create_too_many_args.stderr @@ -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 diff --git a/tests/fail/concurrency/libc_pthread_join_detached.rs b/tests/fail/concurrency/libc_pthread_join_detached.rs index 488b14bbcf..0b810dc8c7 100644 --- a/tests/fail/concurrency/libc_pthread_join_detached.rs +++ b/tests/fail/concurrency/libc_pthread_join_detached.rs @@ -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 } } diff --git a/tests/fail/concurrency/libc_pthread_join_detached.stderr b/tests/fail/concurrency/libc_pthread_join_detached.stderr index be9781ff46..e381a71b25 100644 --- a/tests/fail/concurrency/libc_pthread_join_detached.stderr +++ b/tests/fail/concurrency/libc_pthread_join_detached.stderr @@ -2,7 +2,7 @@ error: Undefined Behavior: trying to join a detached or already joined 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 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 diff --git a/tests/fail/concurrency/thread-spawn.rs b/tests/fail/concurrency/thread-spawn.rs deleted file mode 100644 index 84848e35a0..0000000000 --- a/tests/fail/concurrency/thread-spawn.rs +++ /dev/null @@ -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(|| {}); -} diff --git a/tests/fail/concurrency/thread-spawn.stderr b/tests/fail/concurrency/thread-spawn.stderr deleted file mode 100644 index 2e4b3a045e..0000000000 --- a/tests/fail/concurrency/thread-spawn.stderr +++ /dev/null @@ -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 - diff --git a/tests/fail/concurrency/thread_local_static_dealloc.rs b/tests/fail/concurrency/thread_local_static_dealloc.rs index 7c54e3bca7..d89c670b63 100644 --- a/tests/fail/concurrency/thread_local_static_dealloc.rs +++ b/tests/fail/concurrency/thread_local_static_dealloc.rs @@ -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)] diff --git a/tests/fail/concurrency/unwind_top_of_stack.rs b/tests/fail/concurrency/unwind_top_of_stack.rs index c2b9d56e19..61d3b964e6 100644 --- a/tests/fail/concurrency/unwind_top_of_stack.rs +++ b/tests/fail/concurrency/unwind_top_of_stack.rs @@ -1,4 +1,5 @@ -//@ignore-target-windows: Concurrency on Windows is not supported yet. +//@ignore-windows: No libc on Windows + //@compile-flags: -Zmiri-disable-abi-check //! Unwinding past the top frame of a stack is Undefined Behavior. diff --git a/tests/fail/data_race/alloc_read_race.rs b/tests/fail/data_race/alloc_read_race.rs index f3f63aeb2b..0bd3068af1 100644 --- a/tests/fail/data_race/alloc_read_race.rs +++ b/tests/fail/data_race/alloc_read_race.rs @@ -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)] diff --git a/tests/fail/data_race/alloc_write_race.rs b/tests/fail/data_race/alloc_write_race.rs index a2738c3879..7991280721 100644 --- a/tests/fail/data_race/alloc_write_race.rs +++ b/tests/fail/data_race/alloc_write_race.rs @@ -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)] diff --git a/tests/fail/data_race/atomic_read_na_write_race1.rs b/tests/fail/data_race/atomic_read_na_write_race1.rs index 30900a85d0..2b0446d724 100644 --- a/tests/fail/data_race/atomic_read_na_write_race1.rs +++ b/tests/fail/data_race/atomic_read_na_write_race1.rs @@ -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; diff --git a/tests/fail/data_race/atomic_read_na_write_race2.rs b/tests/fail/data_race/atomic_read_na_write_race2.rs index 493985f6cf..ef5157515c 100644 --- a/tests/fail/data_race/atomic_read_na_write_race2.rs +++ b/tests/fail/data_race/atomic_read_na_write_race2.rs @@ -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; diff --git a/tests/fail/data_race/atomic_write_na_read_race1.rs b/tests/fail/data_race/atomic_write_na_read_race1.rs index ca9c28abf1..8c17e76748 100644 --- a/tests/fail/data_race/atomic_write_na_read_race1.rs +++ b/tests/fail/data_race/atomic_write_na_read_race1.rs @@ -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; diff --git a/tests/fail/data_race/atomic_write_na_read_race2.rs b/tests/fail/data_race/atomic_write_na_read_race2.rs index 1a79dde0b0..f14d7c704d 100644 --- a/tests/fail/data_race/atomic_write_na_read_race2.rs +++ b/tests/fail/data_race/atomic_write_na_read_race2.rs @@ -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; diff --git a/tests/fail/data_race/atomic_write_na_write_race1.rs b/tests/fail/data_race/atomic_write_na_write_race1.rs index 04015c2d14..0804b33407 100644 --- a/tests/fail/data_race/atomic_write_na_write_race1.rs +++ b/tests/fail/data_race/atomic_write_na_write_race1.rs @@ -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; diff --git a/tests/fail/data_race/atomic_write_na_write_race2.rs b/tests/fail/data_race/atomic_write_na_write_race2.rs index 1d242ff3cd..658cddcc9c 100644 --- a/tests/fail/data_race/atomic_write_na_write_race2.rs +++ b/tests/fail/data_race/atomic_write_na_write_race2.rs @@ -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; diff --git a/tests/fail/data_race/dangling_thread_async_race.rs b/tests/fail/data_race/dangling_thread_async_race.rs index 6264255265..af2588e923 100644 --- a/tests/fail/data_race/dangling_thread_async_race.rs +++ b/tests/fail/data_race/dangling_thread_async_race.rs @@ -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}; diff --git a/tests/fail/data_race/dangling_thread_race.rs b/tests/fail/data_race/dangling_thread_race.rs index 6f44fc69db..1ee619c3f9 100644 --- a/tests/fail/data_race/dangling_thread_race.rs +++ b/tests/fail/data_race/dangling_thread_race.rs @@ -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}; diff --git a/tests/fail/data_race/dealloc_read_race1.rs b/tests/fail/data_race/dealloc_read_race1.rs index 5349073ec3..cbc02549a2 100644 --- a/tests/fail/data_race/dealloc_read_race1.rs +++ b/tests/fail/data_race/dealloc_read_race1.rs @@ -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; diff --git a/tests/fail/data_race/dealloc_read_race2.rs b/tests/fail/data_race/dealloc_read_race2.rs index bb9070ef75..24cce5d6fa 100644 --- a/tests/fail/data_race/dealloc_read_race2.rs +++ b/tests/fail/data_race/dealloc_read_race2.rs @@ -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; diff --git a/tests/fail/data_race/dealloc_read_race_stack.rs b/tests/fail/data_race/dealloc_read_race_stack.rs index e114fbb8b4..5484370f35 100644 --- a/tests/fail/data_race/dealloc_read_race_stack.rs +++ b/tests/fail/data_race/dealloc_read_race_stack.rs @@ -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; diff --git a/tests/fail/data_race/dealloc_write_race1.rs b/tests/fail/data_race/dealloc_write_race1.rs index 35949920e1..23bf73fe8c 100644 --- a/tests/fail/data_race/dealloc_write_race1.rs +++ b/tests/fail/data_race/dealloc_write_race1.rs @@ -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; diff --git a/tests/fail/data_race/dealloc_write_race2.rs b/tests/fail/data_race/dealloc_write_race2.rs index b569086c8c..7c8033e233 100644 --- a/tests/fail/data_race/dealloc_write_race2.rs +++ b/tests/fail/data_race/dealloc_write_race2.rs @@ -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; diff --git a/tests/fail/data_race/dealloc_write_race_stack.rs b/tests/fail/data_race/dealloc_write_race_stack.rs index 0c74c7adf5..1872abfe02 100644 --- a/tests/fail/data_race/dealloc_write_race_stack.rs +++ b/tests/fail/data_race/dealloc_write_race_stack.rs @@ -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; diff --git a/tests/fail/data_race/enable_after_join_to_main.rs b/tests/fail/data_race/enable_after_join_to_main.rs index f2235b9525..c11239da7f 100644 --- a/tests/fail/data_race/enable_after_join_to_main.rs +++ b/tests/fail/data_race/enable_after_join_to_main.rs @@ -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; diff --git a/tests/fail/data_race/fence_after_load.rs b/tests/fail/data_race/fence_after_load.rs index 8acbe12e82..ae44390859 100644 --- a/tests/fail/data_race/fence_after_load.rs +++ b/tests/fail/data_race/fence_after_load.rs @@ -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; diff --git a/tests/fail/data_race/read_write_race.rs b/tests/fail/data_race/read_write_race.rs index cff868fb69..482dd2df7d 100644 --- a/tests/fail/data_race/read_write_race.rs +++ b/tests/fail/data_race/read_write_race.rs @@ -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; diff --git a/tests/fail/data_race/read_write_race_stack.rs b/tests/fail/data_race/read_write_race_stack.rs index b22173fa5a..1b4932439b 100644 --- a/tests/fail/data_race/read_write_race_stack.rs +++ b/tests/fail/data_race/read_write_race_stack.rs @@ -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 diff --git a/tests/fail/data_race/relax_acquire_race.rs b/tests/fail/data_race/relax_acquire_race.rs index b99388b892..240b4c90eb 100644 --- a/tests/fail/data_race/relax_acquire_race.rs +++ b/tests/fail/data_race/relax_acquire_race.rs @@ -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}; diff --git a/tests/fail/data_race/release_seq_race.rs b/tests/fail/data_race/release_seq_race.rs index 31fc5d9a47..5ae8012783 100644 --- a/tests/fail/data_race/release_seq_race.rs +++ b/tests/fail/data_race/release_seq_race.rs @@ -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}; diff --git a/tests/fail/data_race/release_seq_race_same_thread.rs b/tests/fail/data_race/release_seq_race_same_thread.rs index c0ce437047..63e6dc2dd7 100644 --- a/tests/fail/data_race/release_seq_race_same_thread.rs +++ b/tests/fail/data_race/release_seq_race_same_thread.rs @@ -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}; diff --git a/tests/fail/data_race/rmw_race.rs b/tests/fail/data_race/rmw_race.rs index 540b01b093..122780d11a 100644 --- a/tests/fail/data_race/rmw_race.rs +++ b/tests/fail/data_race/rmw_race.rs @@ -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}; diff --git a/tests/fail/data_race/write_write_race.rs b/tests/fail/data_race/write_write_race.rs index ac75c771e4..13c31c87cb 100644 --- a/tests/fail/data_race/write_write_race.rs +++ b/tests/fail/data_race/write_write_race.rs @@ -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; diff --git a/tests/fail/data_race/write_write_race_stack.rs b/tests/fail/data_race/write_write_race_stack.rs index 5193b15551..731ac8b26a 100644 --- a/tests/fail/data_race/write_write_race_stack.rs +++ b/tests/fail/data_race/write_write_race_stack.rs @@ -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; diff --git a/tests/fail/should-pass/cpp20_rwc_syncs.rs b/tests/fail/should-pass/cpp20_rwc_syncs.rs index 6a7622671b..545875a582 100644 --- a/tests/fail/should-pass/cpp20_rwc_syncs.rs +++ b/tests/fail/should-pass/cpp20_rwc_syncs.rs @@ -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 diff --git a/tests/fail/weak_memory/racing_mixed_size.rs b/tests/fail/weak_memory/racing_mixed_size.rs index 95a1051071..7bbb7f9fe7 100644 --- a/tests/fail/weak_memory/racing_mixed_size.rs +++ b/tests/fail/weak_memory/racing_mixed_size.rs @@ -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)] diff --git a/tests/fail/weak_memory/racing_mixed_size_read.rs b/tests/fail/weak_memory/racing_mixed_size_read.rs index 1c0b1c2be8..73178980b7 100644 --- a/tests/fail/weak_memory/racing_mixed_size_read.rs +++ b/tests/fail/weak_memory/racing_mixed_size_read.rs @@ -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}; diff --git a/tests/pass/0weak_memory_consistency.rs b/tests/pass/0weak_memory_consistency.rs index b5b6b83cce..8c650bca2f 100644 --- a/tests/pass/0weak_memory_consistency.rs +++ b/tests/pass/0weak_memory_consistency.rs @@ -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 diff --git a/tests/pass/concurrency/channels.rs b/tests/pass/concurrency/channels.rs index 40d729f042..c75c5199bf 100644 --- a/tests/pass/concurrency/channels.rs +++ b/tests/pass/concurrency/channels.rs @@ -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}; diff --git a/tests/pass/concurrency/concurrent_caller_location.rs b/tests/pass/concurrency/concurrent_caller_location.rs index a07f6b13e6..0490330a15 100644 --- a/tests/pass/concurrency/concurrent_caller_location.rs +++ b/tests/pass/concurrency/concurrent_caller_location.rs @@ -1,5 +1,3 @@ -//@ignore-target-windows: Concurrency on Windows is not supported yet. - use std::panic::Location; use std::thread::spawn; diff --git a/tests/pass/concurrency/data_race.rs b/tests/pass/concurrency/data_race.rs index 9c7030db3d..4e3c99058a 100644 --- a/tests/pass/concurrency/data_race.rs +++ b/tests/pass/concurrency/data_race.rs @@ -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}; diff --git a/tests/pass/concurrency/disable_data_race_detector.rs b/tests/pass/concurrency/disable_data_race_detector.rs index a4852b4674..d71e51b038 100644 --- a/tests/pass/concurrency/disable_data_race_detector.rs +++ b/tests/pass/concurrency/disable_data_race_detector.rs @@ -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; diff --git a/tests/pass/concurrency/issue1643.rs b/tests/pass/concurrency/issue1643.rs index f4fe43d88c..c0956569ad 100644 --- a/tests/pass/concurrency/issue1643.rs +++ b/tests/pass/concurrency/issue1643.rs @@ -1,5 +1,3 @@ -//@ignore-target-windows: Concurrency on Windows is not supported yet. - use std::thread::spawn; fn initialize() { diff --git a/tests/pass/concurrency/simple.rs b/tests/pass/concurrency/simple.rs index 1d85c7fc9b..556e0a2476 100644 --- a/tests/pass/concurrency/simple.rs +++ b/tests/pass/concurrency/simple.rs @@ -1,4 +1,3 @@ -//@ignore-target-windows: Concurrency on Windows is not supported yet. //@compile-flags: -Zmiri-strict-provenance use std::thread; diff --git a/tests/pass/concurrency/spin_loop.rs b/tests/pass/concurrency/spin_loop.rs index 5f34168dbb..019bd44f16 100644 --- a/tests/pass/concurrency/spin_loop.rs +++ b/tests/pass/concurrency/spin_loop.rs @@ -1,4 +1,3 @@ -//@ignore-target-windows: Concurrency on Windows is not supported yet. use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread; diff --git a/tests/pass/concurrency/spin_loops_nopreempt.rs b/tests/pass/concurrency/spin_loops_nopreempt.rs index 34be0dd394..5d8e2ef5f0 100644 --- a/tests/pass/concurrency/spin_loops_nopreempt.rs +++ b/tests/pass/concurrency/spin_loops_nopreempt.rs @@ -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 diff --git a/tests/pass/concurrency/thread_locals.rs b/tests/pass/concurrency/thread_locals.rs index 34431def33..b19e56312f 100644 --- a/tests/pass/concurrency/thread_locals.rs +++ b/tests/pass/concurrency/thread_locals.rs @@ -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 diff --git a/tests/pass/concurrency/tls_lib_drop.rs b/tests/pass/concurrency/tls_lib_drop.rs index 6c66cd3a62..8ce011ede3 100644 --- a/tests/pass/concurrency/tls_lib_drop.rs +++ b/tests/pass/concurrency/tls_lib_drop.rs @@ -1,5 +1,3 @@ -//@ignore-target-windows: Concurrency on Windows is not supported yet. - use std::cell::RefCell; use std::thread; diff --git a/tests/pass/panic/concurrent-panic.rs b/tests/pass/panic/concurrent-panic.rs index 1acae69b8d..342269c6ac 100644 --- a/tests/pass/panic/concurrent-panic.rs +++ b/tests/pass/panic/concurrent-panic.rs @@ -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 diff --git a/tests/pass/shims/time.rs b/tests/pass/shims/time.rs index e1094006fb..23b5ab57ef 100644 --- a/tests/pass/shims/time.rs +++ b/tests/pass/shims/time.rs @@ -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(); } diff --git a/tests/pass/threadleak_ignored.rs b/tests/pass/threadleak_ignored.rs index 077d8d0837..99bac7aa42 100644 --- a/tests/pass/threadleak_ignored.rs +++ b/tests/pass/threadleak_ignored.rs @@ -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 diff --git a/tests/pass/weak_memory/extra_cpp.rs b/tests/pass/weak_memory/extra_cpp.rs index d98fba26ff..07cbb4a803 100644 --- a/tests/pass/weak_memory/extra_cpp.rs +++ b/tests/pass/weak_memory/extra_cpp.rs @@ -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 diff --git a/tests/pass/weak_memory/extra_cpp_unsafe.rs b/tests/pass/weak_memory/extra_cpp_unsafe.rs index f5c6021a4a..f7e2748408 100644 --- a/tests/pass/weak_memory/extra_cpp_unsafe.rs +++ b/tests/pass/weak_memory/extra_cpp_unsafe.rs @@ -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 diff --git a/tests/pass/weak_memory/weak.rs b/tests/pass/weak_memory/weak.rs index dc7982991b..4c3be6b355 100644 --- a/tests/pass/weak_memory/weak.rs +++ b/tests/pass/weak_memory/weak.rs @@ -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 From 08ffbb8d8ad5a65825a458982981222bccf951b7 Mon Sep 17 00:00:00 2001 From: DrMeepster <19316085+DrMeepster@users.noreply.github.com> Date: Fri, 15 Jul 2022 01:40:06 -0700 Subject: [PATCH 2/5] fix windows join/detach and add tests --- src/machine.rs | 1 + src/shims/time.rs | 26 ++++++++++++ src/shims/unix/thread.rs | 4 +- src/shims/windows/dlsym.rs | 4 +- src/shims/windows/foreign_items.rs | 19 +-------- src/shims/windows/handle.rs | 15 ++++--- src/shims/windows/thread.rs | 25 ++++------- src/thread.rs | 34 +++++++++++++++ tests/fail/concurrency/unwind_top_of_stack.rs | 2 +- .../fail/concurrency/windows_join_detached.rs | 21 ++++++++++ .../concurrency/windows_join_detached.stderr | 22 ++++++++++ tests/fail/concurrency/windows_join_main.rs | 28 +++++++++++++ .../fail/concurrency/windows_join_main.stderr | 12 ++++++ tests/fail/concurrency/windows_join_self.rs | 25 +++++++++++ .../fail/concurrency/windows_join_self.stderr | 12 ++++++ .../concurrency/windows_detach_terminated.rs | 21 ++++++++++ .../pass/concurrency/windows_join_multiple.rs | 41 +++++++++++++++++++ 17 files changed, 265 insertions(+), 47 deletions(-) create mode 100644 tests/fail/concurrency/windows_join_detached.rs create mode 100644 tests/fail/concurrency/windows_join_detached.stderr create mode 100644 tests/fail/concurrency/windows_join_main.rs create mode 100644 tests/fail/concurrency/windows_join_main.stderr create mode 100644 tests/fail/concurrency/windows_join_self.rs create mode 100644 tests/fail/concurrency/windows_join_self.stderr create mode 100644 tests/pass/concurrency/windows_detach_terminated.rs create mode 100644 tests/pass/concurrency/windows_join_multiple.rs diff --git a/src/machine.rs b/src/machine.rs index 4ebf7dceab..943d5570f7 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -418,6 +418,7 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> { ) -> InterpResult<'tcx> { EnvVars::init(this, config)?; Evaluator::init_extern_statics(this)?; + ThreadManager::init(this); Ok(()) } diff --git a/src/shims/time.rs b/src/shims/time.rs index c3eb0161c2..e495f72366 100644 --- a/src/shims/time.rs +++ b/src/shims/time.rs @@ -233,4 +233,30 @@ 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.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(()) + } } diff --git a/src/shims/unix/thread.rs b/src/shims/unix/thread.rs index 094183b3e7..d675df0f53 100644 --- a/src/shims/unix/thread.rs +++ b/src/shims/unix/thread.rs @@ -37,7 +37,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) } @@ -46,7 +46,7 @@ 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"), false)?; Ok(0) } diff --git a/src/shims/windows/dlsym.rs b/src/shims/windows/dlsym.rs index f18e27d38c..d64be9cc05 100644 --- a/src/shims/windows/dlsym.rs +++ b/src/shims/windows/dlsym.rs @@ -5,7 +5,7 @@ use rustc_target::spec::abi::Abi; use log::trace; use crate::helpers::check_arg_count; -use crate::shims::windows::handle::Handle; +use crate::shims::windows::handle::{EvalContextExt as _, Handle}; use crate::*; #[derive(Debug, Copy, Clone)] @@ -118,7 +118,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx match Handle::from_scalar(this.read_scalar(handle)?.check_init()?, this)? { Some(Handle::Thread(thread)) => thread, Some(Handle::CurrentThread) => this.get_active_thread(), - _ => throw_ub_format!("invalid handle"), + _ => this.invalid_handle("SetThreadDescription")?, }; this.set_thread_name_wide(thread, name); diff --git a/src/shims/windows/foreign_items.rs b/src/shims/windows/foreign_items.rs index 00a80f8697..6014281100 100644 --- a/src/shims/windows/foreign_items.rs +++ b/src/shims/windows/foreign_items.rs @@ -228,24 +228,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx let [timeout] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; - this.check_no_isolation("`Sleep`")?; - - let timeout_ms = this.read_scalar(timeout)?.to_u32()?; - - let duration = Duration::from_millis(timeout_ms as u64); - 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(()) - }), - ); + this.Sleep(timeout)?; } // Synchronization primitives diff --git a/src/shims/windows/handle.rs b/src/shims/windows/handle.rs index ff64a958da..e1617ae6a8 100644 --- a/src/shims/windows/handle.rs +++ b/src/shims/windows/handle.rs @@ -146,16 +146,19 @@ impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tc #[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(); match Handle::from_scalar(this.read_scalar(handle_op)?.check_init()?, this)? { - Some(Handle::Thread(thread)) => this.detach_thread(thread)?, - _ => - throw_machine_stop!(TerminationInfo::Abort( - "invalid handle passed to `CloseHandle`".into() - )), - }; + Some(Handle::Thread(thread)) => this.detach_thread(thread, true)?, + _ => this.invalid_handle("CloseHandle")?, + } Ok(()) } diff --git a/src/shims/windows/thread.rs b/src/shims/windows/thread.rs index 66cd9dd034..6b6c4916de 100644 --- a/src/shims/windows/thread.rs +++ b/src/shims/windows/thread.rs @@ -1,11 +1,8 @@ -use std::time::{Duration, Instant}; - use rustc_middle::ty::layout::LayoutOf; use rustc_target::spec::abi::Abi; -use crate::thread::Time; use crate::*; -use shims::windows::handle::Handle; +use shims::windows::handle::{EvalContextExt as _, Handle}; impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {} @@ -59,25 +56,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx let thread = match Handle::from_scalar(this.read_scalar(handle)?.check_init()?, this)? { Some(Handle::Thread(thread)) => thread, - Some(Handle::CurrentThread) => throw_ub_format!("trying to wait on itself"), - _ => throw_ub_format!("invalid handle"), + // Unlike on posix, joining the current thread is not UB on windows. + // It will just deadlock. + Some(Handle::CurrentThread) => this.get_active_thread(), + _ => this.invalid_handle("WaitForSingleObject")?, }; if this.read_scalar(timeout)?.to_u32()? != this.eval_windows("c", "INFINITE")?.to_u32()? { - this.check_no_isolation("`WaitForSingleObject` with non-infinite timeout")?; + throw_unsup_format!("`WaitForSingleObject` with non-infinite timeout"); } - let timeout_ms = this.read_scalar(timeout)?.to_u32()?; - - let timeout_time = if timeout_ms == this.eval_windows("c", "INFINITE")?.to_u32()? { - None - } else { - let duration = Duration::from_millis(timeout_ms as u64); - - Some(Time::Monotonic(Instant::now().checked_add(duration).unwrap())) - }; - - this.wait_on_thread(timeout_time, thread)?; + this.join_thread(thread)?; Ok(()) } diff --git a/src/thread.rs b/src/thread.rs index fa70dcfa2a..f7fcdd5822 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -405,6 +405,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 + 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)), + "a joinable 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) { self.threads[thread].thread_name = Some(new_thread_name); @@ -700,6 +725,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(); diff --git a/tests/fail/concurrency/unwind_top_of_stack.rs b/tests/fail/concurrency/unwind_top_of_stack.rs index 61d3b964e6..4704cfed03 100644 --- a/tests/fail/concurrency/unwind_top_of_stack.rs +++ b/tests/fail/concurrency/unwind_top_of_stack.rs @@ -1,4 +1,4 @@ -//@ignore-windows: No libc on Windows +//@ignore-target-windows: No libc on Windows //@compile-flags: -Zmiri-disable-abi-check diff --git a/tests/fail/concurrency/windows_join_detached.rs b/tests/fail/concurrency/windows_join_detached.rs new file mode 100644 index 0000000000..548ed63534 --- /dev/null +++ b/tests/fail/concurrency/windows_join_detached.rs @@ -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(); +} diff --git a/tests/fail/concurrency/windows_join_detached.stderr b/tests/fail/concurrency/windows_join_detached.stderr new file mode 100644 index 0000000000..a0e85f6ce5 --- /dev/null +++ b/tests/fail/concurrency/windows_join_detached.stderr @@ -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 + diff --git a/tests/fail/concurrency/windows_join_main.rs b/tests/fail/concurrency/windows_join_main.rs new file mode 100644 index 0000000000..ea52220d44 --- /dev/null +++ b/tests/fail/concurrency/windows_join_main.rs @@ -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: usize, timeout: u32) -> u32; +} + +const INFINITE: u32 = u32::MAX; + +// 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: usize = 1 << 30; + +fn main() { + thread::spawn(|| { + unsafe { + assert_eq!(WaitForSingleObject(MAIN_THREAD, INFINITE), 0); //~ ERROR: deadlock: the evaluated program deadlocked + } + }) + .join() + .unwrap(); +} diff --git a/tests/fail/concurrency/windows_join_main.stderr b/tests/fail/concurrency/windows_join_main.stderr new file mode 100644 index 0000000000..72b854d354 --- /dev/null +++ b/tests/fail/concurrency/windows_join_main.stderr @@ -0,0 +1,12 @@ +error: deadlock: the evaluated program deadlocked + --> $DIR/windows_join_main.rs:LL:CC + | +LL | WaitForSingleObject(MAIN_THREAD, INFINITE); + | ^ the evaluated program deadlocked + | + = note: inside closure at $DIR/windows_join_main.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/tests/fail/concurrency/windows_join_self.rs b/tests/fail/concurrency/windows_join_self.rs new file mode 100644 index 0000000000..d9bbf66a7d --- /dev/null +++ b/tests/fail/concurrency/windows_join_self.rs @@ -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(); +} diff --git a/tests/fail/concurrency/windows_join_self.stderr b/tests/fail/concurrency/windows_join_self.stderr new file mode 100644 index 0000000000..bbec3f7257 --- /dev/null +++ b/tests/fail/concurrency/windows_join_self.stderr @@ -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 + diff --git a/tests/pass/concurrency/windows_detach_terminated.rs b/tests/pass/concurrency/windows_detach_terminated.rs new file mode 100644 index 0000000000..91088ce6ae --- /dev/null +++ b/tests/pass/concurrency/windows_detach_terminated.rs @@ -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); + } +} diff --git a/tests/pass/concurrency/windows_join_multiple.rs b/tests/pass/concurrency/windows_join_multiple.rs new file mode 100644 index 0000000000..986e2b8cc1 --- /dev/null +++ b/tests/pass/concurrency/windows_join_multiple.rs @@ -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(); +} From 9f69c41c5fc0e876c75927cbc3ef7a5eff481ecc Mon Sep 17 00:00:00 2001 From: DrMeepster <19316085+DrMeepster@users.noreply.github.com> Date: Sat, 16 Jul 2022 00:36:11 -0700 Subject: [PATCH 3/5] rewrite handle impl again --- src/shims/windows/dlsym.rs | 15 +- src/shims/windows/foreign_items.rs | 9 +- src/shims/windows/handle.rs | 139 +++++++++--------- src/shims/windows/thread.rs | 4 +- tests/fail/concurrency/windows_join_main.rs | 4 +- .../fail/concurrency/windows_join_main.stderr | 7 +- 6 files changed, 90 insertions(+), 88 deletions(-) diff --git a/src/shims/windows/dlsym.rs b/src/shims/windows/dlsym.rs index d64be9cc05..fc36913638 100644 --- a/src/shims/windows/dlsym.rs +++ b/src/shims/windows/dlsym.rs @@ -5,7 +5,7 @@ use rustc_target::spec::abi::Abi; use log::trace; use crate::helpers::check_arg_count; -use crate::shims::windows::handle::{EvalContextExt as _, Handle}; +use crate::shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle}; use crate::*; #[derive(Debug, Copy, Clone)] @@ -112,14 +112,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx 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(this.read_scalar(handle)?.check_init()?, this)? { - Some(Handle::Thread(thread)) => thread, - Some(Handle::CurrentThread) => this.get_active_thread(), - _ => this.invalid_handle("SetThreadDescription")?, - }; + 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); diff --git a/src/shims/windows/foreign_items.rs b/src/shims/windows/foreign_items.rs index 6014281100..cc030ec3d0 100644 --- a/src/shims/windows/foreign_items.rs +++ b/src/shims/windows/foreign_items.rs @@ -1,14 +1,12 @@ use std::iter; -use std::time::{Duration, Instant}; use rustc_span::Symbol; use rustc_target::abi::Size; use rustc_target::spec::abi::Abi; -use crate::thread::Time; use crate::*; use shims::foreign_items::EmulateByNameResult; -use shims::windows::handle::{EvalContextExt as _, Handle}; +use shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle}; use shims::windows::sync::EvalContextExt as _; use shims::windows::thread::EvalContextExt as _; @@ -373,7 +371,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx "GetCurrentThread" => { let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; - this.write_scalar(Handle::CurrentThread.to_scalar(this), dest)?; + 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. diff --git a/src/shims/windows/handle.rs b/src/shims/windows/handle.rs index e1617ae6a8..041033717e 100644 --- a/src/shims/windows/handle.rs +++ b/src/shims/windows/handle.rs @@ -3,44 +3,79 @@ use std::mem::variant_count; use crate::*; -/// A Windows `HANDLE` that represents a resource instead of being null or a pseudohandle. -/// -/// This is a seperate type from [`Handle`] to simplify the packing and unpacking code. -#[derive(Clone, Copy)] -enum RealHandle { - Thread(ThreadId), +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum PseudoHandle { + CurrentThread, } -impl RealHandle { - const USABLE_BITS: u32 = 31; +impl PseudoHandle { + const CURRENT_THREAD_VALUE: u32 = 0; - const THREAD_DISCRIMINANT: u32 = 1; + fn value(self) -> u32 { + match self { + Self::CurrentThread => Self::CURRENT_THREAD_VALUE, + } + } + + fn from_value(value: u32) -> Option { + match value { + Self::CURRENT_THREAD_VALUE => Some(Self::CurrentThread), + _ => None, + } + } +} + +/// Miri representation of a Windows `HANDLE` +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Handle { + Null, + Pseudo(PseudoHandle), + Thread(ThreadId), +} + +impl Handle { + const NULL_DISCRIMINANT: u32 = 0; + const PSEUDO_DISCRIMINANT: u32 = 1; + const THREAD_DISCRIMINANT: u32 = 2; fn discriminant(self) -> u32 { match self { - // can't use zero here because all zero handle is invalid + 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 { - // log2(x) + 1 is how many bits it takes to store x - // because the discriminants start at 1, the variant count is equal to the highest discriminant - variant_count::().ilog2() + 1 + // ceil(log2(x)) is how many bits it takes to store x numbers + let variant_count = variant_count::(); + + // 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 + let ceil_log2 = if variant_count.is_power_of_two() { floor_log2 } else { floor_log2 + 1 }; + + ceil_log2 } - /// This function packs the discriminant and data values into a 31-bit space. + /// 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. - /// The sign bit is not used to avoid overlapping any pseudo-handles. - fn to_packed(self) -> i32 { + fn to_packed(self) -> u32 { let disc_size = Self::packed_disc_size(); - let data_size = Self::USABLE_BITS - disc_size; + let data_size = u32::BITS - disc_size; let discriminant = self.discriminant(); let data = self.data(); @@ -53,90 +88,54 @@ impl RealHandle { // packs the data into the lower `data_size` bits // and packs the discriminant right above the data - (discriminant << data_size | data) as i32 + discriminant << data_size | data } fn new(discriminant: u32, data: u32) -> Option { 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: i32) -> Option { - let handle_bits = handle as u32; - + fn from_packed(handle: u32) -> Option { let disc_size = Self::packed_disc_size(); - let data_size = Self::USABLE_BITS - 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_bits >> data_size; + let discriminant = handle >> data_size; // the data is stored in the lower `data_size` bits - let data = handle_bits & data_mask; + let data = handle & data_mask; Self::new(discriminant, data) } -} - -/// Miri representation of a Windows `HANDLE` -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum Handle { - Null, // = 0 - - // pseudo-handles - // The lowest real windows pseudo-handle is -6, so miri pseduo-handles start at -7 to break code hardcoding these values - CurrentThread, // = -7 - - // real handles - Thread(ThreadId), -} - -impl Handle { - const CURRENT_THREAD_VALUE: i32 = -7; - - fn to_packed(self) -> i32 { - match self { - Self::Null => 0, - Self::CurrentThread => Self::CURRENT_THREAD_VALUE, - Self::Thread(thread) => RealHandle::Thread(thread).to_packed(), - } - } pub fn to_scalar(self, cx: &impl HasDataLayout) -> Scalar { // 64-bit handles are sign extended 32-bit handles // see https://docs.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication - let handle = self.to_packed().into(); - - Scalar::from_machine_isize(handle, cx) - } - - fn from_packed(handle: i64) -> Option { - let current_thread_val = Self::CURRENT_THREAD_VALUE as i64; - - if handle == 0 { - Some(Self::Null) - } else if handle == current_thread_val { - Some(Self::CurrentThread) - } else if let Ok(handle) = handle.try_into() { - match RealHandle::from_packed(handle)? { - RealHandle::Thread(id) => Some(Self::Thread(id)), - } - } else { - // if a handle doesn't fit in an i32, it isn't valid. - None - } + let signed_handle = self.to_packed() as i32; + Scalar::from_machine_isize(signed_handle.into(), cx) } pub fn from_scalar<'tcx>( handle: Scalar, cx: &impl HasDataLayout, ) -> InterpResult<'tcx, Option> { - let handle = handle.to_machine_isize(cx)?; + let sign_extended_handle = handle.to_machine_isize(cx)?; + + 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)) } diff --git a/src/shims/windows/thread.rs b/src/shims/windows/thread.rs index 6b6c4916de..08eb4ddba1 100644 --- a/src/shims/windows/thread.rs +++ b/src/shims/windows/thread.rs @@ -2,7 +2,7 @@ use rustc_middle::ty::layout::LayoutOf; use rustc_target::spec::abi::Abi; use crate::*; -use shims::windows::handle::{EvalContextExt as _, Handle}; +use shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle}; impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {} @@ -58,7 +58,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx Some(Handle::Thread(thread)) => thread, // Unlike on posix, joining the current thread is not UB on windows. // It will just deadlock. - Some(Handle::CurrentThread) => this.get_active_thread(), + Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => this.get_active_thread(), _ => this.invalid_handle("WaitForSingleObject")?, }; diff --git a/tests/fail/concurrency/windows_join_main.rs b/tests/fail/concurrency/windows_join_main.rs index ea52220d44..d3b54cdf15 100644 --- a/tests/fail/concurrency/windows_join_main.rs +++ b/tests/fail/concurrency/windows_join_main.rs @@ -7,7 +7,7 @@ use std::thread; extern "system" { - fn WaitForSingleObject(handle: usize, timeout: u32) -> u32; + fn WaitForSingleObject(handle: isize, timeout: u32) -> u32; } const INFINITE: u32 = u32::MAX; @@ -15,7 +15,7 @@ const INFINITE: u32 = u32::MAX; // 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: usize = 1 << 30; +const MAIN_THREAD: isize = (2i32 << 30) as isize; fn main() { thread::spawn(|| { diff --git a/tests/fail/concurrency/windows_join_main.stderr b/tests/fail/concurrency/windows_join_main.stderr index 72b854d354..ff0d074fa7 100644 --- a/tests/fail/concurrency/windows_join_main.stderr +++ b/tests/fail/concurrency/windows_join_main.stderr @@ -1,10 +1,11 @@ error: deadlock: the evaluated program deadlocked --> $DIR/windows_join_main.rs:LL:CC | -LL | WaitForSingleObject(MAIN_THREAD, INFINITE); - | ^ the evaluated program deadlocked +LL | assert_eq!(WaitForSingleObject(MAIN_THREAD, INFINITE), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program deadlocked | - = note: inside closure at $DIR/windows_join_main.rs:LL:CC + = 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 From d34242e8f1c538a8b4e01100a273ef1c5d456abd Mon Sep 17 00:00:00 2001 From: DrMeepster <19316085+DrMeepster@users.noreply.github.com> Date: Sun, 31 Jul 2022 17:15:15 -0700 Subject: [PATCH 4/5] fix various issues --- src/shims/os_str.rs | 2 +- src/shims/tls.rs | 5 +- src/shims/unix/thread.rs | 15 +- src/shims/windows/dlsym.rs | 2 +- src/shims/windows/foreign_items.rs | 25 +-- src/shims/windows/handle.rs | 29 +-- src/shims/windows/thread.rs | 48 +++-- src/thread.rs | 23 ++- .../libc_pthread_join_detached.stderr | 6 +- .../concurrency/libc_pthread_join_joined.rs | 2 +- .../libc_pthread_join_joined.stderr | 6 +- .../concurrency/libc_pthread_join_main.rs | 2 +- .../concurrency/libc_pthread_join_main.stderr | 6 +- .../concurrency/libc_pthread_join_multiple.rs | 2 +- .../libc_pthread_join_multiple.stderr | 4 +- tests/fail/concurrency/windows_join_main.rs | 2 +- tests/pass/concurrency/tls_lib_drop.rs | 2 + .../pass/concurrency/tls_lib_drop_windows.rs | 191 ++++++++++++++++++ .../concurrency/tls_lib_drop_windows.stdout | 5 + 19 files changed, 306 insertions(+), 71 deletions(-) create mode 100644 tests/pass/concurrency/tls_lib_drop_windows.rs create mode 100644 tests/pass/concurrency/tls_lib_drop_windows.stdout diff --git a/src/shims/os_str.rs b/src/shims/os_str.rs index f99e2d174b..fcf92dfc9f 100644 --- a/src/shims/os_str.rs +++ b/src/shims/os_str.rs @@ -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) -> InterpResult<'tcx, OsString> { + pub fn u16vec_to_osstring<'tcx>(u16_vec: Vec) -> InterpResult<'tcx, OsString> { Ok(OsString::from_wide(&u16_vec[..])) } #[cfg(not(windows))] diff --git a/src/shims/tls.rs b/src/shims/tls.rs index 8627d9a044..a520548798 100644 --- a/src/shims/tls.rs +++ b/src/shims/tls.rs @@ -244,10 +244,13 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx this.eval_windows("thread_local_key", "p_thread_callback")?.to_pointer(this)?; let thread_callback = this.get_ptr_fn(thread_callback)?.as_instance()?; - // Technically, the reason should be `DLL_PROCESS_DETACH` when the main thread exits but std ignores it. + // 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)`. + // 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 }, diff --git a/src/shims/unix/thread.rs b/src/shims/unix/thread.rs index d675df0f53..9365ec9a21 100644 --- a/src/shims/unix/thread.rs +++ b/src/shims/unix/thread.rs @@ -13,11 +13,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx ) -> InterpResult<'tcx, i32> { let this = self.eval_context_mut(); + let thread_info_place = this.deref_operand(thread)?; + + let start_routine = this.read_pointer(start_routine)?; + + let func_arg = this.read_immediate(arg)?; + this.start_thread( - Some(thread), + Some(thread_info_place), start_routine, Abi::C { unwind: false }, - arg, + func_arg, this.layout_of(this.tcx.types.usize)?, )?; @@ -46,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"), false)?; + this.detach_thread( + thread_id.try_into().expect("thread ID should fit in u32"), + /*allow_terminated_joined*/ false, + )?; Ok(0) } diff --git a/src/shims/windows/dlsym.rs b/src/shims/windows/dlsym.rs index fc36913638..4c5e7a9b31 100644 --- a/src/shims/windows/dlsym.rs +++ b/src/shims/windows/dlsym.rs @@ -122,7 +122,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx _ => this.invalid_handle("SetThreadDescription")?, }; - this.set_thread_name_wide(thread, name); + this.set_thread_name_wide(thread, &name); this.write_null(dest)?; } diff --git a/src/shims/windows/foreign_items.rs b/src/shims/windows/foreign_items.rs index cc030ec3d0..d853f3084d 100644 --- a/src/shims/windows/foreign_items.rs +++ b/src/shims/windows/foreign_items.rs @@ -323,15 +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)?; } - // 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)?; - } "GetStdHandle" => { let [which] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; @@ -339,6 +330,7 @@ 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)?; } "CloseHandle" => { @@ -364,9 +356,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx let [handle, timeout] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; - this.WaitForSingleObject(handle, timeout)?; - - this.write_scalar(Scalar::from_u32(0), dest)?; + 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)?; @@ -382,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() => { @@ -417,6 +409,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx 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), } diff --git a/src/shims/windows/handle.rs b/src/shims/windows/handle.rs index 041033717e..443af1dfea 100644 --- a/src/shims/windows/handle.rs +++ b/src/shims/windows/handle.rs @@ -8,6 +8,14 @@ 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; @@ -25,14 +33,6 @@ impl PseudoHandle { } } -/// Miri representation of a Windows `HANDLE` -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum Handle { - Null, - Pseudo(PseudoHandle), - Thread(ThreadId), -} - impl Handle { const NULL_DISCRIMINANT: u32 = 0; const PSEUDO_DISCRIMINANT: u32 = 1; @@ -62,9 +62,7 @@ impl Handle { let floor_log2 = variant_count.ilog2(); // we need to add one for non powers of two to compensate for the difference - let ceil_log2 = if variant_count.is_power_of_two() { floor_log2 } else { floor_log2 + 1 }; - - ceil_log2 + if variant_count.is_power_of_two() { floor_log2 } else { floor_log2 + 1 } } /// Converts a handle into its machine representation. @@ -120,6 +118,7 @@ impl Handle { pub fn to_scalar(self, cx: &impl HasDataLayout) -> Scalar { // 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) } @@ -130,6 +129,7 @@ impl Handle { ) -> InterpResult<'tcx, Option> { 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 { @@ -154,8 +154,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx fn CloseHandle(&mut self, handle_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - match Handle::from_scalar(this.read_scalar(handle_op)?.check_init()?, this)? { - Some(Handle::Thread(thread)) => this.detach_thread(thread, true)?, + 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")?, } diff --git a/src/shims/windows/thread.rs b/src/shims/windows/thread.rs index 08eb4ddba1..06a5887d3e 100644 --- a/src/shims/windows/thread.rs +++ b/src/shims/windows/thread.rs @@ -19,55 +19,71 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx ) -> InterpResult<'tcx, ThreadId> { let this = self.eval_context_mut(); - if !this.ptr_is_null(this.read_pointer(security_op)?)? { - throw_unsup_format!("non-null `lpThreadAttributes` in `CreateThread`") - } + let security = this.read_pointer(security_op)?; // stacksize is ignored, but still needs to be a valid usize - let _ = this.read_scalar(stacksize_op)?.to_machine_usize(this)?; + 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) } - let thread = - if this.ptr_is_null(this.read_pointer(thread_op)?)? { None } else { Some(thread_op) }; + if !this.ptr_is_null(security)? { + throw_unsup_format!("non-null `lpThreadAttributes` in `CreateThread`") + } this.start_thread( thread, - start_op, + start_routine, Abi::System { unwind: false }, - arg_op, + func_arg, this.layout_of(this.tcx.types.u32)?, ) } fn WaitForSingleObject( &mut self, - handle: &OpTy<'tcx, Provenance>, - timeout: &OpTy<'tcx, Provenance>, - ) -> InterpResult<'tcx> { + handle_op: &OpTy<'tcx, Provenance>, + timeout_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, u32> { let this = self.eval_context_mut(); - let thread = match Handle::from_scalar(this.read_scalar(handle)?.check_init()?, this)? { + 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, joining the current thread is not UB on windows. - // It will just deadlock. + // 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 this.read_scalar(timeout)?.to_u32()? != this.eval_windows("c", "INFINITE")?.to_u32()? { + if timeout != this.eval_windows("c", "INFINITE")?.to_u32()? { throw_unsup_format!("`WaitForSingleObject` with non-infinite timeout"); } this.join_thread(thread)?; - Ok(()) + Ok(0) } } diff --git a/src/thread.rs b/src/thread.rs index f7fcdd5822..b92728be20 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -181,7 +181,7 @@ impl<'mir, 'tcx> Thread<'mir, 'tcx> { } /// A specific moment in time. -#[derive(Debug, Copy, Clone)] +#[derive(Debug)] pub enum Time { Monotonic(Instant), RealTime(SystemTime), @@ -357,16 +357,19 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> { /// /// `allow_terminated_joined` allows detaching joined threads that have already terminated. /// This matches Windows's behavior for `CloseHandle`. + /// + /// See : + /// > 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"); } @@ -406,7 +409,7 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> { } /// Mark that the active thread tries to exclusively join the thread with `joined_thread_id`. - /// If the thread is already joined by another thread + /// If the thread is already joined by another thread, it will throw UB fn join_thread_exclusive( &mut self, joined_thread_id: ThreadId, @@ -424,7 +427,7 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> { self.threads .iter() .all(|thread| thread.state != ThreadState::BlockedOnJoin(joined_thread_id)), - "a joinable thread already has threads waiting for its termination" + "this thread already has threads waiting for its termination" ); self.join_thread(joined_thread_id, data_race) @@ -803,12 +806,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx } #[inline] - fn set_thread_name_wide(&mut self, thread: ThreadId, new_thread_name: Vec) { + fn set_thread_name_wide(&mut self, thread: ThreadId, new_thread_name: &[u16]) { let this = self.eval_context_mut(); - this.machine.threads.set_thread_name( - thread, - new_thread_name.into_iter().flat_map(u16::to_ne_bytes).collect(), - ); + + // 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] diff --git a/tests/fail/concurrency/libc_pthread_join_detached.stderr b/tests/fail/concurrency/libc_pthread_join_detached.stderr index e381a71b25..92b693c0fd 100644 --- a/tests/fail/concurrency/libc_pthread_join_detached.stderr +++ b/tests/fail/concurrency/libc_pthread_join_detached.stderr @@ -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 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 diff --git a/tests/fail/concurrency/libc_pthread_join_joined.rs b/tests/fail/concurrency/libc_pthread_join_joined.rs index ebd1710bbf..04ca4bbb3f 100644 --- a/tests/fail/concurrency/libc_pthread_join_joined.rs +++ b/tests/fail/concurrency/libc_pthread_join_joined.rs @@ -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 } } diff --git a/tests/fail/concurrency/libc_pthread_join_joined.stderr b/tests/fail/concurrency/libc_pthread_join_joined.stderr index 5ac35ffe51..f11b94cde8 100644 --- a/tests/fail/concurrency/libc_pthread_join_joined.stderr +++ b/tests/fail/concurrency/libc_pthread_join_joined.stderr @@ -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 diff --git a/tests/fail/concurrency/libc_pthread_join_main.rs b/tests/fail/concurrency/libc_pthread_join_main.rs index df6b520431..7576518216 100644 --- a/tests/fail/concurrency/libc_pthread_join_main.rs +++ b/tests/fail/concurrency/libc_pthread_join_main.rs @@ -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(); diff --git a/tests/fail/concurrency/libc_pthread_join_main.stderr b/tests/fail/concurrency/libc_pthread_join_main.stderr index fe136549ce..c162f37b30 100644 --- a/tests/fail/concurrency/libc_pthread_join_main.stderr +++ b/tests/fail/concurrency/libc_pthread_join_main.stderr @@ -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 diff --git a/tests/fail/concurrency/libc_pthread_join_multiple.rs b/tests/fail/concurrency/libc_pthread_join_multiple.rs index e5187093be..966f416eea 100644 --- a/tests/fail/concurrency/libc_pthread_join_multiple.rs +++ b/tests/fail/concurrency/libc_pthread_join_multiple.rs @@ -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(); diff --git a/tests/fail/concurrency/libc_pthread_join_multiple.stderr b/tests/fail/concurrency/libc_pthread_join_multiple.stderr index 9b91e5a3d0..c0c73086f1 100644 --- a/tests/fail/concurrency/libc_pthread_join_multiple.stderr +++ b/tests/fail/concurrency/libc_pthread_join_multiple.stderr @@ -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 diff --git a/tests/fail/concurrency/windows_join_main.rs b/tests/fail/concurrency/windows_join_main.rs index d3b54cdf15..cde6d19ef2 100644 --- a/tests/fail/concurrency/windows_join_main.rs +++ b/tests/fail/concurrency/windows_join_main.rs @@ -12,7 +12,7 @@ extern "system" { const INFINITE: u32 = u32::MAX; -// This is how miri represents the handle for thread 0. +// 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; diff --git a/tests/pass/concurrency/tls_lib_drop.rs b/tests/pass/concurrency/tls_lib_drop.rs index 8ce011ede3..3fd6e2d6f2 100644 --- a/tests/pass/concurrency/tls_lib_drop.rs +++ b/tests/pass/concurrency/tls_lib_drop.rs @@ -1,3 +1,5 @@ +//@ignore-target-windows: TLS destructor order is different on Windows. + use std::cell::RefCell; use std::thread; diff --git a/tests/pass/concurrency/tls_lib_drop_windows.rs b/tests/pass/concurrency/tls_lib_drop_windows.rs new file mode 100644 index 0000000000..e8c6538e70 --- /dev/null +++ b/tests/pass/concurrency/tls_lib_drop_windows.rs @@ -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, +} + +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>>, +} + +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(); +} diff --git a/tests/pass/concurrency/tls_lib_drop_windows.stdout b/tests/pass/concurrency/tls_lib_drop_windows.stdout new file mode 100644 index 0000000000..e5b8efcaf5 --- /dev/null +++ b/tests/pass/concurrency/tls_lib_drop_windows.stdout @@ -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. From c466ac0b3ec200ac53b1ca8196c47261b692bda1 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 18 Aug 2022 11:25:20 -0400 Subject: [PATCH 5/5] add some missing assert_target_os --- src/shims/time.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shims/time.rs b/src/shims/time.rs index e495f72366..67303c47db 100644 --- a/src/shims/time.rs +++ b/src/shims/time.rs @@ -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)?)? { @@ -238,6 +237,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx 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()?;