Skip to main content

vyre_foundation/
error.rs

1//! Error types for IR validation, wire-format decoding, and GPU operations.
2//!
3//! Vyre unifies all failure modes under a single `Error` enum so that
4//! frontends, backends, and the conform gate speak the same language.
5//! Every variant carries an actionable `Fix:` message that tells the caller
6//! exactly what invariant was violated and how to recover.
7
8use thiserror::Error;
9
10/// Shorthand result type used throughout the vyre public API.
11///
12/// All fallible vyre operations return `Result<T>` so that callers only need
13/// to learn one error representation. The unified type ensures that a
14/// frontend emitting bad IR, a backend hitting an adapter limit, or a
15/// wire-format decoder seeing truncated bytes all produce the same top-level
16/// failure.
17pub type Result<T> = std::result::Result<T, Error>;
18
19/// The unified failure enum for every vyre operation.
20#[derive(Debug, Clone, PartialEq, Eq, Error)]
21#[non_exhaustive]
22pub enum Error {
23    /// A recursive composition cycle was found during operation inlining.
24    #[error(
25        "IR inlining cycle at operation `{op_id}`. Fix: remove the recursive Expr::Call chain or split the recursive algorithm into an explicit bounded Loop."
26    )]
27    InlineCycle {
28        /// The operation identifier that closed the cycle.
29        op_id: String,
30    },
31
32    /// Operation inlining could not resolve an operation id.
33    #[error(
34        "IR inlining could not resolve operation `{op_id}`. Fix: register a Category A operation with this id before lowering or replace the call with inline IR."
35    )]
36    InlineUnknownOp {
37        /// The missing operation identifier.
38        op_id: String,
39    },
40
41    /// Operation inlining rejected an operation that must dispatch separately.
42    #[error(
43        "IR inlining rejected non-inlinable operation `{op_id}`. Fix: this op processes buffer inputs and must be dispatched as a separate kernel, not composed via Expr::Call."
44    )]
45    InlineNonInlinable {
46        /// The operation identifier that cannot be inlined.
47        op_id: String,
48    },
49
50    /// The number of arguments passed to an inlined operation did not match.
51    #[error(
52        "IR inlining argument count mismatch for operation `{op_id}`: expected {expected}, got {got}. Fix: pass exactly one argument for each ReadOnly or Uniform input buffer declared by the callee program."
53    )]
54    InlineArgCountMismatch {
55        /// The operation identifier being expanded.
56        op_id: String,
57        /// The number of arguments the callee expects.
58        expected: usize,
59        /// The number of arguments the caller provided.
60        got: usize,
61    },
62
63    /// The inlined operation never wrote to its declared output buffer.
64    #[error(
65        "IR inlining found no output write for operation `{op_id}`. Fix: Ensure the op's program() body writes to its output buffer at least once."
66    )]
67    InlineNoOutput {
68        /// The operation identifier being expanded.
69        op_id: String,
70    },
71
72    /// The inlined operation declared an invalid number of output buffers.
73    #[error(
74        "IR inlining found {got} declared output buffers for operation `{op_id}`. Fix: mark exactly one result buffer with BufferDecl::output(...)."
75    )]
76    InlineOutputCountMismatch {
77        /// The operation identifier being expanded.
78        op_id: String,
79        /// The actual number of buffers marked as outputs.
80        got: usize,
81    },
82
83    /// Wire-format payload failed validation checks.
84    #[error(
85        "Wire-format validation failed: {message}. Fix: recompile the frontend program set and ensure the compiler only emits valid instructions."
86    )]
87    WireFormatValidation {
88        /// Human-readable description of the validation failure.
89        message: String,
90    },
91
92    /// target-text lowering failed before a shader could be emitted.
93    #[error(
94        "vyre target-text lowering: {message}. Fix: inspect the Program shape, backend capability report, and emitted shader diagnostics before retrying."
95    )]
96    Lowering {
97        /// Human-readable description of the lowering failure.
98        message: String,
99    },
100
101    /// Reference interpreter execution failed.
102    #[error(
103        "vyre reference interpreter: {message}. Fix: validate the Program and input buffer set before invoking the reference backend."
104    )]
105    Interp {
106        /// Human-readable description of the interpreter failure.
107        message: String,
108    },
109
110    /// GPU execution failed.
111    #[error(
112        "GPU pipeline failed: {message}. Fix: verify a concrete driver is linked and the compiled buffers fit the target adapter limits."
113    )]
114    Gpu {
115        /// Description of the GPU failure.
116        message: String,
117    },
118
119    /// Decode configuration failed validation.
120    #[error(
121        "Decode configuration failed: {message}. Fix: provide valid TOML and non-zero decode thresholds."
122    )]
123    DecodeConfig {
124        /// Description of the configuration failure.
125        message: String,
126    },
127
128    /// Decode execution or readback failed validation.
129    #[error(
130        "Decode pipeline failed: {message}. Fix: inspect shader output sizing and source-region validation."
131    )]
132    Decode {
133        /// Description of the decode failure.
134        message: String,
135    },
136
137    /// Decompression execution, sizing, or readback failed validation.
138    #[error(
139        "Decompression pipeline failed: {message}. Fix: validate frame metadata, split oversized payloads, and inspect GPU decompression status words."
140    )]
141    Decompress {
142        /// Description of the decompression failure.
143        message: String,
144    },
145
146    /// DFA compilation or scanning failed.
147    #[error(
148        "DFA pipeline failed: {message}. Fix: validate DFA transition tables, output links, and target adapter limits."
149    )]
150    Dfa {
151        /// Description of the DFA failure.
152        message: String,
153    },
154
155    /// Dataflow graph execution failed.
156    #[error(
157        "Dataflow pipeline failed: {message}. Fix: validate graph inputs, buffer sizing, and target adapter limits."
158    )]
159    Dataflow {
160        /// Description of the dataflow failure.
161        message: String,
162    },
163
164    /// Prefix-array construction failed before allocation or upload.
165    #[error(
166        "Prefix construction failed: {message}. Fix: split the input before building prefix arrays or reduce per-file scan size."
167    )]
168    Prefix {
169        /// Description of the prefix construction failure.
170        message: String,
171    },
172
173    /// CSR graph construction or validation failed.
174    #[error(
175        "CSR graph construction failed: {message}. Fix: cap graph size and ensure every edge endpoint is within node_count."
176    )]
177    Csr {
178        /// Description of the CSR failure.
179        message: String,
180    },
181
182    /// Serialization or deserialization failed.
183    #[error(
184        "Serialization failed: {message}. Fix: verify the wire payload is not truncated or corrupted."
185    )]
186    Serialization {
187        /// Description of the serialization failure.
188        message: String,
189    },
190
191    /// Rule formula construction or evaluation failed.
192    #[error(
193        "Rule evaluation failed: {message}. Fix: validate rule pattern ids, thresholds, and verdict buffer sizing before lowering."
194    )]
195    RuleEval {
196        /// Description of the rule failure.
197        message: String,
198    },
199
200    /// Wire-format schema version mismatch.
201    #[error(
202        "Wire-format version mismatch: expected {expected}, found {found}. Fix: re-encode with a matching vyre version or upgrade this runtime."
203    )]
204    VersionMismatch {
205        /// The schema version this runtime understands.
206        expected: u32,
207        /// The schema version present on the wire.
208        found: u32,
209    },
210
211    /// Unknown dialect on the wire.
212    #[error(
213        "Unknown dialect `{name}` (requested version `{requested}`). Fix: link the dialect crate providing `{name}` into this runtime or drop the op that uses it before encoding."
214    )]
215    UnknownDialect {
216        /// The dialect identifier on the wire (e.g. `"workgroup"`).
217        name: String,
218        /// The version string the encoder recorded for the dialect.
219        requested: String,
220    },
221
222    /// Unknown op inside a known dialect.
223    #[error(
224        "Unknown op `{op}` in dialect `{dialect}`. Fix: upgrade the runtime to a version that includes this op, or drop the op before encoding."
225    )]
226    UnknownOp {
227        /// The dialect that should contain the op.
228        dialect: String,
229        /// The op identifier that could not be resolved.
230        op: String,
231    },
232}
233
234impl Error {
235    /// Build a target-text lowering error with actionable guidance.
236    #[must_use]
237    pub fn lowering(message: impl Into<String>) -> Self {
238        Self::Lowering {
239            message: message.into(),
240        }
241    }
242
243    /// Build a reference-interpreter error with actionable guidance.
244    #[must_use]
245    pub fn interp(message: impl Into<String>) -> Self {
246        Self::Interp {
247            message: message.into(),
248        }
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255
256    #[test]
257    fn lowering_helper_contains_fix_hint() {
258        let err = Error::lowering("buffer too large");
259        let msg = err.to_string();
260        assert!(msg.contains("buffer too large"));
261        assert!(msg.contains("Fix:"));
262    }
263
264    #[test]
265    fn interp_helper_contains_fix_hint() {
266        let err = Error::interp("division by zero");
267        let msg = err.to_string();
268        assert!(msg.contains("division by zero"));
269        assert!(msg.contains("Fix:"));
270    }
271
272    #[test]
273    fn inline_cycle_display() {
274        let err = Error::InlineCycle {
275            op_id: "math::add".into(),
276        };
277        assert!(err.to_string().contains("math::add"));
278        assert!(err.to_string().contains("cycle"));
279    }
280
281    #[test]
282    fn version_mismatch_display() {
283        let err = Error::VersionMismatch {
284            expected: 6,
285            found: 5,
286        };
287        let msg = err.to_string();
288        assert!(msg.contains("6"));
289        assert!(msg.contains("5"));
290    }
291
292    #[test]
293    fn unknown_dialect_display() {
294        let err = Error::UnknownDialect {
295            name: "my-dialect".into(),
296            requested: "1.0".into(),
297        };
298        assert!(err.to_string().contains("my-dialect"));
299    }
300
301    #[test]
302    fn error_is_clone_and_eq() {
303        let a = Error::lowering("test");
304        let b = a.clone();
305        assert_eq!(a, b);
306    }
307
308    #[test]
309    fn inline_arg_count_mismatch_display() {
310        let err = Error::InlineArgCountMismatch {
311            op_id: "test::op".into(),
312            expected: 3,
313            got: 1,
314        };
315        let msg = err.to_string();
316        assert!(msg.contains("expected 3"));
317        assert!(msg.contains("got 1"));
318    }
319}