Skip to main content

rustc_mir_transform/
check_inline.rs

1//! Check that a body annotated with `#[rustc_force_inline]` will not fail to inline based on its
2//! definition alone (irrespective of any specific caller).
3
4use rustc_attr_ir::{InlineAttr, find_attr};
5use rustc_hir::def_id::DefId;
6use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
7use rustc_middle::mir::{Body, TerminatorKind};
8use rustc_middle::ty;
9use rustc_middle::ty::TyCtxt;
10
11use crate::pass_manager::MirLint;
12
13pub(super) struct CheckForceInline;
14
15impl<'tcx> MirLint<'tcx> for CheckForceInline {
16    fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
17        let def_id = body.source.def_id();
18        if !tcx.hir_body_owner_kind(def_id).is_fn_or_closure() || !def_id.is_local() {
19            return;
20        }
21        let InlineAttr::Force { attr_span, .. } = tcx.codegen_fn_attrs(def_id).inline else {
22            return;
23        };
24
25        if let Err(reason) =
26            is_inline_valid_on_fn(tcx, def_id).and_then(|_| is_inline_valid_on_body(tcx, body))
27        {
28            tcx.dcx().emit_err(crate::diagnostics::InvalidForceInline {
29                attr_span,
30                callee_span: tcx.def_span(def_id),
31                callee: tcx.def_path_str(def_id),
32                reason,
33            });
34        }
35    }
36}
37
38pub(super) fn is_inline_valid_on_fn<'tcx>(
39    tcx: TyCtxt<'tcx>,
40    def_id: DefId,
41) -> Result<(), &'static str> {
42    let codegen_attrs = tcx.codegen_fn_attrs(def_id);
43
44    if find_attr!(tcx, def_id, RustcNoMirInline) {
45        return Err("#[rustc_no_mir_inline]");
46    }
47
48    let ty = tcx.type_of(def_id);
49    if match ty.instantiate_identity().skip_norm_wip().kind() {
50        ty::FnDef(..) => tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip().c_variadic(),
51        ty::Closure(_, args) => args.as_closure().sig().c_variadic(),
52        _ => false,
53    } {
54        return Err("C variadic");
55    }
56
57    if codegen_attrs.flags.contains(CodegenFnAttrFlags::COLD) {
58        return Err("cold");
59    }
60
61    // Intrinsics without fallback body cannot be inlined. The logic for which intrinsics *with*
62    // body can be inlined is in the inlining pass.
63    if let Some(intrinsic) = tcx.intrinsic(def_id)
64        && intrinsic.must_be_overridden
65    {
66        return Err("callee is an intrinsic without fallback body");
67    }
68
69    Ok(())
70}
71
72pub(super) fn is_inline_valid_on_body<'tcx>(
73    _: TyCtxt<'tcx>,
74    body: &Body<'tcx>,
75) -> Result<(), &'static str> {
76    if body
77        .basic_blocks
78        .iter()
79        .any(|bb| matches!(bb.terminator().kind, TerminatorKind::TailCall { .. }))
80    {
81        return Err("can't inline functions with tail calls");
82    }
83
84    Ok(())
85}