Skip to content

Commit a0fbf0d

Browse files
committed
Auto merge of rust-lang#2630 - RalfJung:windows-parking, r=RalfJung
Implement thread parking for Windows Cc rust-lang/miri#2628 Based on code by `@DrMeepster.` However I adjusted `WakeByAddressSingle`: I don't think the futex value is compared *again* after the thread is woken up. I see nothing in the Windows docs indicating such a comparison, and the Linux futex does not behave like that either. So we only check the value before sleeping, same as on Linux.
2 parents 4827d41 + 9b0cdf9 commit a0fbf0d

File tree

11 files changed

+190
-50
lines changed

11 files changed

+190
-50
lines changed

src/tools/miri/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ pub use crate::eval::{
9898
pub use crate::helpers::{CurrentSpan, EvalContextExt as _};
9999
pub use crate::intptrcast::ProvenanceMode;
100100
pub use crate::machine::{
101-
AllocExtra, FrameData, MiriInterpCx, MiriInterpCxExt, MiriMachine, MiriMemoryKind, Provenance,
102-
ProvenanceExtra, PAGE_SIZE, STACK_ADDR, STACK_SIZE,
101+
AllocExtra, FrameData, MiriInterpCx, MiriInterpCxExt, MiriMachine, MiriMemoryKind,
102+
PrimitiveLayouts, Provenance, ProvenanceExtra, PAGE_SIZE, STACK_ADDR, STACK_SIZE,
103103
};
104104
pub use crate::mono_hash_map::MonoHashMap;
105105
pub use crate::operator::EvalContextExt as _;

src/tools/miri/src/machine.rs

+30
Original file line numberDiff line numberDiff line change
@@ -276,10 +276,14 @@ pub struct PrimitiveLayouts<'tcx> {
276276
pub i8: TyAndLayout<'tcx>,
277277
pub i16: TyAndLayout<'tcx>,
278278
pub i32: TyAndLayout<'tcx>,
279+
pub i64: TyAndLayout<'tcx>,
280+
pub i128: TyAndLayout<'tcx>,
279281
pub isize: TyAndLayout<'tcx>,
280282
pub u8: TyAndLayout<'tcx>,
281283
pub u16: TyAndLayout<'tcx>,
282284
pub u32: TyAndLayout<'tcx>,
285+
pub u64: TyAndLayout<'tcx>,
286+
pub u128: TyAndLayout<'tcx>,
283287
pub usize: TyAndLayout<'tcx>,
284288
pub bool: TyAndLayout<'tcx>,
285289
pub mut_raw_ptr: TyAndLayout<'tcx>, // *mut ()
@@ -296,16 +300,42 @@ impl<'mir, 'tcx: 'mir> PrimitiveLayouts<'tcx> {
296300
i8: layout_cx.layout_of(tcx.types.i8)?,
297301
i16: layout_cx.layout_of(tcx.types.i16)?,
298302
i32: layout_cx.layout_of(tcx.types.i32)?,
303+
i64: layout_cx.layout_of(tcx.types.i64)?,
304+
i128: layout_cx.layout_of(tcx.types.i128)?,
299305
isize: layout_cx.layout_of(tcx.types.isize)?,
300306
u8: layout_cx.layout_of(tcx.types.u8)?,
301307
u16: layout_cx.layout_of(tcx.types.u16)?,
302308
u32: layout_cx.layout_of(tcx.types.u32)?,
309+
u64: layout_cx.layout_of(tcx.types.u64)?,
310+
u128: layout_cx.layout_of(tcx.types.u128)?,
303311
usize: layout_cx.layout_of(tcx.types.usize)?,
304312
bool: layout_cx.layout_of(tcx.types.bool)?,
305313
mut_raw_ptr: layout_cx.layout_of(mut_raw_ptr)?,
306314
const_raw_ptr: layout_cx.layout_of(const_raw_ptr)?,
307315
})
308316
}
317+
318+
pub fn uint(&self, size: Size) -> Option<TyAndLayout<'tcx>> {
319+
match size.bits() {
320+
8 => Some(self.u8),
321+
16 => Some(self.u16),
322+
32 => Some(self.u32),
323+
64 => Some(self.u64),
324+
128 => Some(self.u128),
325+
_ => None,
326+
}
327+
}
328+
329+
pub fn int(&self, size: Size) -> Option<TyAndLayout<'tcx>> {
330+
match size.bits() {
331+
8 => Some(self.i8),
332+
16 => Some(self.i16),
333+
32 => Some(self.i32),
334+
64 => Some(self.i64),
335+
128 => Some(self.i128),
336+
_ => None,
337+
}
338+
}
309339
}
310340

