Skip to content

Commit d8e9304

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 d8e9304

File tree

8 files changed

+439
-127
lines changed

8 files changed

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

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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+
/// Linux `expand_check_macro`
85+
#[cfg(target_os = "linux")]
86+
macro_rules! expand_check_macro {
87+
($(($name:tt, $hwcap:expr)),* $(,)?) => {
88+
#[macro_export]
89+
#[doc(hidden)]
90+
macro_rules! check {
91+
$(
92+
($hwcaps:expr, $name) => { (($hwcaps & libc::$hwcap) != 0) };
93+
)*
94+
}
95+
};
96+
}
97+
98+
/// Linux `expand_check_macro`
99+
#[cfg(target_os = "linux")]
100+
expand_check_macro! {
101+
("aes", HWCAP_AES), // Enable AES support.
102+
("sha2", HWCAP_SHA2), // Enable SHA1 and SHA256 support.
103+
("sha3", HWCAP_SHA3), // Enable SHA512 and SHA3 support.
104+
}
105+
106+
/// macOS `check!` macro.
107+
///
108+
/// NOTE: several of these instructions (e.g. `aes`, `sha2`) can be assumed to
109+
/// be present on all Apple ARM64 hardware.
110+
///
111+
/// Newer CPU instructions now have nodes within sysctl's `hw.optional`
112+
/// namespace, however the ones that do not can safely be assumed to be
113+
/// present on all Apple ARM64 devices, now and for the foreseeable future.
114+
///
115+
/// See discussion on this issue for more information:
116+
/// <https://github.com/RustCrypto/utils/issues/378>
117+
#[cfg(target_os = "macos")]
118+
#[macro_export]
119+
#[doc(hidden)]
120+
macro_rules! check {
121+
("aes") => {
122+
true
123+
};
124+
("sha2") => {
125+
true
126+
};
127+
("sha3") => {
128+
unsafe { cpufeatures::aarch64::sysctlbyname(b"hw.optional.armv8_2_sha3\0") }
129+
};
130+
}
131+
132+
/// macOS helper function for calling `sysctlbyname`.
133+
#[cfg(target_os = "macos")]
134+
pub unsafe fn sysctlbyname(name: &[u8]) -> bool {
135+
assert_eq!(
136+
name.last().cloned(),
137+
Some(0),
138+
"name is not NUL terminated: {:?}",
139+
name
140+
);
141+
142+
let mut value: u32 = 0;
143+
let mut size = core::mem::size_of::<u32>();
144+
145+
let rc = libc::sysctlbyname(
146+
name.as_ptr() as *const i8,
147+
&mut value as *mut _ as *mut libc::c_void,
148+
&mut size,
149+
core::ptr::null_mut(),
150+
0,
151+
);
152+
153+
assert_eq!(size, 4, "unexpected sysctlbyname(3) result size");
154+
assert_eq!(rc, 0, "sysctlbyname returned error code: {}", rc);
155+
value != 0
156+
}

0 commit comments

Comments
 (0)