| //! This module ensures that if a function's ABI requires a particular target feature, |
| //! that target feature is enabled both on the callee and all callers. |
| use rustc_abi::{BackendRepr, CanonAbi, RegKind, X86Call}; |
| use rustc_hir::{CRATE_HIR_ID, HirId}; |
| use rustc_middle::mir::{self, Location, traversal}; |
| use rustc_middle::ty::{self, Instance, InstanceKind, Ty, TyCtxt}; |
| use rustc_span::def_id::DefId; |
| use rustc_span::{DUMMY_SP, Span, Symbol, sym}; |
| use rustc_target::callconv::{FnAbi, PassMode}; |
| |
| use crate::errors; |
| |
| /// Are vector registers used? |
| enum UsesVectorRegisters { |
| /// e.g. `neon` |
| FixedVector, |
| /// e.g. `sve` |
| ScalableVector, |
| No, |
| } |
| |
| /// Determines whether the combination of `mode` and `repr` will use fixed vector registers, |
| /// scalable vector registers or no vector registers. |
| fn passes_vectors_by_value(mode: &PassMode, repr: &BackendRepr) -> UsesVectorRegisters { |
| match mode { |
| PassMode::Ignore | PassMode::Indirect { .. } => UsesVectorRegisters::No, |
| PassMode::Cast { pad_i32: _, cast } |
| if cast.prefix.iter().any(|r| r.is_some_and(|x| x.kind == RegKind::Vector)) |
| || cast.rest.unit.kind == RegKind::Vector => |
| { |
| UsesVectorRegisters::FixedVector |
| } |
| PassMode::Direct(..) | PassMode::Pair(..) |
| if matches!(repr, BackendRepr::SimdVector { .. }) => |
| { |
| UsesVectorRegisters::FixedVector |
| } |
| PassMode::Direct(..) | PassMode::Pair(..) |
| if matches!(repr, BackendRepr::ScalableVector { .. }) => |
| { |
| UsesVectorRegisters::ScalableVector |
| } |
| _ => UsesVectorRegisters::No, |
| } |
| } |
| |
| /// Checks whether a certain function ABI is compatible with the target features currently enabled |
| /// for a certain function. |
| /// `is_call` indicates whether this is a call-site check or a definition-site check; |
| /// this is only relevant for the wording in the emitted error. |
| fn do_check_simd_vector_abi<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| abi: &FnAbi<'tcx, Ty<'tcx>>, |
| def_id: DefId, |
| is_call: bool, |
| loc: impl Fn() -> (Span, HirId), |
| ) { |
| let codegen_attrs = tcx.codegen_fn_attrs(def_id); |
| let have_feature = |feat: Symbol| { |
| let target_feats = tcx.sess.unstable_target_features.contains(&feat); |
| let fn_feats = codegen_attrs.target_features.iter().any(|x| x.name == feat); |
| target_feats || fn_feats |
| }; |
| for arg_abi in abi.args.iter().chain(std::iter::once(&abi.ret)) { |
| let size = arg_abi.layout.size; |
| match passes_vectors_by_value(&arg_abi.mode, &arg_abi.layout.backend_repr) { |
| UsesVectorRegisters::FixedVector => { |
| let feature_def = tcx.sess.target.features_for_correct_fixed_length_vector_abi(); |
| // Find the first feature that provides at least this vector size. |
| let feature = match feature_def.iter().find(|(bits, _)| size.bits() <= *bits) { |
| Some((_, feature)) => feature, |
| None => { |
| let (span, _hir_id) = loc(); |
| tcx.dcx().emit_err(errors::AbiErrorUnsupportedVectorType { |
| span, |
| ty: arg_abi.layout.ty, |
| is_call, |
| }); |
| continue; |
| } |
| }; |
| if !feature.is_empty() && !have_feature(Symbol::intern(feature)) { |
| let (span, _hir_id) = loc(); |
| tcx.dcx().emit_err(errors::AbiErrorDisabledVectorType { |
| span, |
| required_feature: feature, |
| ty: arg_abi.layout.ty, |
| is_call, |
| is_scalable: false, |
| }); |
| } |
| } |
| UsesVectorRegisters::ScalableVector => { |
| let Some(required_feature) = |
| tcx.sess.target.features_for_correct_scalable_vector_abi() |
| else { |
| continue; |
| }; |
| if !required_feature.is_empty() && !have_feature(Symbol::intern(required_feature)) { |
| let (span, _) = loc(); |
| tcx.dcx().emit_err(errors::AbiErrorDisabledVectorType { |
| span, |
| required_feature, |
| ty: arg_abi.layout.ty, |
| is_call, |
| is_scalable: true, |
| }); |
| } |
| } |
| UsesVectorRegisters::No => { |
| continue; |
| } |
| } |
| } |
| // The `vectorcall` ABI is special in that it requires SSE2 no matter which types are being passed. |
| if abi.conv == CanonAbi::X86(X86Call::Vectorcall) && !have_feature(sym::sse2) { |
| let (span, _hir_id) = loc(); |
| tcx.dcx().emit_err(errors::AbiRequiredTargetFeature { |
| span, |
| required_feature: "sse2", |
| abi: "vectorcall", |
| is_call, |
| }); |
| } |
| } |
| |
| /// Emit an error when a non-rustic ABI has unsized parameters. |
| /// Unsized types do not have a stable layout, so should not be used with stable ABIs. |
| /// `is_call` indicates whether this is a call-site check or a definition-site check; |
| /// this is only relevant for the wording in the emitted error. |
| fn do_check_unsized_params<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| fn_abi: &FnAbi<'tcx, Ty<'tcx>>, |
| is_call: bool, |
| loc: impl Fn() -> (Span, HirId), |
| ) { |
| // Unsized parameters are allowed with the (unstable) "Rust" (and similar) ABIs. |
| if fn_abi.conv.is_rustic_abi() { |
| return; |
| } |
| |
| for arg_abi in fn_abi.args.iter() { |
| if !arg_abi.layout.layout.is_sized() { |
| let (span, _hir_id) = loc(); |
| tcx.dcx().emit_err(errors::AbiErrorUnsupportedUnsizedParameter { |
| span, |
| ty: arg_abi.layout.ty, |
| is_call, |
| }); |
| } |
| } |
| } |
| |
| /// Checks the ABI of an Instance, emitting an error when: |
| /// |
| /// - a non-rustic ABI uses unsized parameters |
| /// - the signature requires target features that are not enabled |
| fn check_instance_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) { |
| let typing_env = ty::TypingEnv::fully_monomorphized(); |
| let Ok(abi) = tcx.fn_abi_of_instance(typing_env.as_query_input((instance, ty::List::empty()))) |
| else { |
| // An error will be reported during codegen if we cannot determine the ABI of this |
| // function. |
| tcx.dcx().delayed_bug("ABI computation failure should lead to compilation failure"); |
| return; |
| }; |
| // Unlike the call-site check, we do also check "Rust" ABI functions here. |
| // This should never trigger, *except* if we start making use of vector registers |
| // for the "Rust" ABI and the user disables those vector registers (which should trigger a |
| // warning as that's clearly disabling a "required" target feature for this target). |
| // Using such a function is where disabling the vector register actually can start leading |
| // to soundness issues, so erroring here seems good. |
| let loc = || { |
| let def_id = instance.def_id(); |
| ( |
| tcx.def_span(def_id), |
| def_id.as_local().map(|did| tcx.local_def_id_to_hir_id(did)).unwrap_or(CRATE_HIR_ID), |
| ) |
| }; |
| do_check_unsized_params(tcx, abi, /*is_call*/ false, loc); |
| do_check_simd_vector_abi(tcx, abi, instance.def_id(), /*is_call*/ false, loc); |
| } |
| |
| /// Check the ABI at a call site, emitting an error when: |
| /// |
| /// - a non-rustic ABI uses unsized parameters |
| /// - the signature requires target features that are not enabled |
| fn check_call_site_abi<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| callee: Ty<'tcx>, |
| caller: InstanceKind<'tcx>, |
| loc: impl Fn() -> (Span, HirId) + Copy, |
| ) { |
| if callee.fn_sig(tcx).abi().is_rustic_abi() { |
| // We directly handle the soundness of Rust ABIs -- so let's skip the majority of |
| // call sites to avoid a perf regression. |
| return; |
| } |
| let typing_env = ty::TypingEnv::fully_monomorphized(); |
| let callee_abi = match *callee.kind() { |
| ty::FnPtr(..) => { |
| tcx.fn_abi_of_fn_ptr(typing_env.as_query_input((callee.fn_sig(tcx), ty::List::empty()))) |
| } |
| ty::FnDef(def_id, args) => { |
| // Intrinsics are handled separately by the compiler. |
| if tcx.intrinsic(def_id).is_some() { |
| return; |
| } |
| let instance = ty::Instance::expect_resolve(tcx, typing_env, def_id, args, DUMMY_SP); |
| tcx.fn_abi_of_instance(typing_env.as_query_input((instance, ty::List::empty()))) |
| } |
| _ => { |
| panic!("Invalid function call"); |
| } |
| }; |
| |
| let Ok(callee_abi) = callee_abi else { |
| // ABI failed to compute; this will not get through codegen. |
| return; |
| }; |
| do_check_unsized_params(tcx, callee_abi, /*is_call*/ true, loc); |
| do_check_simd_vector_abi(tcx, callee_abi, caller.def_id(), /*is_call*/ true, loc); |
| } |
| |
| fn check_callees_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, body: &mir::Body<'tcx>) { |
| // Check all function call terminators. |
| for (bb, _data) in traversal::mono_reachable(body, tcx, instance) { |
| let terminator = body.basic_blocks[bb].terminator(); |
| match terminator.kind { |
| mir::TerminatorKind::Call { ref func, ref fn_span, .. } |
| | mir::TerminatorKind::TailCall { ref func, ref fn_span, .. } => { |
| let callee_ty = func.ty(body, tcx); |
| let callee_ty = instance.instantiate_mir_and_normalize_erasing_regions( |
| tcx, |
| ty::TypingEnv::fully_monomorphized(), |
| ty::EarlyBinder::bind(callee_ty), |
| ); |
| check_call_site_abi(tcx, callee_ty, body.source.instance, || { |
| let loc = Location { |
| block: bb, |
| statement_index: body.basic_blocks[bb].statements.len(), |
| }; |
| ( |
| *fn_span, |
| body.source_info(loc) |
| .scope |
| .lint_root(&body.source_scopes) |
| .unwrap_or(CRATE_HIR_ID), |
| ) |
| }); |
| } |
| _ => {} |
| } |
| } |
| } |
| |
| pub(crate) fn check_feature_dependent_abi<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| instance: Instance<'tcx>, |
| body: &'tcx mir::Body<'tcx>, |
| ) { |
| check_instance_abi(tcx, instance); |
| check_callees_abi(tcx, instance, body); |
| } |