diff --git a/.github/.cspell/project-dictionary.txt b/.github/.cspell/project-dictionary.txt index d28521d5..04fcdcc7 100644 --- a/.github/.cspell/project-dictionary.txt +++ b/.github/.cspell/project-dictionary.txt @@ -6,6 +6,7 @@ andn aqrl armasm beqz +Bicc bnez casp cbnz @@ -22,7 +23,9 @@ DWCAS espup fild fistp +gaisler getex +GRLIB IMAFD inequal ishld @@ -39,7 +42,9 @@ ldrex ldrexd ldsetp ldxp +leoncasa lgcc +libnspr libunwind linkall lmul @@ -49,6 +54,7 @@ lwarx lwsync machdep mcpu +membar memd memw mfcr @@ -69,6 +75,8 @@ opensbi orrs partword pshufd +putchar +qbsp quadword rcpc risbg @@ -85,6 +93,7 @@ sllv sltui sreg srlv +stbar stilp stlxp stpq @@ -96,7 +105,9 @@ stwcx stxp subarch swpp +tsim uart +ultrasparc usart uwrite uwriteln diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f729b4d9..4ff9fd43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -234,6 +234,10 @@ jobs: target: s390x-unknown-linux-gnu - rust: nightly target: s390x-unknown-linux-gnu + - rust: nightly-2024-11-08 # Rust 1.84, LLVM 19 (oldest version we can use asm_experimental_arch on this target) + target: sparc64-unknown-linux-gnu + - rust: nightly + target: sparc64-unknown-linux-gnu - rust: '1.59' # LLVM 13 target: thumbv7neon-unknown-linux-gnueabihf - rust: '1.74' # LLVM 17 (oldest version that MaybeUninit register is supported) @@ -433,6 +437,7 @@ jobs: apt_packages+=( avr-libc gcc-avr + libnspr4 mspdebug simavr ) @@ -447,10 +452,16 @@ jobs: sudo mv -- "opensbi-${OPENSBI_VERSION}-rv-bin/share/opensbi/ilp32/generic/firmware/fw_dynamic.elf" /usr/share/qemu/opensbi-riscv32-generic-fw_dynamic.elf rm -rf -- "opensbi-${OPENSBI_VERSION}-rv-bin" if [[ "${{ matrix.rust }}" == "nightly"* ]]; then - mkdir -p -- "${HOME}"/msp430-gcc + mkdir -p -- "${HOME}"/{msp430-gcc,sparc-bcc-gcc,tsim} retry curl --proto '=https' --tlsv1.2 -fsSL --retry 10 --retry-connrefused https://dr-download.ti.com/software-development/ide-configuration-compiler-or-debugger/MD-LlCjWuAbzH/9.3.1.2/msp430-gcc-9.3.1.11_linux64.tar.bz2 \ | tar xjf - --strip-components 1 -C "${HOME}"/msp430-gcc printf '%s\n' "${HOME}"/msp430-gcc/bin >>"${GITHUB_PATH}" + retry curl --proto '=https' --tlsv1.2 -fsSL --retry 10 --retry-connrefused https://www.gaisler.com/anonftp/bcc2/bin/bcc-2.3.1-gcc-sparc-linux64.tar.xz \ + | tar xJf - --strip-components 1 -C "${HOME}"/sparc-bcc-gcc + printf '%s\n' "${HOME}"/sparc-bcc-gcc/bin >>"${GITHUB_PATH}" + retry curl --proto '=https' --tlsv1.2 -fsSL --retry 10 --retry-connrefused https://www.gaisler.com/tsim3/tsim-eval/tsim-eval-3.1.11.tar.gz \ + | tar xzf - --strip-components 1 -C "${HOME}"/tsim + printf '%s\n' "${HOME}"/tsim/tsim/linux-x64 >>"${GITHUB_PATH}" retry espup install --targets esp32,esp32s2,esp32s3 fi env: diff --git a/README.md b/README.md index 7250cad5..f5cecccb 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ This crate provides a way to soundly perform such operations. ## Platform Support -Currently, x86, x86_64, Arm, AArch64, RISC-V, LoongArch64, MIPS32, MIPS64, PowerPC, s390x, MSP430, Arm64EC, AVR, Hexagon, M68k, and Xtensa are supported. +Currently, x86, x86_64, Arm, AArch64, RISC-V, LoongArch64, MIPS32, MIPS64, PowerPC, s390x, MSP430, Arm64EC, AVR, SPARC, Hexagon, M68k, and Xtensa are supported. | target_arch | primitives | load/store | swap/CAS | | -------------------------------- | --------------------------------------------------- |:----------:|:--------:| @@ -40,6 +40,8 @@ Currently, x86, x86_64, Arm, AArch64, RISC-V, LoongArch64, MIPS32, MIPS64, Power | arm64ec \[4] | isize,usize,i8,u8,i16,u16,i32,u32,i64,u64,i128,u128 | ✓ | ✓ | | msp430 \[4] (experimental) | isize,usize,i8,u8,i16,u16 | ✓ | ✓ | | avr \[4] (experimental) | isize,usize,i8,u8,i16,u16 | ✓ | ✓ | +| sparc \[4] \[7] (experimental) | isize,usize,i8,u8,i16,u16,i32,u32 | ✓ | ✓ | +| sparc64 \[4] (experimental) | isize,usize,i8,u8,i16,u16,i32,u32,i64,u64 | ✓ | ✓ | | hexagon \[4] (experimental) | isize,usize,i8,u8,i16,u16,i32,u32,i64,u64 | ✓ | ✓ | | m68k \[4] (experimental) | isize,usize,i8,u8,i16,u16,i32,u32 | ✓ | ✓\[1] | | xtensa \[4] (experimental) | isize,usize,i8,u8,i16,u16,i32,u32 | ✓ | ✓\[1] | @@ -50,6 +52,7 @@ Currently, x86, x86_64, Arm, AArch64, RISC-V, LoongArch64, MIPS32, MIPS64, Power \[4] Requires nightly due to `#![feature(asm_experimental_arch)]`.
\[5] Requires cmpxchg16b target feature (enabled by default on Apple and Windows (except Windows 7) targets).
\[6] Requires target-cpu pwr8+ (powerpc64le is pwr8 by default).
+\[7] Requires CAS instruction support.
Feel free to submit an issue if your target is not supported yet. diff --git a/build.rs b/build.rs index a1598cde..61d0ebe6 100644 --- a/build.rs +++ b/build.rs @@ -30,7 +30,7 @@ fn main() { if version.minor >= 80 { println!( - r#"cargo:rustc-check-cfg=cfg(target_feature,values("x87","v8m","fast-serialization","isa-68020"))"# + r#"cargo:rustc-check-cfg=cfg(target_feature,values("x87","v8m","fast-serialization","leoncasa","v9","isa-68020"))"# ); // Custom cfgs set by build script. Not public API. @@ -41,7 +41,7 @@ fn main() { // TODO: handle multi-line target_feature_fallback // grep -F 'target_feature_fallback("' build.rs | grep -Ev '^ *//' | sed -E 's/^.*target_feature_fallback\(//; s/",.*$/"/' | LC_ALL=C sort -u | tr '\n' ',' | sed -E 's/,$/\n/' println!( - r#"cargo:rustc-check-cfg=cfg(atomic_maybe_uninit_target_feature,values("a","cmpxchg16b","fast-serialization","isa-68020","lse","lse128","lse2","mclass","partword-atomics","quadword-atomics","rcpc","rcpc3","v5te","v6","v7","v8","v8m","x87","zaamo","zabha"))"# + r#"cargo:rustc-check-cfg=cfg(atomic_maybe_uninit_target_feature,values("a","cmpxchg16b","fast-serialization","isa-68020","leoncasa","lse","lse128","lse2","mclass","partword-atomics","quadword-atomics","rcpc","rcpc3","v5te","v6","v7","v8","v8m","v9","x87","zaamo","zabha"))"# ); } @@ -107,6 +107,15 @@ fn main() { println!("cargo:rustc-cfg=atomic_maybe_uninit_unstable_asm_experimental_arch"); } } + // https://github.com/rust-lang/rust/pull/132472 merged in Rust 1.84 (nightly-2024-11-08). + "sparc" | "sparc64" => { + if version.nightly + && version.probe(84, 2024, 11, 7) + && is_allowed_feature("asm_experimental_arch") + { + println!("cargo:rustc-cfg=atomic_maybe_uninit_unstable_asm_experimental_arch"); + } + } _ => {} } @@ -351,6 +360,32 @@ fn main() { // bcr 14,0 target_feature_fallback("fast-serialization", arch9_features); } + "sparc" => { + let mut leoncasa = false; + let mut v9 = false; + if let Some(cpu) = target_cpu() { + // https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/llvm/lib/Target/Sparc/Sparc.td + match &*cpu { + "myriad2" | "myriad2.1" | "myriad2.2" | "myriad2.3" | "ma2100" | "ma2150" + | "ma2155" | "ma2450" | "ma2455" | "ma2x5x" | "ma2080" | "ma2085" + | "ma2480" | "ma2485" | "ma2x8x" | "gr712rc" | "leon4" | "gr740" => { + leoncasa = true; + } + "v9" | "ultrasparc" | "ultrasparc3" | "niagara" | "niagara2" | "niagara3" + | "niagara4" => v9 = true, + _ => {} + } + } else { + // https://github.com/llvm/llvm-project/pull/109278 + // https://github.com/rust-lang/rust/blob/1.82.0/compiler/rustc_target/src/spec/targets/sparc_unknown_linux_gnu.rs#L17 + v9 = target_os == "linux" || target_os == "solaris"; + } + // As of rustc 1.82, target_feature "leoncasa"/"v9" is not available on rustc side: + // https://github.com/rust-lang/rust/blob/1.82.0/compiler/rustc_target/src/target_features.rs + // (will be added in https://github.com/rust-lang/rust/pull/132552) + target_feature_fallback("leoncasa", leoncasa); + target_feature_fallback("v9", v9); + } "m68k" => { // https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/llvm/lib/Target/M68k/M68k.td // Linux requires M68020+. diff --git a/src/arch/cfgs/sparc.rs b/src/arch/cfgs/sparc.rs new file mode 100644 index 00000000..a792054a --- /dev/null +++ b/src/arch/cfgs/sparc.rs @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#![allow(missing_docs)] + +#[macro_export] +macro_rules! cfg_has_atomic_8 { + ($($tt:tt)*) => { $($tt)* }; +} +#[macro_export] +macro_rules! cfg_no_atomic_8 { + ($($tt:tt)*) => {}; +} +#[macro_export] +macro_rules! cfg_has_atomic_16 { + ($($tt:tt)*) => { $($tt)* }; +} +#[macro_export] +macro_rules! cfg_no_atomic_16 { + ($($tt:tt)*) => {}; +} +#[macro_export] +macro_rules! cfg_has_atomic_32 { + ($($tt:tt)*) => { $($tt)* }; +} +#[macro_export] +macro_rules! cfg_no_atomic_32 { + ($($tt:tt)*) => {}; +} +// TODO: V8+ with 64-bit g/o reg +#[cfg(target_arch = "sparc")] +#[macro_export] +macro_rules! cfg_has_atomic_64 { + ($($tt:tt)*) => {}; +} +// TODO: V8+ with 64-bit g/o reg +#[cfg(target_arch = "sparc")] +#[macro_export] +macro_rules! cfg_no_atomic_64 { + ($($tt:tt)*) => { $($tt)* }; +} +#[cfg(target_arch = "sparc64")] +#[macro_export] +macro_rules! cfg_has_atomic_64 { + ($($tt:tt)*) => { $($tt)* }; +} +#[cfg(target_arch = "sparc64")] +#[macro_export] +macro_rules! cfg_no_atomic_64 { + ($($tt:tt)*) => {}; +} +#[macro_export] +macro_rules! cfg_has_atomic_128 { + ($($tt:tt)*) => {}; +} +#[macro_export] +macro_rules! cfg_no_atomic_128 { + ($($tt:tt)*) => { $($tt)* }; +} +#[macro_export] +macro_rules! cfg_has_atomic_cas { + ($($tt:tt)*) => { $($tt)* }; +} +#[macro_export] +macro_rules! cfg_no_atomic_cas { + ($($tt:tt)*) => {}; +} diff --git a/src/arch/mod.rs b/src/arch/mod.rs index 2953e638..1d31ff84 100644 --- a/src/arch/mod.rs +++ b/src/arch/mod.rs @@ -39,6 +39,16 @@ target_arch = "powerpc", target_arch = "powerpc64", target_arch = "s390x", + all( + target_arch = "sparc", + any( + target_feature = "leoncasa", + atomic_maybe_uninit_target_feature = "leoncasa", + target_feature = "v9", + atomic_maybe_uninit_target_feature = "v9", + ), + ), + target_arch = "sparc64", target_arch = "xtensa", ), atomic_maybe_uninit_unstable_asm_experimental_arch, @@ -108,6 +118,20 @@ mod riscv; #[cfg(atomic_maybe_uninit_unstable_asm_experimental_arch)] #[cfg_attr(atomic_maybe_uninit_s390x_no_reg_addr, path = "s390x_no_reg_addr.rs")] mod s390x; +#[cfg(any( + all( + target_arch = "sparc", + any( + target_feature = "leoncasa", + atomic_maybe_uninit_target_feature = "leoncasa", + target_feature = "v9", + atomic_maybe_uninit_target_feature = "v9", + ), + ), + target_arch = "sparc64", +))] +#[cfg(atomic_maybe_uninit_unstable_asm_experimental_arch)] +mod sparc; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] mod x86; #[cfg(target_arch = "xtensa")] diff --git a/src/arch/sparc.rs b/src/arch/sparc.rs new file mode 100644 index 00000000..ae430a43 --- /dev/null +++ b/src/arch/sparc.rs @@ -0,0 +1,453 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT + +/* +SPARC + +This architecture provides the following atomic instructions: + +- Load/Store Instructions + - V7-V9: {8,16,32}-bit + - V8+,V9: 64-bit (LDX, STX) + (Section 8.4.4 "Memory Models" of the SPARC Architecture Manual, Version 9) +- Compare-and-Swap Instructions (CAS) + - V8+,V9: {32,64}-bit + - V8 with CAS (e.g., LEON4): 32-bit + (Section 8.4.6 "Hardware Primitives for Mutual Exclusion" of the SPARC Architecture Manual, Version 9) +- SWAP Instructions (RMW) + - V8-V9: 32-bit (deprecated in V9) + (Section 8.4.6 "Hardware Primitives for Mutual Exclusion" and A.57 "Swap Register with Memory" of the SPARC Architecture Manual, Version 9) +- Load Store Unsigned Byte Instructions (RMW) + - V7-V9: 8-bit + (Section 8.4.6 "Hardware Primitives for Mutual Exclusion" of the SPARC Architecture Manual, Version 9) + +Refs: +- The SPARC Architecture Manual, Version 9 + The SPARC Architecture Manual, Version 8 + https://sparc.org/technical-documents +- The V8+ Technical Specification + https://temlib.org/pub/SparcStation/Standards/V8plus.pdf + +Generated asm: +- sparcv8+leoncasa https://godbolt.org/z/n96j1W87s +- sparcv8plus https://godbolt.org/z/qse81K1M5 +- sparc64 https://godbolt.org/z/PbxqToxj4 +*/ + +#[path = "cfgs/sparc.rs"] +mod cfgs; + +use core::{arch::asm, mem::MaybeUninit, sync::atomic::Ordering}; + +use crate::raw::{AtomicCompareExchange, AtomicLoad, AtomicStore, AtomicSwap}; + +#[cfg(any( + target_arch = "sparc64", + target_feature = "v9", + atomic_maybe_uninit_target_feature = "v9", +))] +macro_rules! cas { + ($size:tt, $rs1:tt, $rs2:tt, $rd:tt) => { + concat!("cas", $size, " ", $rs1, ", ", $rs2, ", ", $rd) + }; +} +#[cfg(any(target_feature = "leoncasa", atomic_maybe_uninit_target_feature = "leoncasa"))] +macro_rules! cas { + ("", $rs1:tt, $rs2:tt, $rd:tt) => { + // .p2align 4 is workaround for errata (GRLIB-TN-0011). + concat!(".p2align 4", "\n", "casa ", $rs1, " 10, ", $rs2, ", ", $rd) + }; +} + +// Bicc instructions are deprecated in V9. +#[cfg(any( + target_arch = "sparc64", + target_feature = "v9", + atomic_maybe_uninit_target_feature = "v9", +))] +macro_rules! bne { + ($cc:tt, $label:tt) => { + concat!("bne ", $cc, ", ", $label, "\n", "nop") + }; +} +#[cfg(not(any( + target_arch = "sparc64", + target_feature = "v9", + atomic_maybe_uninit_target_feature = "v9", +)))] +macro_rules! bne { + ("%icc", $label:tt) => { + concat!("bne ", $label, "\n", "nop") + }; +} + +// MOVcc instructions are unavailable in V8. +#[cfg(any( + target_arch = "sparc64", + target_feature = "v9", + atomic_maybe_uninit_target_feature = "v9", +))] +macro_rules! move_ { + ($cc:tt, $val:tt, $rd:tt) => { + concat!("move ", $cc, ", ", $val, ", ", $rd) + }; +} +#[cfg(not(any( + target_arch = "sparc64", + target_feature = "v9", + atomic_maybe_uninit_target_feature = "v9", +)))] +macro_rules! move_ { + ($cc:tt, $val:tt, $rd:tt) => { + concat!(bne!($cc, "99f"), "\n", "mov ", $val, ", ", $rd, "\n", "99:") + }; +} + +// Workaround for errata (GRLIB-TN-0009, GRLIB-TN-0010). +// https://www.gaisler.com/index.php/information/app-tech-notes +#[cfg(not(any(target_feature = "leoncasa", atomic_maybe_uninit_target_feature = "leoncasa")))] +macro_rules! leon_nop { + () => { + "" + }; +} +#[cfg(any(target_feature = "leoncasa", atomic_maybe_uninit_target_feature = "leoncasa"))] +macro_rules! leon_nop { + () => { + "nop" + }; +} + +#[cfg(any( + target_arch = "sparc64", + target_feature = "v9", + atomic_maybe_uninit_target_feature = "v9", +))] +macro_rules! atomic_rmw { + ($op:ident, $order:ident) => { + match $order { + Ordering::Relaxed => $op!("", ""), + Ordering::Acquire => { + $op!("membar #LoadLoad | #StoreLoad | #LoadStore | #StoreStore", "") + } + Ordering::Release => { + $op!("", "membar #LoadLoad | #StoreLoad | #LoadStore | #StoreStore") + } + Ordering::AcqRel | Ordering::SeqCst => $op!( + "membar #LoadLoad | #StoreLoad | #LoadStore | #StoreStore", + "membar #LoadLoad | #StoreLoad | #LoadStore | #StoreStore" + ), + _ => unreachable!(), + } + }; +} +#[cfg(not(any( + target_arch = "sparc64", + target_feature = "v9", + atomic_maybe_uninit_target_feature = "v9", +)))] +macro_rules! atomic_rmw { + ($op:ident, $order:ident) => { + match $order { + Ordering::Relaxed => $op!("", ""), + Ordering::Acquire => $op!("stbar", ""), + Ordering::Release => $op!("", "stbar"), + Ordering::AcqRel | Ordering::SeqCst => $op!("stbar", "stbar"), + _ => unreachable!(), + } + }; +} + +#[rustfmt::skip] +macro_rules! atomic_load_store { + ($int_type:ident, $size:tt, $load_sign:tt) => { + impl AtomicLoad for $int_type { + #[inline] + unsafe fn atomic_load( + src: *const MaybeUninit, + order: Ordering, + ) -> MaybeUninit { + let out: MaybeUninit; + + // SAFETY: the caller must uphold the safety contract. + unsafe { + macro_rules! atomic_load { + ($acquire:tt) => { + asm!( + concat!("ld", $load_sign, $size, " [{src}], {out}"), // atomic { out = *src } + $acquire, // fence + src = in(reg) ptr_reg!(src), + out = lateout(reg) out, + options(nostack, preserves_flags), + ) + }; + } + #[cfg(any( + target_arch = "sparc64", + target_feature = "v9", + atomic_maybe_uninit_target_feature = "v9", + ))] + match order { + Ordering::Relaxed => atomic_load!(""), + Ordering::Acquire | Ordering::SeqCst => atomic_load!("membar #LoadLoad | #StoreLoad | #LoadStore | #StoreStore"), + _ => unreachable!(), + } + #[cfg(not(any( + target_arch = "sparc64", + target_feature = "v9", + atomic_maybe_uninit_target_feature = "v9", + )))] + match order { + Ordering::Relaxed => atomic_load!(""), + Ordering::Acquire | Ordering::SeqCst => atomic_load!("stbar"), + _ => unreachable!(), + } + } + out + } + } + impl AtomicStore for $int_type { + #[inline] + unsafe fn atomic_store( + dst: *mut MaybeUninit, + val: MaybeUninit, + order: Ordering, + ) { + // SAFETY: the caller must uphold the safety contract. + unsafe { + macro_rules! store { + ($acquire:tt, $release:tt) => { + asm!( + leon_nop!(), // Workaround for for errata (GRLIB-TN-0009). + $release, // fence + concat!("st", $size, " {val}, [{dst}]"), // atomic { *dst = val } + $acquire, // fence + leon_nop!(), // Workaround for for errata (GRLIB-TN-0009). + dst = in(reg) ptr_reg!(dst), + val = in(reg) val, + options(nostack, preserves_flags), + ) + }; + } + atomic_rmw!(store, order); + } + } + } + }; +} + +#[rustfmt::skip] +macro_rules! atomic { + ($int_type:ident, $size:tt, $cc:tt) => { + atomic_load_store!($int_type, $size, ""); + impl AtomicSwap for $int_type { + #[inline] + unsafe fn atomic_swap( + dst: *mut MaybeUninit, + val: MaybeUninit, + order: Ordering, + ) -> MaybeUninit { + let mut out: MaybeUninit; + + // SAFETY: the caller must uphold the safety contract. + unsafe { + macro_rules! swap { + ($acquire:tt, $release:tt) => { + asm!( + $release, // fence + concat!("ld", $size, " [{dst}], {out}"), // atomic { out = *dst } + "2:", // 'retry: + "mov {out}, {tmp}", // tmp = out + "mov {val}, {out}", // out = val + cas!($size, "[{dst}]", "{tmp}", "{out}"), // atomic { _x = *dst; if _x == tmp { *dst = out }; out = _x } + "cmp {out}, {tmp}", // if out == tmp { cc.Z = true } else { cc.Z = false } + bne!($cc, "2b"), // if !cc.Z { jump 'retry } + $acquire, // fence + dst = in(reg) ptr_reg!(dst), + val = in(reg) val, + out = out(reg) out, + tmp = out(reg) _, + // Do not use `preserves_flags` because CMP modifies the condition codes. + options(nostack), + ) + }; + } + atomic_rmw!(swap, order); + } + out + } + } + impl AtomicCompareExchange for $int_type { + #[inline] + unsafe fn atomic_compare_exchange( + dst: *mut MaybeUninit, + old: MaybeUninit, + new: MaybeUninit, + success: Ordering, + failure: Ordering, + ) -> (MaybeUninit, bool) { + let order = crate::utils::upgrade_success_ordering(success, failure); + let out: MaybeUninit; + + // SAFETY: the caller must uphold the safety contract. + unsafe { + let mut r: crate::utils::RegSize; + macro_rules! cmpxchg { + ($acquire:tt, $release:tt) => { + asm!( + leon_nop!(), // Workaround for errata (GRLIB-TN-0010). + $release, // fence + cas!($size, "[{dst}]", "{old}", "{out}"), // atomic { _x = *dst; if _x == old { *dst = out }; out = _x } + "cmp {out}, {old}", // if out == old { cc.Z = true } else { cc.Z = false } + "mov %g0, {r}", // r = 0 + move_!($cc, "1", "{r}"), // if cc.Z { r = 1 } + $acquire, // fence + dst = in(reg) ptr_reg!(dst), + old = in(reg) old, + out = inout(reg) new => out, + r = lateout(reg) r, + // Do not use `preserves_flags` because CMP modifies the condition codes. + options(nostack), + ) + }; + } + atomic_rmw!(cmpxchg, order); + crate::utils::assert_unchecked(r == 0 || r == 1); // may help remove extra test + (out, r != 0) + } + } + } + }; +} + +#[rustfmt::skip] +macro_rules! atomic_sub_word { + ($int_type:ident, $size:tt) => { + atomic_load_store!($int_type, $size, "u"); + impl AtomicSwap for $int_type { + #[inline] + unsafe fn atomic_swap( + dst: *mut MaybeUninit, + val: MaybeUninit, + order: Ordering, + ) -> MaybeUninit { + let (dst, shift, mask) = crate::utils::create_sub_word_mask_values(dst); + let mut out: MaybeUninit; + + // SAFETY: the caller must uphold the safety contract. + unsafe { + macro_rules! swap { + ($acquire:tt, $release:tt) => { + // Implement sub-word atomic operations using word-sized CAS loop. + // See also create_sub_word_mask_values. + asm!( + "sll {mask}, {shift}, {mask}", // mask <<= shift & 31 + "sll {val}, {shift}, {val}", // val <<= shift & 31 + $release, // fence + "ld [{dst}], {out}", // atomic { out = *dst } + "2:", // 'retry: + "mov {out}, {tmp}", // tmp = out + "andn {out}, {mask}, {out}", // out &= !mask + "or {out}, {val}, {out}", // out |= val + cas!("", "[{dst}]", "{tmp}", "{out}"), // atomic { _x = *dst; if _x == tmp { *dst = out }; out = _x } + "cmp {out}, {tmp}", // if out == tmp { cc.Z = true } else { cc.Z = false } + bne!("%icc", "2b"), // if !cc.Z { jump 'retry } + "srl {out}, {shift}, {out}", // out >>= shift & 31 + $acquire, // fence + dst = in(reg) ptr_reg!(dst), + val = inout(reg) crate::utils::ZeroExtend::zero_extend(val) => _, + out = out(reg) out, + shift = in(reg) shift, + mask = inout(reg) mask => _, + tmp = out(reg) _, + // Do not use `preserves_flags` because CMP modifies the condition codes. + options(nostack), + ) + }; + } + atomic_rmw!(swap, order); + } + out + } + } + impl AtomicCompareExchange for $int_type { + #[inline] + unsafe fn atomic_compare_exchange( + dst: *mut MaybeUninit, + old: MaybeUninit, + new: MaybeUninit, + success: Ordering, + failure: Ordering, + ) -> (MaybeUninit, bool) { + let order = crate::utils::upgrade_success_ordering(success, failure); + let (dst, shift, mask) = crate::utils::create_sub_word_mask_values(dst); + let mut out: MaybeUninit; + + // SAFETY: the caller must uphold the safety contract. + unsafe { + let mut r: crate::utils::RegSize; + macro_rules! cmpxchg { + ($acquire:tt, $release:tt) => { + // Implement sub-word atomic operations using word-sized CAS loop. + // See also create_sub_word_mask_values. + asm!( + "sll {mask}, {shift}, {mask}", // mask <<= shift & 31 + "sll {old}, {shift}, {old}", // old <<= shift & 31 + "sll {new}, {shift}, {new}", // new <<= shift & 31 + $release, // fence + "ld [{dst}], {out}", // atomic { out = *dst } + "2:", // 'retry: + "and {out}, {mask}, {tmp}", // tmp = out & mask + "cmp {old}, {tmp}", // if old == tmp { cc.Z = true } else { cc.Z = false } + bne!("%icc", "3f"), // if !cc.Z { jump 'cmp-fail } + "mov {out}, {tmp}", // tmp = out + "andn {out}, {mask}, {out}", // out &= !mask + "or {out}, {new}, {out}", // out |= new + cas!("", "[{dst}]", "{tmp}", "{out}"), // atomic { _x = *dst; if _x == tmp { *dst = out }; out = _x } + "cmp {out}, {tmp}", // if out == tmp { cc.Z = true } else { cc.Z = false } + bne!("%icc", "2b"), // if !cc.Z { jump 'retry } + "3:", // 'cmp-fail: + "mov %g0, {tmp}", // tmp = 0 + move_!("%icc", "1", "{tmp}"), // if cc.Z { tmp = 1 } + "srl {out}, {shift}, {out}", // out >>= shift & 31 + $acquire, // fence + dst = in(reg) ptr_reg!(dst), + old = inout(reg) crate::utils::ZeroExtend::zero_extend(old) => _, + new = inout(reg) crate::utils::ZeroExtend::zero_extend(new) => _, + out = out(reg) out, + shift = in(reg) shift, + mask = inout(reg) mask => _, + tmp = out(reg) r, + // Do not use `preserves_flags` because CMP modifies the condition codes. + options(nostack), + ) + }; + } + atomic_rmw!(cmpxchg, order); + crate::utils::assert_unchecked(r == 0 || r == 1); // may help remove extra test + (out, r != 0) + } + } + } + }; +} + +atomic_sub_word!(i8, "b"); +atomic_sub_word!(u8, "b"); +atomic_sub_word!(i16, "h"); +atomic_sub_word!(u16, "h"); +atomic!(i32, "", "%icc"); +atomic!(u32, "", "%icc"); +// TODO: V8+ with 64-bit g/o reg +#[cfg(target_arch = "sparc64")] +atomic!(i64, "x", "%xcc"); +// TODO: V8+ with 64-bit g/o reg +#[cfg(target_arch = "sparc64")] +atomic!(u64, "x", "%xcc"); +#[cfg(target_pointer_width = "32")] +atomic!(isize, "", "%icc"); +#[cfg(target_pointer_width = "32")] +atomic!(usize, "", "%icc"); +#[cfg(target_pointer_width = "64")] +atomic!(isize, "x", "%xcc"); +#[cfg(target_pointer_width = "64")] +atomic!(usize, "x", "%xcc"); diff --git a/src/lib.rs b/src/lib.rs index 3a86cc6b..d404c36f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ This crate provides a way to soundly perform such operations. ## Platform Support -Currently, x86, x86_64, Arm, AArch64, RISC-V, LoongArch64, MIPS32, MIPS64, PowerPC, s390x, MSP430, Arm64EC, AVR, Hexagon, M68k, and Xtensa are supported. +Currently, x86, x86_64, Arm, AArch64, RISC-V, LoongArch64, MIPS32, MIPS64, PowerPC, s390x, MSP430, Arm64EC, AVR, SPARC, Hexagon, M68k, and Xtensa are supported. | target_arch | primitives | load/store | swap/CAS | | -------------------------------- | --------------------------------------------------- |:----------:|:--------:| @@ -34,6 +34,8 @@ Currently, x86, x86_64, Arm, AArch64, RISC-V, LoongArch64, MIPS32, MIPS64, Power | arm64ec \[4] | isize,usize,i8,u8,i16,u16,i32,u32,i64,u64,i128,u128 | ✓ | ✓ | | msp430 \[4] (experimental) | isize,usize,i8,u8,i16,u16 | ✓ | ✓ | | avr \[4] (experimental) | isize,usize,i8,u8,i16,u16 | ✓ | ✓ | +| sparc \[4] \[7] (experimental) | isize,usize,i8,u8,i16,u16,i32,u32 | ✓ | ✓ | +| sparc64 \[4] (experimental) | isize,usize,i8,u8,i16,u16,i32,u32,i64,u64 | ✓ | ✓ | | hexagon \[4] (experimental) | isize,usize,i8,u8,i16,u16,i32,u32,i64,u64 | ✓ | ✓ | | m68k \[4] (experimental) | isize,usize,i8,u8,i16,u16,i32,u32 | ✓ | ✓\[1] | | xtensa \[4] (experimental) | isize,usize,i8,u8,i16,u16,i32,u32 | ✓ | ✓\[1] | @@ -44,6 +46,7 @@ Currently, x86, x86_64, Arm, AArch64, RISC-V, LoongArch64, MIPS32, MIPS64, Power \[4] Requires nightly due to `#![feature(asm_experimental_arch)]`.
\[5] Requires cmpxchg16b target feature (enabled by default on Apple and Windows (except Windows 7) targets).
\[6] Requires target-cpu pwr8+ (powerpc64le is pwr8 by default).
+\[7] Requires CAS instruction support.
Feel free to submit an issue if your target is not supported yet. diff --git a/src/utils.rs b/src/utils.rs index edae658c..3e75ace3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -253,7 +253,7 @@ type RetInt = RegSize; #[allow(dead_code)] #[inline] pub(crate) fn create_sub_word_mask_values(ptr: *mut T) -> (*mut MinWord, RetInt, RetInt) { - // RISC-V, MIPS, LoongArch, Xtensa: shift amount of 32-bit shift instructions is 5 bits unsigned (0-31). + // RISC-V, MIPS, SPARC, LoongArch, Xtensa: shift amount of 32-bit shift instructions is 5 bits unsigned (0-31). // PowerPC, C-SKY: shift amount of 32-bit shift instructions is 6 bits unsigned (0-63) and shift amount 32-63 means "clear". // Arm: shift amount of 32-bit shift instructions is 8 bits unsigned (0-255). // Hexagon: shift amount of 32-bit shift instructions is 7 bits signed (-64-63) and negative shift amount means "reverse the direction of the shift". @@ -267,6 +267,8 @@ pub(crate) fn create_sub_word_mask_values(ptr: *mut T) -> (*mut MinWord, RetI target_arch = "riscv32", target_arch = "riscv64", target_arch = "s390x", + target_arch = "sparc", + target_arch = "sparc64", target_arch = "xtensa", )); let ptr_mask = mem::size_of::() - 1; diff --git a/tests/sparc/.cargo/config.toml b/tests/sparc/.cargo/config.toml new file mode 100644 index 00000000..5fc6fcfc --- /dev/null +++ b/tests/sparc/.cargo/config.toml @@ -0,0 +1,4 @@ +[target.sparc-unknown-none-elf] +linker = "sparc-gaisler-elf-gcc" +# Not in PATH (handled in tools/no-std.sh). +# runner = "tsim-leon3-test-runner.sh" diff --git a/tests/sparc/Cargo.toml b/tests/sparc/Cargo.toml new file mode 100644 index 00000000..d8c62fa7 --- /dev/null +++ b/tests/sparc/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "sparc-test" +version = "0.0.0" +edition = "2021" +publish = false + +[dependencies] +atomic-maybe-uninit = { path = "../.." } + +paste = "1" + +[workspace] +resolver = "2" + +[lints.rust] +rust_2018_idioms = "warn" +single_use_lifetimes = "warn" +# unsafe_op_in_unsafe_fn = "warn" # Set at crate-level instead since https://github.com/rust-lang/rust/pull/100081 is not available on MSRV diff --git a/tests/sparc/src/main.rs b/tests/sparc/src/main.rs new file mode 100644 index 00000000..b3cfba5e --- /dev/null +++ b/tests/sparc/src/main.rs @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#![no_main] +#![no_std] +#![warn(unsafe_op_in_unsafe_fn)] + +use core::{mem::MaybeUninit, sync::atomic::Ordering}; + +use atomic_maybe_uninit::*; + +macro_rules! print { + ($($tt:tt)*) => {{ + use core::fmt::Write as _; + let _ = write!(sim::Console, $($tt)*); + }}; +} +macro_rules! println { + ($($tt:tt)*) => {{ + use core::fmt::Write as _; + let _ = writeln!(sim::Console, $($tt)*); + }}; +} + +macro_rules! __test_atomic { + ($int_type:ident) => { + load_store(); + fn load_store() { + unsafe { + static VAR: AtomicMaybeUninit<$int_type> = + AtomicMaybeUninit::<$int_type>::const_new(MaybeUninit::new(10)); + for (load_order, store_order) in LOAD_ORDERINGS.into_iter().zip(STORE_ORDERINGS) { + assert_eq!(VAR.load(load_order).assume_init(), 10); + VAR.store(MaybeUninit::new(5), store_order); + assert_eq!(VAR.load(load_order).assume_init(), 5); + VAR.store(MaybeUninit::uninit(), store_order); + let _v = VAR.load(load_order); + VAR.store(MaybeUninit::new(10), store_order); + + let a = AtomicMaybeUninit::<$int_type>::new(MaybeUninit::new(1)); + assert_eq!(a.load(load_order).assume_init(), 1); + a.store(MaybeUninit::new(2), store_order); + assert_eq!(a.load(load_order).assume_init(), 2); + let a = AtomicMaybeUninit::<$int_type>::new(MaybeUninit::uninit()); + let _v = a.load(load_order); + a.store(MaybeUninit::new(2), store_order); + assert_eq!(a.load(load_order).assume_init(), 2); + a.store(MaybeUninit::uninit(), store_order); + let _v = a.load(load_order); + } + } + } + cfg_has_atomic_cas! { + swap(); + fn swap() { + unsafe { + for order in SWAP_ORDERINGS { + let a = AtomicMaybeUninit::<$int_type>::new(MaybeUninit::new(5)); + assert_eq!(a.swap(MaybeUninit::new(10), order).assume_init(), 5); + assert_eq!(a.swap(MaybeUninit::uninit(), order).assume_init(), 10); + let _v = a.swap(MaybeUninit::new(15), order); + let a = AtomicMaybeUninit::<$int_type>::new(MaybeUninit::uninit()); + let _v = a.swap(MaybeUninit::new(10), order); + assert_eq!(a.swap(MaybeUninit::uninit(), order).assume_init(), 10); + } + } + } + compare_exchange(); + fn compare_exchange() { + unsafe { + for (success, failure) in COMPARE_EXCHANGE_ORDERINGS { + let a = AtomicMaybeUninit::<$int_type>::new(MaybeUninit::new(5)); + assert_eq!( + a.compare_exchange( + MaybeUninit::new(5), + MaybeUninit::new(10), + success, + failure + ) + .unwrap() + .assume_init(), + 5 + ); + assert_eq!(a.load(Ordering::Relaxed).assume_init(), 10); + assert_eq!( + a.compare_exchange( + MaybeUninit::new(6), + MaybeUninit::new(12), + success, + failure + ) + .unwrap_err() + .assume_init(), + 10 + ); + assert_eq!(a.load(Ordering::Relaxed).assume_init(), 10); + } + } + } + compare_exchange_weak(); + fn compare_exchange_weak() { + unsafe { + for (success, failure) in COMPARE_EXCHANGE_ORDERINGS { + let a = AtomicMaybeUninit::<$int_type>::new(MaybeUninit::new(4)); + assert_eq!( + a.compare_exchange_weak( + MaybeUninit::new(6), + MaybeUninit::new(8), + success, + failure + ) + .unwrap_err() + .assume_init(), + 4 + ); + let mut old = a.load(Ordering::Relaxed); + loop { + let new = MaybeUninit::new(old.assume_init() * 2); + match a.compare_exchange_weak(old, new, success, failure) { + Ok(_) => break, + Err(x) => old = x, + } + } + assert_eq!(a.load(Ordering::Relaxed).assume_init(), 8); + } + } + } + fetch_update(); + fn fetch_update() { + unsafe { + for (success, failure) in COMPARE_EXCHANGE_ORDERINGS { + let a = AtomicMaybeUninit::<$int_type>::new(MaybeUninit::new(7)); + assert_eq!( + a.fetch_update(success, failure, |_| None).unwrap_err().assume_init(), + 7 + ); + assert_eq!( + a.fetch_update(success, failure, |x| Some(MaybeUninit::new( + x.assume_init() + 1 + ))) + .unwrap() + .assume_init(), + 7 + ); + assert_eq!( + a.fetch_update(success, failure, |x| Some(MaybeUninit::new( + x.assume_init() + 1 + ))) + .unwrap() + .assume_init(), + 8 + ); + assert_eq!(a.load(Ordering::Relaxed).assume_init(), 9); + } + } + } + } + }; +} + +#[no_mangle] +extern "C" fn main() -> i32 { + macro_rules! test_atomic { + ($int_type:ident) => { + paste::paste! { + fn []() { + __test_atomic!($int_type); + } + print!("test test_atomic_{} ... ", stringify!($int_type)); + [](); + println!("ok"); + } + }; + } + + cfg_has_atomic_cas! { + println!("target_has_cas: true"); + } + cfg_no_atomic_cas! { + println!("target_has_cas: false"); + } + test_atomic!(isize); + test_atomic!(usize); + test_atomic!(i8); + test_atomic!(u8); + test_atomic!(i16); + test_atomic!(u16); + test_atomic!(i32); + test_atomic!(u32); + + 0 +} + +const LOAD_ORDERINGS: [Ordering; 3] = [Ordering::Relaxed, Ordering::Acquire, Ordering::SeqCst]; +const STORE_ORDERINGS: [Ordering; 3] = [Ordering::Relaxed, Ordering::Release, Ordering::SeqCst]; +cfg_has_atomic_cas! { +const SWAP_ORDERINGS: [Ordering; 5] = + [Ordering::Relaxed, Ordering::Release, Ordering::Acquire, Ordering::AcqRel, Ordering::SeqCst]; +const COMPARE_EXCHANGE_ORDERINGS: [(Ordering, Ordering); 15] = [ + (Ordering::Relaxed, Ordering::Relaxed), + (Ordering::Relaxed, Ordering::Acquire), + (Ordering::Relaxed, Ordering::SeqCst), + (Ordering::Acquire, Ordering::Relaxed), + (Ordering::Acquire, Ordering::Acquire), + (Ordering::Acquire, Ordering::SeqCst), + (Ordering::Release, Ordering::Relaxed), + (Ordering::Release, Ordering::Acquire), + (Ordering::Release, Ordering::SeqCst), + (Ordering::AcqRel, Ordering::Relaxed), + (Ordering::AcqRel, Ordering::Acquire), + (Ordering::AcqRel, Ordering::SeqCst), + (Ordering::SeqCst, Ordering::Relaxed), + (Ordering::SeqCst, Ordering::Acquire), + (Ordering::SeqCst, Ordering::SeqCst), +]; +} + +#[inline(never)] +#[panic_handler] +fn panic(info: &core::panic::PanicInfo<'_>) -> ! { + println!("{info}"); + unsafe { sim::_exit(1) } +} + +mod sim { + // Refs: https://github.com/rust-lang/rust/blob/59ae5eba7e74d2cc7d8d611662e8b3a642d7093a/src/doc/rustc/src/platform-support/sparc-unknown-none-elf.md + + use core::fmt; + + extern "C" { + fn putchar(ch: i32); + pub fn _exit(code: i32) -> !; + } + + pub struct Console; + impl fmt::Write for Console { + fn write_str(&mut self, s: &str) -> fmt::Result { + for &b in s.as_bytes() { + unsafe { putchar(b as i32) } + } + Ok(()) + } + } +} diff --git a/tests/sparc/tsim-commands.txt b/tests/sparc/tsim-commands.txt new file mode 100644 index 00000000..58afc787 --- /dev/null +++ b/tests/sparc/tsim-commands.txt @@ -0,0 +1,2 @@ +run +quit diff --git a/tools/build.sh b/tools/build.sh index 094878fa..f61b9c5a 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -118,6 +118,12 @@ default_targets=( # rustc --print target-list | grep -E '^s390' s390x-unknown-linux-gnu + # sparc + # rustc --print target-list | grep -E '^sparc' + sparc-unknown-none-elf + sparc-unknown-linux-gnu + sparc64-unknown-linux-gnu + # msp430 # rustc --print target-list | grep -E '^msp430' msp430-none-elf @@ -190,7 +196,7 @@ if [[ "${rustc_version}" =~ nightly|dev ]]; then retry rustup ${pre_args[@]+"${pre_args[@]}"} component add rust-src &>/dev/null fi # We only run clippy on the recent nightly to avoid old clippy bugs. - if [[ "${rustc_minor_version}" -ge 84 ]]; then + if [[ "${rustc_minor_version}" -ge 84 ]] && [[ -z "${RUSTC:-}" ]]; then retry rustup ${pre_args[@]+"${pre_args[@]}"} component add clippy &>/dev/null base_args=(${pre_args[@]+"${pre_args[@]}"} hack clippy) base_rustflags+=' -Z crate-attr=feature(unqualified_local_imports) -W unqualified_local_imports' @@ -323,6 +329,11 @@ build() { RUSTFLAGS="${target_rustflags} -C target-cpu=z196" \ x_cargo "${args[@]}" "$@" ;; + sparc-unknown-none-elf) + CARGO_TARGET_DIR="${target_dir}/leon4" \ + RUSTFLAGS="${target_rustflags} -C target-cpu=leon4" \ + x_cargo "${args[@]}" "$@" + ;; esac } diff --git a/tools/no-std.sh b/tools/no-std.sh index 0365d801..600be516 100755 --- a/tools/no-std.sh +++ b/tools/no-std.sh @@ -38,6 +38,9 @@ default_targets=( riscv64imac-unknown-none-elf riscv64gc-unknown-none-elf + # sparc + sparc-unknown-none-elf + # avr avr-unknown-gnu-atmega2560 # custom target @@ -128,6 +131,12 @@ run() { local subcmd=run if [[ -z "${CI:-}" ]]; then case "${target}" in + sparc*) + if ! type -P tsim-leon3 >/dev/null; then + printf '%s\n' "no-std test for ${target} requires tsim-leon3 (switched to build-only)" + subcmd=build + fi + ;; avr*) if ! type -P simavr >/dev/null; then printf '%s\n' "no-std test for ${target} requires simavr (switched to build-only)" @@ -168,6 +177,20 @@ run() { linker=link.x target_rustflags+=" -C link-arg=-T${linker}" ;; + sparc*) + case "${commit_date}" in + 2023-08-23) + # no asm support + printf '%s\n' "target '${target}' is not supported on this version (skipped)" + return 0 + ;; + esac + test_dir=tests/sparc + runner="$(pwd)/tools/tsim-leon3-test-runner.sh" + export "CARGO_TARGET_${target_upper}_RUNNER"="${runner}" + # Refs: https://github.com/ferrous-systems/sparc-experiments/blob/ff502602ffe57a0ac03a461563f8d84870b475a0/sparc-demo-rust/.cargo/config.toml + target_rustflags+=" -C target-cpu=gr712rc -C link-arg=-mcpu=leon3 -C link-arg=-qbsp=leon3" + ;; avr*) test_dir=tests/avr ;; diff --git a/tools/tsim-leon3-test-runner.sh b/tools/tsim-leon3-test-runner.sh new file mode 100755 index 00000000..e06907c7 --- /dev/null +++ b/tools/tsim-leon3-test-runner.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: Apache-2.0 OR MIT +set -CeEuo pipefail +IFS=$'\n\t' + +bin="$1" +stdout='.tsim-leon3.stdout' + +rm -f -- ./"${stdout}" +touch -- ./"${stdout}" + +tail -s0 -f "${stdout}" & +tail_pid=$! + +# There is no way to exit tsim-leon3 with non-zero when failed. +tsim-leon3 -c tsim-commands.txt "${bin}" &>>"${stdout}" & +tsim_pid=$! + +# shellcheck disable=2317 # used in trap +cleanup() { + kill "${tsim_pid}" + kill "${tail_pid}" + rm -- "${stdout}" + exit "${code}" +} + +code=1 +trap -- 'printf >&2 "%s\n" "${0##*/}: trapped SIGINT"; cleanup' SIGINT + +wait "${tsim_pid}" + +if grep -Fq 'panicked' "${stdout}"; then + code=101 +else + code=0 +fi + +rm -- "${stdout}" +exit "${code}"