Move utils from rustc_middle to rustc_ty_utils
This commit is contained in:
parent
a8a847e30d
commit
95b689b1d5
9 changed files with 2337 additions and 2343 deletions
518
compiler/rustc_ty_utils/src/abi.rs
Normal file
518
compiler/rustc_ty_utils/src/abi.rs
Normal file
|
@ -0,0 +1,518 @@
|
|||
use rustc_hir as hir;
|
||||
use rustc_hir::lang_items::LangItem;
|
||||
use rustc_middle::ty::layout::{
|
||||
fn_can_unwind, FnAbiError, HasParamEnv, HasTyCtxt, LayoutCx, LayoutOf, TyAndLayout,
|
||||
};
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_target::abi::call::{
|
||||
ArgAbi, ArgAttribute, ArgAttributes, ArgExtension, Conv, FnAbi, PassMode, Reg, RegKind,
|
||||
};
|
||||
use rustc_target::abi::*;
|
||||
use rustc_target::spec::abi::Abi as SpecAbi;
|
||||
|
||||
use std::iter;
|
||||
|
||||
pub fn provide(providers: &mut ty::query::Providers) {
|
||||
*providers = ty::query::Providers { fn_abi_of_fn_ptr, fn_abi_of_instance, ..*providers };
|
||||
}
|
||||
|
||||
// NOTE(eddyb) this is private to avoid using it from outside of
|
||||
// `fn_abi_of_instance` - any other uses are either too high-level
|
||||
// for `Instance` (e.g. typeck would use `Ty::fn_sig` instead),
|
||||
// or should go through `FnAbi` instead, to avoid losing any
|
||||
// adjustments `fn_abi_of_instance` might be performing.
|
||||
#[tracing::instrument(level = "debug", skip(tcx, param_env))]
|
||||
fn fn_sig_for_fn_abi<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
instance: ty::Instance<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
) -> ty::PolyFnSig<'tcx> {
|
||||
let ty = instance.ty(tcx, param_env);
|
||||
match *ty.kind() {
|
||||
ty::FnDef(..) => {
|
||||
// HACK(davidtwco,eddyb): This is a workaround for polymorphization considering
|
||||
// parameters unused if they show up in the signature, but not in the `mir::Body`
|
||||
// (i.e. due to being inside a projection that got normalized, see
|
||||
// `src/test/ui/polymorphization/normalized_sig_types.rs`), and codegen not keeping
|
||||
// track of a polymorphization `ParamEnv` to allow normalizing later.
|
||||
//
|
||||
// We normalize the `fn_sig` again after substituting at a later point.
|
||||
let mut sig = match *ty.kind() {
|
||||
ty::FnDef(def_id, substs) => tcx
|
||||
.bound_fn_sig(def_id)
|
||||
.map_bound(|fn_sig| {
|
||||
tcx.normalize_erasing_regions(tcx.param_env(def_id), fn_sig)
|
||||
})
|
||||
.subst(tcx, substs),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if let ty::InstanceDef::VTableShim(..) = instance.def {
|
||||
// Modify `fn(self, ...)` to `fn(self: *mut Self, ...)`.
|
||||
sig = sig.map_bound(|mut sig| {
|
||||
let mut inputs_and_output = sig.inputs_and_output.to_vec();
|
||||
inputs_and_output[0] = tcx.mk_mut_ptr(inputs_and_output[0]);
|
||||
sig.inputs_and_output = tcx.intern_type_list(&inputs_and_output);
|
||||
sig
|
||||
});
|
||||
}
|
||||
sig
|
||||
}
|
||||
ty::Closure(def_id, substs) => {
|
||||
let sig = substs.as_closure().sig();
|
||||
|
||||
let bound_vars = tcx.mk_bound_variable_kinds(
|
||||
sig.bound_vars().iter().chain(iter::once(ty::BoundVariableKind::Region(ty::BrEnv))),
|
||||
);
|
||||
let br = ty::BoundRegion {
|
||||
var: ty::BoundVar::from_usize(bound_vars.len() - 1),
|
||||
kind: ty::BoundRegionKind::BrEnv,
|
||||
};
|
||||
let env_region = ty::ReLateBound(ty::INNERMOST, br);
|
||||
let env_ty = tcx.closure_env_ty(def_id, substs, env_region).unwrap();
|
||||
|
||||
let sig = sig.skip_binder();
|
||||
ty::Binder::bind_with_vars(
|
||||
tcx.mk_fn_sig(
|
||||
iter::once(env_ty).chain(sig.inputs().iter().cloned()),
|
||||
sig.output(),
|
||||
sig.c_variadic,
|
||||
sig.unsafety,
|
||||
sig.abi,
|
||||
),
|
||||
bound_vars,
|
||||
)
|
||||
}
|
||||
ty::Generator(_, substs, _) => {
|
||||
let sig = substs.as_generator().poly_sig();
|
||||
|
||||
let bound_vars = tcx.mk_bound_variable_kinds(
|
||||
sig.bound_vars().iter().chain(iter::once(ty::BoundVariableKind::Region(ty::BrEnv))),
|
||||
);
|
||||
let br = ty::BoundRegion {
|
||||
var: ty::BoundVar::from_usize(bound_vars.len() - 1),
|
||||
kind: ty::BoundRegionKind::BrEnv,
|
||||
};
|
||||
let env_region = ty::ReLateBound(ty::INNERMOST, br);
|
||||
let env_ty = tcx.mk_mut_ref(tcx.mk_region(env_region), ty);
|
||||
|
||||
let pin_did = tcx.require_lang_item(LangItem::Pin, None);
|
||||
let pin_adt_ref = tcx.adt_def(pin_did);
|
||||
let pin_substs = tcx.intern_substs(&[env_ty.into()]);
|
||||
let env_ty = tcx.mk_adt(pin_adt_ref, pin_substs);
|
||||
|
||||
let sig = sig.skip_binder();
|
||||
let state_did = tcx.require_lang_item(LangItem::GeneratorState, None);
|
||||
let state_adt_ref = tcx.adt_def(state_did);
|
||||
let state_substs = tcx.intern_substs(&[sig.yield_ty.into(), sig.return_ty.into()]);
|
||||
let ret_ty = tcx.mk_adt(state_adt_ref, state_substs);
|
||||
ty::Binder::bind_with_vars(
|
||||
tcx.mk_fn_sig(
|
||||
[env_ty, sig.resume_ty].iter(),
|
||||
&ret_ty,
|
||||
false,
|
||||
hir::Unsafety::Normal,
|
||||
rustc_target::spec::abi::Abi::Rust,
|
||||
),
|
||||
bound_vars,
|
||||
)
|
||||
}
|
||||
_ => bug!("unexpected type {:?} in Instance::fn_sig", ty),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn conv_from_spec_abi(tcx: TyCtxt<'_>, abi: SpecAbi) -> Conv {
|
||||
use rustc_target::spec::abi::Abi::*;
|
||||
match tcx.sess.target.adjust_abi(abi) {
|
||||
RustIntrinsic | PlatformIntrinsic | Rust | RustCall => Conv::Rust,
|
||||
RustCold => Conv::RustCold,
|
||||
|
||||
// It's the ABI's job to select this, not ours.
|
||||
System { .. } => bug!("system abi should be selected elsewhere"),
|
||||
EfiApi => bug!("eficall abi should be selected elsewhere"),
|
||||
|
||||
Stdcall { .. } => Conv::X86Stdcall,
|
||||
Fastcall { .. } => Conv::X86Fastcall,
|
||||
Vectorcall { .. } => Conv::X86VectorCall,
|
||||
Thiscall { .. } => Conv::X86ThisCall,
|
||||
C { .. } => Conv::C,
|
||||
Unadjusted => Conv::C,
|
||||
Win64 { .. } => Conv::X86_64Win64,
|
||||
SysV64 { .. } => Conv::X86_64SysV,
|
||||
Aapcs { .. } => Conv::ArmAapcs,
|
||||
CCmseNonSecureCall => Conv::CCmseNonSecureCall,
|
||||
PtxKernel => Conv::PtxKernel,
|
||||
Msp430Interrupt => Conv::Msp430Intr,
|
||||
X86Interrupt => Conv::X86Intr,
|
||||
AmdGpuKernel => Conv::AmdGpuKernel,
|
||||
AvrInterrupt => Conv::AvrInterrupt,
|
||||
AvrNonBlockingInterrupt => Conv::AvrNonBlockingInterrupt,
|
||||
Wasm => Conv::C,
|
||||
|
||||
// These API constants ought to be more specific...
|
||||
Cdecl { .. } => Conv::C,
|
||||
}
|
||||
}
|
||||
|
||||
fn fn_abi_of_fn_ptr<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
query: ty::ParamEnvAnd<'tcx, (ty::PolyFnSig<'tcx>, &'tcx ty::List<Ty<'tcx>>)>,
|
||||
) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, FnAbiError<'tcx>> {
|
||||
let (param_env, (sig, extra_args)) = query.into_parts();
|
||||
|
||||
let cx = LayoutCx { tcx, param_env };
|
||||
fn_abi_new_uncached(&cx, sig, extra_args, None, None, false)
|
||||
}
|
||||
|
||||
fn fn_abi_of_instance<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
query: ty::ParamEnvAnd<'tcx, (ty::Instance<'tcx>, &'tcx ty::List<Ty<'tcx>>)>,
|
||||
) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, FnAbiError<'tcx>> {
|
||||
let (param_env, (instance, extra_args)) = query.into_parts();
|
||||
|
||||
let sig = fn_sig_for_fn_abi(tcx, instance, param_env);
|
||||
|
||||
let caller_location = if instance.def.requires_caller_location(tcx) {
|
||||
Some(tcx.caller_location_ty())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
fn_abi_new_uncached(
|
||||
&LayoutCx { tcx, param_env },
|
||||
sig,
|
||||
extra_args,
|
||||
caller_location,
|
||||
Some(instance.def_id()),
|
||||
matches!(instance.def, ty::InstanceDef::Virtual(..)),
|
||||
)
|
||||
}
|
||||
|
||||
// Handle safe Rust thin and fat pointers.
|
||||
fn adjust_for_rust_scalar<'tcx>(
|
||||
cx: LayoutCx<'tcx, TyCtxt<'tcx>>,
|
||||
attrs: &mut ArgAttributes,
|
||||
scalar: Scalar,
|
||||
layout: TyAndLayout<'tcx>,
|
||||
offset: Size,
|
||||
is_return: bool,
|
||||
) {
|
||||
// Booleans are always a noundef i1 that needs to be zero-extended.
|
||||
if scalar.is_bool() {
|
||||
attrs.ext(ArgExtension::Zext);
|
||||
attrs.set(ArgAttribute::NoUndef);
|
||||
return;
|
||||
}
|
||||
|
||||
// Scalars which have invalid values cannot be undef.
|
||||
if !scalar.is_always_valid(&cx) {
|
||||
attrs.set(ArgAttribute::NoUndef);
|
||||
}
|
||||
|
||||
// Only pointer types handled below.
|
||||
let Scalar::Initialized { value: Pointer, valid_range} = scalar else { return };
|
||||
|
||||
if !valid_range.contains(0) {
|
||||
attrs.set(ArgAttribute::NonNull);
|
||||
}
|
||||
|
||||
if let Some(pointee) = layout.pointee_info_at(&cx, offset) {
|
||||
if let Some(kind) = pointee.safe {
|
||||
attrs.pointee_align = Some(pointee.align);
|
||||
|
||||
// `Box` (`UniqueBorrowed`) are not necessarily dereferenceable
|
||||
// for the entire duration of the function as they can be deallocated
|
||||
// at any time. Same for shared mutable references. If LLVM had a
|
||||
// way to say "dereferenceable on entry" we could use it here.
|
||||
attrs.pointee_size = match kind {
|
||||
PointerKind::UniqueBorrowed
|
||||
| PointerKind::UniqueBorrowedPinned
|
||||
| PointerKind::Frozen => pointee.size,
|
||||
PointerKind::SharedMutable | PointerKind::UniqueOwned => Size::ZERO,
|
||||
};
|
||||
|
||||
// `Box`, `&T`, and `&mut T` cannot be undef.
|
||||
// Note that this only applies to the value of the pointer itself;
|
||||
// this attribute doesn't make it UB for the pointed-to data to be undef.
|
||||
attrs.set(ArgAttribute::NoUndef);
|
||||
|
||||
// The aliasing rules for `Box<T>` are still not decided, but currently we emit
|
||||
// `noalias` for it. This can be turned off using an unstable flag.
|
||||
// See https://github.com/rust-lang/unsafe-code-guidelines/issues/326
|
||||
let noalias_for_box = cx.tcx.sess.opts.unstable_opts.box_noalias.unwrap_or(true);
|
||||
|
||||
// `&mut` pointer parameters never alias other parameters,
|
||||
// or mutable global data
|
||||
//
|
||||
// `&T` where `T` contains no `UnsafeCell<U>` is immutable,
|
||||
// and can be marked as both `readonly` and `noalias`, as
|
||||
// LLVM's definition of `noalias` is based solely on memory
|
||||
// dependencies rather than pointer equality
|
||||
//
|
||||
// Due to past miscompiles in LLVM, we apply a separate NoAliasMutRef attribute
|
||||
// for UniqueBorrowed arguments, so that the codegen backend can decide whether
|
||||
// or not to actually emit the attribute. It can also be controlled with the
|
||||
// `-Zmutable-noalias` debugging option.
|
||||
let no_alias = match kind {
|
||||
PointerKind::SharedMutable
|
||||
| PointerKind::UniqueBorrowed
|
||||
| PointerKind::UniqueBorrowedPinned => false,
|
||||
PointerKind::UniqueOwned => noalias_for_box,
|
||||
PointerKind::Frozen => !is_return,
|
||||
};
|
||||
if no_alias {
|
||||
attrs.set(ArgAttribute::NoAlias);
|
||||
}
|
||||
|
||||
if kind == PointerKind::Frozen && !is_return {
|
||||
attrs.set(ArgAttribute::ReadOnly);
|
||||
}
|
||||
|
||||
if kind == PointerKind::UniqueBorrowed && !is_return {
|
||||
attrs.set(ArgAttribute::NoAliasMutRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME(eddyb) perhaps group the signature/type-containing (or all of them?)
|
||||
// arguments of this method, into a separate `struct`.
|
||||
#[tracing::instrument(level = "debug", skip(cx, caller_location, fn_def_id, force_thin_self_ptr))]
|
||||
fn fn_abi_new_uncached<'tcx>(
|
||||
cx: &LayoutCx<'tcx, TyCtxt<'tcx>>,
|
||||
sig: ty::PolyFnSig<'tcx>,
|
||||
extra_args: &[Ty<'tcx>],
|
||||
caller_location: Option<Ty<'tcx>>,
|
||||
fn_def_id: Option<DefId>,
|
||||
// FIXME(eddyb) replace this with something typed, like an `enum`.
|
||||
force_thin_self_ptr: bool,
|
||||
) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, FnAbiError<'tcx>> {
|
||||
let sig = cx.tcx.normalize_erasing_late_bound_regions(cx.param_env, sig);
|
||||
|
||||
let conv = conv_from_spec_abi(cx.tcx(), sig.abi);
|
||||
|
||||
let mut inputs = sig.inputs();
|
||||
let extra_args = if sig.abi == RustCall {
|
||||
assert!(!sig.c_variadic && extra_args.is_empty());
|
||||
|
||||
if let Some(input) = sig.inputs().last() {
|
||||
if let ty::Tuple(tupled_arguments) = input.kind() {
|
||||
inputs = &sig.inputs()[0..sig.inputs().len() - 1];
|
||||
tupled_arguments
|
||||
} else {
|
||||
bug!(
|
||||
"argument to function with \"rust-call\" ABI \
|
||||
is not a tuple"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
bug!(
|
||||
"argument to function with \"rust-call\" ABI \
|
||||
is not a tuple"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
assert!(sig.c_variadic || extra_args.is_empty());
|
||||
extra_args
|
||||
};
|
||||
|
||||
let target = &cx.tcx.sess.target;
|
||||
let target_env_gnu_like = matches!(&target.env[..], "gnu" | "musl" | "uclibc");
|
||||
let win_x64_gnu = target.os == "windows" && target.arch == "x86_64" && target.env == "gnu";
|
||||
let linux_s390x_gnu_like =
|
||||
target.os == "linux" && target.arch == "s390x" && target_env_gnu_like;
|
||||
let linux_sparc64_gnu_like =
|
||||
target.os == "linux" && target.arch == "sparc64" && target_env_gnu_like;
|
||||
let linux_powerpc_gnu_like =
|
||||
target.os == "linux" && target.arch == "powerpc" && target_env_gnu_like;
|
||||
use SpecAbi::*;
|
||||
let rust_abi = matches!(sig.abi, RustIntrinsic | PlatformIntrinsic | Rust | RustCall);
|
||||
|
||||
let arg_of = |ty: Ty<'tcx>, arg_idx: Option<usize>| -> Result<_, FnAbiError<'tcx>> {
|
||||
let span = tracing::debug_span!("arg_of");
|
||||
let _entered = span.enter();
|
||||
let is_return = arg_idx.is_none();
|
||||
|
||||
let layout = cx.layout_of(ty)?;
|
||||
let layout = if force_thin_self_ptr && arg_idx == Some(0) {
|
||||
// Don't pass the vtable, it's not an argument of the virtual fn.
|
||||
// Instead, pass just the data pointer, but give it the type `*const/mut dyn Trait`
|
||||
// or `&/&mut dyn Trait` because this is special-cased elsewhere in codegen
|
||||
make_thin_self_ptr(cx, layout)
|
||||
} else {
|
||||
layout
|
||||
};
|
||||
|
||||
let mut arg = ArgAbi::new(cx, layout, |layout, scalar, offset| {
|
||||
let mut attrs = ArgAttributes::new();
|
||||
adjust_for_rust_scalar(*cx, &mut attrs, scalar, *layout, offset, is_return);
|
||||
attrs
|
||||
});
|
||||
|
||||
if arg.layout.is_zst() {
|
||||
// For some forsaken reason, x86_64-pc-windows-gnu
|
||||
// doesn't ignore zero-sized struct arguments.
|
||||
// The same is true for {s390x,sparc64,powerpc}-unknown-linux-{gnu,musl,uclibc}.
|
||||
if is_return
|
||||
|| rust_abi
|
||||
|| (!win_x64_gnu
|
||||
&& !linux_s390x_gnu_like
|
||||
&& !linux_sparc64_gnu_like
|
||||
&& !linux_powerpc_gnu_like)
|
||||
{
|
||||
arg.mode = PassMode::Ignore;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(arg)
|
||||
};
|
||||
|
||||
let mut fn_abi = FnAbi {
|
||||
ret: arg_of(sig.output(), None)?,
|
||||
args: inputs
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(extra_args.iter().copied())
|
||||
.chain(caller_location)
|
||||
.enumerate()
|
||||
.map(|(i, ty)| arg_of(ty, Some(i)))
|
||||
.collect::<Result<_, _>>()?,
|
||||
c_variadic: sig.c_variadic,
|
||||
fixed_count: inputs.len() as u32,
|
||||
conv,
|
||||
can_unwind: fn_can_unwind(cx.tcx(), fn_def_id, sig.abi),
|
||||
};
|
||||
fn_abi_adjust_for_abi(cx, &mut fn_abi, sig.abi)?;
|
||||
debug!("fn_abi_new_uncached = {:?}", fn_abi);
|
||||
Ok(cx.tcx.arena.alloc(fn_abi))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(cx))]
|
||||
fn fn_abi_adjust_for_abi<'tcx>(
|
||||
cx: &LayoutCx<'tcx, TyCtxt<'tcx>>,
|
||||
fn_abi: &mut FnAbi<'tcx, Ty<'tcx>>,
|
||||
abi: SpecAbi,
|
||||
) -> Result<(), FnAbiError<'tcx>> {
|
||||
if abi == SpecAbi::Unadjusted {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if abi == SpecAbi::Rust
|
||||
|| abi == SpecAbi::RustCall
|
||||
|| abi == SpecAbi::RustIntrinsic
|
||||
|| abi == SpecAbi::PlatformIntrinsic
|
||||
{
|
||||
let fixup = |arg: &mut ArgAbi<'tcx, Ty<'tcx>>| {
|
||||
if arg.is_ignore() {
|
||||
return;
|
||||
}
|
||||
|
||||
match arg.layout.abi {
|
||||
Abi::Aggregate { .. } => {}
|
||||
|
||||
// This is a fun case! The gist of what this is doing is
|
||||
// that we want callers and callees to always agree on the
|
||||
// ABI of how they pass SIMD arguments. If we were to *not*
|
||||
// make these arguments indirect then they'd be immediates
|
||||
// in LLVM, which means that they'd used whatever the
|
||||
// appropriate ABI is for the callee and the caller. That
|
||||
// means, for example, if the caller doesn't have AVX
|
||||
// enabled but the callee does, then passing an AVX argument
|
||||
// across this boundary would cause corrupt data to show up.
|
||||
//
|
||||
// This problem is fixed by unconditionally passing SIMD
|
||||
// arguments through memory between callers and callees
|
||||
// which should get them all to agree on ABI regardless of
|
||||
// target feature sets. Some more information about this
|
||||
// issue can be found in #44367.
|
||||
//
|
||||
// Note that the platform intrinsic ABI is exempt here as
|
||||
// that's how we connect up to LLVM and it's unstable
|
||||
// anyway, we control all calls to it in libstd.
|
||||
Abi::Vector { .. }
|
||||
if abi != SpecAbi::PlatformIntrinsic
|
||||
&& cx.tcx.sess.target.simd_types_indirect =>
|
||||
{
|
||||
arg.make_indirect();
|
||||
return;
|
||||
}
|
||||
|
||||
_ => return,
|
||||
}
|
||||
|
||||
let size = arg.layout.size;
|
||||
if arg.layout.is_unsized() || size > Pointer.size(cx) {
|
||||
arg.make_indirect();
|
||||
} else {
|
||||
// We want to pass small aggregates as immediates, but using
|
||||
// a LLVM aggregate type for this leads to bad optimizations,
|
||||
// so we pick an appropriately sized integer type instead.
|
||||
arg.cast_to(Reg { kind: RegKind::Integer, size });
|
||||
}
|
||||
};
|
||||
fixup(&mut fn_abi.ret);
|
||||
for arg in fn_abi.args.iter_mut() {
|
||||
fixup(arg);
|
||||
}
|
||||
} else {
|
||||
fn_abi.adjust_for_foreign_abi(cx, abi)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(cx))]
|
||||
fn make_thin_self_ptr<'tcx>(
|
||||
cx: &(impl HasTyCtxt<'tcx> + HasParamEnv<'tcx>),
|
||||
layout: TyAndLayout<'tcx>,
|
||||
) -> TyAndLayout<'tcx> {
|
||||
let tcx = cx.tcx();
|
||||
let fat_pointer_ty = if layout.is_unsized() {
|
||||
// unsized `self` is passed as a pointer to `self`
|
||||
// FIXME (mikeyhew) change this to use &own if it is ever added to the language
|
||||
tcx.mk_mut_ptr(layout.ty)
|
||||
} else {
|
||||
match layout.abi {
|
||||
Abi::ScalarPair(..) | Abi::Scalar(..) => (),
|
||||
_ => bug!("receiver type has unsupported layout: {:?}", layout),
|
||||
}
|
||||
|
||||
// In the case of Rc<Self>, we need to explicitly pass a *mut RcBox<Self>
|
||||
// with a Scalar (not ScalarPair) ABI. This is a hack that is understood
|
||||
// elsewhere in the compiler as a method on a `dyn Trait`.
|
||||
// To get the type `*mut RcBox<Self>`, we just keep unwrapping newtypes until we
|
||||
// get a built-in pointer type
|
||||
let mut fat_pointer_layout = layout;
|
||||
'descend_newtypes: while !fat_pointer_layout.ty.is_unsafe_ptr()
|
||||
&& !fat_pointer_layout.ty.is_region_ptr()
|
||||
{
|
||||
for i in 0..fat_pointer_layout.fields.count() {
|
||||
let field_layout = fat_pointer_layout.field(cx, i);
|
||||
|
||||
if !field_layout.is_zst() {
|
||||
fat_pointer_layout = field_layout;
|
||||
continue 'descend_newtypes;
|
||||
}
|
||||
}
|
||||
|
||||
bug!("receiver has no non-zero-sized fields {:?}", fat_pointer_layout);
|
||||
}
|
||||
|
||||
fat_pointer_layout.ty
|
||||
};
|
||||
|
||||
// we now have a type like `*mut RcBox<dyn Trait>`
|
||||
// change its layout to that of `*mut ()`, a thin pointer, but keep the same type
|
||||
// this is understood as a special case elsewhere in the compiler
|
||||
let unit_ptr_ty = tcx.mk_mut_ptr(tcx.mk_unit());
|
||||
|
||||
TyAndLayout {
|
||||
ty: fat_pointer_ty,
|
||||
|
||||
// NOTE(eddyb) using an empty `ParamEnv`, and `unwrap`-ing the `Result`
|
||||
// should always work because the type is always `*mut ()`.
|
||||
..tcx.layout_of(ty::ParamEnv::reveal_all().and(unit_ptr_ty)).unwrap()
|
||||
}
|
||||
}
|
1804
compiler/rustc_ty_utils/src/layout.rs
Normal file
1804
compiler/rustc_ty_utils/src/layout.rs
Normal file
File diff suppressed because it is too large
Load diff
303
compiler/rustc_ty_utils/src/layout_sanity_check.rs
Normal file
303
compiler/rustc_ty_utils/src/layout_sanity_check.rs
Normal file
|
@ -0,0 +1,303 @@
|
|||
use rustc_middle::ty::{
|
||||
layout::{LayoutCx, TyAndLayout},
|
||||
TyCtxt,
|
||||
};
|
||||
use rustc_target::abi::*;
|
||||
|
||||
use std::cmp;
|
||||
|
||||
/// Enforce some basic invariants on layouts.
|
||||
pub(super) fn sanity_check_layout<'tcx>(
|
||||
cx: &LayoutCx<'tcx, TyCtxt<'tcx>>,
|
||||
layout: &TyAndLayout<'tcx>,
|
||||
) {
|
||||
// Type-level uninhabitedness should always imply ABI uninhabitedness.
|
||||
if cx.tcx.conservative_is_privately_uninhabited(cx.param_env.and(layout.ty)) {
|
||||
assert!(layout.abi.is_uninhabited());
|
||||
}
|
||||
|
||||
if layout.size.bytes() % layout.align.abi.bytes() != 0 {
|
||||
bug!("size is not a multiple of align, in the following layout:\n{layout:#?}");
|
||||
}
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
/// Yields non-ZST fields of the type
|
||||
fn non_zst_fields<'tcx, 'a>(
|
||||
cx: &'a LayoutCx<'tcx, TyCtxt<'tcx>>,
|
||||
layout: &'a TyAndLayout<'tcx>,
|
||||
) -> impl Iterator<Item = (Size, TyAndLayout<'tcx>)> + 'a {
|
||||
(0..layout.layout.fields().count()).filter_map(|i| {
|
||||
let field = layout.field(cx, i);
|
||||
// Also checking `align == 1` here leads to test failures in
|
||||
// `layout/zero-sized-array-union.rs`, where a type has a zero-size field with
|
||||
// alignment 4 that still gets ignored during layout computation (which is okay
|
||||
// since other fields already force alignment 4).
|
||||
let zst = field.is_zst();
|
||||
(!zst).then(|| (layout.fields.offset(i), field))
|
||||
})
|
||||
}
|
||||
|
||||
fn skip_newtypes<'tcx>(
|
||||
cx: &LayoutCx<'tcx, TyCtxt<'tcx>>,
|
||||
layout: &TyAndLayout<'tcx>,
|
||||
) -> TyAndLayout<'tcx> {
|
||||
if matches!(layout.layout.variants(), Variants::Multiple { .. }) {
|
||||
// Definitely not a newtype of anything.
|
||||
return *layout;
|
||||
}
|
||||
let mut fields = non_zst_fields(cx, layout);
|
||||
let Some(first) = fields.next() else {
|
||||
// No fields here, so this could be a primitive or enum -- either way it's not a newtype around a thing
|
||||
return *layout
|
||||
};
|
||||
if fields.next().is_none() {
|
||||
let (offset, first) = first;
|
||||
if offset == Size::ZERO && first.layout.size() == layout.size {
|
||||
// This is a newtype, so keep recursing.
|
||||
// FIXME(RalfJung): I don't think it would be correct to do any checks for
|
||||
// alignment here, so we don't. Is that correct?
|
||||
return skip_newtypes(cx, &first);
|
||||
}
|
||||
}
|
||||
// No more newtypes here.
|
||||
*layout
|
||||
}
|
||||
|
||||
fn check_layout_abi<'tcx>(cx: &LayoutCx<'tcx, TyCtxt<'tcx>>, layout: &TyAndLayout<'tcx>) {
|
||||
match layout.layout.abi() {
|
||||
Abi::Scalar(scalar) => {
|
||||
// No padding in scalars.
|
||||
let size = scalar.size(cx);
|
||||
let align = scalar.align(cx).abi;
|
||||
assert_eq!(
|
||||
layout.layout.size(),
|
||||
size,
|
||||
"size mismatch between ABI and layout in {layout:#?}"
|
||||
);
|
||||
assert_eq!(
|
||||
layout.layout.align().abi,
|
||||
align,
|
||||
"alignment mismatch between ABI and layout in {layout:#?}"
|
||||
);
|
||||
// Check that this matches the underlying field.
|
||||
let inner = skip_newtypes(cx, layout);
|
||||
assert!(
|
||||
matches!(inner.layout.abi(), Abi::Scalar(_)),
|
||||
"`Scalar` type {} is newtype around non-`Scalar` type {}",
|
||||
layout.ty,
|
||||
inner.ty
|
||||
);
|
||||
match inner.layout.fields() {
|
||||
FieldsShape::Primitive => {
|
||||
// Fine.
|
||||
}
|
||||
FieldsShape::Union(..) => {
|
||||
// FIXME: I guess we could also check something here? Like, look at all fields?
|
||||
return;
|
||||
}
|
||||
FieldsShape::Arbitrary { .. } => {
|
||||
// Should be an enum, the only field is the discriminant.
|
||||
assert!(
|
||||
inner.ty.is_enum(),
|
||||
"`Scalar` layout for non-primitive non-enum type {}",
|
||||
inner.ty
|
||||
);
|
||||
assert_eq!(
|
||||
inner.layout.fields().count(),
|
||||
1,
|
||||
"`Scalar` layout for multiple-field type in {inner:#?}",
|
||||
);
|
||||
let offset = inner.layout.fields().offset(0);
|
||||
let field = inner.field(cx, 0);
|
||||
// The field should be at the right offset, and match the `scalar` layout.
|
||||
assert_eq!(
|
||||
offset,
|
||||
Size::ZERO,
|
||||
"`Scalar` field at non-0 offset in {inner:#?}",
|
||||
);
|
||||
assert_eq!(
|
||||
field.size, size,
|
||||
"`Scalar` field with bad size in {inner:#?}",
|
||||
);
|
||||
assert_eq!(
|
||||
field.align.abi, align,
|
||||
"`Scalar` field with bad align in {inner:#?}",
|
||||
);
|
||||
assert!(
|
||||
matches!(field.abi, Abi::Scalar(_)),
|
||||
"`Scalar` field with bad ABI in {inner:#?}",
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
panic!("`Scalar` layout for non-primitive non-enum type {}", inner.ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
Abi::ScalarPair(scalar1, scalar2) => {
|
||||
// Sanity-check scalar pairs. These are a bit more flexible and support
|
||||
// padding, but we can at least ensure both fields actually fit into the layout
|
||||
// and the alignment requirement has not been weakened.
|
||||
let size1 = scalar1.size(cx);
|
||||
let align1 = scalar1.align(cx).abi;
|
||||
let size2 = scalar2.size(cx);
|
||||
let align2 = scalar2.align(cx).abi;
|
||||
assert!(
|
||||
layout.layout.align().abi >= cmp::max(align1, align2),
|
||||
"alignment mismatch between ABI and layout in {layout:#?}",
|
||||
);
|
||||
let field2_offset = size1.align_to(align2);
|
||||
assert!(
|
||||
layout.layout.size() >= field2_offset + size2,
|
||||
"size mismatch between ABI and layout in {layout:#?}"
|
||||
);
|
||||
// Check that the underlying pair of fields matches.
|
||||
let inner = skip_newtypes(cx, layout);
|
||||
assert!(
|
||||
matches!(inner.layout.abi(), Abi::ScalarPair(..)),
|
||||
"`ScalarPair` type {} is newtype around non-`ScalarPair` type {}",
|
||||
layout.ty,
|
||||
inner.ty
|
||||
);
|
||||
if matches!(inner.layout.variants(), Variants::Multiple { .. }) {
|
||||
// FIXME: ScalarPair for enums is enormously complicated and it is very hard
|
||||
// to check anything about them.
|
||||
return;
|
||||
}
|
||||
match inner.layout.fields() {
|
||||
FieldsShape::Arbitrary { .. } => {
|
||||
// Checked below.
|
||||
}
|
||||
FieldsShape::Union(..) => {
|
||||
// FIXME: I guess we could also check something here? Like, look at all fields?
|
||||
return;
|
||||
}
|
||||
_ => {
|
||||
panic!("`ScalarPair` layout with unexpected field shape in {inner:#?}");
|
||||
}
|
||||
}
|
||||
let mut fields = non_zst_fields(cx, &inner);
|
||||
let (offset1, field1) = fields.next().unwrap_or_else(|| {
|
||||
panic!("`ScalarPair` layout for type with not even one non-ZST field: {inner:#?}")
|
||||
});
|
||||
let (offset2, field2) = fields.next().unwrap_or_else(|| {
|
||||
panic!("`ScalarPair` layout for type with less than two non-ZST fields: {inner:#?}")
|
||||
});
|
||||
assert!(
|
||||
fields.next().is_none(),
|
||||
"`ScalarPair` layout for type with at least three non-ZST fields: {inner:#?}"
|
||||
);
|
||||
// The fields might be in opposite order.
|
||||
let (offset1, field1, offset2, field2) = if offset1 <= offset2 {
|
||||
(offset1, field1, offset2, field2)
|
||||
} else {
|
||||
(offset2, field2, offset1, field1)
|
||||
};
|
||||
// The fields should be at the right offset, and match the `scalar` layout.
|
||||
assert_eq!(
|
||||
offset1,
|
||||
Size::ZERO,
|
||||
"`ScalarPair` first field at non-0 offset in {inner:#?}",
|
||||
);
|
||||
assert_eq!(
|
||||
field1.size, size1,
|
||||
"`ScalarPair` first field with bad size in {inner:#?}",
|
||||
);
|
||||
assert_eq!(
|
||||
field1.align.abi, align1,
|
||||
"`ScalarPair` first field with bad align in {inner:#?}",
|
||||
);
|
||||
assert!(
|
||||
matches!(field1.abi, Abi::Scalar(_)),
|
||||
"`ScalarPair` first field with bad ABI in {inner:#?}",
|
||||
);
|
||||
assert_eq!(
|
||||
offset2, field2_offset,
|
||||
"`ScalarPair` second field at bad offset in {inner:#?}",
|
||||
);
|
||||
assert_eq!(
|
||||
field2.size, size2,
|
||||
"`ScalarPair` second field with bad size in {inner:#?}",
|
||||
);
|
||||
assert_eq!(
|
||||
field2.align.abi, align2,
|
||||
"`ScalarPair` second field with bad align in {inner:#?}",
|
||||
);
|
||||
assert!(
|
||||
matches!(field2.abi, Abi::Scalar(_)),
|
||||
"`ScalarPair` second field with bad ABI in {inner:#?}",
|
||||
);
|
||||
}
|
||||
Abi::Vector { count, element } => {
|
||||
// No padding in vectors. Alignment can be strengthened, though.
|
||||
assert!(
|
||||
layout.layout.align().abi >= element.align(cx).abi,
|
||||
"alignment mismatch between ABI and layout in {layout:#?}"
|
||||
);
|
||||
let size = element.size(cx) * count;
|
||||
assert_eq!(
|
||||
layout.layout.size(),
|
||||
size.align_to(cx.data_layout().vector_align(size).abi),
|
||||
"size mismatch between ABI and layout in {layout:#?}"
|
||||
);
|
||||
}
|
||||
Abi::Uninhabited | Abi::Aggregate { .. } => {} // Nothing to check.
|
||||
}
|
||||
}
|
||||
|
||||
check_layout_abi(cx, layout);
|
||||
|
||||
if let Variants::Multiple { variants, .. } = &layout.variants {
|
||||
for variant in variants.iter() {
|
||||
// No nested "multiple".
|
||||
assert!(matches!(variant.variants(), Variants::Single { .. }));
|
||||
// Variants should have the same or a smaller size as the full thing,
|
||||
// and same for alignment.
|
||||
if variant.size() > layout.size {
|
||||
bug!(
|
||||
"Type with size {} bytes has variant with size {} bytes: {layout:#?}",
|
||||
layout.size.bytes(),
|
||||
variant.size().bytes(),
|
||||
)
|
||||
}
|
||||
if variant.align().abi > layout.align.abi {
|
||||
bug!(
|
||||
"Type with alignment {} bytes has variant with alignment {} bytes: {layout:#?}",
|
||||
layout.align.abi.bytes(),
|
||||
variant.align().abi.bytes(),
|
||||
)
|
||||
}
|
||||
// Skip empty variants.
|
||||
if variant.size() == Size::ZERO
|
||||
|| variant.fields().count() == 0
|
||||
|| variant.abi().is_uninhabited()
|
||||
{
|
||||
// These are never actually accessed anyway, so we can skip the coherence check
|
||||
// for them. They also fail that check, since they have
|
||||
// `Aggregate`/`Uninhbaited` ABI even when the main type is
|
||||
// `Scalar`/`ScalarPair`. (Note that sometimes, variants with fields have size
|
||||
// 0, and sometimes, variants without fields have non-0 size.)
|
||||
continue;
|
||||
}
|
||||
// The top-level ABI and the ABI of the variants should be coherent.
|
||||
let scalar_coherent = |s1: Scalar, s2: Scalar| {
|
||||
s1.size(cx) == s2.size(cx) && s1.align(cx) == s2.align(cx)
|
||||
};
|
||||
let abi_coherent = match (layout.abi, variant.abi()) {
|
||||
(Abi::Scalar(s1), Abi::Scalar(s2)) => scalar_coherent(s1, s2),
|
||||
(Abi::ScalarPair(a1, b1), Abi::ScalarPair(a2, b2)) => {
|
||||
scalar_coherent(a1, a2) && scalar_coherent(b1, b2)
|
||||
}
|
||||
(Abi::Uninhabited, _) => true,
|
||||
(Abi::Aggregate { .. }, _) => true,
|
||||
_ => false,
|
||||
};
|
||||
if !abi_coherent {
|
||||
bug!(
|
||||
"Variant ABI is incompatible with top-level ABI:\nvariant={:#?}\nTop-level: {layout:#?}",
|
||||
variant
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,8 +9,6 @@
|
|||
#![feature(never_type)]
|
||||
#![feature(box_patterns)]
|
||||
#![recursion_limit = "256"]
|
||||
#![deny(rustc::untranslatable_diagnostic)]
|
||||
#![deny(rustc::diagnostic_outside_of_impl)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate rustc_middle;
|
||||
|
@ -19,21 +17,26 @@ extern crate tracing;
|
|||
|
||||
use rustc_middle::ty::query::Providers;
|
||||
|
||||
mod abi;
|
||||
mod assoc;
|
||||
mod common_traits;
|
||||
mod consts;
|
||||
mod errors;
|
||||
mod implied_bounds;
|
||||
pub mod instance;
|
||||
mod layout;
|
||||
mod layout_sanity_check;
|
||||
mod needs_drop;
|
||||
pub mod representability;
|
||||
mod ty;
|
||||
|
||||
pub fn provide(providers: &mut Providers) {
|
||||
abi::provide(providers);
|
||||
assoc::provide(providers);
|
||||
common_traits::provide(providers);
|
||||
consts::provide(providers);
|
||||
implied_bounds::provide(providers);
|
||||
layout::provide(providers);
|
||||
needs_drop::provide(providers);
|
||||
ty::provide(providers);
|
||||
instance::provide(providers);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue