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, RegKind, X86Call};
4use rustc_hir::{CRATE_HIR_ID, HirId};
5use rustc_middle::mir::{self, Location, traversal};
6use rustc_middle::ty::layout::LayoutCx;
7use rustc_middle::ty::{self, Instance, InstanceKind, Ty, TyCtxt, TypingEnv};
8use rustc_session::lint::builtin::WASM_C_ABI;
9use rustc_span::def_id::DefId;
10use rustc_span::{DUMMY_SP, Span, Symbol, sym};
11use rustc_target::callconv::{ArgAbi, FnAbi, PassMode};
12use rustc_target::spec::{HasWasmCAbiOpt, WasmCAbi};
13
14use crate::errors;
15
16fn uses_vector_registers(mode: &PassMode, repr: &BackendRepr) -> bool {
17    match mode {
18        PassMode::Ignore | PassMode::Indirect { .. } => false,
19        PassMode::Cast { pad_i32: _, cast } => {
20            cast.prefix.iter().any(|r| r.is_some_and(|x| x.kind == RegKind::Vector))
21                || cast.rest.unit.kind == RegKind::Vector
22        }
23        PassMode::Direct(..) | PassMode::Pair(..) => matches!(repr, BackendRepr::SimdVector { .. }),
24    }
25}
26
27/// Checks whether a certain function ABI is compatible with the target features currently enabled
28/// for a certain function.
29/// `is_call` indicates whether this is a call-site check or a definition-site check;
30/// this is only relevant for the wording in the emitted error.
31fn do_check_simd_vector_abi<'tcx>(
32    tcx: TyCtxt<'tcx>,
33    abi: &FnAbi<'tcx, Ty<'tcx>>,
34    def_id: DefId,
35    is_call: bool,
36    loc: impl Fn() -> (Span, HirId),
37) {
38    let feature_def = tcx.sess.target.features_for_correct_vector_abi();
39    let codegen_attrs = tcx.codegen_fn_attrs(def_id);
40    let have_feature = |feat: Symbol| {
41        tcx.sess.unstable_target_features.contains(&feat)
42            || codegen_attrs.target_features.iter().any(|x| x.name == feat)
43    };
44    for arg_abi in abi.args.iter().chain(std::iter::once(&abi.ret)) {
45        let size = arg_abi.layout.size;
46        if uses_vector_registers(&arg_abi.mode, &arg_abi.layout.backend_repr) {
47            // Find the first feature that provides at least this vector size.
48            let feature = match feature_def.iter().find(|(bits, _)| size.bits() <= *bits) {
49                Some((_, feature)) => feature,
50                None => {
51                    let (span, _hir_id) = loc();
52                    tcx.dcx().emit_err(errors::AbiErrorUnsupportedVectorType {
53                        span,
54                        ty: arg_abi.layout.ty,
55                        is_call,
56                    });
57                    continue;
58                }
59            };
60            if !have_feature(Symbol::intern(feature)) {
61                // Emit error.
62                let (span, _hir_id) = loc();
63                tcx.dcx().emit_err(errors::AbiErrorDisabledVectorType {
64                    span,
65                    required_feature: feature,
66                    ty: arg_abi.layout.ty,
67                    is_call,
68                });
69            }
70        }
71    }
72    // The `vectorcall` ABI is special in that it requires SSE2 no matter which types are being passed.
73    if abi.conv == CanonAbi::X86(X86Call::Vectorcall) && !have_feature(sym::sse2) {
74        let (span, _hir_id) = loc();
75        tcx.dcx().emit_err(errors::AbiRequiredTargetFeature {
76            span,
77            required_feature: "sse2",
78            abi: "vectorcall",
79            is_call,
80        });
81    }
82}
83
84/// Determines whether the given argument is passed the same way on the old and new wasm ABIs.
85fn wasm_abi_safe<'tcx>(tcx: TyCtxt<'tcx>, arg: &ArgAbi<'tcx, Ty<'tcx>>) -> bool {
86    if matches!(arg.layout.backend_repr, BackendRepr::Scalar(_)) {
87        return true;
88    }
89
90    // Both the old and the new ABIs treat vector types like `v128` the same
91    // way.
92    if uses_vector_registers(&arg.mode, &arg.layout.backend_repr) {
93        return true;
94    }
95
96    // This matches `unwrap_trivial_aggregate` in the wasm ABI logic.
97    if arg.layout.is_aggregate() {
98        let cx = LayoutCx::new(tcx, TypingEnv::fully_monomorphized());
99        if let Some(unit) = arg.layout.homogeneous_aggregate(&cx).ok().and_then(|ha| ha.unit()) {
100            let size = arg.layout.size;
101            // Ensure there's just a single `unit` element in `arg`.
102            if unit.size == size {
103                return true;
104            }
105        }
106    }
107
108    // Zero-sized types are dropped in both ABIs, so they're safe
109    if arg.layout.is_zst() {
110        return true;
111    }
112
113    false
114}
115
116/// Warns against usage of `extern "C"` on wasm32-unknown-unknown that is affected by the
117/// ABI transition.
118fn do_check_wasm_abi<'tcx>(
119    tcx: TyCtxt<'tcx>,
120    abi: &FnAbi<'tcx, Ty<'tcx>>,
121    is_call: bool,
122    loc: impl Fn() -> (Span, HirId),
123) {
124    // Only proceed for `extern "C" fn` on wasm32-unknown-unknown (same check as what
125    // `adjust_for_foreign_abi` uses to call `compute_wasm_abi_info`), and only proceed if
126    // `wasm_c_abi_opt` indicates we should emit the lint.
127    if !(tcx.sess.target.arch == "wasm32"
128        && tcx.sess.target.os == "unknown"
129        && tcx.wasm_c_abi_opt() == WasmCAbi::Legacy { with_lint: true }
130        && abi.conv == CanonAbi::C)
131    {
132        return;
133    }
134    // Warn against all types whose ABI will change. Return values are not affected by this change.
135    for arg_abi in abi.args.iter() {
136        if wasm_abi_safe(tcx, arg_abi) {
137            continue;
138        }
139        let (span, hir_id) = loc();
140        tcx.emit_node_span_lint(
141            WASM_C_ABI,
142            hir_id,
143            span,
144            errors::WasmCAbiTransition { ty: arg_abi.layout.ty, is_call },
145        );
146        // Let's only warn once per function.
147        break;
148    }
149}
150
151/// Checks that the ABI of a given instance of a function does not contain vector-passed arguments
152/// or return values for which the corresponding target feature is not enabled.
153fn check_instance_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) {
154    let typing_env = ty::TypingEnv::fully_monomorphized();
155    let Ok(abi) = tcx.fn_abi_of_instance(typing_env.as_query_input((instance, ty::List::empty())))
156    else {
157        // An error will be reported during codegen if we cannot determine the ABI of this
158        // function.
159        tcx.dcx().delayed_bug("ABI computation failure should lead to compilation failure");
160        return;
161    };
162    // Unlike the call-site check, we do also check "Rust" ABI functions here.
163    // This should never trigger, *except* if we start making use of vector registers
164    // for the "Rust" ABI and the user disables those vector registers (which should trigger a
165    // warning as that's clearly disabling a "required" target feature for this target).
166    // Using such a function is where disabling the vector register actually can start leading
167    // to soundness issues, so erroring here seems good.
168    let loc = || {
169        let def_id = instance.def_id();
170        (
171            tcx.def_span(def_id),
172            def_id.as_local().map(|did| tcx.local_def_id_to_hir_id(did)).unwrap_or(CRATE_HIR_ID),
173        )
174    };
175    do_check_simd_vector_abi(tcx, abi, instance.def_id(), /*is_call*/ false, loc);
176    do_check_wasm_abi(tcx, abi, /*is_call*/ false, loc);
177}
178
179/// Checks that a call expression does not try to pass a vector-passed argument which requires a
180/// target feature that the caller does not have, as doing so causes UB because of ABI mismatch.
181fn check_call_site_abi<'tcx>(
182    tcx: TyCtxt<'tcx>,
183    callee: Ty<'tcx>,
184    caller: InstanceKind<'tcx>,
185    loc: impl Fn() -> (Span, HirId) + Copy,
186) {
187    if callee.fn_sig(tcx).abi().is_rustic_abi() {
188        // We directly handle the soundness of Rust ABIs -- so let's skip the majority of
189        // call sites to avoid a perf regression.
190        return;
191    }
192    let typing_env = ty::TypingEnv::fully_monomorphized();
193    let callee_abi = match *callee.kind() {
194        ty::FnPtr(..) => {
195            tcx.fn_abi_of_fn_ptr(typing_env.as_query_input((callee.fn_sig(tcx), ty::List::empty())))
196        }
197        ty::FnDef(def_id, args) => {
198            // Intrinsics are handled separately by the compiler.
199            if tcx.intrinsic(def_id).is_some() {
200                return;
201            }
202            let instance = ty::Instance::expect_resolve(tcx, typing_env, def_id, args, DUMMY_SP);
203            tcx.fn_abi_of_instance(typing_env.as_query_input((instance, ty::List::empty())))
204        }
205        _ => {
206            panic!("Invalid function call");
207        }
208    };
209
210    let Ok(callee_abi) = callee_abi else {
211        // ABI failed to compute; this will not get through codegen.
212        return;
213    };
214    do_check_simd_vector_abi(tcx, callee_abi, caller.def_id(), /*is_call*/ true, loc);
215    do_check_wasm_abi(tcx, callee_abi, /*is_call*/ true, loc);
216}
217
218fn check_callees_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, body: &mir::Body<'tcx>) {
219    // Check all function call terminators.
220    for (bb, _data) in traversal::mono_reachable(body, tcx, instance) {
221        let terminator = body.basic_blocks[bb].terminator();
222        match terminator.kind {
223            mir::TerminatorKind::Call { ref func, ref fn_span, .. }
224            | mir::TerminatorKind::TailCall { ref func, ref fn_span, .. } => {
225                let callee_ty = func.ty(body, tcx);
226                let callee_ty = instance.instantiate_mir_and_normalize_erasing_regions(
227                    tcx,
228                    ty::TypingEnv::fully_monomorphized(),
229                    ty::EarlyBinder::bind(callee_ty),
230                );
231                check_call_site_abi(tcx, callee_ty, body.source.instance, || {
232                    let loc = Location {
233                        block: bb,
234                        statement_index: body.basic_blocks[bb].statements.len(),
235                    };
236                    (
237                        *fn_span,
238                        body.source_info(loc)
239                            .scope
240                            .lint_root(&body.source_scopes)
241                            .unwrap_or(CRATE_HIR_ID),
242                    )
243                });
244            }
245            _ => {}
246        }
247    }
248}
249
250pub(crate) fn check_feature_dependent_abi<'tcx>(
251    tcx: TyCtxt<'tcx>,
252    instance: Instance<'tcx>,
253    body: &'tcx mir::Body<'tcx>,
254) {
255    check_instance_abi(tcx, instance);
256    check_callees_abi(tcx, instance, body);
257}