Skip to content

Commit 6f63126

Browse files
committed
riscv64: Support 128-bit atomics (Zacas extension)
1 parent 5dc8f4f commit 6f63126

38 files changed

+3320
-18
lines changed

.github/.cspell/project-dictionary.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ adde
33
alcgr
44
algr
55
allnoconfig
6+
amocas
67
aosp
78
aqrl
89
armasm
@@ -181,4 +182,5 @@ xsave
181182
xsub
182183
zaamo
183184
zabha
185+
zacas
184186
Zhaoxin

.github/workflows/ci.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ jobs:
232232
target: riscv32gc-unknown-linux-gnu
233233
- rust: '1.59'
234234
target: riscv64gc-unknown-linux-gnu
235+
- rust: '1.73' # LLVM 17 (oldest version we can use experimental-zacas on this target)
236+
target: riscv64gc-unknown-linux-gnu
235237
- rust: stable
236238
target: riscv64gc-unknown-linux-gnu
237239
- rust: nightly
@@ -355,6 +357,13 @@ jobs:
355357
RUSTDOCFLAGS: ${{ env.RUSTDOCFLAGS }} -C target-cpu=pwr8
356358
RUSTFLAGS: ${{ env.RUSTFLAGS }} -C target-cpu=pwr8
357359
if: startsWith(matrix.target, 'powerpc64-')
360+
# riscv64 +experimental-zacas
361+
- run: tools/test.sh -vv --tests ${TARGET:-} ${BUILD_STD:-} ${RELEASE:-}
362+
env:
363+
RUSTDOCFLAGS: ${{ env.RUSTDOCFLAGS }} -C target-feature=+experimental-zacas
364+
RUSTFLAGS: ${{ env.RUSTFLAGS }} -C target-feature=+experimental-zacas
365+
# TODO: cranelift doesn't support cfg(target_feature): https://github.com/rust-lang/rustc_codegen_cranelift/issues/1400
366+
if: startsWith(matrix.target, 'riscv64') && !contains(matrix.flags, 'codegen-backend=cranelift')
358367
# s390x z196 (arch9)
359368
- run: tools/test.sh -vv --tests ${TARGET:-} ${BUILD_STD:-} ${RELEASE:-}
360369
env:

