Skip to content

Commit

Permalink
Properly gate atomics implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Joe Richey <[email protected]>
  • Loading branch information
josephlr committed Apr 4, 2024
1 parent 5f07d61 commit 9473ad4
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 161 deletions.
192 changes: 192 additions & 0 deletions src/atomics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
//! Conditionally implement traits for atomic types
//!
//! Note that we only implement traits for atomics types if the corresponding
//! [`#[cfg(target_has_atomic)]`](https://doc.rust-lang.org/reference/conditional-compilation.html#target_has_atomic)
//! configuration option is true.
//!
//! Note: On targets like `thumbv6m-none-eabi` (which has an [`AtomicU32`]
//! type but `#[cfg(target_has_atomic = "32")]` is false), we don't implement
//! the zerocopy traits. We will be able to handle this when Rust stabilizes
//! [`#[cfg(target_has_atomic_load_store)]`](https://www.github.com/rust-lang/rust/issues/94039).

use core::{cell::UnsafeCell, sync::atomic::*};

use crate::{
pointer::{invariant::Invariants, Maybe},
util::{Covariant, Invariant, TransparentWrapper},
FromBytes, FromZeros, IntoBytes, KnownLayout, TryFromBytes, Unaligned,
};

/// Implements `TransparentWrapper` for an atomic type.
///
/// # Safety
///
/// The caller promises that `$atomic` is an atomic type whose native equivalent
/// is `$native`.
macro_rules! unsafe_impl_transparent_wrapper_for_atomic {
($atomic:ty [$native:ty]) => {
// We implement for `$atomic` and set `Inner = $native`. The caller has
// promised that `$atomic` and `$native` are an atomic type and its
// native counterpart, respectively. Per [1], `$atomic` and `$native`
// have the same size.
//
// [1] TODO(#896), TODO(https://github.com/rust-lang/rust/pull/121943):
// Cite docs once they've landed.
type Inner = UnsafeCell<$native>;

// SAFETY: It is "obvious" that each atomic type contains a single
// `UnsafeCell` that covers all bytes of the type, but we can also prove
// it:
// - Since `$atomic` provides an API which permits loading and storing
// values of type `$native` via a `&self` (shared) reference, *some*
// interior mutation must be happening, and interior mutation can only
// happen via `UnsafeCell`. Further, there must be enough bytes in
// `$atomic` covered by an `UnsafeCell` to hold every possible value
// of `$native`.
// - Per [1], `$atomic` has the same size as `$native`. This on its own
// isn't enough: it would still be possible for `$atomic` to store
// `$native` using a compact representation (for `$native` types for
// which some bit patterns are illegal). However, this is ruled out by
// the fact that `$atomic` has the same bit validity as `$native` [1].
// Thus, we can conclude that every byte of `$atomic` must be covered
// by an `UnsafeCell`.
//
// Thus, every byte of `$atomic` is covered by an `UnsafeCell`, and we
// set `type Inner = UnsafeCell<$native>`. Thus, `Self` and
// `Self::Inner` have `UnsafeCell`s covering the same byte ranges.
//
// [1] TODO(#896), TODO(https://github.com/rust-lang/rust/pull/121943):
// Cite docs once they've landed.
type UnsafeCellVariance = Covariant;

// SAFETY: No safety justification is required for an invariant
// variance.
type AlignmentVariance = Invariant;

// SAFETY: Per [1], all atomic types have the same bit validity as their
// native counterparts. The caller has promised that `$atomic` and
// `$native` are an atomic type and its native counterpart,
// respectively.
//
// [1] TODO(#896), TODO(https://github.com/rust-lang/rust/pull/121943):
// Cite docs once they've landed.
type ValidityVariance = Covariant;

fn cast_into_inner(ptr: *mut $atomic) -> *mut UnsafeCell<$native> {
// SAFETY: Per [1] (from comment on impl block), `$atomic` has the
// same size as `$native`. Thus, this cast preserves size.
//
// This cast trivially preserves provenance.
ptr.cast::<UnsafeCell<$native>>()
}

fn cast_from_inner(ptr: *mut UnsafeCell<$native>) -> *mut $atomic {
// SAFETY: Per [1] (from comment on impl block), `$atomic` has the
// same size as `$native`. Thus, this cast preserves size.
//
// This cast trivially preserves provenance.
ptr.cast::<$atomic>()
}
};
}

