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}"