Skip to content

Commit 70c02bc

Browse files
committed
Also allow inlining drop shims
1 parent 07b7dc9 commit 70c02bc

File tree

28 files changed

+2221
-452
lines changed

28 files changed

+2221
-452
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: 205 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ use std::assert_matches::debug_assert_matches;
44
use std::iter;
55
use std::ops::{Range, RangeFrom};
66

7+
use either::Either;
78
use rustc_abi::{ExternAbi, FieldIdx};
9+
use rustc_hir::LangItem;
810
use rustc_hir::attrs::{InlineAttr, OptimizeAttr};
911
use rustc_hir::def::DefKind;
1012
use rustc_hir::def_id::DefId;
@@ -116,6 +118,9 @@ trait Inliner<'tcx> {
116118
/// Has the caller body been changed?
117119
fn changed(self) -> bool;
118120

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

@@ -187,6 +192,10 @@ impl<'tcx> Inliner<'tcx> for ForceInliner<'tcx> {
187192
self.changed
188193
}
189194

195+
fn consider_drops(&self) -> bool {
196+
false
197+
}
198+
190199
fn should_inline_for_callee(&self, def_id: DefId) -> bool {
191200
ForceInline::should_run_pass_for_callee(self.tcx(), def_id)
192201
}
@@ -272,6 +281,7 @@ struct NormalInliner<'tcx> {
272281
typing_env: ty::TypingEnv<'tcx>,
273282
/// `DefId` of caller.
274283
def_id: DefId,
284+
caller_is_coroutine: bool,
275285
/// Stack of inlined instances.
276286
/// We only check the `DefId` and not the args because we want to
277287
/// avoid inlining cases of polymorphic recursion.
@@ -304,6 +314,7 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
304314
tcx,
305315
typing_env,
306316
def_id,
317+
caller_is_coroutine: tcx.is_coroutine(def_id),
307318
history: Vec::new(),
308319
top_down_counter: 0,
309320
changed: false,
@@ -334,6 +345,10 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
334345
self.changed
335346
}
336347

348+
fn consider_drops(&self) -> bool {
349+
!self.caller_is_coroutine
350+
}
351+
337352
fn should_inline_for_callee(&self, _: DefId) -> bool {
338353
true
339354
}
@@ -542,57 +557,133 @@ fn process_blocks<'tcx, I: Inliner<'tcx>>(
542557
}
543558
}
544559

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

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

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

569-
if let InstanceKind::Virtual(..) | InstanceKind::Intrinsic(_) = callee.def {
642+
(def_id, args, *fn_span)
643+
} else {
570644
return None;
571645
}
572-
573-
if inliner.history().contains(&callee.def_id()) {
646+
}
647+
TerminatorKind::Drop { place, .. } if inliner.consider_drops() => {
648+
let drop_ty = place.ty(&caller_body.local_decls, tcx).ty;
649+
if !should_attempt_inline_drop_for_type(tcx, inliner.typing_env(), drop_ty) {
574650
return None;
575651
}
576652

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

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-
}
661+
// To resolve an instance its args have to be fully normalized.
662+
let args = tcx.try_normalize_erasing_regions(inliner.typing_env(), args).ok()?;
663+
let callee = Instance::try_resolve(tcx, inliner.typing_env(), def_id, args).ok().flatten()?;
588664

589-
let source_info = SourceInfo { span: fn_span, ..terminator.source_info };
665+
if let InstanceKind::Virtual(..) | InstanceKind::Intrinsic(_) = callee.def {
666+
return None;
667+
}
590668

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

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

598689
/// Attempts to inline a callsite into the caller body. When successful returns basic blocks
@@ -604,23 +695,38 @@ fn try_inlining<'tcx, I: Inliner<'tcx>>(
604695
callsite: &CallSite<'tcx>,
605696
) -> Result<std::ops::Range<BasicBlock>, &'static str> {
606697
let tcx = inliner.tcx();
698+
607699
check_mir_is_available(inliner, caller_body, callsite.callee)?;
608700

