Skip to content

Commit 6515a6b

Browse files
committed
[WIP] cpufeatures: aarch64 support (Linux and macOS/M4)
Adds preliminary support for runtime feature detection on `aarch64` targets, presently restricted to the following set of target features which are present on both `aarch64-unknown-linux-gnu` and `aarch64-apple-darwin` targets: - `aes`: AES support - `sha2`: SHA1 and SHA256 support - `sha3`: SHA512 and SHA3 support
1 parent 23a677e commit 6515a6b

File tree

8 files changed

+424
-127
lines changed

8 files changed

+424
-127
lines changed

.github/workflows/cpufeatures.yml

+86-9
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,95 @@ env:
1717
RUSTFLAGS: "-Dwarnings"
1818

1919
jobs:
20-
test:
20+
# Linux tests
21+
linux:
22+
strategy:
23+
matrix:
24+
include:
25+
# 32-bit Linux/x86
26+
- target: i686-unknown-linux-gnu
27+
rust: 1.40.0 # MSRV
28+
deps: sudo apt update && sudo apt install gcc-multilib
29+
- target: i686-unknown-linux-gnu
30+
rust: stable
31+
deps: sudo apt update && sudo apt install gcc-multilib
32+
33+
# 64-bit Linux/x86_64
34+
- target: x86_64-unknown-linux-gnu
35+
rust: 1.40.0 # MSRV
36+
- target: x86_64-unknown-linux-gnu
37+
rust: stable
2138
runs-on: ubuntu-latest
39+
steps:
40+
- uses: actions/checkout@v2
41+
- uses: actions-rs/toolchain@v1
42+
with:
43+
toolchain: ${{ matrix.rust }}
44+
target: ${{ matrix.target }}
45+
override: true
46+
profile: minimal
47+
- run: ${{ matrix.deps }}
48+
- run: cargo test --target ${{ matrix.target }} --release
49+
50+
# macOS tests
51+
macos:
2252
strategy:
2353
matrix:
24-
rust:
54+
toolchain:
2555
- 1.40.0 # MSRV
2656
- stable
57+
runs-on: macos-latest
58+
steps:
59+
- uses: actions/checkout@v2
60+
- uses: actions-rs/toolchain@v1
61+
with:
62+
profile: minimal
63+
toolchain: ${{ matrix.toolchain }}
64+
target: x86_64-apple-darwin
65+
override: true
66+
- run: cargo test --release
67+
68+
# Windows tests
69+
windows:
70+
strategy:
71+
matrix:
72+
include:
73+
# 64-bit Windows (GNU)
74+
# TODO(tarcieri): try re-enabling this when we bump MSRV
75+
#- target: x86_64-pc-windows-gnu
76+
# toolchain: 1.40.0 # MSRV
77+
- target: x86_64-pc-windows-gnu
78+
toolchain: stable
79+
runs-on: windows-latest
80+
steps:
81+
- uses: actions/checkout@v2
82+
- uses: actions-rs/toolchain@v1
83+
with:
84+
profile: minimal
85+
toolchain: ${{ matrix.toolchain }}
86+
target: ${{ matrix.target }}
87+
override: true
88+
- run: cargo test --target ${{ matrix.target }} --release
89+
90+
# Cross-compiled tests
91+
cross:
92+
strategy:
93+
matrix:
94+
include:
95+
# ARM64
96+
# TODO(tarcieri): try re-enabling this when we bump MSRV
97+
#- target: aarch64-unknown-linux-gnu
98+
# rust: 1.40.0 # MSRV
99+
- target: aarch64-unknown-linux-gnu
100+
rust: stable
101+
runs-on: ubuntu-latest
27102
steps:
28-
- uses: actions/checkout@v1
29-
- uses: actions-rs/toolchain@v1
30-
with:
31-
profile: minimal
32-
toolchain: ${{ matrix.rust }}
33-
override: true
34-
- run: cargo test --release
103+
- uses: actions/checkout@v2
104+
- uses: actions-rs/toolchain@v1
105+
with:
106+
toolchain: ${{ matrix.rust }}
107+
target: ${{ matrix.target }}
108+
override: true
109+
profile: minimal
110+
- run: cargo install cross
111+
- run: cross test --target ${{ matrix.target }} --release

Cargo.lock

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cpufeatures/Cargo.toml

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
[package]
22
name = "cpufeatures"
33
version = "0.1.0" # Also update html_root_url in lib.rs when bumping this
4+
description = """
5+
Lightweight and efficient no-std compatible alternative to the
6+
is_x86_feature_detected! macro
7+
"""
48
authors = ["RustCrypto Developers"]
59
license = "MIT OR Apache-2.0"
6-
description = "Lightweight and efficient no-std compatible alternative to the is_x86_feature_detected macro"
710
documentation = "https://docs.rs/cpufeatures"
811
repository = "https://github.com/RustCrypto/utils"
912
keywords = ["cpuid", "target-feature"]
1013
categories = ["no-std"]
1114
edition = "2018"
1215
readme = "README.md"
16+
17+
[target.aarch64-apple-darwin.dependencies]
18+
libc = "0.2"
19+
20+
[target.'cfg(all(target_arch = "aarch64", target_os = "linux"))'.dependencies]
21+
libc = "0.2"

