Skip to content

Commit 82914b1

Browse files
committed
Also allow inlining drop shims
1 parent adcb3d3 commit 82914b1

File tree

29 files changed

+2291
-447
lines changed

29 files changed

+2291
-447
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: 191 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::iter;
55
use std::ops::{Range, RangeFrom};
66

77
use rustc_abi::{ExternAbi, FieldIdx};
8+
use rustc_hir::LangItem;
89
use rustc_hir::attrs::{InlineAttr, OptimizeAttr};
910
use rustc_hir::def::DefKind;
1011
use rustc_hir::def_id::DefId;
@@ -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
}
@@ -542,57 +556,133 @@ fn process_blocks<'tcx, I: Inliner<'tcx>>(
542556
}
543557
}
544558

559+
/// Returns a value indicating whether it's worth trying to inline a `drop` for `ty`.
560+
///
561+
/// We only want to bother inlining things that have a change to need to do something.
562+
/// The `RemoveUnneededDrops` pass will handle things that obviously don't need
563+
/// dropping, and will do it more efficiently since it doesn't need to add inlining
564+
/// metadata, defensively add new blocks, etc.
565+
///
566+
/// But this isn't the same as `needs_drop` because we want the opposite fallback:
567+
/// while `needs_drop` is true for a (non-Copy) type parameter, here we don't
568+
/// want to attempt inlining its drop because that'll never work.
569+
fn should_attempt_inline_drop_for_type<'tcx>(
570+
tcx: TyCtxt<'tcx>,
571+
typing_env: ty::TypingEnv<'tcx>,
572+
ty: Ty<'tcx>,
573+
) -> bool {
574+
match ty.kind() {
575+
ty::Tuple(elems) if elems.is_empty() => false,
576+
577+
// Even if these might have drops later, we can't inline them now.
578+
ty::Param(..) | ty::Alias(..) | ty::Dynamic(..) | ty::Foreign(..) => false,
579+
580+
ty::Array(..)
581+
| ty::Adt(..)
582+
| ty::Slice(..)
583+
| ty::Tuple(..)
584+
| ty::Closure(..)
585+
| ty::CoroutineClosure(..)
586+
| ty::Coroutine(..)
587+
| ty::CoroutineWitness(..) => ty.needs_drop(tcx, typing_env),
588+
589+
// Primitives we obviously don't need to inline a drop method
590+
ty::Error(..)
591+
| ty::Bool
592+
| ty::Int(..)
593+
| ty::Uint(..)
594+
| ty::Float(..)
595+
| ty::Never
596+
| ty::FnDef(..)
597+
| ty::FnPtr(..)
598+
| ty::Char
599+
| ty::RawPtr(..)
600+
| ty::Ref(..)
601+
| ty::Str => false,
602+
603+
// FIXME: Unsure what to do with this, but not attempting inlining is safe
604+
ty::Pat(..) | ty::UnsafeBinder(..) => false,
605+
606+
ty::Infer(..) | ty::Placeholder(..) | ty::Bound(..) => {
607+
bug!("weird type while inlining: {ty:?}")
608+
}
609+
}
610+
}
611+
545612
fn resolve_callsite<'tcx, I: Inliner<'tcx>>(
546613
inliner: &I,
547614
caller_body: &Body<'tcx>,
548615
bb: BasicBlock,
549616
bb_data: &BasicBlockData<'tcx>,
550617
) -> Option<CallSite<'tcx>> {
551618
let tcx = inliner.tcx();
552-
// Only consider direct calls to functions
553619
let terminator = bb_data.terminator();
554620

