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_parsing::InlineAttr;
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;
10use rustc_span::sym;
11
12use crate::pass_manager::MirLint;
13
14pub(super) struct CheckForceInline;
15
16impl<'tcx> MirLint<'tcx> for CheckForceInline {
17    fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
18        let def_id = body.source.def_id();
19        if !tcx.hir().body_owner_kind(def_id).is_fn_or_closure() || !def_id.is_local() {
20            return;
21        }
22        let InlineAttr::Force { attr_span, .. } = tcx.codegen_fn_attrs(def_id).inline else {
23            return;
24        };
25
26        if let Err(reason) =
27            is_inline_valid_on_fn(tcx, def_id).and_then(|_| is_inline_valid_on_body(tcx, body))
28        {
29            tcx.dcx().emit_err(crate::errors::InvalidForceInline {
30                attr_span,
31                callee_span: tcx.def_span(def_id),
32                callee: tcx.def_path_str(def_id),
33                reason,
34            });
35        }
36    }
37}
38
39pub(super) fn is_inline_valid_on_fn<'tcx>(
40    tcx: TyCtxt<'tcx>,
41    def_id: DefId,
42) -> Result<(), &'static str> {
43    let codegen_attrs = tcx.codegen_fn_attrs(def_id);
44    if tcx.has_attr(def_id, sym::rustc_no_mir_inline) {
45        return Err("#[rustc_no_mir_inline]");
46    }
47
48    // FIXME(#127234): Coverage instrumentation currently doesn't handle inlined
49    // MIR correctly when Modified Condition/Decision Coverage is enabled.
50    if tcx.sess.instrument_coverage_mcdc() {
51        return Err("incompatible with MC/DC coverage");
52    }
53
54    let ty = tcx.type_of(def_id);
55    if match ty.instantiate_identity().kind() {
56        ty::FnDef(..) => tcx.fn_sig(def_id).instantiate_identity().c_variadic(),
57        ty::Closure(_, args) => args.as_closure().sig().c_variadic(),
58        _ => false,
59    } {
60        return Err("C variadic");
61    }
62
63    if codegen_attrs.flags.contains(CodegenFnAttrFlags::COLD) {
64        return Err("cold");
65    }
66
67    // Intrinsic fallback bodies are automatically made cross-crate inlineable,
68    // but at this stage we don't know whether codegen knows the intrinsic,
69    // so just conservatively don't inline it. This also ensures that we do not
70    // accidentally inline the body of an intrinsic that *must* be overridden.
71    if tcx.has_attr(def_id, sym::rustc_intrinsic) {
72        return Err("callee is an intrinsic");
73    }
74
75    Ok(())
76}
77
78pub(super) fn is_inline_valid_on_body<'tcx>(
79    _: TyCtxt<'tcx>,
80    body: &Body<'tcx>,
81) -> Result<(), &'static str> {
82    if body
83        .basic_blocks
84        .iter()
85        .any(|bb| matches!(bb.terminator().kind, TerminatorKind::TailCall { .. }))
86    {
87        return Err("can't inline functions with tail calls");
88    }
89
90    Ok(())
91}