macro_rules! impl_from_bytes_for_atomic {
(AtomicBool [$native:ty]) => {};
($atomic:ty [$native:ty]) => {
impl_for_transparent_wrapper!(FromBytes for $atomic [UnsafeCell<$native>]);
};
}

/// Implements the zerocopy traits for atomic integer types.
///
/// # Safety
///
/// The caller promises that `$atomic` is an atomic type whose native equivalent
/// is `$native`.
macro_rules! impl_traits_for_atomics {
($($atomic:ident [$native:ty]),*) => {$(
/// SAFETY: All of these pass an atomic type and that type's native equivalent
unsafe impl<I: Invariants> TransparentWrapper<I> for $atomic {
unsafe_impl_transparent_wrapper_for_atomic!($atomic [$native]);
}
impl_for_transparent_wrapper!(TryFromBytes for $atomic [UnsafeCell<$native>]);
impl_for_transparent_wrapper!(FromZeros for $atomic [UnsafeCell<$native>]);
impl_from_bytes_for_atomic!($atomic [$native]);
impl_for_transparent_wrapper!(IntoBytes for $atomic [UnsafeCell<$native>]);

impl_known_layout!($atomic);
)*};
}

#[cfg(target_has_atomic = "8")]
#[cfg_attr(doc_cfg, doc(cfg(target_has_atomic = "8")))]
mod atomic_8 {
use super::*;
impl_traits_for_atomics!(AtomicBool[bool], AtomicU8[u8], AtomicI8[i8]);

safety_comment! {
/// SAFETY:
/// Per [1], `AtomicBool`, `AtomicU8`, and `AtomicI8` have the same size as
/// `bool`, `u8`, and `i8` respectively. Since a type's alignment cannot be
/// smaller than 1 [2], and since its alignment cannot be greater than its
/// size [3], the only possible value for the alignment is 1. Thus, it is
/// sound to implement `Unaligned`.
///
/// [1] TODO(#896), TODO(https://github.com/rust-lang/rust/pull/121943):
/// Cite docs once they've landed.
///
/// [2] Per https://doc.rust-lang.org/reference/type-layout.html#size-and-alignment:
///
/// Alignment is measured in bytes, and must be at least 1.
///
/// [3] Per https://doc.rust-lang.org/reference/type-layout.html#size-and-alignment:
///
/// The size of a value is always a multiple of its alignment.
unsafe_impl!(AtomicBool: Unaligned);
unsafe_impl!(AtomicU8: Unaligned);
unsafe_impl!(AtomicI8: Unaligned);
}
assert_unaligned!(AtomicBool, AtomicU8, AtomicI8);
}

#[cfg(target_has_atomic = "16")]
#[cfg_attr(doc_cfg, doc(cfg(target_has_atomic = "16")))]
mod atomic_16 {
use super::*;
impl_traits_for_atomics!(AtomicU16[u16], AtomicI16[i16]);
}

#[cfg(target_has_atomic = "32")]
#[cfg_attr(doc_cfg, doc(cfg(target_has_atomic = "32")))]
mod atomic_32 {
use super::*;
impl_traits_for_atomics!(AtomicU32[u32], AtomicI32[i32]);
}

#[cfg(target_has_atomic = "64")]
#[cfg_attr(doc_cfg, doc(cfg(target_has_atomic = "64")))]
mod atomic_64 {
use super::*;
impl_traits_for_atomics!(AtomicU64[u64], AtomicI64[i64]);
}

#[cfg(target_has_atomic = "ptr")]
#[cfg_attr(doc_cfg, doc(cfg(target_has_atomic = "ptr")))]
mod atomic_ptr {
use super::*;
impl_traits_for_atomics!(AtomicUsize[usize], AtomicIsize[isize]);

/// SAFETY: All of these pass an atomic type and that type's native equivalent
/// For thin pointers (note that `T: Sized`) AtomicPtr<T> is garunteed to
/// wrap a *mut T. Just like *mut T, we don't implement FromBytes/IntoBytes.
///
/// See the comments on the *mut T impls for more information.
///
/// This macro is just used to keep the impl attributes consistent.
unsafe impl<T, I: Invariants> TransparentWrapper<I> for AtomicPtr<T> {
unsafe_impl_transparent_wrapper_for_atomic!(AtomicPtr<T> [*mut T]);
}
impl_for_transparent_wrapper!(T => TryFromBytes for AtomicPtr<T> [UnsafeCell<*mut T>]);
impl_for_transparent_wrapper!(T => FromZeros for AtomicPtr<T> [UnsafeCell<*mut T>]);
impl_known_layout!(T => AtomicPtr<T>);
}
54 changes: 3 additions & 51 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ extern crate self as zerocopy;
#[macro_use]
mod macros;

