tenferro_runtime/error.rs
1//! Error types for the tenferro runtime crate.
2//!
3//! # Examples
4//!
5//! ```rust
6//! use tenferro_runtime::error::{Error, ErrorPhase};
7//!
8//! let err = Error::invalid_argument(
9//! "einsum",
10//! ErrorPhase::GraphBuild,
11//! "subscripts",
12//! "bad label",
13//! );
14//! assert!(err.to_string().contains("bad label"));
15//! ```
16
17use std::error::Error as StdError;
18use std::sync::atomic::{AtomicUsize, Ordering};
19
20use tenferro_ops::{dim_expr::DimExprEvalError, ShapeRelation, SymDimConversionError};
21use tenferro_tensor::{DType, ErrorKind, ValidationError, ValidationKind};
22
23static NEXT_CONTEXT_ID: AtomicUsize = AtomicUsize::new(1);
24
25/// Boxed source used when a runtime registry or compiler subsystem crosses
26/// the runtime error boundary with a concrete error owned by another crate.
27pub type BoxError = Box<dyn StdError + Send + Sync + 'static>;
28
29/// Phase at which a runtime failure was discovered.
30///
31/// The phase is independent from [`ErrorKind`]: the same validation fact can
32/// be discovered while building a graph, compiling it for concrete inputs,
33/// or executing a compiled program.
34///
35/// # Examples
36///
37/// ```rust
38/// use tenferro_runtime::ErrorPhase;
39///
40/// assert_ne!(ErrorPhase::GraphBuild, ErrorPhase::Execution);
41/// ```
42#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
43#[non_exhaustive]
44pub enum ErrorPhase {
45 /// A caller-controlled graph construction check failed.
46 GraphBuild,
47 /// Shape inference or lowering discovered the failure.
48 Compile,
49 /// Input binding or backend execution discovered the failure.
50 Execution,
51}
52
53/// Typed reason that a symbolic shape constraint could not be evaluated.
54///
55/// # Examples
56///
57/// ```rust
58/// use tenferro_runtime::ShapeConstraintEvalError;
59///
60/// let cause = ShapeConstraintEvalError::MissingInput {
61/// input_idx: 2,
62/// input_count: 1,
63/// };
64/// assert_eq!(
65/// cause.to_string(),
66/// "shape expression references input 2, but only 1 inputs were provided"
67/// );
68/// ```
69#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
70pub enum ShapeConstraintEvalError {
71 /// An expression referenced an input shape that was not supplied.
72 #[error(
73 "shape expression references input {input_idx}, but only {input_count} inputs were provided"
74 )]
75 MissingInput {
76 /// Referenced input index.
77 input_idx: usize,
78 /// Number of supplied input shapes.
79 input_count: usize,
80 },
81 /// An expression referenced an axis outside the selected input's rank.
82 #[error("shape expression references input {input_idx} axis {axis}, but its rank is {rank}")]
83 AxisOutOfBounds {
84 /// Referenced input index.
85 input_idx: usize,
86 /// Referenced axis.
87 axis: usize,
88 /// Rank of the selected input.
89 rank: usize,
90 },
91 /// Checked dimension arithmetic overflowed `usize`.
92 #[error("shape expression arithmetic overflowed")]
93 Overflow,
94 /// Checked dimension subtraction underflowed `usize`.
95 #[error("shape expression subtraction underflowed")]
96 Underflow,
97 /// A floor-division divisor evaluated to zero.
98 #[error("shape expression divided by zero")]
99 DivisionByZero,
100}
101
102impl From<DimExprEvalError> for ShapeConstraintEvalError {
103 fn from(error: DimExprEvalError) -> Self {
104 match error {
105 DimExprEvalError::InputOutOfBounds {
106 input_idx,
107 input_count,
108 } => Self::MissingInput {
109 input_idx,
110 input_count,
111 },
112 DimExprEvalError::AxisOutOfBounds {
113 input_idx,
114 axis,
115 rank,
116 } => Self::AxisOutOfBounds {
117 input_idx,
118 axis,
119 rank,
120 },
121 DimExprEvalError::AddOverflow { .. } | DimExprEvalError::MulOverflow { .. } => {
122 Self::Overflow
123 }
124 DimExprEvalError::SubUnderflow { .. } => Self::Underflow,
125 DimExprEvalError::FloorDivByZero { .. } => Self::DivisionByZero,
126 }
127 }
128}
129
130/// Errors produced by einsum, eval, and other tenferro operations.
131///
132/// # Examples
133///
134/// ```rust
135/// use tenferro_runtime::error::{Error, ErrorPhase};
136///
137/// let err = Error::invalid_argument(
138/// "einsum",
139/// ErrorPhase::GraphBuild,
140/// "subscripts",
141/// "rank mismatch",
142/// );
143/// ```
144#[derive(Debug, thiserror::Error)]
145pub enum Error {
146 /// A shared tensor validation fact, annotated with the runtime phase.
147 #[error("{op} ({phase:?}): {source}")]
148 Validation {
149 /// Public operation name.
150 op: &'static str,
151 /// Phase that discovered the validation fact.
152 phase: ErrorPhase,
153 /// Machine-readable validation payload.
154 #[source]
155 source: ValidationError,
156 },
157
158 /// A required input tensor is missing from the inputs map.
159 #[error("missing input: {0}")]
160 MissingInput(String),
161
162 /// Reverse-mode gradient requires a scalar output.
163 #[error("grad requires a scalar output, got shape {shape:?}")]
164 NonScalarGrad { shape: Vec<usize> },
165
166 /// The operation is known not to support the requested input or
167 /// configuration at the phase where it was requested.
168 #[error("{op} ({phase:?}) is unsupported: {message}")]
169 Unsupported {
170 /// Operation that does not provide the requested behavior.
171 op: &'static str,
172 /// Phase that established the unsupported combination.
173 phase: ErrorPhase,
174 /// Human-readable unsupported-operation detail.
175 message: String,
176 },
177
178 /// Runtime tensor execution failed in the backend layer.
179 #[error(transparent)]
180 TensorRuntime(#[from] tenferro_tensor::Error),
181
182 /// A typed extension-domain error crossed a runtime registry boundary.
183 #[error("extension {family} ({phase:?}) failed for {op}: {source}")]
184 Extension {
185 /// Operation that discovered the extension failure.
186 op: &'static str,
187 /// Phase that discovered the extension failure.
188 phase: ErrorPhase,
189 /// Stable extension family identifier.
190 family: &'static str,
191 /// Coarse classification supplied by the extension owner.
192 kind: ErrorKind,
193 /// Original extension-domain source.
194 #[source]
195 source: BoxError,
196 },
197
198 /// Executor, cache, registry, or device state is unavailable or invalid.
199 #[error("{op} ({phase:?}): runtime state failure: {message}")]
200 RuntimeState {
201 /// Operation whose state was unavailable.
202 op: &'static str,
203 /// Phase that discovered the invalid state.
204 phase: ErrorPhase,
205 /// Human-readable state detail.
206 message: String,
207 },
208
209 /// A runtime-state failure retaining a typed source.
210 #[error("{op} ({phase:?}): runtime state failure: {source}")]
211 RuntimeStateSource {
212 /// Operation whose state was unavailable.
213 op: &'static str,
214 /// Phase that discovered the invalid state.
215 phase: ErrorPhase,
216 /// Typed state source.
217 #[source]
218 source: BoxError,
219 },
220
221 /// A primary error with a second typed error retained as suppressed
222 /// metadata.
223 ///
224 /// The standard error source chain follows `primary`. The suppressed
225 /// error is intentionally exposed through [`Error::suppressed`] because
226 /// [`StdError::source`](std::error::Error::source) can represent only one
227 /// source without losing the primary error's semantics.
228 #[error("primary error: {primary}; suppressed error: {suppressed}")]
229 WithSuppressed {
230 /// The operation's primary failure and the standard error-chain source.
231 #[source]
232 primary: Box<Error>,
233 /// A typed secondary failure retained for diagnostics and recovery.
234 suppressed: Box<Error>,
235 },
236
237 /// A runtime event-domain provenance or admission contract failed.
238 #[error("event-domain operation failed: {source}")]
239 EventDomain {
240 /// Structured event-domain failure with expected/actual provenance.
241 #[from]
242 #[source]
243 source: crate::runtime::EventDomainError,
244 },
245
246 /// A `TracedTensor` supplied as a compiled-graph input binding is not a
247 /// placeholder (has attached data).
248 #[error(
249 "binding #{binding_index} is not a placeholder; \
250 only tensors built via input_concrete_shape / input_symbolic_shape \
251 can be bound"
252 )]
253 UnexpectedBinding { binding_index: usize },
254
255 /// A placeholder appearing in the graph has no binding supplied.
256 #[error("placeholder {input_key} has no runtime input binding")]
257 UnboundPlaceholder { input_key: String },
258
259 /// The number of ordered tensors supplied to a compiled graph is invalid.
260 #[error("compiled graph expects {expected} ordered inputs, got {actual}")]
261 GraphInputCountMismatch { expected: usize, actual: usize },
262
263 /// The same placeholder was bound more than once in the `bindings` slice.
264 #[error("placeholder {input_key} was bound more than once")]
265 DuplicateBinding { input_key: String },
266
267 /// A binding tensor's dtype does not match the placeholder's dtype.
268 #[error("binding dtype mismatch for placeholder: expected {expected:?}, got {actual:?}")]
269 PlaceholderDtypeMismatch { expected: DType, actual: DType },
270
271 /// A binding tensor's shape does not match an `input_concrete_shape`
272 /// placeholder's fixed shape.
273 #[error(
274 "binding shape mismatch for concrete-shape placeholder: \
275 expected {expected:?}, got {actual:?}"
276 )]
277 PlaceholderShapeMismatch {
278 expected: Vec<usize>,
279 actual: Vec<usize>,
280 },
281
282 /// A binding tensor dimension exceeds a semantic input's declared bound.
283 #[error("binding dimension {axis} exceeds semantic input upper bound {bound}: got {actual}")]
284 PlaceholderShapeBoundExceeded {
285 /// Axis whose runtime extent exceeded the bound.
286 axis: usize,
287 /// Evaluated upper bound.
288 bound: usize,
289 /// Runtime extent.
290 actual: usize,
291 },
292
293 /// A binding tensor's rank does not match an `input_symbolic_shape`
294 /// placeholder's declared rank.
295 #[error(
296 "binding rank mismatch for symbolic-shape placeholder: \
297 expected rank {expected}, got rank {actual}"
298 )]
299 PlaceholderRankMismatch { expected: usize, actual: usize },
300
301 /// Operation attempted to mix tensors from different eager contexts.
302 #[error(
303 "tensors belong to different eager AD contexts ({lhs} vs {rhs}); \
304 detach into the target context before combining them"
305 )]
306 ContextMismatch { lhs: ContextId, rhs: ContextId },
307
308 /// An AD transform requires a primitive or extension rule that is not
309 /// registered for the requested operation.
310 #[error("unsupported {transform} AD rule for {op}")]
311 UnsupportedAdRule {
312 /// AD transform that requested the rule, such as `grad` or `backward`.
313 transform: &'static str,
314 /// Operation or extension family identifier that has no applicable rule.
315 op: String,
316 },
317
318 /// A typed AD rule source that crossed an external message-only callback.
319 #[error("{transform} AD rule failed: {source}")]
320 AdRuleSource {
321 /// AD transform that requested the rule.
322 transform: &'static str,
323 /// Original typed source from the AD rule context.
324 #[source]
325 source: BoxError,
326 },
327
328 /// A symbolic extension shape equality evaluated to unequal dimensions.
329 #[error(
330 "extension family {family:?} shape constraint at instruction {instruction_index:?} failed: {lhs_expr} ({lhs_value}) {relation:?} {rhs_expr} ({rhs_value})"
331 )]
332 ShapeConstraintViolation {
333 /// Stable extension family identifier.
334 family: &'static str,
335 /// Stable compiled instruction provenance, when assigned.
336 instruction_index: Option<usize>,
337 /// Shape relation that failed.
338 relation: ShapeRelation,
339 /// Normalized left-hand expression.
340 lhs_expr: String,
341 /// Normalized right-hand expression.
342 rhs_expr: String,
343 /// Concrete left-hand value.
344 lhs_value: usize,
345 /// Concrete right-hand value.
346 rhs_value: usize,
347 },
348
349 /// A symbolic extension shape expression could not be evaluated safely.
350 #[error(
351 "extension family {family:?} shape constraint at instruction {instruction_index:?} could not evaluate {expression} for {relation:?}: {cause}"
352 )]
353 ShapeConstraintEvaluation {
354 /// Stable extension family identifier.
355 family: &'static str,
356 /// Stable compiled instruction provenance, when assigned.
357 instruction_index: Option<usize>,
358 /// Shape relation whose expression failed.
359 relation: ShapeRelation,
360 /// Normalized expression that failed.
361 expression: String,
362 /// Typed evaluation failure.
363 #[source]
364 cause: ShapeConstraintEvalError,
365 },
366
367 /// A symbolic dimension could not be converted into the graph's local
368 /// dimension-expression vocabulary.
369 #[error("{op} ({phase:?}): symbolic shape conversion failed: {source}")]
370 SymbolicShapeConversion {
371 /// Operation that requested the symbolic shape conversion.
372 op: &'static str,
373 /// Phase that discovered the invalid symbolic reference.
374 phase: ErrorPhase,
375 /// Typed symbolic-dimension conversion failure.
376 #[source]
377 source: SymDimConversionError,
378 },
379
380 /// A runtime dimension expression could not be evaluated for concrete
381 /// input shapes.
382 #[error("runtime shape expression {expression} could not evaluate: {cause}")]
383 ShapeExpressionEvaluation {
384 /// Expression that failed during execution.
385 expression: String,
386 /// Typed evaluation failure.
387 #[source]
388 cause: ShapeConstraintEvalError,
389 },
390
391 /// An unexpected internal error.
392 #[error("internal error: {0}")]
393 Internal(String),
394}
395
396impl Error {
397 /// Wrap a shared validation payload with its operation and discovery
398 /// phase.
399 ///
400 /// # Errors
401 ///
402 /// This constructor does not fail; callers receive the returned
403 /// [`Error`] value and can inspect its [`Error::kind`] and
404 /// [`Error::phase`].
405 ///
406 /// # Examples
407 ///
408 /// ```rust
409 /// use tenferro_runtime::{Error, ErrorPhase};
410 /// use tenferro_tensor::{ErrorKind, ShapeMismatch, ValidationKind};
411 ///
412 /// let error = Error::validation(
413 /// "reshape",
414 /// ErrorPhase::GraphBuild,
415 /// ShapeMismatch::ReshapeElementCount { from: 2, to: 3 }.into(),
416 /// );
417 /// assert_eq!(
418 /// error.kind(),
419 /// ErrorKind::Validation(ValidationKind::ShapeMismatch)
420 /// );
421 /// assert_eq!(error.phase(), Some(ErrorPhase::GraphBuild));
422 /// ```
423 pub fn validation(op: &'static str, phase: ErrorPhase, source: ValidationError) -> Self {
424 Self::Validation { op, phase, source }
425 }
426
427 /// Construct a validation error for a caller-controlled argument whose
428 /// failure does not have a more specific shared payload.
429 ///
430 /// # Examples
431 ///
432 /// ```rust
433 /// use tenferro_runtime::{Error, ErrorPhase};
434 ///
435 /// let error = Error::invalid_argument(
436 /// "broadcast_in_dim",
437 /// ErrorPhase::GraphBuild,
438 /// "dims",
439 /// "dimension mapping has the wrong length",
440 /// );
441 /// assert!(matches!(error, Error::Validation { .. }));
442 /// ```
443 pub fn invalid_argument(
444 op: &'static str,
445 phase: ErrorPhase,
446 argument: &'static str,
447 message: impl Into<String>,
448 ) -> Self {
449 Self::validation(
450 op,
451 phase,
452 ValidationError::InvalidArgument {
453 argument,
454 message: message.into(),
455 },
456 )
457 }
458
459 /// Construct a dtype-mismatch validation error using the runtime dtype
460 /// vocabulary.
461 ///
462 /// # Examples
463 ///
464 /// ```rust
465 /// use tenferro_runtime::{DType, Error, ErrorPhase};
466 /// use tenferro_tensor::{ErrorKind, ValidationKind};
467 ///
468 /// let error = Error::dtype_mismatch(
469 /// "add",
470 /// ErrorPhase::GraphBuild,
471 /// DType::F32,
472 /// DType::F64,
473 /// );
474 /// assert_eq!(error.kind(), ErrorKind::Validation(ValidationKind::DTypeMismatch));
475 /// ```
476 pub fn dtype_mismatch(
477 op: &'static str,
478 phase: ErrorPhase,
479 expected: DType,
480 actual: DType,
481 ) -> Self {
482 Self::validation(
483 op,
484 phase,
485 ValidationError::DTypeMismatch {
486 expected: core_dtype(expected),
487 actual: core_dtype(actual),
488 },
489 )
490 }
491
492 /// Preserve a typed extension-domain source at the runtime boundary.
493 ///
494 /// # Examples
495 ///
496 /// ```rust
497 /// use std::error::Error as _;
498 /// use tenferro_runtime::{Error, ErrorPhase};
499 /// use tenferro_tensor::ErrorKind;
500 ///
501 /// let source = std::io::Error::new(std::io::ErrorKind::Other, "extension failed");
502 /// let error = Error::extension(
503 /// "einsum",
504 /// ErrorPhase::GraphBuild,
505 /// "example.extension.v1",
506 /// ErrorKind::RuntimeState,
507 /// source,
508 /// );
509 /// assert_eq!(error.kind(), ErrorKind::RuntimeState);
510 /// assert!(error.source().is_some());
511 /// ```
512 pub fn extension<E>(
513 op: &'static str,
514 phase: ErrorPhase,
515 family: &'static str,
516 kind: ErrorKind,
517 source: E,
518 ) -> Self
519 where
520 E: StdError + Send + Sync + 'static,
521 {
522 Self::Extension {
523 op,
524 phase,
525 family,
526 kind,
527 source: Box::new(source),
528 }
529 }
530
531 /// Construct a runtime-state failure for an unavailable or invalid
532 /// executor, cache, registry, or device state.
533 ///
534 /// # Examples
535 ///
536 /// ```rust
537 /// use tenferro_runtime::{Error, ErrorPhase};
538 /// use tenferro_tensor::ErrorKind;
539 ///
540 /// let error = Error::runtime_state(
541 /// "executor",
542 /// ErrorPhase::Execution,
543 /// "the executor is not initialized",
544 /// );
545 /// assert_eq!(error.kind(), ErrorKind::RuntimeState);
546 /// ```
547 pub fn runtime_state(op: &'static str, phase: ErrorPhase, message: impl Into<String>) -> Self {
548 Self::RuntimeState {
549 op,
550 phase,
551 message: message.into(),
552 }
553 }
554
555 /// Preserve a typed source for an unavailable or invalid runtime state.
556 ///
557 /// # Examples
558 ///
559 /// ```rust
560 /// use std::error::Error as _;
561 /// use tenferro_runtime::{Error, ErrorPhase};
562 /// use tenferro_tensor::ErrorKind;
563 ///
564 /// let error = Error::runtime_state_source(
565 /// "metadata",
566 /// ErrorPhase::Compile,
567 /// std::io::Error::other("registry lock poisoned"),
568 /// );
569 /// assert_eq!(error.kind(), ErrorKind::RuntimeState);
570 /// assert!(error.source().is_some());
571 /// ```
572 pub fn runtime_state_source<E>(op: &'static str, phase: ErrorPhase, source: E) -> Self
573 where
574 E: StdError + Send + Sync + 'static,
575 {
576 Self::RuntimeStateSource {
577 op,
578 phase,
579 source: Box::new(source),
580 }
581 }
582
583 /// Retain a typed secondary error while preserving the primary error's
584 /// classification and standard source chain.
585 ///
586 /// # Examples
587 ///
588 /// ```rust
589 /// use tenferro_runtime::{Error, ErrorPhase};
590 ///
591 /// let error = Error::with_suppressed(
592 /// Error::unsupported("backend", ErrorPhase::Execution, "primary"),
593 /// Error::runtime_state("runtime", ErrorPhase::Execution, "suppressed"),
594 /// );
595 /// assert!(error.primary().is_some());
596 /// assert!(error.suppressed().is_some());
597 /// ```
598 pub fn with_suppressed(primary: Self, suppressed: Self) -> Self {
599 Self::WithSuppressed {
600 primary: Box::new(primary),
601 suppressed: Box::new(suppressed),
602 }
603 }
604
605 /// Return the primary error when this value is a suppressed-error
606 /// aggregate.
607 ///
608 /// # Examples
609 ///
610 /// ```rust
611 /// use tenferro_runtime::{Error, ErrorPhase};
612 ///
613 /// let error = Error::with_suppressed(
614 /// Error::unsupported("backend", ErrorPhase::Execution, "primary"),
615 /// Error::runtime_state("runtime", ErrorPhase::Execution, "suppressed"),
616 /// );
617 /// assert_eq!(error.primary().unwrap().phase(), Some(ErrorPhase::Execution));
618 /// ```
619 pub fn primary(&self) -> Option<&Self> {
620 match self {
621 Self::WithSuppressed { primary, .. } => Some(primary),
622 _ => None,
623 }
624 }
625
626 /// Return the typed suppressed error when this value is an aggregate.
627 ///
628 /// # Examples
629 ///
630 /// ```rust
631 /// use tenferro_runtime::{Error, ErrorPhase};
632 ///
633 /// let error = Error::with_suppressed(
634 /// Error::unsupported("backend", ErrorPhase::Execution, "primary"),
635 /// Error::runtime_state("runtime", ErrorPhase::Execution, "suppressed"),
636 /// );
637 /// assert_eq!(error.suppressed().unwrap().phase(), Some(ErrorPhase::Execution));
638 /// ```
639 pub fn suppressed(&self) -> Option<&Self> {
640 match self {
641 Self::WithSuppressed { suppressed, .. } => Some(suppressed),
642 _ => None,
643 }
644 }
645
646 /// Preserve a typed source returned by an AD rule through a callback
647 /// protocol that can carry only a rendered message.
648 ///
649 /// # Examples
650 ///
651 /// ```rust
652 /// use std::error::Error as _;
653 /// use tenferro_runtime::Error;
654 ///
655 /// let error = Error::ad_rule_source(
656 /// "jvp",
657 /// std::io::Error::other("shape metadata missing"),
658 /// );
659 /// assert!(error.source().is_some());
660 /// ```
661 pub fn ad_rule_source<E>(transform: &'static str, source: E) -> Self
662 where
663 E: StdError + Send + Sync + 'static,
664 {
665 Self::AdRuleSource {
666 transform,
667 source: Box::new(source),
668 }
669 }
670
671 /// Construct an operation-level unsupported error with an explicit
672 /// discovery phase.
673 ///
674 /// # Examples
675 ///
676 /// ```rust
677 /// use tenferro_runtime::{Error, ErrorPhase};
678 /// use tenferro_tensor::ErrorKind;
679 ///
680 /// let error = Error::unsupported(
681 /// "compare",
682 /// ErrorPhase::Compile,
683 /// "complex values have no total order",
684 /// );
685 /// assert_eq!(error.kind(), ErrorKind::Unsupported);
686 /// assert_eq!(error.phase(), Some(ErrorPhase::Compile));
687 /// ```
688 pub fn unsupported(op: &'static str, phase: ErrorPhase, message: impl Into<String>) -> Self {
689 Self::Unsupported {
690 op,
691 phase,
692 message: message.into(),
693 }
694 }
695
696 /// Return the stable coarse classification of this runtime failure.
697 ///
698 /// # Examples
699 ///
700 /// ```rust
701 /// use tenferro_runtime::{Error, ErrorPhase};
702 /// use tenferro_tensor::{ErrorKind, ValidationError, ValidationKind};
703 ///
704 /// let error = Error::validation(
705 /// "transpose",
706 /// ErrorPhase::GraphBuild,
707 /// ValidationError::AxisOutOfBounds { axis: 2, rank: 2 },
708 /// );
709 /// assert_eq!(error.kind(), ErrorKind::Validation(ValidationKind::AxisOutOfBounds));
710 /// ```
711 pub fn kind(&self) -> ErrorKind {
712 match self {
713 Self::Validation { source, .. } => ErrorKind::Validation(source.kind()),
714 Self::MissingInput(_)
715 | Self::UnexpectedBinding { .. }
716 | Self::UnboundPlaceholder { .. }
717 | Self::DuplicateBinding { .. }
718 | Self::ContextMismatch { .. } => ErrorKind::RuntimeState,
719 Self::NonScalarGrad { .. } => ErrorKind::Validation(ValidationKind::InvalidArgument),
720 Self::GraphInputCountMismatch { .. } => {
721 ErrorKind::Validation(ValidationKind::InvalidArgument)
722 }
723 Self::Unsupported { .. } | Self::UnsupportedAdRule { .. } => ErrorKind::Unsupported,
724 Self::AdRuleSource { .. } => ErrorKind::Validation(ValidationKind::InvalidArgument),
725 Self::TensorRuntime(error) => error.kind(),
726 Self::Extension { kind, .. } => *kind,
727 Self::RuntimeState { .. }
728 | Self::RuntimeStateSource { .. }
729 | Self::EventDomain { .. } => ErrorKind::RuntimeState,
730 Self::WithSuppressed { primary, .. } => primary.kind(),
731 Self::PlaceholderDtypeMismatch { .. } => {
732 ErrorKind::Validation(ValidationKind::DTypeMismatch)
733 }
734 Self::PlaceholderShapeMismatch { .. } | Self::PlaceholderShapeBoundExceeded { .. } => {
735 ErrorKind::Validation(ValidationKind::ShapeMismatch)
736 }
737 Self::PlaceholderRankMismatch { .. } => {
738 ErrorKind::Validation(ValidationKind::RankMismatch)
739 }
740 Self::ShapeConstraintViolation { .. } => {
741 ErrorKind::Validation(ValidationKind::ShapeMismatch)
742 }
743 Self::ShapeConstraintEvaluation { .. } => {
744 ErrorKind::Validation(ValidationKind::InvalidArgument)
745 }
746 Self::SymbolicShapeConversion { .. } => {
747 ErrorKind::Validation(ValidationKind::InvalidArgument)
748 }
749 Self::ShapeExpressionEvaluation { .. } => {
750 ErrorKind::Validation(ValidationKind::InvalidArgument)
751 }
752 Self::Internal(_) => ErrorKind::Internal,
753 }
754 }
755
756 /// Return the discovery phase when this error has one.
757 ///
758 /// # Examples
759 ///
760 /// ```rust
761 /// use tenferro_runtime::{Error, ErrorPhase};
762 /// use tenferro_tensor::ValidationError;
763 ///
764 /// let error = Error::validation(
765 /// "reshape",
766 /// ErrorPhase::Compile,
767 /// ValidationError::RankMismatch { expected: 2, actual: 1 },
768 /// );
769 /// assert_eq!(error.phase(), Some(ErrorPhase::Compile));
770 /// ```
771 pub fn phase(&self) -> Option<ErrorPhase> {
772 match self {
773 Self::Validation { phase, .. } => Some(*phase),
774 Self::TensorRuntime(_) => Some(ErrorPhase::Execution),
775 Self::Unsupported { phase, .. } => Some(*phase),
776 Self::Extension { phase, .. } => Some(*phase),
777 Self::RuntimeState { phase, .. } | Self::RuntimeStateSource { phase, .. } => {
778 Some(*phase)
779 }
780 Self::WithSuppressed { primary, .. } => primary.phase(),
781 Self::AdRuleSource { .. } => Some(ErrorPhase::GraphBuild),
782 Self::PlaceholderDtypeMismatch { .. }
783 | Self::PlaceholderShapeMismatch { .. }
784 | Self::PlaceholderRankMismatch { .. }
785 | Self::GraphInputCountMismatch { .. }
786 | Self::UnexpectedBinding { .. }
787 | Self::UnboundPlaceholder { .. }
788 | Self::DuplicateBinding { .. } => Some(ErrorPhase::Execution),
789 Self::EventDomain { .. } => Some(ErrorPhase::Execution),
790 Self::SymbolicShapeConversion { phase, .. } => Some(*phase),
791 Self::ShapeExpressionEvaluation { .. } => Some(ErrorPhase::Execution),
792 _ => None,
793 }
794 }
795}
796
797fn core_dtype(dtype: DType) -> tenferro_tensor::core::DType {
798 match dtype {
799 DType::F32 => tenferro_tensor::core::DType::F32,
800 DType::F64 => tenferro_tensor::core::DType::F64,
801 DType::I32 => tenferro_tensor::core::DType::I32,
802 DType::I64 => tenferro_tensor::core::DType::I64,
803 DType::Bool => tenferro_tensor::core::DType::Bool,
804 DType::C32 => tenferro_tensor::core::DType::C32,
805 DType::C64 => tenferro_tensor::core::DType::C64,
806 }
807}
808
809/// Opaque identifier for an eager AD runtime, used in [`Error::ContextMismatch`].
810#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
811pub struct ContextId(usize);
812
813impl ContextId {
814 /// Generate a fresh opaque runtime context identifier.
815 ///
816 /// Runtime implementations use this when constructing a new execution
817 /// context. The value is intentionally opaque and is only useful in error
818 /// reporting and equality checks.
819 pub fn fresh() -> Self {
820 let id = NEXT_CONTEXT_ID.fetch_add(1, Ordering::Relaxed);
821 Self(id)
822 }
823}
824
825impl std::fmt::Display for ContextId {
826 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
827 write!(f, "ctx@{:x}", self.0)
828 }
829}
830
831/// Result type alias for tenferro operations.
832pub type Result<T> = std::result::Result<T, Error>;
833
834#[cfg(test)]
835mod tests;