1use std::collections::HashMap;
9use std::fmt::{self, Debug};
10use std::marker::PhantomData;
11use std::sync::Arc;
12
13use tenferro_ops::ext_op::ExtensionOp;
14use tenferro_tensor::{CacheStats, Tensor, TensorBackend, TensorRead};
15
16use crate::extension_cache::{ExtensionCacheLimits, ExtensionCacheSelector, ExtensionCacheStore};
17
18#[derive(Debug, thiserror::Error)]
20pub enum ExtensionRuntimeRegistryError {
21 #[error("family_id {family_id:?} does not match the namespaced format")]
24 MalformedFamilyId { family_id: &'static str },
25 #[error("{name} poisoned")]
27 PoisonedLock { name: &'static str },
28}
29
30pub struct ExtensionExecutionContext<'a, B: TensorBackend> {
32 backend: &'a mut B,
33 caches: &'a mut ExtensionCacheStore,
34}
35
36impl<B: TensorBackend> fmt::Debug for ExtensionExecutionContext<'_, B> {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 f.debug_struct("ExtensionExecutionContext")
39 .field("backend_type", &std::any::type_name::<B>())
40 .field("caches", &self.caches)
41 .finish_non_exhaustive()
42 }
43}
44
45impl<'a, B: TensorBackend> ExtensionExecutionContext<'a, B> {
46 pub fn new(backend: &'a mut B, caches: &'a mut ExtensionCacheStore) -> Self {
48 Self { backend, caches }
49 }
50
51 pub fn backend(&self) -> &B {
53 self.backend
54 }
55
56 pub fn backend_mut(&mut self) -> &mut B {
58 self.backend
59 }
60
61 pub fn caches(&self) -> &ExtensionCacheStore {
63 self.caches
64 }
65
66 pub fn caches_mut(&mut self) -> &mut ExtensionCacheStore {
68 self.caches
69 }
70
71 pub fn execute_core_exec_program_unsegmented(
112 &mut self,
113 program: &crate::extension::ExecProgram,
114 inputs: Vec<Tensor>,
115 ) -> crate::error::Result<Vec<Tensor>>
116 where
117 B: 'static,
118 {
119 crate::exec::ensure_core_exec_program(
120 program,
121 "ExtensionExecutionContext::execute_core_exec_program_unsegmented",
122 )?;
123 crate::exec::eval_exec_ir_unsegmented_with_cache(self.backend, program, inputs)
124 }
125
126 pub fn parts_mut(&mut self) -> (&mut B, &mut ExtensionCacheStore) {
128 (self.backend, self.caches)
129 }
130}
131
132pub trait ExtensionRuntime<B: TensorBackend + 'static>: Debug + Send + Sync + 'static {
134 fn family_id(&self) -> &'static str;
136
137 fn execute(
139 &self,
140 op: &dyn ExtensionOp,
141 inputs: &[&Tensor],
142 ctx: &mut ExtensionExecutionContext<'_, B>,
143 ) -> tenferro_tensor::Result<Vec<Tensor>>;
144
145 fn execute_reads(
151 &self,
152 op: &dyn ExtensionOp,
153 inputs: &[TensorRead<'_>],
154 ctx: &mut ExtensionExecutionContext<'_, B>,
155 ) -> tenferro_tensor::Result<Vec<Tensor>>;
156}
157
158#[derive(Clone, Copy)]
174pub struct HostReferenceRuntime<B: TensorBackend + 'static> {
175 family_id: &'static str,
176 _backend: PhantomData<fn() -> B>,
177}
178
179impl<B: TensorBackend + 'static> HostReferenceRuntime<B> {
180 pub fn new(family_id: &'static str) -> Self {
182 Self {
183 family_id,
184 _backend: PhantomData,
185 }
186 }
187}
188
189impl<B: TensorBackend + 'static> Debug for HostReferenceRuntime<B> {
190 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191 f.debug_struct("HostReferenceRuntime")
192 .field("backend_type", &std::any::type_name::<B>())
193 .field("family_id", &self.family_id)
194 .finish()
195 }
196}
197
198impl<B: TensorBackend + 'static> ExtensionRuntime<B> for HostReferenceRuntime<B> {
199 fn family_id(&self) -> &'static str {
200 self.family_id
201 }
202
203 fn execute(
204 &self,
205 op: &dyn ExtensionOp,
206 inputs: &[&Tensor],
207 _ctx: &mut ExtensionExecutionContext<'_, B>,
208 ) -> tenferro_tensor::Result<Vec<Tensor>> {
209 let host = op
210 .host_reference()
211 .ok_or(tenferro_tensor::Error::NoHostReference {
212 family_id: op.family_id(),
213 })?;
214 host.execute(inputs)
215 }
216
217 fn execute_reads(
218 &self,
219 op: &dyn ExtensionOp,
220 inputs: &[TensorRead<'_>],
221 ctx: &mut ExtensionExecutionContext<'_, B>,
222 ) -> tenferro_tensor::Result<Vec<Tensor>> {
223 let materialized_inputs: Vec<Tensor> = inputs
224 .iter()
225 .map(TensorRead::to_tensor)
226 .collect::<tenferro_tensor::Result<_>>()?;
227 let input_refs: Vec<&Tensor> = materialized_inputs.iter().collect();
228 self.execute(op, &input_refs, ctx)
229 }
230}
231
232fn validate_runtime_output_count(
233 op: &dyn ExtensionOp,
234 outputs: Vec<Tensor>,
235) -> tenferro_tensor::Result<Vec<Tensor>> {
236 let expected = op.output_count();
237 if outputs.len() != expected {
238 return Err(tenferro_tensor::Error::InvalidConfig {
239 op: "extension",
240 message: format!(
241 "family_id {:?}: runtime returned {} outputs but op declared {} outputs",
242 op.family_id(),
243 outputs.len(),
244 expected
245 ),
246 });
247 }
248 Ok(outputs)
249}
250
251fn validate_runtime_input_count(
252 op: &dyn ExtensionOp,
253 actual: usize,
254) -> tenferro_tensor::Result<()> {
255 let expected = op.input_count();
256 if actual != expected {
257 return Err(tenferro_tensor::Error::InvalidConfig {
258 op: "extension",
259 message: format!(
260 "family_id {:?}: op expects {} inputs, got {}",
261 op.family_id(),
262 expected,
263 actual
264 ),
265 });
266 }
267 Ok(())
268}
269
270pub struct ExtensionRegistry<B: TensorBackend + 'static> {
272 executors: HashMap<&'static str, Arc<dyn ExtensionRuntime<B>>>,
273}
274
275impl<B: TensorBackend + 'static> fmt::Debug for ExtensionRegistry<B> {
276 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277 let mut families = self.executors.keys().copied().collect::<Vec<_>>();
278 families.sort_unstable();
279 f.debug_struct("ExtensionRegistry")
280 .field("backend_type", &std::any::type_name::<B>())
281 .field("len", &self.executors.len())
282 .field("families", &families)
283 .finish_non_exhaustive()
284 }
285}
286
287impl<B: TensorBackend + 'static> ExtensionRegistry<B> {
288 pub fn new() -> Self {
300 Self {
301 executors: HashMap::new(),
302 }
303 }
304
305 pub fn register(
311 &mut self,
312 executor: Arc<dyn ExtensionRuntime<B>>,
313 ) -> Result<(), ExtensionRuntimeRegistryError> {
314 let family_id = executor.family_id();
315 if !is_valid_family_id(family_id) {
316 return Err(ExtensionRuntimeRegistryError::MalformedFamilyId { family_id });
317 }
318 if self.executors.contains_key(family_id) {
319 return Ok(());
320 }
321 self.executors.insert(family_id, executor);
322 Ok(())
323 }
324
325 pub fn get(&self, family_id: &str) -> Option<Arc<dyn ExtensionRuntime<B>>> {
327 self.executors.get(family_id).cloned()
328 }
329
330 pub fn contains(&self, family_id: &str) -> bool {
332 self.executors.contains_key(family_id)
333 }
334
335 pub fn len(&self) -> usize {
337 self.executors.len()
338 }
339
340 pub fn is_empty(&self) -> bool {
342 self.executors.is_empty()
343 }
344}
345
346impl<B: TensorBackend + 'static> Default for ExtensionRegistry<B> {
347 fn default() -> Self {
348 Self::new()
349 }
350}
351
352pub struct ExtensionExecutor<B: TensorBackend + 'static> {
354 registry: ExtensionRegistry<B>,
355 caches: ExtensionCacheStore,
356 _backend: PhantomData<fn() -> B>,
357}
358
359impl<B: TensorBackend + 'static> fmt::Debug for ExtensionExecutor<B> {
360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361 f.debug_struct("ExtensionExecutor")
362 .field("backend_type", &std::any::type_name::<B>())
363 .field("registry", &self.registry)
364 .field("caches", &self.caches)
365 .finish_non_exhaustive()
366 }
367}
368
369impl<B: TensorBackend + 'static> ExtensionExecutor<B> {
370 pub fn new() -> Self {
382 Self {
383 registry: ExtensionRegistry::new(),
384 caches: ExtensionCacheStore::new(),
385 _backend: PhantomData,
386 }
387 }
388
389 pub fn with_parts(registry: ExtensionRegistry<B>, caches: ExtensionCacheStore) -> Self {
391 Self {
392 registry,
393 caches,
394 _backend: PhantomData,
395 }
396 }
397
398 pub fn registry(&self) -> &ExtensionRegistry<B> {
400 &self.registry
401 }
402
403 pub fn registry_mut(&mut self) -> &mut ExtensionRegistry<B> {
405 &mut self.registry
406 }
407
408 pub fn caches(&self) -> &ExtensionCacheStore {
410 &self.caches
411 }
412
413 pub fn caches_mut(&mut self) -> &mut ExtensionCacheStore {
415 &mut self.caches
416 }
417
418 pub fn execute(
420 &mut self,
421 backend: &mut B,
422 op: &dyn ExtensionOp,
423 inputs: &[&Tensor],
424 ) -> tenferro_tensor::Result<Vec<Tensor>> {
425 validate_runtime_input_count(op, inputs.len())?;
426 let Some(executor) = self.registry.get(op.family_id()) else {
427 return Err(tenferro_tensor::Error::InvalidConfig {
428 op: "extension",
429 message: format!(
430 "missing runtime for family_id {:?}; register the extension on this runtime owner, for example `executor.register_extension(<extension_crate>::register_runtime)` or `eager_runtime.register_extension(<extension_crate>::register_runtime)`",
431 op.family_id()
432 ),
433 });
434 };
435 let mut ctx = ExtensionExecutionContext::new(backend, &mut self.caches);
436 validate_runtime_output_count(op, executor.execute(op, inputs, &mut ctx)?)
437 }
438
439 pub fn execute_reads(
517 &mut self,
518 backend: &mut B,
519 op: &dyn ExtensionOp,
520 inputs: &[TensorRead<'_>],
521 ) -> tenferro_tensor::Result<Vec<Tensor>> {
522 validate_runtime_input_count(op, inputs.len())?;
523 let Some(executor) = self.registry.get(op.family_id()) else {
524 return Err(tenferro_tensor::Error::InvalidConfig {
525 op: "extension",
526 message: format!(
527 "missing runtime for family_id {:?}; register the extension on this runtime owner, for example `executor.register_extension(<extension_crate>::register_runtime)` or `eager_runtime.register_extension(<extension_crate>::register_runtime)`",
528 op.family_id()
529 ),
530 });
531 };
532 let mut ctx = ExtensionExecutionContext::new(backend, &mut self.caches);
533 validate_runtime_output_count(op, executor.execute_reads(op, inputs, &mut ctx)?)
534 }
535
536 pub fn clear_caches(&mut self) {
538 self.caches.clear();
539 }
540
541 pub fn cache_stats(&self) -> CacheStats {
543 self.caches.stats(ExtensionCacheSelector::All)
544 }
545
546 pub fn cache_limits(&self) -> ExtensionCacheLimits {
548 self.caches.limits()
549 }
550
551 pub fn set_cache_limits(&mut self, limits: ExtensionCacheLimits) {
553 self.caches.set_limits(limits);
554 }
555}
556
557impl<B: TensorBackend + 'static> Default for ExtensionExecutor<B> {
558 fn default() -> Self {
559 Self::new()
560 }
561}
562
563#[cfg(test)]
564mod tests;
565
566fn is_valid_family_id(family_id: &str) -> bool {
567 let mut parts = family_id.rsplitn(2, '.');
568 let Some(version_part) = parts.next() else {
569 return false;
570 };
571 let Some(prefix) = parts.next() else {
572 return false;
573 };
574 if !version_part.starts_with('v') {
575 return false;
576 }
577 let digits = &version_part[1..];
578 if digits.is_empty() || !digits.chars().all(|c| c.is_ascii_digit()) {
579 return false;
580 }
581 let Some((crate_name, op_name)) = prefix.split_once('.') else {
582 return false;
583 };
584 if crate_name.is_empty() || op_name.is_empty() {
585 return false;
586 }
587 let any_invalid = |s: &str| s.chars().any(|c| c.is_whitespace() || !c.is_ascii());
588 !any_invalid(crate_name) && !any_invalid(op_name)
589}