@@ -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