1use std::fmt;
9use std::path::Path;
10use std::sync::Once;
11
12use anstyle::{AnsiColor, Style};
13use serde::Deserialize;
14
15const MARKER: &str = "__FURIOSA_KERNEL_LOAD_FAILURE__\n";
16
17pub(crate) fn failure_payload(path: &str) -> Option<String> {
20 let row = log_row(path)?;
21 Some(format!("{MARKER}{row}"))
22}
23
24pub fn install_hook() {
26 static ONCE: Once = Once::new();
27 ONCE.call_once(|| {
28 let default = std::panic::take_hook();
29 std::panic::set_hook(Box::new(move |info| {
30 if let Some(rendered) = try_render(info) {
31 anstream::eprintln!("\n{rendered}\n");
32 } else {
33 default(info);
34 }
35 }));
36 });
37}
38
39fn try_render(info: &std::panic::PanicHookInfo<'_>) -> Option<String> {
40 let payload = info
41 .payload()
42 .downcast_ref::<String>()
43 .map(String::as_str)
44 .or_else(|| info.payload().downcast_ref::<&str>().copied())?;
45 let json = payload.strip_prefix(MARKER)?;
46 let row: LogRow = serde_json::from_str(json).ok()?;
47 Some(render(&row))
48}
49
50fn log_row(path: &str) -> Option<String> {
51 let path = Path::new(path);
52 let stem = path.file_stem()?.to_str()?;
53 let log = std::fs::read_to_string(path.with_file_name("build-log.jsonl")).ok()?;
54 log.lines()
55 .find(|line| match serde_json::from_str::<LogRow>(line) {
56 Ok(row) => row.stage != Stage::Edf && row.fn_name.replace("::", "__") == stem,
57 Err(_) => false,
58 })
59 .map(str::to_string)
60}
61
62#[derive(Deserialize)]
65struct LogRow {
66 #[serde(rename = "fn")]
67 fn_name: String,
68 stage: Stage,
69 file: Option<String>,
70 line: Option<u32>,
71 col: Option<u32>,
72 reason: Option<String>,
73}
74
75#[derive(Deserialize, PartialEq, Eq)]
76#[serde(rename_all = "lowercase")]
77enum Stage {
78 Mir,
79 Visa,
80 Lir,
81 Edf,
82}
83
84impl fmt::Display for Stage {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 f.write_str(match self {
87 Self::Mir => "MIR → VISA translation",
88 Self::Visa => "VISA → LIR translation",
89 Self::Lir => "LIR → kernel code generation",
90 Self::Edf => "edf",
91 })
92 }
93}
94
95fn render(row: &LogRow) -> String {
96 let err = Style::new().fg_color(Some(AnsiColor::Red.into())).bold();
97 let loc = Style::new().fg_color(Some(AnsiColor::Blue.into())).bold();
98 let bold = Style::new().bold();
99
100 let mut out = format!(
101 "{err}error{err:#}{bold}: kernel `{}` was not compiled ({}){bold:#}",
102 row.fn_name, row.stage
103 );
104 if let (Some(file), Some(line), Some(col)) = (&row.file, row.line, row.col) {
105 out.push_str(&format!("\n{loc} -->{loc:#} {file}:{line}:{col}"));
106 append_snippet(&mut out, file, line, col, loc, err);
107 }
108 if let Some(reason) = &row.reason {
109 let mut lines = reason.lines();
110 if let Some(first) = lines.next() {
111 out.push_str(&format!("\n{loc} ={loc:#} {bold}note{bold:#}: {first}"));
112 for tail in lines {
113 out.push_str(&format!("\n {tail}"));
114 }
115 }
116 }
117 out.push_str(&format!(
118 "\n{loc} ={loc:#} {bold}help{bold:#}: run `cargo furiosa-opt compiler build --device-function {}` to reproduce",
119 row.fn_name
120 ));
121 out
122}
123
124fn append_snippet(out: &mut String, file: &str, line: u32, col: u32, loc: Style, err: Style) {
125 let Ok(source) = std::fs::read_to_string(file) else {
126 return;
127 };
128 let Some(src_line) = source.lines().nth(line.saturating_sub(1) as usize) else {
129 return;
130 };
131 let no = line.to_string();
132 let pad = " ".repeat(no.len());
133 let caret_indent = " ".repeat(col.saturating_sub(1) as usize);
134 out.push_str(&format!(
135 "\n{pad} {loc}|{loc:#}\n{loc}{no} |{loc:#} {src_line}\n{pad} {loc}|{loc:#} {caret_indent}{err}^{err:#}\n{pad} {loc}|{loc:#}"
136 ));
137}