Skip to content

Commit 46da748

Browse files
committed
Auto merge of rust-lang#2231 - DrMeepster:winfred, r=RalfJung
Windows thread support: Part 1 This PR adds support for threads on Windows.
2 parents 339500f + c466ac0 commit 46da748

File tree

84 files changed

+919
-223
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+919
-223
lines changed

src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#![feature(try_blocks)]
66
#![feature(let_else)]
77
#![feature(io_error_more)]
8+
#![feature(int_log)]
9+
#![feature(variant_count)]
810
#![feature(yeet_expr)]
911
#![feature(is_some_with)]
1012
#![feature(nonzero_ops)]

src/machine.rs

+1
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,7 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
421421
) -> InterpResult<'tcx> {
422422
EnvVars::init(this, config)?;
423423
Evaluator::init_extern_statics(this)?;
424+
ThreadManager::init(this);
424425
Ok(())
425426
}
426427

src/shims/os_str.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
7474
'mir: 'a,
7575
{
7676
#[cfg(windows)]
77-
pub fn u16vec_to_osstring<'tcx, 'a>(u16_vec: Vec<u16>) -> InterpResult<'tcx, OsString> {
77+
pub fn u16vec_to_osstring<'tcx>(u16_vec: Vec<u16>) -> InterpResult<'tcx, OsString> {
7878
Ok(OsString::from_wide(&u16_vec[..]))
7979
}
8080
#[cfg(not(windows))]

src/shims/time.rs

+29-3
Original file line numberDiff line numberDiff line change
@@ -197,12 +197,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
197197
fn nanosleep(
198198
&mut self,
199199
req_op: &OpTy<'tcx, Provenance>,
200-
_rem: &OpTy<'tcx, Provenance>,
200+
_rem: &OpTy<'tcx, Provenance>, // Signal handlers are not supported, so rem will never be written to.
201201
) -> InterpResult<'tcx, i32> {
202-
// Signal handlers are not supported, so rem will never be written to.
203-
204202
let this = self.eval_context_mut();
205203

204+
this.assert_target_os_is_unix("nanosleep");
206205
this.check_no_isolation("`nanosleep`")?;
207206

208207
let duration = match this.read_timespec(&this.deref_operand(req_op)?)? {
@@ -233,4 +232,31 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
233232

234233
Ok(0)
235234
}
235+
236+
#[allow(non_snake_case)]
237+
fn Sleep(&mut self, timeout: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
238+
let this = self.eval_context_mut();
239+
240+
this.assert_target_os("windows", "Sleep");
241+
this.check_no_isolation("`Sleep`")?;
242+
243+
let timeout_ms = this.read_scalar(timeout)?.to_u32()?;
244+
245+
let duration = Duration::from_millis(timeout_ms.into());
246+
let timeout_time = Time::Monotonic(Instant::now().checked_add(duration).unwrap());
247+
248+
let active_thread = this.get_active_thread();
249+
this.block_thread(active_thread);
250+
251+
this.register_timeout_callback(
252+
active_thread,
253+
timeout_time,
254+
Box::new(move |ecx| {
255+
ecx.unblock_thread(active_thread);
256+
Ok(())
257+
}),
258+
);
259+
260+
Ok(())
261+
}
236262
}

src/shims/tls.rs