build.rs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ fn main() {
4747

4848
if version.minor >= 80 {
4949
println!(
50-
r#"cargo:rustc-check-cfg=cfg(target_feature,values("zaamo","zabha","quadword-atomics","fast-serialization","load-store-on-cond","distinct-ops","miscellaneous-extensions-3"))"#
50+
r#"cargo:rustc-check-cfg=cfg(target_feature,values("zaamo","zabha","experimental-zacas","quadword-atomics","fast-serialization","load-store-on-cond","distinct-ops","miscellaneous-extensions-3"))"#
5151
);
5252

5353
// Custom cfgs set by build script. Not public API.
@@ -58,7 +58,7 @@ fn main() {
5858
// TODO: handle multi-line target_feature_fallback
5959
// 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/'
6060
println!(
61-
r#"cargo:rustc-check-cfg=cfg(portable_atomic_target_feature,values("cmpxchg16b","distinct-ops","fast-serialization","load-store-on-cond","lse","lse128","lse2","mclass","miscellaneous-extensions-3","quadword-atomics","rcpc3","v6","zaamo","zabha"))"#
61+
r#"cargo:rustc-check-cfg=cfg(portable_atomic_target_feature,values("cmpxchg16b","distinct-ops","experimental-zacas","fast-serialization","load-store-on-cond","lse","lse128","lse2","mclass","miscellaneous-extensions-3","quadword-atomics","rcpc3","v6","zaamo","zabha"))"#
6262
);
6363
}
6464

@@ -311,15 +311,22 @@ fn main() {
311311
}
312312
}
313313
"riscv32" | "riscv64" => {
314-
// As of rustc 1.80, target_feature "zaamo"/"zabha" is not available on rustc side:
314+
// As of rustc 1.80, target_feature "zaamo"/"zabha"/"zacas" is not available on rustc side:
315315
// https://github.com/rust-lang/rust/blob/1.80.0/compiler/rustc_target/src/target_features.rs#L273
316-
// zabha implies zaamo in GCC, but do not in LLVM (but enabling it without zaamo is not allowed).
317-
// https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0-rc3/llvm/lib/TargetParser/RISCVISAInfo.cpp#L776-L778
318-
// https://github.com/gcc-mirror/gcc/blob/08693e29ec186fd7941d0b73d4d466388971fe2f/gcc/config/riscv/arch-canonicalize#L45
319-
if version.llvm >= 19 {
320-
// amo*.{b,h}
321-
// available since 19 https://github.com/llvm/llvm-project/commit/89f87c387627150d342722b79c78cea2311cddf7 / https://github.com/llvm/llvm-project/commit/6b7444964a8d028989beee554a1f5c61d16a1cac
322-
target_feature_fallback("zabha", false);
316+
// zacas and zabha imply zaamo in GCC, but do not in LLVM (but enabling them without zaamo is not allowed).
317+
// https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0-rc3/llvm/lib/TargetParser/RISCVISAInfo.cpp#L772-L778
318+
// https://github.com/gcc-mirror/gcc/blob/08693e29ec186fd7941d0b73d4d466388971fe2f/gcc/config/riscv/arch-canonicalize#L45-L46
319+
if version.llvm >= 17 {
320+
// amocas.{w,d,q} (amocas.{b,h} if zabha is also available)
321+
// available as experimental since 17 https://github.com/llvm/llvm-project/commit/29f630a1ddcbb03caa31b5002f0cbc105ff3a869
322+
// attempted to make non-experimental in 19 https://github.com/llvm/llvm-project/commit/95aab69c109adf29e183090c25dc95c773215746
323+
// but reverted in https://github.com/llvm/llvm-project/commit/70e7d26e560173c8b9db4c75ab4a3004cd5f021a
324+
target_feature_fallback("experimental-zacas", false);
325+
if version.llvm >= 19 {
326+
// amo*.{b,h}
327+
// available since 19 https://github.com/llvm/llvm-project/commit/89f87c387627150d342722b79c78cea2311cddf7 / https://github.com/llvm/llvm-project/commit/6b7444964a8d028989beee554a1f5c61d16a1cac
328+
target_feature_fallback("zabha", false);
329+
}
323330
}
324331
// amo*.{w,d}
325332
target_feature_fallback("zaamo", false);

src/cfgs.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,35 @@ mod atomic_64_macros {
241241
),
242242
),
243243
),
244+
all(
245+
target_arch = "riscv64",
246+
not(portable_atomic_no_asm),
247+
any(
248+
target_feature = "experimental-zacas",
249+
portable_atomic_target_feature = "experimental-zacas",
250+
// TODO(riscv64)
251+
// all(
252+
// feature = "fallback",
253+
// not(portable_atomic_no_outline_atomics),
254+
// any(test, portable_atomic_outline_atomics), // TODO(riscv64): currently disabled by default
255+
// any(
256+
// all(
257+
// target_os = "linux",
258+
// any(
259+
// target_env = "gnu",
260+
// all(
261+
// any(target_env = "musl", target_env = "ohos"),
262+
// not(target_feature = "crt-static"),
263+
// ),
264+
// portable_atomic_outline_atomics,
265+
// ),
266+
// ),
267+
// target_os = "android",
268+
// ),
269+
// not(any(miri, portable_atomic_sanitize_thread)),
270+
// ),
271+
),
272+
),
244273
all(
245274
target_arch = "powerpc64",
246275
portable_atomic_unstable_asm_experimental_arch,
@@ -331,6 +360,35 @@ mod atomic_128_macros {
331360
),
332361
),
333362
),
363+
all(
364+
target_arch = "riscv64",
365+
not(portable_atomic_no_asm),
366+
any(
367+
target_feature = "experimental-zacas",
368+
portable_atomic_target_feature = "experimental-zacas",
369+
// TODO(riscv64)
370+
// all(
371+
// feature = "fallback",
372+
// not(portable_atomic_no_outline_atomics),
373+
// any(test, portable_atomic_outline_atomics), // TODO(riscv64): currently disabled by default
374+
// any(
375+
// all(
376+
// target_os = "linux",
377+
// any(
378+
// target_env = "gnu",
379+
// all(
380+
// any(target_env = "musl", target_env = "ohos"),
381+
// not(target_feature = "crt-static"),
382+
// ),
383+
// portable_atomic_outline_atomics,
384+
// ),
385+
// ),
386+
// target_os = "android",
387+
// ),
388+
// not(any(miri, portable_atomic_sanitize_thread)),
389+
// ),
390+
),
391+
),
334392
all(
335393
target_arch = "powerpc64",
336394
portable_atomic_unstable_asm_experimental_arch,

src/imp/atomic128/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Here is the table of targets that support 128-bit atomics and the instructions u
88
| ----------- | ---- | ----- | --- | --- | ---- |
99
| x86_64 | cmpxchg16b or vmovdqa | cmpxchg16b or vmovdqa | cmpxchg16b | cmpxchg16b | cmpxchg16b target feature required. vmovdqa requires Intel, AMD, or Zhaoxin CPU with AVX. <br> Both compile-time and run-time detection are supported for cmpxchg16b. vmovdqa is currently run-time detection only. <br> Requires rustc 1.59+ |
1010
| aarch64 | ldxp/stxp or casp or ldp/ldiapp | ldxp/stxp or casp or stp/stilp/swpp | ldxp/stxp or casp | ldxp/stxp or casp/swpp/ldclrp/ldsetp | casp requires lse target feature, ldp/stp requires lse2 target feature, ldiapp/stilp requires lse2 and rcpc3 target features, swpp/ldclrp/ldsetp requires lse128 target feature. <br> Both compile-time and run-time detection are supported. <br> Requires rustc 1.59+ |
11+
| riscv64 | amocas.q | amocas.q | amocas.q | amocas.q | Requires experimental-zacas target feature. Currently compile-time detection only due to LLVM marking it as experimental. <br> Requires 1.73+ (LLVM 17+) |
1112
| powerpc64 | lq | stq | lqarx/stqcx. | lqarx/stqcx. | Requires target-cpu pwr8+ (powerpc64le is pwr8 by default). Both compile-time and run-time detection are supported (run-time detection is currently disabled by default). <br> Requires nightly |
1213
| s390x | lpq | stpq | cdsg | cdsg | Requires nightly |
1314

@@ -17,7 +18,7 @@ See [aarch64.rs](aarch64.rs) module-level comments for more details on the instr
1718

1819
## Comparison with core::intrinsics::atomic_\* (core::sync::atomic::Atomic{I,U}128)
1920

20-
This directory has target-specific implementations with inline assembly ([aarch64.rs](aarch64.rs), [x86_64.rs](x86_64.rs), [powerpc64.rs](powerpc64.rs), [s390x.rs](s390x.rs)) and an implementation without inline assembly ([intrinsics.rs](intrinsics.rs)). The latter currently always needs nightly compilers and is only used for Miri and ThreadSanitizer, which do not support inline assembly.
21+
This directory has target-specific implementations with inline assembly ([aarch64.rs](aarch64.rs), [x86_64.rs](x86_64.rs), [powerpc64.rs](powerpc64.rs), [riscv64.rs](riscv64.rs), [s390x.rs](s390x.rs)) and an implementation without inline assembly ([intrinsics.rs](intrinsics.rs)). The latter currently always needs nightly compilers and is only used for Miri and ThreadSanitizer, which do not support inline assembly.
2122

2223
Implementations with inline assembly generate assemblies almost equivalent to the `core::intrinsics::atomic_*` (used in `core::sync::atomic::Atomic{I,U}128`) for many operations, but some operations may or may not generate more efficient code. For example:
2324

@@ -45,6 +46,7 @@ Here is the table of targets that support run-time CPU feature detection and the
4546
| aarch64 | macos | sysctl | all | Currently only used in tests because FEAT_LSE and FEAT_LSE2 are always available at compile-time. |
4647
| aarch64 | windows | IsProcessorFeaturePresent | lse | Enabled by default |
4748
| aarch64 | fuchsia | zx_system_get_features | lse | Enabled by default |
49+
| riscv64 | linux | riscv_hwprobe | all | Currently only used in tests due to LLVM marking zacas as experimental |
4850
| powerpc64 | linux | getauxval | all | Disabled by default |
4951
| powerpc64 | freebsd | elf_aux_info | all | Disabled by default |
5052
| powerpc64 | openbsd | elf_aux_info | all | Disabled by default |

src/imp/atomic128/detect/common.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,24 @@ impl CpuInfo {
106106
}
107107
}
108108

