Skip to content

Commit 088e117

Browse files
committed
Try dumping, when a SPIR-T pass panics, its in-progress spirt::Module state.
1 parent cf59e54 commit 088e117

File tree

2 files changed

+128
-64
lines changed
  • crates/rustc_codegen_spirv/src/linker

2 files changed

+128
-64
lines changed

crates/rustc_codegen_spirv/src/linker/mod.rs

Lines changed: 123 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use rustc_data_structures::fx::FxHashMap;
2929
use rustc_errors::ErrorGuaranteed;
3030
use rustc_session::Session;
3131
use rustc_session::config::OutputFilenames;
32+
use std::cell::Cell;
3233
use std::collections::BTreeMap;
3334
use std::ffi::{OsStr, OsString};
3435
use std::path::PathBuf;
@@ -502,42 +503,53 @@ pub fn link(
502503

503504
module,
504505
per_pass_module_for_dumping: vec![],
506+
in_progress_pass_name: Cell::new(Some("lower_from_spv")),
505507
any_spirt_bugs: false,
506508
};
507509
let module = &mut *dump_guard.module;
508-
// FIXME(eddyb) set the name into `dump_guard` to be able to access it on panic.
509-
let before_pass = |pass| sess.timer(pass);
510-
let mut after_pass = |pass, module: &spirt::Module, timer| {
510+
// FIXME(eddyb) consider returning a `Drop`-implementing type instead?
511+
let before_pass = |pass_name| {
512+
let outer_pass_name = dump_guard.in_progress_pass_name.replace(Some(pass_name));
513+
514+
// FIXME(eddyb) could it make sense to allow these to nest?
515+
assert_eq!(outer_pass_name, None);
516+
517+
sess.timer(pass_name)
518+
};
519+
let mut after_pass = |module: Option<&spirt::Module>, timer| {
511520
drop(timer);
512-
if opts.dump_spirt_passes.is_some() {
521+
let pass_name = dump_guard.in_progress_pass_name.take().unwrap();
522+
if let Some(module) = module
523+
&& opts.dump_spirt_passes.is_some()
524+
{
513525
dump_guard
514526
.per_pass_module_for_dumping
515-
.push((pass, module.clone()));
527+
.push((pass_name.into(), module.clone()));
516528
}
517529
};
518530
// HACK(eddyb) don't dump the unstructured state if not requested, as
519531
// after SPIR-T 0.4.0 it's extremely verbose (due to def-use hermeticity).
520-
if opts.spirt_keep_unstructured_cfg_in_dumps || !opts.structurize {
521-
after_pass("lower_from_spv", module, lower_from_spv_timer);
522-
} else {
523-
drop(lower_from_spv_timer);
524-
}
532+
after_pass(
533+
(opts.spirt_keep_unstructured_cfg_in_dumps || !opts.structurize).then_some(&*module),
534+
lower_from_spv_timer,
535+
);
525536

526537
// NOTE(eddyb) this *must* run on unstructured CFGs, to do its job.
527538
// FIXME(eddyb) no longer relying on structurization, try porting this
528539
// to replace custom aborts in `Block`s and inject `ExitInvocation`s
529540
// after them (truncating the `Block` and/or parent region if necessary).
530541
{
531-
let _timer = before_pass(
542+
let timer = before_pass(
532543
"spirt_passes::controlflow::convert_custom_aborts_to_unstructured_returns_in_entry_points",
533544
);
534545
spirt_passes::controlflow::convert_custom_aborts_to_unstructured_returns_in_entry_points(opts, module);
546+
after_pass(None, timer);
535547
}
536548

537549
if opts.structurize {
538550
let timer = before_pass("spirt::legalize::structurize_func_cfgs");
539551
spirt::passes::legalize::structurize_func_cfgs(module);
540-
after_pass("structurize_func_cfgs", module, timer);
552+
after_pass(Some(module), timer);
541553
}
542554

543555
if !opts.spirt_passes.is_empty() {
@@ -553,11 +565,11 @@ pub fn link(
553565
{
554566
let timer = before_pass("spirt_passes::validate");
555567
spirt_passes::validate::validate(module);
556-
after_pass("validate", module, timer);
568+
after_pass(Some(module), timer);
557569
}
558570

559571
{
560-
let _timer = before_pass("spirt_passes::diagnostics::report_diagnostics");
572+
let timer = before_pass("spirt_passes::diagnostics::report_diagnostics");
561573
spirt_passes::diagnostics::report_diagnostics(sess, opts, module).map_err(
562574
|spirt_passes::diagnostics::ReportedDiagnostics {
563575
rustc_errors_guarantee,
@@ -567,17 +579,21 @@ pub fn link(
567579
rustc_errors_guarantee
568580
},
569581
)?;
582+
after_pass(None, timer);
570583
}
571584

572585
// Replace our custom debuginfo instructions just before lifting to SPIR-V.
573586
{
574-
let _timer = before_pass("spirt_passes::debuginfo::convert_custom_debuginfo_to_spv");
587+
let timer = before_pass("spirt_passes::debuginfo::convert_custom_debuginfo_to_spv");
575588
spirt_passes::debuginfo::convert_custom_debuginfo_to_spv(module);
589+
after_pass(None, timer);
576590
}
577591

578592
let spv_words = {
579-
let _timer = before_pass("spirt::Module::lift_to_spv_module_emitter");
580-
module.lift_to_spv_module_emitter().unwrap().words
593+
let timer = before_pass("spirt::Module::lift_to_spv_module_emitter");
594+
let spv_words = module.lift_to_spv_module_emitter().unwrap().words;
595+
after_pass(None, timer);
596+
spv_words
581597
};
582598
// FIXME(eddyb) dump both SPIR-T and `spv_words` if there's an error here.
583599
output = {
@@ -756,13 +772,26 @@ struct SpirtDumpGuard<'a> {
756772
disambiguated_crate_name_for_dumps: &'a OsStr,
757773

758774
module: &'a mut spirt::Module,
759-
per_pass_module_for_dumping: Vec<(&'static str, spirt::Module)>,
775+
per_pass_module_for_dumping: Vec<(Cow<'static, str>, spirt::Module)>,
776+
in_progress_pass_name: Cell<Option<&'static str>>,
760777
any_spirt_bugs: bool,
761778
}
762779

763780
impl Drop for SpirtDumpGuard<'_> {
764781
fn drop(&mut self) {
765-
self.any_spirt_bugs |= std::thread::panicking();
782+
if std::thread::panicking() {
783+
self.any_spirt_bugs = true;
784+
785+
// HACK(eddyb) the active pass panicked, make sure to include its
786+
// (potentially corrupted) state, which will hopefully be printed
787+
// later below (with protection against panicking during printing).
788+
if let Some(pass_name) = self.in_progress_pass_name.get() {
789+
self.per_pass_module_for_dumping.push((
790+
format!("{pass_name} [PANICKED]").into(),
791+
self.module.clone(),
792+
));
793+
}
794+
}
766795

767796
let mut dump_spirt_file_path =
768797
self.linker_options
@@ -782,55 +811,90 @@ impl Drop for SpirtDumpGuard<'_> {
782811
if self.any_spirt_bugs && dump_spirt_file_path.is_none() {
783812
if self.per_pass_module_for_dumping.is_empty() {
784813
self.per_pass_module_for_dumping
785-
.push(("", self.module.clone()));
814+
.push(("".into(), self.module.clone()));
786815
}
787816
dump_spirt_file_path = Some(self.outputs.temp_path_for_diagnostic("spirt"));
788817
}
789818

790-
if let Some(dump_spirt_file_path) = &dump_spirt_file_path {
791-
for (_, module) in &mut self.per_pass_module_for_dumping {
792-
self.linker_options.spirt_cleanup_for_dumping(module);
793-
}
819+
let Some(dump_spirt_file_path) = &dump_spirt_file_path else {
820+
return;
821+
};
822+
823+
for (_, module) in &mut self.per_pass_module_for_dumping {
824+
// FIXME(eddyb) consider catching panics in this?
825+
self.linker_options.spirt_cleanup_for_dumping(module);
826+
}
794827

795-
// FIXME(eddyb) catch panics during pretty-printing itself, and
796-
// tell the user to use `--dump-spirt-passes` (and resolve the
797-
// second FIXME below so it does anything) - also, that may need
828+
let cx = self.module.cx();
829+
let versions = self
830+
.per_pass_module_for_dumping
831+
.iter()
832+
.map(|(pass_name, module)| (format!("after {pass_name}"), module));
833+
834+
let mut panicked_printing_after_passes = None;
835+
for truncate_version_count in 1..=versions.len() {
836+
// FIXME(eddyb) tell the user to use `--dump-spirt-passes` if that
837+
// wasn't active but a panic happens - on top of that, it may need
798838
// quieting the panic handler, likely controlled by a `thread_local!`
799-
// (while the panic handler is global), but that would be useful
839+
// (while the panic handler is global), and that would also be useful
800840
// for collecting a panic message (assuming any of this is worth it).
801-
// FIXME(eddyb) when per-pass versions are available, try building
802-
// plans for individual versions, or maybe repeat `Plan::for_versions`
803-
// without the last version if it initially panicked?
804-
let plan = spirt::print::Plan::for_versions(
805-
self.module.cx_ref(),
806-
self.per_pass_module_for_dumping
807-
.iter()
808-
.map(|(pass, module)| (format!("after {pass}"), module)),
809-
);
810-
let pretty = plan.pretty_print();
811-
812-
// FIXME(eddyb) don't allocate whole `String`s here.
813-
std::fs::write(dump_spirt_file_path, pretty.to_string()).unwrap();
814-
std::fs::write(
815-
dump_spirt_file_path.with_extension("spirt.html"),
816-
pretty
817-
.render_to_html()
818-
.with_dark_mode_support()
819-
.to_html_doc(),
820-
)
821-
.unwrap();
822-
if self.any_spirt_bugs {
823-
let mut note = self.sess.dcx().struct_note("SPIR-T bugs were encountered");
824-
note.help(format!(
825-
"pretty-printed SPIR-T was saved to {}.html",
826-
dump_spirt_file_path.display()
827-
));
828-
if self.linker_options.dump_spirt_passes.is_none() {
829-
note.help("re-run with `RUSTGPU_CODEGEN_ARGS=\"--dump-spirt-passes=$PWD\"` for more details");
841+
// HACK(eddyb) for now, keeping the panic handler works out, as the
842+
// panic messages would at least be seen by the user.
843+
let printed_or_panicked =
844+
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
845+
let pretty = spirt::print::Plan::for_versions(
846+
&cx,
847+
versions.clone().take(truncate_version_count),
848+
)
849+
.pretty_print();
850+
851+
// FIXME(eddyb) don't allocate whole `String`s here.
852+
std::fs::write(dump_spirt_file_path, pretty.to_string()).unwrap();
853+
std::fs::write(
854+
dump_spirt_file_path.with_extension("spirt.html"),
855+
pretty
856+
.render_to_html()
857+
.with_dark_mode_support()
858+
.to_html_doc(),
859+
)
860+
.unwrap();
861+
}));
862+
match printed_or_panicked {
863+
Ok(()) => {
864+
if truncate_version_count != versions.len() {
865+
panicked_printing_after_passes = Some(
866+
self.per_pass_module_for_dumping[..truncate_version_count]
867+
.iter()
868+
.map(|(pass_name, _)| format!("`{pass_name}`"))
869+
.collect::<Vec<_>>()
870+
.join(", "),
871+
);
872+
}
873+
break;
874+
}
875+
Err(panic) => {
876+
if truncate_version_count == 1 {
877+
std::panic::resume_unwind(panic);
878+
}
830879
}
831-
note.note("pretty-printed SPIR-T is preferred when reporting Rust-GPU issues");
832-
note.emit();
833880
}
834881
}
882+
if self.any_spirt_bugs || panicked_printing_after_passes.is_some() {
883+
let mut note = self.sess.dcx().struct_note("SPIR-T bugs were encountered");
884+
if let Some(pass_names) = panicked_printing_after_passes {
885+
note.warn(format!(
886+
"SPIR-T pretty-printing panicked after: {pass_names}"
887+
));
888+
}
889+
note.help(format!(
890+
"pretty-printed SPIR-T was saved to {}.html",
891+
dump_spirt_file_path.display()
892+
));
893+
if self.linker_options.dump_spirt_passes.is_none() {
894+
note.help("re-run with `RUSTGPU_CODEGEN_ARGS=\"--dump-spirt-passes=$PWD\"` for more details");
895+
}
896+
note.note("pretty-printed SPIR-T is preferred when reporting Rust-GPU issues");
897+
note.emit();
898+
}
835899
}
836900
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ pub(super) fn run_func_passes<P>(
120120
passes: &[impl AsRef<str>],
121121
// FIXME(eddyb) this is a very poor approximation of a "profiler" abstraction.
122122
mut before_pass: impl FnMut(&'static str, &Module) -> P,
123-
mut after_pass: impl FnMut(&'static str, &Module, P),
123+
mut after_pass: impl FnMut(Option<&Module>, P),
124124
) {
125125
let cx = &module.cx();
126126

@@ -156,15 +156,15 @@ pub(super) fn run_func_passes<P>(
156156

157157
let profiler = before_pass("qptr::lower_from_spv_ptrs", module);
158158
spirt::passes::qptr::lower_from_spv_ptrs(module, layout_config);
159-
after_pass("qptr::lower_from_spv_ptrs", module, profiler);
159+
after_pass(Some(module), profiler);
160160

161161
let profiler = before_pass("qptr::analyze_uses", module);
162162
spirt::passes::qptr::analyze_uses(module, layout_config);
163-
after_pass("qptr::analyze_uses", module, profiler);
163+
after_pass(Some(module), profiler);
164164

165165
let profiler = before_pass("qptr::lift_to_spv_ptrs", module);
166166
spirt::passes::qptr::lift_to_spv_ptrs(module, layout_config);
167-
after_pass("qptr::lift_to_spv_ptrs", module, profiler);
167+
after_pass(Some(module), profiler);
168168

169169
continue;
170170
}
@@ -187,7 +187,7 @@ pub(super) fn run_func_passes<P>(
187187
remove_unused_values_in_func(cx, func_def_body);
188188
}
189189
}
190-
after_pass(full_name, module, profiler);
190+
after_pass(Some(module), profiler);
191191
}
192192
}
193193

0 commit comments

Comments
 (0)