+11-8
Original file line numberDiff line numberDiff line change
@@ -229,25 +229,28 @@ impl<'tcx> TlsData<'tcx> {
229229

230230
impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
231231
trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
232-
/// Schedule TLS destructors for the main thread on Windows. The
233-
/// implementation assumes that we do not support concurrency on Windows
234-
/// yet.
232+
/// Schedule TLS destructors for Windows.
233+
/// On windows, TLS destructors are managed by std.
235234
fn schedule_windows_tls_dtors(&mut self) -> InterpResult<'tcx> {
236235
let this = self.eval_context_mut();
237236
let active_thread = this.get_active_thread();
238-
assert_eq!(this.get_total_thread_count(), 1, "concurrency on Windows is not supported");
237+
239238
// Windows has a special magic linker section that is run on certain events.
240239
// Instead of searching for that section and supporting arbitrary hooks in there
241240
// (that would be basically https://github.com/rust-lang/miri/issues/450),
242241
// we specifically look up the static in libstd that we know is placed
243242
// in that section.
244-
let thread_callback = this
245-
.eval_path_scalar(&["std", "sys", "windows", "thread_local_key", "p_thread_callback"])?
246-
.to_pointer(this)?;
243+
let thread_callback =
244+
this.eval_windows("thread_local_key", "p_thread_callback")?.to_pointer(this)?;
247245
let thread_callback = this.get_ptr_fn(thread_callback)?.as_instance()?;
248246

247+
// FIXME: Technically, the reason should be `DLL_PROCESS_DETACH` when the main thread exits
248+
// but std treats both the same.
249+
let reason = this.eval_windows("c", "DLL_THREAD_DETACH")?;
250+
249251
// The signature of this function is `unsafe extern "system" fn(h: c::LPVOID, dwReason: c::DWORD, pv: c::LPVOID)`.
250-
let reason = this.eval_path_scalar(&["std", "sys", "windows", "c", "DLL_THREAD_DETACH"])?;
252+
// FIXME: `h` should be a handle to the current module and what `pv` should be is unknown
253+
// but both are ignored by std
251254
this.call_function(
252255
thread_callback,
253256
Abi::System { unwind: false },

src/shims/unix/thread.rs

+12-36
Original file line numberDiff line numberDiff line change
@@ -13,47 +13,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
1313
) -> InterpResult<'tcx, i32> {
1414
let this = self.eval_context_mut();
1515

16-
// Create the new thread
17-
let new_thread_id = this.create_thread();
18-
19-
// Write the current thread-id, switch to the next thread later
20-
// to treat this write operation as occuring on the current thread.
2116
let thread_info_place = this.deref_operand(thread)?;
22-
this.write_scalar(
23-
Scalar::from_uint(new_thread_id.to_u32(), thread_info_place.layout.size),
24-
&thread_info_place.into(),
25-
)?;
26-
27-
// Read the function argument that will be sent to the new thread
28-
// before the thread starts executing since reading after the
29-
// context switch will incorrectly report a data-race.
30-
let fn_ptr = this.read_pointer(start_routine)?;
31-
let func_arg = this.read_immediate(arg)?;
3217

33-
// Finally switch to new thread so that we can push the first stackframe.
34-
// After this all accesses will be treated as occuring in the new thread.
35-
let old_thread_id = this.set_active_thread(new_thread_id);
18+
let start_routine = this.read_pointer(start_routine)?;
3619

37-
// Perform the function pointer load in the new thread frame.
38-
let instance = this.get_ptr_fn(fn_ptr)?.as_instance()?;
39-
40-
// Note: the returned value is currently ignored (see the FIXME in
41-
// pthread_join below) because the Rust standard library does not use
42-
// it.
43-
let ret_place =
44-
this.allocate(this.layout_of(this.tcx.types.usize)?, MiriMemoryKind::Machine.into())?;
20+
let func_arg = this.read_immediate(arg)?;
4521

46-
this.call_function(
47-
instance,
22+
this.start_thread(
23+
Some(thread_info_place),
24+
start_routine,
4825
Abi::C { unwind: false },
49-
&[*func_arg],
50-
Some(&ret_place.into()),
51-
StackPopCleanup::Root { cleanup: true },
26+
func_arg,
27+
this.layout_of(this.tcx.types.usize)?,
5228
)?;
5329

54-
// Restore the old active thread frame.
55-
this.set_active_thread(old_thread_id);
56-
5730
Ok(0)
5831
}
5932

@@ -70,7 +43,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
7043
}
7144

7245
let thread_id = this.read_scalar(thread)?.to_machine_usize(this)?;
73-
this.join_thread(thread_id.try_into().expect("thread ID should fit in u32"))?;
46+
this.join_thread_exclusive(thread_id.try_into().expect("thread ID should fit in u32"))?;
7447

