rustc_monomorphize/mono_checks/
abi_check.rs

1//! This module ensures that if a function's ABI requires a particular target feature,
2//! that target feature is enabled both on the callee and all callers.
3use rustc_abi::{BackendRepr, CanonAbi, RegKind, X86Call};
4use rustc_hir::{CRATE_HIR_ID, HirId};
5use rustc_middle::mir::{self, Location, traversal};
6use rustc_middle::ty::{self, Instance, InstanceKind, Ty, TyCtxt};
7use rustc_span::def_id::DefId;
8use rustc_span::{DUMMY_SP, Span, Symbol, sym};
9use rustc_target::callconv::{FnAbi, PassMode};
10
11use crate::errors;
12
13fn uses_vector_registers(mode: &PassMode, repr: &BackendRepr) -> bool {
14    match mode {
15        PassMode::Ignore | PassMode::Indirect { .. } => false,
16        PassMode::Cast { pad_i32: _, cast } => {
17            cast.prefix.iter().any(|r| r.is_some_and(|x| x.kind == RegKind::Vector))
18                || cast.rest.unit.kind == RegKind::Vector
19        }
20        PassMode::Direct(..) | PassMode::Pair(..) => matches!(repr, BackendRepr::SimdVector { .. }),
21    }
22}
23
24/// Checks whether a certain function ABI is compatible with the target features currently enabled
25/// for a certain function.
26/// `is_call` indicates whether this is a call-site check or a definition-site check;
27/// this is only relevant for the wording in the emitted error.
28fn do_check_simd_vector_abi<'tcx>(
29    tcx: TyCtxt<'tcx>,
30    abi: &FnAbi<'tcx, Ty<'tcx>>,
31    def_id: DefId,
32    is_call: bool,
33    loc: impl Fn() -> (Span, HirId),
34) {
35    let feature_def = tcx.sess.target.features_for_correct_vector_abi();
36    let codegen_attrs = tcx.codegen_fn_attrs(def_id);
37    let have_feature = |feat: Symbol| {
38        tcx.sess.unstable_target_features.contains(&feat)
39            || codegen_attrs.target_features.iter().any(|x| x.name == feat)
40    };
41    for arg_abi in abi.args.iter().chain(std::iter::once(&abi.ret)) {
42        let size = arg_abi.layout.size;
43        if uses_vector_registers(&arg_abi.mode, &arg_abi.layout.backend_repr) {
44            // Find the first feature that provides at least this vector size.
45            let feature = match feature_def.iter().find(|(bits, _)| size.bits() <= *bits) {
46                Some((_, feature)) => feature,
47                None => {
48                    let (span, _hir_id) = loc();
49                    tcx.dcx().emit_err(errors::AbiErrorUnsupportedVectorType {
50                        span,
51                        ty: arg_abi.layout.ty,
52                        is_call,
53                    });
54                    continue;
55                }
56            };
57            if !have_feature(Symbol::intern(feature)) {
58                // Emit error.
59                let (span, _hir_id) = loc();
60                tcx.dcx().emit_err(errors::AbiErrorDisabledVectorType {
61                    span,
62                    required_feature: feature,
63                    ty: arg_abi.layout.ty,
64                    is_call,
65                });
66            }
67        }
68    }
69    // The `vectorcall` ABI is special in that it requires SSE2 no matter which types are being passed.
70    if abi.conv == CanonAbi::X86(X86Call::Vectorcall) && !have_feature(sym::sse2) {
71        let (span, _hir_id) = loc();
72        tcx.dcx().emit_err(errors::AbiRequiredTargetFeature {
73            span,
74            required_feature: "sse2",
75            abi: "vectorcall",
76            is_call,
77        });
78    }
79}
80
81/// Checks that the ABI of a given instance of a function does not contain vector-passed arguments
82/// or return values for which the corresponding target feature is not enabled.
83fn check_instance_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) {
84    let typing_env = ty::TypingEnv::fully_monomorphized();
85    let Ok(abi) = tcx.fn_abi_of_instance(typing_env.as_query_input((instance, ty::List::empty())))
86    else {
87        // An error will be reported during codegen if we cannot determine the ABI of this
88        // function.
89        tcx.dcx().delayed_bug("ABI computation failure should lead to compilation failure");
90        return;
91    };
92    // Unlike the call-site check, we do also check "Rust" ABI functions here.
93    // This should never trigger, *except* if we start making use of vector registers
94    // for the "Rust" ABI and the user disables those vector registers (which should trigger a
95    // warning as that's clearly disabling a "required" target feature for this target).
96    // Using such a function is where disabling the vector register actually can start leading
97    // to soundness issues, so erroring here seems good.
98    let loc = || {
99        let def_id = instance.def_id();
100        (
101            tcx.def_span(def_id),
102            def_id.as_local().map(|did| tcx.local_def_id_to_hir_id(did)).unwrap_or(CRATE_HIR_ID),
103        )
104    };
105    do_check_simd_vector_abi(tcx, abi, instance.def_id(), /*is_call*/ false, loc);
106}
107
108/// Checks that a call expression does not try to pass a vector-passed argument which requires a
109/// target feature that the caller does not have, as doing so causes UB because of ABI mismatch.
110fn check_call_site_abi<'tcx>(
111    tcx: TyCtxt<'tcx>,
112    callee: Ty<'tcx>,
113    caller: InstanceKind<'tcx>,
114    loc: impl Fn() -> (Span, HirId) + Copy,
115) {
116    if callee.fn_sig(tcx).abi().is_rustic_abi() {
117        // We directly handle the soundness of Rust ABIs -- so let's skip the majority of
118        // call sites to avoid a perf regression.
119        return;
120    }
121    let typing_env = ty::TypingEnv::fully_monomorphized();
122    let callee_abi = match *callee.kind() {
123        ty::FnPtr(..) => {
124            tcx.fn_abi_of_fn_ptr(typing_env.as_query_input((callee.fn_sig(tcx), ty::List::empty())))
125        }
126        ty::FnDef(def_id, args) => {
127            // Intrinsics are handled separately by the compiler.
128            if tcx.intrinsic(def_id).is_some() {
129                return;
130            }
131            let instance = ty::Instance::expect_resolve(tcx, typing_env, def_id, args, DUMMY_SP);
132            tcx.fn_abi_of_instance(typing_env.as_query_input((instance, ty::List::empty())))
133        }
134        _ => {
135            panic!("Invalid function call");
136        }
137    };
138
139    let Ok(callee_abi) = callee_abi else {
140        // ABI failed to compute; this will not get through codegen.
141        return;
142    };
143    do_check_simd_vector_abi(tcx, callee_abi, caller.def_id(), /*is_call*/ true, loc);
144}
145
146fn check_callees_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, body: &mir::Body<'tcx>) {
147    // Check all function call terminators.
148    for (bb, _data) in traversal::mono_reachable(body, tcx, instance) {
149        let terminator = body.basic_blocks[bb].terminator();
150        match terminator.kind {
151            mir::TerminatorKind::Call { ref func, ref fn_span, .. }
152            | mir::TerminatorKind::TailCall { ref func, ref fn_span, .. } => {
153                let callee_ty = func.ty(body, tcx);
154                let callee_ty = instance.instantiate_mir_and_normalize_erasing_regions(
155                    tcx,
156                    ty::TypingEnv::fully_monomorphized(),
157                    ty::EarlyBinder::bind(callee_ty),
158                );
159                check_call_site_abi(tcx, callee_ty, body.source.instance, || {
160                    let loc = Location {
161                        block: bb,
162                        statement_index: body.basic_blocks[bb].statements.len(),
163                    };
164                    (
165                        *fn_span,
166                        body.source_info(loc)
167                            .scope
168                            .lint_root(&body.source_scopes)
169                            .unwrap_or(CRATE_HIR_ID),
170                    )
171                });
172            }
173            _ => {}
174        }
175    }
176}
177
178pub(crate) fn check_feature_dependent_abi<'tcx>(
179    tcx: TyCtxt<'tcx>,
180    instance: Instance<'tcx>,
181    body: &'tcx mir::Body<'tcx>,
182) {
183    check_instance_abi(tcx, instance);
184    check_callees_abi(tcx, instance, body);
185}