311341
/// The machine itself.

src/tools/miri/src/shims/unix/linux/sync.rs

+8-24
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
use std::time::SystemTime;
2+
13
use crate::concurrency::thread::{MachineCallback, Time};
24
use crate::*;
3-
use rustc_target::abi::{Align, Size};
4-
use std::time::SystemTime;
55

66
/// Implementation of the SYS_futex syscall.
77
/// `args` is the arguments *after* the syscall number.
@@ -28,13 +28,14 @@ pub fn futex<'tcx>(
2828
// The first three arguments (after the syscall number itself) are the same to all futex operations:
2929
// (int *addr, int op, int val).
3030
// We checked above that these definitely exist.
31-
let addr = this.read_immediate(&args[0])?;
31+
let addr = this.read_pointer(&args[0])?;
3232
let op = this.read_scalar(&args[1])?.to_i32()?;
3333
let val = this.read_scalar(&args[2])?.to_i32()?;
3434

3535
let thread = this.get_active_thread();
36-
let addr_scalar = addr.to_scalar();
37-
let addr_usize = addr_scalar.to_machine_usize(this)?;
36+
// This is a vararg function so we have to bring our own type for this pointer.
37+
let addr = MPlaceTy::from_aligned_ptr(addr, this.machine.layouts.i32);
38+
let addr_usize = addr.ptr.addr().bytes();
3839

3940
let futex_private = this.eval_libc_i32("FUTEX_PRIVATE_FLAG")?;
4041
let futex_wait = this.eval_libc_i32("FUTEX_WAIT")?;
@@ -117,15 +118,6 @@ pub fn futex<'tcx>(
117118
}
118119
})
119120
};
120-
// Check the pointer for alignment and validity.
121-
// The API requires `addr` to be a 4-byte aligned pointer, and will
122-
// use the 4 bytes at the given address as an (atomic) i32.
123-
this.check_ptr_access_align(
124-
addr_scalar.to_pointer(this)?,
125-
Size::from_bytes(4),
126-
Align::from_bytes(4).unwrap(),
127-
CheckInAllocMsg::MemoryAccessTest,
128-
)?;
129121
// There may be a concurrent thread changing the value of addr
130122
// and then invoking the FUTEX_WAKE syscall. It is critical that the
131123
// effects of this and the other thread are correctly observed,
@@ -172,14 +164,7 @@ pub fn futex<'tcx>(
172164
this.atomic_fence(AtomicFenceOrd::SeqCst)?;
173165
// Read an `i32` through the pointer, regardless of any wrapper types.
174166
// It's not uncommon for `addr` to be passed as another type than `*mut i32`, such as `*const AtomicI32`.
175-
let futex_val = this
176-
.read_scalar_at_offset_atomic(
177-
&addr.into(),
178-
0,
179-
this.machine.layouts.i32,
180-
AtomicReadOrd::Relaxed,
181-
)?
182-
.to_i32()?;
167+
let futex_val = this.read_scalar_atomic(&addr, AtomicReadOrd::Relaxed)?.to_i32()?;
183168
if val == futex_val {
184169
// The value still matches, so we block the thread make it wait for FUTEX_WAKE.
185170
this.block_thread(thread);
@@ -214,11 +199,10 @@ pub fn futex<'tcx>(
214199
}
215200
}
216201

217-
let dest = dest.clone();
218202
this.register_timeout_callback(
219203
thread,
220204
timeout_time,
221-
Box::new(Callback { thread, addr_usize, dest }),
205+
Box::new(Callback { thread, addr_usize, dest: dest.clone() }),
222206
);
223207
}
224208
} else {

src/tools/miri/src/shims/windows/dlsym.rs

+15
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ use log::trace;
66

77
use crate::helpers::check_arg_count;
88
use crate::shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle};
9+
use crate::shims::windows::sync::EvalContextExt as _;
910
use crate::*;
1011

1112
#[derive(Debug, Copy, Clone)]
1213
pub enum Dlsym {
1314
NtWriteFile,
1415
SetThreadDescription,
16+
WaitOnAddress,
17+
WakeByAddressSingle,
1518
}
1619