555621
// 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-
}
622+
let (def_id, args, fn_span) = match &terminator.kind {
623+
TerminatorKind::Call { func, fn_span, .. } => {
624+
let func_ty = func.ty(caller_body, tcx);
625+
if let ty::FnDef(def_id, args) = *func_ty.kind() {
626+
if !inliner.should_inline_for_callee(def_id) {
627+
debug!("not enabled");
628+
return None;
629+
}
563630

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()?;
631+
// Allow RemoveUnneededDrops to handle these, rather than inlining,
632+
// since it doesn't add the extra locals nor the metadata.
633+
if inliner.consider_drops()
634+
&& tcx.is_lang_item(def_id, LangItem::DropInPlace)
635+
&& let drop_ty = args.type_at(0)
636+
&& !should_attempt_inline_drop_for_type(tcx, inliner.typing_env(), drop_ty)
637+
{
638+
return None;
639+
}
568640

569-
if let InstanceKind::Virtual(..) | InstanceKind::Intrinsic(_) = callee.def {
641+
(def_id, args, *fn_span)
642+
} else {
570643
return None;
571644
}
572-
573-
if inliner.history().contains(&callee.def_id()) {
645+
}
646+
TerminatorKind::Drop { place, .. } if inliner.consider_drops() => {
647+
let drop_ty = place.ty(&caller_body.local_decls, tcx).ty;
648+
if !should_attempt_inline_drop_for_type(tcx, inliner.typing_env(), drop_ty) {
574649
return None;
575650
}
576651

577-
let fn_sig = tcx.fn_sig(def_id).instantiate(tcx, args);
652+
let drop_def_id =
653+
tcx.require_lang_item(LangItem::DropInPlace, terminator.source_info.span);
654+
let args = tcx.mk_args(&[drop_ty.into()]);
655+
(drop_def_id, args, rustc_span::DUMMY_SP)
656+
}
657+
_ => return None,
658+
};
578659

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-
}
660+
// To resolve an instance its args have to be fully normalized.
661+
let args = tcx.try_normalize_erasing_regions(inliner.typing_env(), args).ok()?;
662+
let callee = Instance::try_resolve(tcx, inliner.typing_env(), def_id, args).ok().flatten()?;
663+
664+
if let InstanceKind::Virtual(..) | InstanceKind::Intrinsic(_) = callee.def {
665+
return None;
666+
}
588667

589-
let source_info = SourceInfo { span: fn_span, ..terminator.source_info };
668+
if inliner.history().contains(&callee.def_id()) {
669+
return None;
670+
}
590671

591-
return Some(CallSite { callee, fn_sig, block: bb, source_info });
592-
}
672+
let fn_sig = tcx.fn_sig(def_id).instantiate(tcx, args);
673+
674+
// Additionally, check that the body that we're inlining actually agrees
675+
// with the ABI of the trait that the item comes from.
676+
if let InstanceKind::Item(instance_def_id) = callee.def
677+
&& tcx.def_kind(instance_def_id) == DefKind::AssocFn
678+
&& let instance_fn_sig = tcx.fn_sig(instance_def_id).skip_binder()
679+
&& instance_fn_sig.abi() != fn_sig.abi()
680+
{
681+
return None;
593682
}
594683

595-
None
684+
let source_info = SourceInfo { span: fn_span, ..terminator.source_info };
685+
Some(CallSite { callee, fn_sig, block: bb, source_info })
596686
}
597687

598688
/// Attempts to inline a callsite into the caller body. When successful returns basic blocks
@@ -604,23 +694,33 @@ fn try_inlining<'tcx, I: Inliner<'tcx>>(
604694
callsite: &CallSite<'tcx>,
605695
) -> Result<std::ops::Range<BasicBlock>, &'static str> {
606696
let tcx = inliner.tcx();
697+
607698
check_mir_is_available(inliner, caller_body, callsite.callee)?;
608699

609700
let callee_attrs = tcx.codegen_fn_attrs(callsite.callee.def_id());
610701
check_inline::is_inline_valid_on_fn(tcx, callsite.callee.def_id())?;
611702
check_codegen_attributes(inliner, callsite, callee_attrs)?;
612703
inliner.check_codegen_attributes_extra(callee_attrs)?;
613704