7548
Ok(0)
7649
}
@@ -79,7 +52,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
7952
let this = self.eval_context_mut();
8053

8154
let thread_id = this.read_scalar(thread)?.to_machine_usize(this)?;
82-
this.detach_thread(thread_id.try_into().expect("thread ID should fit in u32"))?;
55+
this.detach_thread(
56+
thread_id.try_into().expect("thread ID should fit in u32"),
57+
/*allow_terminated_joined*/ false,
58+
)?;
8359

8460
Ok(0)
8561
}

src/shims/windows/dlsym.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ use rustc_target::spec::abi::Abi;
55
use log::trace;
66

77
use crate::helpers::check_arg_count;
8+
use crate::shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle};
89
use crate::*;
910

1011
#[derive(Debug, Copy, Clone)]
1112
pub enum Dlsym {
1213
NtWriteFile,
14+
SetThreadDescription,
1315
}
1416

1517
impl Dlsym {
@@ -18,8 +20,8 @@ impl Dlsym {
1820
pub fn from_str<'tcx>(name: &str) -> InterpResult<'tcx, Option<Dlsym>> {
1921
Ok(match name {
2022
"GetSystemTimePreciseAsFileTime" => None,
21-
"SetThreadDescription" => None,
2223
"NtWriteFile" => Some(Dlsym::NtWriteFile),
24+
"SetThreadDescription" => Some(Dlsym::SetThreadDescription),
2325
_ => throw_unsup_format!("unsupported Windows dlsym: {}", name),
2426
})
2527
}
@@ -107,6 +109,23 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
107109
dest,
108110
)?;
109111
}
112+
Dlsym::SetThreadDescription => {
113+
let [handle, name] = check_arg_count(args)?;
114+
115+
let handle = this.read_scalar(handle)?.check_init()?;
116+
117+
let name = this.read_wide_str(this.read_pointer(name)?)?;
118+
119+
let thread = match Handle::from_scalar(handle, this)? {
120+
Some(Handle::Thread(thread)) => thread,
121+
Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => this.get_active_thread(),
122+
_ => this.invalid_handle("SetThreadDescription")?,
123+
};
124+
125+
this.set_thread_name_wide(thread, &name);
126+
127+
this.write_null(dest)?;
128+
}
110129
}
111130

112131
trace!("{:?}", this.dump_place(**dest));

src/shims/windows/foreign_items.rs

+49-44
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ use rustc_target::spec::abi::Abi;
66

77
use crate::*;
88
use shims::foreign_items::EmulateByNameResult;
9+
use shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle};
910
use shims::windows::sync::EvalContextExt as _;
11+
use shims::windows::thread::EvalContextExt as _;
12+
1013
use smallvec::SmallVec;
1114

1215
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
@@ -219,6 +222,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
219222
let result = this.QueryPerformanceFrequency(lpFrequency)?;
220223
this.write_scalar(Scalar::from_i32(result), dest)?;
221224
}
225+
"Sleep" => {
226+
let [timeout] =
227+
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
228+
229+
this.Sleep(timeout)?;
230+
}
222231

223232
// Synchronization primitives
224233
"AcquireSRWLockExclusive" => {
@@ -314,36 +323,57 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
314323
// FIXME: we should set last_error, but to what?
315324
this.write_null(dest)?;
316325
}
317-
"SwitchToThread" => {
318-
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
319-
// Note that once Miri supports concurrency, this will need to return a nonzero
320-
// value if this call does result in switching to another thread.
321-
this.write_null(dest)?;
322-
}
323326
"GetStdHandle" => {
324327
let [which] =
325328
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
326329
let which = this.read_scalar(which)?.to_i32()?;
327330
// We just make this the identity function, so we know later in `NtWriteFile` which
328331
// one it is. This is very fake, but libtest needs it so we cannot make it a
329332
// std-only shim.
333+
// FIXME: this should return real HANDLEs when io support is added
330334
this.write_scalar(Scalar::from_machine_isize(which.into(), this), dest)?;
331335
}
336+
"CloseHandle" => {
337+
let [handle] =
338+
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
339+
340+
this.CloseHandle(handle)?;
341+
342+
this.write_scalar(Scalar::from_u32(1), dest)?;
343+
}
332344

