Skip to content

Commit 676eed3

Browse files
Remove no-op cleanups as post-mono MIR opt
On cargo this cuts ~5% of the LLVM IR lines we generate (measured with -Cno-prepopulate-passes).
1 parent d41e12f commit 676eed3

File tree

4 files changed

+172
-13
lines changed

4 files changed

+172
-13
lines changed

compiler/rustc_codegen_ssa/src/mir/analyze.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -279,10 +279,14 @@ impl CleanupKind {
279279
/// MSVC requires unwinding code to be split to a tree of *funclets*, where each funclet can only
280280
/// branch to itself or to its parent. Luckily, the code we generates matches this pattern.
281281
/// Recover that structure in an analyze pass.
282-
pub(crate) fn cleanup_kinds(mir: &mir::Body<'_>) -> IndexVec<mir::BasicBlock, CleanupKind> {
282+
pub(crate) fn cleanup_kinds(
283+
mir: &mir::Body<'_>,
284+
live_blocks: &DenseBitSet<mir::BasicBlock>,
285+
) -> IndexVec<mir::BasicBlock, CleanupKind> {
283286
fn discover_masters<'tcx>(
284287
result: &mut IndexSlice<mir::BasicBlock, CleanupKind>,
285288
mir: &mir::Body<'tcx>,
289+
live_blocks: &DenseBitSet<mir::BasicBlock>,
286290
) {
287291
for (bb, data) in mir.basic_blocks.iter_enumerated() {
288292
match data.terminator().kind {
@@ -301,7 +305,9 @@ pub(crate) fn cleanup_kinds(mir: &mir::Body<'_>) -> IndexVec<mir::BasicBlock, Cl
301305
| TerminatorKind::InlineAsm { unwind, .. }
302306
| TerminatorKind::Assert { unwind, .. }
303307
| TerminatorKind::Drop { unwind, .. } => {
304-
if let mir::UnwindAction::Cleanup(unwind) = unwind {
308+
if let mir::UnwindAction::Cleanup(unwind) = unwind
309+
&& live_blocks.contains(unwind)
310+
{
305311
debug!(
306312
"cleanup_kinds: {:?}/{:?} registering {:?} as funclet",
307313
bb, data, unwind
@@ -382,7 +388,7 @@ pub(crate) fn cleanup_kinds(mir: &mir::Body<'_>) -> IndexVec<mir::BasicBlock, Cl
382388

383389
let mut result = IndexVec::from_elem(CleanupKind::NotCleanup, &mir.basic_blocks);
384390

385-
discover_masters(&mut result, mir);
391+
discover_masters(&mut result, mir, &live_blocks);
386392
propagate(&mut result, mir);
387393
debug!("cleanup_kinds: result={:?}", result);
388394
result

compiler/rustc_codegen_ssa/src/mir/block.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,13 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
200200
}
201201

202202
let unwind_block = match unwind {
203-
mir::UnwindAction::Cleanup(cleanup) => Some(self.llbb_with_cleanup(fx, cleanup)),
203+
mir::UnwindAction::Cleanup(cleanup) => {
204+
if fx.live_blocks.contains(cleanup) {
205+
Some(self.llbb_with_cleanup(fx, cleanup))
206+
} else {
207+
None
208+
}
209+
}
204210
mir::UnwindAction::Continue => None,
205211
mir::UnwindAction::Unreachable => None,
206212
mir::UnwindAction::Terminate(reason) => {
@@ -286,7 +292,13 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
286292
mergeable_succ: bool,
287293
) -> MergingSucc {
288294
let unwind_target = match unwind {
289-
mir::UnwindAction::Cleanup(cleanup) => Some(self.llbb_with_cleanup(fx, cleanup)),
295+
mir::UnwindAction::Cleanup(cleanup) => {
296+
if fx.live_blocks.contains(cleanup) {
297+
Some(self.llbb_with_cleanup(fx, cleanup))
298+
} else {
299+
None
300+
}
301+
}
290302
mir::UnwindAction::Terminate(reason) => Some(fx.terminate_block(reason)),
291303
mir::UnwindAction::Continue => None,
292304
mir::UnwindAction::Unreachable => None,

compiler/rustc_codegen_ssa/src/mir/mod.rs

Lines changed: 148 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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.

tests/codegen/mem-replace-big-type.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub fn replace_big(dst: &mut Big, src: Big) -> Big {
2525
// CHECK-NOT: call void @llvm.memcpy
2626

2727
// For a large type, we expect exactly three `memcpy`s
28-
// CHECK-LABEL: define internal void @{{.+}}mem{{.+}}replace{{.+}}(ptr
28+
// CHECK-LABEL: define void @{{.+}}mem{{.+}}replace{{.+}}(ptr
2929
// CHECK-SAME: sret([56 x i8]){{.+}}[[RESULT:%.+]], ptr{{.+}}%dest, ptr{{.+}}%src)
3030
// CHECK-NOT: call void @llvm.memcpy
3131
// CHECK: call void @llvm.memcpy.{{.+}}(ptr align 8 [[RESULT]], ptr align 8 %dest, i{{.*}} 56, i1 false)

0 commit comments

Comments
 (0)