Skip to content

Commit e90f047

Browse files
committed
Auto merge of #3745 - joboet:os_unfair_lock, r=RalfJung
Implement the `os_unfair_lock` functions on macOS These are needed for #122408. See the documentation [here](https://developer.apple.com/documentation/os/synchronization?language=objc) and the implementation [here](https://github.com/apple-oss-distributions/libplatform/blob/a00a4cc36da2110578bcf3b8eeeeb93dcc7f4e11/src/os/lock.c#L645).
2 parents 5f99349 + 32221c3 commit e90f047

15 files changed

+284
-7
lines changed

src/tools/miri/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/tools/miri/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/tools/miri/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

Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod foreign_items;
2+
pub mod sync;
+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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+
//! Note that we don't emulate every edge-case behaviour of the locks. Notably,
9+
//! we don't abort when locking a lock owned by a thread that has already exited
10+
//! and we do not detect copying of the lock, but macOS doesn't guarantee anything
11+
//! in that case either.
12+
13+
use crate::*;
14+
15+
impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
16+
trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
17+
fn os_unfair_lock_getid(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx, MutexId> {
18+
let this = self.eval_context_mut();
19+
// os_unfair_lock holds a 32-bit value, is initialized with zero and
20+
// must be assumed to be opaque. Therefore, we can just store our
21+
// internal mutex ID in the structure without anyone noticing.
22+
this.mutex_get_or_create_id(lock_op, this.libc_ty_layout("os_unfair_lock"), 0)
23+
}
24+
}
25+
26+
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
27+
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
28+
fn os_unfair_lock_lock(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
29+
let this = self.eval_context_mut();
30+
31+
let id = this.os_unfair_lock_getid(lock_op)?;
32+
if this.mutex_is_locked(id) {
33+
if this.mutex_get_owner(id) == this.active_thread() {
34+
// Matching the current macOS implementation: abort on reentrant locking.
35+
throw_machine_stop!(TerminationInfo::Abort(
36+
"attempted to lock an os_unfair_lock that is already locked by the current thread".to_owned()
37+
));
38+
}
39+
40+
this.mutex_enqueue_and_block(id, None);
41+
} else {
42+
this.mutex_lock(id);
43+
}
44+
45+
Ok(())
46+
}
47+
48+
fn os_unfair_lock_trylock(
49+
&mut self,
50+
lock_op: &OpTy<'tcx>,
51+
dest: &MPlaceTy<'tcx>,
52+
) -> InterpResult<'tcx> {
53+
let this = self.eval_context_mut();
54+
55+
let id = this.os_unfair_lock_getid(lock_op)?;
56+
if this.mutex_is_locked(id) {
57+
// Contrary to the blocking lock function, this does not check for
58+
// reentrancy.
59+
this.write_scalar(Scalar::from_bool(false), dest)?;
60+
} else {
61+
this.mutex_lock(id);
62+
this.write_scalar(Scalar::from_bool(true), dest)?;
63+
}
64+
65+
Ok(())
66+
}
67+
68+
fn os_unfair_lock_unlock(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
69+
let this = self.eval_context_mut();
70+
71+
let id = this.os_unfair_lock_getid(lock_op)?;
72+
if this.mutex_unlock(id)?.is_none() {
73+
// Matching the current macOS implementation: abort.
74+
throw_machine_stop!(TerminationInfo::Abort(
75+
"attempted to unlock an os_unfair_lock not owned by the current thread".to_owned()
76+
));
77+
}
78+
79+
Ok(())
80+
}
81+
82+
fn os_unfair_lock_assert_owner(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
83+
let this = self.eval_context_mut();
84+
85+
let id = this.os_unfair_lock_getid(lock_op)?;
86+
if !this.mutex_is_locked(id) || this.mutex_get_owner(id) != this.active_thread() {
87+
throw_machine_stop!(TerminationInfo::Abort(
88+
"called os_unfair_lock_assert_owner on an os_unfair_lock not owned by the current thread".to_owned()
89+
));
90+
}
91+
92+
Ok(())
93+
}
94+
95+
fn os_unfair_lock_assert_not_owner(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
96+
let this = self.eval_context_mut();
97+
98+
let id = this.os_unfair_lock_getid(lock_op)?;
99+
if this.mutex_is_locked(id) && this.mutex_get_owner(id) == this.active_thread() {
100+
throw_machine_stop!(TerminationInfo::Abort(
101+
"called os_unfair_lock_assert_not_owner on an os_unfair_lock owned by the current thread".to_owned()
102+
));
103+
}
104+
105+
Ok(())
106+
}
107+
}

src/tools/miri/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,25 @@
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.
18+
// In the real implementation, even moving it while locked is possible
19+
// (and "forks" the lock, i.e. old and new location have independent wait queues);
20+
// Miri behavior differs here and anyway none of this is documented.
21+
let lock = lock;
22+
let locked = unsafe { libc::os_unfair_lock_trylock(lock.get()) };
23+
assert!(locked);
24+
let _lock = lock;
25+
}

0 commit comments

Comments
 (0)