Rollup merge of #66705 - pitdicker:atomic_mut_ptr, r=KodrAus

Atomic as_mut_ptr

I encountered the following pattern a few times: In Rust we use some atomic type like `AtomicI32`, and an FFI interface exposes this as `*mut i32` (or some similar `libc` type).

It was not obvious to me if a just transmuting a pointer to the atomic was acceptable, or if this should use a cast that goes through an `UnsafeCell`. See https://github.com/rust-lang/rust/issues/66136#issuecomment-557802477

Transmuting the pointer directly:
```rust
let atomic = AtomicI32::new(1);
let ptr = &atomic as *const AtomicI32 as *mut i32;
unsafe {
    ffi(ptr);
}
```

A dance with `UnsafeCell`:
```rust
let atomic = AtomicI32::new(1);
unsafe {
    let ptr = (&*(&atomic as *const AtomicI32 as *const UnsafeCell<i32>)).get();
    ffi(ptr);
}
```

Maybe in the end both ways could be valid. But why not expose a direct method to get a pointer from the standard library?

An `as_mut_ptr` method on atomics can be safe, because only the use of the resulting pointer is where things can get unsafe. I documented its use for FFI, and "Doing non-atomic reads and writes on the resulting integer can be a data race."

The standard library could make use this method in a few places in the WASM module.

cc @RalfJung as you answered my original question.
This commit is contained in:
Mazdak Farrokhzad 2019-11-30 16:56:47 +01:00 committed by GitHub
commit 123406cac7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 80 additions and 5 deletions

View File

@ -802,6 +802,43 @@ pub fn fetch_or(&self, val: bool, order: Ordering) -> bool {
pub fn fetch_xor(&self, val: bool, order: Ordering) -> bool {
unsafe { atomic_xor(self.v.get(), val as u8, order) != 0 }
}
/// Returns a mutable pointer to the underlying [`bool`].
///
/// Doing non-atomic reads and writes on the resulting integer can be a data race.
/// This method is mostly useful for FFI, where the function signature may use
/// `*mut bool` instead of `&AtomicBool`.
///
/// Returning an `*mut` pointer from a shared reference to this atomic is safe because the
/// atomic types work with interior mutability. All modifications of an atomic change the value
/// through a shared reference, and can do so safely as long as they use atomic operations. Any
/// use of the returned raw pointer requires an `unsafe` block and still has to uphold the same
/// restriction: operations on it must be atomic.
///
/// [`bool`]: ../../../std/primitive.bool.html
///
/// # Examples
///
/// ```ignore (extern-declaration)
/// # fn main() {
/// use std::sync::atomic::AtomicBool;
/// extern {
/// fn my_atomic_op(arg: *mut bool);
/// }
///
/// let mut atomic = AtomicBool::new(true);
/// unsafe {
/// my_atomic_op(atomic.as_mut_ptr());
/// }
/// # }
/// ```
#[inline]
#[unstable(feature = "atomic_mut_ptr",
reason = "recently added",
issue = "66893")]
pub fn as_mut_ptr(&self) -> *mut bool {
self.v.get() as *mut bool
}
}
#[cfg(target_has_atomic_load_store = "ptr")]
@ -1891,6 +1928,43 @@ pub fn fetch_min(&self, val: $int_type, order: Ordering) -> $int_type {
}
}
doc_comment! {
concat!("Returns a mutable pointer to the underlying integer.
Doing non-atomic reads and writes on the resulting integer can be a data race.
This method is mostly useful for FFI, where the function signature may use
`*mut ", stringify!($int_type), "` instead of `&", stringify!($atomic_type), "`.
Returning an `*mut` pointer from a shared reference to this atomic is safe because the
atomic types work with interior mutability. All modifications of an atomic change the value
through a shared reference, and can do so safely as long as they use atomic operations. Any
use of the returned raw pointer requires an `unsafe` block and still has to uphold the same
restriction: operations on it must be atomic.
# Examples
```ignore (extern-declaration)
# fn main() {
", $extra_feature, "use std::sync::atomic::", stringify!($atomic_type), ";
extern {
fn my_atomic_op(arg: *mut ", stringify!($int_type), ");
}
let mut atomic = ", stringify!($atomic_type), "::new(1);
unsafe {
my_atomic_op(atomic.as_mut_ptr());
}
# }
```"),
#[inline]
#[unstable(feature = "atomic_mut_ptr",
reason = "recently added",
issue = "66893")]
pub fn as_mut_ptr(&self) -> *mut $int_type {
self.v.get()
}
}
}
}
}

View File

@ -234,6 +234,7 @@
#![feature(allocator_internals)]
#![feature(allow_internal_unsafe)]
#![feature(allow_internal_unstable)]
#![feature(atomic_mut_ptr)]
#![feature(arbitrary_self_types)]
#![feature(array_error_internals)]
#![feature(asm)]

View File

@ -67,7 +67,7 @@ pub fn lock() -> DropLock {
//
// unsafe {
// let r = core::arch::wasm32::i32_atomic_wait(
// &LOCKED as *const AtomicI32 as *mut i32,
// LOCKED.as_mut_ptr(),
// 1, // expected value
// -1, // timeout
// );
@ -143,7 +143,7 @@ fn drop(&mut self) {
//
// unsafe {
// core::arch::wasm32::atomic_notify(
// &LOCKED as *const AtomicI32 as *mut i32,
// LOCKED.as_mut_ptr(),
// 1, // only one thread
// );
// }

View File

@ -89,6 +89,6 @@ pub unsafe fn destroy(&self) {
#[inline]
fn ptr(&self) -> *mut i32 {
assert_eq!(mem::size_of::<usize>(), mem::size_of::<i32>());
&self.cnt as *const AtomicUsize as *mut i32
self.cnt.as_mut_ptr() as *mut i32
}
}

View File

@ -56,7 +56,7 @@ pub unsafe fn destroy(&self) {
#[inline]
fn ptr(&self) -> *mut i32 {
assert_eq!(mem::size_of::<usize>(), mem::size_of::<i32>());
&self.locked as *const AtomicUsize as *mut isize as *mut i32
self.locked.as_mut_ptr() as *mut i32
}
}
@ -145,6 +145,6 @@ pub unsafe fn destroy(&self) {
#[inline]
fn ptr(&self) -> *mut i32 {
&self.owner as *const AtomicU32 as *mut i32
self.owner.as_mut_ptr() as *mut i32
}
}