Skip to content

Commit

Permalink
Detect atomic support using target_has_atomic
Browse files Browse the repository at this point in the history
Implements `TryFromBytes` and `FromZeros` for `AtomicPtr`; `FromBytes`
and `IntoBytes` are blocked by #170.

This is adapted from @josephlr's similar implementation in #1092.

Fixes #1086

Co-authored-by: Joe Richey <[email protected]>
  • Loading branch information
joshlf and josephlr committed Aug 20, 2024
1 parent 739c3d1 commit 0bc9d36
Show file tree
Hide file tree
Showing 34 changed files with 236 additions and 137 deletions.
33 changes: 32 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,19 @@ jobs:
matrix:
# See `INTERNAL.md` for an explanation of these pinned toolchain
# versions.
toolchain: [ "msrv", "stable", "nightly", "zerocopy-generic-bounds-in-const-fn", "zerocopy-aarch64-simd", "zerocopy-panic-in-const", ]
toolchain: [
"msrv",
"stable",
"nightly",

# These are the names of specific Rust versions detected in
# `build.rs`. Each of these represents the minimum Rust version for
# which a particular feature is supported.
"zerocopy-generic-bounds-in-const-fn",
"zerocopy-target-has-atomics",
"zerocopy-aarch64-simd",
"zerocopy-panic-in-const"
]
target: [
"i686-unknown-linux-gnu",
"x86_64-unknown-linux-gnu",
Expand All @@ -57,6 +69,7 @@ jobs:
"riscv64gc-unknown-linux-gnu",
"s390x-unknown-linux-gnu",
"x86_64-pc-windows-msvc",
"thumbv6m-none-eabi",
"wasm32-wasi"
]
features: [ "--no-default-features", "", "--features __internal_use_only_features_that_work_on_stable", "--all-features" ]
Expand Down Expand Up @@ -109,6 +122,8 @@ jobs:
event_name: "pull_request"
- target: "s390x-unknown-linux-gnu"
event_name: "pull_request"
- target: "thumbv6m-none-eabi"
event_name: "pull_request"
- target: "wasm32-wasi"
event_name: "pull_request"

Expand All @@ -120,6 +135,19 @@ jobs:
- name: Populate cache
uses: ./.github/actions/cache

# Ensure that Cargo resolves the minimum possible syn version so that if we
# accidentally make a change which depends upon features added in more
# recent versions of syn, we'll catch it in CI.
#
# TODO(#1595): Debug why this step is still necessary after #1564 and maybe
# remove it.
- name: Pin syn dependency
run: |
set -eo pipefail
# Override the exising `syn` dependency with one which requires an exact
# version.
cargo add -p zerocopy-derive 'syn@=2.0.46'
- name: Configure environment variables
run: |
set -eo pipefail
Expand Down Expand Up @@ -488,6 +516,9 @@ jobs:
# See comment on "Pin syn dependency" job for why we do this. It needs
# to happen before the subsequent `cargo check`, so we don't
# background it.
#
# TODO(#1595): Debug why this step is still necessary after #1564 and
# maybe remove it.
cargo add -p zerocopy-derive 'syn@=2.0.46' &> /dev/null
cargo check --workspace --tests &> /dev/null &
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ exclude = [".*"]
# From 1.61.0, Rust supports generic types with trait bounds in `const fn`.
zerocopy-generic-bounds-in-const-fn = "1.61.0"

# From 1.60.0, Rust supports `cfg(target_has_atomics)`, which allows us to
# detect whether a target supports particular sets of atomics.
zerocopy-target-has-atomics = "1.60.0"

# When the "simd" feature is enabled, include SIMD types from the
# `core::arch::aarch64` module, which was stabilized in 1.59.0. On earlier Rust
# versions, these types require the "simd-nightly" feature.
Expand Down
166 changes: 127 additions & 39 deletions src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,49 +440,137 @@ safety_comment! {
unsafe_impl_for_power_set!(A, B, C, D, E, F, G, H, I, J, K, L -> M => Immutable for opt_extern_c_fn!(...));
}

