Skip to content

Commit 4e89f37

Browse files
committed
move rwlock dequeuing to shared code, and use that code for Windows rwlocks
1 parent a9dc279 commit 4e89f37

File tree

5 files changed

+160
-165
lines changed

5 files changed

+160
-165
lines changed

src/shims/posix/sync.rs

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::convert::TryInto;
22
use std::time::{Duration, SystemTime};
3-
use std::ops::Not;
43

54
use crate::*;
65
use stacked_borrows::Tag;
@@ -548,27 +547,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
548547
let active_thread = this.get_active_thread();
549548

550549
if this.rwlock_reader_unlock(id, active_thread) {
551-
// The thread was a reader.
552-
if this.rwlock_is_locked(id).not() {
553-
// No more readers owning the lock. Give it to a writer if there
554-
// is any.
555-
this.rwlock_dequeue_and_lock_writer(id);
556-
}
557550
Ok(0)
558-
} else if Some(active_thread) == this.rwlock_writer_unlock(id) {
559-
// The thread was a writer.
560-
//
561-
// We are prioritizing writers here against the readers. As a
562-
// result, not only readers can starve writers, but also writers can
563-
// starve readers.
564-
if this.rwlock_dequeue_and_lock_writer(id) {
565-
// Someone got the write lock, nice.
566-
} else {
567-
// Give the lock to all readers.
568-
while this.rwlock_dequeue_and_lock_reader(id) {
569-
// Rinse and repeat.
570-
}
571-
}
551+
} else if this.rwlock_writer_unlock(id, active_thread) {
572552
Ok(0)
573553
} else {
574554
throw_ub_format!("unlocked an rwlock that was not locked by the active thread");

src/shims/windows/foreign_items.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
257257

258258
// Better error for attempts to create a thread
259259
"CreateThread" => {
260-
throw_unsup_format!("Miri does not support threading");
260+
throw_unsup_format!("Miri does not support concurrency on Windows");
261261
}
262262

263263
// Incomplete shims that we "stub out" just to get pre-main initialization code to work.
@@ -292,7 +292,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
292292
if this.frame().instance.to_string().starts_with("std::sys::windows::") => {
293293
#[allow(non_snake_case)]
294294
let &[_lpCriticalSection] = check_arg_count(args)?;
295-
assert_eq!(this.get_total_thread_count(), 1, "concurrency on Windows not supported");
295+
assert_eq!(this.get_total_thread_count(), 1, "concurrency on Windows is not supported");
296296
// Nothing to do, not even a return value.
297297
// (Windows locks are reentrant, and we have only 1 thread,
298298
// so not doing any futher checks here is at least not incorrect.)
@@ -301,7 +301,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
301301
if this.frame().instance.to_string().starts_with("std::sys::windows::") => {
302302
#[allow(non_snake_case)]
303303
let &[_lpCriticalSection] = check_arg_count(args)?;
304-
assert_eq!(this.get_total_thread_count(), 1, "concurrency on Windows not supported");
304+
assert_eq!(this.get_total_thread_count(), 1, "concurrency on Windows is not supported");
305305
// There is only one thread, so this always succeeds and returns TRUE.
306306
this.write_scalar(Scalar::from_i32(1), dest)?;
307307
}

src/shims/windows/sync.rs

Lines changed: 54 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
1-
use rustc_target::abi::Size;
2-
31
use crate::*;
42

53
// Locks are pointer-sized pieces of data, initialized to 0.
6-
// We use them to count readers, with usize::MAX representing the write-locked state.
4+
// We use the first 4 bytes to store the RwLockId.
75

8-
fn deref_lock<'mir, 'tcx: 'mir>(
6+
fn srwlock_get_or_create_id<'mir, 'tcx: 'mir>(
97
ecx: &mut MiriEvalContext<'mir, 'tcx>,
108
lock_op: OpTy<'tcx, Tag>,
11-
) -> InterpResult<'tcx, MPlaceTy<'tcx, Tag>> {
12-
// `lock` is a pointer to `void*`; cast it to a pointer to `usize`.
13-
let lock = ecx.deref_operand(lock_op)?;
14-
let usize = ecx.machine.layouts.usize;
15-
assert_eq!(lock.layout.size, usize.size);
16-
Ok(lock.offset(Size::ZERO, MemPlaceMeta::None, usize, ecx)?)
9+
) -> InterpResult<'tcx, RwLockId> {
10+
let id = ecx.read_scalar_at_offset(lock_op, 0, ecx.machine.layouts.u32)?.to_u32()?;
11+
if id == 0 {
12+
// 0 is a default value and also not a valid rwlock id. Need to allocate
13+
// a new rwlock.
14+
let id = ecx.rwlock_create();
15+
ecx.write_scalar_at_offset(lock_op, 0, id.to_u32_scalar(), ecx.machine.layouts.u32)?;
16+
Ok(id)
17+
} else {
18+
Ok(RwLockId::from_u32(id))
19+
}
1720
}
1821

