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