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
478/// Reports failure to admit an asynchronous runtime submission.
479///
480/// # Examples
481///
482/// ```
483/// use std::io;
484/// use tenferro_runtime::SubmissionError;
485///
486/// let error = SubmissionError::WorkerSpawn {
487/// source: io::Error::other("worker unavailable"),
488/// };
489/// assert!(error.to_string().contains("spawn"));
490/// ```
491#[derive(Debug, thiserror::Error)]
492#[non_exhaustive]
493pub enum SubmissionError {
494 /// The runtime could not create the worker after synchronous preparation.
495 #[error("failed to spawn runtime submission worker")]
496 WorkerSpawn {
497 /// Operating-system thread creation failure.
498 #[source]
499 source: std::io::Error,
500 },
501}
502
503impl From<IdentityError> for RuntimeConfigError {
504 fn from(source: IdentityError) -> Self {
505 Self::MalformedIdentity {
506 kind: source.kind(),
507 }
508 }
509}
510
511/// Reports failure to publish a runtime reconfiguration.
512///
513/// # Examples
514///
515/// ```
516/// use tenferro_runtime::{RuntimeConfigError, RuntimeReconfigureError};
517///
518/// let error = RuntimeReconfigureError::Edit {
519/// source: RuntimeConfigError::IdentityExhausted,
520/// };
521/// assert!(std::error::Error::source(&error).is_some());
522/// ```
523#[derive(Debug, thiserror::Error)]
524#[non_exhaustive]
525pub enum RuntimeReconfigureError {
526 /// Runtime state could not be read or published.
527 #[error("runtime state failed")]
528 State {
529 /// Typed state source.
530 #[source]
531 source: super::RuntimeStateError,
532 },
533 /// The edit closure or publication validation rejected the candidate.
534 #[error("runtime reconfiguration edit failed")]
535 Edit {
536 /// Typed configuration source.
537 #[source]
538 source: RuntimeConfigError,
539 },
540 /// Another writer published over the snapshot this edit was based on.
541 #[error("runtime was concurrently reconfigured from {base:?} to {current:?}")]
542 ConcurrentReconfiguration {
543 /// Epoch captured before the edit closure ran.
544 base: RuntimeEpoch,
545 /// Epoch observed at publication time.
546 current: RuntimeEpoch,
547 },
548 /// Runtime epoch cannot advance without wrapping.
549 #[error("runtime epoch exhausted at {current:?}")]
550 EpochExhausted {
551 /// Current maximum epoch.
552 current: RuntimeEpoch,
553 },
554}
555
556/// Reports invalid extension module registration transactions.
557///
558/// # Examples
559///
560/// ```
561/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
562/// use tenferro_runtime::{EngineId, ExtensionModuleError, ExtensionModuleId};
563///
564/// let error = ExtensionModuleError::MissingPlanningConfig {
565/// module_id: ExtensionModuleId::new("tenferro.module.test")?,
566/// engine_id: EngineId::new("tenferro.engine.test")?,
567/// };
568/// assert!(error.to_string().contains("planning config"));
569/// # Ok(())
570/// # }
571/// ```
572#[derive(Debug, thiserror::Error)]
573#[non_exhaustive]
574pub enum ExtensionModuleError {
575 /// A distinct module is already installed at the same module ID.
576 #[error("extension module {module_id:?} is already installed")]
577 ConflictingModule {
578 /// Conflicting module ID.
579 module_id: ExtensionModuleId,
580 },
581 /// A distinct extension engine is already registered for the same
582 /// `(family, engine)` key.
583 #[error("extension module {module_id:?} has conflicting engine {family_id:?}/{engine_id:?}")]
584 ConflictingEngine {
585 /// Extension module ID.
586 module_id: ExtensionModuleId,
587 /// Extension family ID.
588 family_id: &'static str,
589 /// Runtime engine ID.
590 engine_id: EngineId,
591 },
592 /// A planning config was registered without exactly one matching engine.
593 #[error(
594 "extension module {module_id:?} registered planning config without engine {engine_id:?}"
595 )]
596 PlanningConfigWithoutEngine {
597 /// Extension module ID.
598 module_id: ExtensionModuleId,
599 /// Runtime engine ID.
600 engine_id: EngineId,
601 },
602 /// Planning config family did not match the registered engine family.
603 #[error(
604 "extension module {module_id:?} config for engine {engine_id:?} has family \
605 {actual:?}, expected {expected:?}"
606 )]
607 PlanningConfigFamilyMismatch {
608 /// Extension module ID.
609 module_id: ExtensionModuleId,
610 /// Runtime engine ID.
611 engine_id: EngineId,
612 /// Expected extension family ID.
613 expected: &'static str,
614 /// Actual extension family ID.
615 actual: &'static str,
616 },
617 /// An engine was registered without its required planning config.
618 #[error("extension module {module_id:?} engine {engine_id:?} has no planning config")]
619 MissingPlanningConfig {
620 /// Extension module ID.
621 module_id: ExtensionModuleId,
622 /// Runtime engine ID.
623 engine_id: EngineId,
624 },
625 /// A distinct planning config is already registered for the same engine.
626 #[error("extension module {module_id:?} has conflicting planning config for {engine_id:?}")]
627 ConflictingPlanningConfig {
628 /// Extension module ID.
629 module_id: ExtensionModuleId,
630 /// Runtime engine ID.
631 engine_id: EngineId,
632 },
633 /// A distinct cache owner is already registered for the same local owner ID.
634 #[error("extension module {module_id:?} has conflicting cache owner {owner:?}")]
635 ConflictingCacheOwner {
636 /// Extension module ID.
637 module_id: ExtensionModuleId,
638 /// Local cache owner ID.
639 owner: CacheOwnerId,
640 },
641}
642
643/// Reports failures while building value-free input signatures.
644///
645/// # Examples
646///
647/// ```
648/// use tenferro_runtime::{DType, InputSignatureEntry, LayoutClass};
649/// use tenferro_tensor::Placement;
650///
651/// let error = InputSignatureEntry::new(
652/// DType::F64,
653/// [2_usize].into_iter().collect(),
654/// Placement::default(),
655/// LayoutClass::new("tenferro.layout.strided").unwrap(),
656/// [1_isize, 2].into_iter().collect(),
657/// None,
658/// )
659/// .unwrap_err();
660/// assert!(matches!(error, tenferro_runtime::InputSignatureError::ShapeStrideRankMismatch { .. }));
661/// ```
662#[derive(Debug, thiserror::Error)]
663pub enum InputSignatureError {
664 /// Shape rank and stride rank disagree.
665 #[error("shape rank {rank} does not match stride count {stride_count}")]
666 ShapeStrideRankMismatch {
667 /// Number of shape axes.
668 rank: usize,
669 /// Number of stride axes.
670 stride_count: usize,
671 },
672 /// The alignment class is outside the finite `usize` alignment lattice.
673 #[error("alignment class {alignment_log2} is outside the usize alignment lattice")]
674 InvalidAlignmentClass {
675 /// Base-2 logarithm of the requested alignment.
676 alignment_log2: u8,
677 },
678 /// Tensor metadata could not be read for an input.
679 #[error("input {input} metadata is unavailable")]
680 TensorMetadata {
681 /// Input position in the prepare request.
682 input: usize,
683 /// Original typed tensor error.
684 source: TensorError,
685 },
686}
687
688/// Explains why rank specialization is required by a requested field.
689///
690/// # Examples
691///
692/// ```
693/// use tenferro_runtime::RankRequirement;
694///
695/// assert_eq!(RankRequirement::ExactStrides.to_string(), "exact strides");
696/// ```
697#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
698pub enum RankRequirement {
699 /// A concrete axis dimension was requested.
700 ConcreteAxis {
701 /// Axis that requires rank specialization.
702 axis: u32,
703 },
704 /// Exact strides were requested.
705 ExactStrides,
706}
707
708impl std::fmt::Display for RankRequirement {
709 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
710 match self {
711 Self::ConcreteAxis { axis } => write!(formatter, "concrete axis {axis}"),
712 Self::ExactStrides => formatter.write_str("exact strides"),
713 }
714 }
715}
716
717/// Reports invalid input-specialization requirements.
718///
719/// # Examples
720///
721/// ```
722/// use tenferro_runtime::{
723/// InputSpecializationRequirements, InputSpecializationRequirementsError, RankRequirement,
724/// };
725///
726/// let mut builder = InputSpecializationRequirements::builder();
727/// builder.rank(false).concrete_dimensions(vec![2]);
728/// assert_eq!(
729/// builder.build().unwrap_err(),
730/// InputSpecializationRequirementsError::RankRequired {
731/// reason: RankRequirement::ConcreteAxis { axis: 2 },
732/// }
733/// );
734/// ```
735#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
736pub enum InputSpecializationRequirementsError {
737 /// The same concrete axis appears more than once.
738 #[error("axis {axis} is duplicated at positions {first_index} and {duplicate_index}")]
739 DuplicateAxis {
740 /// Duplicated concrete axis.
741 axis: u32,
742 /// First position carrying the axis.
743 first_index: usize,
744 /// Duplicate position that failed validation.
745 duplicate_index: usize,
746 },
747 /// A requested specialization field requires rank specialization.
748 #[error("{reason} requires rank specialization")]
749 RankRequired {
750 /// Requirement that needs rank specialization.
751 reason: RankRequirement,
752 },
753 /// The alignment class is outside the finite `usize` alignment lattice.
754 #[error("alignment class {alignment_log2} is outside the usize alignment lattice")]
755 InvalidAlignmentClass {
756 /// Base-2 logarithm of the requested alignment.
757 alignment_log2: u8,
758 },
759}
760
761/// Reports failures while projecting signatures through specialization requirements.
762///
763/// # Examples
764///
765/// ```
766/// use tenferro_runtime::{InputSignature, SpecializationRequirements};
767///
768/// let error = SpecializationRequirements::polymorphic(1)
769/// .project(&InputSignature::new(Vec::new()))
770/// .unwrap_err();
771/// assert!(matches!(error, tenferro_runtime::PrepareError::Specialization { .. }));
772/// ```
773#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
774pub enum SpecializationError {
775 /// The signature does not have the required input arity.
776 #[error("expected {expected} inputs but got {actual}")]
777 WrongInputCount {
778 /// Required input count.
779 expected: usize,
780 /// Actual input count.
781 actual: usize,
782 },
783 /// A concrete-axis request names an axis outside the actual rank.
784 #[error("input {input} axis {axis} is outside rank {rank}")]
785 AxisOutOfRange {
786 /// Input position in the signature.
787 input: usize,
788 /// Requested axis.
789 axis: u32,
790 /// Actual rank.
791 rank: usize,
792 },
793 /// A required host-pointer alignment class is unavailable.
794 #[error("input {input} has unknown alignment; required class {required_alignment_log2}")]
795 AlignmentUnavailable {
796 /// Input position in the signature.
797 input: usize,
798 /// Required base-2 logarithm alignment class.
799 required_alignment_log2: u8,
800 },
801 /// A specialization projection could not encode every requested axis in
802 /// the finite public axis type.
803 #[error("input {input} rank {rank} is too large to project into u32 axes")]
804 ProjectionOverflow {
805 /// Input position in the signature.
806 input: usize,
807 /// Rank that could not be encoded.
808 rank: usize,
809 },
810}
811
812/// A typed execution-context mismatch from erased runtime dispatch.
813///
814/// # Examples
815///
816/// ```
817/// use tenferro_runtime::{ExecutionContextIdentity, ExecutionContextMismatch};
818///
819/// let error = ExecutionContextMismatch {
820/// expected: ExecutionContextIdentity::of::<u64>(),
821/// actual: ExecutionContextIdentity::of::<u32>(),
822/// };
823/// assert_eq!(error.expected, ExecutionContextIdentity::of::<u64>());
824/// ```
825#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
826#[error("execution context mismatch: expected {expected:?}, actual {actual:?}")]
827pub struct ExecutionContextMismatch {
828 /// Expected execution-context type identity.
829 pub expected: super::ExecutionContextIdentity,
830 /// Actual stored execution-context type identity.
831 pub actual: super::ExecutionContextIdentity,
832}
833
834/// Reports provider violations of runtime preparation contracts.
835///
836/// # Examples
837///
838/// ```
839/// use tenferro_runtime::{CoreCapabilityKind, ProviderContractError};
840///
841/// let error = ProviderContractError::WrongOperationFamily {
842/// expected: CoreCapabilityKind::Elementwise,
843/// operation: "dot_general",
844/// };
845/// assert!(error.to_string().contains("dot_general"));
846/// ```
847#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
848pub enum ProviderContractError {
849 /// A provider accepted a request from the wrong core operation family.
850 #[error("operation {operation:?} is not in {expected:?}")]
851 WrongOperationFamily {
852 /// Expected core capability family.
853 expected: CoreCapabilityKind,
854 /// Operation diagnostic name.
855 operation: &'static str,
856 },
857 /// A prepared operation returned a different binding than requested.
858 #[error("prepared binding mismatch")]
859 BindingMismatch {
860 /// Runtime-created binding requested from the provider.
861 expected: Box<PreparedOperationBinding>,
862 /// Binding returned by the provider.
863 actual: Box<PreparedOperationBinding>,
864 },
865 /// A prepared operation returned a different specialization projection.
866 #[error("prepared specialization projection mismatch")]
867 ProjectionMismatch {
868 /// Runtime-created projection requested from the provider.
869 expected: Box<SpecializationProjection>,
870 /// Projection returned by the provider.
871 actual: Box<SpecializationProjection>,
872 },
873 /// A provider returned an invalid specialization.
874 #[error("invalid provider specialization: {source}")]
875 InvalidSpecialization {
876 /// Original specialization failure.
877 #[source]
878 source: SpecializationError,
879 },
880 /// A provider requested specialization that did not strictly widen the
881 /// current requirements.
882 #[error("specialization did not strictly widen the current requirements")]
883 NonWideningSpecialization {
884 /// Previous specialization requirements.
885 previous: Box<super::SpecializationRequirements>,
886 /// Provider-requested specialization requirements.
887 next: Box<super::SpecializationRequirements>,
888 },
889 /// A provider exceeded the finite specialization retry bound.
890 #[error("specialization retry limit exceeded after {attempts} attempts; limit was {limit}")]
891 SpecializationRetryLimitExceeded {
892 /// Number of redirect attempts observed.
893 attempts: usize,
894 /// Computed finite retry bound.
895 limit: usize,
896 },
897}
898
899/// Reports failures while preparing runtime execution artifacts.
900///
901/// # Examples
902///
903/// ```
904/// use tenferro_runtime::{InputSignature, PrepareError, SpecializationRequirements};
905///
906/// let error = SpecializationRequirements::polymorphic(1)
907/// .project(&InputSignature::new(Vec::new()))
908/// .unwrap_err();
909/// assert!(matches!(error, PrepareError::Specialization { .. }));
910/// ```
911#[derive(Debug, thiserror::Error)]
912pub enum PrepareError {
913 /// Prepared artifact belongs to a different runtime.
914 #[error("runtime mismatch: expected {expected:?}, actual {actual:?}")]
915 RuntimeMismatch {
916 /// Runtime expected by the prepared artifact.
917 expected: RuntimeId,
918 /// Runtime currently executing.
919 actual: RuntimeId,
920 },
921 /// Prepared artifact was created for a stale runtime epoch.
922 #[error("stale runtime epoch: prepared {prepared:?}, current {current:?}")]
923 StaleEpoch {
924 /// Epoch captured by the prepared artifact.
925 prepared: RuntimeEpoch,
926 /// Runtime's current epoch.
927 current: RuntimeEpoch,
928 },
929 /// Input signature construction failed.
930 #[error("input signature failed")]
931 InputSignature {
932 /// Typed source error.
933 source: InputSignatureError,
934 },
935 /// Semantic shape guards failed.
936 #[error("shape guard failed")]
937 ShapeGuard {
938 /// Typed source error.
939 #[source]
940 source: ShapeGuardError,
941 },
942 /// Signature projection through specialization requirements failed.
943 #[error("specialization failed")]
944 Specialization {
945 /// Typed source error.
946 source: SpecializationError,
947 },
948 /// No eligible engine matched the placement and capability constraints.
949 #[error("no eligible engine for {constraint:?}")]
950 NoEligibleEngine {
951 /// Placement constraint that could not be satisfied.
952 constraint: ProgramPlacementConstraint,
953 },
954 /// A preparation-only engine advertised the required capability but cannot
955 /// produce an executable prepared schedule.
956 #[error(
957 "engine {engine_id:?} has a matching preparation capability but no executable binding"
958 )]
959 NoExecutableEngine {
960 /// Engine whose preparation capability cannot be used for execution.
961 engine_id: EngineId,
962 },
963 /// The compiled graph uses an extension family absent from the runtime.
964 #[error("missing extension family {family_id:?}")]
965 MissingExtension {
966 /// Stable extension family identifier.
967 family_id: &'static str,
968 },
969 /// No eligible engine declared an ingress compatible with an input placement.
970 #[error("no eligible input ingress for input {input_index} at placement {placement:?}")]
971 NoInputIngress {
972 /// Input position in the compiled-program signature.
973 input_index: usize,
974 /// Placement rejected by every eligible engine ingress.
975 placement: tenferro_tensor::Placement,
976 },
977 /// Placement resolution exceeded its bounded combination-search budget.
978 #[error(
979 "placement resolution exhausted its search budget after {attempts} attempts \
980 (limit {limit})"
981 )]
982 DispatchSearchBudgetExceeded {
983 /// Number of dispatch combinations attempted.
984 attempts: usize,
985 /// Configured hard limit for this search.
986 limit: usize,
987 },
988 /// No available copy of a value has a registered direct transfer provider
989 /// to the storage required by its consumer.
990 #[error(
991 "instruction {instruction_index} has no direct transfer provider for value slot \
992 {value_slot} from {available_source_endpoints:?} to {destination_endpoint:?}"
993 )]
994 MissingTransferProvider {
995 /// Staged instruction whose input cannot be reached.
996 instruction_index: usize,
997 /// Value slot required by the instruction.
998 value_slot: usize,
999 /// Destination endpoint required by the instruction.
1000 destination_endpoint: TransferEndpoint,
1001 /// Endpoints retaining an available copy.
1002 available_source_endpoints: Vec<TransferEndpoint>,
1003 },
1004 /// A resolved placement references an engine absent from its preparation snapshot.
1005 #[error("resolved engine {engine_id:?} is unavailable in the preparation snapshot")]
1006 ResolvedEngineUnavailable {
1007 /// Engine referenced by the resolved placement.
1008 engine_id: EngineId,
1009 },
1010 /// No provider supports this operation under the requested constraints.
1011 #[error("operation is unsupported: {reason}")]
1012 Unsupported {
1013 /// Deterministic unsupported reason.
1014 reason: UnsupportedReason,
1015 },
1016 /// Deterministic execution was requested from an engine that cannot provide it.
1017 #[error("determinism unsupported by engine {engine_id:?}")]
1018 DeterminismUnsupported {
1019 /// Engine that cannot satisfy deterministic preparation.
1020 engine_id: EngineId,
1021 },
1022 /// Provider returned a value that violates the runtime preparation contract.
1023 #[error("provider contract violation: {source}")]
1024 ProviderContract {
1025 /// Typed provider-contract source.
1026 #[source]
1027 source: ProviderContractError,
1028 },
1029 /// Preparation recursion reached an unsupported cycle.
1030 #[error("preparation cycle at {key:?}")]
1031 PreparationCycle {
1032 /// Bounded summary of the recursive key.
1033 key: PreparationKeySummary,
1034 },
1035 /// Preparation recursively requested a different key.
1036 #[error("nested preparation unsupported: parent {parent:?}, requested {requested:?}")]
1037 NestedPreparationUnsupported {
1038 /// Parent key currently preparing on this thread.
1039 parent: PreparationKeySummary,
1040 /// Nested key requested by the producer.
1041 requested: PreparationKeySummary,
1042 },
1043 /// The preparation cache is full of active/queued distinct keys.
1044 #[error(
1045 "preparation cache is at capacity: {in_flight} in flight and \
1046 {queued_distinct_keys} queued distinct keys"
1047 )]
1048 CacheInFlightCapacityExceeded {
1049 /// Active distinct preparations.
1050 in_flight: usize,
1051 /// Queued distinct keys.
1052 queued_distinct_keys: usize,
1053 },
1054 /// Runtime cache state could not be accessed.
1055 #[error("preparation cache state unavailable: {source}")]
1056 CacheState {
1057 /// Runtime state source.
1058 #[source]
1059 source: super::RuntimeStateError,
1060 },
1061 /// Engine or internal preparation source failed.
1062 #[error("engine preparation failed: {source}")]
1063 Engine {
1064 /// Shared typed source.
1065 #[source]
1066 source: Arc<dyn Error + Send + Sync>,
1067 },
1068}