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 /// Engine registration provided no supported storage classes.
261 #[error("engine {engine_id:?} has no storage classes")]
262 EmptyStorageClasses {
263 /// Engine being registered.
264 engine_id: EngineId,
265 },
266 /// Engine registration repeated a storage class.
267 #[error(
268 "engine {engine_id:?} storage class {storage_class:?} is duplicated at \
269 positions {first_index} and {duplicate_index}"
270 )]
271 DuplicateStorageClass {
272 /// Engine being registered.
273 engine_id: EngineId,
274 /// Duplicated storage class.
275 storage_class: StorageClass,
276 /// First position where the storage class appeared.
277 first_index: usize,
278 /// Duplicate position that failed validation.
279 duplicate_index: usize,
280 },
281 /// Engine registration selected a default storage class that is not in its
282 /// supported list.
283 #[error("engine {engine_id:?} default storage class {default_storage_class:?} is not listed")]
284 DefaultStorageClassNotListed {
285 /// Engine being registered.
286 engine_id: EngineId,
287 /// Missing default storage class.
288 default_storage_class: StorageClass,
289 },
290 /// A transfer endpoint references an engine that is absent from the
291 /// candidate configuration.
292 #[error("transfer endpoint {endpoint:?} references an unregistered engine")]
293 UnknownTransferEndpointEngine {
294 /// Endpoint that references the missing engine.
295 endpoint: TransferEndpoint,
296 },
297 /// A transfer endpoint names a storage class unsupported by its engine.
298 #[error("transfer endpoint {endpoint:?} uses storage unsupported by its engine")]
299 UnsupportedTransferEndpointStorage {
300 /// Endpoint whose storage class is unsupported by its engine.
301 endpoint: TransferEndpoint,
302 },
303 /// A transfer route removal named an endpoint pair that is not registered.
304 #[error(
305 "no transfer provider is registered from source endpoint {source_endpoint:?} to destination \
306 endpoint {destination:?}"
307 )]
308 MissingTransferProvider {
309 /// Source endpoint of the absent route.
310 source_endpoint: TransferEndpoint,
311 /// Destination endpoint of the absent route.
312 destination: TransferEndpoint,
313 },
314 /// A route retains a physical binding that no longer matches its engine.
315 #[error(
316 "stale transfer route from source endpoint {source_endpoint:?} to destination endpoint \
317 {destination:?}: endpoint {endpoint:?} was registered for {registered:?} but \
318 now resolves to {current:?}"
319 )]
320 StaleTransferRoute {
321 /// Source endpoint of the stale route.
322 source_endpoint: TransferEndpoint,
323 /// Destination endpoint of the stale route.
324 destination: TransferEndpoint,
325 /// Route endpoint whose physical binding changed.
326 endpoint: TransferEndpoint,
327 /// Binding captured when this route was registered/frozen.
328 registered: Box<ProviderDeviceIdentity>,
329 /// Binding currently attached to the endpoint's engine.
330 current: Box<ProviderDeviceIdentity>,
331 },
332 /// A route reached freezing without a physical binding after validation.
333 #[error(
334 "transfer route from source endpoint {source_endpoint:?} to destination endpoint {destination:?} \
335 has no resolved provider/device binding"
336 )]
337 UnboundTransferRoute {
338 /// Source endpoint of the unbound route.
339 source_endpoint: TransferEndpoint,
340 /// Destination endpoint of the unbound route.
341 destination: TransferEndpoint,
342 },
343 /// A bound candidate referenced an engine that disappeared before freeze;
344 /// this indicates runtime corruption rather than candidate validation.
345 #[error("bound candidate invariant failed for transfer endpoint {endpoint:?}")]
346 BoundCandidateInvariant {
347 /// Endpoint whose already-bound engine lookup failed.
348 endpoint: TransferEndpoint,
349 },
350 /// An execution bridge omitted the validators required for explicit input
351 /// ingress and destination-buffer residency.
352 #[error("engine {engine_id:?} execution bridge has no complete input ingress validator")]
353 MissingInputIngressValidator {
354 /// Engine with the incomplete execution contract.
355 engine_id: EngineId,
356 },
357 /// A replacement attempted to reuse an engine ID with a different execution
358 /// context identity in a context where that is invalid.
359 #[error("engine {engine_id:?} context identity mismatch")]
360 ContextIdentityMismatch {
361 /// Engine with the mismatched context identity.
362 engine_id: EngineId,
363 },
364 /// Execution policy failed validation.
365 #[error("invalid execution policy: {reason}")]
366 InvalidExecutionPolicy {
367 /// Typed policy validation reason.
368 reason: ExecutionPolicyError,
369 },
370 /// Extension module transaction failed.
371 #[error("extension module registration failed")]
372 ExtensionModule {
373 /// Typed extension module source.
374 #[source]
375 source: ExtensionModuleError,
376 },
377}
378
379/// Reports a runtime execution bridge that violated its output contract.
380///
381/// # Examples
382///
383/// ```
384/// use tenferro_runtime::{EngineExecutionContractError, EngineId, StorageClass};
385///
386/// let error = EngineExecutionContractError::OutputResidencyMismatch {
387/// instruction_index: 3,
388/// output_slot: 7,
389/// engine_id: EngineId::new("tenferro.cpu")?,
390/// storage_class: StorageClass::new("tenferro.storage.host")?,
391/// backend_family: Some("foreign-backend"),
392/// allocation_domain: None,
393/// };
394/// assert!(error.to_string().contains("output slot 7"));
395/// # Ok::<(), Box<dyn std::error::Error>>(())
396/// ```
397#[derive(Debug, thiserror::Error)]
398#[non_exhaustive]
399pub enum EngineExecutionContractError {
400 /// An executor returned a tensor not owned by its scheduled engine/storage.
401 #[error(
402 "instruction {instruction_index} output slot {output_slot} is not resident in \
403 engine {engine_id:?} storage {storage_class:?} \
404 (backend family {backend_family:?}, allocation domain {allocation_domain:?})"
405 )]
406 OutputResidencyMismatch {
407 /// Execution-program instruction that produced the invalid tensor.
408 instruction_index: usize,
409 /// Execution slot containing the invalid tensor.
410 output_slot: usize,
411 /// Engine selected by the prepared schedule.
412 engine_id: EngineId,
413 /// Storage class selected by the prepared schedule.
414 storage_class: StorageClass,
415 /// Physical backend family reported by the tensor.
416 backend_family: Option<&'static str>,
417 /// Physical allocation domain reported by the tensor.
418 allocation_domain: Option<AllocationDomainId>,
419 },
420}
421
422/// Reports a runtime input that violates a prepared ingress contract.
423///
424/// # Examples
425///
426/// ```
427/// use tenferro_runtime::{EngineId, InputIngressContractError, StorageClass};
428/// use tenferro_tensor::Placement;
429///
430/// let error = InputIngressContractError::ResidencyMismatch {
431/// input_slot: 0,
432/// ingress_engine_id: EngineId::new("tenferro.cpu")?,
433/// ingress_storage_class: StorageClass::new("tenferro.storage.host")?,
434/// placement: Placement::default(),
435/// backend_family: Some("foreign-backend"),
436/// allocation_domain: None,
437/// };
438/// assert!(error.to_string().contains("input slot 0"));
439/// # Ok::<(), Box<dyn std::error::Error>>(())
440/// ```
441#[derive(Debug, thiserror::Error)]
442#[non_exhaustive]
443pub enum InputIngressContractError {
444 /// A runtime input's physical residency is incompatible with its prepared ingress.
445 #[error(
446 "input slot {input_slot} at placement {placement:?} with backend family \
447 {backend_family:?} and allocation domain {allocation_domain:?} is not accepted by \
448 ingress engine {ingress_engine_id:?} storage {ingress_storage_class:?}"
449 )]
450 ResidencyMismatch {
451 /// Execution slot containing the rejected runtime input.
452 input_slot: usize,
453 /// Engine selected as the prepared input ingress.
454 ingress_engine_id: EngineId,
455 /// Storage class selected as the prepared input ingress.
456 ingress_storage_class: StorageClass,
457 /// Logical placement reported by the input.
458 placement: Placement,
459 /// Physical backend family reported by the input.
460 backend_family: Option<&'static str>,
461 /// Physical allocation domain reported by the input.
462 allocation_domain: Option<AllocationDomainId>,
463 },
464}
465
466/// Reports failure to admit an asynchronous runtime submission.
467///
468/// # Examples
469///
470/// ```
471/// use std::io;
472/// use tenferro_runtime::SubmissionError;
473///
474/// let error = SubmissionError::WorkerSpawn {
475/// source: io::Error::other("worker unavailable"),
476/// };
477/// assert!(error.to_string().contains("spawn"));
478/// ```
479#[derive(Debug, thiserror::Error)]
480#[non_exhaustive]
481pub enum SubmissionError {
482 /// The runtime could not create the worker after synchronous preparation.
483 #[error("failed to spawn runtime submission worker")]
484 WorkerSpawn {
485 /// Operating-system thread creation failure.
486 #[source]
487 source: std::io::Error,
488 },
489}
490
491impl From<IdentityError> for RuntimeConfigError {
492 fn from(source: IdentityError) -> Self {
493 Self::MalformedIdentity {
494 kind: source.kind(),
495 }
496 }
497}
498
499/// Reports failure to publish a runtime reconfiguration.
500///
501/// # Examples
502///
503/// ```
504/// use tenferro_runtime::{RuntimeConfigError, RuntimeReconfigureError};
505///
506/// let error = RuntimeReconfigureError::Edit {
507/// source: RuntimeConfigError::IdentityExhausted,
508/// };
509/// assert!(std::error::Error::source(&error).is_some());
510/// ```
511#[derive(Debug, thiserror::Error)]
512#[non_exhaustive]
513pub enum RuntimeReconfigureError {
514 /// Runtime state could not be read or published.
515 #[error("runtime state failed")]
516 State {
517 /// Typed state source.
518 #[source]
519 source: super::RuntimeStateError,
520 },
521 /// The edit closure or publication validation rejected the candidate.
522 #[error("runtime reconfiguration edit failed")]
523 Edit {
524 /// Typed configuration source.
525 #[source]
526 source: RuntimeConfigError,
527 },
528 /// Another writer published over the snapshot this edit was based on.
529 #[error("runtime was concurrently reconfigured from {base:?} to {current:?}")]
530 ConcurrentReconfiguration {
531 /// Epoch captured before the edit closure ran.
532 base: RuntimeEpoch,
533 /// Epoch observed at publication time.
534 current: RuntimeEpoch,
535 },
536 /// Runtime epoch cannot advance without wrapping.
537 #[error("runtime epoch exhausted at {current:?}")]
538 EpochExhausted {
539 /// Current maximum epoch.
540 current: RuntimeEpoch,
541 },
542}
543
544/// Reports invalid extension module registration transactions.
545///
546/// # Examples
547///
548/// ```
549/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
550/// use tenferro_runtime::{EngineId, ExtensionModuleError, ExtensionModuleId};
551///
552/// let error = ExtensionModuleError::MissingPlanningConfig {
553/// module_id: ExtensionModuleId::new("tenferro.module.test")?,
554/// engine_id: EngineId::new("tenferro.engine.test")?,
555/// };
556/// assert!(error.to_string().contains("planning config"));
557/// # Ok(())
558/// # }
559/// ```
560#[derive(Debug, thiserror::Error)]
561#[non_exhaustive]
562pub enum ExtensionModuleError {
563 /// A distinct module is already installed at the same module ID.
564 #[error("extension module {module_id:?} is already installed")]
565 ConflictingModule {
566 /// Conflicting module ID.
567 module_id: ExtensionModuleId,
568 },
569 /// A distinct extension engine is already registered for the same
570 /// `(family, engine)` key.
571 #[error("extension module {module_id:?} has conflicting engine {family_id:?}/{engine_id:?}")]
572 ConflictingEngine {
573 /// Extension module ID.
574 module_id: ExtensionModuleId,
575 /// Extension family ID.
576 family_id: &'static str,
577 /// Runtime engine ID.
578 engine_id: EngineId,
579 },
580 /// A planning config was registered without exactly one matching engine.
581 #[error(
582 "extension module {module_id:?} registered planning config without engine {engine_id:?}"
583 )]
584 PlanningConfigWithoutEngine {
585 /// Extension module ID.
586 module_id: ExtensionModuleId,
587 /// Runtime engine ID.
588 engine_id: EngineId,
589 },
590 /// Planning config family did not match the registered engine family.
591 #[error(
592 "extension module {module_id:?} config for engine {engine_id:?} has family \
593 {actual:?}, expected {expected:?}"
594 )]
595 PlanningConfigFamilyMismatch {
596 /// Extension module ID.
597 module_id: ExtensionModuleId,
598 /// Runtime engine ID.
599 engine_id: EngineId,
600 /// Expected extension family ID.
601 expected: &'static str,
602 /// Actual extension family ID.
603 actual: &'static str,
604 },
605 /// An engine was registered without its required planning config.
606 #[error("extension module {module_id:?} engine {engine_id:?} has no planning config")]
607 MissingPlanningConfig {
608 /// Extension module ID.
609 module_id: ExtensionModuleId,
610 /// Runtime engine ID.
611 engine_id: EngineId,
612 },
613 /// A distinct planning config is already registered for the same engine.
614 #[error("extension module {module_id:?} has conflicting planning config for {engine_id:?}")]
615 ConflictingPlanningConfig {
616 /// Extension module ID.
617 module_id: ExtensionModuleId,
618 /// Runtime engine ID.
619 engine_id: EngineId,
620 },
621 /// A distinct cache owner is already registered for the same local owner ID.
622 #[error("extension module {module_id:?} has conflicting cache owner {owner:?}")]
623 ConflictingCacheOwner {
624 /// Extension module ID.
625 module_id: ExtensionModuleId,
626 /// Local cache owner ID.
627 owner: CacheOwnerId,
628 },
629}
630
631/// Reports failures while building value-free input signatures.
632///
633/// # Examples
634///
635/// ```
636/// use tenferro_runtime::{DType, InputSignatureEntry, LayoutClass};
637/// use tenferro_tensor::Placement;
638///
639/// let error = InputSignatureEntry::new(
640/// DType::F64,
641/// [2_usize].into_iter().collect(),
642/// Placement::default(),
643/// LayoutClass::new("tenferro.layout.strided").unwrap(),
644/// [1_isize, 2].into_iter().collect(),
645/// None,
646/// )
647/// .unwrap_err();
648/// assert!(matches!(error, tenferro_runtime::InputSignatureError::ShapeStrideRankMismatch { .. }));
649/// ```
650#[derive(Debug, thiserror::Error)]
651pub enum InputSignatureError {
652 /// Shape rank and stride rank disagree.
653 #[error("shape rank {rank} does not match stride count {stride_count}")]
654 ShapeStrideRankMismatch {
655 /// Number of shape axes.
656 rank: usize,
657 /// Number of stride axes.
658 stride_count: usize,
659 },
660 /// The alignment class is outside the finite `usize` alignment lattice.
661 #[error("alignment class {alignment_log2} is outside the usize alignment lattice")]
662 InvalidAlignmentClass {
663 /// Base-2 logarithm of the requested alignment.
664 alignment_log2: u8,
665 },
666 /// Tensor metadata could not be read for an input.
667 #[error("input {input} metadata is unavailable")]
668 TensorMetadata {
669 /// Input position in the prepare request.
670 input: usize,
671 /// Original typed tensor error.
672 source: TensorError,
673 },
674}
675
676/// Explains why rank specialization is required by a requested field.
677///
678/// # Examples
679///
680/// ```
681/// use tenferro_runtime::RankRequirement;
682///
683/// assert_eq!(RankRequirement::ExactStrides.to_string(), "exact strides");
684/// ```
685#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
686pub enum RankRequirement {
687 /// A concrete axis dimension was requested.
688 ConcreteAxis {
689 /// Axis that requires rank specialization.
690 axis: u32,
691 },
692 /// Exact strides were requested.
693 ExactStrides,
694}
695
696impl std::fmt::Display for RankRequirement {
697 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
698 match self {
699 Self::ConcreteAxis { axis } => write!(formatter, "concrete axis {axis}"),
700 Self::ExactStrides => formatter.write_str("exact strides"),
701 }
702 }
703}
704
705/// Reports invalid input-specialization requirements.
706///
707/// # Examples
708///
709/// ```
710/// use tenferro_runtime::{
711/// InputSpecializationRequirements, InputSpecializationRequirementsError, RankRequirement,
712/// };
713///
714/// let mut builder = InputSpecializationRequirements::builder();
715/// builder.rank(false).concrete_dimensions(vec![2]);
716/// assert_eq!(
717/// builder.build().unwrap_err(),
718/// InputSpecializationRequirementsError::RankRequired {
719/// reason: RankRequirement::ConcreteAxis { axis: 2 },
720/// }
721/// );
722/// ```
723#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
724pub enum InputSpecializationRequirementsError {
725 /// The same concrete axis appears more than once.
726 #[error("axis {axis} is duplicated at positions {first_index} and {duplicate_index}")]
727 DuplicateAxis {
728 /// Duplicated concrete axis.
729 axis: u32,
730 /// First position carrying the axis.
731 first_index: usize,
732 /// Duplicate position that failed validation.
733 duplicate_index: usize,
734 },
735 /// A requested specialization field requires rank specialization.
736 #[error("{reason} requires rank specialization")]
737 RankRequired {
738 /// Requirement that needs rank specialization.
739 reason: RankRequirement,
740 },
741 /// The alignment class is outside the finite `usize` alignment lattice.
742 #[error("alignment class {alignment_log2} is outside the usize alignment lattice")]
743 InvalidAlignmentClass {
744 /// Base-2 logarithm of the requested alignment.
745 alignment_log2: u8,
746 },
747}
748
749/// Reports failures while projecting signatures through specialization requirements.
750///
751/// # Examples
752///
753/// ```
754/// use tenferro_runtime::{InputSignature, SpecializationRequirements};
755///
756/// let error = SpecializationRequirements::polymorphic(1)
757/// .project(&InputSignature::new(Vec::new()))
758/// .unwrap_err();
759/// assert!(matches!(error, tenferro_runtime::PrepareError::Specialization { .. }));
760/// ```
761#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
762pub enum SpecializationError {
763 /// The signature does not have the required input arity.
764 #[error("expected {expected} inputs but got {actual}")]
765 WrongInputCount {
766 /// Required input count.
767 expected: usize,
768 /// Actual input count.
769 actual: usize,
770 },
771 /// A concrete-axis request names an axis outside the actual rank.
772 #[error("input {input} axis {axis} is outside rank {rank}")]
773 AxisOutOfRange {
774 /// Input position in the signature.
775 input: usize,
776 /// Requested axis.
777 axis: u32,
778 /// Actual rank.
779 rank: usize,
780 },
781 /// A required host-pointer alignment class is unavailable.
782 #[error("input {input} has unknown alignment; required class {required_alignment_log2}")]
783 AlignmentUnavailable {
784 /// Input position in the signature.
785 input: usize,
786 /// Required base-2 logarithm alignment class.
787 required_alignment_log2: u8,
788 },
789 /// A specialization projection could not encode every requested axis in
790 /// the finite public axis type.
791 #[error("input {input} rank {rank} is too large to project into u32 axes")]
792 ProjectionOverflow {
793 /// Input position in the signature.
794 input: usize,
795 /// Rank that could not be encoded.
796 rank: usize,
797 },
798}
799
800/// A typed execution-context mismatch from erased runtime dispatch.
801///
802/// # Examples
803///
804/// ```
805/// use tenferro_runtime::{ExecutionContextIdentity, ExecutionContextMismatch};
806///
807/// let error = ExecutionContextMismatch {
808/// expected: ExecutionContextIdentity::of::<u64>(),
809/// actual: ExecutionContextIdentity::of::<u32>(),
810/// };
811/// assert_eq!(error.expected, ExecutionContextIdentity::of::<u64>());
812/// ```
813#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
814#[error("execution context mismatch: expected {expected:?}, actual {actual:?}")]
815pub struct ExecutionContextMismatch {
816 /// Expected execution-context type identity.
817 pub expected: super::ExecutionContextIdentity,
818 /// Actual stored execution-context type identity.
819 pub actual: super::ExecutionContextIdentity,
820}
821
822/// Reports provider violations of runtime preparation contracts.
823///
824/// # Examples
825///
826/// ```
827/// use tenferro_runtime::{CoreCapabilityKind, ProviderContractError};
828///
829/// let error = ProviderContractError::WrongOperationFamily {
830/// expected: CoreCapabilityKind::Elementwise,
831/// operation: "dot_general",
832/// };
833/// assert!(error.to_string().contains("dot_general"));
834/// ```
835#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
836pub enum ProviderContractError {
837 /// A provider accepted a request from the wrong core operation family.
838 #[error("operation {operation:?} is not in {expected:?}")]
839 WrongOperationFamily {
840 /// Expected core capability family.
841 expected: CoreCapabilityKind,
842 /// Operation diagnostic name.
843 operation: &'static str,
844 },
845 /// A prepared operation returned a different binding than requested.
846 #[error("prepared binding mismatch")]
847 BindingMismatch {
848 /// Runtime-created binding requested from the provider.
849 expected: Box<PreparedOperationBinding>,
850 /// Binding returned by the provider.
851 actual: Box<PreparedOperationBinding>,
852 },
853 /// A prepared operation returned a different specialization projection.
854 #[error("prepared specialization projection mismatch")]
855 ProjectionMismatch {
856 /// Runtime-created projection requested from the provider.
857 expected: Box<SpecializationProjection>,
858 /// Projection returned by the provider.
859 actual: Box<SpecializationProjection>,
860 },
861 /// A provider returned an invalid specialization.
862 #[error("invalid provider specialization: {source}")]
863 InvalidSpecialization {
864 /// Original specialization failure.
865 #[source]
866 source: SpecializationError,
867 },
868 /// A provider requested specialization that did not strictly widen the
869 /// current requirements.
870 #[error("specialization did not strictly widen the current requirements")]
871 NonWideningSpecialization {
872 /// Previous specialization requirements.
873 previous: Box<super::SpecializationRequirements>,
874 /// Provider-requested specialization requirements.
875 next: Box<super::SpecializationRequirements>,
876 },
877 /// A provider exceeded the finite specialization retry bound.
878 #[error("specialization retry limit exceeded after {attempts} attempts; limit was {limit}")]
879 SpecializationRetryLimitExceeded {
880 /// Number of redirect attempts observed.
881 attempts: usize,
882 /// Computed finite retry bound.
883 limit: usize,
884 },
885}
886
887/// Reports failures while preparing runtime execution artifacts.
888///
889/// # Examples
890///
891/// ```
892/// use tenferro_runtime::{InputSignature, PrepareError, SpecializationRequirements};
893///
894/// let error = SpecializationRequirements::polymorphic(1)
895/// .project(&InputSignature::new(Vec::new()))
896/// .unwrap_err();
897/// assert!(matches!(error, PrepareError::Specialization { .. }));
898/// ```
899#[derive(Debug, thiserror::Error)]
900pub enum PrepareError {
901 /// Prepared artifact belongs to a different runtime.
902 #[error("runtime mismatch: expected {expected:?}, actual {actual:?}")]
903 RuntimeMismatch {
904 /// Runtime expected by the prepared artifact.
905 expected: RuntimeId,
906 /// Runtime currently executing.
907 actual: RuntimeId,
908 },
909 /// Prepared artifact was created for a stale runtime epoch.
910 #[error("stale runtime epoch: prepared {prepared:?}, current {current:?}")]
911 StaleEpoch {
912 /// Epoch captured by the prepared artifact.
913 prepared: RuntimeEpoch,
914 /// Runtime's current epoch.
915 current: RuntimeEpoch,
916 },
917 /// Input signature construction failed.
918 #[error("input signature failed")]
919 InputSignature {
920 /// Typed source error.
921 source: InputSignatureError,
922 },
923 /// Semantic shape guards failed.
924 #[error("shape guard failed")]
925 ShapeGuard {
926 /// Typed source error.
927 #[source]
928 source: ShapeGuardError,
929 },
930 /// Signature projection through specialization requirements failed.
931 #[error("specialization failed")]
932 Specialization {
933 /// Typed source error.
934 source: SpecializationError,
935 },
936 /// No eligible engine matched the placement and capability constraints.
937 #[error("no eligible engine for {constraint:?}")]
938 NoEligibleEngine {
939 /// Placement constraint that could not be satisfied.
940 constraint: ProgramPlacementConstraint,
941 },
942 /// A preparation-only engine advertised the required capability but cannot
943 /// produce an executable prepared schedule.
944 #[error(
945 "engine {engine_id:?} has a matching preparation capability but no executable binding"
946 )]
947 NoExecutableEngine {
948 /// Engine whose preparation capability cannot be used for execution.
949 engine_id: EngineId,
950 },
951 /// No eligible engine declared an ingress compatible with an input placement.
952 #[error("no eligible input ingress for input {input_index} at placement {placement:?}")]
953 NoInputIngress {
954 /// Input position in the compiled-program signature.
955 input_index: usize,
956 /// Placement rejected by every eligible engine ingress.
957 placement: tenferro_tensor::Placement,
958 },
959 /// Placement resolution exceeded its bounded combination-search budget.
960 #[error(
961 "placement resolution exhausted its search budget after {attempts} attempts \
962 (limit {limit})"
963 )]
964 DispatchSearchBudgetExceeded {
965 /// Number of dispatch combinations attempted.
966 attempts: usize,
967 /// Configured hard limit for this search.
968 limit: usize,
969 },
970 /// No available copy of a value has a registered direct transfer provider
971 /// to the storage required by its consumer.
972 #[error(
973 "instruction {instruction_index} has no direct transfer provider for value slot \
974 {value_slot} from {available_source_endpoints:?} to {destination_endpoint:?}"
975 )]
976 MissingTransferProvider {
977 /// Staged instruction whose input cannot be reached.
978 instruction_index: usize,
979 /// Value slot required by the instruction.
980 value_slot: usize,
981 /// Destination endpoint required by the instruction.
982 destination_endpoint: TransferEndpoint,
983 /// Endpoints retaining an available copy.
984 available_source_endpoints: Vec<TransferEndpoint>,
985 },
986 /// A resolved placement references an engine absent from its preparation snapshot.
987 #[error("resolved engine {engine_id:?} is unavailable in the preparation snapshot")]
988 ResolvedEngineUnavailable {
989 /// Engine referenced by the resolved placement.
990 engine_id: EngineId,
991 },
992 /// No provider supports this operation under the requested constraints.
993 #[error("operation is unsupported: {reason}")]
994 Unsupported {
995 /// Deterministic unsupported reason.
996 reason: UnsupportedReason,
997 },
998 /// Deterministic execution was requested from an engine that cannot provide it.
999 #[error("determinism unsupported by engine {engine_id:?}")]
1000 DeterminismUnsupported {
1001 /// Engine that cannot satisfy deterministic preparation.
1002 engine_id: EngineId,
1003 },
1004 /// Provider returned a value that violates the runtime preparation contract.
1005 #[error("provider contract violation: {source}")]
1006 ProviderContract {
1007 /// Typed provider-contract source.
1008 #[source]
1009 source: ProviderContractError,
1010 },
1011 /// Preparation recursion reached an unsupported cycle.
1012 #[error("preparation cycle at {key:?}")]
1013 PreparationCycle {
1014 /// Bounded summary of the recursive key.
1015 key: PreparationKeySummary,
1016 },
1017 /// Preparation recursively requested a different key.
1018 #[error("nested preparation unsupported: parent {parent:?}, requested {requested:?}")]
1019 NestedPreparationUnsupported {
1020 /// Parent key currently preparing on this thread.
1021 parent: PreparationKeySummary,
1022 /// Nested key requested by the producer.
1023 requested: PreparationKeySummary,
1024 },
1025 /// The preparation cache is full of active/queued distinct keys.
1026 #[error(
1027 "preparation cache is at capacity: {in_flight} in flight and \
1028 {queued_distinct_keys} queued distinct keys"
1029 )]
1030 CacheInFlightCapacityExceeded {
1031 /// Active distinct preparations.
1032 in_flight: usize,
1033 /// Queued distinct keys.
1034 queued_distinct_keys: usize,
1035 },
1036 /// Runtime cache state could not be accessed.
1037 #[error("preparation cache state unavailable: {source}")]
1038 CacheState {
1039 /// Runtime state source.
1040 #[source]
1041 source: super::RuntimeStateError,
1042 },
1043 /// Engine or internal preparation source failed.
1044 #[error("engine preparation failed: {source}")]
1045 Engine {
1046 /// Shared typed source.
1047 #[source]
1048 source: Arc<dyn Error + Send + Sync>,
1049 },
1050}