about summary refs log tree commit diff
path: root/compiler/rustc_mir_transform/src/check_inline.rs
blob: 83c3cda5a50593930ff115725548f4f9d3a8cd3b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
//! Check that a body annotated with `#[rustc_force_inline]` will not fail to inline based on its
//! definition alone (irrespective of any specific caller).

use rustc_attr_parsing::InlineAttr;
use rustc_hir::def_id::DefId;
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
use rustc_middle::mir::{Body, TerminatorKind};
use rustc_middle::ty;
use rustc_middle::ty::TyCtxt;
use rustc_span::sym;

use crate::pass_manager::MirLint;

pub(super) struct CheckForceInline;

impl<'tcx> MirLint<'tcx> for CheckForceInline {
    fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
        let def_id = body.source.def_id();
        if !tcx.hir_body_owner_kind(def_id).is_fn_or_closure() || !def_id.is_local() {
            return;
        }
        let InlineAttr::Force { attr_span, .. } = tcx.codegen_fn_attrs(def_id).inline else {
            return;
        };

        if let Err(reason) =
            is_inline_valid_on_fn(tcx, def_id).and_then(|_| is_inline_valid_on_body(tcx, body))
        {
            tcx.dcx().emit_err(crate::errors::InvalidForceInline {
                attr_span,
                callee_span: tcx.def_span(def_id),
                callee: tcx.def_path_str(def_id),
                reason,
            });
        }
    }
}

pub(super) fn is_inline_valid_on_fn<'tcx>(
    tcx: TyCtxt<'tcx>,
    def_id: DefId,
) -> Result<(), &'static str> {
    let codegen_attrs = tcx.codegen_fn_attrs(def_id);
    if tcx.has_attr(def_id, sym::rustc_no_mir_inline) {
        return Err("#[rustc_no_mir_inline]");
    }

    // FIXME(#127234): Coverage instrumentation currently doesn't handle inlined
    // MIR correctly when Modified Condition/Decision Coverage is enabled.
    if tcx.sess.instrument_coverage_mcdc() {
        return Err("incompatible with MC/DC coverage");
    }

    let ty = tcx.type_of(def_id);
    if match ty.instantiate_identity().kind() {
        ty::FnDef(..) => tcx.fn_sig(def_id).instantiate_identity().c_variadic(),
        ty::Closure(_, args) => args.as_closure().sig().c_variadic(),
        _ => false,
    } {
        return Err("C variadic");
    }

    if codegen_attrs.flags.contains(CodegenFnAttrFlags::COLD) {
        return Err("cold");
    }

    // Intrinsic fallback bodies are automatically made cross-crate inlineable,
    // but at this stage we don't know whether codegen knows the intrinsic,
    // so just conservatively don't inline it. This also ensures that we do not
    // accidentally inline the body of an intrinsic that *must* be overridden.
    if tcx.has_attr(def_id, sym::rustc_intrinsic) {
        return Err("callee is an intrinsic");
    }

    Ok(())
}

pub(super) fn is_inline_valid_on_body<'tcx>(
    _: TyCtxt<'tcx>,
    body: &Body<'tcx>,
) -> Result<(), &'static str> {
    if body
        .basic_blocks
        .iter()
        .any(|bb| matches!(bb.terminator().kind, TerminatorKind::TailCall { .. }))
    {
        return Err("can't inline functions with tail calls");
    }

    Ok(())
}