@@ -29,6 +29,7 @@ use rustc_data_structures::fx::FxHashMap;
2929use rustc_errors:: ErrorGuaranteed ;
3030use rustc_session:: Session ;
3131use rustc_session:: config:: OutputFilenames ;
32+ use std:: cell:: Cell ;
3233use std:: collections:: BTreeMap ;
3334use std:: ffi:: { OsStr , OsString } ;
3435use 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
763780impl 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 ( ) ) . rev ( ) {
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}
0 commit comments