1720
impl Dlsym {
@@ -22,6 +25,8 @@ impl Dlsym {
2225
"GetSystemTimePreciseAsFileTime" => None,
2326
"NtWriteFile" => Some(Dlsym::NtWriteFile),
2427
"SetThreadDescription" => Some(Dlsym::SetThreadDescription),
28+
"WaitOnAddress" => Some(Dlsym::WaitOnAddress),
29+
"WakeByAddressSingle" => Some(Dlsym::WakeByAddressSingle),
2530
_ => throw_unsup_format!("unsupported Windows dlsym: {}", name),
2631
})
2732
}
@@ -127,6 +132,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
127132

128133
this.write_null(dest)?;
129134
}
135+
Dlsym::WaitOnAddress => {
136+
let [ptr_op, compare_op, size_op, timeout_op] = check_arg_count(args)?;
137+
138+
this.WaitOnAddress(ptr_op, compare_op, size_op, timeout_op, dest)?;
139+
}
140+
Dlsym::WakeByAddressSingle => {
141+
let [ptr_op] = check_arg_count(args)?;
142+
143+
this.WakeByAddressSingle(ptr_op)?;
144+
}
130145
}
131146

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

src/tools/miri/src/shims/windows/sync.rs

+107-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
use std::time::Duration;
2+
3+
use rustc_target::abi::Size;
4+
15
use crate::concurrency::init_once::InitOnceStatus;
26
use crate::concurrency::thread::MachineCallback;
37
use crate::*;
@@ -6,7 +10,6 @@ const SRWLOCK_ID_OFFSET: u64 = 0;
610
const INIT_ONCE_ID_OFFSET: u64 = 0;
711

812
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
9-
1013
#[allow(non_snake_case)]
1114
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
1215
fn AcquireSRWLockExclusive(&mut self, lock_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
@@ -221,4 +224,107 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
221224

222225
this.eval_windows("c", "TRUE")
223226
}
227+
228+
fn WaitOnAddress(
229+
&mut self,
230+
ptr_op: &OpTy<'tcx, Provenance>,
231+
compare_op: &OpTy<'tcx, Provenance>,
232+
size_op: &OpTy<'tcx, Provenance>,
233+
timeout_op: &OpTy<'tcx, Provenance>,
234+
dest: &PlaceTy<'tcx, Provenance>,
235+
) -> InterpResult<'tcx> {
236+
let this = self.eval_context_mut();
237+
238+
let ptr = this.read_pointer(ptr_op)?;
239+
let compare = this.read_pointer(compare_op)?;
240+
let size = this.read_scalar(size_op)?.to_machine_usize(this)?;
241+
let timeout_ms = this.read_scalar(timeout_op)?.to_u32()?;
242+
243+
let thread = this.get_active_thread();
244+
let addr = ptr.addr().bytes();
245+
246+
if size > 8 || !size.is_power_of_two() {
247+
let invalid_param = this.eval_windows("c", "ERROR_INVALID_PARAMETER")?;
248+
this.set_last_error(invalid_param)?;
249+
this.write_scalar(Scalar::from_i32(0), dest)?;
250+
return Ok(());
251+
};
252+
let size = Size::from_bytes(size);
253+
254+
let timeout_time = if timeout_ms == this.eval_windows("c", "INFINITE")?.to_u32()? {
255+
None
256+
} else {
257+
this.check_no_isolation("`WaitOnAddress` with non-infinite timeout")?;
258+
259+
let duration = Duration::from_millis(timeout_ms.into());
260+
Some(Time::Monotonic(this.machine.clock.now().checked_add(duration).unwrap()))
261+
};
262+
263+
// See the Linux futex implementation for why this fence exists.
264+
this.atomic_fence(AtomicFenceOrd::SeqCst)?;
265+
266+
let layout = this.machine.layouts.uint(size).unwrap();
267+
let futex_val = this
268+
.read_scalar_atomic(&MPlaceTy::from_aligned_ptr(ptr, layout), AtomicReadOrd::Relaxed)?;
269+
let compare_val = this.read_scalar(&MPlaceTy::from_aligned_ptr(compare, layout).into())?;
270+
271+
if futex_val == compare_val {
272+
// If the values are the same, we have to block.
273+
this.block_thread(thread);
274+
this.futex_wait(addr, thread, u32::MAX);
275+
276+
if let Some(timeout_time) = timeout_time {
277+
struct Callback<'tcx> {
278+
thread: ThreadId,
279+
addr: u64,
280+
dest: PlaceTy<'tcx, Provenance>,
281+
}
282+
283+
impl<'tcx> VisitTags for Callback<'tcx> {
284+
fn visit_tags(&self, visit: &mut dyn FnMut(SbTag)) {
285+
let Callback { thread: _, addr: _, dest } = self;
286+
dest.visit_tags(visit);
287+
}
288+
}
289+
290+
impl<'mir, 'tcx: 'mir> MachineCallback<'mir, 'tcx> for Callback<'tcx> {
291+
fn call(&self, this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
292+
this.unblock_thread(self.thread);
293+
this.futex_remove_waiter(self.addr, self.thread);
294+
let error_timeout = this.eval_windows("c", "ERROR_TIMEOUT")?;
295+
this.set_last_error(error_timeout)?;
296+
this.write_scalar(Scalar::from_i32(0), &self.dest)?;
297+
298+
Ok(())
299+
}
300+
}
301+
302+
this.register_timeout_callback(
303+
thread,
304+
timeout_time,
305+
Box::new(Callback { thread, addr, dest: dest.clone() }),
306+
);
307+
}
308+
}
309+
310+
this.write_scalar(Scalar::from_i32(1), dest)?;
311+
312+
Ok(())
313+
}
314+
315+
fn WakeByAddressSingle(&mut self, ptr_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
316+
let this = self.eval_context_mut();
317+
318+
let ptr = this.read_pointer(ptr_op)?;
319+
320+
// See the Linux futex implementation for why this fence exists.
321+
this.atomic_fence(AtomicFenceOrd::SeqCst)?;
322+
323+
if let Some(thread) = this.futex_wake(ptr.addr().bytes(), u32::MAX) {
324+
this.unblock_thread(thread);
325+
this.unregister_timeout_callback_if_exists(thread);
326+
}
327+
328+
Ok(())
329+
}
224330
}

