axiolid_backend_cpu/
config.rs

1//! Validated CPU backend builder.
2
3use core::fmt;
4use std::num::NonZeroUsize;
5
6use crate::{CpuExecution, CpuFeatures, CpuInstructionSet};
7
8/// Instruction-selection policy.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum InstructionPolicy {
11    /// Best runtime-detected path compiled into this crate.
12    Auto,
13    /// Always portable scalar.
14    Portable,
15    /// Fail construction unless one path is supported.
16    Require(CpuInstructionSet),
17}
18
19/// Invalid backend configuration.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum CpuConfigError {
22    /// Required instruction set is absent on this machine.
23    UnsupportedInstructionSet(CpuInstructionSet),
24    /// More than one thread was requested without the `parallel` feature.
25    ParallelFeatureDisabled,
26    /// Local Rayon pool construction failed.
27    ThreadPool(String),
28}
29
30impl fmt::Display for CpuConfigError {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        match self {
33            Self::UnsupportedInstructionSet(value) => {
34                write!(f, "required CPU instruction set {value:?} is unavailable")
35            }
36            Self::ParallelFeatureDisabled => {
37                f.write_str("multiple workers require axiolid-backend-cpu's `parallel` feature")
38            }
39            Self::ThreadPool(reason) => {
40                write!(f, "failed to build local CPU thread pool: {reason}")
41            }
42        }
43    }
44}
45
46impl std::error::Error for CpuConfigError {}
47
48/// Builder for a validated backend bound to current runtime capabilities.
49#[derive(Debug, Clone, Copy)]
50pub struct CpuExecutionBuilder {
51    instruction_policy: InstructionPolicy,
52    threads: NonZeroUsize,
53}
54
55impl Default for CpuExecutionBuilder {
56    fn default() -> Self {
57        Self {
58            instruction_policy: InstructionPolicy::Auto,
59            threads: NonZeroUsize::MIN,
60        }
61    }
62}
63
64impl CpuExecutionBuilder {
65    /// New portable-safe builder.
66    pub fn new() -> Self {
67        Self::default()
68    }
69
70    /// Choose instruction policy.
71    pub const fn instruction_policy(mut self, policy: InstructionPolicy) -> Self {
72        self.instruction_policy = policy;
73        self
74    }
75
76    /// Set a nonzero worker bound.
77    pub const fn threads(mut self, threads: NonZeroUsize) -> Self {
78        self.threads = threads;
79        self
80    }
81
82    /// Detect hardware, validate policy, and construct the backend.
83    pub fn build(self) -> Result<CpuExecution, CpuConfigError> {
84        let features = CpuFeatures::detect();
85        let instruction_set = match self.instruction_policy {
86            InstructionPolicy::Portable => CpuInstructionSet::Portable,
87            InstructionPolicy::Auto => {
88                #[cfg(feature = "simd")]
89                {
90                    features.best()
91                }
92                #[cfg(not(feature = "simd"))]
93                {
94                    CpuInstructionSet::Portable
95                }
96            }
97            InstructionPolicy::Require(value) => {
98                if value != CpuInstructionSet::Portable && !cfg!(feature = "simd") {
99                    return Err(CpuConfigError::UnsupportedInstructionSet(value));
100                }
101                if !features.supports(value) {
102                    return Err(CpuConfigError::UnsupportedInstructionSet(value));
103                }
104                value
105            }
106        };
107        CpuExecution::from_configuration(instruction_set, features, self.threads)
108    }
109}