macro_rules! impl_traits_for_atomics {
($($atomics:ident),* $(,)?) => {
$(
impl_for_transparent_wrapper!(=> TryFromBytes for $atomics);
impl_for_transparent_wrapper!(=> FromZeros for $atomics);
impl_for_transparent_wrapper!(=> FromBytes for $atomics);
impl_for_transparent_wrapper!(=> IntoBytes for $atomics);
)*
#[cfg(zerocopy_target_has_atomics)]
mod atomics {
use core::sync::atomic::{
AtomicBool, AtomicI16, AtomicI32, AtomicI64, AtomicI8, AtomicIsize, AtomicPtr, AtomicU16,
AtomicU32, AtomicU64, AtomicU8, AtomicUsize,
};
}

#[rustfmt::skip]
impl_traits_for_atomics!(
AtomicI16, AtomicI32, AtomicI8, AtomicIsize,
AtomicU16, AtomicU32, AtomicU8, AtomicUsize,
);
use super::*;

impl_for_transparent_wrapper!(=> TryFromBytes for AtomicBool);
impl_for_transparent_wrapper!(=> FromZeros for AtomicBool);
impl_for_transparent_wrapper!(=> IntoBytes for AtomicBool);
macro_rules! impl_traits_for_atomics {
($($atomics:ident),* $(,)?) => {
$(
impl_known_layout!($atomics);
impl_for_transparent_wrapper!(=> TryFromBytes for $atomics);
impl_for_transparent_wrapper!(=> FromZeros for $atomics);
impl_for_transparent_wrapper!(=> FromBytes for $atomics);
impl_for_transparent_wrapper!(=> IntoBytes for $atomics);
)*
};
}

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 = "8")]
#[cfg_attr(doc_cfg, doc(cfg(target_has_atomic = "8")))]
mod atomic_8 {
use super::*;

impl_traits_for_atomics!(AtomicU8, AtomicI8);

impl_known_layout!(AtomicBool);

impl_for_transparent_wrapper!(=> TryFromBytes for AtomicBool);
impl_for_transparent_wrapper!(=> FromZeros for AtomicBool);
impl_for_transparent_wrapper!(=> IntoBytes for AtomicBool);

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:
/// All of these pass an atomic type and that type's native equivalent, as
/// required by the macro safety preconditions.
unsafe_impl_transparent_wrapper_for_atomic!(AtomicU8 [u8], AtomicI8 [i8], AtomicBool [bool]);
}
}

#[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, AtomicI16);