1922
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
@@ -24,17 +27,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
2427
lock_op: OpTy<'tcx, Tag>,
2528
) -> InterpResult<'tcx> {
2629
let this = self.eval_context_mut();
27-
assert_eq!(this.get_total_thread_count(), 1, "concurrency on Windows is not supported");
28-
29-
let lock = deref_lock(this, lock_op)?;
30-
let lock_val = this.read_scalar(lock.into())?.to_machine_usize(this)?;
31-
if lock_val == 0 {
32-
// Currently not locked. Lock it.
33-
let new_val = Scalar::from_machine_usize(this.machine_usize_max(), this);
34-
this.write_scalar(new_val, lock.into())?;
30+
let id = srwlock_get_or_create_id(this, lock_op)?;
31+
let active_thread = this.get_active_thread();
32+
33+
if this.rwlock_is_locked(id) {
34+
// Note: this will deadlock if the lock is already locked by this
35+
// thread in any way.
36+
//
37+
// FIXME: Detect and report the deadlock proactively. (We currently
38+
// report the deadlock only when no thread can continue execution,
39+
// but we could detect that this lock is already locked and report
40+
// an error.)
41+
this.rwlock_enqueue_and_block_writer(id, active_thread);
3542
} else {
36-
// Lock is already held. This is a deadlock.
37-
throw_machine_stop!(TerminationInfo::Deadlock);
43+
this.rwlock_writer_lock(id, active_thread);
3844
}
3945

4046
Ok(())
@@ -46,18 +52,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
4652
lock_op: OpTy<'tcx, Tag>,
4753
) -> InterpResult<'tcx, u8> {
4854
let this = self.eval_context_mut();
49-
assert_eq!(this.get_total_thread_count(), 1, "concurrency on Windows is not supported");
50-
51-
let lock = deref_lock(this, lock_op)?;
52-
let lock_val = this.read_scalar(lock.into())?.to_machine_usize(this)?;
53-
if lock_val == 0 {
54-
// Currently not locked. Lock it.
55-
let new_val = this.machine_usize_max();
56-
this.write_scalar(Scalar::from_machine_usize(new_val, this), lock.into())?;
57-
Ok(1)
58-
} else {
55+
let id = srwlock_get_or_create_id(this, lock_op)?;
56+
let active_thread = this.get_active_thread();
57+
58+
if this.rwlock_is_locked(id) {
5959
// Lock is already held.
6060
Ok(0)
61+
} else {
62+
this.rwlock_writer_lock(id, active_thread);
63+
Ok(1)
6164
}
6265
}
6366

@@ -67,17 +70,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
6770
lock_op: OpTy<'tcx, Tag>,
6871
) -> InterpResult<'tcx> {
6972
let this = self.eval_context_mut();
70-
assert_eq!(this.get_total_thread_count(), 1, "concurrency on Windows is not supported");
71-
72-
let lock = deref_lock(this, lock_op)?;
73-
let lock_val = this.read_scalar(lock.into())?.to_machine_usize(this)?;
74-
if lock_val == this.machine_usize_max() {
75-
// Currently locked. Unlock it.
76-
let new_val = 0;
77-
this.write_scalar(Scalar::from_machine_usize(new_val, this), lock.into())?;
78-
} else {
79-
// Lock is not locked.
80-
throw_ub_format!("calling ReleaseSRWLockExclusive on an SRWLock that is not exclusively locked");
73+
let id = srwlock_get_or_create_id(this, lock_op)?;
74+
let active_thread = this.get_active_thread();
75+
76+
if !this.rwlock_writer_unlock(id, active_thread) {
77+
// The docs do not say anything about this case, but it seems better to not allow it.
78+
throw_ub_format!("calling ReleaseSRWLockExclusive on an SRWLock that is not exclusively locked by the current thread");
8179
}
8280

8381
Ok(())
@@ -89,21 +87,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
8987
lock_op: OpTy<'tcx, Tag>,
9088
) -> InterpResult<'tcx> {
9189
let this = self.eval_context_mut();
92-
assert_eq!(this.get_total_thread_count(), 1, "concurrency on Windows is not supported");
90+
let id = srwlock_get_or_create_id(this, lock_op)?;
91+
let active_thread = this.get_active_thread();
9392

