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/// Emit an error when a non-rustic ABI has unsized parameters.
82/// Unsized types do not have a stable layout, so should not be used with stable ABIs.
83/// `is_call` indicates whether this is a call-site check or a definition-site check;
84/// this is only relevant for the wording in the emitted error.
85fn do_check_unsized_params<'tcx>(
86    tcx: TyCtxt<'tcx>,
87    fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
88    is_call: bool,
89    loc: impl Fn() -> (Span, HirId),
90) {
91    // Unsized parameters are allowed with the (unstable) "Rust" (and similar) ABIs.
92    if fn_abi.conv.is_rustic_abi() {
93        return;
94    }
95
96    for arg_abi in fn_abi.args.iter() {
97        if !arg_abi.layout.layout.is_sized() {
98            let (span, _hir_id) = loc();
99            tcx.dcx().emit_err(errors::AbiErrorUnsupportedUnsizedParameter {
100                span,
101                ty: arg_abi.layout.ty,
102                is_call,
103            });
104        }
105    }
106}
107
108/// Checks the ABI of an Instance, emitting an error when:
109///
110/// - a non-rustic ABI uses unsized parameters
111/// - the signature requires target features that are not enabled
112fn check_instance_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) {
113    let typing_env = ty::TypingEnv::fully_monomorphized();
114    let Ok(abi) = tcx.fn_abi_of_instance(typing_env.as_query_input((instance, ty::List::empty())))
115    else {
116        // An error will be reported during codegen if we cannot determine the ABI of this
117        // function.
118        tcx.dcx().delayed_bug("ABI computation failure should lead to compilation failure");
119        return;
120    };
121    // Unlike the call-site check, we do also check "Rust" ABI functions here.
122    // This should never trigger, *except* if we start making use of vector registers
123    // for the "Rust" ABI and the user disables those vector registers (which should trigger a
124    // warning as that's clearly disabling a "required" target feature for this target).
125    // Using such a function is where disabling the vector register actually can start leading
126    // to soundness issues, so erroring here seems good.
127    let loc = || {
128        let def_id = instance.def_id();
129        (
130            tcx.def_span(def_id),
131            def_id.as_local().map(|did| tcx.local_def_id_to_hir_id(did)).unwrap_or(CRATE_HIR_ID),
132        )
133    };
134    do_check_unsized_params(tcx, abi, /*is_call*/ false, loc);
135    do_check_simd_vector_abi(tcx, abi, instance.def_id(), /*is_call*/ false, loc);
136}
137
138/// Check the ABI at a call site, emitting an error when:
139///
140/// - a non-rustic ABI uses unsized parameters
141/// - the signature requires target features that are not enabled
142fn check_call_site_abi<'tcx>(
143    tcx: TyCtxt<'tcx>,
144    callee: Ty<'tcx>,
145    caller: InstanceKind<'tcx>,
146    loc: impl Fn() -> (Span, HirId) + Copy,
147) {
148    if callee.fn_sig(tcx).abi().is_rustic_abi() {
149        // We directly handle the soundness of Rust ABIs -- so let's skip the majority of
150        // call sites to avoid a perf regression.
151        return;
152    }
153    let typing_env = ty::TypingEnv::fully_monomorphized();
154    let callee_abi = match *callee.kind() {
155        ty::FnPtr(..) => {
156            tcx.fn_abi_of_fn_ptr(typing_env.as_query_input((callee.fn_sig(tcx), ty::List::empty())))
157        }
158        ty::FnDef(def_id, args) => {
159            // Intrinsics are handled separately by the compiler.
160            if tcx.intrinsic(def_id).is_some() {
161                return;
162            }
163            let instance = ty::Instance::expect_resolve(tcx, typing_env, def_id, args, DUMMY_SP);
164            tcx.fn_abi_of_instance(typing_env.as_query_input((instance, ty::List::empty())))
165        }
166        _ => {
167            panic!("Invalid function call");
168        }
169    };
170
171    let Ok(callee_abi) = callee_abi else {
172        // ABI failed to compute; this will not get through codegen.
173        return;
174    };
175    do_check_unsized_params(tcx, callee_abi, /*is_call*/ true, loc);
176    do_check_simd_vector_abi(tcx, callee_abi, caller.def_id(), /*is_call*/ true, loc);
177}
178
179fn check_callees_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, body: &mir::Body<'tcx>) {
180    // Check all function call terminators.
181    for (bb, _data) in traversal::mono_reachable(body, tcx, instance) {
182        let terminator = body.basic_blocks[bb].terminator();
183        match terminator.kind {
184            mir::TerminatorKind::Call { ref func, ref fn_span, .. }
185            | mir::TerminatorKind::TailCall { ref func, ref fn_span, .. } => {
186                let callee_ty = func.ty(body, tcx);
187                let callee_ty = instance.instantiate_mir_and_normalize_erasing_regions(
188                    tcx,
189                    ty::TypingEnv::fully_monomorphized(),
190                    ty::EarlyBinder::bind(callee_ty),
191                );
192                check_call_site_abi(tcx, callee_ty, body.source.instance, || {
193                    let loc = Location {
194                        block: bb,
195                        statement_index: body.basic_blocks[bb].statements.len(),
196                    };
197                    (
198                        *fn_span,
199                        body.source_info(loc)
200                            .scope
201                            .lint_root(&body.source_scopes)
202                            .unwrap_or(CRATE_HIR_ID),
203                    )
204                });
205            }
206            _ => {}
207        }
208    }
209}
210
211pub(crate) fn check_feature_dependent_abi<'tcx>(
212    tcx: TyCtxt<'tcx>,
213    instance: Instance<'tcx>,
214    body: &'tcx mir::Body<'tcx>,
215) {
216    check_instance_abi(tcx, instance);
217    check_callees_abi(tcx, instance, body);
218}