Skip to content

Commit e2f3e84

Browse files
committed
basic theading
1 parent 0ea599e commit e2f3e84

Some content is hidden

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

43 files changed

+480
-315
lines changed

src/lib.rs

Lines changed: 2 additions & 0 deletions
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
#![warn(rust_2018_idioms)]
911
#![allow(
1012
clippy::collapsible_else_if,

src/shims/tls.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
235235
fn schedule_windows_tls_dtors(&mut self) -> InterpResult<'tcx> {
236236
let this = self.eval_context_mut();
237237
let active_thread = this.get_active_thread();
238-
assert_eq!(this.get_total_thread_count(), 1, "concurrency on Windows is not supported");
238+
239239
// Windows has a special magic linker section that is run on certain events.
240240
// Instead of searching for that section and supporting arbitrary hooks in there
241241
// (that would be basically https://github.com/rust-lang/miri/issues/450),
@@ -252,7 +252,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
252252
this.get_ptr_fn(this.scalar_to_ptr(thread_callback)?)?.as_instance()?;
253253

254254
// The signature of this function is `unsafe extern "system" fn(h: c::LPVOID, dwReason: c::DWORD, pv: c::LPVOID)`.
255-
let reason = this.eval_path_scalar(&["std", "sys", "windows", "c", "DLL_THREAD_DETACH"])?;
255+
let reason = this.eval_windows("c", "DLL_THREAD_DETACH")?;
256256
let ret_place = MPlaceTy::dangling(this.machine.layouts.unit).into();
257257
this.call_function(
258258
thread_callback,

src/shims/unix/thread.rs

Lines changed: 6 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,47 +13,14 @@ 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.
21-
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(),
16+
this.start_thread(
17+
Some(thread),
18+
start_routine,
19+
Abi::System { unwind: false },
20+
arg,
21+
this.layout_of(this.tcx.types.usize)?,
2522
)?;
2623

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)?;
32-
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);
36-
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())?;
45-
46-
this.call_function(
47-
instance,
48-
Abi::C { unwind: false },
49-
&[*func_arg],
50-
&ret_place.into(),
51-
StackPopCleanup::Root { cleanup: true },
52-
)?;
53-
54-
// Restore the old active thread frame.
55-
this.set_active_thread(old_thread_id);
56-
5724
Ok(0)
5825
}
5926

src/shims/windows/dlsym.rs

Lines changed: 19 additions & 0 deletions
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::Handle;
89
use crate::*;
910

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

1517
impl Dlsym {
@@ -20,6 +22,7 @@ impl Dlsym {
2022
"GetSystemTimePreciseAsFileTime" => None,
2123
"SetThreadDescription" => None,
2224
"NtWriteFile" => Some(Dlsym::NtWriteFile),
25+
"SetThreadDescription" => Some(Dlsym::SetThreadDescription),
2326
_ => throw_unsup_format!("unsupported Windows dlsym: {}", name),
2427
})
2528
}
@@ -106,6 +109,22 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
106109
dest,
107110
)?;
108111
}
112+
Dlsym::SetThreadDescription => {
113+
let [handle, name] = check_arg_count(args)?;
114+
115+
let name = this.read_wide_str(this.read_pointer(name)?)?;
116+
117+
let thread =
118+
match Handle::from_scalar(this.read_scalar(handle)?.check_init()?, this)? {
119+
Some(Handle::Thread(thread)) => thread,
120+
Some(Handle::CurrentThread) => this.get_active_thread(),
121+
_ => throw_ub_format!("invalid handle"),
122+
};
123+
124+
this.set_thread_name_wide(thread, name);
125+
126+
this.write_null(dest)?;
127+
}
109128
}
110129

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

src/shims/windows/foreign_items.rs

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
use std::iter;
2+
use std::time::{Duration, Instant};
23

34
use rustc_middle::mir;
5+
use rustc_middle::ty::layout::LayoutOf;
46
use rustc_span::Symbol;
57
use rustc_target::abi::Size;
68
use rustc_target::spec::abi::Abi;
79

10+
use crate::thread::Time;
811
use crate::*;
912
use shims::foreign_items::EmulateByNameResult;
13+
use shims::windows::handle::Handle;
1014
use shims::windows::sync::EvalContextExt as _;
15+
use shims::windows::thread::EvalContextExt as _;
16+
1117
use smallvec::SmallVec;
1218

