cpubits/lib.rs
1#![no_std]
2#![doc = include_str!("../README.md")]
3#![doc(
4 html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
5 html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
6)]
7#![warn(clippy::pedantic)]
8
9//! # Supported bit sizes
10//!
11//! This crate supports the following bit sizes:
12//! - `16`
13//! - `32`
14//! - `64`
15//!
16//! This matches the available options for `target_pointer_width` in `rustc`:
17//!
18//! ```text
19//! expected values for `target_pointer_width` are: `16`, `32`, and `64`
20//! ```
21//!
22//! # Overriding the selection result via `cfg`
23//!
24//! This crate supports overriding its detection heuristics via an explicit `cfg` setting:
25//!
26//! - `cpubits = "16"`: force 16-bit
27//! - `cpubits = "32"`: force 32-bit
28//! - `cpubits = "64"`: force 64-bit
29//!
30//! This can be useful for testing different backends, and also in the event you would like to
31//! override the default detection result (in which case we would appreciate it if you opened an
32//! issue and let us know why).
33//!
34//! You can set `cfg` via the `RUSTFLAGS` environment variable:
35//!
36//! ```console
37//! $ RUSTFLAGS='--cfg cpubits="64"' cargo build --release
38//! ```
39//!
40//! Or you can persistently configure it for your project in `.cargo/config.toml`:
41//!
42//! ```toml
43//! # In .cargo/config.toml
44//! [build]
45//! rustflags = ['--cfg', 'cpubits="64"']
46//! ```
47//!
48//! ## Lint configuration for `cfg(cpubits)`
49//!
50//! If you are using the `cpubits!` macro you will notice the following warning being emitted:
51//!
52//! ```text
53//! warning: unexpected `cfg` condition name: `cpubits`
54//! ```
55//!
56//! You will need to add the following configuration to your `Cargo.toml` to silence the warning:
57//!
58//! ```toml
59//! [lints.rust.unexpected_cfgs]
60//! level = "warn"
61//! check-cfg = ['cfg(cpubits, values("16", "32", "64"))']
62//! ```
63
64// End of toplevel rustdoc, beginning of macro documentation. We put the detailed docs on the macro
65// itself so we can re-export it, and people can easily get to these docs from the re-exported
66// version.
67
68/// A macro for defining code based on the optimal word size to use for the target, as chosen
69/// heuristically at compile-time using `cfg`-based predicates.
70///
71/// # Usage
72///
73/// The macro works like a `match` expression that takes an implicit argument representing the
74/// number of CPU bits, which is one of `16`, `32`, or `64`.
75///
76/// Use this macro to conditionally emit code specific to certain CPU word sizes, e.g. defining
77/// types at compile-time based on the word size.
78///
79/// The macro doesn't create a new block and supports arbitrary statements in toplevel code, just
80/// like the `cfg-if` crate (whose guts it recycles).
81///
82/// ## Basic usage
83///
84/// ```
85/// cpubits::cpubits! {
86/// 16 => { pub type Word = u16; }
87/// 32 => { pub type Word = u32; }
88/// 64 => { pub type Word = u64; }
89/// }
90/// ```
91///
92/// NOTE: rustc will complain: "warning: unexpected `cfg` condition name: `cpubits`"
93///
94/// See the [lint configuration for `cfg(cpubits)`](./index.html#lint-configuration-for-cfgcpubits)
95/// documentation for how to silence the warning.
96///
97/// ## Grouping multiple bit sizes
98///
99/// If you would like to group together 16-bit and 32-bit platforms, you can do so as follows:
100///
101/// ```
102/// cpubits::cpubits! {
103/// 16 | 32 => { pub type Word = u32; }
104/// 64 => { pub type Word = u64; }
105/// }
106/// ```
107///
108/// ## Handling single-size cases
109///
110/// If you only want a block to run for a specific size, e.g. to know when it's possible to write
111/// `impl From<u64> for MyWordNewtype`, you can do the following:
112///
113/// ```
114/// # type Word = u64;
115/// pub struct MyWordNewtype(Word);
116///
117/// cpubits::cpubits! {
118/// 64 => {
119/// impl From<u64> for MyWordNewtype {
120/// #[inline]
121/// fn from(n: u64) -> MyWordNewtype {
122/// MyWordNewtype(n)
123/// }
124/// }
125/// }
126/// }
127/// ```
128///
129/// ## Use as an expression
130///
131/// It's also possible to use the macro as an expression, although in somewhat limited contexts
132/// due to its attribute handling:
133///
134/// ```
135/// fn detected_cpubits() -> u32 {
136/// cpubits::cpubits! {
137/// 16 => { 16 }
138/// 32 => { 32 }
139/// 64 => { 64 }
140/// }
141/// }
142/// ```
143///
144/// # Selection rules
145///
146/// The macro augments `target_pointer_width`-based selection with specific overrides which promote
147/// certain targets from 32-bit to 64-bit ones.
148///
149/// This 64-bit promotion occurs if `any` of the following `cfg`s are true:
150/// - `armv7`: `all(target_arch = "arm", target_feature = "v7")`
151/// - `wasm32`: `target_arch = "wasm32"`
152#[macro_export]
153macro_rules! cpubits {
154 // Only run the given block if we have selected a 16-bit word size, i.e. the code will be
155 // ignored on 32-bit and 64-bit platforms.
156 ( 16 => { $( $tokens:tt )* } ) => {
157 $crate::cpubits! {
158 16 => { $( $tokens )* },
159 32 | 64 => { }
160 }
161 };
162
163 // Only run the given block if we have selected a 32-bit word size, i.e. the code will be
164 // ignored on 32-bit and 64-bit platforms.
165 ( 32 => { $( $tokens:tt )* } ) => {
166 $crate::cpubits! {
167 16 => { }
168 32 => { $( $tokens )* }
169 64 => { }
170 }
171 };
172
173 // Only run the given block if we have selected a 64-bit word size, i.e. the code will be
174 // ignored on 16-bit and 32-bit platforms.
175 ( 64 => { $( $tokens:tt )* } ) => {
176 $crate::cpubits! {
177 16 | 32 => { }
178 64 => { $( $tokens )* }
179 }
180 };
181
182 // Only run the block on 16-bit and 32-bit targets.
183 ( 16 | 32 => { $( $tokens:tt )* } ) => {
184 $crate::cpubits! {
185 16 => { $( $tokens )* }
186 32 => { $( $tokens )* }
187 64 => { }
188 }
189 };
190
191 // Only run the block on 32-bit and 64-bit targets.
192 ( 32 | 64 => { $( $tokens:tt )* } ) => {
193 $crate::cpubits! {
194 16 => { }
195 32 => { $( $tokens )* }
196 64 => { $( $tokens )* }
197 }
198 };
199
200 // Select between 16-bit and 32-bit options, where no code will be generated for 64-bit targets
201 (
202 16 => { $( $tokens16:tt )* }
203 32 => { $( $tokens32:tt )* }
204 ) => {
205 $crate::cpubits! {
206 16 => { $( $tokens16 )* }
207 32 => { $( $tokens32 )* }
208 64 => { }
209 }
210 };
211
212 // Select between 32-bit and 64-bit options, where no code will be generated for 16-bit targets
213 (
214 32 => { $( $tokens32:tt )* }
215 64 => { $( $tokens64:tt )* }
216 ) => {
217 $crate::cpubits! {
218 16 => { }
219 32 => { $( $tokens32 )* }
220 64 => { $( $tokens64 )* }
221 }
222 };
223
224 // Select between 16-bit and 32-bit options, where 64-bit will use the 32-bit option
225 (
226 16 => { $( $tokens16:tt )* }
227 32 | 64 => { $( $tokens32:tt )* }
228 ) => {
229 $crate::cpubits! {
230 16 => { $( $tokens16 )* }
231 32 => { $( $tokens32 )* }
232 64 => { $( $tokens32 )* }
233 }
234 };
235
236 // Select between 32-bit and 64-bit options, where 16-bit will use the 32-bit option
237 (
238 16 | 32 => { $( $tokens32:tt )* }
239 64 => { $( $tokens64:tt )* }
240 ) => {
241 $crate::cpubits! {
242 16 => { $( $tokens32 )* }
243 32 => { $( $tokens32 )* }
244 64 => { $( $tokens64 )* }
245 }
246 };
247
248 // The general API which runs a different block for each possible word size
249 (
250 16 => { $( $tokens16:tt )* }
251 32 => { $( $tokens32:tt )* }
252 64 => { $( $tokens64:tt )* }
253 ) => {
254 $crate::cpubits! {
255 // `cfg` selector for 64-bit target overrides
256 #[cfg(enable_64_bit = any(
257 // ARMv7
258 all(target_arch = "arm", target_feature = "v7"),
259 // WASM
260 target_arch = "wasm32",
261 ))]
262 16 => { $( $tokens16 )* }
263 32 => { $( $tokens32 )* }
264 64 => { $( $tokens64 )* }
265 }
266 };
267
268 // Same API as immediately above, but with a pseudo-attribute we use to pass the `cfg` overrides
269 // for `target_pointer_width` that promote a 32-bit target into a 64-bit one.
270 (
271 #[cfg(enable_64_bit = $($enable_64_bit:tt)+ )]
272 16 => { $( $tokens16:tt )* }
273 32 => { $( $tokens32:tt )* }
274 64 => { $( $tokens64:tt )* }
275 ) => {
276 $crate::cfg_if! {
277 @__items () ;
278 // The following are effectively `if`/`else` clauses in a Lispy syntax, where each
279 // 2-tuple is `( ( predicate ) ( body ) )`. The first clause with a matching predicate
280 // is taken and its body executed and the rest are ignored just like `if`/`else`.
281 //
282 // We first match on each of the explicit overrides, and if none of them are configured
283 // apply our heuristic logic which allows certain targets to be overridden to use
284 // 64-bit backends, as configured in the `enable_64_bit` predicate above.
285 (
286 ( cpubits = "16" )
287 ( $( $tokens16 )* )
288 ),
289 (
290 ( cpubits = "32" )
291 ( $( $tokens32 )* )
292 ),
293 (
294 ( cpubits = "64" )
295 ( $( $tokens64 )* )
296 ),
297 (
298 ( target_pointer_width = "16" )
299 ( $( $tokens16 )* )
300 ),
301 (
302 ( all(target_pointer_width = "32", not($( $enable_64_bit )+)) )
303 ( $( $tokens32 )* )
304 ),
305 (
306 ( any(target_pointer_width = "64", $( $enable_64_bit )+) )
307 ( $( $tokens64 )* )
308 ),
309 (
310 ()
311 ( compile_error!("unsupported target pointer width") )
312 ),
313 }
314 };
315}
316
317/// Vendored copy of the `cfg_if::cfg_if` macro.
318/// Copyright (c) 2014 Alex Crichton. Dual-licensed Apache 2.0 + MIT.
319///
320/// NOTE: though this is marked `doc(hidden)`, it is considered a stable part of the public API.
321#[doc(hidden)]
322#[macro_export]
323macro_rules! cfg_if {
324 // NOTE(cpubits): we deliberately include the original frontend even though we don't use it
325 // internally within this crate so consumers of `cpubits` can use the vendored `cfg_if` instead
326 // of requiring both `cpubits` and `cfg-if`.
327 (
328 if #[cfg( $($i_meta:tt)+ )] { $( $i_tokens:tt )* }
329 $(
330 else if #[cfg( $($ei_meta:tt)+ )] { $( $ei_tokens:tt )* }
331 )*
332 $(
333 else { $( $e_tokens:tt )* }
334 )?
335 ) => {
336 $crate::cfg_if! {
337 @__items () ;
338 (( $($i_meta)+ ) ( $( $i_tokens )* )),
339 $(
340 (( $($ei_meta)+ ) ( $( $ei_tokens )* )),
341 )*
342 $(
343 (() ( $( $e_tokens )* )),
344 )?
345 }
346 };
347
348 // Internal and recursive macro to emit all the items
349 //
350 // Collects all the previous cfgs in a list at the beginning, so they can be
351 // negated. After the semicolon are all the remaining items.
352 (@__items ( $( ($($_:tt)*) , )* ) ; ) => {};
353 (
354 @__items ( $( ($($no:tt)+) , )* ) ;
355 (( $( $($yes:tt)+ )? ) ( $( $tokens:tt )* )),
356 $( $rest:tt , )*
357 ) => {
358 // Emit all items within one block, applying an appropriate #[cfg]. The
359 // #[cfg] will require all `$yes` matchers specified and must also negate
360 // all previous matchers.
361 #[cfg(all(
362 $( $($yes)+ , )?
363 not(any( $( $($no)+ ),* ))
364 ))]
365 // Subtle: You might think we could put `$( $tokens )*` here. But if
366 // that contains multiple items then the `#[cfg(all(..))]` above would
367 // only apply to the first one. By wrapping `$( $tokens )*` in this
368 // macro call, we temporarily group the items into a single thing (the
369 // macro call) that will be included/excluded by the `#[cfg(all(..))]`
370 // as appropriate. If the `#[cfg(all(..))]` succeeds, the macro call
371 // will be included, and then evaluated, producing `$( $tokens )*`. See
372 // also the "issue #90" test below.
373 $crate::cfg_if! { @__temp_group $( $tokens )* }
374
375 // Recurse to emit all other items in `$rest`, and when we do so add all
376 // our `$yes` matchers to the list of `$no` matchers as future emissions
377 // will have to negate everything we just matched as well.
378 $crate::cfg_if! {
379 @__items ( $( ($($no)+) , )* $( ($($yes)+) , )? ) ;
380 $( $rest , )*
381 }
382 };
383
384 // See the "Subtle" comment above.
385 (@__temp_group $( $tokens:tt )* ) => {
386 $( $tokens )*
387 };
388}
389
390/// Constant representing the detection result from `cpubits!` on the current target.
391pub const CPUBITS: u32 = {
392 cpubits! {
393 16 => { 16 }
394 32 => { 32 }
395 64 => { 64 }
396 }
397};
398
399#[cfg(test)]
400mod tests {
401 use super::CPUBITS;
402
403 /// Return the expected number of bits for the target.
404 #[cfg(not(any(cpubits = "16", cpubits = "32", cpubits = "64")))]
405 fn expected_bits() -> u32 {
406 // Duplicated 64-bit override predicates need to go here
407 if cfg!(any(
408 // ARMv7
409 all(target_arch = "arm", target_feature = "v7"),
410 // WASM
411 target_arch = "wasm32"
412 )) {
413 64
414 } else {
415 usize::BITS
416 }
417 }
418
419 #[cfg(not(any(cpubits = "16", cpubits = "32", cpubits = "64")))]
420 #[test]
421 fn cpubits_works() {
422 assert_eq!(CPUBITS, expected_bits());
423 }
424
425 /// Explicit test for ARMv7 so we can see the predicate is working
426 #[cfg(all(target_arch = "arm", target_feature = "v7"))]
427 #[test]
428 fn cpubits_on_armv7_is_64bit() {
429 assert_eq!(CPUBITS, 64);
430 }
431
432 /// Explicit test for WASM so we can see the predicate is working
433 #[cfg(target_arch = "wasm32")]
434 #[test]
435 fn cpubits_on_wasm_is_64bit() {
436 assert_eq!(CPUBITS, 64);
437 }
438
439 /// Test for the `16 | 32` syntax.
440 #[cfg(not(any(cpubits = "16", cpubits = "32", cpubits = "64")))]
441 #[test]
442 fn cpubits_16_or_32_vs_64() {
443 const BITS: u32 = {
444 cpubits! {
445 16 | 32 => { 32 }
446 64 => { 64 }
447 }
448 };
449
450 match expected_bits() {
451 16 | 32 => assert_eq!(32, BITS),
452 64 => assert_eq!(64, BITS),
453 bits => unreachable!("#{bits}-bits should be one of: 16, 32, 64"),
454 }
455 }
456
457 #[cfg(cpubits = "32")]
458 #[test]
459 fn cpubits_32_bit_override() {
460 assert_eq!(CPUBITS, 32);
461 }
462}