1use std::fmt;
2use thiserror::Error;
3
4pub fn render_diagnostic(diag: &dyn miette::Diagnostic) -> String {
9 use miette::{GraphicalReportHandler, GraphicalTheme, ThemeCharacters, ThemeStyles};
10
11 let theme = GraphicalTheme {
12 characters: ThemeCharacters::unicode(),
13 styles: ThemeStyles::none(),
14 };
15 let handler = GraphicalReportHandler::new_themed(theme)
16 .with_links(false)
17 .with_width(120);
18 let mut buf = String::new();
19 match handler.render_report(&mut buf, diag) {
20 Ok(()) => buf,
21 Err(_) => diag.to_string(),
23 }
24}
25
26fn render_analyzer_error(error: &veryl_analyzer::AnalyzerError) -> String {
27 let mut rendered = render_diagnostic(error);
28 if let veryl_analyzer::AnalyzerError::UnresolvableGenericExpression { identifier, .. } = error {
29 rendered.push_str("\nhelp: if this is a module `param ");
30 rendered.push_str(identifier);
31 rendered.push_str(
32 ": type`, declare it as a module generic parameter like `module ModuleName::<",
33 );
34 rendered.push_str(identifier);
35 rendered.push_str(": type>` instead");
36 }
37 rendered
38}
39
40#[derive(Debug)]
42pub enum CompilationWarning {
43 Analyzer(veryl_analyzer::AnalyzerError),
44 Frontend(celox_frontend_veryl::FrontendDiagnostic),
45}
46
47impl fmt::Display for CompilationWarning {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 match self {
50 Self::Analyzer(diagnostic) => diagnostic.fmt(f),
51 Self::Frontend(diagnostic) => diagnostic.fmt(f),
52 }
53 }
54}
55
56impl std::error::Error for CompilationWarning {}
57
58impl miette::Diagnostic for CompilationWarning {
59 fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
60 match self {
61 Self::Analyzer(diagnostic) => miette::Diagnostic::code(diagnostic),
62 Self::Frontend(diagnostic) => miette::Diagnostic::code(diagnostic),
63 }
64 }
65
66 fn severity(&self) -> Option<miette::Severity> {
67 match self {
68 Self::Analyzer(diagnostic) => miette::Diagnostic::severity(diagnostic),
69 Self::Frontend(diagnostic) => miette::Diagnostic::severity(diagnostic),
70 }
71 }
72
73 fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
74 match self {
75 Self::Analyzer(diagnostic) => miette::Diagnostic::help(diagnostic),
76 Self::Frontend(diagnostic) => miette::Diagnostic::help(diagnostic),
77 }
78 }
79
80 fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
81 match self {
82 Self::Analyzer(diagnostic) => miette::Diagnostic::url(diagnostic),
83 Self::Frontend(diagnostic) => miette::Diagnostic::url(diagnostic),
84 }
85 }
86
87 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
88 match self {
89 Self::Analyzer(diagnostic) => miette::Diagnostic::source_code(diagnostic),
90 Self::Frontend(diagnostic) => miette::Diagnostic::source_code(diagnostic),
91 }
92 }
93
94 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
95 match self {
96 Self::Analyzer(diagnostic) => miette::Diagnostic::labels(diagnostic),
97 Self::Frontend(diagnostic) => miette::Diagnostic::labels(diagnostic),
98 }
99 }
100}
101
102#[derive(Debug)]
104pub enum SimulatorErrorKind {
105 FrontendArtifact(crate::FrontendArtifactError),
106 SIRParser(crate::ParserError),
107 Analyzer(Vec<veryl_analyzer::AnalyzerError>),
108 Frontend(Vec<celox_frontend_veryl::FrontendDiagnostic>),
109 Runtime(crate::RuntimeErrorCode),
110 Codegen(CodegenError),
111}
112
113#[derive(Debug, Error)]
115#[non_exhaustive]
116pub enum CodegenError {
117 #[cfg(feature = "host-runtime")]
118 #[error("{0}")]
119 Cranelift(
120 #[from]
121 #[source]
122 celox_backend_cranelift::CraneliftError,
123 ),
124
125 #[error("{context}: {source}")]
126 Optimization {
127 context: &'static str,
128 #[source]
129 source: celox_sir_opt::OptimizationError,
130 },
131
132 #[error("{phase}: {source}")]
133 SirVerification {
134 phase: String,
135 #[source]
136 source: celox_sir::verify::SirVerifyError,
137 },
138
139 #[cfg(all(
140 feature = "host-runtime",
141 any(
142 target_arch = "x86_64",
143 feature = "arm64-codegen",
144 target_arch = "aarch64"
145 )
146 ))]
147 #[error("native emission failed: {source}")]
148 NativePipeline {
149 #[source]
150 source: crate::backend::native::emit::ChainedEmitError,
151 },
152
153 #[cfg(all(
154 feature = "host-runtime",
155 any(
156 target_arch = "x86_64",
157 feature = "arm64-codegen",
158 target_arch = "aarch64"
159 )
160 ))]
161 #[error("native emission failed: {source}")]
162 NativeEmission {
163 #[source]
164 source: crate::backend::native::emit::EmitError,
165 },
166
167 #[cfg(all(
168 feature = "host-runtime",
169 any(
170 target_arch = "x86_64",
171 feature = "arm64-codegen",
172 target_arch = "aarch64"
173 )
174 ))]
175 #[error("failed to allocate executable native memory: {source}")]
176 NativeMemory {
177 #[source]
178 source: std::io::Error,
179 },
180
181 #[cfg(feature = "host-runtime")]
182 #[error("WASM {stage} failed: {source}")]
183 Wasm {
184 stage: &'static str,
185 #[source]
186 source: wasmtime::Error,
187 },
188
189 #[cfg(feature = "host-runtime")]
190 #[error("{message}")]
191 Message { message: String },
192
193 #[error("compilation was cancelled")]
196 Cancelled,
197}
198
199#[cfg(feature = "host-runtime")]
200impl CodegenError {
201 pub(crate) fn message(message: impl Into<String>) -> Self {
202 Self::Message {
203 message: message.into(),
204 }
205 }
206}
207
208#[derive(Debug)]
210pub struct SimulatorError {
211 kind: Box<SimulatorErrorKind>,
212 warnings: Vec<CompilationWarning>,
213}
214
215impl SimulatorError {
216 pub fn new(kind: SimulatorErrorKind) -> Self {
218 Self {
219 kind: Box::new(kind),
220 warnings: Vec::new(),
221 }
222 }
223
224 pub fn with_warnings(mut self, warnings: Vec<CompilationWarning>) -> Self {
226 self.warnings = warnings;
227 self
228 }
229
230 pub fn kind(&self) -> &SimulatorErrorKind {
232 &self.kind
233 }
234
235 pub fn warnings(&self) -> &[CompilationWarning] {
237 &self.warnings
238 }
239}
240
241impl fmt::Display for SimulatorError {
242 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243 match self.kind.as_ref() {
244 SimulatorErrorKind::FrontendArtifact(error) => write!(f, "{error}")?,
245 SimulatorErrorKind::SIRParser(e) => f.write_str(&render_diagnostic(e))?,
246 SimulatorErrorKind::Analyzer(errors) => {
247 for (i, e) in errors.iter().enumerate() {
248 if i > 0 {
249 f.write_str("\n")?;
250 }
251 f.write_str(&render_analyzer_error(e))?;
252 }
253 }
254 SimulatorErrorKind::Frontend(diagnostics) => {
255 for (i, diagnostic) in diagnostics.iter().enumerate() {
256 if i > 0 {
257 f.write_str("\n")?;
258 }
259 f.write_str(&render_diagnostic(diagnostic))?;
260 }
261 }
262 SimulatorErrorKind::Runtime(e) => write!(f, "Runtime error: {e}")?,
263 SimulatorErrorKind::Codegen(error) => write!(f, "JIT Code generation error: {error}")?,
264 }
265 if !self.warnings.is_empty() {
266 f.write_str("\n\n--- warnings ---\n\n")?;
267 for (i, w) in self.warnings.iter().enumerate() {
268 if i > 0 {
269 f.write_str("\n")?;
270 }
271 f.write_str(&render_diagnostic(w))?;
272 }
273 }
274 Ok(())
275 }
276}
277
278impl std::error::Error for SimulatorError {
279 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
280 match self.kind.as_ref() {
281 SimulatorErrorKind::FrontendArtifact(error) => Some(error),
282 SimulatorErrorKind::SIRParser(e) => Some(e),
283 SimulatorErrorKind::Runtime(e) => Some(e),
284 SimulatorErrorKind::Codegen(error) => Some(error),
285 _ => None,
286 }
287 }
288}
289
290impl From<crate::FrontendArtifactError> for SimulatorError {
291 fn from(error: crate::FrontendArtifactError) -> Self {
292 SimulatorError::new(SimulatorErrorKind::FrontendArtifact(error))
293 }
294}
295
296impl From<crate::RuntimeErrorCode> for SimulatorError {
297 fn from(e: crate::RuntimeErrorCode) -> Self {
298 SimulatorError::new(SimulatorErrorKind::Runtime(e))
299 }
300}
301
302impl From<crate::ParserError> for SimulatorError {
303 fn from(e: crate::ParserError) -> Self {
304 SimulatorError::new(SimulatorErrorKind::SIRParser(e))
305 }
306}
307
308impl From<CodegenError> for SimulatorError {
309 fn from(error: CodegenError) -> Self {
310 SimulatorError::new(SimulatorErrorKind::Codegen(error))
311 }
312}
313
314#[cfg(feature = "host-runtime")]
315impl From<celox_backend_cranelift::CraneliftError> for SimulatorError {
316 fn from(error: celox_backend_cranelift::CraneliftError) -> Self {
317 CodegenError::from(error).into()
318 }
319}
320
321#[cfg(all(test, feature = "host-runtime"))]
322mod tests {
323 use std::error::Error as _;
324
325 use super::{CodegenError, SimulatorError, SimulatorErrorKind};
326
327 #[test]
328 fn codegen_error_is_available_as_the_simulator_error_source() {
329 let error = SimulatorError::from(CodegenError::message("compile worker stopped"));
330
331 assert!(matches!(error.kind(), SimulatorErrorKind::Codegen(_)));
332 assert_eq!(
333 error.to_string(),
334 "JIT Code generation error: compile worker stopped"
335 );
336 assert_eq!(
337 error.source().unwrap().to_string(),
338 "compile worker stopped"
339 );
340 }
341
342 #[test]
343 fn simulator_error_keeps_the_backend_source_chain() {
344 let backend = celox_backend_cranelift::CraneliftError::NativeTarget {
345 message: "unsupported test target",
346 };
347 let error = SimulatorError::from(backend);
348
349 let codegen = error.source().expect("codegen source");
350 assert!(codegen.to_string().contains("native target"));
351 let backend = codegen.source().expect("backend source");
352 assert!(backend.to_string().contains("unsupported test target"));
353 }
354}