Skip to content

Commit bf0f47b

Browse files
committed
Auto merge of rust-lang#2638 - DrMeepster:windows-condvars, r=RalfJung
Implement condvars for Windows Adds 3 shims for Windows: `SleepConditionVariableSRW`, `WakeConditionVariable`, `WakeAllConditionVariable` to add support for condvars (which fixes rust-lang#2628). Salvaged from what was removed from rust-lang#2231
2 parents 4a3ed29 + 958ca31 commit bf0f47b

File tree

9 files changed

+504
-30
lines changed

9 files changed

+504
-30
lines changed

src/concurrency/sync.rs

+18-6
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,25 @@ struct RwLock {
116116

117117
declare_id!(CondvarId);
118118

119+
#[derive(Debug, Copy, Clone)]
120+
pub enum RwLockMode {
121+
Read,
122+
Write,
123+
}
124+
125+
#[derive(Debug)]
126+
pub enum CondvarLock {
127+
Mutex(MutexId),
128+
RwLock { id: RwLockId, mode: RwLockMode },
129+
}
130+
119131
/// A thread waiting on a conditional variable.
120132
#[derive(Debug)]
121133
struct CondvarWaiter {
122134
/// The thread that is waiting on this variable.
123135
thread: ThreadId,
124-
/// The mutex on which the thread is waiting.
125-
mutex: MutexId,
136+
/// The mutex or rwlock on which the thread is waiting.
137+
lock: CondvarLock,
126138
}
127139

128140
/// The conditional variable state.
@@ -569,16 +581,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
569581
}
570582

571583
/// Mark that the thread is waiting on the conditional variable.
572-
fn condvar_wait(&mut self, id: CondvarId, thread: ThreadId, mutex: MutexId) {
584+
fn condvar_wait(&mut self, id: CondvarId, thread: ThreadId, lock: CondvarLock) {
573585
let this = self.eval_context_mut();
574586
let waiters = &mut this.machine.threads.sync.condvars[id].waiters;
575587
assert!(waiters.iter().all(|waiter| waiter.thread != thread), "thread is already waiting");
576-
waiters.push_back(CondvarWaiter { thread, mutex });
588+
waiters.push_back(CondvarWaiter { thread, lock });
577589
}
578590

579591
/// Wake up some thread (if there is any) sleeping on the conditional
580592
/// variable.
581-
fn condvar_signal(&mut self, id: CondvarId) -> Option<(ThreadId, MutexId)> {
593+
fn condvar_signal(&mut self, id: CondvarId) -> Option<(ThreadId, CondvarLock)> {
582594
let this = self.eval_context_mut();
583595
let current_thread = this.get_active_thread();
584596
let condvar = &mut this.machine.threads.sync.condvars[id];
@@ -592,7 +604,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
592604
if let Some(data_race) = data_race {
593605
data_race.validate_lock_acquire(&condvar.data_race, waiter.thread);
594606
}
595-
(waiter.thread, waiter.mutex)
607+
(waiter.thread, waiter.lock)
596608
})
597609
}
598610

src/shims/unix/sync.rs

+15-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::time::SystemTime;
33
use rustc_hir::LangItem;
44
use rustc_middle::ty::{layout::TyAndLayout, query::TyCtxtAt, Ty};
55

6+
use crate::concurrency::sync::CondvarLock;
67
use crate::concurrency::thread::{MachineCallback, Time};
78
use crate::*;
89

@@ -696,8 +697,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
696697
fn pthread_cond_signal(&mut self, cond_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
697698
let this = self.eval_context_mut();
698699
let id = this.condvar_get_or_create_id(cond_op, CONDVAR_ID_OFFSET)?;
699-
if let Some((thread, mutex)) = this.condvar_signal(id) {
700-
post_cond_signal(this, thread, mutex)?;
700+
if let Some((thread, lock)) = this.condvar_signal(id) {
701+
if let CondvarLock::Mutex(mutex) = lock {
702+
post_cond_signal(this, thread, mutex)?;
703+
} else {
704+
panic!("condvar should not have an rwlock on unix");
705+
}
701706
}
702707

703708
Ok(0)
@@ -710,8 +715,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
710715
let this = self.eval_context_mut();
711716
let id = this.condvar_get_or_create_id(cond_op, CONDVAR_ID_OFFSET)?;
712717

713-
while let Some((thread, mutex)) = this.condvar_signal(id) {
714-
post_cond_signal(this, thread, mutex)?;
718+
while let Some((thread, lock)) = this.condvar_signal(id) {
719+
if let CondvarLock::Mutex(mutex) = lock {
720+
post_cond_signal(this, thread, mutex)?;
721+
} else {
722+
panic!("condvar should not have an rwlock on unix");
723+
}
715724
}
716725

717726
Ok(0)
@@ -729,7 +738,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
729738
let active_thread = this.get_active_thread();
730739

731740
release_cond_mutex_and_block(this, active_thread, mutex_id)?;
732-
this.condvar_wait(id, active_thread, mutex_id);
741+
this.condvar_wait(id, active_thread, CondvarLock::Mutex(mutex_id));
733742

734743
Ok(0)
735744
}
@@ -768,7 +777,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
768777
};
769778

770779
release_cond_mutex_and_block(this, active_thread, mutex_id)?;
771-
this.condvar_wait(id, active_thread, mutex_id);
780+
this.condvar_wait(id, active_thread, CondvarLock::Mutex(mutex_id));
772781

773782
// We return success for now and override it in the timeout callback.
774783
this.write_scalar(Scalar::from_i32(0), dest)?;

src/shims/windows/foreign_items.rs

+19
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,25 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
273273
let result = this.InitOnceComplete(ptr, flags, context)?;
274274
this.write_scalar(result, dest)?;
275275
}
276+
"SleepConditionVariableSRW" => {
277+
let [condvar, lock, timeout, flags] =
278+
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
279+
280+
let result = this.SleepConditionVariableSRW(condvar, lock, timeout, flags, dest)?;
281+
this.write_scalar(result, dest)?;
282+
}
283+
"WakeConditionVariable" => {
284+
let [condvar] =
285+
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
286+
287+
this.WakeConditionVariable(condvar)?;
288+
}
289+
"WakeAllConditionVariable" => {
290+
let [condvar] =
291+
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
292+
293+
this.WakeAllConditionVariable(condvar)?;
294+
}
276295

277296
// Dynamic symbol loading
278297
"GetProcAddress" => {

src/shims/windows/sync.rs

+161
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,45 @@ use std::time::Duration;
33
use rustc_target::abi::Size;
44

55
use crate::concurrency::init_once::InitOnceStatus;
6+
use crate::concurrency::sync::{CondvarLock, RwLockMode};
67
use crate::concurrency::thread::MachineCallback;
78
use crate::*;
89

910
const SRWLOCK_ID_OFFSET: u64 = 0;
1011
const INIT_ONCE_ID_OFFSET: u64 = 0;
12+
const CONDVAR_ID_OFFSET: u64 = 0;
13+
14+
impl<'mir, 'tcx> EvalContextExtPriv<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
15+
trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
16+
/// Try to reacquire the lock associated with the condition variable after we
17+
/// were signaled.
18+
fn reacquire_cond_lock(
19+
&mut self,
20+
thread: ThreadId,
21+
lock: RwLockId,
22+
mode: RwLockMode,
23+
) -> InterpResult<'tcx> {
24+
let this = self.eval_context_mut();
25+
this.unblock_thread(thread);
26+
27+
match mode {
28+
RwLockMode::Read =>
29+
if this.rwlock_is_write_locked(lock) {
30+
this.rwlock_enqueue_and_block_reader(lock, thread);
31+
} else {
32+
this.rwlock_reader_lock(lock, thread);
33+
},
34+
RwLockMode::Write =>
35+
if this.rwlock_is_locked(lock) {
36+
this.rwlock_enqueue_and_block_writer(lock, thread);
37+
} else {
38+
this.rwlock_writer_lock(lock, thread);
39+
},
40+
}
41+
42+
Ok(())
43+
}
44+
}
1145