1319
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
@@ -230,6 +236,29 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
230236
let result = this.QueryPerformanceFrequency(lpFrequency)?;
231237
this.write_scalar(Scalar::from_i32(result), dest)?;
232238
}
239+
"Sleep" => {
240+
let [timeout] =
241+
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
242+
243+
this.check_no_isolation("`Sleep`")?;
244+
245+
let timeout_ms = this.read_scalar(timeout)?.to_u32()?;
246+
247+
let duration = Duration::from_millis(timeout_ms as u64);
248+
let timeout_time = Time::Monotonic(Instant::now().checked_add(duration).unwrap());
249+
250+
let active_thread = this.get_active_thread();
251+
this.block_thread(active_thread);
252+
253+
this.register_timeout_callback(
254+
active_thread,
255+
timeout_time,
256+
Box::new(move |ecx| {
257+
ecx.unblock_thread(active_thread);
258+
Ok(())
259+
}),
260+
);
261+
}
233262

234263
// Synchronization primitives
235264
"AcquireSRWLockExclusive" => {
@@ -340,14 +369,48 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
340369
// std-only shim.
341370
this.write_scalar(Scalar::from_machine_isize(which.into(), this), dest)?;
342371
}
372+
"CloseHandle" => {
373+
let [handle] =
374+
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
375+
376+
match Handle::from_scalar(this.read_scalar(handle)?.check_init()?, this)? {
377+
Some(Handle::Thread(thread)) => this.detach_thread(thread)?,
378+
_ => throw_ub_format!("invalid handle"),
379+
};
343380

344-
// Better error for attempts to create a thread
381+
this.write_scalar(Scalar::from_u32(1), dest)?;
382+
}
383+
384+
// Threading
345385
"CreateThread" => {
346-
let [_, _, _, _, _, _] =
386+
let [_security, _stacksize, start, arg, _flags, thread] =
387+
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
388+
389+
let thread =
390+
if this.ptr_is_null(this.read_pointer(thread)?)? { None } else { Some(thread) };
391+
392+
let thread_id = this.start_thread(
393+
thread,
394+
start,
395+
Abi::System { unwind: false },
396+
arg,
397+
this.layout_of(this.tcx.types.u32)?,
398+
)?;
399+
400+
this.write_scalar(Handle::Thread(thread_id).to_scalar(this), dest)?;
401+
}
402+
"WaitForSingleObject" => {
403+
let [handle, timeout] =
347404
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
348405

349-
this.handle_unsupported("can't create threads on Windows")?;
350-
return Ok(EmulateByNameResult::AlreadyJumped);
406+
this.WaitForSingleObject(handle, timeout)?;
407+
408+
this.write_scalar(Scalar::from_u32(0), dest)?;
409+
}
410+
"GetCurrentThread" => {
411+
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
412+
413+
this.write_scalar(Handle::CurrentThread.to_scalar(this), dest)?;
351414
}
352415

353416
// Incomplete shims that we "stub out" just to get pre-main initialization code to work.

