axiolid_backend_cpu/
execution.rs1use core::fmt;
4use std::num::NonZeroUsize;
5#[cfg(feature = "parallel")]
6use std::sync::Arc;
7
8use crate::{CpuConfigError, CpuFeatures, CpuInstructionSet};
9
10#[derive(Clone)]
16pub struct CpuExecution {
17 instruction_set: CpuInstructionSet,
18 features: CpuFeatures,
19 threads: NonZeroUsize,
20 #[cfg(feature = "parallel")]
21 pool: Option<Arc<rayon::ThreadPool>>,
22}
23
24impl fmt::Debug for CpuExecution {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 f.debug_struct("CpuExecution")
27 .field("instruction_set", &self.instruction_set)
28 .field("features", &self.features)
29 .field("threads", &self.threads)
30 .finish_non_exhaustive()
31 }
32}
33
34impl CpuExecution {
35 pub fn portable() -> Self {
37 Self {
38 instruction_set: CpuInstructionSet::Portable,
39 features: CpuFeatures::detect(),
40 threads: NonZeroUsize::MIN,
41 #[cfg(feature = "parallel")]
42 pool: None,
43 }
44 }
45
46 pub fn detect() -> Self {
48 let features = CpuFeatures::detect();
49 #[cfg(feature = "simd")]
50 let instruction_set = features.best();
51 #[cfg(not(feature = "simd"))]
52 let instruction_set = CpuInstructionSet::Portable;
53 Self {
54 instruction_set,
55 features,
56 threads: NonZeroUsize::MIN,
57 #[cfg(feature = "parallel")]
58 pool: None,
59 }
60 }
61
62 pub(crate) fn from_configuration(
63 instruction_set: CpuInstructionSet,
64 features: CpuFeatures,
65 threads: NonZeroUsize,
66 ) -> Result<Self, CpuConfigError> {
67 #[cfg(not(feature = "parallel"))]
68 if threads.get() > 1 {
69 return Err(CpuConfigError::ParallelFeatureDisabled);
70 }
71
72 #[cfg(feature = "parallel")]
73 let pool = if threads.get() > 1 {
74 Some(Arc::new(
75 rayon::ThreadPoolBuilder::new()
76 .num_threads(threads.get())
77 .thread_name(|index| format!("axiolid-cpu-{index}"))
78 .build()
79 .map_err(|error| CpuConfigError::ThreadPool(error.to_string()))?,
80 ))
81 } else {
82 None
83 };
84
85 Ok(Self {
86 instruction_set,
87 features,
88 threads,
89 #[cfg(feature = "parallel")]
90 pool,
91 })
92 }
93
94 pub const fn instruction_set(&self) -> CpuInstructionSet {
96 self.instruction_set
97 }
98
99 pub const fn features(&self) -> CpuFeatures {
101 self.features
102 }
103
104 pub const fn thread_count(&self) -> NonZeroUsize {
106 self.threads
107 }
108
109 #[cfg(feature = "parallel")]
111 pub fn install<R: Send>(&self, operation: impl FnOnce() -> R + Send) -> R {
112 match &self.pool {
113 Some(pool) => pool.install(operation),
114 None => operation(),
115 }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use crate::{CpuExecutionBuilder, InstructionPolicy};
123
124 fn assert_runtime_traits<T: fmt::Debug + Clone + Send + Sync>() {}
125
126 #[test]
127 fn portable_context_is_constructible_but_claims_no_operation_trait() {
128 assert_runtime_traits::<CpuExecution>();
129 let backend = CpuExecutionBuilder::new()
130 .instruction_policy(InstructionPolicy::Portable)
131 .build()
132 .expect("portable context");
133 assert_eq!(backend.instruction_set(), CpuInstructionSet::Portable);
134 }
135
136 #[cfg(not(feature = "simd"))]
137 #[test]
138 fn auto_selection_stays_portable_without_the_simd_feature() {
139 assert_eq!(
140 CpuExecution::detect().instruction_set(),
141 CpuInstructionSet::Portable
142 );
143 }
144
145 #[cfg(feature = "simd")]
146 #[test]
147 fn auto_selection_uses_the_best_runtime_supported_instruction_set() {
148 let backend = CpuExecution::detect();
149 assert_eq!(backend.instruction_set(), backend.features().best());
150 assert!(backend.features().supports(backend.instruction_set()));
151 }
152
153 #[cfg(feature = "parallel")]
154 #[test]
155 fn configured_worker_bound_controls_the_local_pool() {
156 let backend = CpuExecutionBuilder::new()
157 .threads(NonZeroUsize::new(2).unwrap())
158 .build()
159 .expect("two-worker context");
160 assert_eq!(backend.thread_count().get(), 2);
161 assert_eq!(backend.install(rayon::current_num_threads), 2);
162 }
163
164 #[cfg(not(feature = "parallel"))]
165 #[test]
166 fn multiple_workers_require_the_parallel_feature() {
167 let error = CpuExecutionBuilder::new()
168 .threads(NonZeroUsize::new(2).unwrap())
169 .build()
170 .unwrap_err();
171 assert_eq!(error, CpuConfigError::ParallelFeatureDisabled);
172 }
173}