safe_arch/lib.rs
1#![no_std]
2#![warn(missing_docs)]
3#![allow(unused_imports)]
4#![allow(clippy::too_many_arguments)]
5#![allow(clippy::transmute_ptr_to_ptr)]
6#![cfg_attr(docsrs, feature(doc_cfg))]
7
8//! A crate that safely exposes arch intrinsics via `#[cfg()]`.
9//!
10//! `safe_arch` lets you safely use CPU intrinsics. Those things in the
11//! [`core::arch`](core::arch) modules. It works purely via `#[cfg()]` and
12//! compile time CPU feature declaration. If you want to check for a feature at
13//! runtime and then call an intrinsic or use a fallback path based on that then
14//! this crate is sadly not for you.
15//!
16//! SIMD register types are "newtype'd" so that better trait impls can be given
17//! to them, but the inner value is a `pub` field so feel free to just grab it
18//! out if you need to. Trait impls of the newtypes include: `Default` (zeroed),
19//! `From`/`Into` of appropriate data types, and appropriate operator
20//! overloading.
21//!
22//! * Most intrinsics (like addition and multiplication) are totally safe to use
23//! as long as the CPU feature is available. In this case, what you get is 1:1
24//! with the actual intrinsic.
25//! * Some intrinsics take a pointer of an assumed minimum alignment and
26//! validity span. For these, the `safe_arch` function takes a reference of an
27//! appropriate type to uphold safety.
28//! * Try the [bytemuck](https://docs.rs/bytemuck) crate (and turn on the
29//! `bytemuck` feature of this crate) if you want help safely casting
30//! between reference types.
31//! * Some intrinsics are not safe unless you're _very_ careful about how you
32//! use them, such as the streaming operations requiring you to use them in
33//! combination with an appropriate memory fence. Those operations aren't
34//! exposed here.
35//! * Some intrinsics mess with the processor state, such as changing the
36//! floating point flags, saving and loading special register state, and so
37//! on. LLVM doesn't really support you messing with that within a high level
38//! language, so those operations aren't exposed here. Use assembly or
39//! something if you want to do that.
40//!
41//! ## Naming Conventions
42//! The `safe_arch` crate does not simply use the "official" names for each
43//! intrinsic, because the official names are generally poor. Instead, the
44//! operations have been given better names that makes things hopefully easier
45//! to understand then you're reading the code.
46//!
47//! For a full explanation of the naming used, see the [Naming
48//! Conventions](crate::naming_conventions) page.
49//!
50//! ## Current Support
51//! * `x86` / `x86_64` (Intel, AMD, etc)
52//! * 128-bit: `sse`, `sse2`, `sse3`, `ssse3`, `sse4.1`, `sse4.2`
53//! * 256-bit: `avx`, `avx2`
54//! * Other: `adx`, `aes`, `bmi1`, `bmi2`, `fma`, `lzcnt`, `pclmulqdq`,
55//! `popcnt`, `rdrand`, `rdseed`
56//!
57//! ## Compile Time CPU Target Features
58//!
59//! At the time of me writing this, Rust enables the `sse` and `sse2` CPU
60//! features by default for all `i686` (x86) and `x86_64` builds. Those CPU
61//! features are built into the design of `x86_64`, and you'd need a _super_ old
62//! `x86` CPU for it to not support at least `sse` and `sse2`, so they're a safe
63//! bet for the language to enable all the time. In fact, because the standard
64//! library is compiled with them enabled, simply trying to _disable_ those
65//! features would actually cause ABI issues and fill your program with UB
66//! ([link][rustc_docs]).
67//!
68//! If you want additional CPU features available at compile time you'll have to
69//! enable them with an additional arg to `rustc`. For a feature named `name`
70//! you pass `-C target-feature=+name`, such as `-C target-feature=+sse3` for
71//! `sse3`.
72//!
73//! You can alternately enable _all_ target features of the current CPU with `-C
74//! target-cpu=native`. This is primarily of use if you're building a program
75//! you'll only run on your own system.
76//!
77//! It's sometimes hard to know if your target platform will support a given
78//! feature set, but the [Steam Hardware Survey][steam-survey] is generally
79//! taken as a guide to what you can expect people to have available. If you
80//! click "Other Settings" it'll expand into a list of CPU target features and
81//! how common they are. These days, it seems that `sse3` can be safely assumed,
82//! and `ssse3`, `sse4.1`, and `sse4.2` are pretty safe bets as well. The stuff
83//! above 128-bit isn't as common yet, give it another few years.
84//!
85//! **Please note that executing a program on a CPU that doesn't support the
86//! target features it was compiles for is Undefined Behavior.**
87//!
88//! Currently, Rust doesn't actually support an easy way for you to check that a
89//! feature enabled at compile time is _actually_ available at runtime. There is
90//! the "[feature_detected][feature_detected]" family of macros, but if you
91//! enable a feature they will evaluate to a constant `true` instead of actually
92//! deferring the check for the feature to runtime. This means that, if you
93//! _did_ want a check at the start of your program, to confirm that all the
94//! assumed features are present and error out when the assumptions don't hold,
95//! you can't use that macro. You gotta use CPUID and check manually. rip.
96//! Hopefully we can make that process easier in a future version of this crate.
97//!
98//! [steam-survey]:
99//! https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam
100//! [feature_detected]:
101//! https://doc.rust-lang.org/std/index.html?search=feature_detected
102//! [rustc_docs]: https://doc.rust-lang.org/rustc/targets/known-issues.html
103//!
104//! ### A Note On Working With Cfg
105//!
106//! There's two main ways to use `cfg`:
107//! * Via an attribute placed on an item, block, or expression:
108//! * `#[cfg(debug_assertions)] println!("hello");`
109//! * Via a macro used within an expression position:
110//! * `if cfg!(debug_assertions) { println!("hello"); }`
111//!
112//! The difference might seem small but it's actually very important:
113//! * The attribute form will include code or not _before_ deciding if all the
114//! items named and so forth really exist or not. This means that code that is
115//! configured via attribute can safely name things that don't always exist as
116//! long as the things they name do exist whenever that code is configured
117//! into the build.
118//! * The macro form will include the configured code _no matter what_, and then
119//! the macro resolves to a constant `true` or `false` and the compiler uses
120//! dead code elimination to cut out the path not taken.
121//!
122//! This crate uses `cfg` via the attribute, so the functions it exposes don't
123//! exist at all when the appropriate CPU target features aren't enabled.
124//! Accordingly, if you plan to call this crate or not depending on what
125//! features are enabled in the build you'll also need to control your use of
126//! this crate via cfg attribute, not cfg macro.
127
128use core::{
129 convert::AsRef,
130 fmt::{Binary, Debug, Display, LowerExp, LowerHex, Octal, UpperExp, UpperHex},
131 ops::{Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div, DivAssign, Mul, MulAssign, Neg, Not, Sub, SubAssign},
132};
133
134pub mod naming_conventions;
135
136/// Turns a round operator token to the correct constant value.
137#[macro_export]
138#[cfg_attr(docsrs, doc(cfg(target_feature = "avx")))]
139// Note(Lokathor): keep this at the crate root.
140macro_rules! round_op {
141 (Nearest) => {{
142 #[cfg(target_arch = "x86")]
143 use ::core::arch::x86::{_MM_FROUND_NO_EXC, _MM_FROUND_TO_NEAREST_INT};
144 #[cfg(target_arch = "x86_64")]
145 use ::core::arch::x86_64::{_MM_FROUND_NO_EXC, _MM_FROUND_TO_NEAREST_INT};
146 _MM_FROUND_NO_EXC | _MM_FROUND_TO_NEAREST_INT
147 }};
148 (NegInf) => {{
149 #[cfg(target_arch = "x86")]
150 use ::core::arch::x86::{_MM_FROUND_NO_EXC, _MM_FROUND_TO_NEG_INF};
151 #[cfg(target_arch = "x86_64")]
152 use ::core::arch::x86_64::{_MM_FROUND_NO_EXC, _MM_FROUND_TO_NEG_INF};
153 _MM_FROUND_NO_EXC | _MM_FROUND_TO_NEG_INF
154 }};
155 (PosInf) => {{
156 #[cfg(target_arch = "x86")]
157 use ::core::arch::x86::{_MM_FROUND_NO_EXC, _MM_FROUND_TO_POS_INF};
158 #[cfg(target_arch = "x86_64")]
159 use ::core::arch::x86_64::{_MM_FROUND_NO_EXC, _MM_FROUND_TO_POS_INF};
160 _MM_FROUND_NO_EXC | _MM_FROUND_TO_POS_INF
161 }};
162 (Zero) => {{
163 #[cfg(target_arch = "x86")]
164 use ::core::arch::x86::{_MM_FROUND_NO_EXC, _MM_FROUND_TO_ZERO, _mm256_round_pd};
165 #[cfg(target_arch = "x86_64")]
166 use ::core::arch::x86_64::{_MM_FROUND_NO_EXC, _MM_FROUND_TO_ZERO, _mm256_round_pd};
167 _MM_FROUND_NO_EXC | _MM_FROUND_TO_ZERO
168 }};
169}
170
171/// Declares a private mod and then a glob `use` with the visibility specified.
172macro_rules! submodule {
173 ($v:vis $name:ident) => {
174 mod $name;
175 $v use $name::*;
176 };
177 ($v:vis $name:ident { $($content:tt)* }) => {
178 mod $name { $($content)* }
179 $v use $name::*;
180 };
181}
182
183// Note(Lokathor): Stupid as it sounds, we need to put the imports here at the
184// crate root because the arch-specific macros that we define in our inner
185// modules are actually "scoped" to also be at the crate root. We want the
186// rustdoc generation of the macros to "see" these imports so that the docs link
187// over to the `core::arch` module correctly.
188// https://github.com/rust-lang/rust/issues/72243
189
190#[cfg(target_arch = "x86")]
191use core::arch::x86::*;
192#[cfg(target_arch = "x86_64")]
193use core::arch::x86_64::*;
194
195#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
196submodule!(pub x86_x64 {
197 //! Types and functions for safe `x86` / `x86_64` intrinsic usage.
198 //!
199 //! `x86_64` is essentially a superset of `x86`, so we just lump it all into
200 //! one module. Anything not available on `x86` simply won't be in the build
201 //! on that arch.
202 use super::*;
203
204 submodule!(pub m128_);
205 submodule!(pub m128d_);
206 submodule!(pub m128i_);
207
208 submodule!(pub m256_);
209 submodule!(pub m256d_);
210 submodule!(pub m256i_);
211
212 submodule!(pub m512_);
213 submodule!(pub m512d_);
214 submodule!(pub m512i_);
215
216 // Note(Lokathor): We only include these sub-modules with the actual functions
217 // if the feature is enabled. Ae *also* have a cfg attribute on the inside of
218 // the modules as a "double-verification" of sorts. Technically either way on
219 // its own would also be fine.
220
221 // These CPU features follow a fairly clear and strict progression that's easy
222 // to remember. Most of them offer a fair pile of new functions.
223 #[cfg(target_feature = "sse")]
224 submodule!(pub sse);
225 #[cfg(target_feature = "sse2")]
226 submodule!(pub sse2);
227 #[cfg(target_feature = "sse3")]
228 submodule!(pub sse3);
229 #[cfg(target_feature = "ssse3")]
230 submodule!(pub ssse3);
231 #[cfg(target_feature = "sse4.1")]
232 submodule!(pub sse4_1);
233 #[cfg(target_feature = "sse4.2")]
234 submodule!(pub sse4_2);
235 #[cfg(target_feature = "avx")]
236 submodule!(pub avx);
237 #[cfg(target_feature = "avx2")]
238 submodule!(pub avx2);
239 #[cfg(target_feature = "avx512f")]
240 submodule!(pub avx512);
241
242 // These features aren't as easy to remember the progression of and they each
243 // only add a small handful of functions.
244 #[cfg(target_feature = "adx")]
245 submodule!(pub adx);
246 #[cfg(target_feature = "aes")]
247 submodule!(pub aes);
248 #[cfg(target_feature = "bmi1")]
249 submodule!(pub bmi1);
250 #[cfg(target_feature = "bmi2")]
251 submodule!(pub bmi2);
252 #[cfg(target_feature = "fma")]
253 submodule!(pub fma);
254 #[cfg(target_feature = "lzcnt")]
255 submodule!(pub lzcnt);
256 #[cfg(target_feature = "pclmulqdq")]
257 submodule!(pub pclmulqdq);
258 #[cfg(target_feature = "popcnt")]
259 submodule!(pub popcnt);
260 #[cfg(target_feature = "rdrand")]
261 submodule!(pub rdrand);
262 #[cfg(target_feature = "rdseed")]
263 submodule!(pub rdseed);
264
265 /// Reads the CPU's timestamp counter value.
266 ///
267 /// This is a monotonically increasing time-stamp that goes up every clock
268 /// cycle of the CPU. However, since modern CPUs are variable clock rate
269 /// depending on demand this can't actually be used for telling the time. It
270 /// also does _not_ fully serialize all operations, so previous instructions
271 /// might still be in progress when this reads the timestamp.
272 ///
273 /// * **Intrinsic:** `_rdtsc`
274 /// * **Assembly:** `rdtsc`
275 pub fn read_timestamp_counter() -> u64 {
276 // Note(Lokathor): This was changed from i64 to u64 at some point, but
277 // everyone ever was already casting this value to `u64` so crater didn't
278 // even consider it a problem. We will follow suit.
279 #[allow(clippy::unnecessary_cast)]
280 unsafe { _rdtsc() as u64 }
281 }
282
283 /// Reads the CPU's timestamp counter value and store the processor signature.
284 ///
285 /// This works similar to [`read_timestamp_counter`] with two main
286 /// differences:
287 /// * It and also stores the `IA32_TSC_AUX MSR` value to the reference given.
288 /// * It waits on all previous instructions to finish before reading the
289 /// timestamp (though it doesn't prevent other instructions from starting).
290 ///
291 /// As with `read_timestamp_counter`, you can't actually use this to tell the
292 /// time.
293 ///
294 /// * **Intrinsic:** `__rdtscp`
295 /// * **Assembly:** `rdtscp`
296 pub fn read_timestamp_counter_p(aux: &mut u32) -> u64 {
297 unsafe { __rdtscp(aux) }
298 }
299
300 /// Swap the bytes of the given 32-bit value.
301 ///
302 /// ```
303 /// # use safe_arch::*;
304 /// assert_eq!(byte_swap_i32(0x0A123456), 0x5634120A);
305 /// ```
306 /// * **Intrinsic:** `_bswap`
307 /// * **Assembly:** `bswap r32`
308 pub fn byte_swap_i32(i: i32) -> i32 {
309 #[allow(unused_unsafe)]
310 unsafe { _bswap(i)}
311 }
312
313 /// Swap the bytes of the given 64-bit value.
314 ///
315 /// ```
316 /// # use safe_arch::*;
317 /// assert_eq!(byte_swap_i64(0x0A123456_789ABC01), 0x01BC9A78_5634120A);
318 /// ```
319 /// * **Intrinsic:** `_bswap64`
320 /// * **Assembly:** `bswap r64`
321 #[cfg(target_arch="x86_64")]
322 pub fn byte_swap_i64(i: i64) -> i64 {
323 #[allow(unused_unsafe)]
324 unsafe { _bswap64(i) }
325 }
326});