609701
let callee_attrs = tcx.codegen_fn_attrs(callsite.callee.def_id());
610702
check_inline::is_inline_valid_on_fn(tcx, callsite.callee.def_id())?;
611703
check_codegen_attributes(inliner, callsite, callee_attrs)?;
612704
inliner.check_codegen_attributes_extra(callee_attrs)?;
613705

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");
706+
let (arg_tys_iter, destination_ty) = match &caller_body[callsite.block].terminator().kind {
707+
TerminatorKind::Call { args, destination, .. } => {
708+
for arg in args {
709+
if !arg.node.ty(&caller_body.local_decls, tcx).is_sized(tcx, inliner.typing_env()) {
710+
// We do not allow inlining functions with unsized params. Inlining these functions
711+
// could create unsized locals, which are unsound and being phased out.
712+
return Err("call has unsized argument");
713+
}
714+
}
715+
(
716+
Either::Left(args.iter().map(|arg| arg.node.ty(&caller_body.local_decls, tcx))),
717+
destination.ty(&caller_body.local_decls, tcx).ty,
718+
)
622719
}
623-
}
720+
TerminatorKind::Drop { place, .. } => {
721+
let place_ty = place.ty(&caller_body.local_decls, tcx).ty;
722+
let arg_ty = Ty::new_mut_ptr(tcx, place_ty);
723+
// We cheat a bit here. Obviously there *is* an argument to the
724+
// `drop_in_place`, but all the checks that look at it are ok to skip
725+
// since we're generating them with always the correct type.
726+
(Either::Right(std::iter::once(arg_ty)), tcx.types.unit)
727+
}
728+
_ => bug!(),
729+
};
624730

625731
let callee_body = try_instance_mir(tcx, callsite.callee.def)?;
626732
check_inline::is_inline_valid_on_body(tcx, callee_body)?;
@@ -651,15 +757,15 @@ fn try_inlining<'tcx, I: Inliner<'tcx>>(
651757
return Err("implementation limitation -- return type mismatch");
652758
}
653759
if callsite.fn_sig.abi() == ExternAbi::RustCall {
654-
let (self_arg, arg_tuple) = match &args[..] {
655-
[arg_tuple] => (None, arg_tuple),
656-
[self_arg, arg_tuple] => (Some(self_arg), arg_tuple),
657-
_ => bug!("Expected `rust-call` to have 1 or 2 args"),
760+
let (self_arg_ty, arg_tuple_ty) = {
761+
let mut arg_tys_iter = arg_tys_iter.fuse();
762+
match (arg_tys_iter.next(), arg_tys_iter.next(), arg_tys_iter.next()) {
763+
(Some(arg_tuple), None, None) => (None, arg_tuple),
764+
(Some(self_arg), Some(arg_tuple), None) => (Some(self_arg), arg_tuple),
765+
_ => bug!("Expected `rust-call` to have 1 or 2 args"),
766+
}
658767
};
659768

660-
let self_arg_ty = self_arg.map(|self_arg| self_arg.node.ty(&caller_body.local_decls, tcx));
661-
662-
let arg_tuple_ty = arg_tuple.node.ty(&caller_body.local_decls, tcx);
663769
let arg_tys = if callee_body.spread_arg.is_some() {
664770
std::slice::from_ref(&arg_tuple_ty)
665771
} else {
@@ -680,9 +786,8 @@ fn try_inlining<'tcx, I: Inliner<'tcx>>(
680786
}
681787
}
682788
} else {
683-
for (arg, input) in args.iter().zip(callee_body.args_iter()) {
789+
for (arg_ty, input) in arg_tys_iter.zip(callee_body.args_iter()) {
684790
let input_type = callee_body.local_decls[input].ty;
685-
let arg_ty = arg.node.ty(&caller_body.local_decls, tcx);
686791
if !util::sub_types(tcx, inliner.typing_env(), input_type, arg_ty) {
687792
trace!(?arg_ty, ?input_type);
688793
debug!("failed to normalize argument type");
@@ -691,6 +796,59 @@ fn try_inlining<'tcx, I: Inliner<'tcx>>(
691796
}
692797
}
693798

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

0 commit comments

Comments
 (0)