rssn_advanced/error/mod.rs
1//! Cold-path error infrastructure.
2//!
3//! Provides the `rssn_error!` macro that generates, for every variant of an
4//! error enum, a `cold_*` constructor marked `#[cold]` `#[inline(never)]`
5//! `#[track_caller]`. The hot path branches into these constructors only on
6//! genuine failure, keeping the success path's I-cache footprint minimal and
7//! letting the branch predictor assume success.
8//!
9//! Each module-level error type below is the canonical failure surface for one
10//! subsystem. They are intentionally narrow — variants describe *what* went
11//! wrong, not *where* (the `#[track_caller]` attribute on the cold constructor
12//! captures the call site).
13
14use core::fmt;
15
16// =========================================================================
17// The rssn_error! macro
18// =========================================================================
19
20/// Declares an error enum together with one `#[cold]` constructor per variant.
21///
22/// For each variant `Foo`, the macro emits a public free function
23/// `cold_<enum>_foo(...) -> Result<T, Enum>` that returns
24/// `Err(Enum::Foo { .. })`. Callers in hot code use:
25///
26/// ```ignore
27/// return cold_dag_error_arena_full();
28/// ```
29///
30/// The function is `#[cold] #[inline(never)] #[track_caller]`, ensuring the
31/// compiler lays it out off the hot path and that panic-like diagnostics
32/// preserve caller location.
33#[doc(hidden)]
34#[macro_export]
35macro_rules! rssn_error {
36 (
37 $(#[$meta:meta])*
38 $vis:vis enum $name:ident {
39 $(
40 $(#[$variant_meta:meta])*
41 $variant:ident $( { $( $(#[$field_meta:meta])* $field:ident : $ftype:ty ),* $(,)? } )? $( ( $( $(#[$tuple_meta:meta])* $tname:ident : $ttype:ty ),* $(,)? ) )?
42 ),* $(,)?
43 }
44 ) => {
45 $(#[$meta])*
46 $vis enum $name {
47 $(
48 $(#[$variant_meta])*
49 $variant $( { $( $(#[$field_meta])* $field : $ftype ),* } )? $( ( $( $(#[$tuple_meta])* $ttype ),* ) )?,
50 )*
51 }
52
53 pastey::paste! {
54 $(
55 $(#[$variant_meta])*
56 #[doc(hidden)]
57 #[cold]
58 #[track_caller]
59 #[inline(never)]
60 pub const fn [<cold_ $name:snake _ $variant:snake>]<T>(
61 $($($field : $ftype),*)?
62 $($($tname : $ttype),*)?
63 ) -> core::result::Result<T, $name> {
64 core::result::Result::Err($name::$variant $( { $($field),* } )? $( ( $( $tname ),* ) )?)
65 }
66 )*
67 }
68 };
69}
70
71// =========================================================================
72// Module-level error enums
73// =========================================================================
74
75rssn_error! {
76 /// AST / projection-layer failures.
77 #[derive(Debug, Clone, PartialEq, Eq)]
78 pub enum AstError {
79 /// The AST projection ran out of inline buffer space.
80 ProjectionOverflow,
81 /// A relative pointer overflowed its underlying integer width.
82 RelPtrOverflow,
83 /// Conversion between DAG and AST encountered an unknown symbol kind.
84 UnknownSymbolKind,
85 /// A cycle was detected while converting; the input is not a DAG.
86 CycleDetected,
87 }
88}
89
90rssn_error! {
91 /// DAG arena / dedup / interning failures.
92 #[derive(Debug, Clone, PartialEq, Eq)]
93 pub enum DagError {
94 /// The arena hit its maximum addressable capacity (`u32::MAX - 1`).
95 ArenaFull,
96 /// A `DagNodeId` referred to a slot outside the current arena.
97 DanglingId,
98 /// Attempted to dereference the null sentinel.
99 NullSentinel,
100 /// A node's child count exceeded the configured arity limit.
101 ArityOverflow,
102 }
103}
104
105rssn_error! {
106 /// JIT compilation pipeline failures.
107 #[derive(Debug, Clone, PartialEq, Eq)]
108 pub enum JitError {
109 /// Cranelift rejected the emitted IR (verifier or backend failure).
110 VerifierRejected,
111 /// A custom function symbol has no registered implementation.
112 UnknownFunction,
113 /// The IR builder hit its instruction budget.
114 BudgetExhausted,
115 /// Codegen visited a node whose shape contradicts its `SymbolKind`.
116 MalformedNode,
117 /// The Cranelift backend or native target failed to initialize.
118 InitFailed,
119 }
120}
121
122rssn_error! {
123 /// Parallel evaluation / chunking failures.
124 #[derive(Debug, Clone, PartialEq, Eq)]
125 pub enum ParallelError {
126 /// The expression has no commutative split point.
127 NotSplittable,
128 /// A worker fiber panicked before reporting a result.
129 WorkerLost,
130 /// A required variable binding was missing.
131 MissingVariable,
132 }
133}
134
135rssn_error! {
136 /// Parser surface failures (token / grammar level).
137 #[derive(Debug, Clone, PartialEq, Eq)]
138 pub enum ParserError {
139 /// Reached end of input while a production was still pending.
140 UnexpectedEof,
141 /// Saw a token that no production could accept.
142 UnexpectedToken,
143 /// Parenthesis depth exceeded `MAX_PAREN_DEPTH`.
144 ParenDepthExceeded,
145 /// A numeric literal could not be parsed as `f64`.
146 InvalidNumber,
147 }
148}
149
150rssn_error! {
151 /// Storage / cache / serialization failures.
152 #[derive(Debug, Clone, PartialEq, Eq)]
153 pub enum StorageError {
154 /// Underlying IO operation failed (open, read, write, mmap).
155 Io,
156 /// `bincode` decoded a structurally invalid arena header.
157 CorruptHeader,
158 /// The on-disk version tag did not match the runtime version.
159 VersionMismatch,
160 /// Mmap requested but unsupported on this platform.
161 MmapUnsupported,
162 }
163}
164
165rssn_error! {
166 /// FFI boundary failures.
167 #[derive(Debug, Clone, PartialEq, Eq)]
168 pub enum FfiError {
169 /// A `*const T` or `*mut T` argument was null where required.
170 NullArgument,
171 /// A C string was not valid UTF-8.
172 InvalidUtf8,
173 /// The handle's tag/generation does not match a live object.
174 StaleHandle,
175 /// The fiber runtime is not initialized.
176 RuntimeUninitialized,
177 }
178}
179
180// =========================================================================
181// Display impls (kept hand-written; the macro stays narrow on purpose)
182// =========================================================================
183
184macro_rules! impl_display {
185 ($($t:ty),* $(,)?) => {
186 $(
187 impl fmt::Display for $t {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 fmt::Debug::fmt(self, f)
190 }
191 }
192 )*
193 };
194}
195
196impl_display!(
197 AstError,
198 DagError,
199 JitError,
200 ParallelError,
201 ParserError,
202 StorageError,
203 FfiError,
204);
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn cold_constructor_returns_err_variant() {
212 let r: Result<(), DagError> = cold_dag_error_arena_full();
213 assert_eq!(r, Err(DagError::ArenaFull));
214 }
215
216 #[test]
217 fn cold_constructor_for_every_module() {
218 let _: Result<u8, AstError> = cold_ast_error_projection_overflow();
219 let _: Result<u8, JitError> = cold_jit_error_verifier_rejected();
220 let _: Result<u8, ParallelError> = cold_parallel_error_not_splittable();
221 let _: Result<u8, ParserError> = cold_parser_error_unexpected_eof();
222 let _: Result<u8, StorageError> = cold_storage_error_io();
223 let _: Result<u8, FfiError> = cold_ffi_error_null_argument();
224 }
225
226 #[test]
227 fn display_uses_debug() {
228 assert_eq!(format!("{}", DagError::ArenaFull), "ArenaFull");
229 }
230}