rustc_mir_transform/
check_inline_always_target_features.rs

1use rustc_hir::attrs::InlineAttr;
2use rustc_middle::middle::codegen_fn_attrs::{TargetFeature, TargetFeatureKind};
3use rustc_middle::mir::{Body, TerminatorKind};
4use rustc_middle::ty::{self, TyCtxt};
5
6use crate::pass_manager::MirLint;
7
8pub(super) struct CheckInlineAlwaysTargetFeature;
9
10impl<'tcx> MirLint<'tcx> for CheckInlineAlwaysTargetFeature {
11    fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
12        check_inline_always_target_features(tcx, body)
13    }
14}
15
16/// `#[target_feature]`-annotated functions can be marked `#[inline]` and will only be inlined if
17/// the target features match (as well as all of the other inlining heuristics). `#[inline(always)]`
18/// will always inline regardless of matching target features, which can result in errors from LLVM.
19/// However, it is desirable to be able to always annotate certain functions (e.g. SIMD intrinsics)
20/// as `#[inline(always)]` but check the target features match in Rust to avoid the LLVM errors.
21///
22/// We check the caller and callee target features to ensure that this can
23/// be done or emit a lint.
24#[inline]
25fn check_inline_always_target_features<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
26    let caller_def_id = body.source.def_id().expect_local();
27    if !tcx.def_kind(caller_def_id).has_codegen_attrs() {
28        return;
29    }
30
31    let caller_codegen_fn_attrs = tcx.codegen_fn_attrs(caller_def_id);
32
33    for bb in body.basic_blocks.iter() {
34        let terminator = bb.terminator();
35        match &terminator.kind {
36            TerminatorKind::Call { func, .. } | TerminatorKind::TailCall { func, .. } => {
37                let fn_ty = func.ty(body, tcx);
38                let ty::FnDef(callee_def_id, _) = *fn_ty.kind() else {
39                    continue;
40                };
41
42                if !tcx.def_kind(callee_def_id).has_codegen_attrs() {
43                    continue;
44                }
45                let callee_codegen_fn_attrs = tcx.codegen_fn_attrs(callee_def_id);
46                if callee_codegen_fn_attrs.inline != InlineAttr::Always
47                    || callee_codegen_fn_attrs.target_features.is_empty()
48                {
49                    continue;
50                }
51
52                // Scan the users defined target features and ensure they
53                // match the caller.
54                if tcx.is_target_feature_call_safe(
55                    &callee_codegen_fn_attrs.target_features,
56                    &caller_codegen_fn_attrs
57                        .target_features
58                        .iter()
59                        .cloned()
60                        .chain(tcx.sess.target_features.iter().map(|feat| TargetFeature {
61                            name: *feat,
62                            kind: TargetFeatureKind::Implied,
63                        }))
64                        .collect::<Vec<_>>(),
65                ) {
66                    continue;
67                }
68
69                let callee_only: Vec<_> = callee_codegen_fn_attrs
70                    .target_features
71                    .iter()
72                    .filter(|it| !caller_codegen_fn_attrs.target_features.contains(it))
73                    .filter(|it| !matches!(it.kind, TargetFeatureKind::Implied))
74                    .map(|it| it.name.as_str())
75                    .collect();
76
77                crate::errors::emit_inline_always_target_feature_diagnostic(
78                    tcx,
79                    terminator.source_info.span,
80                    callee_def_id,
81                    caller_def_id.into(),
82                    &callee_only,
83                );
84            }
85            _ => (),
86        }
87    }
88}