Skip to content

Commit d093acb

Browse files
committed
Also allow inlining drop shims
1 parent edc3841 commit d093acb

22 files changed

+828
-351
lines changed

compiler/rustc_middle/src/mir/visit.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,9 +1328,9 @@ pub enum NonMutatingUseContext {
13281328
pub enum MutatingUseContext {
13291329
/// Appears as LHS of an assignment.
13301330
Store,
1331-
/// Appears on `SetDiscriminant`
1331+
/// Appears on [`StatementKind::SetDiscriminant`]
13321332
SetDiscriminant,
1333-
/// Appears on `Deinit`
1333+
/// Appears on [`StatementKind::Deinit`]
13341334
Deinit,
13351335
/// Output operand of an inline assembly block.
13361336
AsmOutput,

compiler/rustc_mir_transform/src/inline.rs

Lines changed: 129 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::ops::{Range, RangeFrom};
66

77
use rustc_abi::{ExternAbi, FieldIdx};
88
use rustc_attr_data_structures::{InlineAttr, OptimizeAttr};
9+
use rustc_hir::LangItem;
910
use rustc_hir::def::DefKind;
1011
use rustc_hir::def_id::DefId;
1112
use rustc_index::Idx;
@@ -116,6 +117,9 @@ trait Inliner<'tcx> {
116117
/// Has the caller body been changed?
117118
fn changed(self) -> bool;
118119

120+
/// Whether to also attempt to inline `Drop` terminators (not just `Call`s)
121+
fn consider_drops(&self) -> bool;
122+
119123
/// Should inlining happen for a given callee?
120124
fn should_inline_for_callee(&self, def_id: DefId) -> bool;
121125

@@ -187,6 +191,10 @@ impl<'tcx> Inliner<'tcx> for ForceInliner<'tcx> {
187191
self.changed
188192
}
189193

194+
fn consider_drops(&self) -> bool {
195+
false
196+
}
197+
190198
fn should_inline_for_callee(&self, def_id: DefId) -> bool {
191199
ForceInline::should_run_pass_for_callee(self.tcx(), def_id)
192200
}
@@ -272,6 +280,7 @@ struct NormalInliner<'tcx> {
272280
typing_env: ty::TypingEnv<'tcx>,
273281
/// `DefId` of caller.
274282
def_id: DefId,
283+
caller_is_coroutine: bool,
275284
/// Stack of inlined instances.
276285
/// We only check the `DefId` and not the args because we want to
277286
/// avoid inlining cases of polymorphic recursion.
@@ -304,6 +313,7 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
304313
tcx,
305314
typing_env,
306315
def_id,
316+
caller_is_coroutine: tcx.is_coroutine(def_id),
307317
history: Vec::new(),
308318
top_down_counter: 0,
309319
changed: false,
@@ -334,6 +344,10 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
334344
self.changed
335345
}
336346

347+
fn consider_drops(&self) -> bool {
348+
!self.caller_is_coroutine
349+
}
350+
337351
fn should_inline_for_callee(&self, _: DefId) -> bool {
338352
true
339353
}
@@ -549,50 +563,77 @@ fn resolve_callsite<'tcx, I: Inliner<'tcx>>(
549563
bb_data: &BasicBlockData<'tcx>,
550564
) -> Option<CallSite<'tcx>> {
551565
let tcx = inliner.tcx();
552-
// Only consider direct calls to functions
553566
let terminator = bb_data.terminator();
554567

