Skip to content

Commit 16e4ecf

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 16e4ecf

File tree

8 files changed

+420
-127
lines changed

8 files changed

+420
-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

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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+
#[cfg(target_os = "linux")]
43+
let res = {
44+
let hwcaps = unsafe { libc::getauxval(libc::AT_HWCAP) };
45+
$(cpufeatures::check!(hwcaps, $tf) & )+ true
46+
}
47+
48+
#[cfg(target_os = "macos")]
49+
let res = $(cpufeatures::check!($tf) & )+ true;
50+
51+
STORAGE.store(res as u8, Relaxed);
52+
res
53+
} else {
54+
val == 1
55+
}
56+
};
57+
#[cfg(all($(target_feature=$tf, )*))]
58+
let res = true;
59+
60+
(InitToken(()), res)
61+
}
62+
63+
/// Initialize underlying storage if needed and get
64+
/// initialization token.
65+
#[inline]
66+
pub fn init() -> InitToken {
67+
init_get().0
68+
}
69+
70+
/// Initialize underlying storage if needed and get
71+
/// stored value.
72+
#[inline]
73+
pub fn get() -> bool {
74+
init_get().1
75+
}
76+
}
77+
};
78+
}
79+
80+
#[cfg(target_os = "linux")]
81+
macro_rules! expand_check_macro {
82+
($(($name:tt, $hwcap:expr)),* $(,)?) => {
83+
#[macro_export]
84+
#[doc(hidden)]
85+
macro_rules! check {
86+
$(
87+
($hwcaps:expr, $name) => { (($hwcaps & libc::$hwcap) != 0) };
88+
)*
89+
}
90+
};
91+
}
92+
93+
#[cfg(target_os = "linux")]
94+
expand_check_macro! {
95+
("aes", HWCAP_AES), // Enable AES support.
96+
("sha2", HWCAP_SHA2), // Enable SHA1 and SHA256 support.
97+
("sha3", HWCAP_SHA3), // Enable SHA512 and SHA3 support.
98+
}
99+
100+
// TODO(tarcieri): extract this into a function?
101+
#[cfg(target_os = "macos")]
102+
#[macro_export]
103+
#[doc(hidden)]
104+
macro_rules! sysctlbyname {
105+
// WARNING: `$name` MUST be a byte slice terminated by `\0`
106+
($name:expr) => {{
107+
let mut value: u32 = 0;
108+
let mut size = core::mem::size_of::<u32>();
109+
110+
let rc = unsafe {
111+
libc::sysctlbyname(
112+
$name.as_ptr() as *const i8,
113+
&mut value as *mut _ as *mut libc::c_void,
114+
&mut size,
115+
core::ptr::null_mut(),
116+
0,
117+
)
118+
};
119+
120+
assert_eq!(rc, 0, "sysctlbyname returned error code: {}", rc);
121+
value != 0
122+
}};
123+
}
124+
125+
#[cfg(target_os = "macos")]
126+
#[macro_export]
127+
#[doc(hidden)]
128+
macro_rules! check {
129+
("aes") => {
130+
true
131+
};
132+
("sha2") => {
133+
true
134+
};
135+
("sha3") => {
136+
cpufeatures::sysctlbyname!(b"hw.optional.armv8_2_sha3\0")
137+
};
138+
}

0 commit comments

Comments
 (0)