Skip to content

Commit 6ca9d0f

Browse files
committed
implement the os_unfair_lock functions on macOS
1 parent 7184789 commit 6ca9d0f

15 files changed

+277
-7
lines changed

src/concurrency/sync.rs

+15-6
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ pub(super) trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
269269
let this = self.eval_context_mut();
270270
if this.mutex_is_locked(mutex) {
271271
assert_ne!(this.mutex_get_owner(mutex), this.active_thread());
272-
this.mutex_enqueue_and_block(mutex, retval, dest);
272+
this.mutex_enqueue_and_block(mutex, Some((retval, dest)));
273273
} else {
274274
// We can have it right now!
275275
this.mutex_lock(mutex);
@@ -390,9 +390,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
390390
}
391391

392392
/// Put the thread into the queue waiting for the mutex.
393-
/// Once the Mutex becomes available, `retval` will be written to `dest`.
393+
///
394+
/// Once the Mutex becomes available and if it exists, `retval_dest.0` will
395+
/// be written to `retval_dest.1`.
394396
#[inline]
395-
fn mutex_enqueue_and_block(&mut self, id: MutexId, retval: Scalar, dest: MPlaceTy<'tcx>) {
397+
fn mutex_enqueue_and_block(
398+
&mut self,
399+
id: MutexId,
400+
retval_dest: Option<(Scalar, MPlaceTy<'tcx>)>,
401+
) {
396402
let this = self.eval_context_mut();
397403
assert!(this.mutex_is_locked(id), "queing on unlocked mutex");
398404
let thread = this.active_thread();
@@ -403,13 +409,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
403409
callback!(
404410
@capture<'tcx> {
405411
id: MutexId,
406-
retval: Scalar,
407-
dest: MPlaceTy<'tcx>,
412+
retval_dest: Option<(Scalar, MPlaceTy<'tcx>)>,
408413
}
409414
@unblock = |this| {
410415
assert!(!this.mutex_is_locked(id));
411416
this.mutex_lock(id);
412-
this.write_scalar(retval, &dest)?;
417+
418+
if let Some((retval, dest)) = retval_dest {
419+
this.write_scalar(retval, &dest)?;
420+
}
421+
413422
Ok(())
414423
}
415424
),

src/provenance_gc.rs

+11
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ impl<T: VisitProvenance> VisitProvenance for Option<T> {
3030
}
3131
}
3232

33+
impl<A, B> VisitProvenance for (A, B)
34+
where
35+
A: VisitProvenance,
36+
B: VisitProvenance,
37+
{
38+
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
39+
self.0.visit_provenance(visit);
40+
self.1.visit_provenance(visit);
41+
}
42+
}
43+
3344
impl<T: VisitProvenance> VisitProvenance for std::cell::RefCell<T> {
3445
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
3546
self.borrow().visit_provenance(visit)

src/shims/unix/macos/foreign_items.rs

+22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use rustc_span::Symbol;
22
use rustc_target::spec::abi::Abi;
33

4+
use super::sync::EvalContextExt as _;
45
use crate::shims::unix::*;
56
use crate::*;
67

@@ -174,6 +175,27 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
174175
this.write_scalar(res, dest)?;
175176
}
176177

178+
"os_unfair_lock_lock" => {
179+
let [lock_op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
180+
this.os_unfair_lock_lock(lock_op)?;
181+
}
182+
"os_unfair_lock_trylock" => {
183+
let [lock_op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
184+
this.os_unfair_lock_trylock(lock_op, dest)?;
185+
}
186+
"os_unfair_lock_unlock" => {
187+
let [lock_op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
188+
this.os_unfair_lock_unlock(lock_op)?;
189+
}
190+
"os_unfair_lock_assert_owner" => {
191+
let [lock_op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
192+
this.os_unfair_lock_assert_owner(lock_op)?;
193+
}
194+
"os_unfair_lock_assert_not_owner" => {
195+
let [lock_op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
196+
this.os_unfair_lock_assert_not_owner(lock_op)?;
197+
}
198+
177199
_ => return Ok(EmulateItemResult::NotSupported),
178200
};
179201

src/shims/unix/macos/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod foreign_items;
2+
pub mod sync;

src/shims/unix/macos/sync.rs

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//! Contains macOS-specific synchronization functions.
2+
//!
3+
//! For `os_unfair_lock`, see the documentation
4+
//! <https://developer.apple.com/documentation/os/synchronization?language=objc>
5+
//! and in case of underspecification its implementation
6+
//! <https://github.com/apple-oss-distributions/libplatform/blob/a00a4cc36da2110578bcf3b8eeeeb93dcc7f4e11/src/os/lock.c#L645>
7+
8+
use crate::*;
9+
10+
impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
11+
trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
12+
fn os_unfair_lock_getid(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx, MutexId> {
13+
let this = self.eval_context_mut();
14+
// os_unfair_lock holds a 32-bit value, is initialized with zero and
15+
// must be assumed to be opaque. Therefore, we can just store our
16+
// internal mutex ID in the structure without anyone noticing.
17+
this.mutex_get_or_create_id(lock_op, this.libc_ty_layout("os_unfair_lock"), 0)
18+
}
19+
}
20+
21+
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
22+
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
23+
fn os_unfair_lock_lock(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
24+
let this = self.eval_context_mut();
25+
26+
let id = this.os_unfair_lock_getid(lock_op)?;
27+
if this.mutex_is_locked(id) {
28+
if this.mutex_get_owner(id) == this.active_thread() {
29+
// Matching the current macOS implementation: abort on reentrant locking.
30+
throw_machine_stop!(TerminationInfo::Abort(
31+
"attempted to lock an os_unfair_lock that is already locked by the current thread".to_owned()
32+
));
33+
}
34+
35+
this.mutex_enqueue_and_block(id, None);
36+
} else {
37+
this.mutex_lock(id);
38+
}
39+
40+
Ok(())
41+
}
42+
43+
fn os_unfair_lock_trylock(
44+
&mut self,
45+
lock_op: &OpTy<'tcx>,
46+
dest: &MPlaceTy<'tcx>,
47+
) -> InterpResult<'tcx> {
48+
let this = self.eval_context_mut();
49+
50+
let id = this.os_unfair_lock_getid(lock_op)?;
51+
if this.mutex_is_locked(id) {
52+
// Contrary to the blocking lock function, this does not check for
53+
// reentrancy.
54+
this.write_scalar(Scalar::from_bool(false), dest)?;
55+
} else {
56+
this.mutex_lock(id);
57+
this.write_scalar(Scalar::from_bool(true), dest)?;
58+
}
59+
60+
Ok(())
61+
}
62+
63+
fn os_unfair_lock_unlock(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
64+
let this = self.eval_context_mut();
65+
66+
let id = this.os_unfair_lock_getid(lock_op)?;
67+
if this.mutex_unlock(id)?.is_none() {
68+
// Matching the current macOS implementation: abort.
69+
throw_machine_stop!(TerminationInfo::Abort(
70+
"attempted to unlock an os_unfair_lock not owned by the current thread".to_owned()
71+
));
72+
}
73+
74+
Ok(())
75+
}
76+
77+
fn os_unfair_lock_assert_owner(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
78+
let this = self.eval_context_mut();
79+
80+
let id = this.os_unfair_lock_getid(lock_op)?;
81+
if !this.mutex_is_locked(id) || this.mutex_get_owner(id) != this.active_thread() {
82+
throw_machine_stop!(TerminationInfo::Abort(
83+
"called os_unfair_lock_assert_owner on an os_unfair_lock not owned by the current thread".to_owned()
84+
));
85+
}
86+
87+
Ok(())
88+
}
89+
90+
fn os_unfair_lock_assert_not_owner(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
91+
let this = self.eval_context_mut();
92+
93+
let id = this.os_unfair_lock_getid(lock_op)?;
94+
if this.mutex_is_locked(id) && this.mutex_get_owner(id) == this.active_thread() {
95+
throw_machine_stop!(TerminationInfo::Abort(
96+
"called os_unfair_lock_assert_not_owner on an os_unfair_lock owned by the current thread".to_owned()
97+
));
98+
}
99+
100+
Ok(())
101+
}
102+
}

src/shims/unix/sync.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
473473
let ret = if this.mutex_is_locked(id) {
474474
let owner_thread = this.mutex_get_owner(id);
475475
if owner_thread != this.active_thread() {
476-
this.mutex_enqueue_and_block(id, Scalar::from_i32(0), dest.clone());
476+
this.mutex_enqueue_and_block(id, Some((Scalar::from_i32(0), dest.clone())));
477477
return Ok(());
478478
} else {
479479
// Trying to acquire the same mutex again.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//@ only-target-darwin
2+
3+
use std::cell::UnsafeCell;
4+
5+
fn main() {
6+
let lock = UnsafeCell::new(libc::OS_UNFAIR_LOCK_INIT);
7+
8+
unsafe {
9+
libc::os_unfair_lock_lock(lock.get());
10+
libc::os_unfair_lock_assert_not_owner(lock.get());
11+
//~^ error: abnormal termination: called os_unfair_lock_assert_not_owner on an os_unfair_lock owned by the current thread
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
error: abnormal termination: called os_unfair_lock_assert_not_owner on an os_unfair_lock owned by the current thread
2+
--> $DIR/apple_os_unfair_lock_assert_not_owner.rs:LL:CC
3+
|
4+
LL | libc::os_unfair_lock_assert_not_owner(lock.get());
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ called os_unfair_lock_assert_not_owner on an os_unfair_lock owned by the current thread
6+
|
7+
= note: BACKTRACE:
8+
= note: inside `main` at $DIR/apple_os_unfair_lock_assert_not_owner.rs:LL:CC
9+
10+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
11+
12+
error: aborting due to 1 previous error
13+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//@ only-target-darwin
2+
3+
use std::cell::UnsafeCell;
4+
5+
fn main() {
6+
let lock = UnsafeCell::new(libc::OS_UNFAIR_LOCK_INIT);
7+
8+
unsafe {
9+
libc::os_unfair_lock_assert_owner(lock.get());
10+
//~^ error: abnormal termination: called os_unfair_lock_assert_owner on an os_unfair_lock not owned by the current thread
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
error: abnormal termination: called os_unfair_lock_assert_owner on an os_unfair_lock not owned by the current thread
2+
--> $DIR/apple_os_unfair_lock_assert_owner.rs:LL:CC
3+
|
4+
LL | libc::os_unfair_lock_assert_owner(lock.get());
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ called os_unfair_lock_assert_owner on an os_unfair_lock not owned by the current thread
6+
|
7+
= note: BACKTRACE:
8+
= note: inside `main` at $DIR/apple_os_unfair_lock_assert_owner.rs:LL:CC
9+
10+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
11+
12+
error: aborting due to 1 previous error
13+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//@ only-target-darwin
2+
3+
use std::cell::UnsafeCell;
4+
5+
fn main() {
6+
let lock = UnsafeCell::new(libc::OS_UNFAIR_LOCK_INIT);
7+
8+
unsafe {
9+
libc::os_unfair_lock_lock(lock.get());
10+
libc::os_unfair_lock_lock(lock.get());
11+
//~^ error: abnormal termination: attempted to lock an os_unfair_lock that is already locked by the current thread
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
error: abnormal termination: attempted to lock an os_unfair_lock that is already locked by the current thread
2+
--> $DIR/apple_os_unfair_lock_reentrant.rs:LL:CC
3+
|
4+
LL | libc::os_unfair_lock_lock(lock.get());
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ attempted to lock an os_unfair_lock that is already locked by the current thread
6+
|
7+
= note: BACKTRACE:
8+
= note: inside `main` at $DIR/apple_os_unfair_lock_reentrant.rs:LL:CC
9+
10+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
11+
12+
error: aborting due to 1 previous error
13+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//@ only-target-darwin
2+
3+
use std::cell::UnsafeCell;
4+
5+
fn main() {
6+
let lock = UnsafeCell::new(libc::OS_UNFAIR_LOCK_INIT);
7+
8+
unsafe {
9+
libc::os_unfair_lock_unlock(lock.get());
10+
//~^ error: abnormal termination: attempted to unlock an os_unfair_lock not owned by the current thread
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
error: abnormal termination: attempted to unlock an os_unfair_lock not owned by the current thread
2+
--> $DIR/apple_os_unfair_lock_unowned.rs:LL:CC
3+
|
4+
LL | libc::os_unfair_lock_unlock(lock.get());
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ attempted to unlock an os_unfair_lock not owned by the current thread
6+
|
7+
= note: BACKTRACE:
8+
= note: inside `main` at $DIR/apple_os_unfair_lock_unowned.rs:LL:CC
9+
10+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
11+
12+
error: aborting due to 1 previous error
13+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//@ only-target-darwin
2+
3+
use std::cell::UnsafeCell;
4+
5+
fn main() {
6+
let lock = UnsafeCell::new(libc::OS_UNFAIR_LOCK_INIT);
7+
8+
unsafe {
9+
libc::os_unfair_lock_lock(lock.get());
10+
libc::os_unfair_lock_assert_owner(lock.get());
11+
assert!(!libc::os_unfair_lock_trylock(lock.get()));
12+
libc::os_unfair_lock_unlock(lock.get());
13+
14+
libc::os_unfair_lock_assert_not_owner(lock.get());
15+
}
16+
17+
// `os_unfair_lock`s can be moved and leaked, even when locked,
18+
// as long as no operation is occurring on the lock:
19+
let lock = lock;
20+
let locked = unsafe { libc::os_unfair_lock_trylock(lock.get()) };
21+
assert!(locked);
22+
let _lock = lock;
23+
}

0 commit comments

Comments
 (0)