Skip to main content

tenferro_fft/
backend.rs

1use std::fmt;
2
3use tenferro_runtime::ExtensionCacheStore;
4use tenferro_tensor::{BackendSession, Tensor, TensorRead};
5
6use crate::{FftPlanCache, FftPlanSpec};
7
8#[derive(Clone, Copy, Debug)]
9enum FftCacheOwner {
10    CallerOwned,
11    RuntimeOwned,
12}
13
14/// Execution-cache state supplied to an [`FftBackend`].
15///
16/// Direct repeated calls use a caller-owned [`FftPlanCache`], while traced
17/// execution uses the owning runtime's [`ExtensionCacheStore`]. Both ownership
18/// paths expose the same bounded typed store to a backend, so CPU, Metal, CUDA,
19/// and future implementations can retain private plans or workspaces in their
20/// own cache namespace. Constructors keep the owner representation closed.
21///
22/// # Examples
23///
24/// ```
25/// use tenferro_fft::{FftExecutionCache, FftPlanCache};
26///
27/// let mut plans = FftPlanCache::default();
28/// let cache = FftExecutionCache::caller_owned(&mut plans);
29/// assert!(format!("{cache:?}").contains("CallerOwned"));
30/// ```
31pub struct FftExecutionCache<'a> {
32    owner: FftCacheOwner,
33    store: &'a mut ExtensionCacheStore,
34}
35
36impl fmt::Debug for FftExecutionCache<'_> {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        f.debug_struct("FftExecutionCache")
39            .field("owner", &self.owner)
40            .field(
41                "stats",
42                &self
43                    .store
44                    .stats(tenferro_runtime::ExtensionCacheSelector::All),
45            )
46            .finish_non_exhaustive()
47    }
48}
49
50impl<'a> FftExecutionCache<'a> {
51    /// Build a context backed by a caller-owned typed FFT execution cache.
52    pub fn caller_owned(cache: &'a mut FftPlanCache) -> Self {
53        Self {
54            owner: FftCacheOwner::CallerOwned,
55            store: cache.store_mut(),
56        }
57    }
58
59    /// Build a context backed by an extension runtime cache store.
60    pub fn runtime_owned(cache: &'a mut ExtensionCacheStore) -> Self {
61        Self {
62            owner: FftCacheOwner::RuntimeOwned,
63            store: cache,
64        }
65    }
66
67    /// Borrow the bounded typed store owned by the caller or extension runtime.
68    ///
69    /// Backend implementations should use a stable family/cache namespace and
70    /// include every plan-identity field in the key discriminator. The store
71    /// owns LRU bounds, typed retrieval, clear behavior, entry counts, and the
72    /// retained-byte estimates supplied at insertion.
73    pub fn store_mut(&mut self) -> &mut ExtensionCacheStore {
74        self.store
75    }
76}
77
78/// Explicit backend capability required by concrete and traced FFT execution.
79///
80/// Implementations must execute on the input's existing placement. Unsupported
81/// dtypes, layouts, placements, or operations return an error; they must never
82/// transfer the tensor or select a different backend.
83///
84/// # Examples
85///
86/// ```
87/// use tenferro_cpu::{with_cpu_exec_session, CpuBackend};
88/// use tenferro_fft::FftBackend;
89/// use tenferro_tensor::BackendSessionHost;
90///
91/// fn accepts_fft_backend<B: FftBackend>(_backend: &mut B) {}
92///
93/// let mut backend = CpuBackend::new();
94/// backend.with_backend_session(|session| {
95///     with_cpu_exec_session(session, |exec_session| accepts_fft_backend(exec_session))
96///         .expect("CpuBackend must expose a CPU execution session");
97/// });
98/// ```
99///
100/// A generic tensor backend without this explicit capability cannot build
101/// the FFT extension module:
102///
103/// ```compile_fail
104/// use tenferro_tensor::{Tensor, TensorBackend};
105/// use tenferro_fft::{FftNorm, TensorFftExt};
106///
107/// fn use_without_fft_capability<B: TensorBackend + 'static>(backend: &mut B, input: &Tensor) {
108///     let _module =
109///         tenferro_fft::extension_module::<B>(tenferro_cpu::runtime_engine_id().unwrap()).unwrap();
110///     let _ = input.fft(None, -1, FftNorm::Backward, backend);
111/// }
112/// ```
113pub trait FftBackend: BackendSession {
114    /// Validate a borrowed runtime input before the extension read path
115    /// materializes it into a compact tensor.
116    ///
117    /// Backends that can materialize their own placement may keep the default
118    /// no-op. CPU overrides this to report device inputs as unsupported FFT
119    /// placement errors instead of leaking a generic host-materialization error.
120    ///
121    /// # Errors
122    ///
123    /// Returns [`tenferro_tensor::Error::Unsupported`] when this backend cannot
124    /// consume the borrowed input placement without an explicit transfer.
125    fn validate_fft_read_input(
126        &self,
127        _op: &'static str,
128        _input: &TensorRead<'_>,
129    ) -> tenferro_tensor::Result<()> {
130        Ok(())
131    }
132
133    /// Execute one validated FFT request on `input`'s existing placement.
134    ///
135    /// # Errors
136    ///
137    /// Returns [`tenferro_tensor::Error::Unsupported`] for an unsupported
138    /// operation, dtype, layout, or placement;
139    /// [`tenferro_tensor::Error::Validation`] for inconsistent input/spec
140    /// metadata or checked shape arithmetic; and a typed backend or runtime
141    /// source when plan creation, cache access, or execution fails.
142    fn execute_fft(
143        &mut self,
144        input: &Tensor,
145        spec: &FftPlanSpec,
146        cache: FftExecutionCache<'_>,
147    ) -> tenferro_tensor::Result<Tensor>;
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn execution_cache_debug_identifies_both_owners_and_exposes_the_store() {
156        let mut caller = FftPlanCache::default();
157        let mut caller_cache = FftExecutionCache::caller_owned(&mut caller);
158        assert!(format!("{caller_cache:?}").contains("CallerOwned"));
159        assert_eq!(
160            caller_cache
161                .store_mut()
162                .stats(tenferro_runtime::ExtensionCacheSelector::All)
163                .entries,
164            0
165        );
166
167        let mut runtime = ExtensionCacheStore::default();
168        let mut runtime_cache = FftExecutionCache::runtime_owned(&mut runtime);
169        assert!(format!("{runtime_cache:?}").contains("RuntimeOwned"));
170        assert_eq!(
171            runtime_cache
172                .store_mut()
173                .stats(tenferro_runtime::ExtensionCacheSelector::All)
174                .entries,
175            0
176        );
177    }
178}