555568
// FIXME(explicit_tail_calls): figure out if we can inline tail calls
556-
if let TerminatorKind::Call { ref func, fn_span, .. } = terminator.kind {
557-
let func_ty = func.ty(caller_body, tcx);
558-
if let ty::FnDef(def_id, args) = *func_ty.kind() {
559-
if !inliner.should_inline_for_callee(def_id) {
560-
debug!("not enabled");
561-
return None;
562-
}
569+
let (def_id, args, fn_span) = match &terminator.kind {
570+
TerminatorKind::Call { func, fn_span, .. } => {
571+
let func_ty = func.ty(caller_body, tcx);
572+
if let ty::FnDef(def_id, args) = *func_ty.kind() {
573+
if !inliner.should_inline_for_callee(def_id) {
574+
debug!("not enabled");
575+
return None;
576+
}
563577

564-
// To resolve an instance its args have to be fully normalized.
565-
let args = tcx.try_normalize_erasing_regions(inliner.typing_env(), args).ok()?;
566-
let callee =
567-
Instance::try_resolve(tcx, inliner.typing_env(), def_id, args).ok().flatten()?;
578+
// Allow RemoveUnneededDrops to handle these, rather than inlining,
579+
// since it doesn't add the extra locals nor the metadata.
580+
if inliner.consider_drops()
581+
&& tcx.is_lang_item(def_id, LangItem::DropInPlace)
582+
&& let drop_ty = args.type_at(0)
583+
&& !drop_ty.needs_drop(tcx, inliner.typing_env())
584+
{
585+
return None;
586+
}
568587

569-
if let InstanceKind::Virtual(..) | InstanceKind::Intrinsic(_) = callee.def {
588+
(def_id, args, *fn_span)
589+
} else {
570590
return None;
571591
}
572-
573-
if inliner.history().contains(&callee.def_id()) {
592+
}
593+
TerminatorKind::Drop { place, .. } if inliner.consider_drops() => {
594+
let drop_ty = place.ty(&caller_body.local_decls, tcx).ty;
595+
if !drop_ty.needs_drop(tcx, inliner.typing_env())
596+
// FIXME: Box's shim looks like it inlines fine, but it's quite
597+
// a bit of MIR right now, so is currently skipped to avoid churn.
598+
|| drop_ty.is_box()
599+
{
574600
return None;
575601
}
576602

577-
let fn_sig = tcx.fn_sig(def_id).instantiate(tcx, args);
603+
let drop_def_id =
604+
tcx.require_lang_item(LangItem::DropInPlace, terminator.source_info.span);
605+
let args = tcx.mk_args(&[drop_ty.into()]);
606+
(drop_def_id, args, rustc_span::DUMMY_SP)
607+
}
608+
_ => return None,
609+
};
578610

579-
// Additionally, check that the body that we're inlining actually agrees
580-
// with the ABI of the trait that the item comes from.
581-
if let InstanceKind::Item(instance_def_id) = callee.def
582-
&& tcx.def_kind(instance_def_id) == DefKind::AssocFn
583-
&& let instance_fn_sig = tcx.fn_sig(instance_def_id).skip_binder()
584-
&& instance_fn_sig.abi() != fn_sig.abi()
585-
{
586-
return None;
587-
}
611+
// To resolve an instance its args have to be fully normalized.
612+
let args = tcx.try_normalize_erasing_regions(inliner.typing_env(), args).ok()?;
613+
let callee = Instance::try_resolve(tcx, inliner.typing_env(), def_id, args).ok().flatten()?;
588614

589-
let source_info = SourceInfo { span: fn_span, ..terminator.source_info };
615+
if let InstanceKind::Virtual(..) | InstanceKind::Intrinsic(_) = callee.def {
616+
return None;
617+
}
590618

591-
return Some(CallSite { callee, fn_sig, block: bb, source_info });
592-
}
619+
if inliner.history().contains(&callee.def_id()) {
620+
return None;
621+
}
622+
623+
let fn_sig = tcx.fn_sig(def_id).instantiate(tcx, args);
624+
625+
// Additionally, check that the body that we're inlining actually agrees
626+
// with the ABI of the trait that the item comes from.
627+
if let InstanceKind::Item(instance_def_id) = callee.def
628+
&& tcx.def_kind(instance_def_id) == DefKind::AssocFn
629+
&& let instance_fn_sig = tcx.fn_sig(instance_def_id).skip_binder()
630+
&& instance_fn_sig.abi() != fn_sig.abi()
631+
{
632+
return None;
593633
}
594634

595-
None
635+
let source_info = SourceInfo { span: fn_span, ..terminator.source_info };
636+
Some(CallSite { callee, fn_sig, block: bb, source_info })
596637
}
597638

598639
/// Attempts to inline a callsite into the caller body. When successful returns basic blocks
@@ -604,14 +645,72 @@ fn try_inlining<'tcx, I: Inliner<'tcx>>(
604645
callsite: &CallSite<'tcx>,
605646
) -> Result<std::ops::Range<BasicBlock>, &'static str> {
606647
let tcx = inliner.tcx();
648+
607649
check_mir_is_available(inliner, caller_body, callsite.callee)?;
608650

609651
let callee_attrs = tcx.codegen_fn_attrs(callsite.callee.def_id());
610652
check_inline::is_inline_valid_on_fn(tcx, callsite.callee.def_id())?;
611653
check_codegen_attributes(inliner, callsite, callee_attrs)?;
612654
inliner.check_codegen_attributes_extra(callee_attrs)?;
613655

614-
let terminator = caller_body[callsite.block].terminator.as_ref().unwrap();
656+
let callee_body = try_instance_mir(tcx, callsite.callee.def)?;
657+
check_inline::is_inline_valid_on_body(tcx, callee_body)?;
658+
inliner.check_callee_mir_body(callsite, callee_body, callee_attrs)?;
659+
660+
if inliner.consider_drops() {
661+
let block_mut = &mut caller_body.basic_blocks.as_mut()[callsite.block];
662+
let terminator = block_mut.terminator.as_mut().unwrap();
663+
if let TerminatorKind::Drop {
664+
place,
665+
target,
666+
unwind,
667+
replace: _,
668+
drop: None,
669+
async_fut: None,
670+
} = terminator.kind
671+
{
672+
// Rather than updating everything after here to also handle `Drop`,
673+
// just replace the terminator with a `Call`, since we'll need things
674+
// like the local for the argument anyway.
675+
let source_info = terminator.source_info;
676+
let drop_ty = place.ty(&caller_body.local_decls, tcx).ty;
677+
if !drop_ty.needs_drop(tcx, inliner.typing_env()) {
678+
//return None;
679+
bug!("Why does it think {drop_ty:?} needs a drop *now*?");
680+
}
681+
682+
let drop_ty_mut = Ty::new_mut_ptr(tcx, drop_ty);
683+
let arg = caller_body.local_decls.push(LocalDecl::new(drop_ty_mut, source_info.span));
684+
block_mut.statements.push(Statement::new(
685+
source_info,
686+
StatementKind::Assign(Box::new((
687+
Place::from(arg),
688+
Rvalue::RawPtr(RawPtrKind::Mut, place),
689+
))),
690+
));
691+
let unit_local =
692+
caller_body.local_decls.push(LocalDecl::new(tcx.types.unit, source_info.span));
693+
terminator.kind = TerminatorKind::Call {
694+
func: Operand::function_handle(
695+
tcx,
696+
callsite.callee.def_id(),
697+
[drop_ty.into()],
698+
source_info.span,
699+
),
700+
args: Box::new([Spanned {
701+
node: Operand::Move(Place::from(arg)),
702+
span: source_info.span,
703+
}]),
704+
destination: Place::from(unit_local),
705+
target: Some(target),
706+
unwind,
707+
call_source: CallSource::Misc,
708+
fn_span: source_info.span,
709+
};
710+
}
711+
}
712+
713+
let terminator = caller_body[callsite.block].terminator();
615714
let TerminatorKind::Call { args, destination, .. } = &terminator.kind else { bug!() };
616715
let destination_ty = destination.ty(&caller_body.local_decls, tcx).ty;
617716
for arg in args {
@@ -622,10 +721,6 @@ fn try_inlining<'tcx, I: Inliner<'tcx>>(
622721
}
623722
}
624723

625-
let callee_body = try_instance_mir(tcx, callsite.callee.def)?;
626-
check_inline::is_inline_valid_on_body(tcx, callee_body)?;
627-
inliner.check_callee_mir_body(callsite, callee_body, callee_attrs)?;
628-
629724
let Ok(callee_body) = callsite.callee.try_instantiate_mir_and_normalize_erasing_regions(
630725
tcx,
631726
inliner.typing_env(),

compiler/rustc_mir_transform/src/inline/cycle.rs

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
22
use rustc_data_structures::stack::ensure_sufficient_stack;
33
use rustc_data_structures::unord::UnordSet;
4+
use rustc_hir::LangItem;
45
use rustc_hir::def_id::{DefId, LocalDefId};
56
use rustc_middle::mir::TerminatorKind;
6-
use rustc_middle::ty::{self, GenericArgsRef, InstanceKind, TyCtxt, TypeVisitableExt};
7+
use rustc_middle::ty::{self, GenericArgsRef, InstanceKind, TyCtxt, TypeVisitableExt, TypingEnv};
78
use rustc_session::Limit;
89
use rustc_span::sym;
910
use tracing::{instrument, trace};
@@ -204,23 +205,40 @@ pub(crate) fn mir_inliner_callees<'tcx>(
204205
let mut calls = FxIndexSet::default();
205206
for bb_data in body.basic_blocks.iter() {
206207
let terminator = bb_data.terminator();
207-
if let TerminatorKind::Call { func, args: call_args, .. } = &terminator.kind {
208-
let ty = func.ty(&body.local_decls, tcx);
209-
let ty::FnDef(def_id, generic_args) = ty.kind() else {
210-
continue;
211-
};
212-
let call = if tcx.is_intrinsic(*def_id, sym::const_eval_select) {
213-
let func = &call_args[2].node;
208+
let call = match &terminator.kind {
209+
TerminatorKind::Call { func, args: call_args, .. } => {
214210
let ty = func.ty(&body.local_decls, tcx);
215211
let ty::FnDef(def_id, generic_args) = ty.kind() else {
216212
continue;
217213
};
218-
(*def_id, *generic_args)
219-
} else {
220-
(*def_id, *generic_args)
221-
};
222-
calls.insert(call);
223-
}
214+
if tcx.is_intrinsic(*def_id, sym::const_eval_select) {
215+
let func = &call_args[2].node;
216+
let ty = func.ty(&body.local_decls, tcx);
217+
let ty::FnDef(def_id, generic_args) = ty.kind() else {
218+
continue;
219+
};
220+
(*def_id, *generic_args)
221+
} else {
222+
(*def_id, *generic_args)
223+
}
224+
}
225+
// Coroutines need optimized MIR for layout, so skip them to avoid cycles
226+
TerminatorKind::Drop { place, drop: None, async_fut: None, .. }
227+
if !tcx.is_coroutine(instance.def_id()) =>
228+
{
229+
let drop_ty = place.ty(&body.local_decls, tcx).ty;
230+
if !drop_ty.needs_drop(tcx, TypingEnv::post_analysis(tcx, instance.def_id())) {
231+
continue;
232+
}
233+
234+
let drop_def_id =
235+
tcx.require_lang_item(LangItem::DropInPlace, terminator.source_info.span);
236+
let args = tcx.mk_args(&[drop_ty.into()]);
237+
(drop_def_id, args)
238+
}
239+
_ => continue,
240+
};
241+
calls.insert(call);
224242
}
225243
tcx.arena.alloc_from_iter(calls.iter().copied())
226244
}

library/core/src/future/ready.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,20 @@ impl<T> Future for Ready<T> {
2020

2121
#[inline]
2222
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<T> {
23-
Poll::Ready(self.0.take().expect("`Ready` polled after completion"))
23+
match self.0.take() {
24+
Some(v) => Poll::Ready(v),
25+
None => polled_after_completion(),
26+
}
2427
}
2528
}
2629

30+
// Have only one of these, rather than mono'ing the panic for every `T`
31+
#[cold]
32+
#[inline]
33+
fn polled_after_completion() -> ! {
34+
panic!("`Ready` polled after completion")
35+
}
36+
2737
impl<T> Ready<T> {
2838
/// Consumes the `Ready`, returning the wrapped value.
2939
///

tests/codegen-llvm/drop-in-place-noalias.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//@ compile-flags: -Copt-level=3 -C no-prepopulate-passes
1+
//@ compile-flags: -Copt-level=3 -C no-prepopulate-passes -Z inline-mir=no
22

33
// Tests that the compiler can apply `noalias` and other &mut attributes to `drop_in_place`.
44
// Note that non-Unpin types should not get `noalias`, matching &mut behavior.

tests/codegen-llvm/drop.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//@ needs-unwind - this test verifies the amount of drop calls when unwinding is used
2-
//@ compile-flags: -C no-prepopulate-passes
2+
//@ compile-flags: -C no-prepopulate-passes -Z inline-mir=no
33

44
#![crate_type = "lib"]
55

tests/mir-opt/c_unwind_terminate.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
struct Noise;
44
impl Drop for Noise {
5+
#[inline(never)]
56
fn drop(&mut self) {
67
eprintln!("Noisy Drop");
78
}
@@ -14,7 +15,9 @@ fn panic() {
1415
// EMIT_MIR c_unwind_terminate.test.AbortUnwindingCalls.after.mir
1516
extern "C" fn test() {
1617
// CHECK-LABEL: fn test(
17-
// CHECK: drop
18+
// CHECK: inlined drop_in_place::<Noise>
19+
// CHECK-NOT: drop
20+
// CHECK: <Noise as Drop>::drop
1821
// CHECK-SAME: unwind: [[unwind:bb.*]]]
1922
// CHECK: [[unwind]] (cleanup)
2023
// CHECK-NEXT: terminate(abi)

0 commit comments

Comments
 (0)