1#![allow(unexpected_cfgs)]
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
7#[repr(u8)]
8#[non_exhaustive]
9pub enum InstructionSet {
10 Scalar,
12
13 #[cfg(feature = "std_simd")]
15 StdSimd,
16
17 Unknown,
20
21 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
23 X86V1,
24
25 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
27 X86V2,
28
29 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
31 X86V3,
32
33 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
35 X86V4,
36
37 #[cfg(target_arch = "aarch64")]
39 NEON,
40
41 #[cfg(all(feature = "wasm", target_arch = "wasm32"))]
43 WASM32,
44
45 #[cfg(all(feature = "wasm", target_arch = "wasm64"))]
47 WASM64,
48
49 #[cfg(all(feature = "spirv", target_arch = "spirv"))]
51 SPIRV,
52}
53
54mod detect_once;
55pub use detect_once::DetectOnce;
56
57#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
60pub mod x86;
61
62#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
63mod x86_detector;
64
65impl InstructionSet {
66 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
68 #[inline]
69 pub fn get() -> InstructionSet {
70 static DETECTOR: DetectOnce<InstructionSet> = DetectOnce::new(InstructionSet::Scalar);
71
72 *DETECTOR.get(x86_detector::detect)
73 }
74
75 #[cfg(target_arch = "aarch64")]
80 pub fn get() -> InstructionSet {
81 InstructionSet::NEON
82 }
83
84 #[cfg(all(feature = "wasm", target_arch = "wasm32"))]
89 pub fn get() -> InstructionSet {
90 InstructionSet::WASM32
91 }
92
93 #[cfg(all(feature = "wasm", target_arch = "wasm64"))]
95 pub fn get() -> InstructionSet {
96 InstructionSet::WASM64
97 }
98
99 #[cfg(all(feature = "spirv", target_arch = "spirv"))]
101 pub fn get() -> InstructionSet {
102 InstructionSet::SPIRV
103 }
104
105 #[cfg(not(any(
113 any(target_arch = "x86", target_arch = "x86_64"),
114 target_arch = "aarch64",
115 all(feature = "wasm", any(target_arch = "wasm32", target_arch = "wasm64")),
116 all(feature = "spirv", target_arch = "spirv"),
117 )))]
118 pub fn get() -> InstructionSet {
119 InstructionSet::Scalar
120 }
121
122 #[inline(always)]
129 pub const fn min(a: InstructionSet, b: InstructionSet) -> InstructionSet {
130 if (a as u8) < (b as u8) { a } else { b }
131 }
132
133 #[inline(always)]
135 pub const fn max(a: InstructionSet, b: InstructionSet) -> InstructionSet {
136 if (a as u8) > (b as u8) { a } else { b }
137 }
138
139 #[inline(always)]
142 pub const fn assert_eq(a: InstructionSet, b: InstructionSet) -> InstructionSet {
143 assert!((a as u8) == (b as u8), "InstructionSet equality assertion failed");
144
145 a
146 }
147
148 #[inline(always)]
156 pub const fn has_instruction_level_parallelism(self) -> bool {
157 cfg!(any(
158 target_arch = "x86",
159 target_arch = "x86_64",
160 target_arch = "arm",
161 target_arch = "aarch64"
162 ))
163 }
164}
165
166macro_rules! isa_properties {
177 ($(
178 $(#[cfg $cfg:tt])?
179 $variant:ident {
180 registers: $registers:expr,
181 fma: $fma:expr,
182 simd: $simd:expr,
183 unaligned_cheap: $unaligned:expr,
184 unroll: $unroll:expr,
185 masked: $masked:expr,
186 }
187 )*) => {
188 impl InstructionSet {
189 #[inline(always)]
195 pub const fn num_registers(self) -> usize {
196 match self { $( $(#[cfg $cfg])? Self::$variant => $registers, )* }
197 }
198
199 #[inline(always)]
201 pub const fn has_fma(self) -> bool {
202 match self { $( $(#[cfg $cfg])? Self::$variant => $fma, )* }
203 }
204
205 #[inline(always)]
208 pub const fn is_simd(self) -> bool {
209 match self { $( $(#[cfg $cfg])? Self::$variant => $simd, )* }
210 }
211
212 #[inline(always)]
215 pub const fn unaligned_is_cheap(self) -> bool {
216 match self { $( $(#[cfg $cfg])? Self::$variant => $unaligned, )* }
217 }
218
219 #[inline(always)]
222 pub const fn unroll_factor(self) -> usize {
223 match self { $( $(#[cfg $cfg])? Self::$variant => $unroll, )* }
224 }
225
226 #[inline(always)]
230 pub const fn has_masked_operations(self) -> bool {
231 match self { $( $(#[cfg $cfg])? Self::$variant => $masked, )* }
232 }
233 }
234 };
235}
236
237isa_properties! {
238 Scalar {
239 registers: 1, fma: false, simd: false, unaligned_cheap: true, unroll: 4, masked: false,
240 }
241
242 #[cfg(feature = "std_simd")]
243 StdSimd {
244 registers: 1, fma: false, simd: true, unaligned_cheap: false, unroll: 1, masked: false,
245 }
246
247 Unknown {
248 registers: 1, fma: false, simd: false, unaligned_cheap: false, unroll: 1, masked: false,
249 }
250
251 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
253 X86V1 {
254 registers: 8, fma: false, simd: true, unaligned_cheap: false, unroll: 4, masked: false,
255 }
256
257 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
258 X86V2 {
259 registers: 16, fma: false, simd: true, unaligned_cheap: false, unroll: 4, masked: false,
260 }
261
262 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
264 X86V3 {
265 registers: 16, fma: true, simd: true, unaligned_cheap: true, unroll: 4, masked: false,
266 }
267
268 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
271 X86V4 {
272 registers: 32, fma: true, simd: true, unaligned_cheap: true, unroll: 8, masked: true,
273 }
274
275 #[cfg(target_arch = "aarch64")]
276 NEON {
277 registers: 32, fma: true, simd: true, unaligned_cheap: true, unroll: 4, masked: false,
278 }
279
280 #[cfg(all(feature = "wasm", target_arch = "wasm32"))]
283 WASM32 {
284 registers: 16, fma: false, simd: true, unaligned_cheap: false, unroll: 2, masked: false,
285 }
286
287 #[cfg(all(feature = "wasm", target_arch = "wasm64"))]
288 WASM64 {
289 registers: 16, fma: false, simd: true, unaligned_cheap: false, unroll: 2, masked: false,
290 }
291
292 #[cfg(all(feature = "spirv", target_arch = "spirv"))]
295 SPIRV {
296 registers: 1, fma: true, simd: false, unaligned_cheap: true, unroll: 1, masked: false,
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::InstructionSet;
303
304 fn all() -> &'static [InstructionSet] {
306 &[
307 InstructionSet::Scalar,
308 InstructionSet::Unknown,
309 #[cfg(feature = "std_simd")]
310 InstructionSet::StdSimd,
311 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
312 InstructionSet::X86V1,
313 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
314 InstructionSet::X86V2,
315 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
316 InstructionSet::X86V3,
317 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
318 InstructionSet::X86V4,
319 #[cfg(target_arch = "aarch64")]
320 InstructionSet::NEON,
321 #[cfg(all(feature = "wasm", target_arch = "wasm32"))]
322 InstructionSet::WASM32,
323 #[cfg(all(feature = "wasm", target_arch = "wasm64"))]
324 InstructionSet::WASM64,
325 ]
326 }
327
328 #[test]
331 fn properties_are_sane() {
332 for &isa in all() {
333 assert!(isa.num_registers() >= 1, "{isa:?}: zero registers");
334 assert!(isa.unroll_factor() >= 1, "{isa:?}: zero unroll factor");
335 assert!(
337 !isa.has_masked_operations() || isa.is_simd(),
338 "{isa:?}: masked but not SIMD"
339 );
340 }
341
342 assert!(!InstructionSet::Scalar.is_simd());
343 assert!(!InstructionSet::Unknown.is_simd());
344 assert!(!InstructionSet::Scalar.has_fma());
345 }
346
347 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
351 #[test]
352 fn x86_rows() {
353 use InstructionSet::{X86V1, X86V2, X86V3, X86V4};
354
355 assert_eq!((X86V1.num_registers(), X86V2.num_registers()), (8, 16));
356 assert_eq!((X86V3.num_registers(), X86V4.num_registers()), (16, 32));
357
358 assert!(!X86V1.has_fma() && !X86V2.has_fma() && X86V3.has_fma() && X86V4.has_fma());
360
361 assert!(!X86V1.unaligned_is_cheap() && !X86V2.unaligned_is_cheap());
363 assert!(X86V3.unaligned_is_cheap() && X86V4.unaligned_is_cheap());
364
365 assert!(!X86V1.has_masked_operations() && !X86V2.has_masked_operations());
367 assert!(!X86V3.has_masked_operations() && X86V4.has_masked_operations());
368
369 assert_eq!(X86V3.unroll_factor(), 4);
371 assert_eq!(X86V4.unroll_factor(), 8);
372
373 assert!(InstructionSet::Scalar < X86V1 && X86V1 < X86V2 && X86V2 < X86V3 && X86V3 < X86V4);
375 assert_eq!(InstructionSet::min(X86V2, X86V4), X86V2);
376 assert_eq!(InstructionSet::max(X86V2, X86V4), X86V4);
377 assert_eq!(InstructionSet::assert_eq(X86V3, X86V3), X86V3);
378 }
379
380 #[test]
381 #[should_panic(expected = "InstructionSet equality assertion failed")]
382 fn assert_eq_rejects_mismatch() {
383 InstructionSet::assert_eq(InstructionSet::Scalar, InstructionSet::Unknown);
384 }
385
386 #[test]
390 fn ilp_does_not_vary_by_variant() {
391 let expected = InstructionSet::Scalar.has_instruction_level_parallelism();
392 for &isa in all() {
393 assert_eq!(isa.has_instruction_level_parallelism(), expected, "{isa:?}");
394 }
395 assert_eq!(
396 expected,
397 cfg!(any(
398 target_arch = "x86",
399 target_arch = "x86_64",
400 target_arch = "arm",
401 target_arch = "aarch64"
402 ))
403 );
404 }
405
406 #[test]
408 fn get_is_available() {
409 let isa = InstructionSet::get();
410 assert!(
411 all().contains(&isa) || isa == InstructionSet::Scalar,
412 "{isa:?} is not a compiled variant"
413 );
414 }
415}