tenferro_runtime/runtime/error.rs
1use std::error::Error;
2use std::fmt;
3use std::sync::Arc;
4
5use super::capability::{
6 CoreCapabilityKind, PreparationKeySummary, PreparedOperationBinding, UnsupportedReason,
7};
8use super::identity::{EngineId, ProviderDeviceIdentity, RuntimeEpoch, RuntimeId};
9use super::policy::{ProgramPlacementConstraint, StorageClass, TransferEndpoint};
10use super::specialization::SpecializationProjection;
11use super::{CacheOwnerId, ExtensionModuleId};
12use tenferro_ops::ShapeGuardError;
13use tenferro_tensor::{AllocationDomainId, Error as TensorError, Placement};
14
15/// Classifies a malformed runtime identifier without retaining its input.
16///
17/// # Examples
18///
19/// ```
20/// use tenferro_runtime::EngineId;
21///
22/// let error = EngineId::new("not namespaced").unwrap_err();
23/// assert_eq!(error.kind(), tenferro_runtime::IdentityKind::Engine);
24/// ```
25#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
26#[error("malformed {kind:?} identifier")]
27pub struct IdentityError {
28 kind: IdentityKind,
29}
30
31impl IdentityError {
32 /// Return the kind of identifier that failed validation.
33 ///
34 /// # Examples
35 ///
36 /// ```
37 /// use tenferro_runtime::{EngineId, IdentityKind};
38 ///
39 /// assert_eq!(EngineId::new("engine").unwrap_err().kind(), IdentityKind::Engine);
40 /// ```
41 pub fn kind(&self) -> IdentityKind {
42 self.kind
43 }
44
45 pub(super) const fn malformed(kind: IdentityKind) -> Self {
46 Self { kind }
47 }
48}
49
50/// Identifies the runtime namespace validated by an opaque identifier.
51///
52/// # Examples
53///
54/// ```
55/// use tenferro_runtime::IdentityKind;
56///
57/// assert_eq!(IdentityKind::CacheOwner, IdentityKind::CacheOwner);
58/// ```
59#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
60#[non_exhaustive]
61pub enum IdentityKind {
62 /// An execution engine identifier.
63 Engine,
64 /// A provider identifier.
65 Provider,
66 /// A provider-canonical physical target identity.
67 ProviderTarget,
68 /// A hardware-class identifier.
69 HardwareClass,
70 /// A storage-class identifier.
71 StorageClass,
72 /// A layout-class identifier.
73 LayoutClass,
74 /// A runtime cache-owner identifier.
75 CacheOwner,
76 /// An extension module identifier.
77 ExtensionModule,
78}
79
80/// Reports invalid placement constraints.
81///
82/// # Examples
83///
84/// ```
85/// use tenferro_runtime::{EngineId, PlacementConstraintError, ProgramPlacementConstraint};
86///
87/// let engine = EngineId::new("tenferro.cpu").unwrap();
88/// let error = ProgramPlacementConstraint::new(vec![engine.clone(), engine], None).unwrap_err();
89/// assert!(matches!(error, PlacementConstraintError::DuplicateEngine { .. }));
90/// ```
91#[derive(Debug, Eq, PartialEq, thiserror::Error)]
92#[non_exhaustive]
93pub enum PlacementConstraintError {
94 /// The same engine appears more than once in a preference list.
95 #[error(
96 "engine {engine_id:?} is duplicated at positions \
97 {first_index} and {duplicate_index}"
98 )]
99 DuplicateEngine {
100 /// The duplicated engine identifier.
101 engine_id: EngineId,
102 /// The first position where the engine appeared.
103 first_index: usize,
104 /// The duplicate position that failed validation.
105 duplicate_index: usize,
106 },
107}
108
109/// Identifies a runtime registration slot involved in a configuration conflict.
110///
111/// # Examples
112///
113/// ```
114/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
115/// use tenferro_runtime::{EngineId, RegistrationKey};
116///
117/// let key = RegistrationKey::Engine(EngineId::new("tenferro.cpu")?);
118/// assert!(format!("{key:?}").contains("tenferro.cpu"));
119/// # Ok(())
120/// # }
121/// ```
122#[derive(Clone, Debug, Eq, PartialEq)]
123#[non_exhaustive]
124pub enum RegistrationKey {
125 /// A direct engine registration.
126 Engine(EngineId),
127 /// An extension engine registration.
128 ExtensionEngine {
129 /// Extension family.
130 family: &'static str,
131 /// Runtime engine.
132 engine: EngineId,
133 },
134 /// An extension planning config registration.
135 ExtensionPlanning {
136 /// Extension module.
137 module: ExtensionModuleId,
138 /// Runtime engine.
139 engine: EngineId,
140 },
141 /// An extension cache-owner registration.
142 ExtensionCacheOwner {
143 /// Extension module.
144 module: ExtensionModuleId,
145 /// Local cache owner.
146 owner: CacheOwnerId,
147 },
148 /// A transfer provider keyed by source and destination endpoints.
149 TransferProvider {
150 /// Source endpoint.
151 source: TransferEndpoint,
152 /// Destination endpoint.
153 destination: TransferEndpoint,
154 },
155}
156
157/// Reports invalid execution-policy configuration.
158///
159/// Phase 4 currently has no invalid public execution policy state, but the
160/// error type is part of the accepted runtime configuration contract.
161///
162/// # Examples
163///
164/// ```
165/// use std::fmt::Debug;
166/// use tenferro_runtime::ExecutionPolicyError;
167///
168/// fn requires_debug<T: Debug>() {}
169/// requires_debug::<ExecutionPolicyError>();
170/// ```
171#[derive(Clone, Copy, Debug, Eq, PartialEq)]
172#[non_exhaustive]
173pub enum ExecutionPolicyError {}
174
175impl fmt::Display for ExecutionPolicyError {
176 fn fmt(&self, _formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
177 match *self {}
178 }
179}
180
181impl std::error::Error for ExecutionPolicyError {}
182
183/// Reports invalid runtime configuration.
184///
185/// # Examples
186///
187/// ```
188/// use tenferro_runtime::{EngineId, RuntimeConfigError};
189///
190/// let error = RuntimeConfigError::DuplicateEngine {
191/// engine_id: EngineId::new("tenferro.cpu").unwrap(),
192/// };
193/// assert!(error.to_string().contains("already registered"));
194/// ```
195#[derive(Debug, thiserror::Error)]
196#[non_exhaustive]
197pub enum RuntimeConfigError {
198 /// Runtime identity or registration identity allocation exhausted.
199 #[error("runtime identity space exhausted")]
200 IdentityExhausted,
201 /// A namespaced runtime identifier was malformed.
202 #[error("malformed {kind:?} identifier")]
203 MalformedIdentity {
204 /// Identifier class that failed validation.
205 kind: IdentityKind,
206 },
207 /// An engine registration tried to insert a different value for an
208 /// already-registered engine ID.
209 #[error("engine {engine_id:?} is already registered")]
210 DuplicateEngine {
211 /// Duplicated engine identifier.
212 engine_id: EngineId,
213 },
214 /// Two direct engines selected the same provider/device target.
215 #[error(
216 "provider/device target {provider_device_identity:?} is already bound to engine \
217 {first_engine_id:?}; cannot bind engine {duplicate_engine_id:?}"
218 )]
219 DuplicateProviderDeviceTarget {
220 /// Physical provider/device binding that was selected twice.
221 provider_device_identity: ProviderDeviceIdentity,
222 /// First logical engine using the binding.
223 first_engine_id: EngineId,
224 /// Later logical engine that attempted to reuse the binding.
225 duplicate_engine_id: EngineId,
226 },
227 /// A replacement attempted to change the physical target of an engine ID.
228 #[error(
229 "engine {engine_id:?} cannot rebind from provider/device target {current:?} \
230 to {replacement:?}; remove affected transfer routes, remove the old engine, \
231 register the replacement under the engine ID, and re-register the routes"
232 )]
233 EngineTargetRebind {
234 /// Logical engine whose physical binding was changed.
235 engine_id: EngineId,
236 /// Binding currently attached to the engine.
237 current: ProviderDeviceIdentity,
238 /// Binding requested by the replacement.
239 replacement: ProviderDeviceIdentity,
240 },
241 /// A direct capability slot was registered twice where replacement is not
242 /// accepted.
243 #[error("duplicate {capability:?} capability")]
244 DuplicateCapability {
245 /// Duplicated capability kind.
246 capability: CoreCapabilityKind,
247 },
248 /// A registration key conflicted across transactional registration input.
249 #[error("conflicting registration {key:?}")]
250 ConflictingRegistration {
251 /// Conflicting registration key.
252 key: RegistrationKey,
253 },
254 /// A requested engine replacement or removal had no existing record.
255 #[error("engine {engine_id:?} is not registered")]
256 MissingEngine {
257 /// Missing engine identifier.
258 engine_id: EngineId,
259 },
260 /// An extension module does not register a requested family for an engine.
261 #[error(
262 "extension module {module_id:?} family {family_id:?} has no engine registration for runtime engine {engine_id:?}"
263 )]
264 MissingExtensionEngine {
265 /// Extension module whose engine registration is missing.
266 module_id: ExtensionModuleId,
267 /// Extension family whose engine registration is missing.
268 family_id: &'static str,
269 /// Runtime engine selected by the owner.
270 engine_id: EngineId,
271 },
272 /// Engine registration provided no supported storage classes.
273 #[error("engine {engine_id:?} has no storage classes")]
274 EmptyStorageClasses {
275 /// Engine being registered.
276 engine_id: EngineId,
277 },
278 /// Engine registration repeated a storage class.
279 #[error(
280 "engine {engine_id:?} storage class {storage_class:?} is duplicated at \
281 positions {first_index} and {duplicate_index}"
282 )]
283 DuplicateStorageClass {
284 /// Engine being registered.
285 engine_id: EngineId,
286 /// Duplicated storage class.
287 storage_class: StorageClass,
288 /// First position where the storage class appeared.
289 first_index: usize,
290 /// Duplicate position that failed validation.
291 duplicate_index: usize,
292 },
293 /// Engine registration selected a default storage class that is not in its
294 /// supported list.
295 #[error("engine {engine_id:?} default storage class {default_storage_class:?} is not listed")]
296 DefaultStorageClassNotListed {
297 /// Engine being registered.
298 engine_id: EngineId,
299 /// Missing default storage class.
300 default_storage_class: StorageClass,
301 },
302 /// A transfer endpoint references an engine that is absent from the
303 /// candidate configuration.
304 #[error("transfer endpoint {endpoint:?} references an unregistered engine")]
305 UnknownTransferEndpointEngine {
306 /// Endpoint that references the missing engine.
307 endpoint: TransferEndpoint,
308 },
309 /// A transfer endpoint names a storage class unsupported by its engine.
310 #[error("transfer endpoint {endpoint:?} uses storage unsupported by its engine")]
311 UnsupportedTransferEndpointStorage {
312 /// Endpoint whose storage class is unsupported by its engine.
313 endpoint: TransferEndpoint,
314 },
315 /// A transfer route removal named an endpoint pair that is not registered.
316 #[error(
317 "no transfer provider is registered from source endpoint {source_endpoint:?} to destination \
318 endpoint {destination:?}"
319 )]
320 MissingTransferProvider {
321 /// Source endpoint of the absent route.
322 source_endpoint: TransferEndpoint,
323 /// Destination endpoint of the absent route.
324 destination: TransferEndpoint,
325 },
326 /// A route retains a physical binding that no longer matches its engine.
327 #[error(
328 "stale transfer route from source endpoint {source_endpoint:?} to destination endpoint \
329 {destination:?}: endpoint {endpoint:?} was registered for {registered:?} but \
330 now resolves to {current:?}"
331 )]
332 StaleTransferRoute {
333 /// Source endpoint of the stale route.
334 source_endpoint: TransferEndpoint,
335 /// Destination endpoint of the stale route.
336 destination: TransferEndpoint,
337 /// Route endpoint whose physical binding changed.
338 endpoint: TransferEndpoint,
339 /// Binding captured when this route was registered/frozen.
340 registered: Box<ProviderDeviceIdentity>,
341 /// Binding currently attached to the endpoint's engine.
342 current: Box<ProviderDeviceIdentity>,
343 },
344 /// A route reached freezing without a physical binding after validation.
345 #[error(
346 "transfer route from source endpoint {source_endpoint:?} to destination endpoint {destination:?} \
347 has no resolved provider/device binding"
348 )]
349 UnboundTransferRoute {
350 /// Source endpoint of the unbound route.
351 source_endpoint: TransferEndpoint,
352 /// Destination endpoint of the unbound route.
353 destination: TransferEndpoint,
354 },
355 /// A bound candidate referenced an engine that disappeared before freeze;
356 /// this indicates runtime corruption rather than candidate validation.
357 #[error("bound candidate invariant failed for transfer endpoint {endpoint:?}")]
358 BoundCandidateInvariant {
359 /// Endpoint whose already-bound engine lookup failed.
360 endpoint: TransferEndpoint,
361 },
362 /// An execution bridge omitted the validators required for explicit input
363 /// ingress and destination-buffer residency.
364 #[error("engine {engine_id:?} execution bridge has no complete input ingress validator")]
365 MissingInputIngressValidator {
366 /// Engine with the incomplete execution contract.
367 engine_id: EngineId,
368 },
369 /// A replacement attempted to reuse an engine ID with a different execution
370 /// context identity in a context where that is invalid.
371 #[error("engine {engine_id:?} context identity mismatch")]
372 ContextIdentityMismatch {
373 /// Engine with the mismatched context identity.
374 engine_id: EngineId,
375 },
376 /// Execution policy failed validation.
377 #[error("invalid execution policy: {reason}")]
378 InvalidExecutionPolicy {
379 /// Typed policy validation reason.
380 reason: ExecutionPolicyError,
381 },
382 /// Extension module transaction failed.
383 #[error("extension module registration failed")]
384 ExtensionModule {
385 /// Typed extension module source.
386 #[source]
387 source: ExtensionModuleError,
388 },
389}
390
391/// Reports a runtime execution bridge that violated its output contract.
392///
393/// # Examples
394///
395/// ```
396/// use tenferro_runtime::{EngineExecutionContractError, EngineId, StorageClass};
397///
398/// let error = EngineExecutionContractError::OutputResidencyMismatch {
399/// instruction_index: 3,
400/// output_slot: 7,
401/// engine_id: EngineId::new("tenferro.cpu")?,
402/// storage_class: StorageClass::new("tenferro.storage.host")?,
403/// backend_family: Some("foreign-backend"),
404/// allocation_domain: None,
405/// };
406/// assert!(error.to_string().contains("output slot 7"));
407/// # Ok::<(), Box<dyn std::error::Error>>(())
408/// ```
409#[derive(Debug, thiserror::Error)]
410#[non_exhaustive]
411pub enum EngineExecutionContractError {
412 /// An executor returned a tensor not owned by its scheduled engine/storage.
413 #[error(
414 "instruction {instruction_index} output slot {output_slot} is not resident in \
415 engine {engine_id:?} storage {storage_class:?} \
416 (backend family {backend_family:?}, allocation domain {allocation_domain:?})"
417 )]
418 OutputResidencyMismatch {
419 /// Execution-program instruction that produced the invalid tensor.
420 instruction_index: usize,
421 /// Execution slot containing the invalid tensor.
422 output_slot: usize,
423 /// Engine selected by the prepared schedule.
424 engine_id: EngineId,
425 /// Storage class selected by the prepared schedule.
426 storage_class: StorageClass,
427 /// Physical backend family reported by the tensor.
428 backend_family: Option<&'static str>,
429 /// Physical allocation domain reported by the tensor.
430 allocation_domain: Option<AllocationDomainId>,
431 },
432}
433
434/// Reports a runtime input that violates a prepared ingress contract.
435///
436/// # Examples
437///
438/// ```
439/// use tenferro_runtime::{EngineId, InputIngressContractError, StorageClass};
440/// use tenferro_tensor::Placement;
441///
442/// let error = InputIngressContractError::ResidencyMismatch {
443/// input_slot: 0,
444/// ingress_engine_id: EngineId::new("tenferro.cpu")?,
445/// ingress_storage_class: StorageClass::new("tenferro.storage.host")?,
446/// placement: Placement::default(),
447/// backend_family: Some("foreign-backend"),
448/// allocation_domain: None,
449/// };
450/// assert!(error.to_string().contains("input slot 0"));
451/// # Ok::<(), Box<dyn std::error::Error>>(())
452/// ```
453#[derive(Debug, thiserror::Error)]
454#[non_exhaustive]
455pub enum InputIngressContractError {
456 /// A runtime input's physical residency is incompatible with its prepared ingress.
457 #[error(
458 "input slot {input_slot} at placement {placement:?} with backend family \
459 {backend_family:?} and allocation domain {allocation_domain:?} is not accepted by \
460 ingress engine {ingress_engine_id:?} storage {ingress_storage_class:?}"
461 )]
462 ResidencyMismatch {
463 /// Execution slot containing the rejected runtime input.
464 input_slot: usize,
465 /// Engine selected as the prepared input ingress.
466 ingress_engine_id: EngineId,
467 /// Storage class selected as the prepared input ingress.
468 ingress_storage_class: StorageClass,
469 /// Logical placement reported by the input.
470 placement: Placement,
471 /// Physical backend family reported by the input.
472 backend_family: Option<&'static str>,
473 /// Physical allocation domain reported by the input.
474 allocation_domain: Option<AllocationDomainId>,
475 },
476}
477
478impl From<IdentityError> for RuntimeConfigError {
479 fn from(source: IdentityError) -> Self {
480 Self::MalformedIdentity {
481 kind: source.kind(),
482 }
483 }
484}
485
486/// Reports failure to publish a runtime reconfiguration.
487///
488/// # Examples
489///
490/// ```
491/// use tenferro_runtime::{RuntimeConfigError, RuntimeReconfigureError};
492///
493/// let error = RuntimeReconfigureError::Edit {
494/// source: RuntimeConfigError::IdentityExhausted,
495/// };
496/// assert!(std::error::Error::source(&error).is_some());
497/// ```
498#[derive(Debug, thiserror::Error)]
499#[non_exhaustive]
500pub enum RuntimeReconfigureError {
501 /// Runtime state could not be read or published.
502 #[error("runtime state failed")]
503 State {
504 /// Typed state source.
505 #[source]
506 source: super::RuntimeStateError,
507 },
508 /// The edit closure or publication validation rejected the candidate.
509 #[error("runtime reconfiguration edit failed")]
510 Edit {
511 /// Typed configuration source.
512 #[source]
513 source: RuntimeConfigError,
514 },
515 /// Another writer published over the snapshot this edit was based on.
516 #[error("runtime was concurrently reconfigured from {base:?} to {current:?}")]
517 ConcurrentReconfiguration {
518 /// Epoch captured before the edit closure ran.
519 base: RuntimeEpoch,
520 /// Epoch observed at publication time.
521 current: RuntimeEpoch,
522 },
523 /// Runtime epoch cannot advance without wrapping.
524 #[error("runtime epoch exhausted at {current:?}")]
525 EpochExhausted {
526 /// Current maximum epoch.
527 current: RuntimeEpoch,
528 },
529}
530
531/// Reports invalid extension module registration transactions.
532///
533/// # Examples
534///
535/// ```
536/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
537/// use tenferro_runtime::{EngineId, ExtensionModuleError, ExtensionModuleId};
538///
539/// let error = ExtensionModuleError::MissingPlanningConfig {
540/// module_id: ExtensionModuleId::new("tenferro.module.test")?,
541/// engine_id: EngineId::new("tenferro.engine.test")?,
542/// };
543/// assert!(error.to_string().contains("planning config"));
544/// # Ok(())
545/// # }
546/// ```
547#[derive(Debug, thiserror::Error)]
548#[non_exhaustive]
549pub enum ExtensionModuleError {
550 /// A distinct module is already installed at the same module ID.
551 #[error("extension module {module_id:?} is already installed")]
552 ConflictingModule {
553 /// Conflicting module ID.
554 module_id: ExtensionModuleId,
555 },
556 /// A distinct extension engine is already registered for the same
557 /// `(family, engine)` key.
558 #[error("extension module {module_id:?} has conflicting engine {family_id:?}/{engine_id:?}")]
559 ConflictingEngine {
560 /// Extension module ID.
561 module_id: ExtensionModuleId,
562 /// Extension family ID.
563 family_id: &'static str,
564 /// Runtime engine ID.
565 engine_id: EngineId,
566 },
567 /// A planning config was registered without exactly one matching engine.
568 #[error(
569 "extension module {module_id:?} registered planning config without engine {engine_id:?}"
570 )]
571 PlanningConfigWithoutEngine {
572 /// Extension module ID.
573 module_id: ExtensionModuleId,
574 /// Runtime engine ID.
575 engine_id: EngineId,
576 },
577 /// Planning config family did not match the registered engine family.
578 #[error(
579 "extension module {module_id:?} config for engine {engine_id:?} has family \
580 {actual:?}, expected {expected:?}"
581 )]
582 PlanningConfigFamilyMismatch {
583 /// Extension module ID.
584 module_id: ExtensionModuleId,
585 /// Runtime engine ID.
586 engine_id: EngineId,
587 /// Expected extension family ID.
588 expected: &'static str,
589 /// Actual extension family ID.
590 actual: &'static str,
591 },
592 /// An engine was registered without its required planning config.
593 #[error("extension module {module_id:?} engine {engine_id:?} has no planning config")]
594 MissingPlanningConfig {
595 /// Extension module ID.
596 module_id: ExtensionModuleId,
597 /// Runtime engine ID.
598 engine_id: EngineId,
599 },
600 /// A distinct planning config is already registered for the same engine.
601 #[error("extension module {module_id:?} has conflicting planning config for {engine_id:?}")]
602 ConflictingPlanningConfig {
603 /// Extension module ID.
604 module_id: ExtensionModuleId,
605 /// Runtime engine ID.
606 engine_id: EngineId,
607 },
608 /// A distinct cache owner is already registered for the same local owner ID.
609 #[error("extension module {module_id:?} has conflicting cache owner {owner:?}")]
610 ConflictingCacheOwner {
611 /// Extension module ID.
612 module_id: ExtensionModuleId,
613 /// Local cache owner ID.
614 owner: CacheOwnerId,
615 },
616}
617
618/// Reports failures while building value-free input signatures.
619///
620/// # Examples
621///
622/// ```
623/// use tenferro_runtime::{DType, InputSignatureEntry, LayoutClass};
624/// use tenferro_tensor::Placement;
625///
626/// let error = InputSignatureEntry::new(
627/// DType::F64,
628/// [2_usize].into_iter().collect(),
629/// Placement::default(),
630/// LayoutClass::new("tenferro.layout.strided").unwrap(),
631/// [1_isize, 2].into_iter().collect(),
632/// None,
633/// )
634/// .unwrap_err();
635/// assert!(matches!(error, tenferro_runtime::InputSignatureError::ShapeStrideRankMismatch { .. }));
636/// ```
637#[derive(Debug, thiserror::Error)]
638pub enum InputSignatureError {
639 /// Shape rank and stride rank disagree.
640 #[error("shape rank {rank} does not match stride count {stride_count}")]
641 ShapeStrideRankMismatch {
642 /// Number of shape axes.
643 rank: usize,
644 /// Number of stride axes.
645 stride_count: usize,
646 },
647 /// The alignment class is outside the finite `usize` alignment lattice.
648 #[error("alignment class {alignment_log2} is outside the usize alignment lattice")]
649 InvalidAlignmentClass {
650 /// Base-2 logarithm of the requested alignment.
651 alignment_log2: u8,
652 },
653 /// Tensor metadata could not be read for an input.
654 #[error("input {input} metadata is unavailable")]
655 TensorMetadata {
656 /// Input position in the prepare request.
657 input: usize,
658 /// Original typed tensor error.
659 source: TensorError,
660 },
661}
662
663/// Explains why rank specialization is required by a requested field.
664///
665/// # Examples
666///
667/// ```
668/// use tenferro_runtime::RankRequirement;
669///
670/// assert_eq!(RankRequirement::ExactStrides.to_string(), "exact strides");
671/// ```
672#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
673pub enum RankRequirement {
674 /// A concrete axis dimension was requested.
675 ConcreteAxis {
676 /// Axis that requires rank specialization.
677 axis: u32,
678 },
679 /// Exact strides were requested.
680 ExactStrides,
681}
682
683impl std::fmt::Display for RankRequirement {
684 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
685 match self {
686 Self::ConcreteAxis { axis } => write!(formatter, "concrete axis {axis}"),
687 Self::ExactStrides => formatter.write_str("exact strides"),
688 }
689 }
690}
691
692/// Reports invalid input-specialization requirements.
693///
694/// # Examples
695///
696/// ```
697/// use tenferro_runtime::{
698/// InputSpecializationRequirements, InputSpecializationRequirementsError, RankRequirement,
699/// };
700///
701/// let mut builder = InputSpecializationRequirements::builder();
702/// builder.rank(false).concrete_dimensions(vec![2]);
703/// assert_eq!(
704/// builder.build().unwrap_err(),
705/// InputSpecializationRequirementsError::RankRequired {
706/// reason: RankRequirement::ConcreteAxis { axis: 2 },
707/// }
708/// );
709/// ```
710#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
711pub enum InputSpecializationRequirementsError {
712 /// The same concrete axis appears more than once.
713 #[error("axis {axis} is duplicated at positions {first_index} and {duplicate_index}")]
714 DuplicateAxis {
715 /// Duplicated concrete axis.
716 axis: u32,
717 /// First position carrying the axis.
718 first_index: usize,
719 /// Duplicate position that failed validation.
720 duplicate_index: usize,
721 },
722 /// A requested specialization field requires rank specialization.
723 #[error("{reason} requires rank specialization")]
724 RankRequired {
725 /// Requirement that needs rank specialization.
726 reason: RankRequirement,
727 },
728 /// The alignment class is outside the finite `usize` alignment lattice.
729 #[error("alignment class {alignment_log2} is outside the usize alignment lattice")]
730 InvalidAlignmentClass {
731 /// Base-2 logarithm of the requested alignment.
732 alignment_log2: u8,
733 },
734}
735
736/// Reports failures while projecting signatures through specialization requirements.
737///
738/// # Examples
739///
740/// ```
741/// use tenferro_runtime::{InputSignature, SpecializationRequirements};
742///
743/// let error = SpecializationRequirements::polymorphic(1)
744/// .project(&InputSignature::new(Vec::new()))
745/// .unwrap_err();
746/// assert!(matches!(error, tenferro_runtime::PrepareError::Specialization { .. }));
747/// ```
748#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
749pub enum SpecializationError {
750 /// The signature does not have the required input arity.
751 #[error("expected {expected} inputs but got {actual}")]
752 WrongInputCount {
753 /// Required input count.
754 expected: usize,
755 /// Actual input count.
756 actual: usize,
757 },
758 /// A concrete-axis request names an axis outside the actual rank.
759 #[error("input {input} axis {axis} is outside rank {rank}")]
760 AxisOutOfRange {
761 /// Input position in the signature.
762 input: usize,
763 /// Requested axis.
764 axis: u32,
765 /// Actual rank.
766 rank: usize,
767 },
768 /// A required host-pointer alignment class is unavailable.
769 #[error("input {input} has unknown alignment; required class {required_alignment_log2}")]
770 AlignmentUnavailable {
771 /// Input position in the signature.
772 input: usize,
773 /// Required base-2 logarithm alignment class.
774 required_alignment_log2: u8,
775 },
776 /// A specialization projection could not encode every requested axis in
777 /// the finite public axis type.
778 #[error("input {input} rank {rank} is too large to project into u32 axes")]
779 ProjectionOverflow {
780 /// Input position in the signature.
781 input: usize,
782 /// Rank that could not be encoded.
783 rank: usize,
784 },
785}
786
787/// A typed execution-context mismatch from erased runtime dispatch.
788///
789/// # Examples
790///
791/// ```
792/// use tenferro_runtime::{ExecutionContextIdentity, ExecutionContextMismatch};
793///
794/// let error = ExecutionContextMismatch {
795/// expected: ExecutionContextIdentity::of::<u64>(),
796/// actual: ExecutionContextIdentity::of::<u32>(),
797/// };
798/// assert_eq!(error.expected, ExecutionContextIdentity::of::<u64>());
799/// ```
800#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
801#[error("execution context mismatch: expected {expected:?}, actual {actual:?}")]
802pub struct ExecutionContextMismatch {
803 /// Expected execution-context type identity.
804 pub expected: super::ExecutionContextIdentity,
805 /// Actual stored execution-context type identity.
806 pub actual: super::ExecutionContextIdentity,
807}
808
809/// Reports provider violations of runtime preparation contracts.
810///
811/// # Examples
812///
813/// ```
814/// use tenferro_runtime::{CoreCapabilityKind, ProviderContractError};
815///
816/// let error = ProviderContractError::WrongOperationFamily {
817/// expected: CoreCapabilityKind::Elementwise,
818/// operation: "dot_general",
819/// };
820/// assert!(error.to_string().contains("dot_general"));
821/// ```
822#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
823pub enum ProviderContractError {
824 /// A provider accepted a request from the wrong core operation family.
825 #[error("operation {operation:?} is not in {expected:?}")]
826 WrongOperationFamily {
827 /// Expected core capability family.
828 expected: CoreCapabilityKind,
829 /// Operation diagnostic name.
830 operation: &'static str,
831 },
832 /// A prepared operation returned a different binding than requested.
833 #[error("prepared binding mismatch")]
834 BindingMismatch {
835 /// Runtime-created binding requested from the provider.
836 expected: Box<PreparedOperationBinding>,
837 /// Binding returned by the provider.
838 actual: Box<PreparedOperationBinding>,
839 },
840 /// A prepared operation returned a different specialization projection.
841 #[error("prepared specialization projection mismatch")]
842 ProjectionMismatch {
843 /// Runtime-created projection requested from the provider.
844 expected: Box<SpecializationProjection>,
845 /// Projection returned by the provider.
846 actual: Box<SpecializationProjection>,
847 },
848 /// A provider returned an invalid specialization.
849 #[error("invalid provider specialization: {source}")]
850 InvalidSpecialization {
851 /// Original specialization failure.
852 #[source]
853 source: SpecializationError,
854 },
855 /// A provider requested specialization that did not strictly widen the
856 /// current requirements.
857 #[error("specialization did not strictly widen the current requirements")]
858 NonWideningSpecialization {
859 /// Previous specialization requirements.
860 previous: Box<super::SpecializationRequirements>,
861 /// Provider-requested specialization requirements.
862 next: Box<super::SpecializationRequirements>,
863 },
864 /// A provider exceeded the finite specialization retry bound.
865 #[error("specialization retry limit exceeded after {attempts} attempts; limit was {limit}")]
866 SpecializationRetryLimitExceeded {
867 /// Number of redirect attempts observed.
868 attempts: usize,
869 /// Computed finite retry bound.
870 limit: usize,
871 },
872}
873
874/// Reports failures while preparing runtime execution artifacts.
875///
876/// # Examples
877///
878/// ```
879/// use tenferro_runtime::{InputSignature, PrepareError, SpecializationRequirements};
880///
881/// let error = SpecializationRequirements::polymorphic(1)
882/// .project(&InputSignature::new(Vec::new()))
883/// .unwrap_err();
884/// assert!(matches!(error, PrepareError::Specialization { .. }));
885/// ```
886#[derive(Debug, thiserror::Error)]
887pub enum PrepareError {
888 /// Prepared artifact belongs to a different runtime.
889 #[error("runtime mismatch: expected {expected:?}, actual {actual:?}")]
890 RuntimeMismatch {
891 /// Runtime expected by the prepared artifact.
892 expected: RuntimeId,
893 /// Runtime currently executing.
894 actual: RuntimeId,
895 },
896 /// Prepared artifact was created for a stale runtime epoch.
897 #[error("stale runtime epoch: prepared {prepared:?}, current {current:?}")]
898 StaleEpoch {
899 /// Epoch captured by the prepared artifact.
900 prepared: RuntimeEpoch,
901 /// Runtime's current epoch.
902 current: RuntimeEpoch,
903 },
904 /// Input signature construction failed.
905 #[error("input signature failed")]
906 InputSignature {
907 /// Typed source error.
908 source: InputSignatureError,
909 },
910 /// Semantic shape guards failed.
911 #[error("shape guard failed")]
912 ShapeGuard {
913 /// Typed source error.
914 #[source]
915 source: ShapeGuardError,
916 },
917 /// Signature projection through specialization requirements failed.
918 #[error("specialization failed")]
919 Specialization {
920 /// Typed source error.
921 source: SpecializationError,
922 },
923 /// No eligible engine matched the placement and capability constraints.
924 #[error("no eligible engine for {constraint:?}")]
925 NoEligibleEngine {
926 /// Placement constraint that could not be satisfied.
927 constraint: ProgramPlacementConstraint,
928 },
929 /// A preparation-only engine advertised the required capability but cannot
930 /// produce an executable prepared schedule.
931 #[error(
932 "engine {engine_id:?} has a matching preparation capability but no executable binding"
933 )]
934 NoExecutableEngine {
935 /// Engine whose preparation capability cannot be used for execution.
936 engine_id: EngineId,
937 },
938 /// The compiled graph uses an extension family absent from the runtime.
939 #[error("missing extension family {family_id:?}")]
940 MissingExtension {
941 /// Stable extension family identifier.
942 family_id: &'static str,
943 },
944 /// No eligible engine declared an ingress compatible with an input placement.
945 #[error("no eligible input ingress for input {input_index} at placement {placement:?}")]
946 NoInputIngress {
947 /// Input position in the compiled-program signature.
948 input_index: usize,
949 /// Placement rejected by every eligible engine ingress.
950 placement: tenferro_tensor::Placement,
951 },
952 /// Placement resolution exceeded its bounded combination-search budget.
953 #[error(
954 "placement resolution exhausted its search budget after {attempts} attempts \
955 (limit {limit})"
956 )]
957 DispatchSearchBudgetExceeded {
958 /// Number of dispatch combinations attempted.
959 attempts: usize,
960 /// Configured hard limit for this search.
961 limit: usize,
962 },
963 /// No available copy of a value has a registered direct transfer provider
964 /// to the storage required by its consumer.
965 #[error(
966 "instruction {instruction_index} has no direct transfer provider for value slot \
967 {value_slot} from {available_source_endpoints:?} to {destination_endpoint:?}"
968 )]
969 MissingTransferProvider {
970 /// Staged instruction whose input cannot be reached.
971 instruction_index: usize,
972 /// Value slot required by the instruction.
973 value_slot: usize,
974 /// Destination endpoint required by the instruction.
975 destination_endpoint: TransferEndpoint,
976 /// Endpoints retaining an available copy.
977 available_source_endpoints: Vec<TransferEndpoint>,
978 },
979 /// A resolved placement references an engine absent from its preparation snapshot.
980 #[error("resolved engine {engine_id:?} is unavailable in the preparation snapshot")]
981 ResolvedEngineUnavailable {
982 /// Engine referenced by the resolved placement.
983 engine_id: EngineId,
984 },
985 /// No provider supports this operation under the requested constraints.
986 #[error("operation is unsupported: {reason}")]
987 Unsupported {
988 /// Deterministic unsupported reason.
989 reason: UnsupportedReason,
990 },
991 /// Deterministic execution was requested from an engine that cannot provide it.
992 #[error("determinism unsupported by engine {engine_id:?}")]
993 DeterminismUnsupported {
994 /// Engine that cannot satisfy deterministic preparation.
995 engine_id: EngineId,
996 },
997 /// Provider returned a value that violates the runtime preparation contract.
998 #[error("provider contract violation: {source}")]
999 ProviderContract {
1000 /// Typed provider-contract source.
1001 #[source]
1002 source: ProviderContractError,
1003 },
1004 /// Preparation recursion reached an unsupported cycle.
1005 #[error("preparation cycle at {key:?}")]
1006 PreparationCycle {
1007 /// Bounded summary of the recursive key.
1008 key: PreparationKeySummary,
1009 },
1010 /// Preparation recursively requested a different key.
1011 #[error("nested preparation unsupported: parent {parent:?}, requested {requested:?}")]
1012 NestedPreparationUnsupported {
1013 /// Parent key currently preparing on this thread.
1014 parent: PreparationKeySummary,
1015 /// Nested key requested by the producer.
1016 requested: PreparationKeySummary,
1017 },
1018 /// The preparation cache is full of active/queued distinct keys.
1019 #[error(
1020 "preparation cache is at capacity: {in_flight} in flight and \
1021 {queued_distinct_keys} queued distinct keys"
1022 )]
1023 CacheInFlightCapacityExceeded {
1024 /// Active distinct preparations.
1025 in_flight: usize,
1026 /// Queued distinct keys.
1027 queued_distinct_keys: usize,
1028 },
1029 /// Runtime cache state could not be accessed.
1030 #[error("preparation cache state unavailable: {source}")]
1031 CacheState {
1032 /// Runtime state source.
1033 #[source]
1034 source: super::RuntimeStateError,
1035 },
1036 /// Engine or internal preparation source failed.
1037 #[error("engine preparation failed: {source}")]
1038 Engine {
1039 /// Shared typed source.
1040 #[source]
1041 source: Arc<dyn Error + Send + Sync>,
1042 },
1043}