1use core::sync::atomic::{AtomicU8, Ordering};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum BackendKind {
9 Cpu,
11 Metal,
13 Cuda,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
19pub enum BackendRequest {
20 #[default]
22 Auto,
23 Cpu,
25 Metal,
27 Cuda,
29}
30
31impl BackendRequest {
32 pub const ACCELERATED: Self = Self::Auto;
35 pub const CPU_ONLY: Self = Self::Cpu;
37 pub const STRICT_METAL: Self = Self::Metal;
39 pub const STRICT_CUDA: Self = Self::Cuda;
41}
42
43#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
45pub struct CpuFeatures {
46 pub avx2: bool,
48 pub sse41: bool,
50 pub neon: bool,
52}
53
54impl CpuFeatures {
55 pub fn detect() -> Self {
57 static DETECTED: AtomicU8 = AtomicU8::new(0);
58
59 let cached = DETECTED.load(Ordering::Acquire);
60 if cached != 0 {
61 return Self::from_cache_byte(cached);
62 }
63
64 let detected = Self::detect_uncached();
65 let encoded = detected.to_cache_byte();
66 let _ = DETECTED.compare_exchange(0, encoded, Ordering::AcqRel, Ordering::Acquire);
67 Self::from_cache_byte(DETECTED.load(Ordering::Acquire))
68 }
69
70 fn detect_uncached() -> Self {
71 #[cfg(target_arch = "x86_64")]
72 {
73 Self {
74 avx2: detect_x86_avx2(),
75 sse41: detect_x86_sse41(),
76 neon: false,
77 }
78 }
79
80 #[cfg(target_arch = "aarch64")]
81 {
82 Self {
83 avx2: false,
84 sse41: false,
85 neon: true,
86 }
87 }
88
89 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
90 {
91 Self::default()
92 }
93 }
94
95 const fn to_cache_byte(self) -> u8 {
96 let mut encoded = 1_u8;
97 if self.avx2 {
98 encoded |= 1 << 1;
99 }
100 if self.sse41 {
101 encoded |= 1 << 2;
102 }
103 if self.neon {
104 encoded |= 1 << 3;
105 }
106 encoded
107 }
108
109 const fn from_cache_byte(encoded: u8) -> Self {
110 let bits = encoded.saturating_sub(1);
111 Self {
112 avx2: (bits & (1 << 1)) != 0,
113 sse41: (bits & (1 << 2)) != 0,
114 neon: (bits & (1 << 3)) != 0,
115 }
116 }
117}
118
119#[cfg(target_arch = "x86_64")]
120fn detect_x86_sse41() -> bool {
121 let features = core::arch::x86_64::__cpuid(1);
122 (features.ecx & (1 << 19)) != 0
123}
124
125#[cfg(target_arch = "x86_64")]
126fn detect_x86_avx2() -> bool {
127 let leaf1 = core::arch::x86_64::__cpuid(1);
128 let osxsave = (leaf1.ecx & (1 << 27)) != 0;
129 let avx = (leaf1.ecx & (1 << 28)) != 0;
130 if !(osxsave && avx) {
131 return false;
132 }
133
134 let xcr0 = unsafe { core::arch::x86_64::_xgetbv(0) };
136 let xmm_enabled = (xcr0 & 0b10) != 0;
137 let ymm_enabled = (xcr0 & 0b100) != 0;
138 if !(xmm_enabled && ymm_enabled) {
139 return false;
140 }
141
142 let max_leaf = core::arch::x86_64::__cpuid(0).eax;
143 if max_leaf < 7 {
144 return false;
145 }
146
147 let leaf7 = core::arch::x86_64::__cpuid_count(7, 0);
148 (leaf7.ebx & (1 << 5)) != 0
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub struct BackendCapabilities {
154 pub cpu: CpuFeatures,
156 pub metal: bool,
158 pub cuda: bool,
160}
161
162impl BackendCapabilities {
163 #[must_use]
169 pub fn compile_time_defaults() -> Self {
170 Self {
171 cpu: CpuFeatures::detect(),
172 metal: cfg!(target_os = "macos"),
173 cuda: false,
174 }
175 }
176
177 #[must_use]
179 pub const fn supports(self, request: BackendRequest) -> bool {
180 match request {
181 BackendRequest::Auto | BackendRequest::Cpu => true,
182 BackendRequest::Metal => self.metal,
183 BackendRequest::Cuda => self.cuda,
184 }
185 }
186
187 #[must_use]
193 pub fn resolve(self, request: BackendRequest) -> Option<BackendKind> {
194 match request {
195 BackendRequest::Auto | BackendRequest::Cpu => Some(BackendKind::Cpu),
196 BackendRequest::Metal if self.metal => Some(BackendKind::Metal),
197 BackendRequest::Cuda if self.cuda => Some(BackendKind::Cuda),
198 BackendRequest::Metal | BackendRequest::Cuda => None,
199 }
200 }
201
202 #[must_use]
205 pub const fn first_available_accelerator(self) -> Option<BackendKind> {
206 if self.metal {
207 Some(BackendKind::Metal)
208 } else if self.cuda {
209 Some(BackendKind::Cuda)
210 } else {
211 None
212 }
213 }
214}