src/shims/windows/handle.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
use rustc_target::abi::HasDataLayout;
2+
use std::mem::variant_count;
3+
4+
use crate::*;
5+
6+
#[derive(Clone, Copy)]
7+
enum RealHandle {
8+
Thread(ThreadId),
9+
}
10+
11+
impl RealHandle {
12+
fn discriminant(self) -> u64 {
13+
match self {
14+
// can't use zero here because all zero handle is invalid
15+
Self::Thread(_) => 1,
16+
}
17+
}
18+
19+
fn data(self) -> u64 {
20+
match self {
21+
Self::Thread(thread) => thread.to_u32() as u64,
22+
}
23+
}
24+
25+
fn packed_disc_size() -> u64 {
26+
(variant_count::<Self>().log2() + 1) as u64
27+
}
28+
29+
fn to_packed(self, bits: u64) -> u64 {
30+
// top bit and lower 2 bits need to be clear
31+
let usable_bits = (bits - 3).min(32);
32+
33+
let disc_size = Self::packed_disc_size();
34+
let data_size = usable_bits - disc_size;
35+
36+
let discriminant = self.discriminant();
37+
let data = self.data();
38+
39+
assert!(discriminant < 2u64.pow(disc_size as u32));
40+
assert!(data < 2u64.pow(data_size as u32));
41+
42+
(discriminant << data_size | data) << 2
43+
}
44+
45+
fn new(discriminant: u32, data: u32) -> Option<Self> {
46+
match discriminant {
47+
1 => Some(Self::Thread(data.into())),
48+
_ => None,
49+
}
50+
}
51+
52+
fn from_packed(handle: u64, bits: u64) -> Option<Self> {
53+
let usable_bits = (bits - 3).min(32);
54+
55+
let disc_size = Self::packed_disc_size();
56+
let data_size = usable_bits - disc_size;
57+
let data_mask = 2u64.pow(data_size as u32) - 1;
58+
59+
let discriminant = handle >> data_size >> 2;
60+
let data = handle >> 2 & data_mask;
61+
62+
Self::new(discriminant.try_into().unwrap(), data.try_into().unwrap())
63+
}
64+
}
65+
66+
/// Miri representation of a Windows `HANDLE`
67+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
68+
pub enum Handle {
69+
Null, // = 0
70+
// pseudo-handles
71+
CurrentThread, // = -2
72+
// real handles
73+
Thread(ThreadId),
74+
}
75+
76+
impl Handle {
77+
fn to_packed(self, bits: u64) -> i64 {
78+
match self {
79+
Self::Null => 0,
80+
Self::CurrentThread => -2,
81+
Self::Thread(thread) => RealHandle::Thread(thread).to_packed(bits) as i64,
82+
}
83+
}
84+
85+
pub fn to_scalar(self, cx: &impl HasDataLayout) -> Scalar<Tag> {
86+
let bits = cx.data_layout().pointer_size.bits();
87+
88+
let handle = self.to_packed(bits);
89+
90+
Scalar::from_machine_isize(handle, cx)
91+
}
92+
93+
fn from_packed(handle: i64, bits: u64) -> Option<Self> {
94+
if handle == 0 {
95+
Some(Self::Null)
96+
} else if handle == -2 {
97+
Some(Self::CurrentThread)
98+
} else {
99+
match RealHandle::from_packed(handle as u64, bits)? {
100+
RealHandle::Thread(id) => Some(Self::Thread(id)),
101+
}
102+
}
103+
}
104+
105+
pub fn from_scalar<'tcx>(
106+
handle: Scalar<Tag>,
107+
cx: &impl HasDataLayout,
108+
) -> InterpResult<'tcx, Option<Self>> {
109+
let bits = cx.data_layout().pointer_size.bits();
110+
111+
let handle = handle.to_machine_isize(cx)?;
112+
113+
Ok(Self::from_packed(handle, bits))
114+
}
115+
}

src/shims/windows/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
pub mod dlsym;
22
pub mod foreign_items;
33

4+
mod handle;
45
mod sync;
6+
mod thread;

src/shims/windows/thread.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use std::time::{Duration, Instant};
2+
3+
use crate::thread::Time;
4+
use crate::*;
5+
use shims::windows::handle::Handle;
6+
7+
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
8+
9+
#[allow(non_snake_case)]
10+
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
11+
fn WaitForSingleObject(
12+
&mut self,
13+
handle: &OpTy<'tcx, Tag>,
14+
timeout: &OpTy<'tcx, Tag>,
15+
) -> InterpResult<'tcx> {
16+
let this = self.eval_context_mut();
17+
18+
let thread = match Handle::from_scalar(this.read_scalar(handle)?.check_init()?, this)? {
19+
Some(Handle::Thread(thread)) => thread,
20+
Some(Handle::CurrentThread) => throw_ub_format!("trying to wait on itself"),
21+
_ => throw_ub_format!("invalid handle"),
22+
};
23+
24+
if this.read_scalar(timeout)?.to_u32()? != this.eval_windows("c", "INFINITE")?.to_u32()? {
25+
throw_unsup_format!("Miri does not support joining with timeout")
26+
}
27+
28+
let timeout_ms = this.read_scalar(timeout)?.to_u32()?;
29+
30+
let timeout_time = if timeout_ms == this.eval_windows("c", "INFINITE")?.to_u32()? {
31+
None
32+
} else {
33+
let duration = Duration::from_millis(timeout_ms as u64);
34+
35+
Some(Time::Monotonic(Instant::now().checked_add(duration).unwrap()))
36+
};
37+
38+
this.wait_on_thread(timeout_time, thread)?;
39+
40+
Ok(())
41+
}
42+
}

0 commit comments

Comments
 (0)