trueno/brick/simd_config/
mod.rs1use super::ComputeBackend;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
17pub enum SimdBackendState {
18 #[default]
20 Uninitialized,
21 Configuring,
23 Ready,
25 Failed,
27}
28
29#[derive(Debug)]
33pub struct LazySimdConfig {
34 state: SimdBackendState,
36 best_backend: ComputeBackend,
38 amx_supported: bool,
40 tile_config: Option<AmxTileConfig>,
42}
43
44#[derive(Debug, Clone, Copy, Default)]
46pub struct AmxTileConfig {
47 pub palette: u8,
49 pub start_row: u8,
51 pub rows: u8,
53 pub bytes_per_row: u16,
55}
56
57impl LazySimdConfig {
58 #[must_use]
60 pub fn new() -> Self {
61 Self {
62 state: SimdBackendState::Uninitialized,
63 best_backend: Self::detect_best_backend(),
64 amx_supported: Self::detect_amx(),
65 tile_config: None,
66 }
67 }
68
69 fn detect_best_backend() -> ComputeBackend {
71 #[cfg(target_arch = "x86_64")]
72 {
73 if is_x86_feature_detected!("avx512f") {
74 return ComputeBackend::Avx512;
75 }
76 if is_x86_feature_detected!("avx2") {
77 return ComputeBackend::Avx2;
78 }
79 if is_x86_feature_detected!("sse2") {
80 return ComputeBackend::Sse2;
81 }
82 }
83 #[cfg(target_arch = "aarch64")]
84 {
85 ComputeBackend::Neon
87 }
88 #[cfg(not(target_arch = "aarch64"))]
89 {
90 ComputeBackend::Scalar
91 }
92 }
93
94 fn detect_amx() -> bool {
96 #[cfg(target_arch = "x86_64")]
97 {
98 false
101 }
102 #[cfg(not(target_arch = "x86_64"))]
103 {
104 false
105 }
106 }
107
108 pub fn ensure_ready(&mut self) -> Result<ComputeBackend, SimdBackendState> {
110 match self.state {
111 SimdBackendState::Ready => Ok(self.best_backend),
112 SimdBackendState::Failed => Err(SimdBackendState::Failed),
113 SimdBackendState::Configuring => Err(SimdBackendState::Configuring),
114 SimdBackendState::Uninitialized => {
115 self.state = SimdBackendState::Configuring;
116
117 if self.amx_supported {
119 self.tile_config = Some(AmxTileConfig {
120 palette: 1,
121 start_row: 0,
122 rows: 16,
123 bytes_per_row: 64,
124 });
125 }
127
128 self.state = SimdBackendState::Ready;
129 Ok(self.best_backend)
130 }
131 }
132 }
133
134 #[must_use]
136 pub fn state(&self) -> SimdBackendState {
137 self.state
138 }
139
140 #[must_use]
142 pub fn best_backend(&self) -> ComputeBackend {
143 self.best_backend
144 }
145
146 #[must_use]
148 pub fn has_amx(&self) -> bool {
149 self.amx_supported
150 }
151
152 pub fn reset(&mut self) {
154 self.state = SimdBackendState::Uninitialized;
155 self.tile_config = None;
156 }
157}
158
159impl Default for LazySimdConfig {
160 fn default() -> Self {
161 Self::new()
162 }
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub enum UnrollFactor {
172 None,
174 X2,
176 X4,
178 X8,
180}
181
182impl UnrollFactor {
183 #[must_use]
185 pub fn value(&self) -> usize {
186 match self {
187 UnrollFactor::None => 1,
188 UnrollFactor::X2 => 2,
189 UnrollFactor::X4 => 4,
190 UnrollFactor::X8 => 8,
191 }
192 }
193
194 #[must_use]
196 pub fn for_backend(backend: ComputeBackend) -> Self {
197 match backend {
198 ComputeBackend::Avx512 => UnrollFactor::X8,
199 ComputeBackend::Avx2 => UnrollFactor::X4,
200 ComputeBackend::Sse2 | ComputeBackend::Neon => UnrollFactor::X2,
201 _ => UnrollFactor::None,
202 }
203 }
204}
205
206#[derive(Debug)]
210pub struct UnrollTailIterator {
211 total: usize,
213 position: usize,
215 chunk_size: usize,
217}
218
219impl UnrollTailIterator {
220 pub fn new(total: usize, factor: UnrollFactor) -> Self {
222 Self { total, position: 0, chunk_size: factor.value() }
223 }
224
225 #[must_use]
227 pub fn full_iterations(&self) -> usize {
228 self.total / self.chunk_size
229 }
230
231 #[must_use]
233 pub fn tail_size(&self) -> usize {
234 self.total % self.chunk_size
235 }
236
237 #[must_use]
239 pub fn has_tail(&self) -> bool {
240 self.tail_size() > 0
241 }
242
243 pub fn next_chunk(&mut self) -> Option<(usize, usize)> {
245 if self.position + self.chunk_size <= self.total {
246 let start = self.position;
247 self.position += self.chunk_size;
248 Some((start, start + self.chunk_size))
249 } else {
250 None
251 }
252 }
253
254 pub fn tail_range(&self) -> Option<(usize, usize)> {
256 let tail_start = self.full_iterations() * self.chunk_size;
257 if tail_start < self.total {
258 Some((tail_start, self.total))
259 } else {
260 None
261 }
262 }
263}
264
265pub fn unroll_tail_process<T, U, F, G>(
277 data: &[T],
278 factor: UnrollFactor,
279 mut process_chunk: F,
280 mut process_elem: G,
281) -> Vec<U>
282where
283 F: FnMut(&[T]) -> U,
284 G: FnMut(&T) -> U,
285{
286 let mut iter = UnrollTailIterator::new(data.len(), factor);
287 let mut results =
288 Vec::with_capacity(iter.full_iterations() + if iter.has_tail() { 1 } else { 0 });
289
290 while let Some((start, end)) = iter.next_chunk() {
292 results.push(process_chunk(&data[start..end]));
293 }
294
295 if let Some((start, end)) = iter.tail_range() {
297 for elem in &data[start..end] {
298 results.push(process_elem(elem));
299 }
300 }
301
302 results
303}
304
305#[cfg(test)]
306mod tests;