safety_comment! {
/// SAFETY:
/// All of these pass an atomic type and that type's native equivalent, as
/// required by the macro safety preconditions.
unsafe_impl_transparent_wrapper_for_atomic!(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, AtomicI32);

safety_comment! {
/// SAFETY:
/// All of these pass an atomic type and that type's native equivalent, as
/// required by the macro safety preconditions.
unsafe_impl_transparent_wrapper_for_atomic!(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, AtomicI64);

safety_comment! {
/// SAFETY:
/// All of these pass an atomic type and that type's native equivalent, as
/// required by the macro safety preconditions.
unsafe_impl_transparent_wrapper_for_atomic!(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, AtomicIsize);

impl_known_layout!(T => AtomicPtr<T>);

// TODO(#170): Implement `FromBytes` and `IntoBytes` once we implement
// those traits for `*mut T`.
impl_for_transparent_wrapper!(T => TryFromBytes for AtomicPtr<T>);
impl_for_transparent_wrapper!(T => FromZeros for AtomicPtr<T>);

safety_comment! {
/// SAFETY:
/// This passes an atomic type and that type's native equivalent, as
/// required by the macro safety preconditions.
unsafe_impl_transparent_wrapper_for_atomic!(AtomicUsize [usize], AtomicIsize [isize]);
unsafe_impl_transparent_wrapper_for_atomic!(T => AtomicPtr<T> [*mut T]);
}
}
}

safety_comment! {
Expand Down
14 changes: 4 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ extern crate self as zerocopy;

#[macro_use]
mod macros;
#[macro_use]
mod util;

pub mod byte_slice;
pub mod byteorder;
Expand All @@ -313,7 +315,6 @@ pub mod macro_util;
#[doc(hidden)]
pub mod pointer;
mod r#ref;
mod util;
// TODO(#252): If we make this pub, come up with a better name.
mod wrappers;

Expand All @@ -337,10 +338,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, BecauseExclusive, BecauseImmutable};
Expand Down Expand Up @@ -819,9 +816,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 @@ -830,8 +825,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
33 changes: 6 additions & 27 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ use core::{
mem::{self, ManuallyDrop, MaybeUninit},
num::{NonZeroUsize, Wrapping},
ptr::NonNull,
sync::atomic::{
AtomicBool, AtomicI16, AtomicI32, AtomicI8, AtomicIsize, AtomicPtr, AtomicU16, AtomicU32,
AtomicU8, AtomicUsize,
},
};

use crate::{
Expand Down Expand Up @@ -332,12 +328,13 @@ unsafe impl<T, I: Invariants> TransparentWrapper<I> for Unalign<T> {
///
/// The caller promises that `$atomic` is an atomic type whose natie equivalent
/// is `$native`.
#[cfg(zerocopy_target_has_atomics)]
macro_rules! unsafe_impl_transparent_wrapper_for_atomic {
($(#[$attr:meta])* $(,)?) => {};
($(#[$attr:meta])* $atomic:ty [$native:ty], $($atomics:ty [$natives:ty]),* $(,)?) => {
$(#[$attr])*
// SAFETY: See safety comment in next match arm.
unsafe impl<I: Invariants> TransparentWrapper<I> for $atomic {
unsafe impl<I: crate::invariant::Invariants> crate::util::TransparentWrapper<I> for $atomic {
unsafe_impl_transparent_wrapper_for_atomic!(@inner $atomic [$native]);
}
unsafe_impl_transparent_wrapper_for_atomic!($(#[$attr])* $($atomics [$natives],)*);
Expand All @@ -351,7 +348,7 @@ macro_rules! unsafe_impl_transparent_wrapper_for_atomic {
// [1] TODO(#896), TODO(https://github.com/rust-lang/rust/pull/121943):
// Cite docs once they've landed.
$(#[$attr])*
unsafe impl<$tyvar, I: Invariants> TransparentWrapper<I> for $atomic {
unsafe impl<$tyvar, I: crate::invariant::Invariants> crate::util::TransparentWrapper<I> for $atomic {
unsafe_impl_transparent_wrapper_for_atomic!(@inner $atomic [$native]);
}
};
Expand Down Expand Up @@ -381,11 +378,11 @@ macro_rules! unsafe_impl_transparent_wrapper_for_atomic {
//
// [1] TODO(#896), TODO(https://github.com/rust-lang/rust/pull/121943):
// Cite docs once they've landed.
type UnsafeCellVariance = Covariant;
type UnsafeCellVariance = crate::util::Covariant;

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

// SAFETY: Per [1], all atomic types have the same bit validity as their
// native counterparts. The caller has promised that `$atomic` and
Expand All @@ -394,7 +391,7 @@ macro_rules! unsafe_impl_transparent_wrapper_for_atomic {
//
// [1] TODO(#896), TODO(https://github.com/rust-lang/rust/pull/121943):
// Cite docs once they've landed.
type ValidityVariance = Covariant;
type ValidityVariance = crate::util::Covariant;

fn cast_into_inner(ptr: *mut $atomic) -> *mut UnsafeCell<$native> {
// SAFETY: Per [1] (from comment on impl block), `$atomic` has the
Expand All @@ -414,24 +411,6 @@ macro_rules! unsafe_impl_transparent_wrapper_for_atomic {
};
}

safety_comment! {
/// SAFETY:
/// All of these pass an atomic type and that type's native equivalent, as
/// required by the macro safety preconditions.
unsafe_impl_transparent_wrapper_for_atomic!(T => AtomicPtr<T> [*mut T]);
unsafe_impl_transparent_wrapper_for_atomic!(
AtomicBool [bool],
AtomicI16 [i16], AtomicI32 [i32], AtomicI8 [i8], AtomicIsize [isize],
AtomicU16 [u16], AtomicU32 [u32], AtomicU8 [u8], AtomicUsize [usize],
);
#[cfg(not(target_arch = "powerpc"))]
unsafe_impl_transparent_wrapper_for_atomic!(
#[cfg_attr(doc_cfg, doc(cfg(not(target_arch = "powerpc"))))]
core::sync::atomic::AtomicI64 [i64],
core::sync::atomic::AtomicU64 [u64],
);
}

/// Like [`PhantomData`], but [`Send`] and [`Sync`] regardless of whether the
/// wrapped `T` is.
pub(crate) struct SendSyncPhantomData<T: ?Sized>(PhantomData<T>);
Expand Down
2 changes: 1 addition & 1 deletion tests/ui-nightly/include_value_not_from_bytes.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ error[E0277]: the trait bound `NotZerocopy<u32>: zerocopy::FromBytes` is not sat
AU16
AtomicI16
AtomicI32
AtomicI64
AtomicI8
AtomicIsize
AtomicU16
AtomicU32
and $N others
note: required by a bound in `AssertIsFromBytes`
--> tests/ui-nightly/include_value_not_from_bytes.rs:15:42
Expand Down
Loading

0 comments on commit 0bc9d36

Please sign in to comment.