109+
#[cfg(target_arch = "riscv64")]
110+
impl CpuInfo {
111+
// NB: update test_bit_flags test when adding new flag.
112+
const HAS_ZACAS: u32 = 1; // amocas.{w,d,q}
113+
114+
#[cfg(any(
115+
test,
116+
not(any(
117+
target_feature = "experimental-zacas",
118+
portable_atomic_target_feature = "experimental-zacas"
119+
))
120+
))]
121+
#[inline]
122+
pub(crate) fn has_zacas(self) -> bool {
123+
self.test(CpuInfo::HAS_ZACAS)
124+
}
125+
}
126+
109127
#[cfg(target_arch = "powerpc64")]
110128
impl CpuInfo {
111129
// NB: update test_bit_flags test when adding new flag.
@@ -125,7 +143,7 @@ impl CpuInfo {
125143
}
126144

127145
// core::ffi::c_* (except c_void) requires Rust 1.64, libc will soon require Rust 1.47
128-
#[cfg(any(target_arch = "aarch64", target_arch = "powerpc64"))]
146+
#[cfg(any(target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64"))]
129147
#[cfg(not(windows))]
130148
#[allow(dead_code, non_camel_case_types)]
131149
mod c_types {
@@ -219,6 +237,8 @@ mod tests_common {
219237
]);
220238
#[cfg(target_arch = "x86_64")]
221239
test_flags(&[CpuInfo::INIT, CpuInfo::HAS_CMPXCHG16B, CpuInfo::HAS_VMOVDQA_ATOMIC]);
240+
#[cfg(target_arch = "riscv64")]
241+
test_flags(&[CpuInfo::INIT, CpuInfo::HAS_ZACAS]);
222242
#[cfg(target_arch = "powerpc64")]
223243
test_flags(&[CpuInfo::INIT, CpuInfo::HAS_QUADWORD_ATOMICS]);
224244
}
@@ -268,6 +288,19 @@ mod tests_common {
268288
)),
269289
);
270290
}
291+
#[cfg(target_arch = "riscv64")]
292+
{
293+
features.push_str("run-time:\n");
294+
print_feature!("zacas", detect().test(CpuInfo::HAS_ZACAS));
295+
features.push_str("compile-time:\n");
296+
print_feature!(
297+
"zacas",
298+
cfg!(any(
299+
target_feature = "experimental-zacas",
300+
portable_atomic_target_feature = "experimental-zacas"
301+
)),
302+
);
303+
}
271304
#[cfg(target_arch = "powerpc64")]
272305
{
273306
features.push_str("run-time:\n");
@@ -356,6 +389,16 @@ mod tests_common {
356389
}
357390
}
358391
}
392+
#[cfg(target_arch = "riscv64")]
393+
#[test]
394+
#[cfg_attr(portable_atomic_test_outline_atomics_detect_false, ignore)]
395+
fn test_detect() {
396+
if detect().has_zacas() {
397+
assert!(detect().test(CpuInfo::HAS_ZACAS));
398+
} else {
399+
assert!(!detect().test(CpuInfo::HAS_ZACAS));
400+
}
401+
}
359402
#[cfg(target_arch = "powerpc64")]
360403
#[test]
361404
#[cfg_attr(portable_atomic_test_outline_atomics_detect_false, ignore)]
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// SPDX-License-Identifier: Apache-2.0 OR MIT
2+
3+
// Run-time CPU feature detection on riscv64 Linux/Android by using riscv_hwprobe.
4+
//
5+
// On RISC-V, detection using auxv only supports single-character extensions.
6+
//
7+
// Refs:
8+
// - https://github.com/torvalds/linux/blob/v6.10/Documentation/arch/riscv/hwprobe.rst
9+
// - https://github.com/golang/sys/commit/3283fc3f6160baf63bec24fbaa24e094e9ff6daf
10+
11+
include!("common.rs");
12+
13+
// core::ffi::c_* (except c_void) requires Rust 1.64, libc will soon require Rust 1.47
14+
#[allow(non_camel_case_types, non_upper_case_globals)]
15+
mod ffi {
16+
pub(crate) use super::c_types::c_long;
17+
18+
extern "C" {
19+
// https://man7.org/linux/man-pages/man2/syscall.2.html
20+
// https://github.com/rust-lang/libc/blob/0.2.139/src/unix/linux_like/android/mod.rs#L3215
21+
// https://github.com/rust-lang/libc/blob/0.2.139/src/unix/linux_like/linux/mod.rs#L4080
22+
pub(crate) fn syscall(number: c_long, ...) -> c_long;
23+
}
24+
25+
// https://github.com/torvalds/linux/blob/v6.10/arch/riscv/include/uapi/asm/hwprobe.h
26+
#[derive(Copy, Clone)]
27+
#[repr(C)]
28+
pub(crate) struct riscv_hwprobe {
29+
pub(crate) key: i64,
30+
pub(crate) value: u64,
31+
}
32+
33+
pub(crate) const __NR_riscv_hwprobe: c_long = 258;
34+
35+
// https://github.com/torvalds/linux/blob/v6.10/arch/riscv/include/uapi/asm/hwprobe.h
36+
pub(crate) const RISCV_HWPROBE_KEY_IMA_EXT_0: i64 = 4;
37+
pub(crate) const RISCV_HWPROBE_EXT_ZACAS: u64 = 1 << 34;
38+
}
39+
40+
fn riscv_hwprobe(out: &mut ffi::riscv_hwprobe, flags: usize) -> bool {
41+
// SAFETY: We've passed the valid pointer and length.
42+
unsafe {
43+
ffi::syscall(
44+
ffi::__NR_riscv_hwprobe,
45+
out as *mut ffi::riscv_hwprobe,
46+
1_usize,
47+
0_usize,
48+
0_usize,
49+
flags,
50+
0_usize,
51+
) == 0
52+
}
53+
}
54+
55+
#[cold]
56+
fn _detect(info: &mut CpuInfo) {
57+
let mut out = ffi::riscv_hwprobe { key: ffi::RISCV_HWPROBE_KEY_IMA_EXT_0, value: 0 };
58+
if riscv_hwprobe(&mut out, 0) {
59+
if out.key != -1 {
60+
if out.value & ffi::RISCV_HWPROBE_EXT_ZACAS != 0 {
61+
info.set(CpuInfo::HAS_ZACAS);
62+
}
63+
}
64+
}
65+
}
66+
67+
#[allow(
68+
clippy::alloc_instead_of_core,
69+
clippy::std_instead_of_alloc,
70+
clippy::std_instead_of_core,
71+
clippy::undocumented_unsafe_blocks,
72+
clippy::wildcard_imports
73+
)]
74+
#[cfg(test)]
75+
mod tests {
76+
use super::*;
77+
78+
// Static assertions for FFI bindings.
79+
// This checks that FFI bindings defined in this crate, FFI bindings defined
80+
// in libc, and FFI bindings generated for the platform's latest header file
81+
// using bindgen have compatible signatures (or the same values if constants).
82+
// Since this is static assertion, we can detect problems with
83+
// `cargo check --tests --target <target>` run in CI (via TESTS=1 build.sh)
84+
// without actually running tests on these platforms.
85+
// See also tools/codegen/src/ffi.rs.
86+
// TODO(codegen): auto-generate this test
87+
#[allow(
88+
clippy::cast_possible_wrap,
89+
clippy::cast_sign_loss,
90+
clippy::no_effect_underscore_binding
91+
)]
92+
const _: fn() = || {
93+
use test_helper::sys;
94+
// TODO: syscall,riscv_hwprobe
95+
// static_assert!(ffi::__NR_riscv_hwprobe == libc::__NR_riscv_hwprobe);
96+
static_assert!(ffi::__NR_riscv_hwprobe == sys::__NR_riscv_hwprobe as ffi::c_long);
97+
// static_assert!(ffi::RISCV_HWPROBE_KEY_IMA_EXT_0 == libc::RISCV_HWPROBE_KEY_IMA_EXT_0);
98+
static_assert!(ffi::RISCV_HWPROBE_KEY_IMA_EXT_0 == sys::RISCV_HWPROBE_KEY_IMA_EXT_0 as i64);
99+
// static_assert!(ffi::RISCV_HWPROBE_EXT_ZACAS == libc::RISCV_HWPROBE_EXT_ZACAS);
100+
static_assert!(ffi::RISCV_HWPROBE_EXT_ZACAS == sys::RISCV_HWPROBE_EXT_ZACAS);
101+
};
102+
}

src/imp/atomic128/macros.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,12 @@ macro_rules! atomic128 {
264264
};
265265
}
266266

267-
#[cfg(any(target_arch = "powerpc64", target_arch = "s390x", target_arch = "x86_64"))]
267+
#[cfg(any(
268+
target_arch = "powerpc64",
269+
target_arch = "riscv64",
270+
target_arch = "s390x",
271+
target_arch = "x86_64",
272+
))]
268273
#[allow(unused_macros)] // also used by intrinsics.rs
269274
macro_rules! atomic_rmw_by_atomic_update {
270275
() => {

0 commit comments

Comments
 (0)