333-
// Better error for attempts to create a thread
345+
// Threading
334346
"CreateThread" => {
335-
let [_, _, _, _, _, _] =
347+
let [security, stacksize, start, arg, flags, thread] =
336348
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
337349

338-
this.handle_unsupported("can't create threads on Windows")?;
339-
return Ok(EmulateByNameResult::AlreadyJumped);
350+
let thread_id =
351+
this.CreateThread(security, stacksize, start, arg, flags, thread)?;
352+
353+
this.write_scalar(Handle::Thread(thread_id).to_scalar(this), dest)?;
354+
}
355+
"WaitForSingleObject" => {
356+
let [handle, timeout] =
357+
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
358+
359+
let ret = this.WaitForSingleObject(handle, timeout)?;
360+
this.write_scalar(Scalar::from_u32(ret), dest)?;
361+
}
362+
"GetCurrentThread" => {
363+
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
364+
365+
this.write_scalar(
366+
Handle::Pseudo(PseudoHandle::CurrentThread).to_scalar(this),
367+
dest,
368+
)?;
340369
}
341370

342371
// Incomplete shims that we "stub out" just to get pre-main initialization code to work.
343372
// These shims are enabled only when the caller is in the standard library.
344373
"GetProcessHeap" if this.frame_in_std() => {
345374
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
346375
// Just fake a HANDLE
376+
// It's fine to not use the Handle type here because its a stub
347377
this.write_scalar(Scalar::from_machine_isize(1, this), dest)?;
348378
}
349379
"GetModuleHandleA" if this.frame_in_std() => {
@@ -374,45 +404,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
374404
// Any non zero value works for the stdlib. This is just used for stack overflows anyway.
375405
this.write_scalar(Scalar::from_u32(1), dest)?;
376406
}
377-
| "InitializeCriticalSection"
378-
| "EnterCriticalSection"
379-
| "LeaveCriticalSection"
380-
| "DeleteCriticalSection"
381-
if this.frame_in_std() =>
382-
{
383-
#[allow(non_snake_case)]
384-
let [_lpCriticalSection] =
385-
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
386-
assert_eq!(
387-
this.get_total_thread_count(),
388-
1,
389-
"concurrency on Windows is not supported"
390-
);
391-
// Nothing to do, not even a return value.
392-
// (Windows locks are reentrant, and we have only 1 thread,
393-
// so not doing any futher checks here is at least not incorrect.)
394-
}
395-
"TryEnterCriticalSection" if this.frame_in_std() => {
396-
#[allow(non_snake_case)]
397-
let [_lpCriticalSection] =
398-
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
399-
assert_eq!(
400-
this.get_total_thread_count(),
401-
1,
402-
"concurrency on Windows is not supported"
403-
);
404-
// There is only one thread, so this always succeeds and returns TRUE.
405-
this.write_scalar(Scalar::from_i32(1), dest)?;
406-
}
407-
"GetCurrentThread" if this.frame_in_std() => {
408-
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
409-
this.write_scalar(Scalar::from_machine_isize(1, this), dest)?;
410-
}
411407
"GetCurrentProcessId" if this.frame_in_std() => {
412408
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
413409
let result = this.GetCurrentProcessId()?;
414410
this.write_scalar(Scalar::from_u32(result), dest)?;
415411
}
412+
// this is only callable from std because we know that std ignores the return value
413+
"SwitchToThread" if this.frame_in_std() => {
414+
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
415+
416+
this.yield_active_thread();
417+
418+
// FIXME: this should return a nonzero value if this call does result in switching to another thread.
419+
this.write_null(dest)?;
420+
}
416421

417422
_ => return Ok(EmulateByNameResult::NotSupported),
418423
}

0 commit comments

Comments
 (0)