lamina_platform/
cpu_level.rs1use std::{fmt, str::FromStr};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
6pub enum X86CpuLevel {
7 V1,
8 #[default]
9 V2,
10 V3,
11 V4,
12}
13
14impl X86CpuLevel {
15 pub const ALL: [X86CpuLevel; 4] = [Self::V1, Self::V2, Self::V3, Self::V4];
16
17 pub fn features(self) -> &'static str {
18 match self {
19 Self::V1 => "SSE2",
20 Self::V2 => "SSE3, SSSE3, SSE4.1, SSE4.2, POPCNT, CMPXCHG16B",
21 Self::V3 => "v2 + AVX, AVX2, BMI1, BMI2, F16C, FMA, LZCNT, MOVBE",
22 Self::V4 => "v3 + AVX-512F, AVX-512BW, AVX-512CD, AVX-512DQ, AVX-512VL",
23 }
24 }
25}
26
27impl fmt::Display for X86CpuLevel {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 let n = match self {
30 Self::V1 => 1,
31 Self::V2 => 2,
32 Self::V3 => 3,
33 Self::V4 => 4,
34 };
35 write!(f, "x86-64-v{n}")
36 }
37}
38
39impl FromStr for X86CpuLevel {
40 type Err = String;
41
42 fn from_str(s: &str) -> Result<Self, Self::Err> {
43 let lower = s.to_ascii_lowercase();
44 let digit = lower
45 .strip_prefix("x86-64-v")
46 .or_else(|| lower.strip_prefix('v'))
47 .unwrap_or("");
48 match digit {
49 "1" => Ok(Self::V1),
50 "2" => Ok(Self::V2),
51 "3" => Ok(Self::V3),
52 "4" => Ok(Self::V4),
53 _ => Err(format!(
54 "unknown CPU level '{s}' (expected v1, v2, v3 or v4)"
55 )),
56 }
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn parses_short_and_psabi_names() {
66 assert_eq!("v1".parse(), Ok(X86CpuLevel::V1));
67 assert_eq!("V2".parse(), Ok(X86CpuLevel::V2));
68 assert_eq!("x86-64-v3".parse(), Ok(X86CpuLevel::V3));
69 assert_eq!("X86-64-V4".parse(), Ok(X86CpuLevel::V4));
70 assert!("v5".parse::<X86CpuLevel>().is_err());
71 assert!("native".parse::<X86CpuLevel>().is_err());
72 }
73
74 #[test]
75 fn levels_are_ordered_and_default_to_v2() {
76 assert!(X86CpuLevel::V1 < X86CpuLevel::V2);
77 assert!(X86CpuLevel::V4 > X86CpuLevel::V3);
78 assert_eq!(X86CpuLevel::default(), X86CpuLevel::V2);
79 assert_eq!(X86CpuLevel::V2.to_string(), "x86-64-v2");
80 }
81}