@@ -97,6 +97,12 @@ pub struct FunctionCx<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> {
9797 /// A cold block is a block that is unlikely to be executed at runtime.
9898 cold_blocks : IndexVec < mir:: BasicBlock , bool > ,
9999
100+ /// A set of blocks which are live.
101+ ///
102+ /// This is constrained by the reachable block set, currently specifically targeting replacing
103+ /// UnwindAction::Cleanup(target) with UnwindAction::Continue if target is dead.
104+ live_blocks : DenseBitSet < mir:: BasicBlock > ,
105+
100106 /// The location where each MIR arg/var/tmp/ret is stored. This is
101107 /// usually an `PlaceRef` representing an alloca, but not always:
102108 /// sometimes we can skip the alloca and just store the value
@@ -176,8 +182,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
176182
177183 let mut mir = tcx. instance_mir ( instance. def ) ;
178184
179- let fn_abi = cx. fn_abi_of_instance ( instance, ty:: List :: empty ( ) ) ;
180- debug ! ( "fn_abi: {:?}" , fn_abi) ;
185+ let live_blocks = find_noop_cleanup :: < Bx > ( cx, & mir, instance) ;
181186
182187 if tcx. features ( ) . ergonomic_clones ( ) {
183188 let monomorphized_mir = instance. instantiate_mir_and_normalize_erasing_regions (
@@ -188,19 +193,24 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
188193 mir = tcx. arena . alloc ( optimize_use_clone :: < Bx > ( cx, monomorphized_mir) ) ;
189194 }
190195
196+ let fn_abi = cx. fn_abi_of_instance ( instance, ty:: List :: empty ( ) ) ;
197+ debug ! ( "fn_abi: {:?}" , fn_abi) ;
198+
191199 let debug_context = cx. create_function_debug_context ( instance, fn_abi, llfn, & mir) ;
192200
193201 let start_llbb = Bx :: append_block ( cx, llfn, "start" ) ;
194202 let mut start_bx = Bx :: build ( cx, start_llbb) ;
195203
196- if mir. basic_blocks . iter ( ) . any ( |bb| {
197- bb. is_cleanup || matches ! ( bb. terminator( ) . unwind( ) , Some ( mir:: UnwindAction :: Terminate ( _) ) )
204+ if mir:: traversal:: mono_reachable ( & mir, tcx, instance) . any ( |( bb, block) | {
205+ live_blocks. contains ( bb)
206+ && ( block. is_cleanup
207+ || matches ! ( block. terminator( ) . unwind( ) , Some ( mir:: UnwindAction :: Terminate ( _) ) ) )
198208 } ) {
199209 start_bx. set_personality_fn ( cx. eh_personality ( ) ) ;
200210 }
201211
202- let cleanup_kinds =
203- base :: wants_new_eh_instructions ( tcx . sess ) . then ( || analyze:: cleanup_kinds ( & mir) ) ;
212+ let cleanup_kinds = base :: wants_new_eh_instructions ( tcx . sess )
213+ . then ( || analyze:: cleanup_kinds ( & mir, & live_blocks ) ) ;
204214
205215 let cached_llbbs: IndexVec < mir:: BasicBlock , CachedLlbb < Bx :: BasicBlock > > =
206216 mir. basic_blocks
@@ -228,6 +238,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
228238 debug_context,
229239 per_local_var_debug_info : None ,
230240 caller_location : None ,
241+ live_blocks,
231242 } ;
232243
233244 // It may seem like we should iterate over `required_consts` to ensure they all successfully
@@ -239,7 +250,8 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
239250 fx. compute_per_local_var_debug_info ( & mut start_bx) . unzip ( ) ;
240251 fx. per_local_var_debug_info = per_local_var_debug_info;
241252
242- let traversal_order = traversal:: mono_reachable_reverse_postorder ( mir, tcx, instance) ;
253+ let mut traversal_order = traversal:: mono_reachable_reverse_postorder ( mir, tcx, instance) ;
254+ traversal_order. retain ( |bb| fx. live_blocks . contains ( * bb) ) ;
243255 let memory_locals = analyze:: non_ssa_locals ( & fx, & traversal_order) ;
244256
245257 // Allocate variable and temp allocas
@@ -371,6 +383,135 @@ fn optimize_use_clone<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
371383 mir
372384}
373385
386+ //
387+ /// Detect cases where monomorphized MIR has a cleanup block (or series of blocks) that never does
388+ /// anything, just resumes unwinding.
389+ ///
390+ /// This usually results from pre-mono MIR having a no-op drop(...) for a specific type.
391+ ///
392+ /// Returns a set with all basic blocks that should be treated as dead code (i.e., not codegen'd and
393+ /// any Cleanup branch to them should instead UnwindAction::Continue). This doesn't mutate the MIR
394+ /// because that can be quite expensive.
395+ fn find_noop_cleanup < ' a , ' tcx , Bx : BuilderMethods < ' a , ' tcx > > (
396+ cx : & ' a Bx :: CodegenCx ,
397+ mir : & Body < ' tcx > ,
398+ instance : Instance < ' tcx > ,
399+ ) -> DenseBitSet < mir:: BasicBlock > {
400+ let tcx = cx. tcx ( ) ;
401+
402+ // First we construct a bitset containing basic blocks that do something (`any_action`). These
403+ // are live code that can't be skipped at codegen time.
404+ let mut any_action = DenseBitSet :: new_empty ( mir. basic_blocks . len ( ) ) ;
405+ for ( bb, block) in mir. basic_blocks . iter_enumerated ( ) {
406+ if !block. is_cleanup {
407+ // We don't care about non-cleanup blocks.
408+ any_action. insert ( bb) ;
409+ continue ;
410+ }
411+
412+ let mut has_actions = false ;
413+ for stmt in & block. statements {
414+ match stmt. kind {
415+ mir:: StatementKind :: SetDiscriminant { .. }
416+ | mir:: StatementKind :: Deinit ( ..)
417+ | mir:: StatementKind :: StorageLive ( ..)
418+ | mir:: StatementKind :: StorageDead ( ..)
419+ | mir:: StatementKind :: Retag ( ..)
420+ | mir:: StatementKind :: Coverage ( ..)
421+ | mir:: StatementKind :: Intrinsic ( ..)
422+ | mir:: StatementKind :: Assign ( ..) => {
423+ has_actions = true ;
424+ break ;
425+ }
426+ mir:: StatementKind :: FakeRead ( ..)
427+ | mir:: StatementKind :: PlaceMention ( ..)
428+ | mir:: StatementKind :: AscribeUserType ( ..)
429+ | mir:: StatementKind :: ConstEvalCounter
430+ | mir:: StatementKind :: Nop
431+ | mir:: StatementKind :: BackwardIncompatibleDropHint { .. } => { }
432+ }
433+ }
434+ match block. terminator ( ) . kind {
435+ mir:: TerminatorKind :: Goto { .. }
436+ | mir:: TerminatorKind :: SwitchInt { .. }
437+ | mir:: TerminatorKind :: UnwindResume
438+ | mir:: TerminatorKind :: Unreachable => { }
439+
440+ mir:: TerminatorKind :: Call { .. }
441+ | mir:: TerminatorKind :: Assert { .. }
442+ | mir:: TerminatorKind :: Yield { .. }
443+ | mir:: TerminatorKind :: InlineAsm { .. }
444+ | mir:: TerminatorKind :: CoroutineDrop
445+ | mir:: TerminatorKind :: TailCall { .. }
446+ | mir:: TerminatorKind :: UnwindTerminate ( ..)
447+ | mir:: TerminatorKind :: Return => has_actions = true ,
448+
449+ mir:: TerminatorKind :: Drop { place, .. } => {
450+ let ty = place. ty ( mir, tcx) . ty ;
451+ debug ! ( "monomorphize: instance={:?}" , instance) ;
452+ let ty = instance. instantiate_mir_and_normalize_erasing_regions (
453+ tcx,
454+ cx. typing_env ( ) ,
455+ ty:: EarlyBinder :: bind ( ty) ,
456+ ) ;
457+ let drop_fn = Instance :: resolve_drop_in_place ( tcx, ty) ;
458+ if let ty:: InstanceKind :: DropGlue ( _, None ) = drop_fn. def {
459+ // no need to drop anything
460+ } else {
461+ has_actions = true ;
462+ }
463+ }
464+
465+ mir:: TerminatorKind :: FalseEdge { .. } | mir:: TerminatorKind :: FalseUnwind { .. } => {
466+ bug ! ( "not present in optimized mir" )
467+ }
468+ }
469+
470+ if has_actions {
471+ any_action. insert ( bb) ;
472+ }
473+ }
474+
475+ let mut visited = DenseBitSet :: new_empty ( mir. basic_blocks . len ( ) ) ;
476+ let mut stack = vec ! [ mir:: START_BLOCK ] ;
477+ while let Some ( next) = stack. pop ( ) {
478+ // Mark blocks live as we go.
479+ if !visited. insert ( next) {
480+ continue ;
481+ }
482+
483+ // To find successors, we consider whether to skip the target of the
484+ // UnwindAction::Cleanup(...) edge. If it hits blocks which do something (are in
485+ // any_action), then the edge must be kept and those blocks visited. Otherwise the
486+ // blocks can be considered dead.
487+ if let Some ( mir:: UnwindAction :: Cleanup ( cleanup_target) ) =
488+ mir. basic_blocks [ next] . terminator ( ) . unwind ( )
489+ {
490+ let found_action = mir:: traversal:: Postorder :: new (
491+ & mir. basic_blocks ,
492+ * cleanup_target,
493+ Some ( ( tcx, instance) ) ,
494+ )
495+ . any ( |bb| any_action. contains ( bb) ) ;
496+ if !found_action {
497+ // Do not traverse into the cleanup target if no action is found.
498+ stack. extend (
499+ mir. basic_blocks [ next]
500+ . mono_successors ( tcx, instance)
501+ . filter ( |v| v != cleanup_target) ,
502+ ) ;
503+
504+ // Avoid hitting the extend below.
505+ continue ;
506+ }
507+ }
508+
509+ stack. extend ( mir. basic_blocks [ next] . mono_successors ( tcx, instance) ) ;
510+ }
511+
512+ visited
513+ }
514+
374515/// Produces, for each argument, a `Value` pointing at the
375516/// argument's value. As arguments are places, these are always
376517/// indirect.
0 commit comments