blob: a5f2ba2f1ced91047942126b5c63f1fabafb501d [file] [log] [blame]
//! 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);
}