@@ -29,6 +29,7 @@ use rustc_data_structures::fx::FxHashMap;
29
29
use rustc_errors:: ErrorGuaranteed ;
30
30
use rustc_session:: Session ;
31
31
use rustc_session:: config:: OutputFilenames ;
32
+ use std:: cell:: Cell ;
32
33
use std:: collections:: BTreeMap ;
33
34
use std:: ffi:: { OsStr , OsString } ;
34
35
use std:: path:: PathBuf ;
@@ -502,42 +503,53 @@ pub fn link(
502
503
503
504
module,
504
505
per_pass_module_for_dumping : vec ! [ ] ,
506
+ in_progress_pass_name : Cell :: new ( Some ( "lower_from_spv" ) ) ,
505
507
any_spirt_bugs : false ,
506
508
} ;
507
509
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| {
511
520
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
+ {
513
525
dump_guard
514
526
. per_pass_module_for_dumping
515
- . push ( ( pass , module. clone ( ) ) ) ;
527
+ . push ( ( pass_name . into ( ) , module. clone ( ) ) ) ;
516
528
}
517
529
} ;
518
530
// HACK(eddyb) don't dump the unstructured state if not requested, as
519
531
// 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
+ ) ;
525
536
526
537
// NOTE(eddyb) this *must* run on unstructured CFGs, to do its job.
527
538
// FIXME(eddyb) no longer relying on structurization, try porting this
528
539
// to replace custom aborts in `Block`s and inject `ExitInvocation`s
529
540
// after them (truncating the `Block` and/or parent region if necessary).
530
541
{
531
- let _timer = before_pass (
542
+ let timer = before_pass (
532
543
"spirt_passes::controlflow::convert_custom_aborts_to_unstructured_returns_in_entry_points" ,
533
544
) ;
534
545
spirt_passes:: controlflow:: convert_custom_aborts_to_unstructured_returns_in_entry_points ( opts, module) ;
546
+ after_pass ( None , timer) ;
535
547
}
536
548
537
549
if opts. structurize {
538
550
let timer = before_pass ( "spirt::legalize::structurize_func_cfgs" ) ;
539
551
spirt:: passes:: legalize:: structurize_func_cfgs ( module) ;
540
- after_pass ( "structurize_func_cfgs" , module, timer) ;
552
+ after_pass ( Some ( module) , timer) ;
541
553
}
542
554
543
555
if !opts. spirt_passes . is_empty ( ) {
@@ -553,11 +565,11 @@ pub fn link(
553
565
{
554
566
let timer = before_pass ( "spirt_passes::validate" ) ;
555
567
spirt_passes:: validate:: validate ( module) ;
556
- after_pass ( "validate" , module, timer) ;
568
+ after_pass ( Some ( module) , timer) ;
557
569
}
558
570
559
571
{
560
- let _timer = before_pass ( "spirt_passes::diagnostics::report_diagnostics" ) ;
572
+ let timer = before_pass ( "spirt_passes::diagnostics::report_diagnostics" ) ;
561
573
spirt_passes:: diagnostics:: report_diagnostics ( sess, opts, module) . map_err (
562
574
|spirt_passes:: diagnostics:: ReportedDiagnostics {
563
575
rustc_errors_guarantee,
@@ -567,17 +579,21 @@ pub fn link(
567
579
rustc_errors_guarantee
568
580
} ,
569
581
) ?;
582
+ after_pass ( None , timer) ;
570
583
}
571
584
572
585
// Replace our custom debuginfo instructions just before lifting to SPIR-V.
573
586
{
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" ) ;
575
588
spirt_passes:: debuginfo:: convert_custom_debuginfo_to_spv ( module) ;
589
+ after_pass ( None , timer) ;
576
590
}
577
591
578
592
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
581
597
} ;
582
598
// FIXME(eddyb) dump both SPIR-T and `spv_words` if there's an error here.
583
599
output = {
@@ -756,13 +772,26 @@ struct SpirtDumpGuard<'a> {
756
772
disambiguated_crate_name_for_dumps : & ' a OsStr ,
757
773
758
774
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 > > ,
760
777
any_spirt_bugs : bool ,
761
778
}
762
779
763
780
impl Drop for SpirtDumpGuard < ' _ > {
764
781
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
+ }
766
795
767
796
let mut dump_spirt_file_path =
768
797
self . linker_options
@@ -782,55 +811,90 @@ impl Drop for SpirtDumpGuard<'_> {
782
811
if self . any_spirt_bugs && dump_spirt_file_path. is_none ( ) {
783
812
if self . per_pass_module_for_dumping . is_empty ( ) {
784
813
self . per_pass_module_for_dumping
785
- . push ( ( "" , self . module . clone ( ) ) ) ;
814
+ . push ( ( "" . into ( ) , self . module . clone ( ) ) ) ;
786
815
}
787
816
dump_spirt_file_path = Some ( self . outputs . temp_path_for_diagnostic ( "spirt" ) ) ;
788
817
}
789
818
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
+ }
794
827
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
798
838
// 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
800
840
// 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
+ }
830
879
}
831
- note. note ( "pretty-printed SPIR-T is preferred when reporting Rust-GPU issues" ) ;
832
- note. emit ( ) ;
833
880
}
834
881
}
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
+ }
835
899
}
836
900
}
0 commit comments