94-
let lock = deref_lock(this, lock_op)?;
95-
let lock_val = this.read_scalar(lock.into())?.to_machine_usize(this)?;
96-
if lock_val == this.machine_usize_max() {
97-
// Currently write locked. This is a deadlock.
98-
throw_machine_stop!(TerminationInfo::Deadlock);
93+
if this.rwlock_is_write_locked(id) {
94+
this.rwlock_enqueue_and_block_reader(id, active_thread);
9995
} else {
100-
// Bump up read counter (cannot overflow as we just checkd against usize::MAX);
101-
let new_val = lock_val+1;
102-
// Make sure this does not reach the "write locked" flag.
103-
if new_val == this.machine_usize_max() {
104-
throw_unsup_format!("SRWLock read-acquired too many times");
105-
}
106-
this.write_scalar(Scalar::from_machine_usize(new_val, this), lock.into())?;
96+
this.rwlock_reader_lock(id, active_thread);
10797
}
10898

10999
Ok(())
@@ -115,21 +105,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
115105
lock_op: OpTy<'tcx, Tag>,
116106
) -> InterpResult<'tcx, u8> {
117107
let this = self.eval_context_mut();
118-
assert_eq!(this.get_total_thread_count(), 1, "concurrency on Windows is not supported");
108+
let id = srwlock_get_or_create_id(this, lock_op)?;
109+
let active_thread = this.get_active_thread();
119110

120-
let lock = deref_lock(this, lock_op)?;
121-
let lock_val = this.read_scalar(lock.into())?.to_machine_usize(this)?;
122-
if lock_val == this.machine_usize_max() {
123-
// Currently write locked.
111+
if this.rwlock_is_write_locked(id) {
124112
Ok(0)
125113
} else {
126-
// Bump up read counter (cannot overflow as we just checkd against usize::MAX);
127-
let new_val = lock_val+1;
128-
// Make sure this does not reach the "write locked" flag.
129-
if new_val == this.machine_usize_max() {
130-
throw_unsup_format!("SRWLock read-acquired too many times");
131-
}
132-
this.write_scalar(Scalar::from_machine_usize(new_val, this), lock.into())?;
114+
this.rwlock_reader_lock(id, active_thread);
133115
Ok(1)
134116
}
135117
}
@@ -140,20 +122,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
140122
lock_op: OpTy<'tcx, Tag>,
141123
) -> InterpResult<'tcx> {
142124
let this = self.eval_context_mut();
143-
assert_eq!(this.get_total_thread_count(), 1, "concurrency on Windows is not supported");
144-
145-
let lock = deref_lock(this, lock_op)?;
146-
let lock_val = this.read_scalar(lock.into())?.to_machine_usize(this)?;
147-
if lock_val == this.machine_usize_max() {
148-
// Currently write locked. This is a UB.
149-
throw_ub_format!("calling ReleaseSRWLockShared on write-locked SRWLock");
150-
} else if lock_val == 0 {
151-
// Currently not locked at all.
152-
throw_ub_format!("calling ReleaseSRWLockShared on unlocked SRWLock");
153-
} else {
154-
// Decrement read counter (cannot overflow as we just checkd against 0);
155-
let new_val = lock_val-1;
156-
this.write_scalar(Scalar::from_machine_usize(new_val, this), lock.into())?;
125+
let id = srwlock_get_or_create_id(this, lock_op)?;
126+
let active_thread = this.get_active_thread();
127+
128+
if !this.rwlock_reader_unlock(id, active_thread) {
129+
// The docs do not say anything about this case, but it seems better to not allow it.
130+
throw_ub_format!("calling ReleaseSRWLockShared on an SRWLock that is not locked by the current thread");
157131
}
158132

159133
Ok(())

0 commit comments

Comments
 (0)