mod atomics;
pub mod byteorder;
mod deprecated;
#[doc(hidden)]
Expand Down Expand Up @@ -305,10 +306,6 @@ use core::{
ops::{Deref, DerefMut},
ptr::{self, NonNull},
slice,
sync::atomic::{
AtomicBool, AtomicI16, AtomicI32, AtomicI8, AtomicIsize, AtomicPtr, AtomicU16, AtomicU32,
AtomicU8, AtomicUsize,
},
};

use crate::pointer::invariant;
Expand Down Expand Up @@ -1101,9 +1098,7 @@ impl_known_layout!(
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize, f32, f64,
bool, char,
NonZeroU8, NonZeroI8, NonZeroU16, NonZeroI16, NonZeroU32, NonZeroI32,
NonZeroU64, NonZeroI64, NonZeroU128, NonZeroI128, NonZeroUsize, NonZeroIsize,
AtomicBool, AtomicI16, AtomicI32, AtomicI8, AtomicIsize, AtomicU16, AtomicU32,
AtomicU8, AtomicUsize
NonZeroU64, NonZeroI64, NonZeroU128, NonZeroI128, NonZeroUsize, NonZeroIsize
);
#[rustfmt::skip]
impl_known_layout!(
Expand All @@ -1112,8 +1107,7 @@ impl_known_layout!(
T => Wrapping<T>,
T => MaybeUninit<T>,
T: ?Sized => *const T,
T: ?Sized => *mut T,
T => AtomicPtr<T>
T: ?Sized => *mut T
);
impl_known_layout!(const N: usize, T => [T; N]);

Expand Down Expand Up @@ -3900,48 +3894,6 @@ safety_comment! {
unsafe_impl_for_power_set!(A, B, C, D, E, F, G, H, I, J, K, L -> M => NoCell for opt_extern_c_fn!(...));
}

macro_rules! impl_traits_for_atomics {
($($atomics:ident [$inners:ident]),* $(,)?) => {
$(
impl_for_transparent_wrapper!(TryFromBytes for $atomics [UnsafeCell<$inners>]);
impl_for_transparent_wrapper!(FromZeros for $atomics [UnsafeCell<$inners>]);
impl_for_transparent_wrapper!(FromBytes for $atomics [UnsafeCell<$inners>]);
impl_for_transparent_wrapper!(IntoBytes for $atomics [UnsafeCell<$inners>]);
)*
};
}

#[rustfmt::skip]
impl_traits_for_atomics!(
AtomicBool [bool],
AtomicI16 [i16], AtomicI32 [i32], AtomicI8 [i8], AtomicIsize [isize],
AtomicU16 [u16], AtomicU32 [u32], AtomicU8 [u8], AtomicUsize [usize],
);

safety_comment! {
/// SAFETY:
/// Per [1], `AtomicBool`, `AtomicU8`, and `AtomicI8` have the same size as
/// `bool`, `u8`, and `i8` respectively. Since a type's alignment cannot be
/// smaller than 1 [2], and since its alignment cannot be greater than its
/// size [3], the only possible value for the alignment is 1. Thus, it is
/// sound to implement `Unaligned`.
///
/// [1] TODO(#896), TODO(https://github.com/rust-lang/rust/pull/121943):
/// Cite docs once they've landed.
///
/// [2] Per https://doc.rust-lang.org/reference/type-layout.html#size-and-alignment:
///
/// Alignment is measured in bytes, and must be at least 1.
///
/// [3] Per https://doc.rust-lang.org/reference/type-layout.html#size-and-alignment:
///
/// The size of a value is always a multiple of its alignment.
unsafe_impl!(AtomicBool: Unaligned);
unsafe_impl!(AtomicU8: Unaligned);
unsafe_impl!(AtomicI8: Unaligned);
assert_unaligned!(AtomicBool, AtomicU8, AtomicI8);
}

safety_comment! {
/// SAFETY:
/// Per reference [1]:
Expand Down
Loading

0 comments on commit 9473ad4

Please sign in to comment.