614-
let terminator = caller_body[callsite.block].terminator.as_ref().unwrap();
615-
let TerminatorKind::Call { args, destination, .. } = &terminator.kind else { bug!() };
616-
let destination_ty = destination.ty(&caller_body.local_decls, tcx).ty;
617-
for arg in args {
618-
if !arg.node.ty(&caller_body.local_decls, tcx).is_sized(tcx, inliner.typing_env()) {
619-
// We do not allow inlining functions with unsized params. Inlining these functions
620-
// could create unsized locals, which are unsound and being phased out.
621-
return Err("call has unsized argument");
705+
let (args, destination_ty) = match &caller_body[callsite.block].terminator().kind {
706+
TerminatorKind::Call { args, destination, .. } => {
707+
for arg in args {
708+
if !arg.node.ty(&caller_body.local_decls, tcx).is_sized(tcx, inliner.typing_env()) {
709+
// We do not allow inlining functions with unsized params. Inlining these functions
710+
// could create unsized locals, which are unsound and being phased out.
711+
return Err("call has unsized argument");
712+
}
713+
}
714+
(&**args, destination.ty(&caller_body.local_decls, tcx).ty)
622715
}
623-
}
716+
TerminatorKind::Drop { .. } => {
717+
// We cheat a bit here. Obviously there *is* an argument to the
718+
// `drop_in_place`, but all the checks that look at it are ok to skip
719+
// since we're generating them with always the correct type.
720+
(&[][..], tcx.types.unit)
721+
}
722+
_ => bug!(),
723+
};
624724

625725
let callee_body = try_instance_mir(tcx, callsite.callee.def)?;
626726
check_inline::is_inline_valid_on_body(tcx, callee_body)?;
@@ -691,6 +791,59 @@ fn try_inlining<'tcx, I: Inliner<'tcx>>(
691791
}
692792
}
693793

794+
if inliner.consider_drops() {
795+
let block_mut = &mut caller_body.basic_blocks.as_mut()[callsite.block];
796+
let terminator = block_mut.terminator.as_mut().unwrap();
797+
if let TerminatorKind::Drop {
798+
place,
799+
target,
800+
unwind,
801+
replace: _,
802+
drop: None,
803+
async_fut: None,
804+
} = terminator.kind
805+
{
806+
// Rather than updating everything after here to also handle `Drop`,
807+
// just replace the terminator with a `Call`, since we'll need things
808+
// like the local for the argument anyway.
809+
let source_info = terminator.source_info;
810+
let drop_ty = place.ty(&caller_body.local_decls, tcx).ty;
811+
812+
// We shouldn't have gotten here if the shim is empty, though it's
813+
// not actually a *problem* if we do -- it's easy to inline nothing.
814+
debug_assert!(drop_ty.needs_drop(tcx, inliner.typing_env()));
815+
816+
let drop_ty_mut = Ty::new_mut_ptr(tcx, drop_ty);
817+
let arg = caller_body.local_decls.push(LocalDecl::new(drop_ty_mut, source_info.span));
818+
block_mut.statements.push(Statement::new(
819+
source_info,
820+
StatementKind::Assign(Box::new((
821+
Place::from(arg),
822+
Rvalue::RawPtr(RawPtrKind::Mut, place),
823+
))),
824+
));
825+
let unit_local =
826+
caller_body.local_decls.push(LocalDecl::new(tcx.types.unit, source_info.span));
827+
terminator.kind = TerminatorKind::Call {
828+
func: Operand::function_handle(
829+
tcx,
830+
callsite.callee.def_id(),
831+
[drop_ty.into()],
832+
source_info.span,
833+
),
834+
args: Box::new([Spanned {
835+
node: Operand::Move(Place::from(arg)),
836+
span: source_info.span,
837+
}]),
838+
destination: Place::from(unit_local),
839+
target: Some(target),
840+
unwind,
841+
call_source: CallSource::Misc,
842+
fn_span: source_info.span,
843+
};
844+
}
845+
}
846+
694847
let old_blocks = caller_body.basic_blocks.next_index();
695848
inline_call(inliner, caller_body, callsite, callee_body);
696849
let new_blocks = old_blocks..caller_body.basic_blocks.next_index();

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
}

0 commit comments

Comments
 (0)