use std::fmt;
use thiserror::Error;
pub fn render_diagnostic(diag: &dyn miette::Diagnostic) -> String {
use miette::{GraphicalReportHandler, GraphicalTheme, ThemeCharacters, ThemeStyles};
let theme = GraphicalTheme {
characters: ThemeCharacters::unicode(),
styles: ThemeStyles::none(),
};
let handler = GraphicalReportHandler::new_themed(theme)
.with_links(false)
.with_width(120);
let mut buf = String::new();
match handler.render_report(&mut buf, diag) {
Ok(()) => buf,
Err(_) => diag.to_string(),
}
}
fn render_analyzer_error(error: &veryl_analyzer::AnalyzerError) -> String {
let mut rendered = render_diagnostic(error);
if let veryl_analyzer::AnalyzerError::UnresolvableGenericExpression { identifier, .. } = error {
rendered.push_str("\nhelp: if this is a module `param ");
rendered.push_str(identifier);
rendered.push_str(
": type`, declare it as a module generic parameter like `module ModuleName::<",
);
rendered.push_str(identifier);
rendered.push_str(": type>` instead");
}
rendered
}
#[derive(Debug)]
pub enum CompilationWarning {
Analyzer(veryl_analyzer::AnalyzerError),
Frontend(celox_frontend_veryl::FrontendDiagnostic),
}
impl fmt::Display for CompilationWarning {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Analyzer(diagnostic) => diagnostic.fmt(f),
Self::Frontend(diagnostic) => diagnostic.fmt(f),
}
}
}
impl std::error::Error for CompilationWarning {}
impl miette::Diagnostic for CompilationWarning {
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
match self {
Self::Analyzer(diagnostic) => miette::Diagnostic::code(diagnostic),
Self::Frontend(diagnostic) => miette::Diagnostic::code(diagnostic),
}
}
fn severity(&self) -> Option<miette::Severity> {
match self {
Self::Analyzer(diagnostic) => miette::Diagnostic::severity(diagnostic),
Self::Frontend(diagnostic) => miette::Diagnostic::severity(diagnostic),
}
}
fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
match self {
Self::Analyzer(diagnostic) => miette::Diagnostic::help(diagnostic),
Self::Frontend(diagnostic) => miette::Diagnostic::help(diagnostic),
}
}
fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
match self {
Self::Analyzer(diagnostic) => miette::Diagnostic::url(diagnostic),
Self::Frontend(diagnostic) => miette::Diagnostic::url(diagnostic),
}
}
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
match self {
Self::Analyzer(diagnostic) => miette::Diagnostic::source_code(diagnostic),
Self::Frontend(diagnostic) => miette::Diagnostic::source_code(diagnostic),
}
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
match self {
Self::Analyzer(diagnostic) => miette::Diagnostic::labels(diagnostic),
Self::Frontend(diagnostic) => miette::Diagnostic::labels(diagnostic),
}
}
}
#[derive(Debug)]
pub enum SimulatorErrorKind {
FrontendArtifact(crate::FrontendArtifactError),
SIRParser(crate::ParserError),
Analyzer(Vec<veryl_analyzer::AnalyzerError>),
Frontend(Vec<celox_frontend_veryl::FrontendDiagnostic>),
Runtime(crate::RuntimeErrorCode),
Codegen(CodegenError),
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum CodegenError {
#[cfg(feature = "host-runtime")]
#[error("{0}")]
Cranelift(
#[from]
#[source]
celox_backend_cranelift::CraneliftError,
),
#[error("{context}: {source}")]
Optimization {
context: &'static str,
#[source]
source: celox_sir_opt::OptimizationError,
},
#[error("{phase}: {source}")]
SirVerification {
phase: String,
#[source]
source: celox_sir::verify::SirVerifyError,
},
#[cfg(all(
feature = "host-runtime",
any(
target_arch = "x86_64",
feature = "arm64-codegen",
target_arch = "aarch64"
)
))]
#[error("native emission failed: {source}")]
NativePipeline {
#[source]
source: crate::backend::native::emit::ChainedEmitError,
},
#[cfg(all(
feature = "host-runtime",
any(
target_arch = "x86_64",
feature = "arm64-codegen",
target_arch = "aarch64"
)
))]
#[error("native emission failed: {source}")]
NativeEmission {
#[source]
source: crate::backend::native::emit::EmitError,
},
#[cfg(all(
feature = "host-runtime",
any(
target_arch = "x86_64",
feature = "arm64-codegen",
target_arch = "aarch64"
)
))]
#[error("failed to allocate executable native memory: {source}")]
NativeMemory {
#[source]
source: std::io::Error,
},
#[cfg(feature = "host-runtime")]
#[error("WASM {stage} failed: {source}")]
Wasm {
stage: &'static str,
#[source]
source: wasmtime::Error,
},
#[cfg(feature = "host-runtime")]
#[error("{message}")]
Message { message: String },
}
#[cfg(feature = "host-runtime")]
impl CodegenError {
pub(crate) fn message(message: impl Into<String>) -> Self {
Self::Message {
message: message.into(),
}
}
}
#[derive(Debug)]
pub struct SimulatorError {
kind: Box<SimulatorErrorKind>,
warnings: Vec<CompilationWarning>,
}
impl SimulatorError {
pub fn new(kind: SimulatorErrorKind) -> Self {
Self {
kind: Box::new(kind),
warnings: Vec::new(),
}
}
pub fn with_warnings(mut self, warnings: Vec<CompilationWarning>) -> Self {
self.warnings = warnings;
self
}
pub fn kind(&self) -> &SimulatorErrorKind {
&self.kind
}
pub fn warnings(&self) -> &[CompilationWarning] {
&self.warnings
}
}
impl fmt::Display for SimulatorError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind.as_ref() {
SimulatorErrorKind::FrontendArtifact(error) => write!(f, "{error}")?,
SimulatorErrorKind::SIRParser(e) => f.write_str(&render_diagnostic(e))?,
SimulatorErrorKind::Analyzer(errors) => {
for (i, e) in errors.iter().enumerate() {
if i > 0 {
f.write_str("\n")?;
}
f.write_str(&render_analyzer_error(e))?;
}
}
SimulatorErrorKind::Frontend(diagnostics) => {
for (i, diagnostic) in diagnostics.iter().enumerate() {
if i > 0 {
f.write_str("\n")?;
}
f.write_str(&render_diagnostic(diagnostic))?;
}
}
SimulatorErrorKind::Runtime(e) => write!(f, "Runtime error: {e}")?,
SimulatorErrorKind::Codegen(error) => write!(f, "JIT Code generation error: {error}")?,
}
if !self.warnings.is_empty() {
f.write_str("\n\n--- warnings ---\n\n")?;
for (i, w) in self.warnings.iter().enumerate() {
if i > 0 {
f.write_str("\n")?;
}
f.write_str(&render_diagnostic(w))?;
}
}
Ok(())
}
}
impl std::error::Error for SimulatorError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self.kind.as_ref() {
SimulatorErrorKind::FrontendArtifact(error) => Some(error),
SimulatorErrorKind::SIRParser(e) => Some(e),
SimulatorErrorKind::Runtime(e) => Some(e),
SimulatorErrorKind::Codegen(error) => Some(error),
_ => None,
}
}
}
impl From<crate::FrontendArtifactError> for SimulatorError {
fn from(error: crate::FrontendArtifactError) -> Self {
SimulatorError::new(SimulatorErrorKind::FrontendArtifact(error))
}
}
impl From<crate::RuntimeErrorCode> for SimulatorError {
fn from(e: crate::RuntimeErrorCode) -> Self {
SimulatorError::new(SimulatorErrorKind::Runtime(e))
}
}
impl From<crate::ParserError> for SimulatorError {
fn from(e: crate::ParserError) -> Self {
SimulatorError::new(SimulatorErrorKind::SIRParser(e))
}
}
impl From<CodegenError> for SimulatorError {
fn from(error: CodegenError) -> Self {
SimulatorError::new(SimulatorErrorKind::Codegen(error))
}
}
#[cfg(feature = "host-runtime")]
impl From<celox_backend_cranelift::CraneliftError> for SimulatorError {
fn from(error: celox_backend_cranelift::CraneliftError) -> Self {
CodegenError::from(error).into()
}
}
#[cfg(all(test, feature = "host-runtime"))]
mod tests {
use std::error::Error as _;
use super::{CodegenError, SimulatorError, SimulatorErrorKind};
#[test]
fn codegen_error_is_available_as_the_simulator_error_source() {
let error = SimulatorError::from(CodegenError::message("compile worker stopped"));
assert!(matches!(error.kind(), SimulatorErrorKind::Codegen(_)));
assert_eq!(
error.to_string(),
"JIT Code generation error: compile worker stopped"
);
assert_eq!(
error.source().unwrap().to_string(),
"compile worker stopped"
);
}
#[test]
fn simulator_error_keeps_the_backend_source_chain() {
let backend = celox_backend_cranelift::CraneliftError::NativeTarget {
message: "unsupported test target",
};
let error = SimulatorError::from(backend);
let codegen = error.source().expect("codegen source");
assert!(codegen.to_string().contains("native target"));
let backend = codegen.source().expect("backend source");
assert!(backend.to_string().contains("unsupported test target"));
}
}