Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use asm-based atomic load/store on thumbv6m #18

Merged
merged 1 commit into from
Jul 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Portable atomic types including support for 128-bit atomics, atomic float, etc.
- Provide `AtomicI128` and `AtomicU128`.
- Provide `AtomicF32` and `AtomicF64`. (optional)
<!-- - Provide generic `Atomic<T>` type. (optional) -->
- Provide atomic load/store for targets where atomic is not available at all in the standard library. (riscv without A-extension, msp430, avr)
- Provide atomic load/store for targets where atomic is not available at all in the standard library. (thumbv6m, riscv without A-extension, msp430, avr)
- Provide atomic CAS for targets where atomic CAS is not available in the standard library. (thumbv6m, riscv without A-extension, msp430, avr) (optional, [single-core only](#optional-cfg))

## 128-bit atomics support
Expand Down
1 change: 1 addition & 0 deletions no_atomic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,5 @@ const NO_ATOMIC: &[&str] = &[
"riscv32i-unknown-none-elf",
"riscv32im-unknown-none-elf",
"riscv32imc-unknown-none-elf",
"thumbv6m-none-eabi",
];
18 changes: 18 additions & 0 deletions specs/thumbv6m-none-eabi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"abi": "eabi",
"arch": "arm",
"atomic-cas": false,
"max-atomic-width": 0,
"c-enum-min-bits": 8,
"data-layout": "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64",
"emit-debug-gdb-scripts": false,
"features": "+strict-align",
"frame-pointer": "always",
"is-builtin": false,
"linker": "rust-lld",
"linker-flavor": "ld.lld",
"llvm-target": "thumbv6m-none-eabi",
"panic-strategy": "abort",
"relocation-model": "static",
"target-pointer-width": "32"
}
288 changes: 288 additions & 0 deletions src/imp/arm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
// Atomic load/store implementation on ARMv6-M.
//
// Refs:
// - atomic-maybe-uninit https://github.com/taiki-e/atomic-maybe-uninit
//
// Generated asm: https://godbolt.org/z/hx3a6j9vv

#[cfg(not(portable_atomic_no_asm))]
use core::arch::asm;
use core::{cell::UnsafeCell, sync::atomic::Ordering};

use crate::utils::{assert_load_ordering, assert_store_ordering};

// Only a full system barrier exists in the M-class architectures.
macro_rules! dmb {
() => {
"dmb sy"
};
}

#[repr(transparent)]
pub(crate) struct AtomicBool {
v: UnsafeCell<u8>,
}

// Send is implicitly implemented.
// SAFETY: any data races are prevented by atomic operations.
unsafe impl Sync for AtomicBool {}

impl AtomicBool {
#[cfg(any(test, not(portable_atomic_unsafe_assume_single_core)))]
#[inline]
pub(crate) const fn new(v: bool) -> Self {
Self { v: UnsafeCell::new(v as u8) }
}

#[cfg(any(test, not(portable_atomic_unsafe_assume_single_core)))]
#[inline]
pub(crate) fn is_lock_free() -> bool {
Self::is_always_lock_free()
}
#[cfg(any(test, not(portable_atomic_unsafe_assume_single_core)))]
#[inline]
pub(crate) const fn is_always_lock_free() -> bool {
true
}

#[cfg(any(test, not(portable_atomic_unsafe_assume_single_core)))]
#[inline]
pub(crate) fn get_mut(&mut self) -> &mut bool {
// SAFETY: the mutable reference guarantees unique ownership.
unsafe { &mut *(self.v.get() as *mut bool) }
}

#[cfg(any(test, not(portable_atomic_unsafe_assume_single_core)))]
#[inline]
pub(crate) fn into_inner(self) -> bool {
self.v.into_inner() != 0
}

#[inline]
pub(crate) fn load(&self, order: Ordering) -> bool {
assert_load_ordering(order);
// SAFETY: any data races are prevented by atomic intrinsics and the raw
// pointer passed in is valid because we got it from a reference.
unsafe { u8::atomic_load(self.v.get(), order) != 0 }
}

#[inline]
pub(crate) fn store(&self, val: bool, order: Ordering) {
assert_store_ordering(order);
// SAFETY: any data races are prevented by atomic intrinsics and the raw
// pointer passed in is valid because we got it from a reference.
unsafe {
u8::atomic_store(self.v.get(), val as u8, order);
}
}
}

#[repr(transparent)]
pub(crate) struct AtomicPtr<T> {
p: UnsafeCell<*mut T>,
}

// SAFETY: any data races are prevented by atomic operations.
unsafe impl<T> Send for AtomicPtr<T> {}
// SAFETY: any data races are prevented by atomic operations.
unsafe impl<T> Sync for AtomicPtr<T> {}

impl<T> AtomicPtr<T> {
#[cfg(any(test, not(portable_atomic_unsafe_assume_single_core)))]
#[inline]
pub(crate) const fn new(p: *mut T) -> Self {
Self { p: UnsafeCell::new(p) }
}

#[cfg(any(test, not(portable_atomic_unsafe_assume_single_core)))]
#[inline]
pub(crate) fn is_lock_free() -> bool {
Self::is_always_lock_free()
}
#[cfg(any(test, not(portable_atomic_unsafe_assume_single_core)))]
#[inline]
pub(crate) const fn is_always_lock_free() -> bool {
true
}

#[cfg(any(test, not(portable_atomic_unsafe_assume_single_core)))]
#[inline]
pub(crate) fn get_mut(&mut self) -> &mut *mut T {
self.p.get_mut()
}

#[cfg(any(test, not(portable_atomic_unsafe_assume_single_core)))]
#[inline]
pub(crate) fn into_inner(self) -> *mut T {
self.p.into_inner()
}

#[inline]
pub(crate) fn load(&self, order: Ordering) -> *mut T {
assert_load_ordering(order);
// SAFETY: any data races are prevented by atomic intrinsics and the raw
// pointer passed in is valid because we got it from a reference.
// TODO: remove int to ptr cast
unsafe { usize::atomic_load(self.p.get() as *mut usize, order) as *mut T }
}

#[inline]
pub(crate) fn store(&self, ptr: *mut T, order: Ordering) {
assert_store_ordering(order);
// SAFETY: any data races are prevented by atomic intrinsics and the raw
// pointer passed in is valid because we got it from a reference.
// TODO: remove int to ptr cast
unsafe {
usize::atomic_store(self.p.get() as *mut usize, ptr as usize, order);
}
}
}

macro_rules! atomic_int {
($int_type:ident, $atomic_type:ident, $asm_suffix:expr) => {
#[repr(transparent)]
pub(crate) struct $atomic_type {
v: UnsafeCell<$int_type>,
}

// Send is implicitly implemented.
// SAFETY: any data races are prevented by atomic operations.
unsafe impl Sync for $atomic_type {}

impl $atomic_type {
#[cfg(any(test, not(portable_atomic_unsafe_assume_single_core)))]
#[inline]
pub(crate) const fn new(v: $int_type) -> Self {
Self { v: UnsafeCell::new(v) }
}

#[cfg(any(test, not(portable_atomic_unsafe_assume_single_core)))]
#[inline]
pub(crate) fn is_lock_free() -> bool {
Self::is_always_lock_free()
}
#[cfg(any(test, not(portable_atomic_unsafe_assume_single_core)))]
#[inline]
pub(crate) const fn is_always_lock_free() -> bool {
true
}

#[cfg(any(test, not(portable_atomic_unsafe_assume_single_core)))]
#[inline]
pub(crate) fn get_mut(&mut self) -> &mut $int_type {
self.v.get_mut()
}

#[cfg(any(test, not(portable_atomic_unsafe_assume_single_core)))]
#[inline]
pub(crate) fn into_inner(self) -> $int_type {
self.v.into_inner()
}

#[inline]
pub(crate) fn load(&self, order: Ordering) -> $int_type {
assert_load_ordering(order);
// SAFETY: any data races are prevented by atomic intrinsics and the raw
// pointer passed in is valid because we got it from a reference.
unsafe { $int_type::atomic_load(self.v.get(), order) }
}

#[inline]
pub(crate) fn store(&self, val: $int_type, order: Ordering) {
assert_store_ordering(order);
// SAFETY: any data races are prevented by atomic intrinsics and the raw
// pointer passed in is valid because we got it from a reference.
unsafe {
$int_type::atomic_store(self.v.get(), val, order);
}
}
}

impl AtomicLoadStore for $int_type {
#[inline]
unsafe fn atomic_load(src: *const Self, order: Ordering) -> Self {
// SAFETY: the caller must uphold the safety contract for `atomic_load`.
unsafe {
let out;
match order {
Ordering::Relaxed => {
asm!(
concat!("ldr", $asm_suffix, " {out}, [{src}]"),
src = in(reg) src,
out = lateout(reg) out,
options(nostack, readonly),
);
}
// Acquire and SeqCst loads are equivalent.
Ordering::Acquire | Ordering::SeqCst => {
asm!(
concat!("ldr", $asm_suffix, " {out}, [{src}]"),
dmb!(),
src = in(reg) src,
out = lateout(reg) out,
options(nostack),
);
}
_ => unreachable!("{:?}", order),
}
out
}
}

#[inline]
unsafe fn atomic_store(dst: *mut Self, val: Self, order: Ordering) {
// SAFETY: the caller must uphold the safety contract for `atomic_store`.
unsafe {
macro_rules! atomic_store {
($acquire:expr, $release:expr) => {
asm!(
$release,
concat!("str", $asm_suffix, " {val}, [{dst}]"),
$acquire,
dst = in(reg) dst,
val = in(reg) val,
options(nostack),
)
};
}
match order {
Ordering::Relaxed => atomic_store!("", ""),
Ordering::Release => atomic_store!("", dmb!()),
Ordering::SeqCst => atomic_store!(dmb!(), dmb!()),
_ => unreachable!("{:?}", order),
}
}
}
}
}
}

atomic_int!(i8, AtomicI8, "b");
atomic_int!(u8, AtomicU8, "b");
atomic_int!(i16, AtomicI16, "h");
atomic_int!(u16, AtomicU16, "h");
atomic_int!(i32, AtomicI32, "");
atomic_int!(u32, AtomicU32, "");
atomic_int!(isize, AtomicIsize, "");
atomic_int!(usize, AtomicUsize, "");

trait AtomicLoadStore: Sized {
unsafe fn atomic_load(src: *const Self, order: Ordering) -> Self;
unsafe fn atomic_store(dst: *mut Self, val: Self, order: Ordering);
}

#[cfg(test)]
mod tests {
use super::*;

test_atomic_bool_load_store!();
test_atomic_ptr_load_store!();
test_atomic_int_load_store!(i8);
test_atomic_int_load_store!(u8);
test_atomic_int_load_store!(i16);
test_atomic_int_load_store!(u16);
test_atomic_int_load_store!(i32);
test_atomic_int_load_store!(u32);
test_atomic_int_load_store!(isize);
test_atomic_int_load_store!(usize);
}
4 changes: 2 additions & 2 deletions src/imp/interrupt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
// CAS together with atomic load/store. The load/store will not be
// called while interrupts are disabled, and since the load/store is
// atomic, it is not affected by interrupts even if interrupts are enabled.
#[cfg(portable_atomic_armv6m)]
use super::arm as atomic;
#[cfg(target_arch = "msp430")]
use super::msp430 as atomic;
#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
use super::riscv as atomic;
#[cfg(target_arch = "arm")]
use core::sync::atomic;

#[cfg_attr(portable_atomic_armv6m, path = "armv6m.rs")]
#[cfg_attr(target_arch = "avr", path = "avr.rs")]
Expand Down
21 changes: 21 additions & 0 deletions src/imp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ mod s390x;
#[cfg(target_arch = "msp430")]
mod msp430;

#[cfg(any(not(portable_atomic_no_asm), portable_atomic_nightly))]
#[cfg(portable_atomic_armv6m)]
mod arm;
#[cfg(not(any(not(portable_atomic_no_asm), portable_atomic_nightly)))]
#[cfg(portable_atomic_armv6m)]
#[path = "core_atomic.rs"]
mod arm;

#[cfg_attr(portable_atomic_no_cfg_target_has_atomic, cfg(any(test, portable_atomic_no_atomic_cas)))]
#[cfg_attr(
not(portable_atomic_no_cfg_target_has_atomic),
Expand Down Expand Up @@ -124,11 +132,19 @@ mod interrupt;
pub(crate) use self::core_atomic::{
AtomicBool, AtomicI16, AtomicI8, AtomicIsize, AtomicPtr, AtomicU16, AtomicU8, AtomicUsize,
};
// armv6m
#[cfg(not(portable_atomic_unsafe_assume_single_core))]
#[cfg(portable_atomic_armv6m)]
pub(crate) use self::arm::{
AtomicBool, AtomicI16, AtomicI8, AtomicIsize, AtomicPtr, AtomicU16, AtomicU8, AtomicUsize,
};
// msp430
#[cfg(not(portable_atomic_unsafe_assume_single_core))]
#[cfg(target_arch = "msp430")]
pub(crate) use self::msp430::{
AtomicBool, AtomicI16, AtomicI8, AtomicIsize, AtomicPtr, AtomicU16, AtomicU8, AtomicUsize,
};
// riscv32 without A-extension
#[cfg(not(portable_atomic_unsafe_assume_single_core))]
#[cfg(target_arch = "riscv32")]
#[cfg_attr(portable_atomic_no_cfg_target_has_atomic, cfg(portable_atomic_no_atomic_cas))]
Expand Down Expand Up @@ -173,6 +189,11 @@ pub(crate) use self::interrupt::{
))
)]
pub(crate) use self::core_atomic::{AtomicI32, AtomicU32};
// armv6m
#[cfg(not(portable_atomic_unsafe_assume_single_core))]
#[cfg(portable_atomic_armv6m)]
pub(crate) use self::arm::{AtomicI32, AtomicU32};
// riscv32 without A-extension
#[cfg(not(portable_atomic_unsafe_assume_single_core))]
#[cfg(target_arch = "riscv32")]
#[cfg_attr(portable_atomic_no_cfg_target_has_atomic, cfg(portable_atomic_no_atomic_cas))]
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Portable atomic types including support for 128-bit atomics, atomic float, etc.
- Provide `AtomicI128` and `AtomicU128`.
- Provide `AtomicF32` and `AtomicF64`. (optional)
<!-- - Provide generic `Atomic<T>` type. (optional) -->
- Provide atomic load/store for targets where atomic is not available at all in the standard library. (riscv without A-extension, msp430, avr)
- Provide atomic load/store for targets where atomic is not available at all in the standard library. (thumbv6m, riscv without A-extension, msp430, avr)
- Provide atomic CAS for targets where atomic CAS is not available in the standard library. (thumbv6m, riscv without A-extension, msp430, avr) (optional, [single-core only](#optional-cfg))

## 128-bit atomics support
Expand Down
Loading