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 <joerichey@google.com>
  • Loading branch information
joshlf and josephlr committed Aug 20, 2024
1 parent e29fb6a commit 690de69
Show file tree
Hide file tree
Showing 34 changed files with 272 additions and 137 deletions.
44 changes: 44 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ jobs:
# `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"
]
Expand All @@ -68,6 +69,7 @@ jobs:
"riscv64gc-unknown-linux-gnu",
"s390x-unknown-linux-gnu",
"x86_64-pc-windows-msvc",
"thumbv6m-none-eabi",
"wasm32-wasi"
]
features: [
Expand All @@ -87,6 +89,8 @@ jobs:
features: "--all-features"
- toolchain: "zerocopy-generic-bounds-in-const-fn"
features: "--all-features"
- toolchain: "zerocopy-target-has-atomics"
features: "--all-features"
- toolchain: "zerocopy-aarch64-simd"
features: "--all-features"
- toolchain: "zerocopy-panic-in-const"
Expand All @@ -105,6 +109,8 @@ jobs:
# zerocopy-derive doesn't behave different on these toolchains.
- crate: "zerocopy-derive"
toolchain: "zerocopy-generic-bounds-in-const-fn"
- crate: "zerocopy-derive"
toolchain: "zerocopy-target-has-atomics"
- crate: "zerocopy-derive"
toolchain: "zerocopy-aarch64-simd"
- crate: "zerocopy-derive"
Expand All @@ -127,6 +133,8 @@ jobs:
target: "s390x-unknown-linux-gnu"
- toolchain: "zerocopy-aarch64-simd"
target: "x86_64-pc-windows-msvc"
- toolchain: "zerocopy-aarch64-simd"
target: "thumbv6m-none-eabi"
- toolchain: "zerocopy-aarch64-simd"
target: "wasm32-wasi"
# Exclude most targets targets from the
Expand All @@ -147,8 +155,17 @@ jobs:
target: "s390x-unknown-linux-gnu"
- toolchain: "zerocopy-generic-bounds-in-const-fn"
target: "x86_64-pc-windows-msvc"
- toolchain: "zerocopy-generic-bounds-in-const-fn"
target: "thumbv6m-none-eabi"
- toolchain: "zerocopy-generic-bounds-in-const-fn"
target: "wasm32-wasi"
# Exclude `thumbv6m-none-eabi` combined with any feature that implies
# the `std` feature since `thumbv6m-none-eabi` does not include a
# pre-compiled std.
- target: "thumbv6m-none-eabi"
features: "--features __internal_use_only_features_that_work_on_stable"
- target: "thumbv6m-none-eabi"
features: "--all-features"
# Exclude most targets during PR development, but allow them in the
# merge queue. This speeds up our development flow, while still
# ensuring that errors on these targets are caught before a PR is
Expand All @@ -165,6 +182,9 @@ jobs:
event_name: "pull_request"
- target: "s390x-unknown-linux-gnu"
event_name: "pull_request"
# TODO(#1586): Uncomment this before merging.
# - target: "thumbv6m-none-eabi"
# event_name: "pull_request"
- target: "wasm32-wasi"
event_name: "pull_request"

Expand All @@ -176,6 +196,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 @@ -240,11 +273,19 @@ jobs:
with:
key: "${{ matrix.target }}"

# On the `thumbv6m-none-eabi` target, we can't run `cargo check --tests` due
# to the `memchr` crate, so we just do `cargo check` instead.
- name: Check
run: ./cargo.sh +${{ matrix.toolchain }} check --package ${{ matrix.crate }} --target ${{ matrix.target }} ${{ matrix.features }} --verbose
if: matrix.target == 'thumbv6m-none-eabi'

- name: Check tests
run: ./cargo.sh +${{ matrix.toolchain }} check --tests --package ${{ matrix.crate }} --target ${{ matrix.target }} ${{ matrix.features }} --verbose
if: matrix.target != 'thumbv6m-none-eabi'

- name: Build
run: ./cargo.sh +${{ matrix.toolchain }} build --package ${{ matrix.crate }} --target ${{ matrix.target }} ${{ matrix.features }} --verbose
if: matrix.target != 'thumbv6m-none-eabi'

# When building tests for the i686 target, we need certain libraries which
# are not installed by default; `gcc-multilib` includes these libraries.
Expand Down Expand Up @@ -544,6 +585,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
182 changes: 142 additions & 40 deletions src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,49 +440,151 @@ 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(all(
zerocopy_target_has_atomics,
any(
target_has_atomic = "8",
target_has_atomic = "16",
target_has_atomic = "32",
target_has_atomic = "64",
target_has_atomic = "ptr"
)
))]
mod atomics {
use super::*;

#[rustfmt::skip]
impl_traits_for_atomics!(
AtomicI16, AtomicI32, AtomicI8, AtomicIsize,
AtomicU16, AtomicU32, AtomicU8, AtomicUsize,
);
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);
)*
};
}

impl_for_transparent_wrapper!(=> TryFromBytes for AtomicBool);
impl_for_transparent_wrapper!(=> FromZeros for AtomicBool);
impl_for_transparent_wrapper!(=> IntoBytes for AtomicBool);
#[cfg(target_has_atomic = "8")]
#[cfg_attr(doc_cfg, doc(cfg(target_has_atomic = "8")))]
mod atomic_8 {
use core::sync::atomic::{AtomicBool, AtomicI8, AtomicU8};

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]);
}
}

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 core::sync::atomic::{AtomicI16, AtomicU16};

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 core::sync::atomic::{AtomicI32, AtomicU32};

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 core::sync::atomic::{AtomicI64, AtomicU64};

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 core::sync::atomic::{AtomicIsize, AtomicPtr, AtomicUsize};

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
Loading

0 comments on commit 690de69

Please sign in to comment.