1246
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
1347
#[allow(non_snake_case)]
@@ -327,4 +361,131 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
327361

328362
Ok(())
329363
}
364+
365+
fn SleepConditionVariableSRW(
366+
&mut self,
367+
condvar_op: &OpTy<'tcx, Provenance>,
368+
lock_op: &OpTy<'tcx, Provenance>,
369+
timeout_op: &OpTy<'tcx, Provenance>,
370+
flags_op: &OpTy<'tcx, Provenance>,
371+
dest: &PlaceTy<'tcx, Provenance>,
372+
) -> InterpResult<'tcx, Scalar<Provenance>> {
373+
let this = self.eval_context_mut();
374+
375+
let condvar_id = this.condvar_get_or_create_id(condvar_op, CONDVAR_ID_OFFSET)?;
376+
let lock_id = this.rwlock_get_or_create_id(lock_op, SRWLOCK_ID_OFFSET)?;
377+
let timeout_ms = this.read_scalar(timeout_op)?.to_u32()?;
378+
let flags = this.read_scalar(flags_op)?.to_u32()?;
379+
380+
let timeout_time = if timeout_ms == this.eval_windows("c", "INFINITE")?.to_u32()? {
381+
None
382+
} else {
383+
let duration = Duration::from_millis(timeout_ms.into());
384+
Some(this.machine.clock.now().checked_add(duration).unwrap())
385+
};
386+
387+
let shared_mode = 0x1; // CONDITION_VARIABLE_LOCKMODE_SHARED is not in std
388+
let mode = if flags == 0 {
389+
RwLockMode::Write
390+
} else if flags == shared_mode {
391+
RwLockMode::Read
392+
} else {
393+
throw_unsup_format!("unsupported `Flags` {flags} in `SleepConditionVariableSRW`");
394+
};
395+
396+
let active_thread = this.get_active_thread();
397+
398+
let was_locked = match mode {
399+
RwLockMode::Read => this.rwlock_reader_unlock(lock_id, active_thread),
400+
RwLockMode::Write => this.rwlock_writer_unlock(lock_id, active_thread),
401+
};
402+
403+
if !was_locked {
404+
throw_ub_format!(
405+
"calling SleepConditionVariableSRW with an SRWLock that is not locked by the current thread"
406+
);
407+
}
408+
409+
this.block_thread(active_thread);
410+
this.condvar_wait(condvar_id, active_thread, CondvarLock::RwLock { id: lock_id, mode });
411+
412+
if let Some(timeout_time) = timeout_time {
413+
struct Callback<'tcx> {
414+
thread: ThreadId,
415+
condvar_id: CondvarId,
416+
lock_id: RwLockId,
417+
mode: RwLockMode,
418+
dest: PlaceTy<'tcx, Provenance>,
419+
}
420+
421+
impl<'tcx> VisitTags for Callback<'tcx> {
422+
fn visit_tags(&self, visit: &mut dyn FnMut(SbTag)) {
423+
let Callback { thread: _, condvar_id: _, lock_id: _, mode: _, dest } = self;
424+
dest.visit_tags(visit);
425+
}
426+
}
427+
428+
impl<'mir, 'tcx: 'mir> MachineCallback<'mir, 'tcx> for Callback<'tcx> {
429+
fn call(&self, this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
430+
this.reacquire_cond_lock(self.thread, self.lock_id, self.mode)?;
431+
432+
this.condvar_remove_waiter(self.condvar_id, self.thread);
433+
434+
let error_timeout = this.eval_windows("c", "ERROR_TIMEOUT")?;
435+
this.set_last_error(error_timeout)?;
436+
this.write_scalar(this.eval_windows("c", "FALSE")?, &self.dest)?;
437+
Ok(())
438+
}
439+
}
440+
441+
this.register_timeout_callback(
442+
active_thread,
443+
Time::Monotonic(timeout_time),
444+
Box::new(Callback {
445+
thread: active_thread,
446+
condvar_id,
447+
lock_id,
448+
mode,
449+
dest: dest.clone(),
450+
}),
451+
);
452+
}
453+
454+
this.eval_windows("c", "TRUE")
455+
}
456+
457+
fn WakeConditionVariable(&mut self, condvar_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
458+
let this = self.eval_context_mut();
459+
let condvar_id = this.condvar_get_or_create_id(condvar_op, CONDVAR_ID_OFFSET)?;
460+
461+
if let Some((thread, lock)) = this.condvar_signal(condvar_id) {
462+
if let CondvarLock::RwLock { id, mode } = lock {
463+
this.reacquire_cond_lock(thread, id, mode)?;
464+
this.unregister_timeout_callback_if_exists(thread);
465+
} else {
466+
panic!("mutexes should not exist on windows");
467+
}
468+
}
469+
470+
Ok(())
471+
}
472+
473+
fn WakeAllConditionVariable(
474+
&mut self,
475+
condvar_op: &OpTy<'tcx, Provenance>,
476+
) -> InterpResult<'tcx> {
477+
let this = self.eval_context_mut();
478+
let condvar_id = this.condvar_get_or_create_id(condvar_op, CONDVAR_ID_OFFSET)?;
479+
480+
while let Some((thread, lock)) = this.condvar_signal(condvar_id) {
481+
if let CondvarLock::RwLock { id, mode } = lock {
482+
this.reacquire_cond_lock(thread, id, mode)?;
483+
this.unregister_timeout_callback_if_exists(thread);
484+
} else {
485+
panic!("mutexes should not exist on windows");
486+
}
487+
}
488+
489+
Ok(())
490+
}
330491
}

tests/pass/concurrency/sync.rs

+4-16
Original file line numberDiff line numberDiff line change
@@ -230,20 +230,8 @@ fn main() {
230230
check_once();
231231
park_timeout();
232232
park_unpark();
233-
234-
if !cfg!(windows) {
235-
// ignore-target-windows: Condvars on Windows are not supported yet
236-
check_barriers();
237-
check_conditional_variables_notify_one();
238-
check_conditional_variables_timed_wait_timeout();
239-
check_conditional_variables_timed_wait_notimeout();
240-
} else {
241-
// We need to fake the same output...
242-
for _ in 0..10 {
243-
println!("before wait");
244-
}
245-
for _ in 0..10 {
246-
println!("after wait");
247-
}
248-
}
233+
check_barriers();
234+
check_conditional_variables_notify_one();
235+
check_conditional_variables_timed_wait_timeout();
236+
check_conditional_variables_timed_wait_notimeout();
249237
}

tests/pass/concurrency/sync_nopreempt.rs

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
//@ignore-target-windows: Condvars on Windows are not supported yet.
21
// We are making scheduler assumptions here.
32
//@compile-flags: -Zmiri-strict-provenance -Zmiri-preemption-rate=0
43

0 commit comments

Comments
 (0)