1 //! A DSL for describing x64 CPU features.
2 
3 use core::fmt;
4 use std::ops::{BitAnd, BitOr};
5 
6 /// A boolean term of CPU features.
7 ///
8 /// An instruction is valid when the boolean term (a recursive tree of `AND` and
9 /// `OR` terms) is satisfied.
10 ///
11 /// ```
12 /// # use cranelift_assembler_x64_meta::dsl::{Features, Feature};
13 /// let fs = Feature::_64b | Feature::compat;
14 /// assert_eq!(fs.to_string(), "(_64b | compat)");
15 /// ```
16 #[derive(PartialEq)]
17 pub enum Features {
18     And(Box<Features>, Box<Features>),
19     Or(Box<Features>, Box<Features>),
20     Feature(Feature),
21 }
22 
23 impl Features {
is_sse(&self) -> bool24     pub(crate) fn is_sse(&self) -> bool {
25         use Feature::*;
26         match self {
27             Features::And(lhs, rhs) => lhs.is_sse() || rhs.is_sse(),
28             Features::Or(lhs, rhs) => lhs.is_sse() || rhs.is_sse(),
29             Features::Feature(feature) => {
30                 matches!(feature, sse | sse2 | sse3 | ssse3 | sse41 | sse42)
31             }
32         }
33     }
34 }
35 
36 impl fmt::Display for Features {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result37     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38         match self {
39             Features::And(lhs, rhs) => write!(f, "({lhs} & {rhs})"),
40             Features::Or(lhs, rhs) => write!(f, "({lhs} | {rhs})"),
41             Features::Feature(feature) => write!(f, "{feature:#?}"),
42         }
43     }
44 }
45 
46 impl<T> BitOr<T> for Features
47 where
48     T: Into<Features>,
49 {
50     type Output = Features;
bitor(self, rhs: T) -> Self::Output51     fn bitor(self, rhs: T) -> Self::Output {
52         Features::Or(Box::new(self), Box::new(rhs.into()))
53     }
54 }
55 
56 impl<T> BitAnd<T> for Features
57 where
58     T: Into<Features>,
59 {
60     type Output = Features;
bitand(self, rhs: T) -> Self::Output61     fn bitand(self, rhs: T) -> Self::Output {
62         Features::And(Box::new(self), Box::new(rhs.into()))
63     }
64 }
65 
66 /// A CPU feature.
67 ///
68 /// IA-32e mode is the typical mode of operation for modern 64-bit x86
69 /// processors. It consists of two sub-modes:
70 /// - __64-bit mode__: uses the full 64-bit address space
71 /// - __compatibility mode__: allows use of legacy 32-bit code
72 ///
73 /// Other features listed here should match the __CPUID Feature Flags__ column
74 /// of the instruction tables of the x64 reference manual.
75 #[derive(Clone, Copy, Debug, PartialEq)]
76 #[allow(non_camel_case_types, reason = "makes DSL definitions easier to read")]
77 pub enum Feature {
78     _64b,
79     compat,
80     sse,
81     sse2,
82     sse3,
83     ssse3,
84     sse41,
85     sse42,
86     bmi1,
87     bmi2,
88     lzcnt,
89     popcnt,
90     avx,
91     avx2,
92     avx512f,
93     avx512vl,
94     avx512dq,
95     avx512bitalg,
96     avx512vbmi,
97     cmpxchg16b,
98     fma,
99 }
100 
101 /// List all CPU features.
102 ///
103 /// It is critical that this list contains _all_ variants of the [`Feature`]
104 /// `enum`. We use this list here in the `meta` level so that we can accurately
105 /// transcribe each variant to an `enum` available in the generated layer above.
106 /// If this list is incomplete, we will (fortunately) see compile errors for
107 /// generated functions that use the missing variants.
108 pub const ALL_FEATURES: &[Feature] = &[
109     Feature::_64b,
110     Feature::compat,
111     Feature::sse,
112     Feature::sse2,
113     Feature::sse3,
114     Feature::ssse3,
115     Feature::sse41,
116     Feature::sse42,
117     Feature::bmi1,
118     Feature::bmi2,
119     Feature::lzcnt,
120     Feature::popcnt,
121     Feature::avx,
122     Feature::avx2,
123     Feature::avx512f,
124     Feature::avx512vl,
125     Feature::avx512dq,
126     Feature::avx512bitalg,
127     Feature::avx512vbmi,
128     Feature::cmpxchg16b,
129     Feature::fma,
130 ];
131 
132 impl fmt::Display for Feature {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result133     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
134         fmt::Debug::fmt(self, f)
135     }
136 }
137 
138 impl From<Feature> for Features {
from(f: Feature) -> Self139     fn from(f: Feature) -> Self {
140         Features::Feature(f)
141     }
142 }
143 
144 impl<T> BitAnd<T> for Feature
145 where
146     T: Into<Features>,
147 {
148     type Output = Features;
bitand(self, rhs: T) -> Self::Output149     fn bitand(self, rhs: T) -> Self::Output {
150         Features::from(self) & rhs.into()
151     }
152 }
153 
154 impl<T> BitOr<T> for Feature
155 where
156     T: Into<Features>,
157 {
158     type Output = Features;
bitor(self, rhs: T) -> Self::Output159     fn bitor(self, rhs: T) -> Self::Output {
160         Features::from(self) | rhs.into()
161     }
162 }
163