src/tools/miri/tests/pass-dep/shims/libc-misc.rs

+10-12
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ fn test_posix_realpath_errors() {
8787
assert_eq!(e.kind(), ErrorKind::NotFound);
8888
}
8989

90-
#[cfg(any(target_os = "linux"))]
90+
#[cfg(target_os = "linux")]
9191
fn test_posix_fadvise() {
9292
use std::convert::TryInto;
9393
use std::io::Write;
@@ -115,7 +115,7 @@ fn test_posix_fadvise() {
115115
assert_eq!(result, 0);
116116
}
117117

118-
#[cfg(any(target_os = "linux"))]
118+
#[cfg(target_os = "linux")]
119119
fn test_sync_file_range() {
120120
use std::io::Write;
121121

@@ -181,7 +181,7 @@ fn test_thread_local_errno() {
181181
}
182182

183183
/// Tests whether clock support exists at all
184-
#[cfg(any(target_os = "linux"))]
184+
#[cfg(target_os = "linux")]
185185
fn test_clocks() {
186186
let mut tp = std::mem::MaybeUninit::<libc::timespec>::uninit();
187187
let is_error = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, tp.as_mut_ptr()) };
@@ -283,23 +283,21 @@ fn test_posix_mkstemp() {
283283
}
284284

285285
fn main() {
286-
#[cfg(any(target_os = "linux"))]
287-
test_posix_fadvise();
288-
289286
test_posix_gettimeofday();
290287
test_posix_mkstemp();
291288

292289
test_posix_realpath_alloc();
293290
test_posix_realpath_noalloc();
294291
test_posix_realpath_errors();
295292

296-
#[cfg(any(target_os = "linux"))]
297-
test_sync_file_range();
298-
299293
test_thread_local_errno();
300294

301-
#[cfg(any(target_os = "linux"))]
302-
test_clocks();
303-
304295
test_isatty();
296+
297+
#[cfg(target_os = "linux")]
298+
{
299+
test_posix_fadvise();
300+
test_sync_file_range();
301+
test_clocks();
302+
}
305303
}

src/tools/miri/tests/pass-dep/shims/pthreads.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ fn main() {
1010
test_rwlock_libc_static_initializer();
1111
test_named_thread_truncation();
1212

13-
#[cfg(any(target_os = "linux"))]
13+
#[cfg(target_os = "linux")]
1414
test_mutex_libc_static_initializer_recursive();
1515
}
1616

src/tools/miri/tests/pass/concurrency/channels.rs

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
//@ignore-target-windows: Channels on Windows are not supported yet.
21
//@compile-flags: -Zmiri-strict-provenance
32

43
use std::sync::mpsc::{channel, sync_channel};

src/tools/miri/tests/pass/concurrency/spin_loops_nopreempt.rs

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
//@ignore-target-windows: Channels on Windows are not supported yet.
21
// This specifically tests behavior *without* preemption.
32
//@compile-flags: -Zmiri-preemption-rate=0
43

0 commit comments

Comments
 (0)