Skip to content

Commit 060d05e

Browse files
eddybLegNeato
authored andcommitted
linker: also dump SPIR-T on panic, not just during successful compilation.
1 parent fad761e commit 060d05e

File tree

2 files changed

+147
-102
lines changed

2 files changed

+147
-102
lines changed

crates/rustc_codegen_spirv/src/linker/mod.rs

Lines changed: 147 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -197,17 +197,21 @@ pub fn link(
197197
};
198198

199199
// FIXME(eddyb) should've really been "spirt::Module::lower_from_spv_bytes".
200-
let _timer = sess.timer("spirt::Module::lower_from_spv_file");
200+
let lower_from_spv_timer = sess.timer("spirt::Module::lower_from_spv_file");
201201
let cx = std::rc::Rc::new(spirt::Context::new());
202202
crate::custom_insts::register_to_spirt_context(&cx);
203203
(
204204
spv_words,
205205
spirt::Module::lower_from_spv_bytes(cx, spv_bytes),
206+
// HACK(eddyb) this is only returned for `SpirtDumpGuard`.
207+
lower_from_spv_timer,
206208
)
207209
};
208210

211+
// FIXME(eddyb) deduplicate with `SpirtDumpGuard`.
209212
let dump_spv_and_spirt = |spv_module: &Module, dump_file_path_stem: PathBuf| {
210-
let (spv_words, spirt_module_or_err) = spv_module_to_spv_words_and_spirt_module(spv_module);
213+
let (spv_words, spirt_module_or_err, _) =
214+
spv_module_to_spv_words_and_spirt_module(spv_module);
211215
std::fs::write(
212216
dump_file_path_stem.with_extension("spv"),
213217
spirv_tools::binary::from_binary(&spv_words),
@@ -474,15 +478,9 @@ pub fn link(
474478

475479
// NOTE(eddyb) SPIR-T pipeline is entirely limited to this block.
476480
{
477-
let mut per_pass_module_for_dumping = vec![];
478-
let mut after_pass = |pass, module: &spirt::Module| {
479-
if opts.dump_spirt_passes.is_some() {
480-
per_pass_module_for_dumping.push((pass, module.clone()));
481-
}
482-
};
483-
484-
let (spv_words, module_or_err) = spv_module_to_spv_words_and_spirt_module(&output);
485-
let mut module = module_or_err.map_err(|e| {
481+
let (spv_words, module_or_err, lower_from_spv_timer) =
482+
spv_module_to_spv_words_and_spirt_module(&output);
483+
let module = &mut module_or_err.map_err(|e| {
486484
let spv_path = outputs.temp_path_ext("spirt-lower-from-spv-input.spv", None);
487485

488486
let was_saved_msg =
@@ -497,122 +495,87 @@ pub fn link(
497495
.with_note(format!("input SPIR-V module {was_saved_msg}"))
498496
.emit()
499497
})?;
498+
499+
let mut dump_guard = SpirtDumpGuard {
500+
sess,
501+
linker_options: opts,
502+
outputs,
503+
disambiguated_crate_name_for_dumps,
504+
505+
module,
506+
per_pass_module_for_dumping: vec![],
507+
any_spirt_bugs: false,
508+
};
509+
let module = &mut *dump_guard.module;
510+
// FIXME(eddyb) set the name into `dump_guard` to be able to access it on panic.
511+
let before_pass = |pass| sess.timer(pass);
512+
let mut after_pass = |pass, module: &spirt::Module, timer| {
513+
drop(timer);
514+
if opts.dump_spirt_passes.is_some() {
515+
dump_guard
516+
.per_pass_module_for_dumping
517+
.push((pass, module.clone()));
518+
}
519+
};
500520
// HACK(eddyb) don't dump the unstructured state if not requested, as
501521
// after SPIR-T 0.4.0 it's extremely verbose (due to def-use hermeticity).
502522
if opts.spirt_keep_unstructured_cfg_in_dumps || !opts.structurize {
503-
after_pass("lower_from_spv", &module);
523+
after_pass("lower_from_spv", module, lower_from_spv_timer);
524+
} else {
525+
drop(lower_from_spv_timer);
504526
}
505527

506528
// NOTE(eddyb) this *must* run on unstructured CFGs, to do its job.
507529
// FIXME(eddyb) no longer relying on structurization, try porting this
508530
// to replace custom aborts in `Block`s and inject `ExitInvocation`s
509531
// after them (truncating the `Block` and/or parent region if necessary).
510532
{
511-
let _timer = sess.timer("spirt_passes::controlflow::convert_custom_aborts_to_unstructured_returns_in_entry_points");
512-
spirt_passes::controlflow::convert_custom_aborts_to_unstructured_returns_in_entry_points(opts, &mut module);
533+
let _timer = before_pass(
534+
"spirt_passes::controlflow::convert_custom_aborts_to_unstructured_returns_in_entry_points",
535+
);
536+
spirt_passes::controlflow::convert_custom_aborts_to_unstructured_returns_in_entry_points(opts, module);
513537
}
514538

515539
if opts.structurize {
516-
{
517-
let _timer = sess.timer("spirt::legalize::structurize_func_cfgs");
518-
spirt::passes::legalize::structurize_func_cfgs(&mut module);
519-
}
520-
after_pass("structurize_func_cfgs", &module);
540+
let timer = before_pass("spirt::legalize::structurize_func_cfgs");
541+
spirt::passes::legalize::structurize_func_cfgs(module);
542+
after_pass("structurize_func_cfgs", module, timer);
521543
}
522544

523545
if !opts.spirt_passes.is_empty() {
524546
// FIXME(eddyb) why does this focus on functions, it could just be module passes??
525547
spirt_passes::run_func_passes(
526-
&mut module,
548+
module,
527549
&opts.spirt_passes,
528-
|name, _module| sess.timer(name),
529-
|name, module, timer| {
530-
drop(timer);
531-
after_pass(name, module);
532-
},
550+
|name, _module| before_pass(name),
551+
|name, module, timer| after_pass(name, module, timer),
533552
);
534553
}
535554

536-
let report_diagnostics_result = {
537-
let _timer = sess.timer("spirt_passes::diagnostics::report_diagnostics");
538-
spirt_passes::diagnostics::report_diagnostics(sess, opts, &module)
539-
};
540-
let any_spirt_bugs = report_diagnostics_result
541-
.as_ref()
542-
.err()
543-
.map_or(false, |e| e.any_errors_were_spirt_bugs);
544-
545-
let mut dump_spirt_file_path = opts.dump_spirt_passes.as_ref().map(|dump_dir| {
546-
dump_dir
547-
.join(disambiguated_crate_name_for_dumps)
548-
.with_extension("spirt")
549-
});
550-
551-
// FIXME(eddyb) this won't allow seeing the individual passes, but it's
552-
// better than nothing (we could theoretically put this whole block in
553-
// a loop so that we redo everything but keeping `Module` clones?).
554-
if any_spirt_bugs && dump_spirt_file_path.is_none() {
555-
if per_pass_module_for_dumping.is_empty() {
556-
per_pass_module_for_dumping.push(("", module.clone()));
557-
}
558-
dump_spirt_file_path = Some(outputs.temp_path_ext("spirt", None));
559-
}
560-
561-
// NOTE(eddyb) this should be *before* `lift_to_spv` below,
562-
// so if that fails, the dump could be used to debug it.
563-
if let Some(dump_spirt_file_path) = &dump_spirt_file_path {
564-
for (_, module) in &mut per_pass_module_for_dumping {
565-
opts.spirt_cleanup_for_dumping(module);
566-
}
567-
568-
let plan = spirt::print::Plan::for_versions(
569-
module.cx_ref(),
570-
per_pass_module_for_dumping
571-
.iter()
572-
.map(|(pass, module)| (format!("after {pass}"), module)),
573-
);
574-
let pretty = plan.pretty_print();
575-
576-
// FIXME(eddyb) don't allocate whole `String`s here.
577-
std::fs::write(dump_spirt_file_path, pretty.to_string()).unwrap();
578-
std::fs::write(
579-
dump_spirt_file_path.with_extension("spirt.html"),
580-
pretty
581-
.render_to_html()
582-
.with_dark_mode_support()
583-
.to_html_doc(),
584-
)
585-
.unwrap();
586-
}
587-
588-
if any_spirt_bugs {
589-
let mut note = sess.dcx().struct_note("SPIR-T bugs were reported");
590-
note.help(format!(
591-
"pretty-printed SPIR-T was saved to {}.html",
592-
dump_spirt_file_path.as_ref().unwrap().display()
593-
));
594-
if opts.dump_spirt_passes.is_none() {
595-
note.help("re-run with `RUSTGPU_CODEGEN_ARGS=\"--dump-spirt-passes=$PWD\"` for more details");
596-
}
597-
note.with_note("pretty-printed SPIR-T is preferred when reporting Rust-GPU issues")
598-
.emit();
555+
{
556+
let _timer = before_pass("spirt_passes::diagnostics::report_diagnostics");
557+
spirt_passes::diagnostics::report_diagnostics(sess, opts, module).map_err(
558+
|spirt_passes::diagnostics::ReportedDiagnostics {
559+
rustc_errors_guarantee,
560+
any_errors_were_spirt_bugs,
561+
}| {
562+
dump_guard.any_spirt_bugs |= any_errors_were_spirt_bugs;
563+
rustc_errors_guarantee
564+
},
565+
)?;
599566
}
600567

601-
// NOTE(eddyb) this is late so that `--dump-spirt-passes` is processed,
602-
// even/especially when errors were reported, but lifting to SPIR-V is
603-
// skipped (since it could very well fail due to reported errors).
604-
report_diagnostics_result?;
605-
606568
// Replace our custom debuginfo instructions just before lifting to SPIR-V.
607569
{
608-
let _timer = sess.timer("spirt_passes::debuginfo::convert_custom_debuginfo_to_spv");
609-
spirt_passes::debuginfo::convert_custom_debuginfo_to_spv(&mut module);
570+
let _timer = before_pass("spirt_passes::debuginfo::convert_custom_debuginfo_to_spv");
571+
spirt_passes::debuginfo::convert_custom_debuginfo_to_spv(module);
610572
}
611573

612574
let spv_words = {
613-
let _timer = sess.timer("spirt::Module::lift_to_spv_module_emitter");
575+
let _timer = before_pass("spirt::Module::lift_to_spv_module_emitter");
614576
module.lift_to_spv_module_emitter().unwrap().words
615577
};
578+
// FIXME(eddyb) dump both SPIR-T and `spv_words` if there's an error here.
616579
output = {
617580
let _timer = sess.timer("parse-spv_words-from-spirt");
618581
let mut loader = Loader::new();
@@ -771,3 +734,91 @@ pub fn link(
771734

772735
Ok(output)
773736
}
737+
738+
/// Helper for dumping SPIR-T on drop, which allows panics to also dump,
739+
/// not just successful compilation (i.e. via `--dump-spirt-passes`).
740+
struct SpirtDumpGuard<'a> {
741+
sess: &'a Session,
742+
linker_options: &'a Options,
743+
outputs: &'a OutputFilenames,
744+
disambiguated_crate_name_for_dumps: &'a OsStr,
745+
746+
module: &'a mut spirt::Module,
747+
per_pass_module_for_dumping: Vec<(&'static str, spirt::Module)>,
748+
any_spirt_bugs: bool,
749+
}
750+
751+
impl Drop for SpirtDumpGuard<'_> {
752+
fn drop(&mut self) {
753+
self.any_spirt_bugs |= std::thread::panicking();
754+
755+
let mut dump_spirt_file_path =
756+
self.linker_options
757+
.dump_spirt_passes
758+
.as_ref()
759+
.map(|dump_dir| {
760+
dump_dir
761+
.join(self.disambiguated_crate_name_for_dumps)
762+
.with_extension("spirt")
763+
});
764+
765+
// FIXME(eddyb) this won't allow seeing the individual passes, but it's
766+
// better than nothing (theoretically the whole "SPIR-T pipeline" could
767+
// be put in a loop so that everything is redone with per-pass tracking,
768+
// but that requires keeping around e.g. the initial SPIR-V for longer,
769+
// and probably invoking the "SPIR-T pipeline" here, as looping is hard).
770+
if self.any_spirt_bugs && dump_spirt_file_path.is_none() {
771+
if self.per_pass_module_for_dumping.is_empty() {
772+
self.per_pass_module_for_dumping
773+
.push(("", self.module.clone()));
774+
}
775+
dump_spirt_file_path = Some(self.outputs.temp_path_ext("spirt", None));
776+
}
777+
778+
if let Some(dump_spirt_file_path) = &dump_spirt_file_path {
779+
for (_, module) in &mut self.per_pass_module_for_dumping {
780+
self.linker_options.spirt_cleanup_for_dumping(module);
781+
}
782+
783+
// FIXME(eddyb) catch panics during pretty-printing itself, and
784+
// tell the user to use `--dump-spirt-passes` (and resolve the
785+
// second FIXME below so it does anything) - also, that may need
786+
// quieting the panic handler, likely controlled by a `thread_local!`
787+
// (while the panic handler is global), but that would be useful
788+
// for collecting a panic message (assuming any of this is worth it).
789+
// FIXME(eddyb) when per-pass versions are available, try building
790+
// plans for individual versions, or maybe repeat `Plan::for_versions`
791+
// without the last version if it initially panicked?
792+
let plan = spirt::print::Plan::for_versions(
793+
self.module.cx_ref(),
794+
self.per_pass_module_for_dumping
795+
.iter()
796+
.map(|(pass, module)| (format!("after {pass}"), module)),
797+
);
798+
let pretty = plan.pretty_print();
799+
800+
// FIXME(eddyb) don't allocate whole `String`s here.
801+
std::fs::write(dump_spirt_file_path, pretty.to_string()).unwrap();
802+
std::fs::write(
803+
dump_spirt_file_path.with_extension("spirt.html"),
804+
pretty
805+
.render_to_html()
806+
.with_dark_mode_support()
807+
.to_html_doc(),
808+
)
809+
.unwrap();
810+
if self.any_spirt_bugs {
811+
let mut note = self.sess.dcx().struct_note("SPIR-T bugs were encountered");
812+
note.help(format!(
813+
"pretty-printed SPIR-T was saved to {}.html",
814+
dump_spirt_file_path.display()
815+
));
816+
if self.linker_options.dump_spirt_passes.is_none() {
817+
note.help("re-run with `RUSTGPU_CODEGEN_ARGS=\"--dump-spirt-passes=$PWD\"` for more details");
818+
}
819+
note.note("pretty-printed SPIR-T is preferred when reporting Rust-GPU issues");
820+
note.emit();
821+
}
822+
}
823+
}
824+
}

crates/rustc_codegen_spirv/src/linker/spirt_passes/diagnostics.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,6 @@ pub(crate) struct ReportedDiagnostics {
2222
pub any_errors_were_spirt_bugs: bool,
2323
}
2424

25-
impl From<ReportedDiagnostics> for rustc_errors::ErrorGuaranteed {
26-
fn from(r: ReportedDiagnostics) -> Self {
27-
r.rustc_errors_guarantee
28-
}
29-
}
30-
3125
pub(crate) fn report_diagnostics(
3226
sess: &Session,
3327
linker_options: &crate::linker::Options,

0 commit comments

Comments
 (0)