@@ -97,6 +97,12 @@ pub struct FunctionCx<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> {
97
97
/// A cold block is a block that is unlikely to be executed at runtime.
98
98
cold_blocks : IndexVec < mir:: BasicBlock , bool > ,
99
99
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
+
100
106
/// The location where each MIR arg/var/tmp/ret is stored. This is
101
107
/// usually an `PlaceRef` representing an alloca, but not always:
102
108
/// 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>>(
176
182
177
183
let mut mir = tcx. instance_mir ( instance. def ) ;
178
184
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) ;
181
186
182
187
if tcx. features ( ) . ergonomic_clones ( ) {
183
188
let monomorphized_mir = instance. instantiate_mir_and_normalize_erasing_regions (
@@ -188,19 +193,24 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
188
193
mir = tcx. arena . alloc ( optimize_use_clone :: < Bx > ( cx, monomorphized_mir) ) ;
189
194
}
190
195
196
+ let fn_abi = cx. fn_abi_of_instance ( instance, ty:: List :: empty ( ) ) ;
197
+ debug ! ( "fn_abi: {:?}" , fn_abi) ;
198
+
191
199
let debug_context = cx. create_function_debug_context ( instance, fn_abi, llfn, & mir) ;
192
200
193
201
let start_llbb = Bx :: append_block ( cx, llfn, "start" ) ;
194
202
let mut start_bx = Bx :: build ( cx, start_llbb) ;
195
203
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 ( _) ) ) )
198
208
} ) {
199
209
start_bx. set_personality_fn ( cx. eh_personality ( ) ) ;
200
210
}
201
211
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 ) ) ;
204
214
205
215
let cached_llbbs: IndexVec < mir:: BasicBlock , CachedLlbb < Bx :: BasicBlock > > =
206
216
mir. basic_blocks
@@ -228,6 +238,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
228
238
debug_context,
229
239
per_local_var_debug_info : None ,
230
240
caller_location : None ,
241
+ live_blocks,
231
242
} ;
232
243
233
244
// 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>>(
239
250
fx. compute_per_local_var_debug_info ( & mut start_bx) . unzip ( ) ;
240
251
fx. per_local_var_debug_info = per_local_var_debug_info;
241
252
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) ) ;
243
255
let memory_locals = analyze:: non_ssa_locals ( & fx, & traversal_order) ;
244
256
245
257
// Allocate variable and temp allocas
@@ -371,6 +383,135 @@ fn optimize_use_clone<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
371
383
mir
372
384
}
373
385
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
+
374
515
/// Produces, for each argument, a `Value` pointing at the
375
516
/// argument's value. As arguments are places, these are always
376
517
/// indirect.
0 commit comments