cpufeatures/src/aarch64.rs

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/// ARM64 CPU feature detection support.
2+
///
3+
/// Unfortunately ARM instructions to detect CPU features cannot be called from
4+
/// unprivileged userspace code, so this implementation relies on OS-specific
5+
/// APIs for feature detection.
6+
7+
/// Create module with CPU feature detection code.
8+
#[macro_export]
9+
macro_rules! new {
10+
($mod_name:ident, $($tf:tt),+ $(,)? ) => {
11+
mod $mod_name {
12+
use core::sync::atomic::{AtomicU8, Ordering::Relaxed};
13+
14+
const UNINIT: u8 = u8::max_value();
15+
static STORAGE: AtomicU8 = AtomicU8::new(UNINIT);
16+
17+
/// Initialization token
18+
#[derive(Copy, Clone, Debug)]
19+
pub struct InitToken(());
20+
21+
impl InitToken {
22+
/// Get initialized value
23+
#[inline(always)]
24+
pub fn get(&self) -> bool {
25+
#[cfg(not(all($(target_feature=$tf, )*)))]
26+
let res = STORAGE.load(Relaxed) == 1;
27+
#[cfg(all($(target_feature=$tf, )*))]
28+
let res = true;
29+
res
30+
}
31+
}
32+
33+
/// Initialize underlying storage if needed and get
34+
/// stored value and initialization token.
35+
#[inline]
36+
pub fn init_get() -> (InitToken, bool) {
37+
#[cfg(not(all($(target_feature=$tf, )*)))]
38+
let res = {
39+
// Relaxed ordering is fine, as we only have a single atomic variable.
40+
let val = STORAGE.load(Relaxed);
41+
if val == UNINIT {
42+
let res = {
43+
#[cfg(target_os = "linux")]
44+
{
45+
let hwcaps = unsafe { libc::getauxval(libc::AT_HWCAP) };
46+
$(cpufeatures::check!(hwcaps, $tf) & )+ true
47+
}
48+
49+
#[cfg(target_os = "macos")]
50+
{
51+
$(cpufeatures::check!($tf) & )+ true
52+
}
53+
};
54+
55+
STORAGE.store(res as u8, Relaxed);
56+
res
57+
} else {
58+
val == 1
59+
}
60+
};
61+
#[cfg(all($(target_feature=$tf, )*))]
62+
let res = true;
63+
64+
(InitToken(()), res)
65+
}
66+
67+
/// Initialize underlying storage if needed and get
68+
/// initialization token.
69+
#[inline]
70+
pub fn init() -> InitToken {
71+
init_get().0
72+
}
73+
74+
/// Initialize underlying storage if needed and get
75+
/// stored value.
76+
#[inline]
77+
pub fn get() -> bool {
78+
init_get().1
79+
}
80+
}
81+
};
82+
}
83+
84+
#[cfg(target_os = "linux")]
85+
macro_rules! expand_check_macro {
86+
($(($name:tt, $hwcap:expr)),* $(,)?) => {
87+
#[macro_export]
88+
#[doc(hidden)]
89+
macro_rules! check {
90+
$(
91+
($hwcaps:expr, $name) => { (($hwcaps & libc::$hwcap) != 0) };
92+
)*
93+
}
94+
};
95+
}
96+
97+
#[cfg(target_os = "linux")]
98+
expand_check_macro! {
99+
("aes", HWCAP_AES), // Enable AES support.
100+
("sha2", HWCAP_SHA2), // Enable SHA1 and SHA256 support.
101+
("sha3", HWCAP_SHA3), // Enable SHA512 and SHA3 support.
102+
}
103+
104+
// TODO(tarcieri): extract this into a function?
105+
#[cfg(target_os = "macos")]
106+
#[macro_export]
107+
#[doc(hidden)]
108+
macro_rules! sysctlbyname {
109+
// WARNING: `$name` MUST be a byte slice terminated by `\0`
110+
($name:expr) => {{
111+
let mut value: u32 = 0;
112+
let mut size = core::mem::size_of::<u32>();
113+
114+
let rc = unsafe {
115+
libc::sysctlbyname(
116+
$name.as_ptr() as *const i8,
117+
&mut value as *mut _ as *mut libc::c_void,
118+
&mut size,
119+
core::ptr::null_mut(),
120+
0,
121+
)
122+
};
123+
124+
assert_eq!(rc, 0, "sysctlbyname returned error code: {}", rc);
125+
value != 0
126+
}};
127+
}
128+
129+
#[cfg(target_os = "macos")]
130+
#[macro_export]
131+
#[doc(hidden)]
132+
macro_rules! check {
133+
("aes") => {
134+
true
135+
};
136+
("sha2") => {
137+
true
138+
};
139+
("sha3") => {
140+
cpufeatures::sysctlbyname!(b"hw.optional.armv8_2_sha3\0")
141+
};
142+
}

0 commit comments

Comments
 (0)