@@ -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+ } 
0 commit comments