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::errors;
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: _, cast }
28            if cast
29                .prefix
30                .iter()
31                .any(|r| r.is_some_and(|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.unstable_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                let feature_def = tcx.sess.target.features_for_correct_fixed_length_vector_abi();
72                // Find the first feature that provides at least this vector size.
73                let feature = match feature_def.iter().find(|(bits, _)| size.bits() <= *bits) {
74                    Some((_, feature)) => feature,
75                    None => {
76                        let (span, _hir_id) = loc();
77                        tcx.dcx().emit_err(errors::AbiErrorUnsupportedVectorType {
78                            span,
79                            ty: arg_abi.layout.ty,
80                            is_call,
81                        });
82                        continue;
83                    }
84                };
85                if !feature.is_empty() && !have_feature(Symbol::intern(feature)) {
86                    let (span, _hir_id) = loc();
87                    tcx.dcx().emit_err(errors::AbiErrorDisabledVectorType {
88                        span,
89                        required_feature: feature,
90                        ty: arg_abi.layout.ty,
91                        is_call,
92                        is_scalable: false,
93                    });
94                }
95            }
96            UsesVectorRegisters::ScalableVector => {
97                let Some(required_feature) =
98                    tcx.sess.target.features_for_correct_scalable_vector_abi()
99                else {
100                    continue;
101                };
102                if !required_feature.is_empty() && !have_feature(Symbol::intern(required_feature)) {
103                    let (span, _) = loc();
104                    tcx.dcx().emit_err(errors::AbiErrorDisabledVectorType {
105                        span,
106                        required_feature,
107                        ty: arg_abi.layout.ty,
108                        is_call,
109                        is_scalable: true,
110                    });
111                }
112            }
113            UsesVectorRegisters::No => {
114                continue;
115            }
116        }
117    }
118    // The `vectorcall` ABI is special in that it requires SSE2 no matter which types are being passed.
119    if abi.conv == CanonAbi::X86(X86Call::Vectorcall) && !have_feature(sym::sse2) {
120        let (span, _hir_id) = loc();
121        tcx.dcx().emit_err(errors::AbiRequiredTargetFeature {
122            span,
123            required_feature: "sse2",
124            abi: "vectorcall",
125            is_call,
126        });
127    }
128}
129
130/// Emit an error when a non-rustic ABI has unsized parameters.
131/// Unsized types do not have a stable layout, so should not be used with stable ABIs.
132/// `is_call` indicates whether this is a call-site check or a definition-site check;
133/// this is only relevant for the wording in the emitted error.
134fn do_check_unsized_params<'tcx>(
135    tcx: TyCtxt<'tcx>,
136    fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
137    is_call: bool,
138    loc: impl Fn() -> (Span, HirId),
139) {
140    // Unsized parameters are allowed with the (unstable) "Rust" (and similar) ABIs.
141    if fn_abi.conv.is_rustic_abi() {
142        return;
143    }
144
145    for arg_abi in fn_abi.args.iter() {
146        if !arg_abi.layout.layout.is_sized() {
147            let (span, _hir_id) = loc();
148            tcx.dcx().emit_err(errors::AbiErrorUnsupportedUnsizedParameter {
149                span,
150                ty: arg_abi.layout.ty,
151                is_call,
152            });
153        }
154    }
155}
156
157/// Checks the ABI of an Instance, emitting an error when:
158///
159/// - a non-rustic ABI uses unsized parameters
160/// - the signature requires target features that are not enabled
161fn check_instance_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) {
162    let typing_env = ty::TypingEnv::fully_monomorphized();
163    let ty = instance.ty(tcx, typing_env);
164    if ty.is_fn() && ty.fn_sig(tcx).abi() == ExternAbi::Unadjusted {
165        // We disable all checks for the unadjusted ABI to allow linking to arbitrary LLVM
166        // intrinsics
167        return;
168    }
169    let Ok(abi) = tcx.fn_abi_of_instance(typing_env.as_query_input((instance, ty::List::empty())))
170    else {
171        // An error will be reported during codegen if we cannot determine the ABI of this
172        // function.
173        tcx.dcx().delayed_bug("ABI computation failure should lead to compilation failure");
174        return;
175    };
176    // Unlike the call-site check, we do also check "Rust" ABI functions here.
177    // This should never trigger, *except* if we start making use of vector registers
178    // for the "Rust" ABI and the user disables those vector registers (which should trigger a
179    // warning as that's clearly disabling a "required" target feature for this target).
180    // Using such a function is where disabling the vector register actually can start leading
181    // to soundness issues, so erroring here seems good.
182    let loc = || {
183        let def_id = instance.def_id();
184        (
185            tcx.def_span(def_id),
186            def_id.as_local().map(|did| tcx.local_def_id_to_hir_id(did)).unwrap_or(CRATE_HIR_ID),
187        )
188    };
189    do_check_unsized_params(tcx, abi, /*is_call*/ false, loc);
190    do_check_simd_vector_abi(tcx, abi, instance.def_id(), /*is_call*/ false, loc);
191}
192
193/// Check the ABI at a call site, emitting an error when:
194///
195/// - a non-rustic ABI uses unsized parameters
196/// - the signature requires target features that are not enabled
197fn check_call_site_abi<'tcx>(
198    tcx: TyCtxt<'tcx>,
199    callee: Ty<'tcx>,
200    caller: InstanceKind<'tcx>,
201    loc: impl Fn() -> (Span, HirId) + Copy,
202) {
203    let extern_abi = callee.fn_sig(tcx).abi();
204    if extern_abi.is_rustic_abi() || extern_abi == ExternAbi::Unadjusted {
205        // We directly handle the soundness of Rust ABIs -- so let's skip the majority of
206        // call sites to avoid a perf regression.
207        // We disable all checks for the unadjusted ABI to allow linking to arbitrary LLVM
208        // intrinsics
209        return;
210    }
211    let typing_env = ty::TypingEnv::fully_monomorphized();
212    let callee_abi = match *callee.kind() {
213        ty::FnPtr(..) => {
214            tcx.fn_abi_of_fn_ptr(typing_env.as_query_input((callee.fn_sig(tcx), ty::List::empty())))
215        }
216        ty::FnDef(def_id, args) => {
217            // Intrinsics are handled separately by the compiler.
218            if tcx.intrinsic(def_id).is_some() {
219                return;
220            }
221            let instance = ty::Instance::expect_resolve(tcx, typing_env, def_id, args, DUMMY_SP);
222            tcx.fn_abi_of_instance(typing_env.as_query_input((instance, ty::List::empty())))
223        }
224        _ => {
225            { ::core::panicking::panic_fmt(format_args!("Invalid function call")); };panic!("Invalid function call");
226        }
227    };
228
229    let Ok(callee_abi) = callee_abi else {
230        // ABI failed to compute; this will not get through codegen.
231        return;
232    };
233    do_check_unsized_params(tcx, callee_abi, /*is_call*/ true, loc);
234    do_check_simd_vector_abi(tcx, callee_abi, caller.def_id(), /*is_call*/ true, loc);
235}
236
237fn check_callees_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, body: &mir::Body<'tcx>) {
238    // Check all function call terminators.
239    for (bb, _data) in traversal::mono_reachable(body, tcx, instance) {
240        let terminator = body.basic_blocks[bb].terminator();
241        match terminator.kind {
242            mir::TerminatorKind::Call { ref func, ref fn_span, .. }
243            | mir::TerminatorKind::TailCall { ref func, ref fn_span, .. } => {
244                let callee_ty = func.ty(body, tcx);
245                let callee_ty = instance.instantiate_mir_and_normalize_erasing_regions(
246                    tcx,
247                    ty::TypingEnv::fully_monomorphized(),
248                    ty::EarlyBinder::bind(callee_ty),
249                );
250                check_call_site_abi(tcx, callee_ty, body.source.instance, || {
251                    let loc = Location {
252                        block: bb,
253                        statement_index: body.basic_blocks[bb].statements.len(),
254                    };
255                    (
256                        *fn_span,
257                        body.source_info(loc)
258                            .scope
259                            .lint_root(&body.source_scopes)
260                            .unwrap_or(CRATE_HIR_ID),
261                    )
262                });
263            }
264            _ => {}
265        }
266    }
267}
268
269pub(crate) fn check_feature_dependent_abi<'tcx>(
270    tcx: TyCtxt<'tcx>,
271    instance: Instance<'tcx>,
272    body: &'tcx mir::Body<'tcx>,
273) {
274    check_instance_abi(tcx, instance);
275    check_callees_abi(tcx, instance, body);
276}