Skip to main content

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, ExternAbi, RegKind, X86Call};
4use rustc_hir::{CRATE_HIR_ID, HirId};
5use rustc_middle::mir::{self, Location, traversal};
6use rustc_middle::ty::layout::{FnAbiRequest, codegen_handle_fn_abi_err};
7use rustc_middle::ty::{self, Instance, InstanceKind, Ty, TyCtxt};
8use rustc_span::def_id::DefId;
9use rustc_span::{DUMMY_SP, Span, Symbol, sym};
10use rustc_target::callconv::{FnAbi, PassMode};
11
12use crate::diagnostics;
13
14/// Are vector registers used?
15enum UsesVectorRegisters {
16    /// e.g. `neon`
17    FixedVector,
18    /// e.g. `sve`
19    ScalableVector,
20    No,
21}
22
23/// Determines whether the combination of `mode` and `repr` will use fixed vector registers,
24/// scalable vector registers or no vector registers.
25fn passes_vectors_by_value(mode: &PassMode, repr: &BackendRepr) -> UsesVectorRegisters {
26    match mode {
27        PassMode::Ignore | PassMode::Indirect { .. } => UsesVectorRegisters::No,
28        PassMode::Cast { pad_i32_count: _, cast }
29            if cast.prefix.iter().any(|x| #[allow(non_exhaustive_omitted_patterns)] match x.kind {
    RegKind::Vector { .. } => true,
    _ => false,
}matches!(x.kind, RegKind::Vector { .. }))
30                || #[allow(non_exhaustive_omitted_patterns)] match cast.rest.unit.kind {
    RegKind::Vector { .. } => true,
    _ => false,
}matches!(cast.rest.unit.kind, RegKind::Vector { .. }) =>
31        {
32            UsesVectorRegisters::FixedVector
33        }
34        PassMode::Direct(..) | PassMode::Pair(..)
35            if #[allow(non_exhaustive_omitted_patterns)] match repr {
    BackendRepr::SimdVector { .. } => true,
    _ => false,
}matches!(repr, BackendRepr::SimdVector { .. }) =>
36        {
37            UsesVectorRegisters::FixedVector
38        }
39        PassMode::Direct(..) | PassMode::Pair(..)
40            if #[allow(non_exhaustive_omitted_patterns)] match repr {
    BackendRepr::SimdScalableVector { .. } => true,
    _ => false,
}matches!(repr, BackendRepr::SimdScalableVector { .. }) =>
41        {
42            UsesVectorRegisters::ScalableVector
43        }
44        _ => UsesVectorRegisters::No,
45    }
46}
47
48/// Checks whether a certain function ABI is compatible with the target features currently enabled
49/// for a certain function.
50/// `is_call` indicates whether this is a call-site check or a definition-site check;
51/// this is only relevant for the wording in the emitted error.
52fn do_check_simd_vector_abi<'tcx>(
53    tcx: TyCtxt<'tcx>,
54    abi: &FnAbi<'tcx, Ty<'tcx>>,
55    def_id: DefId,
56    is_call: bool,
57    loc: impl Fn() -> (Span, HirId),
58) {
59    let codegen_attrs = tcx.codegen_fn_attrs(def_id);
60    let have_feature = |feat: Symbol| {
61        let target_feats = tcx.sess.internal_target_features.contains(&feat);
62        let fn_feats = codegen_attrs.target_features.iter().any(|x| x.name == feat);
63        target_feats || fn_feats
64    };
65    for arg_abi in abi.args.iter().chain(std::iter::once(&abi.ret)) {
66        let size = arg_abi.layout.size;
67        match passes_vectors_by_value(&arg_abi.mode, &arg_abi.layout.backend_repr) {
68            UsesVectorRegisters::FixedVector => {
69                // Some targets use homogeneous aggregates, where the unit size counts.
70                let unit_size = match &arg_abi.mode {
71                    PassMode::Cast { pad_i32_count: _, cast } if cast.prefix.is_empty() => {
72                        cast.rest.unit.size
73                    }
74                    _ => size,
75                };
76
77                let feature_def = tcx.sess.target.features_for_correct_fixed_length_vector_abi();
78                // Find the first feature that provides at least this vector size.
79                let feature = match feature_def.iter().find(|(bits, _)| unit_size.bits() <= *bits) {
80                    Some((_, feature)) => feature,
81                    None => {
82                        let (span, _hir_id) = loc();
83                        tcx.dcx().emit_err(diagnostics::AbiErrorUnsupportedVectorType {
84                            span,
85                            ty: arg_abi.layout.ty,
86                            is_call,
87                        });
88                        continue;
89                    }
90                };
91                if !feature.is_empty() && !have_feature(Symbol::intern(feature)) {
92                    let (span, _hir_id) = loc();
93                    tcx.dcx().emit_err(diagnostics::AbiErrorDisabledVectorType {
94                        span,
95                        required_feature: feature,
96                        abi: abi.conv.to_string(),
97                        ty: arg_abi.layout.ty,
98                        is_call,
99                        is_scalable: false,
100                    });
101                }
102            }
103            UsesVectorRegisters::ScalableVector => {
104                let Some(required_feature) =
105                    tcx.sess.target.features_for_correct_scalable_vector_abi()
106                else {
107                    continue;
108                };
109                if !required_feature.is_empty() && !have_feature(Symbol::intern(required_feature)) {
110                    let (span, _) = loc();
111                    tcx.dcx().emit_err(diagnostics::AbiErrorDisabledVectorType {
112                        span,
113                        required_feature,
114                        abi: abi.conv.to_string(),
115                        ty: arg_abi.layout.ty,
116                        is_call,
117                        is_scalable: true,
118                    });
119                }
120            }
121            UsesVectorRegisters::No => {
122                continue;
123            }
124        }
125    }
126    // The `vectorcall` ABI is special in that it requires SSE2 no matter which types are being passed.
127    if abi.conv == CanonAbi::X86(X86Call::Vectorcall) && !have_feature(sym::sse2) {
128        let (span, _hir_id) = loc();
129        tcx.dcx().emit_err(diagnostics::AbiRequiredTargetFeature {
130            span,
131            required_feature: "sse2",
132            abi: "vectorcall",
133            is_call,
134        });
135    }
136}
137
138/// Emit an error when a non-rustic ABI has unsized parameters.
139/// Unsized types do not have a stable layout, so should not be used with stable ABIs.
140/// `is_call` indicates whether this is a call-site check or a definition-site check;
141/// this is only relevant for the wording in the emitted error.
142fn do_check_unsized_params<'tcx>(
143    tcx: TyCtxt<'tcx>,
144    fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
145    is_call: bool,
146    loc: impl Fn() -> (Span, HirId),
147) {
148    // Unsized parameters are allowed with the (unstable) "Rust" (and similar) ABIs.
149    if fn_abi.conv.is_rustic_abi() {
150        return;
151    }
152
153    for arg_abi in fn_abi.args.iter() {
154        if !arg_abi.layout.layout.is_sized() {
155            let (span, _hir_id) = loc();
156            tcx.dcx().emit_err(diagnostics::AbiErrorUnsupportedUnsizedParameter {
157                span,
158                ty: arg_abi.layout.ty,
159                is_call,
160            });
161        }
162    }
163}
164
165/// Checks the ABI of an Instance, emitting an error when:
166///
167/// - a non-rustic ABI uses unsized parameters
168/// - the signature requires target features that are not enabled
169fn check_instance_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) {
170    let typing_env = ty::TypingEnv::fully_monomorphized();
171    let ty = instance.ty(tcx, typing_env);
172    if ty.is_fn() && ty.fn_sig(tcx).abi() == ExternAbi::LlvmIntrinsic {
173        // We disable all checks for the llvm-intrinsic ABI to allow linking to arbitrary
174        // LLVM intrinsics
175        return;
176    }
177    let abi = match tcx.fn_abi_of_instance(typing_env.as_query_input((instance, ty::List::empty())))
178    {
179        Ok(abi) => abi,
180        Err(err) => {
181            codegen_handle_fn_abi_err(
182                tcx,
183                *err,
184                tcx.def_span(instance.def_id()),
185                FnAbiRequest::OfInstance { instance, extra_args: ty::List::empty() },
186            );
187            // ABI failed to compute; this will not get through codegen.
188            return;
189        }
190    };
191    // Unlike the call-site check, we do also check "Rust" ABI functions here. This can actually
192    // trigger due to scalable vectors being required for the "Rust" ABI for some types.
193    let loc = || {
194        let def_id = instance.def_id();
195        (
196            tcx.def_span(def_id),
197            def_id.as_local().map(|did| tcx.local_def_id_to_hir_id(did)).unwrap_or(CRATE_HIR_ID),
198        )
199    };
200    do_check_unsized_params(tcx, abi, /*is_call*/ false, loc);
201    do_check_simd_vector_abi(tcx, abi, instance.def_id(), /*is_call*/ false, loc);
202}
203
204/// Check the ABI at a call site, emitting an error when:
205///
206/// - a non-rustic ABI uses unsized parameters
207/// - the signature requires target features that are not enabled
208fn check_call_site_abi<'tcx>(
209    tcx: TyCtxt<'tcx>,
210    callee: Ty<'tcx>,
211    caller: InstanceKind<'tcx>,
212    loc: impl Fn() -> (Span, HirId) + Copy,
213) {
214    let extern_abi = callee.fn_sig(tcx).abi();
215    if extern_abi.is_rustic_abi() || extern_abi == ExternAbi::LlvmIntrinsic {
216        // We directly handle the soundness of Rust ABIs -- so let's skip the majority of
217        // call sites to avoid a perf regression.
218        // FIXME(#161753, rustc_scalable_vector/stdarch_aarch64_sve): this is unsound! Above we
219        // argue we need the callee-site check for the Rust ABI; we need the call-site check here as
220        // well then.
221        // We disable all checks for the llvm-intrinsic ABI to allow linking to arbitrary
222        // LLVM intrinsics. The caller is responsible for ensuring the ABI makes sense.
223        return;
224    }
225    let typing_env = ty::TypingEnv::fully_monomorphized();
226    let callee_abi = match *callee.kind() {
227        ty::FnPtr(..) => {
228            let sig = callee.fn_sig(tcx);
229            match tcx.fn_abi_of_fn_ptr(typing_env.as_query_input((sig, ty::List::empty()))) {
230                Ok(callee_abi) => callee_abi,
231                Err(err) => {
232                    codegen_handle_fn_abi_err(
233                        tcx,
234                        *err,
235                        loc().0,
236                        FnAbiRequest::OfFnPtr { sig, extra_args: ty::List::empty() },
237                    );
238                    // ABI failed to compute; this will not get through codegen.
239                    return;
240                }
241            }
242        }
243        ty::FnDef(def_id, args) => {
244            // Intrinsics are handled separately by the compiler.
245            if tcx.intrinsic(def_id).is_some() {
246                return;
247            }
248            let instance = ty::Instance::expect_resolve(
249                tcx,
250                typing_env,
251                def_id,
252                args.no_bound_vars().unwrap(),
253                DUMMY_SP,
254            );
255            if let InstanceKind::LlvmIntrinsic(..) = instance.def {
256                // LLVM intrinsics don't have an ABI, so there is nothing to check.
257                return;
258            }
259            match tcx.fn_abi_of_instance(typing_env.as_query_input((instance, ty::List::empty()))) {
260                Ok(callee_abi) => callee_abi,
261                Err(err) => {
262                    codegen_handle_fn_abi_err(
263                        tcx,
264                        *err,
265                        loc().0,
266                        FnAbiRequest::OfInstance { instance, extra_args: ty::List::empty() },
267                    );
268                    // ABI failed to compute; this will not get through codegen.
269                    return;
270                }
271            }
272        }
273        _ => {
274            { ::core::panicking::panic_fmt(format_args!("Invalid function call")); };panic!("Invalid function call");
275        }
276    };
277
278    do_check_unsized_params(tcx, callee_abi, /*is_call*/ true, loc);
279    do_check_simd_vector_abi(tcx, callee_abi, caller.def_id(), /*is_call*/ true, loc);
280}
281
282fn check_callees_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, body: &mir::Body<'tcx>) {
283    // Check all function call terminators.
284    for (bb, _data) in traversal::mono_reachable(body, tcx, instance) {
285        let terminator = body.basic_blocks[bb].terminator();
286        match terminator.kind {
287            mir::TerminatorKind::Call { ref func, ref fn_span, .. }
288            | mir::TerminatorKind::TailCall { ref func, ref fn_span, .. } => {
289                let callee_ty = func.ty(body, tcx);
290                let callee_ty = instance.instantiate_mir_and_normalize_erasing_regions(
291                    tcx,
292                    ty::TypingEnv::fully_monomorphized(),
293                    ty::EarlyBinder::bind(tcx, callee_ty),
294                );
295                check_call_site_abi(tcx, callee_ty, body.source.instance, || {
296                    let loc = Location {
297                        block: bb,
298                        statement_index: body.basic_blocks[bb].statements.len(),
299                    };
300                    (
301                        *fn_span,
302                        body.source_info(loc)
303                            .scope
304                            .lint_root(&body.source_scopes)
305                            .unwrap_or(CRATE_HIR_ID),
306                    )
307                });
308            }
309            _ => {}
310        }
311    }
312}
313
314pub(crate) fn check_feature_dependent_abi<'tcx>(
315    tcx: TyCtxt<'tcx>,
316    instance: Instance<'tcx>,
317    body: &'tcx mir::Body<'tcx>,
318) {
319    check_instance_abi(tcx, instance);
320    check_callees_abi(tcx, instance, body);
321}