use std::fmt;
use std::path::Path;
use std::sync::Once;
use anstyle::{AnsiColor, Style};
use serde::Deserialize;
const MARKER: &str = "__FURIOSA_KERNEL_LOAD_FAILURE__\n";
pub(crate) fn failure_payload(path: &str) -> Option<String> {
let row = log_row(path)?;
Some(format!("{MARKER}{row}"))
}
pub fn install_hook() {
static ONCE: Once = Once::new();
ONCE.call_once(|| {
let default = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
if let Some(rendered) = try_render(info) {
anstream::eprintln!("\n{rendered}\n");
} else {
default(info);
}
}));
});
}
fn try_render(info: &std::panic::PanicHookInfo<'_>) -> Option<String> {
let payload = info
.payload()
.downcast_ref::<String>()
.map(String::as_str)
.or_else(|| info.payload().downcast_ref::<&str>().copied())?;
let json = payload.strip_prefix(MARKER)?;
let row: LogRow = serde_json::from_str(json).ok()?;
Some(render(&row))
}
fn log_row(path: &str) -> Option<String> {
let path = Path::new(path);
let stem = path.file_stem()?.to_str()?;
let log = std::fs::read_to_string(path.with_file_name("build-log.jsonl")).ok()?;
log.lines()
.find(|line| match serde_json::from_str::<LogRow>(line) {
Ok(row) => row.stage != Stage::Edf && row.fn_name.replace("::", "__") == stem,
Err(_) => false,
})
.map(str::to_string)
}
#[derive(Deserialize)]
struct LogRow {
#[serde(rename = "fn")]
fn_name: String,
stage: Stage,
file: Option<String>,
line: Option<u32>,
col: Option<u32>,
reason: Option<String>,
}
#[derive(Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
enum Stage {
Mir,
Visa,
Lir,
Edf,
}
impl fmt::Display for Stage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Mir => "MIR → VISA translation",
Self::Visa => "VISA → LIR translation",
Self::Lir => "LIR → kernel code generation",
Self::Edf => "edf",
})
}
}
fn render(row: &LogRow) -> String {
let err = Style::new().fg_color(Some(AnsiColor::Red.into())).bold();
let loc = Style::new().fg_color(Some(AnsiColor::Blue.into())).bold();
let bold = Style::new().bold();
let mut out = format!(
"{err}error{err:#}{bold}: kernel `{}` failed during {}{bold:#}",
row.fn_name, row.stage
);
if let (Some(file), Some(line), Some(col)) = (&row.file, row.line, row.col) {
out.push_str(&format!("\n{loc} -->{loc:#} {file}:{line}:{col}"));
append_snippet(&mut out, file, line, col, loc, err);
}
if let Some(reason) = &row.reason {
let mut lines = reason.lines();
if let Some(first) = lines.next() {
out.push_str(&format!("\n{loc} ={loc:#} {bold}note{bold:#}: {first}"));
for tail in lines {
out.push_str(&format!("\n {tail}"));
}
}
}
out.push_str(&format!(
"\n{loc} ={loc:#} {bold}help{bold:#}: run `cargo furiosa-opt compiler build --device-function {}` to reproduce",
row.fn_name
));
out
}
fn append_snippet(out: &mut String, file: &str, line: u32, col: u32, loc: Style, err: Style) {
let Ok(source) = std::fs::read_to_string(file) else {
return;
};
let Some(src_line) = source.lines().nth(line.saturating_sub(1) as usize) else {
return;
};
let no = line.to_string();
let pad = " ".repeat(no.len());
let caret_indent = " ".repeat(col.saturating_sub(1) as usize);
out.push_str(&format!(
"\n{pad} {loc}|{loc:#}\n{loc}{no} |{loc:#} {src_line}\n{pad} {loc}|{loc:#} {caret_indent}{err}^{err:#}\n{pad} {loc}|{loc:#}"
));
}