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