@@ -197,17 +197,21 @@ pub fn link(
197
197
} ;
198
198
199
199
// 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" ) ;
201
201
let cx = std:: rc:: Rc :: new ( spirt:: Context :: new ( ) ) ;
202
202
crate :: custom_insts:: register_to_spirt_context ( & cx) ;
203
203
(
204
204
spv_words,
205
205
spirt:: Module :: lower_from_spv_bytes ( cx, spv_bytes) ,
206
+ // HACK(eddyb) this is only returned for `SpirtDumpGuard`.
207
+ lower_from_spv_timer,
206
208
)
207
209
} ;
208
210
211
+ // FIXME(eddyb) deduplicate with `SpirtDumpGuard`.
209
212
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) ;
211
215
std:: fs:: write (
212
216
dump_file_path_stem. with_extension ( "spv" ) ,
213
217
spirv_tools:: binary:: from_binary ( & spv_words) ,
@@ -474,15 +478,9 @@ pub fn link(
474
478
475
479
// NOTE(eddyb) SPIR-T pipeline is entirely limited to this block.
476
480
{
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| {
486
484
let spv_path = outputs. temp_path_ext ( "spirt-lower-from-spv-input.spv" , None ) ;
487
485
488
486
let was_saved_msg =
@@ -497,122 +495,87 @@ pub fn link(
497
495
. with_note ( format ! ( "input SPIR-V module {was_saved_msg}" ) )
498
496
. emit ( )
499
497
} ) ?;
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
+ } ;
500
520
// HACK(eddyb) don't dump the unstructured state if not requested, as
501
521
// after SPIR-T 0.4.0 it's extremely verbose (due to def-use hermeticity).
502
522
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) ;
504
526
}
505
527
506
528
// NOTE(eddyb) this *must* run on unstructured CFGs, to do its job.
507
529
// FIXME(eddyb) no longer relying on structurization, try porting this
508
530
// to replace custom aborts in `Block`s and inject `ExitInvocation`s
509
531
// after them (truncating the `Block` and/or parent region if necessary).
510
532
{
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) ;
513
537
}
514
538
515
539
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) ;
521
543
}
522
544
523
545
if !opts. spirt_passes . is_empty ( ) {
524
546
// FIXME(eddyb) why does this focus on functions, it could just be module passes??
525
547
spirt_passes:: run_func_passes (
526
- & mut module,
548
+ module,
527
549
& 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) ,
533
552
) ;
534
553
}
535
554
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
+ ) ?;
599
566
}
600
567
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
-
606
568
// Replace our custom debuginfo instructions just before lifting to SPIR-V.
607
569
{
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) ;
610
572
}
611
573
612
574
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" ) ;
614
576
module. lift_to_spv_module_emitter ( ) . unwrap ( ) . words
615
577
} ;
578
+ // FIXME(eddyb) dump both SPIR-T and `spv_words` if there's an error here.
616
579
output = {
617
580
let _timer = sess. timer ( "parse-spv_words-from-spirt" ) ;
618
581
let mut loader = Loader :: new ( ) ;
@@ -771,3 +734,91 @@ pub fn link(
771
734
772
735
Ok ( output)
773
736
}
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