rust_target_feature_data/
lib.rs

1//! Data about Rust target features.
2//!
3//! Different versions of Rust support different [target
4//! features](https://rust-lang.github.io/rfcs/2045-target-feature.html). Different Rust targets
5//! automatically enable different target features. Different versions of Rust automatically enable
6//! different target features _for the same target_. Enabling the same target feature implies
7// enabling different target features in different versions of Rust.
8//!
9//! This crate provides target feature data for all targets covering Rust versions:
10//!
11//! * `"1.83.0"`
12//! * `"1.84.0"` (1.84.1 is identical)
13//! * `"1.85.0"` (1.85.1 is identical)
14//! * `"1.86.0"`
15//! * `"1.87.0"` (from 1.87.0-beta.5)
16//!
17//! Rust 1.88.0 provides target feature data for the selected target [via `rustdoc`'s JSON output
18//! format](https://docs.rs/rustdoc-types/latest/rustdoc_types/struct.TargetFeature.html), making
19//! this crate obsolete going forward.
20//!
21//! # Example
22//!
23//! ```
24//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
25//! use rust_target_feature_data::find;
26//!
27//! let features: Vec<_> = find("1.83.0", "x86_64-unknown-linux-gnu")?.collect();
28//! let pclmulqdq = features.iter().find(|f| f.name == "pclmulqdq").unwrap();
29//! assert_eq!(pclmulqdq.globally_enabled, false);
30//! assert_eq!(pclmulqdq.implies_features, [].into());
31//!
32//! let features: Vec<_> = find("1.84.0", "x86_64-unknown-linux-gnu")?.collect();
33//! let pclmulqdq = features.iter().find(|f| f.name == "pclmulqdq").unwrap();
34//! assert_eq!(pclmulqdq.implies_features, ["sse2"].into());
35//! # Ok(()) }
36//! ```
37
38use std::collections::BTreeSet;
39
40#[rustfmt::skip]
41mod generated;
42
43/// Information about a target feature.
44///
45/// Rust target features are used to influence code generation, especially around selecting
46/// instructions which are not universally supported by the target architecture.
47///
48/// Target features are commonly enabled by the [`#[target_feature]` attribute][1] to influence code
49/// generation for a particular function, and less commonly enabled by compiler options like
50/// `-Ctarget-feature` or `-Ctarget-cpu`. Targets themselves automatically enable certain target
51/// features by default, for example because the target's ABI specification requires saving specific
52/// registers which only exist in an architectural extension.
53///
54/// Target features can imply other target features: for example, x86-64 `avx2` implies `avx`, and
55/// aarch64 `sve2` implies `sve`, since both of these architectural extensions depend on their
56/// predecessors.
57///
58/// Target features can be probed at compile time by [`#[cfg(target_feature)]`][2] or `cfg!(…)`
59/// conditional compilation to determine whether a target feature is enabled in a particular
60/// context.
61///
62/// [1]: https://doc.rust-lang.org/stable/reference/attributes/codegen.html#the-target_feature-attribute
63/// [2]: https://doc.rust-lang.org/reference/conditional-compilation.html#target_feature
64#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
65pub struct TargetFeature {
66    /// The name of this target feature.
67    pub name: &'static str,
68    /// Other target features which are implied by this target feature, if any.
69    pub implies_features: BTreeSet<&'static str>,
70    /// If this target feature is unstable, the name of the associated language feature gate.
71    pub unstable_feature_gate: Option<&'static str>,
72    /// Whether this feature is globally enabled by default.
73    ///
74    /// Target features can be globally enabled implicitly as a result of the target's definition.
75    /// For example, x86-64 hardware floating point ABIs require saving x87 and SSE2 registers,
76    /// which in turn requires globally enabling the `x87` and `sse2` target features so that the
77    /// generated machine code conforms to the target's ABI.
78    ///
79    /// Target features can also be globally enabled explicitly as a result of compiler flags like
80    /// [`-Ctarget-feature`][1] or [`-Ctarget-cpu`][2].
81    ///
82    /// [1]: https://doc.rust-lang.org/beta/rustc/codegen-options/index.html#target-feature
83    /// [2]: https://doc.rust-lang.org/beta/rustc/codegen-options/index.html#target-cpu
84    pub globally_enabled: bool,
85}
86
87/// An error finding target feature data.
88#[derive(Debug, Clone, Eq, PartialEq)]
89pub enum NotFoundError {
90    /// The compiler version was not found
91    CompilerNotFound(String),
92    /// The compiler was found but the target was not
93    TargetNotFound(String),
94}
95
96impl std::error::Error for NotFoundError {}
97
98impl std::fmt::Display for NotFoundError {
99    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
100        match self {
101            NotFoundError::CompilerNotFound(name) => {
102                write!(f, "compiler version {:?} not found", name)
103            }
104            NotFoundError::TargetNotFound(name) => {
105                write!(f, "target {:?} not found", name)
106            }
107        }
108    }
109}
110
111/// Find the target features applicable to a Rust version and target.
112///
113/// ```
114/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
115/// use rust_target_feature_data::NotFoundError;
116///
117/// // Different versions return different features
118/// for (version, count) in [
119///     ("1.83.0", 90),
120///     ("1.84.0", 91),
121///     ("1.85.0", 92),
122///     ("1.86.0", 92),
123///     ("1.87.0", 92),
124/// ] {
125///     assert_eq!(
126///         rust_target_feature_data::find(version, "aarch64-apple-darwin")?.count(),
127///         count,
128///     );
129/// }
130///
131/// // 1.82.0 data is not included
132/// assert_eq!(
133///     rust_target_feature_data::find("1.82.0", "x86_64-unknown-linux-gnu").err().unwrap(),
134///     NotFoundError::CompilerNotFound("1.82.0".into())
135/// );
136///
137/// // i686-unknown-redox became i586-unknown-redox
138/// assert!(
139///     rust_target_feature_data::find("1.85.0", "i686-unknown-redox").is_ok()
140/// );
141/// assert_eq!(
142///     rust_target_feature_data::find("1.86.0", "i686-unknown-redox").err().unwrap(),
143///     NotFoundError::TargetNotFound("i686-unknown-redox".into())
144/// );
145/// # Ok(()) }
146/// ```
147pub fn find(
148    rust_version: &str,
149    target: &str,
150) -> Result<impl Iterator<Item = TargetFeature>, NotFoundError> {
151    let mut targets = generated::all()
152        .find(|(version, _)| *version == rust_version)
153        .ok_or_else(|| NotFoundError::CompilerNotFound(rust_version.into()))?
154        .1;
155
156    targets
157        .find(|(name, _)| *name == target)
158        .map(|(_, features)| features)
159        .ok_or_else(|| NotFoundError::TargetNotFound(target.into()))
160}
161
162#[cfg(test)]
163mod tests;