Skip to main content

j2k_core/
backend.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// j2k-coverage: shared-accelerator-host
3
4use core::sync::atomic::{AtomicU8, Ordering};
5
6/// Runtime backend that executes codec work.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum BackendKind {
9    /// Portable CPU implementation.
10    Cpu,
11    /// Apple Metal implementation.
12    Metal,
13    /// NVIDIA CUDA implementation.
14    Cuda,
15}
16
17/// Caller preference for backend selection.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
19pub enum BackendRequest {
20    /// Let the codec choose the best available backend.
21    #[default]
22    Auto,
23    /// Force the portable CPU backend.
24    Cpu,
25    /// Force Metal and fail if unavailable.
26    Metal,
27    /// Force CUDA and fail if unavailable.
28    Cuda,
29}
30
31impl BackendRequest {
32    /// Adaptive accelerated route: let the codec choose CPU and device stages
33    /// for benchmark-approved workload shapes.
34    pub const ACCELERATED: Self = Self::Auto;
35    /// Explicit portable CPU route.
36    pub const CPU_ONLY: Self = Self::Cpu;
37    /// Strict Metal route; fail when Metal is unavailable or unsupported.
38    pub const STRICT_METAL: Self = Self::Metal;
39    /// Strict CUDA route; fail when CUDA is unavailable or unsupported.
40    pub const STRICT_CUDA: Self = Self::Cuda;
41}
42
43/// CPU SIMD feature flags detected for the current host.
44#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
45pub struct CpuFeatures {
46    /// True when AVX2 is available and enabled by the OS.
47    pub avx2: bool,
48    /// True when SSE4.1 is available.
49    pub sse41: bool,
50    /// True when NEON is available.
51    pub neon: bool,
52}
53
54impl CpuFeatures {
55    /// Detect CPU SIMD features once and reuse the cached result.
56    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    // SAFETY: XGETBV is only executed after CPUID reports OSXSAVE support.
135    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/// Backend availability for a codec/runtime combination.
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub struct BackendCapabilities {
154    /// Host CPU feature set.
155    pub cpu: CpuFeatures,
156    /// True when Metal is available to this crate.
157    pub metal: bool,
158    /// True when CUDA is available to this crate.
159    pub cuda: bool,
160}
161
162impl BackendCapabilities {
163    /// Return default capabilities implied by the current build target.
164    ///
165    /// This does not probe GPU devices or runtime libraries. Codec facades and
166    /// adapters must further gate the returned device flags by their compiled
167    /// features and runtime availability.
168    #[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    /// Return whether a backend request can be satisfied.
178    #[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    /// Resolve a backend request to the concrete backend that should run.
188    ///
189    /// `Auto` resolves to CPU here. Workload-aware device promotion belongs in
190    /// codec-specific route planners that have benchmark evidence for the
191    /// requested operation.
192    #[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    /// Return an available accelerator backend without implying it should be
203    /// selected for a workload.
204    #[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}