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, RegKind};
4use rustc_hir::CRATE_HIR_ID;
5use rustc_middle::mir::{self, traversal};
6use rustc_middle::ty::{self, Instance, InstanceKind, Ty, TyCtxt};
7use rustc_session::lint::builtin::ABI_UNSUPPORTED_VECTOR_TYPES;
8use rustc_span::def_id::DefId;
9use rustc_span::{DUMMY_SP, Span, Symbol, sym};
10use rustc_target::callconv::{Conv, FnAbi, PassMode};
11
12use crate::errors;
13
14fn uses_vector_registers(mode: &PassMode, repr: &BackendRepr) -> bool {
15    match mode {
16        PassMode::Ignore | PassMode::Indirect { .. } => false,
17        PassMode::Cast { pad_i32: _, cast } => {
18            cast.prefix.iter().any(|r| r.is_some_and(|x| x.kind == RegKind::Vector))
19                || cast.rest.unit.kind == RegKind::Vector
20        }
21        PassMode::Direct(..) | PassMode::Pair(..) => matches!(repr, BackendRepr::SimdVector { .. }),
22    }
23}
24
25/// Checks whether a certain function ABI is compatible with the target features currently enabled
26/// for a certain function.
27/// `is_call` indicates whether this is a call-site check or a definition-site check;
28/// this is only relevant for the wording in the emitted error.
29fn do_check_abi<'tcx>(
30    tcx: TyCtxt<'tcx>,
31    abi: &FnAbi<'tcx, Ty<'tcx>>,
32    def_id: DefId,
33    is_call: bool,
34    span: impl Fn() -> Span,
35) {
36    let feature_def = tcx.sess.target.features_for_correct_vector_abi();
37    let codegen_attrs = tcx.codegen_fn_attrs(def_id);
38    let have_feature = |feat: Symbol| {
39        tcx.sess.unstable_target_features.contains(&feat)
40            || codegen_attrs.target_features.iter().any(|x| x.name == feat)
41    };
42    for arg_abi in abi.args.iter().chain(std::iter::once(&abi.ret)) {
43        let size = arg_abi.layout.size;
44        if uses_vector_registers(&arg_abi.mode, &arg_abi.layout.backend_repr) {
45            // Find the first feature that provides at least this vector size.
46            let feature = match feature_def.iter().find(|(bits, _)| size.bits() <= *bits) {
47                Some((_, feature)) => feature,
48                None => {
49                    let span = span();
50                    tcx.emit_node_span_lint(
51                        ABI_UNSUPPORTED_VECTOR_TYPES,
52                        CRATE_HIR_ID,
53                        span,
54                        errors::AbiErrorUnsupportedVectorType {
55                            span,
56                            ty: arg_abi.layout.ty,
57                            is_call,
58                        },
59                    );
60                    continue;
61                }
62            };
63            if !have_feature(Symbol::intern(feature)) {
64                // Emit error.
65                let span = span();
66                tcx.emit_node_span_lint(
67                    ABI_UNSUPPORTED_VECTOR_TYPES,
68                    CRATE_HIR_ID,
69                    span,
70                    errors::AbiErrorDisabledVectorType {
71                        span,
72                        required_feature: feature,
73                        ty: arg_abi.layout.ty,
74                        is_call,
75                    },
76                );
77            }
78        }
79    }
80    // The `vectorcall` ABI is special in that it requires SSE2 no matter which types are being passed.
81    if abi.conv == Conv::X86VectorCall && !have_feature(sym::sse2) {
82        tcx.dcx().emit_err(errors::AbiRequiredTargetFeature {
83            span: span(),
84            required_feature: "sse2",
85            abi: "vectorcall",
86            is_call,
87        });
88    }
89}
90
91/// Checks that the ABI of a given instance of a function does not contain vector-passed arguments
92/// or return values for which the corresponding target feature is not enabled.
93fn check_instance_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) {
94    let typing_env = ty::TypingEnv::fully_monomorphized();
95    let Ok(abi) = tcx.fn_abi_of_instance(typing_env.as_query_input((instance, ty::List::empty())))
96    else {
97        // An error will be reported during codegen if we cannot determine the ABI of this
98        // function.
99        return;
100    };
101    do_check_abi(
102        tcx,
103        abi,
104        instance.def_id(),
105        /*is_call*/ false,
106        || tcx.def_span(instance.def_id()),
107    )
108}
109
110/// Checks that a call expression does not try to pass a vector-passed argument which requires a
111/// target feature that the caller does not have, as doing so causes UB because of ABI mismatch.
112fn check_call_site_abi<'tcx>(
113    tcx: TyCtxt<'tcx>,
114    callee: Ty<'tcx>,
115    span: Span,
116    caller: InstanceKind<'tcx>,
117) {
118    if callee.fn_sig(tcx).abi().is_rustic_abi() {
119        // we directly handle the soundness of Rust ABIs
120        return;
121    }
122    let typing_env = ty::TypingEnv::fully_monomorphized();
123    let callee_abi = match *callee.kind() {
124        ty::FnPtr(..) => {
125            tcx.fn_abi_of_fn_ptr(typing_env.as_query_input((callee.fn_sig(tcx), ty::List::empty())))
126        }
127        ty::FnDef(def_id, args) => {
128            // Intrinsics are handled separately by the compiler.
129            if tcx.intrinsic(def_id).is_some() {
130                return;
131            }
132            let instance = ty::Instance::expect_resolve(tcx, typing_env, def_id, args, DUMMY_SP);
133            tcx.fn_abi_of_instance(typing_env.as_query_input((instance, ty::List::empty())))
134        }
135        _ => {
136            panic!("Invalid function call");
137        }
138    };
139
140    let Ok(callee_abi) = callee_abi else {
141        // ABI failed to compute; this will not get through codegen.
142        return;
143    };
144    do_check_abi(tcx, callee_abi, caller.def_id(), /*is_call*/ true, || span);
145}
146
147fn check_callees_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, body: &mir::Body<'tcx>) {
148    // Check all function call terminators.
149    for (bb, _data) in traversal::mono_reachable(body, tcx, instance) {
150        let terminator = body.basic_blocks[bb].terminator();
151        match terminator.kind {
152            mir::TerminatorKind::Call { ref func, ref fn_span, .. }
153            | mir::TerminatorKind::TailCall { ref func, ref fn_span, .. } => {
154                let callee_ty = func.ty(body, tcx);
155                let callee_ty = instance.instantiate_mir_and_normalize_erasing_regions(
156                    tcx,
157                    ty::TypingEnv::fully_monomorphized(),
158                    ty::EarlyBinder::bind(callee_ty),
159                );
160                check_call_site_abi(tcx, callee_ty, *fn_span, body.source.instance);
161            }
162            _ => {}
163        }
164    }
165}
166
167pub(crate) fn check_feature_dependent_abi<'tcx>(
168    tcx: TyCtxt<'tcx>,
169    instance: Instance<'tcx>,
170    body: &'tcx mir::Body<'tcx>,
171) {
172    check_instance_abi(tcx, instance);
173    check_callees_abi(tcx, instance, body);
174}