Skip to main content

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}