tenferro_runtime/runtime/capability.rs
1use std::any::Any;
2use std::fmt;
3use std::hash::Hash;
4use std::sync::Arc;
5
6use crate::program::SemanticOperationView;
7use crate::ExtensionCacheStore;
8use tenferro_tensor::{Engine, Tensor, TensorRead};
9
10use super::{
11 ExecutionContextIdentity, ExecutionContextMismatch, HardwareClassId, InputSignature,
12 PrepareError, PrepareOptionsKey, RegistrationIdentity, ResolvedPlanningConfig,
13 ResolvedProgramPlacement, RuntimeEpoch, RuntimeId, SpecializationProjection,
14 SpecializationRequirements,
15};
16
17/// Core operation family handled by a direct runtime capability slot.
18///
19/// # Examples
20///
21/// ```
22/// use tenferro_runtime::CoreCapabilityKind;
23///
24/// assert_eq!(CoreCapabilityKind::Elementwise, CoreCapabilityKind::Elementwise);
25/// ```
26#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
27pub enum CoreCapabilityKind {
28 /// Elementwise core operations.
29 Elementwise,
30 /// Reduction core operations.
31 Reduction,
32 /// Indexing and slicing core operations.
33 Indexing,
34 /// Dot-general preparation.
35 DotGeneral,
36 /// Layout-only operations.
37 Layout,
38}
39
40/// Deterministic reason why a provider cannot prepare an operation.
41///
42/// # Examples
43///
44/// ```
45/// use tenferro_runtime::{CoreCapabilityKind, UnsupportedReason};
46///
47/// let reason = UnsupportedReason::MissingCapability {
48/// capability: CoreCapabilityKind::Elementwise,
49/// };
50/// assert!(reason.to_string().contains("Elementwise"));
51/// ```
52#[derive(Clone, Debug, Eq, Hash, PartialEq)]
53#[non_exhaustive]
54pub enum UnsupportedReason {
55 /// The runtime snapshot has no provider for this core capability.
56 MissingCapability {
57 /// Missing core capability kind.
58 capability: CoreCapabilityKind,
59 },
60 /// The named operation is unsupported by the selected provider.
61 Operation {
62 /// Stable operation diagnostic.
63 operation: &'static str,
64 },
65 /// The selected provider does not support the required storage class.
66 StorageClass {
67 /// Unsupported storage class.
68 storage_class: super::StorageClass,
69 },
70}
71
72impl fmt::Display for UnsupportedReason {
73 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
74 match self {
75 Self::MissingCapability { capability } => {
76 write!(formatter, "missing {capability:?} capability")
77 }
78 Self::Operation { operation } => {
79 write!(formatter, "unsupported operation {operation:?}")
80 }
81 Self::StorageClass { storage_class } => {
82 write!(
83 formatter,
84 "unsupported storage class {:?}",
85 storage_class.as_str()
86 )
87 }
88 }
89 }
90}
91
92/// Bounded diagnostic summary of a preparation cache key.
93///
94/// A1 exposes only the summary shape. C1 owns the concrete key construction.
95///
96/// # Examples
97///
98/// ```
99/// use std::fmt::Debug;
100/// use tenferro_runtime::PreparationKeySummary;
101///
102/// fn requires_debug<T: Debug>() {}
103/// requires_debug::<PreparationKeySummary>();
104/// ```
105#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
106pub struct PreparationKeySummary {
107 compact_digest: u128,
108}
109
110impl PreparationKeySummary {
111 #[allow(dead_code)]
112 pub(crate) const fn new(compact_digest: u128) -> Self {
113 Self { compact_digest }
114 }
115
116 #[cfg(test)]
117 #[allow(dead_code)]
118 pub(crate) const fn for_test(value: u64) -> Self {
119 Self {
120 compact_digest: value as u128,
121 }
122 }
123}
124
125/// Runtime-created immutable binding for one prepared operation.
126///
127/// A1 has no public constructor. B0/C1 provide the runtime creation path.
128///
129/// # Examples
130///
131/// ```
132/// use std::fmt::Debug;
133/// use tenferro_runtime::PreparedOperationBinding;
134///
135/// fn requires_debug<T: Debug>() {}
136/// requires_debug::<PreparedOperationBinding>();
137/// ```
138#[derive(Clone, Debug, Eq, Hash, PartialEq)]
139pub struct PreparedOperationBinding {
140 runtime_id: RuntimeId,
141 epoch: RuntimeEpoch,
142 engine_id: super::EngineId,
143 registration_identity: RegistrationIdentity,
144 context_identity: ExecutionContextIdentity,
145 hardware_class: HardwareClassId,
146}
147
148impl PreparedOperationBinding {
149 #[allow(dead_code)]
150 pub(crate) fn new(
151 runtime_id: RuntimeId,
152 epoch: RuntimeEpoch,
153 engine_id: super::EngineId,
154 registration_identity: RegistrationIdentity,
155 context_identity: ExecutionContextIdentity,
156 hardware_class: HardwareClassId,
157 ) -> Self {
158 Self {
159 runtime_id,
160 epoch,
161 engine_id,
162 registration_identity,
163 context_identity,
164 hardware_class,
165 }
166 }
167
168 /// Return the runtime identity that created this binding.
169 ///
170 /// # Examples
171 ///
172 /// ```
173 /// use std::fmt::Debug;
174 /// use tenferro_runtime::PreparedOperationBinding;
175 ///
176 /// fn requires_debug<T: Debug>() {}
177 /// requires_debug::<PreparedOperationBinding>();
178 /// ```
179 pub fn runtime_id(&self) -> RuntimeId {
180 self.runtime_id
181 }
182
183 /// Return the immutable runtime epoch that created this binding.
184 ///
185 /// # Examples
186 ///
187 /// ```
188 /// use std::fmt::Debug;
189 /// use tenferro_runtime::PreparedOperationBinding;
190 ///
191 /// fn requires_debug<T: Debug>() {}
192 /// requires_debug::<PreparedOperationBinding>();
193 /// ```
194 pub fn epoch(&self) -> RuntimeEpoch {
195 self.epoch
196 }
197
198 /// Return the selected engine identifier.
199 ///
200 /// # Examples
201 ///
202 /// ```
203 /// use std::fmt::Debug;
204 /// use tenferro_runtime::PreparedOperationBinding;
205 ///
206 /// fn requires_debug<T: Debug>() {}
207 /// requires_debug::<PreparedOperationBinding>();
208 /// ```
209 pub fn engine_id(&self) -> &super::EngineId {
210 &self.engine_id
211 }
212
213 /// Return the runtime-local engine registration identity.
214 ///
215 /// # Examples
216 ///
217 /// ```
218 /// use std::fmt::Debug;
219 /// use tenferro_runtime::PreparedOperationBinding;
220 ///
221 /// fn requires_debug<T: Debug>() {}
222 /// requires_debug::<PreparedOperationBinding>();
223 /// ```
224 pub fn registration_identity(&self) -> RegistrationIdentity {
225 self.registration_identity
226 }
227
228 /// Return the execution-context type identity.
229 ///
230 /// # Examples
231 ///
232 /// ```
233 /// use std::fmt::Debug;
234 /// use tenferro_runtime::PreparedOperationBinding;
235 ///
236 /// fn requires_debug<T: Debug>() {}
237 /// requires_debug::<PreparedOperationBinding>();
238 /// ```
239 pub fn context_identity(&self) -> ExecutionContextIdentity {
240 self.context_identity
241 }
242
243 /// Return the selected hardware-class identifier.
244 ///
245 /// # Examples
246 ///
247 /// ```
248 /// use std::fmt::Debug;
249 /// use tenferro_runtime::PreparedOperationBinding;
250 ///
251 /// fn requires_debug<T: Debug>() {}
252 /// requires_debug::<PreparedOperationBinding>();
253 /// ```
254 pub fn hardware_class(&self) -> &HardwareClassId {
255 &self.hardware_class
256 }
257}
258
259/// Immutable metadata for a prepared provider operation.
260///
261/// # Examples
262///
263/// ```
264/// use tenferro_runtime::PreparedOperation;
265///
266/// fn accepts_prepared_operation(_: Option<&dyn PreparedOperation>) {}
267/// let _ = accepts_prepared_operation;
268/// ```
269pub trait PreparedOperation: fmt::Debug + Send + Sync + 'static {
270 /// Return the runtime-created binding carried by this operation.
271 fn binding(&self) -> &PreparedOperationBinding;
272 /// Return the specialization projection this operation depends on.
273 fn specialization(&self) -> &SpecializationProjection;
274 /// Return logical heap bytes owned exclusively by this operation.
275 fn retained_bytes(&self) -> usize;
276}
277
278#[doc(hidden)]
279/// Internal execution bridge for a prepared provider operation.
280///
281/// This remains a public trait only so out-of-crate extension providers can
282/// hand an executable plan back to the runtime. Public prepared-operation
283/// metadata stays on [`PreparedOperation`]; scheduled runtime execution owns
284/// when and how this bridge is invoked.
285pub trait PreparedOperationExecutor: fmt::Debug + Send + Sync + 'static {
286 /// Execute this prepared operation using a runtime-owned erased backend
287 /// context and extension cache store.
288 ///
289 /// # Errors
290 ///
291 /// Returns [`Error`] when the prepared operation is not executable for the
292 /// supplied context, input reads are invalid, or backend execution fails.
293 fn execute(
294 &self,
295 context: &mut ErasedExecutionContext<'_>,
296 extension_caches: &mut ExtensionCacheStore,
297 inputs: &[TensorRead<'_>],
298 ) -> crate::Result<Vec<Tensor>>;
299
300 /// Return whether this prepared operation can execute inside a borrowed
301 /// backend session.
302 ///
303 /// The scheduler uses this capability to form a session-compatible
304 /// admission region. A false result keeps the operation on the ordinary
305 /// backend-owned execution path; it is never silently retried from inside
306 /// a session.
307 fn supports_session(&self) -> bool {
308 false
309 }
310
311 /// Execute this prepared operation inside the scheduler-owned session.
312 ///
313 /// Implementations must use `session` directly and must not reacquire a
314 /// backend session. The borrowed session and any operation-local state
315 /// must not escape this call.
316 ///
317 /// # Errors
318 ///
319 /// The default implementation returns `Error::Unsupported` because the
320 /// prepared operation has no session-aware execution implementation.
321 /// Implementations may return the same typed error when the selected
322 /// backend session does not provide the required capability, or any other
323 /// typed execution error from the operation itself.
324 fn execute_in_session(
325 &self,
326 _session: &mut dyn Engine,
327 _extension_caches: &mut ExtensionCacheStore,
328 _inputs: &[TensorRead<'_>],
329 ) -> crate::Result<Vec<Tensor>> {
330 Err(crate::Error::unsupported(
331 "prepared_operation.execute_in_session",
332 crate::ErrorPhase::Execution,
333 "prepared operation does not support the scheduler-owned backend session",
334 ))
335 }
336}
337
338/// Shared prepared-operation handle.
339///
340/// # Examples
341///
342/// ```
343/// use std::fmt::Debug;
344/// use tenferro_runtime::PreparedOperationHandle;
345///
346/// fn requires_debug<T: Debug>() {}
347/// requires_debug::<PreparedOperationHandle>();
348/// ```
349pub type PreparedOperationHandle = Arc<dyn PreparedOperation>;
350
351#[doc(hidden)]
352/// Shared prepared-operation executor handle.
353///
354/// This alias is part of the runtime/provider bridge, not the user-facing
355/// prepared-operation metadata surface.
356pub type PreparedOperationExecutorHandle = Arc<dyn PreparedOperationExecutor>;
357
358/// Runtime-owned prepared operation plan with separated metadata and optional
359/// execution bridge.
360///
361/// Core operations are metadata-only because scheduled execution dispatches
362/// them through the selected engine's core executor. Extension operations carry
363/// an executor bridge that is invoked only by the scheduled runtime loop.
364///
365/// # Examples
366///
367/// ```
368/// use std::fmt::Debug;
369/// use tenferro_runtime::PreparedOperationPlan;
370///
371/// fn requires_debug<T: Debug>() {}
372/// requires_debug::<PreparedOperationPlan>();
373/// ```
374#[derive(Clone)]
375pub struct PreparedOperationPlan {
376 operation: PreparedOperationHandle,
377 executor: Option<PreparedOperationExecutorHandle>,
378}
379
380impl fmt::Debug for PreparedOperationPlan {
381 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
382 formatter
383 .debug_struct("PreparedOperationPlan")
384 .field("operation", &self.operation)
385 .field("has_executor", &self.executor.is_some())
386 .finish()
387 }
388}
389
390impl PreparedOperationPlan {
391 /// Construct a metadata-only prepared operation plan.
392 ///
393 /// # Examples
394 ///
395 /// ```
396 /// use tenferro_runtime::{PreparedOperationHandle, PreparedOperationPlan};
397 ///
398 /// fn provider_handle() -> PreparedOperationHandle {
399 /// unreachable!("example is type-checked but not executed")
400 /// }
401 ///
402 /// if false {
403 /// let plan = PreparedOperationPlan::metadata(provider_handle());
404 /// assert!(plan.executor().is_none());
405 /// }
406 /// ```
407 #[must_use]
408 pub fn metadata(operation: PreparedOperationHandle) -> Self {
409 Self {
410 operation,
411 executor: None,
412 }
413 }
414
415 /// Construct an executable prepared operation plan.
416 ///
417 /// # Examples
418 ///
419 /// ```
420 /// use tenferro_runtime::{
421 /// PreparedOperationExecutorHandle, PreparedOperationHandle, PreparedOperationPlan,
422 /// };
423 ///
424 /// fn provider_handle() -> PreparedOperationHandle {
425 /// unreachable!("example is type-checked but not executed")
426 /// }
427 ///
428 /// fn executor_handle() -> PreparedOperationExecutorHandle {
429 /// unreachable!("example is type-checked but not executed")
430 /// }
431 ///
432 /// if false {
433 /// let plan = PreparedOperationPlan::executable(provider_handle(), executor_handle());
434 /// assert!(plan.executor().is_some());
435 /// }
436 /// ```
437 #[must_use]
438 pub fn executable(
439 operation: PreparedOperationHandle,
440 executor: PreparedOperationExecutorHandle,
441 ) -> Self {
442 Self {
443 operation,
444 executor: Some(executor),
445 }
446 }
447
448 /// Return the prepared-operation metadata handle.
449 ///
450 /// # Examples
451 ///
452 /// ```
453 /// use tenferro_runtime::{PreparedOperationHandle, PreparedOperationPlan};
454 ///
455 /// fn provider_handle() -> PreparedOperationHandle {
456 /// unreachable!("example is type-checked but not executed")
457 /// }
458 ///
459 /// if false {
460 /// let plan = PreparedOperationPlan::metadata(provider_handle());
461 /// let _operation = plan.operation();
462 /// }
463 /// ```
464 #[must_use]
465 pub fn operation(&self) -> &PreparedOperationHandle {
466 &self.operation
467 }
468
469 /// Return the optional prepared-operation executor bridge.
470 #[doc(hidden)]
471 #[must_use]
472 pub fn executor(&self) -> Option<&PreparedOperationExecutorHandle> {
473 self.executor.as_ref()
474 }
475
476 /// Return the runtime-created binding carried by the metadata operation.
477 ///
478 /// # Examples
479 ///
480 /// ```
481 /// use tenferro_runtime::{PreparedOperationHandle, PreparedOperationPlan};
482 ///
483 /// fn provider_handle() -> PreparedOperationHandle {
484 /// unreachable!("example is type-checked but not executed")
485 /// }
486 ///
487 /// if false {
488 /// let plan = PreparedOperationPlan::metadata(provider_handle());
489 /// let _binding = plan.binding();
490 /// }
491 /// ```
492 #[must_use]
493 pub fn binding(&self) -> &PreparedOperationBinding {
494 self.operation.binding()
495 }
496
497 /// Return the specialization projection carried by the metadata operation.
498 ///
499 /// # Examples
500 ///
501 /// ```
502 /// use tenferro_runtime::{PreparedOperationHandle, PreparedOperationPlan};
503 ///
504 /// fn provider_handle() -> PreparedOperationHandle {
505 /// unreachable!("example is type-checked but not executed")
506 /// }
507 ///
508 /// if false {
509 /// let plan = PreparedOperationPlan::metadata(provider_handle());
510 /// let _specialization = plan.specialization();
511 /// }
512 /// ```
513 #[must_use]
514 pub fn specialization(&self) -> &SpecializationProjection {
515 self.operation.specialization()
516 }
517
518 /// Return logical heap bytes owned by metadata and executor handles.
519 ///
520 /// # Examples
521 ///
522 /// ```
523 /// use tenferro_runtime::{PreparedOperationHandle, PreparedOperationPlan};
524 ///
525 /// fn provider_handle() -> PreparedOperationHandle {
526 /// unreachable!("example is type-checked but not executed")
527 /// }
528 ///
529 /// if false {
530 /// let plan = PreparedOperationPlan::metadata(provider_handle());
531 /// let _bytes = plan.retained_bytes();
532 /// }
533 /// ```
534 #[must_use]
535 pub fn retained_bytes(&self) -> usize {
536 self.operation.retained_bytes()
537 }
538}
539
540/// Result of asking a provider to prepare an operation.
541///
542/// # Examples
543///
544/// ```
545/// use tenferro_runtime::{CoreCapabilityKind, PrepareCapability, UnsupportedReason};
546///
547/// let capability = PrepareCapability::Unsupported(UnsupportedReason::MissingCapability {
548/// capability: CoreCapabilityKind::Elementwise,
549/// });
550/// assert!(matches!(capability, PrepareCapability::Unsupported { .. }));
551/// ```
552#[derive(Clone, Debug)]
553pub enum PrepareCapability {
554 /// Provider returned an immutable prepared operation.
555 Prepared(PreparedOperationPlan),
556 /// Provider requires a wider specialization before it can prepare.
557 NeedsSpecialization(SpecializationRequirements),
558 /// Provider does not support this request.
559 Unsupported(UnsupportedReason),
560}
561
562/// Type-erased execution context with a checked identity.
563///
564/// # Examples
565///
566/// ```
567/// use tenferro_runtime::{ErasedExecutionContext, ExecutionContextIdentity};
568///
569/// let mut value = 1_u64;
570/// let erased = ErasedExecutionContext::new(&mut value);
571/// assert_eq!(erased.identity(), ExecutionContextIdentity::of::<u64>());
572/// ```
573pub struct ErasedExecutionContext<'a> {
574 identity: ExecutionContextIdentity,
575 value: &'a mut dyn Any,
576}
577
578impl<'a> ErasedExecutionContext<'a> {
579 /// Erase a concrete execution context while retaining its type identity.
580 ///
581 /// # Examples
582 ///
583 /// ```
584 /// use tenferro_runtime::ErasedExecutionContext;
585 ///
586 /// let mut value = 1_u64;
587 /// let erased = ErasedExecutionContext::new(&mut value);
588 /// assert_eq!(erased.identity().type_name(), std::any::type_name::<u64>());
589 /// ```
590 pub fn new<T: 'static>(value: &'a mut T) -> Self {
591 Self {
592 identity: ExecutionContextIdentity::of::<T>(),
593 value,
594 }
595 }
596
597 /// Return the stored execution-context type identity.
598 ///
599 /// # Examples
600 ///
601 /// ```
602 /// use tenferro_runtime::{ErasedExecutionContext, ExecutionContextIdentity};
603 ///
604 /// let mut value = 1_u64;
605 /// let erased = ErasedExecutionContext::new(&mut value);
606 /// assert_eq!(erased.identity(), ExecutionContextIdentity::of::<u64>());
607 /// ```
608 /// Wrap an engine already erased to `dyn Any`, with the identity of its
609 /// concrete type.
610 ///
611 /// # Examples
612 ///
613 /// ```
614 /// use tenferro_runtime::{ErasedExecutionContext, ExecutionContextIdentity};
615 ///
616 /// let mut value = 7_u32;
617 /// let context = ErasedExecutionContext::from_dyn(ExecutionContextIdentity::of::<u32>(), &mut value);
618 /// assert_eq!(context.identity(), ExecutionContextIdentity::of::<u32>());
619 /// ```
620 pub fn from_dyn(identity: ExecutionContextIdentity, value: &'a mut dyn Any) -> Self {
621 Self { identity, value }
622 }
623
624 pub fn identity(&self) -> ExecutionContextIdentity {
625 self.identity
626 }
627
628 /// Downcast to the requested execution-context type after checking identity.
629 ///
630 /// # Examples
631 ///
632 /// ```
633 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
634 /// use tenferro_runtime::{ErasedExecutionContext, ExecutionContextIdentity};
635 ///
636 /// let mut value = 1_u64;
637 /// let mut erased = ErasedExecutionContext::new(&mut value);
638 /// *erased.downcast_mut::<u64>(ExecutionContextIdentity::of::<u64>())? = 2;
639 /// # Ok(())
640 /// # }
641 /// ```
642 ///
643 /// # Errors
644 ///
645 /// Returns [`ExecutionContextMismatch`] when the stored identity, supplied
646 /// expected identity, and requested type do not all agree.
647 pub fn downcast_mut<T: 'static>(
648 &mut self,
649 expected: ExecutionContextIdentity,
650 ) -> Result<&mut T, ExecutionContextMismatch> {
651 let requested = ExecutionContextIdentity::of::<T>();
652 if requested != expected {
653 return Err(ExecutionContextMismatch {
654 expected: requested,
655 actual: self.identity,
656 });
657 }
658 if self.identity != expected {
659 return Err(ExecutionContextMismatch {
660 expected,
661 actual: self.identity,
662 });
663 }
664 self.value
665 .downcast_mut::<T>()
666 .ok_or(ExecutionContextMismatch {
667 expected,
668 actual: self.identity,
669 })
670 }
671}
672
673impl fmt::Debug for ErasedExecutionContext<'_> {
674 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
675 formatter
676 .debug_struct("ErasedExecutionContext")
677 .field("identity", &self.identity)
678 .finish_non_exhaustive()
679 }
680}
681
682/// Runtime-created borrowed context passed to direct core providers.
683///
684/// # Examples
685///
686/// ```
687/// use std::fmt::Debug;
688/// use tenferro_runtime::CorePrepareContext;
689///
690/// fn requires_debug<T: Debug>() {}
691/// requires_debug::<CorePrepareContext<'_>>();
692/// ```
693pub struct CorePrepareContext<'a> {
694 binding: &'a PreparedOperationBinding,
695 inputs: &'a InputSignature,
696 resolved_placement: &'a ResolvedProgramPlacement,
697 planning: &'a ResolvedPlanningConfig,
698 prepare_options_key: &'a PrepareOptionsKey,
699 specialization: &'a SpecializationProjection,
700}
701
702impl<'a> CorePrepareContext<'a> {
703 #[allow(dead_code)]
704 pub(crate) fn new(
705 binding: &'a PreparedOperationBinding,
706 inputs: &'a InputSignature,
707 resolved_placement: &'a ResolvedProgramPlacement,
708 planning: &'a ResolvedPlanningConfig,
709 prepare_options_key: &'a PrepareOptionsKey,
710 specialization: &'a SpecializationProjection,
711 ) -> Self {
712 Self {
713 binding,
714 inputs,
715 resolved_placement,
716 planning,
717 prepare_options_key,
718 specialization,
719 }
720 }
721
722 /// Return the runtime-created binding.
723 pub fn binding(&self) -> &'a PreparedOperationBinding {
724 self.binding
725 }
726
727 /// Return value-free input metadata.
728 pub fn inputs(&self) -> &'a InputSignature {
729 self.inputs
730 }
731
732 /// Return the selected program placement.
733 pub fn resolved_placement(&self) -> &'a ResolvedProgramPlacement {
734 self.resolved_placement
735 }
736
737 /// Return resolved planning policy.
738 pub fn planning(&self) -> &'a ResolvedPlanningConfig {
739 self.planning
740 }
741
742 /// Return the normalized prepare-options key.
743 pub fn prepare_options_key(&self) -> &'a PrepareOptionsKey {
744 self.prepare_options_key
745 }
746
747 /// Return the concrete specialization projection.
748 pub fn specialization(&self) -> &'a SpecializationProjection {
749 self.specialization
750 }
751}
752
753impl fmt::Debug for CorePrepareContext<'_> {
754 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
755 formatter
756 .debug_struct("CorePrepareContext")
757 .field("binding", self.binding)
758 .field("inputs", &self.inputs.entries().len())
759 .field("resolved_placement", self.resolved_placement)
760 .field("planning", self.planning)
761 .field("prepare_options_key", self.prepare_options_key)
762 .field("specialization", self.specialization)
763 .finish()
764 }
765}
766
767macro_rules! request_type {
768 ($name:ident) => {
769 /// Runtime-created direct core preparation request.
770 pub struct $name<'a> {
771 operation: SemanticOperationView<'a>,
772 context: &'a CorePrepareContext<'a>,
773 }
774
775 impl<'a> $name<'a> {
776 #[allow(dead_code)]
777 pub(crate) fn new(
778 operation: SemanticOperationView<'a>,
779 context: &'a CorePrepareContext<'a>,
780 ) -> Self {
781 Self { operation, context }
782 }
783
784 /// Return the immutable semantic operation view.
785 pub fn operation(&self) -> SemanticOperationView<'a> {
786 self.operation
787 }
788
789 /// Return the shared runtime preparation context.
790 pub fn context(&self) -> &'a CorePrepareContext<'a> {
791 self.context
792 }
793 }
794
795 impl fmt::Debug for $name<'_> {
796 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
797 formatter
798 .debug_struct(stringify!($name))
799 .field("operation", &self.operation)
800 .field("context", &self.context)
801 .finish()
802 }
803 }
804 };
805}
806
807request_type!(ElementwisePrepareRequest);
808request_type!(ReductionPrepareRequest);
809request_type!(IndexingPrepareRequest);
810request_type!(DotGeneralPrepareRequest);
811request_type!(LayoutPrepareRequest);
812
813/// Direct runtime provider for elementwise core operations.
814pub trait ElementwiseRuntime: fmt::Debug + Send + Sync + 'static {
815 /// Prepare one elementwise operation.
816 ///
817 /// # Errors
818 ///
819 /// Returns [`PrepareError`] when provider preparation fails.
820 fn prepare(
821 &self,
822 request: ElementwisePrepareRequest<'_>,
823 ) -> Result<PrepareCapability, PrepareError>;
824
825 /// Largest number of distinct external inputs one fused elementwise region
826 /// may read on this engine, or `None` when the engine has no such limit.
827 ///
828 /// The prepared planner does not plan a region above this limit, so the
829 /// region runs instruction by instruction without a fusion attempt that the
830 /// backend would decline anyway. The default is `None`.
831 ///
832 /// # Examples
833 ///
834 /// ```rust
835 /// use tenferro_cpu::CpuEngine;
836 ///
837 /// // The CPU engine registration exposes its own fused-region limit.
838 /// let registration = tenferro_cpu::runtime_engine_registration(&CpuEngine::serial())?;
839 /// let elementwise = registration.capabilities().elementwise().unwrap();
840 /// assert_eq!(elementwise.max_fused_region_inputs(), Some(4));
841 /// # Ok::<(), Box<dyn std::error::Error>>(())
842 /// ```
843 fn max_fused_region_inputs(&self) -> Option<usize> {
844 None
845 }
846}
847
848/// Direct runtime provider for reduction core operations.
849pub trait ReductionRuntime: fmt::Debug + Send + Sync + 'static {
850 /// Prepare one reduction operation.
851 ///
852 /// # Errors
853 ///
854 /// Returns [`PrepareError`] when provider preparation fails.
855 fn prepare(
856 &self,
857 request: ReductionPrepareRequest<'_>,
858 ) -> Result<PrepareCapability, PrepareError>;
859}
860
861/// Direct runtime provider for indexing core operations.
862pub trait IndexingRuntime: fmt::Debug + Send + Sync + 'static {
863 /// Prepare one indexing operation.
864 ///
865 /// # Errors
866 ///
867 /// Returns [`PrepareError`] when provider preparation fails.
868 fn prepare(
869 &self,
870 request: IndexingPrepareRequest<'_>,
871 ) -> Result<PrepareCapability, PrepareError>;
872}
873
874/// Preparation-only direct runtime provider for dot-general operations.
875pub trait DotGeneralPreparation: fmt::Debug + Send + Sync + 'static {
876 /// Prepare one dot-general operation.
877 ///
878 /// # Errors
879 ///
880 /// Returns [`PrepareError`] when provider preparation fails.
881 fn prepare(
882 &self,
883 request: DotGeneralPrepareRequest<'_>,
884 ) -> Result<PrepareCapability, PrepareError>;
885}
886
887/// Direct runtime provider for layout core operations.
888pub trait LayoutRuntime: fmt::Debug + Send + Sync + 'static {
889 /// Prepare one layout operation.
890 ///
891 /// # Errors
892 ///
893 /// Returns [`PrepareError`] when provider preparation fails.
894 fn prepare(&self, request: LayoutPrepareRequest<'_>)
895 -> Result<PrepareCapability, PrepareError>;
896
897 /// Whether this engine's `*_read` entries consume a strided view in place,
898 /// so a metadata-only operation (transpose, reshape, broadcast, slice)
899 /// with one reading consumer may become that consumer's input view at
900 /// preparation instead of running as its own command (#2226).
901 ///
902 /// The default is `false`: an engine whose read entries reject or stage a
903 /// view keeps every metadata operation as a materializing command, and its
904 /// schedule is unchanged.
905 ///
906 /// # Examples
907 ///
908 /// ```rust
909 /// use tenferro_cpu::CpuEngine;
910 ///
911 /// // The CPU engine reads views in place for every operation family.
912 /// let registration = tenferro_cpu::runtime_engine_registration(&CpuEngine::serial())?;
913 /// let layout = registration.capabilities().layout().unwrap();
914 /// assert!(layout.folds_metadata_views());
915 /// # Ok::<(), Box<dyn std::error::Error>>(())
916 /// ```
917 fn folds_metadata_views(&self) -> bool {
918 false
919 }
920}
921
922#[derive(Clone, Copy, Debug)]
923enum ReservedSubgraphSlot {}
924
925/// Direct core capability slots captured by a runtime snapshot.
926///
927/// # Examples
928///
929/// ```
930/// use tenferro_runtime::CoreCapabilityBundle;
931///
932/// assert!(CoreCapabilityBundle::builder().build().elementwise().is_none());
933/// ```
934#[derive(Clone, Default)]
935pub struct CoreCapabilityBundle {
936 elementwise: Option<Arc<dyn ElementwiseRuntime>>,
937 reduction: Option<Arc<dyn ReductionRuntime>>,
938 indexing: Option<Arc<dyn IndexingRuntime>>,
939 dot_general: Option<Arc<dyn DotGeneralPreparation>>,
940 layout: Option<Arc<dyn LayoutRuntime>>,
941 reserved_subgraph: Option<ReservedSubgraphSlot>,
942}
943
944impl CoreCapabilityBundle {
945 /// Return an empty direct-capability builder.
946 pub fn builder() -> CoreCapabilityBundleBuilder {
947 CoreCapabilityBundleBuilder::new()
948 }
949
950 /// Return the elementwise provider slot.
951 pub fn elementwise(&self) -> Option<&Arc<dyn ElementwiseRuntime>> {
952 self.elementwise.as_ref()
953 }
954
955 /// Return the reduction provider slot.
956 pub fn reduction(&self) -> Option<&Arc<dyn ReductionRuntime>> {
957 self.reduction.as_ref()
958 }
959
960 /// Return the indexing provider slot.
961 pub fn indexing(&self) -> Option<&Arc<dyn IndexingRuntime>> {
962 self.indexing.as_ref()
963 }
964
965 /// Return the dot-general preparation slot.
966 pub fn dot_general(&self) -> Option<&Arc<dyn DotGeneralPreparation>> {
967 self.dot_general.as_ref()
968 }
969
970 /// Return the layout provider slot.
971 pub fn layout(&self) -> Option<&Arc<dyn LayoutRuntime>> {
972 self.layout.as_ref()
973 }
974}
975
976impl fmt::Debug for CoreCapabilityBundle {
977 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
978 formatter
979 .debug_struct("CoreCapabilityBundle")
980 .field("elementwise", &self.elementwise.is_some())
981 .field("reduction", &self.reduction.is_some())
982 .field("indexing", &self.indexing.is_some())
983 .field("dot_general", &self.dot_general.is_some())
984 .field("layout", &self.layout.is_some())
985 .field("reserved_subgraph", &self.reserved_subgraph.is_some())
986 .finish()
987 }
988}
989
990/// Builder for direct core capability slots.
991///
992/// Setters replace any previous value for the same slot and therefore are
993/// infallible.
994///
995/// # Examples
996///
997/// ```
998/// use tenferro_runtime::CoreCapabilityBundleBuilder;
999///
1000/// assert!(CoreCapabilityBundleBuilder::new().build().layout().is_none());
1001/// ```
1002#[derive(Clone, Default)]
1003pub struct CoreCapabilityBundleBuilder {
1004 elementwise: Option<Arc<dyn ElementwiseRuntime>>,
1005 reduction: Option<Arc<dyn ReductionRuntime>>,
1006 indexing: Option<Arc<dyn IndexingRuntime>>,
1007 dot_general: Option<Arc<dyn DotGeneralPreparation>>,
1008 layout: Option<Arc<dyn LayoutRuntime>>,
1009}
1010
1011impl CoreCapabilityBundleBuilder {
1012 /// Return an empty builder.
1013 pub fn new() -> Self {
1014 Self::default()
1015 }
1016
1017 /// Set or replace the elementwise slot.
1018 pub fn elementwise(&mut self, capability: Arc<dyn ElementwiseRuntime>) -> &mut Self {
1019 self.elementwise = Some(capability);
1020 self
1021 }
1022
1023 /// Set or replace the reduction slot.
1024 pub fn reduction(&mut self, capability: Arc<dyn ReductionRuntime>) -> &mut Self {
1025 self.reduction = Some(capability);
1026 self
1027 }
1028
1029 /// Set or replace the indexing slot.
1030 pub fn indexing(&mut self, capability: Arc<dyn IndexingRuntime>) -> &mut Self {
1031 self.indexing = Some(capability);
1032 self
1033 }
1034
1035 /// Set or replace the dot-general preparation slot.
1036 pub fn dot_general(&mut self, capability: Arc<dyn DotGeneralPreparation>) -> &mut Self {
1037 self.dot_general = Some(capability);
1038 self
1039 }
1040
1041 /// Set or replace the layout slot.
1042 pub fn layout(&mut self, capability: Arc<dyn LayoutRuntime>) -> &mut Self {
1043 self.layout = Some(capability);
1044 self
1045 }
1046
1047 /// Build an immutable capability bundle.
1048 pub fn build(self) -> CoreCapabilityBundle {
1049 CoreCapabilityBundle {
1050 elementwise: self.elementwise,
1051 reduction: self.reduction,
1052 indexing: self.indexing,
1053 dot_general: self.dot_general,
1054 layout: self.layout,
1055 reserved_subgraph: None,
1056 }
1057 }
1058}
1059
1060impl fmt::Debug for CoreCapabilityBundleBuilder {
1061 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1062 formatter
1063 .debug_struct("CoreCapabilityBundleBuilder")
1064 .field("elementwise", &self.elementwise.is_some())
1065 .field("reduction", &self.reduction.is_some())
1066 .field("indexing", &self.indexing.is_some())
1067 .field("dot_general", &self.dot_general.is_some())
1068 .field("layout", &self.layout.is_some())
1069 .finish()
1070 }
1071}