Auto merge of #115372 - RalfJung:abi-assert-eq, r=davidtwco

add rustc_abi(assert_eq) to test some guaranteed or at least highly expected ABI compatibility guarantees

This new repr(transparent) test is super useful, it would have found https://github.com/rust-lang/rust/issues/115336 and found https://github.com/rust-lang/rust/issues/115404, https://github.com/rust-lang/rust/issues/115481, https://github.com/rust-lang/rust/issues/115509.
This commit is contained in:
bors 2023-09-08 11:56:08 +00:00
commit cd71a37f32
17 changed files with 1305 additions and 175 deletions

View File

@ -1300,12 +1300,18 @@ impl Abi {
matches!(*self, Abi::Uninhabited)
}
/// Returns `true` is this is a scalar type
/// Returns `true` if this is a scalar type
#[inline]
pub fn is_scalar(&self) -> bool {
matches!(*self, Abi::Scalar(_))
}
/// Returns `true` if this is a bool
#[inline]
pub fn is_bool(&self) -> bool {
matches!(*self, Abi::Scalar(s) if s.is_bool())
}
/// Returns the fixed alignment of this ABI, if any is mandated.
pub fn inherent_align<C: HasDataLayout>(&self, cx: &C) -> Option<AbiAndPrefAlign> {
Some(match *self {
@ -1348,6 +1354,23 @@ impl Abi {
Abi::Uninhabited | Abi::Aggregate { .. } => Abi::Aggregate { sized: true },
}
}
pub fn eq_up_to_validity(&self, other: &Self) -> bool {
match (self, other) {
// Scalar, Vector, ScalarPair have `Scalar` in them where we ignore validity ranges.
// We do *not* ignore the sign since it matters for some ABIs (e.g. s390x).
(Abi::Scalar(l), Abi::Scalar(r)) => l.primitive() == r.primitive(),
(
Abi::Vector { element: element_l, count: count_l },
Abi::Vector { element: element_r, count: count_r },
) => element_l.primitive() == element_r.primitive() && count_l == count_r,
(Abi::ScalarPair(l1, l2), Abi::ScalarPair(r1, r2)) => {
l1.primitive() == r1.primitive() && l2.primitive() == r2.primitive()
}
// Everything else must be strictly identical.
_ => self == other,
}
}
}
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
@ -1686,6 +1709,22 @@ impl LayoutS {
Abi::Aggregate { sized } => sized && self.size.bytes() == 0,
}
}
/// Checks if these two `Layout` are equal enough to be considered "the same for all function
/// call ABIs". Note however that real ABIs depend on more details that are not reflected in the
/// `Layout`; the `PassMode` need to be compared as well.
pub fn eq_abi(&self, other: &Self) -> bool {
// The one thing that we are not capturing here is that for unsized types, the metadata must
// also have the same ABI, and moreover that the same metadata leads to the same size. The
// 2nd point is quite hard to check though.
self.size == other.size
&& self.is_sized() == other.is_sized()
&& self.abi.eq_up_to_validity(&other.abi)
&& self.abi.is_bool() == other.abi.is_bool()
&& self.align.abi == other.align.abi
&& self.max_repr_align == other.max_repr_align
&& self.unadjusted_abi_align == other.unadjusted_abi_align
}
}
#[derive(Copy, Clone, Debug)]

View File

@ -340,15 +340,50 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
};
for arg in args {
// Note that the exact number of arguments pushed here is carefully synchronized with
// code all over the place, both in the codegen_llvm and codegen_ssa crates. That's how
// other code then knows which LLVM argument(s) correspond to the n-th Rust argument.
let llarg_ty = match &arg.mode {
PassMode::Ignore => continue,
PassMode::Direct(_) => arg.layout.immediate_llvm_type(cx),
PassMode::Direct(_) => {
// ABI-compatible Rust types have the same `layout.abi` (up to validity ranges),
// and for Scalar ABIs the LLVM type is fully determined by `layout.abi`,
// guarnateeing that we generate ABI-compatible LLVM IR. Things get tricky for
// aggregates...
if matches!(arg.layout.abi, abi::Abi::Aggregate { .. }) {
// This really shouldn't happen, since `immediate_llvm_type` will use
// `layout.fields` to turn this Rust type into an LLVM type. This means all
// sorts of Rust type details leak into the ABI. However wasm sadly *does*
// currently use this mode so we have to allow it -- but we absolutely
// shouldn't let any more targets do that.
// (Also see <https://github.com/rust-lang/rust/issues/115666>.)
assert!(
matches!(&*cx.tcx.sess.target.arch, "wasm32" | "wasm64"),
"`PassMode::Direct` for aggregates only allowed on wasm targets\nProblematic type: {:#?}",
arg.layout,
);
}
arg.layout.immediate_llvm_type(cx)
}
PassMode::Pair(..) => {
// ABI-compatible Rust types have the same `layout.abi` (up to validity ranges),
// so for ScalarPair we can easily be sure that we are generating ABI-compatible
// LLVM IR.
assert!(
matches!(arg.layout.abi, abi::Abi::ScalarPair(..)),
"PassMode::Pair for type {}",
arg.layout.ty
);
llargument_tys.push(arg.layout.scalar_pair_element_llvm_type(cx, 0, true));
llargument_tys.push(arg.layout.scalar_pair_element_llvm_type(cx, 1, true));
continue;
}
PassMode::Indirect { attrs: _, extra_attrs: Some(_), on_stack: _ } => {
assert!(arg.layout.is_unsized());
// Construct the type of a (wide) pointer to `ty`, and pass its two fields.
// Any two ABI-compatible unsized types have the same metadata type and
// moreover the same metadata value leads to the same dynamic size and
// alignment, so this respects ABI compatibility.
let ptr_ty = Ty::new_mut_ptr(cx.tcx, arg.layout.ty);
let ptr_layout = cx.layout_of(ptr_ty);
llargument_tys.push(ptr_layout.scalar_pair_element_llvm_type(cx, 0, true));
@ -360,6 +395,8 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
if *pad_i32 {
llargument_tys.push(Reg::i32().llvm_type(cx));
}
// Compute the LLVM type we use for this function from the cast type.
// We assume here that ABI-compatible Rust types have the same cast type.
cast.llvm_type(cx)
}
PassMode::Indirect { attrs: _, extra_attrs: None, on_stack: _ } => cx.type_ptr(),

View File

@ -10,7 +10,7 @@ use rustc_middle::{
Instance, Ty,
},
};
use rustc_target::abi::call::{ArgAbi, ArgAttribute, ArgAttributes, FnAbi, PassMode};
use rustc_target::abi::call::{ArgAbi, FnAbi, PassMode};
use rustc_target::abi::{self, FieldIdx};
use rustc_target::spec::abi::Abi;
@ -291,32 +291,17 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
return true;
}
match (caller_layout.abi, callee_layout.abi) {
// If both sides have Scalar/Vector/ScalarPair ABI, we can easily directly compare them.
// Different valid ranges are okay (the validity check will complain if this leads to
// invalid transmutes). Different signs are *not* okay on some targets (e.g. `extern
// "C"` on `s390x` where small integers are passed zero/sign-extended in large
// registers), so we generally reject them to increase portability.
match caller_layout.abi {
// For Scalar/Vector/ScalarPair ABI, we directly compare them.
// NOTE: this is *not* a stable guarantee! It just reflects a property of our current
// ABIs. It's also fragile; the same pair of types might be considered ABI-compatible
// when used directly by-value but not considered compatible as a struct field or array
// element.
(abi::Abi::Scalar(caller), abi::Abi::Scalar(callee)) => {
caller.primitive() == callee.primitive()
abi::Abi::Scalar(..) | abi::Abi::ScalarPair(..) | abi::Abi::Vector { .. } => {
caller_layout.abi.eq_up_to_validity(&callee_layout.abi)
}
(
abi::Abi::Vector { element: caller_element, count: caller_count },
abi::Abi::Vector { element: callee_element, count: callee_count },
) => {
caller_element.primitive() == callee_element.primitive()
&& caller_count == callee_count
}
(abi::Abi::ScalarPair(caller1, caller2), abi::Abi::ScalarPair(callee1, callee2)) => {
caller1.primitive() == callee1.primitive()
&& caller2.primitive() == callee2.primitive()
}
(abi::Abi::Aggregate { .. }, abi::Abi::Aggregate { .. }) => {
// Aggregates are compatible only if they newtype-wrap the same type, or if they are both 1-ZST.
_ => {
// Everything else is compatible only if they newtype-wrap the same type, or if they are both 1-ZST.
// (The latter part is needed to ensure e.g. that `struct Zst` is compatible with `struct Wrap((), Zst)`.)
// This is conservative, but also means that our check isn't quite so heavily dependent on the `PassMode`,
// which means having ABI-compatibility on one target is much more likely to imply compatibility for other targets.
@ -329,9 +314,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
== self.unfold_transparent(callee_layout).ty
}
}
// What remains is `Abi::Uninhabited` (which can never be passed anyway) and
// mismatching ABIs, that should all be rejected.
_ => false,
}
}
@ -340,40 +322,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
caller_abi: &ArgAbi<'tcx, Ty<'tcx>>,
callee_abi: &ArgAbi<'tcx, Ty<'tcx>>,
) -> bool {
// When comparing the PassMode, we have to be smart about comparing the attributes.
let arg_attr_compat = |a1: &ArgAttributes, a2: &ArgAttributes| {
// There's only one regular attribute that matters for the call ABI: InReg.
// Everything else is things like noalias, dereferenceable, nonnull, ...
// (This also applies to pointee_size, pointee_align.)
if a1.regular.contains(ArgAttribute::InReg) != a2.regular.contains(ArgAttribute::InReg)
{
return false;
}
// We also compare the sign extension mode -- this could let the callee make assumptions
// about bits that conceptually were not even passed.
if a1.arg_ext != a2.arg_ext {
return false;
}
return true;
};
let mode_compat = || match (&caller_abi.mode, &callee_abi.mode) {
(PassMode::Ignore, PassMode::Ignore) => true, // can still be reached for the return type
(PassMode::Direct(a1), PassMode::Direct(a2)) => arg_attr_compat(a1, a2),
(PassMode::Pair(a1, b1), PassMode::Pair(a2, b2)) => {
arg_attr_compat(a1, a2) && arg_attr_compat(b1, b2)
}
(PassMode::Cast(c1, pad1), PassMode::Cast(c2, pad2)) => c1 == c2 && pad1 == pad2,
(
PassMode::Indirect { attrs: a1, extra_attrs: None, on_stack: s1 },
PassMode::Indirect { attrs: a2, extra_attrs: None, on_stack: s2 },
) => arg_attr_compat(a1, a2) && s1 == s2,
(
PassMode::Indirect { attrs: a1, extra_attrs: Some(e1), on_stack: s1 },
PassMode::Indirect { attrs: a2, extra_attrs: Some(e2), on_stack: s2 },
) => arg_attr_compat(a1, a2) && arg_attr_compat(e1, e2) && s1 == s2,
_ => false,
};
// Ideally `PassMode` would capture everything there is about argument passing, but that is
// not the case: in `FnAbi::llvm_type`, also parts of the layout and type information are
// used. So we need to check that *both* sufficiently agree to ensures the arguments are
@ -381,13 +329,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// For instance, `layout_compat` is needed to reject `i32` vs `f32`, which is not reflected
// in `PassMode`. `mode_compat` is needed to reject `u8` vs `bool`, which have the same
// `abi::Primitive` but different `arg_ext`.
if self.layout_compat(caller_abi.layout, callee_abi.layout) && mode_compat() {
// Something went very wrong if our checks don't even imply that the layout is the same.
assert!(
caller_abi.layout.size == callee_abi.layout.size
&& caller_abi.layout.align.abi == callee_abi.layout.align.abi
&& caller_abi.layout.is_sized() == callee_abi.layout.is_sized()
);
if self.layout_compat(caller_abi.layout, callee_abi.layout)
&& caller_abi.mode.eq_abi(&callee_abi.mode)
{
// Something went very wrong if our checks don't imply layout ABI compatibility.
assert!(caller_abi.layout.eq_abi(&callee_abi.layout));
return true;
} else {
trace!(

View File

@ -4,14 +4,14 @@
-passes_see_issue =
see issue #{$issue} <https://github.com/rust-lang/rust/issues/{$issue}> for more information
passes_abi =
abi: {$abi}
passes_abi_invalid_attribute =
`#[rustc_abi]` can only be applied to function items, type aliases, and associated functions
passes_abi_ne =
ABIs are not compatible
left ABI = {$left}
right ABI = {$right}
passes_abi_of =
fn_abi_of_instance({$fn_name}) = {$fn_abi}
passes_align =
align: {$align}
fn_abi_of({$fn_name}) = {$fn_abi}
passes_allow_incoherent_impl =
`rustc_allow_incoherent_impl` attribute should be applied to impl items.
@ -318,9 +318,6 @@ passes_has_incoherent_inherent_impl =
`rustc_has_incoherent_inherent_impls` attribute should be applied to types or traits.
.label = only adts, extern types and traits are supported
passes_homogeneous_aggregate =
homogeneous_aggregate: {$homogeneous_aggregate}
passes_ignored_attr =
`#[{$sym}]` is ignored on struct fields and match arms
.warn = {-passes_previously_accepted}
@ -404,9 +401,18 @@ passes_lang_item_on_incorrect_target =
passes_layout =
layout error: {$layout_error}
passes_layout_abi =
abi: {$abi}
passes_layout_align =
align: {$align}
passes_layout_homogeneous_aggregate =
homogeneous_aggregate: {$homogeneous_aggregate}
passes_layout_invalid_attribute =
`#[rustc_layout]` can only be applied to `struct`/`enum`/`union` declarations and type aliases
passes_layout_of =
layout_of({$normalized_ty}) = {$ty_layout}
passes_layout_size =
size: {$size}
passes_link =
attribute should be applied to an `extern` block with non-Rust ABI
@ -662,9 +668,6 @@ passes_should_be_applied_to_trait =
attribute should be applied to a trait
.label = not a trait
passes_size =
size: {$size}
passes_skipping_const_checks = skipping const checks
passes_stability_promotable =

View File

@ -2,11 +2,12 @@ use rustc_ast::Attribute;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_middle::ty::layout::{FnAbiError, LayoutError};
use rustc_middle::ty::{self, GenericArgs, Instance, TyCtxt};
use rustc_middle::ty::{self, GenericArgs, Instance, Ty, TyCtxt};
use rustc_span::source_map::Spanned;
use rustc_span::symbol::sym;
use rustc_target::abi::call::FnAbi;
use crate::errors::{AbiOf, UnrecognizedField};
use crate::errors::{AbiInvalidAttribute, AbiNe, AbiOf, UnrecognizedField};
pub fn test_abi(tcx: TyCtxt<'_>) {
if !tcx.features().rustc_attrs {
@ -14,66 +15,44 @@ pub fn test_abi(tcx: TyCtxt<'_>) {
return;
}
for id in tcx.hir().items() {
match tcx.def_kind(id.owner_id) {
DefKind::Fn => {
for attr in tcx.get_attrs(id.owner_id, sym::rustc_abi) {
dump_abi_of(tcx, id.owner_id.def_id.into(), attr);
for attr in tcx.get_attrs(id.owner_id, sym::rustc_abi) {
match tcx.def_kind(id.owner_id) {
DefKind::Fn => {
dump_abi_of_fn_item(tcx, id.owner_id.def_id.into(), attr);
}
DefKind::TyAlias { .. } => {
dump_abi_of_fn_type(tcx, id.owner_id.def_id.into(), attr);
}
_ => {
tcx.sess.emit_err(AbiInvalidAttribute { span: tcx.def_span(id.owner_id) });
}
}
DefKind::Impl { .. } => {
// To find associated functions we need to go into the child items here.
for &id in tcx.associated_item_def_ids(id.owner_id) {
if matches!(tcx.def_kind(id), DefKind::AssocFn) {
for attr in tcx.get_attrs(id, sym::rustc_abi) {
dump_abi_of(tcx, id, attr);
}
if matches!(tcx.def_kind(id.owner_id), DefKind::Impl { .. }) {
// To find associated functions we need to go into the child items here.
for &id in tcx.associated_item_def_ids(id.owner_id) {
for attr in tcx.get_attrs(id, sym::rustc_abi) {
match tcx.def_kind(id) {
DefKind::AssocFn => {
dump_abi_of_fn_item(tcx, id, attr);
}
_ => {
tcx.sess.emit_err(AbiInvalidAttribute { span: tcx.def_span(id) });
}
}
}
}
_ => {}
}
}
}
fn dump_abi_of(tcx: TyCtxt<'_>, item_def_id: DefId, attr: &Attribute) {
let param_env = tcx.param_env(item_def_id);
let args = GenericArgs::identity_for_item(tcx, item_def_id);
let instance = match Instance::resolve(tcx, param_env, item_def_id, args) {
Ok(Some(instance)) => instance,
Ok(None) => {
// Not sure what to do here, but `LayoutError::Unknown` seems reasonable?
let ty = tcx.type_of(item_def_id).instantiate_identity();
tcx.sess.emit_fatal(Spanned {
node: LayoutError::Unknown(ty).into_diagnostic(),
span: tcx.def_span(item_def_id),
});
}
Err(_guaranteed) => return,
};
match tcx.fn_abi_of_instance(param_env.and((instance, /* extra_args */ ty::List::empty()))) {
Ok(abi) => {
// Check out the `#[rustc_abi(..)]` attribute to tell what to dump.
// The `..` are the names of fields to dump.
let meta_items = attr.meta_item_list().unwrap_or_default();
for meta_item in meta_items {
match meta_item.name_or_empty() {
sym::debug => {
let fn_name = tcx.item_name(item_def_id);
tcx.sess.emit_err(AbiOf {
span: tcx.def_span(item_def_id),
fn_name,
fn_abi: format!("{:#?}", abi),
});
}
name => {
tcx.sess.emit_err(UnrecognizedField { span: meta_item.span(), name });
}
}
}
}
fn unwrap_fn_abi<'tcx>(
abi: Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>>,
tcx: TyCtxt<'tcx>,
item_def_id: DefId,
) -> &'tcx FnAbi<'tcx, Ty<'tcx>> {
match abi {
Ok(abi) => abi,
Err(FnAbiError::Layout(layout_error)) => {
tcx.sess.emit_fatal(Spanned {
node: layout_error.into_diagnostic(),
@ -91,3 +70,141 @@ fn dump_abi_of(tcx: TyCtxt<'_>, item_def_id: DefId, attr: &Attribute) {
}
}
}
fn dump_abi_of_fn_item(tcx: TyCtxt<'_>, item_def_id: DefId, attr: &Attribute) {
let param_env = tcx.param_env(item_def_id);
let args = GenericArgs::identity_for_item(tcx, item_def_id);
let instance = match Instance::resolve(tcx, param_env, item_def_id, args) {
Ok(Some(instance)) => instance,
Ok(None) => {
// Not sure what to do here, but `LayoutError::Unknown` seems reasonable?
let ty = tcx.type_of(item_def_id).instantiate_identity();
tcx.sess.emit_fatal(Spanned {
node: LayoutError::Unknown(ty).into_diagnostic(),
span: tcx.def_span(item_def_id),
});
}
Err(_guaranteed) => return,
};
let abi = unwrap_fn_abi(
tcx.fn_abi_of_instance(param_env.and((instance, /* extra_args */ ty::List::empty()))),
tcx,
item_def_id,
);
// Check out the `#[rustc_abi(..)]` attribute to tell what to dump.
// The `..` are the names of fields to dump.
let meta_items = attr.meta_item_list().unwrap_or_default();
for meta_item in meta_items {
match meta_item.name_or_empty() {
sym::debug => {
let fn_name = tcx.item_name(item_def_id);
tcx.sess.emit_err(AbiOf {
span: tcx.def_span(item_def_id),
fn_name,
fn_abi: format!("{:#?}", abi),
});
}
name => {
tcx.sess.emit_err(UnrecognizedField { span: meta_item.span(), name });
}
}
}
}
fn test_abi_eq<'tcx>(abi1: &'tcx FnAbi<'tcx, Ty<'tcx>>, abi2: &'tcx FnAbi<'tcx, Ty<'tcx>>) -> bool {
if abi1.conv != abi2.conv
|| abi1.args.len() != abi2.args.len()
|| abi1.c_variadic != abi2.c_variadic
|| abi1.fixed_count != abi2.fixed_count
|| abi1.can_unwind != abi2.can_unwind
{
return false;
}
abi1.ret.eq_abi(&abi2.ret)
&& abi1.args.iter().zip(abi2.args.iter()).all(|(arg1, arg2)| arg1.eq_abi(arg2))
}
fn dump_abi_of_fn_type(tcx: TyCtxt<'_>, item_def_id: DefId, attr: &Attribute) {
let param_env = tcx.param_env(item_def_id);
let ty = tcx.type_of(item_def_id).instantiate_identity();
let meta_items = attr.meta_item_list().unwrap_or_default();
for meta_item in meta_items {
match meta_item.name_or_empty() {
sym::debug => {
let ty::FnPtr(sig) = ty.kind() else {
span_bug!(
meta_item.span(),
"`#[rustc_abi(debug)]` on a type alias requires function pointer type"
);
};
let abi = unwrap_fn_abi(
tcx.fn_abi_of_fn_ptr(param_env.and((*sig, /* extra_args */ ty::List::empty()))),
tcx,
item_def_id,
);
let fn_name = tcx.item_name(item_def_id);
tcx.sess.emit_err(AbiOf {
span: tcx.def_span(item_def_id),
fn_name,
fn_abi: format!("{:#?}", abi),
});
}
sym::assert_eq => {
let ty::Tuple(fields) = ty.kind() else {
span_bug!(
meta_item.span(),
"`#[rustc_abi(assert_eq)]` on a type alias requires pair type"
);
};
let [field1, field2] = ***fields else {
span_bug!(
meta_item.span(),
"`#[rustc_abi(assert_eq)]` on a type alias requires pair type"
);
};
let ty::FnPtr(sig1) = field1.kind() else {
span_bug!(
meta_item.span(),
"`#[rustc_abi(assert_eq)]` on a type alias requires pair of function pointer types"
);
};
let abi1 = unwrap_fn_abi(
tcx.fn_abi_of_fn_ptr(
param_env.and((*sig1, /* extra_args */ ty::List::empty())),
),
tcx,
item_def_id,
);
let ty::FnPtr(sig2) = field2.kind() else {
span_bug!(
meta_item.span(),
"`#[rustc_abi(assert_eq)]` on a type alias requires pair of function pointer types"
);
};
let abi2 = unwrap_fn_abi(
tcx.fn_abi_of_fn_ptr(
param_env.and((*sig2, /* extra_args */ ty::List::empty())),
),
tcx,
item_def_id,
);
if !test_abi_eq(abi1, abi2) {
tcx.sess.emit_err(AbiNe {
span: tcx.def_span(item_def_id),
left: format!("{:#?}", abi1),
right: format!("{:#?}", abi2),
});
}
}
name => {
tcx.sess.emit_err(UnrecognizedField { span: meta_item.span(), name });
}
}
}
}

View File

@ -873,32 +873,32 @@ pub struct DuplicateDiagnosticItemInCrate {
}
#[derive(Diagnostic)]
#[diag(passes_abi)]
pub struct Abi {
#[diag(passes_layout_abi)]
pub struct LayoutAbi {
#[primary_span]
pub span: Span,
pub abi: String,
}
#[derive(Diagnostic)]
#[diag(passes_align)]
pub struct Align {
#[diag(passes_layout_align)]
pub struct LayoutAlign {
#[primary_span]
pub span: Span,
pub align: String,
}
#[derive(Diagnostic)]
#[diag(passes_size)]
pub struct Size {
#[diag(passes_layout_size)]
pub struct LayoutSize {
#[primary_span]
pub span: Span,
pub size: String,
}
#[derive(Diagnostic)]
#[diag(passes_homogeneous_aggregate)]
pub struct HomogeneousAggregate {
#[diag(passes_layout_homogeneous_aggregate)]
pub struct LayoutHomogeneousAggregate {
#[primary_span]
pub span: Span,
pub homogeneous_aggregate: String,
@ -913,6 +913,13 @@ pub struct LayoutOf {
pub ty_layout: String,
}
#[derive(Diagnostic)]
#[diag(passes_layout_invalid_attribute)]
pub struct LayoutInvalidAttribute {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(passes_abi_of)]
pub struct AbiOf {
@ -922,6 +929,22 @@ pub struct AbiOf {
pub fn_abi: String,
}
#[derive(Diagnostic)]
#[diag(passes_abi_ne)]
pub struct AbiNe {
#[primary_span]
pub span: Span,
pub left: String,
pub right: String,
}
#[derive(Diagnostic)]
#[diag(passes_abi_invalid_attribute)]
pub struct AbiInvalidAttribute {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(passes_unrecognized_field)]
pub struct UnrecognizedField {

View File

@ -8,7 +8,10 @@ use rustc_span::symbol::sym;
use rustc_span::Span;
use rustc_target::abi::{HasDataLayout, TargetDataLayout};
use crate::errors::{Abi, Align, HomogeneousAggregate, LayoutOf, Size, UnrecognizedField};
use crate::errors::{
LayoutAbi, LayoutAlign, LayoutHomogeneousAggregate, LayoutInvalidAttribute, LayoutOf,
LayoutSize, UnrecognizedField,
};
pub fn test_layout(tcx: TyCtxt<'_>) {
if !tcx.features().rustc_attrs {
@ -16,12 +19,22 @@ pub fn test_layout(tcx: TyCtxt<'_>) {
return;
}
for id in tcx.hir().items() {
if matches!(
tcx.def_kind(id.owner_id),
DefKind::TyAlias { .. } | DefKind::Enum | DefKind::Struct | DefKind::Union
) {
for attr in tcx.get_attrs(id.owner_id, sym::rustc_layout) {
dump_layout_of(tcx, id.owner_id.def_id, attr);
for attr in tcx.get_attrs(id.owner_id, sym::rustc_layout) {
match tcx.def_kind(id.owner_id) {
DefKind::TyAlias { .. } | DefKind::Enum | DefKind::Struct | DefKind::Union => {
dump_layout_of(tcx, id.owner_id.def_id, attr);
}
_ => {
tcx.sess.emit_err(LayoutInvalidAttribute { span: tcx.def_span(id.owner_id) });
}
}
}
if matches!(tcx.def_kind(id.owner_id), DefKind::Impl { .. }) {
// To find associated functions we need to go into the child items here.
for &id in tcx.associated_item_def_ids(id.owner_id) {
for _attr in tcx.get_attrs(id, sym::rustc_layout) {
tcx.sess.emit_err(LayoutInvalidAttribute { span: tcx.def_span(id) });
}
}
}
}
@ -38,28 +51,28 @@ fn dump_layout_of(tcx: TyCtxt<'_>, item_def_id: LocalDefId, attr: &Attribute) {
for meta_item in meta_items {
match meta_item.name_or_empty() {
sym::abi => {
tcx.sess.emit_err(Abi {
tcx.sess.emit_err(LayoutAbi {
span: tcx.def_span(item_def_id.to_def_id()),
abi: format!("{:?}", ty_layout.abi),
});
}
sym::align => {
tcx.sess.emit_err(Align {
tcx.sess.emit_err(LayoutAlign {
span: tcx.def_span(item_def_id.to_def_id()),
align: format!("{:?}", ty_layout.align),
});
}
sym::size => {
tcx.sess.emit_err(Size {
tcx.sess.emit_err(LayoutSize {
span: tcx.def_span(item_def_id.to_def_id()),
size: format!("{:?}", ty_layout.size),
});
}
sym::homogeneous_aggregate => {
tcx.sess.emit_err(HomogeneousAggregate {
tcx.sess.emit_err(LayoutHomogeneousAggregate {
span: tcx.def_span(item_def_id.to_def_id()),
homogeneous_aggregate: format!(
"{:?}",

View File

@ -387,6 +387,7 @@ symbols! {
asm_sym,
asm_unwind,
assert,
assert_eq,
assert_eq_macro,
assert_inhabited,
assert_macro,

View File

@ -36,7 +36,10 @@ pub enum PassMode {
Ignore,
/// Pass the argument directly.
///
/// The argument has a layout abi of `Scalar`, `Vector` or in rare cases `Aggregate`.
/// The argument has a layout abi of `Scalar` or `Vector`.
/// Unfortunately due to past mistakes, in rare cases on wasm, it can also be `Aggregate`.
/// This is bad since it leaks LLVM implementation details into the ABI.
/// (Also see <https://github.com/rust-lang/rust/issues/115666>.)
Direct(ArgAttributes),
/// Pass a pair's elements directly in two arguments.
///
@ -55,6 +58,29 @@ pub enum PassMode {
Indirect { attrs: ArgAttributes, extra_attrs: Option<ArgAttributes>, on_stack: bool },
}
impl PassMode {
/// Checks if these two `PassMode` are equal enough to be considered "the same for all
/// function call ABIs". However, the `Layout` can also impact ABI decisions,
/// so that needs to be compared as well!
pub fn eq_abi(&self, other: &Self) -> bool {
match (self, other) {
(PassMode::Ignore, PassMode::Ignore) => true, // can still be reached for the return type
(PassMode::Direct(a1), PassMode::Direct(a2)) => a1.eq_abi(a2),
(PassMode::Pair(a1, b1), PassMode::Pair(a2, b2)) => a1.eq_abi(a2) && b1.eq_abi(b2),
(PassMode::Cast(c1, pad1), PassMode::Cast(c2, pad2)) => c1.eq_abi(c2) && pad1 == pad2,
(
PassMode::Indirect { attrs: a1, extra_attrs: None, on_stack: s1 },
PassMode::Indirect { attrs: a2, extra_attrs: None, on_stack: s2 },
) => a1.eq_abi(a2) && s1 == s2,
(
PassMode::Indirect { attrs: a1, extra_attrs: Some(e1), on_stack: s1 },
PassMode::Indirect { attrs: a2, extra_attrs: Some(e2), on_stack: s2 },
) => a1.eq_abi(a2) && e1.eq_abi(e2) && s1 == s2,
_ => false,
}
}
}
// Hack to disable non_upper_case_globals only for the bitflags! and not for the rest
// of this module
pub use attr_impl::ArgAttribute;
@ -127,6 +153,24 @@ impl ArgAttributes {
pub fn contains(&self, attr: ArgAttribute) -> bool {
self.regular.contains(attr)
}
/// Checks if these two `ArgAttributes` are equal enough to be considered "the same for all
/// function call ABIs".
pub fn eq_abi(&self, other: &Self) -> bool {
// There's only one regular attribute that matters for the call ABI: InReg.
// Everything else is things like noalias, dereferenceable, nonnull, ...
// (This also applies to pointee_size, pointee_align.)
if self.regular.contains(ArgAttribute::InReg) != other.regular.contains(ArgAttribute::InReg)
{
return false;
}
// We also compare the sign extension mode -- this could let the callee make assumptions
// about bits that conceptually were not even passed.
if self.arg_ext != other.arg_ext {
return false;
}
return true;
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, HashStable_Generic)]
@ -272,6 +316,14 @@ impl CastTarget {
acc.max(align)
})
}
/// Checks if these two `CastTarget` are equal enough to be considered "the same for all
/// function call ABIs".
pub fn eq_abi(&self, other: &Self) -> bool {
let CastTarget { prefix: prefix_l, rest: rest_l, attrs: attrs_l } = self;
let CastTarget { prefix: prefix_r, rest: rest_r, attrs: attrs_r } = other;
prefix_l == prefix_r && rest_l == rest_r && attrs_l.eq_abi(attrs_r)
}
}
/// Return value from the `homogeneous_aggregate` test function.
@ -465,6 +517,7 @@ pub struct ArgAbi<'a, Ty> {
}
impl<'a, Ty> ArgAbi<'a, Ty> {
/// This defines the "default ABI" for that type, that is then later adjusted in `fn_abi_adjust_for_abi`.
pub fn new(
cx: &impl HasDataLayout,
layout: TyAndLayout<'a, Ty>,
@ -478,6 +531,7 @@ impl<'a, Ty> ArgAbi<'a, Ty> {
scalar_attrs(&layout, b, a.size(cx).align_to(b.align(cx).abi)),
),
Abi::Vector { .. } => PassMode::Direct(ArgAttributes::new()),
// The `Aggregate` ABI should always be adjusted later.
Abi::Aggregate { .. } => PassMode::Direct(ArgAttributes::new()),
};
ArgAbi { layout, mode }
@ -570,6 +624,14 @@ impl<'a, Ty> ArgAbi<'a, Ty> {
pub fn is_ignore(&self) -> bool {
matches!(self.mode, PassMode::Ignore)
}
/// Checks if these two `ArgAbi` are equal enough to be considered "the same for all
/// function call ABIs".
pub fn eq_abi(&self, other: &Self) -> bool {
// Ideally we'd just compare the `mode`, but that is not enough -- for some modes LLVM will look
// at the type.
self.layout.eq_abi(&other.layout) && self.mode.eq_abi(&other.mode)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, HashStable_Generic)]

View File

@ -61,6 +61,10 @@ where
/// The purpose of this ABI is for matching the WebAssembly standard. This
/// intentionally diverges from the C ABI and is specifically crafted to take
/// advantage of LLVM's support of multiple returns in WebAssembly.
///
/// This ABI is *bad*! It uses `PassMode::Direct` for `abi::Aggregate` types, which leaks LLVM
/// implementation details into the ABI. It's just hard to fix because ABIs are hard to change.
/// Also see <https://github.com/rust-lang/rust/issues/115666>.
pub fn compute_wasm_abi_info<Ty>(fn_abi: &mut FnAbi<'_, Ty>) {
if !fn_abi.ret.is_ignore() {
classify_ret(&mut fn_abi.ret);

View File

@ -520,6 +520,8 @@ fn fn_abi_adjust_for_abi<'tcx>(
_ => return,
}
// `Aggregate` ABI must be adjusted to ensure that ABI-compatible Rust types are passed
// the same way.
let size = arg.layout.size;
if arg.layout.is_unsized() || size > Pointer(AddressSpace::DATA).size(cx) {

View File

@ -43,7 +43,6 @@ pub extern "C" fn test_WithZst(_: WithZst) -> WithZst { loop {} }
#[repr(transparent)]
pub struct WithZeroSizedArray(*const f32, [i8; 0]);
// Apparently we use i32* when newtype-unwrapping f32 pointers. Whatever.
// CHECK: define{{.*}}ptr @test_WithZeroSizedArray(ptr noundef %_1)
#[no_mangle]
pub extern "C" fn test_WithZeroSizedArray(_: WithZeroSizedArray) -> WithZeroSizedArray { loop {} }

View File

@ -0,0 +1,149 @@
// check-pass
#![feature(rustc_attrs, transparent_unions)]
#![allow(unused, improper_ctypes_definitions)]
use std::marker::PhantomData;
use std::num::NonZeroI32;
use std::ptr::NonNull;
macro_rules! assert_abi_compatible {
($name:ident, $t1:ty, $t2:ty) => {
mod $name {
use super::*;
// Test argument and return value, `Rust` and `C` ABIs.
#[rustc_abi(assert_eq)]
type TestRust = (fn($t1) -> $t1, fn($t2) -> $t2);
#[rustc_abi(assert_eq)]
type TestC = (extern "C" fn($t1) -> $t1, extern "C" fn($t2) -> $t2);
}
};
}
#[derive(Copy, Clone)]
struct Zst;
#[repr(C)]
struct ReprC1<T>(T);
#[repr(C)]
struct ReprC2Int<T>(i32, T);
#[repr(C)]
struct ReprC2Float<T>(f32, T);
#[repr(C)]
struct ReprC4<T>(T, Vec<i32>, Zst, T);
#[repr(C)]
struct ReprC4Mixed<T>(T, f32, i32, T);
#[repr(C)]
enum ReprCEnum<T> {
Variant1,
Variant2(T),
}
#[repr(C)]
union ReprCUnion<T: Copy> {
nothing: (),
something: T,
}
macro_rules! test_abi_compatible {
($name:ident, $t1:ty, $t2:ty) => {
mod $name {
use super::*;
assert_abi_compatible!(plain, $t1, $t2);
// We also do some tests with differences in fields of `repr(C)` types.
assert_abi_compatible!(repr_c_1, ReprC1<$t1>, ReprC1<$t2>);
assert_abi_compatible!(repr_c_2_int, ReprC2Int<$t1>, ReprC2Int<$t2>);
assert_abi_compatible!(repr_c_2_float, ReprC2Float<$t1>, ReprC2Float<$t2>);
assert_abi_compatible!(repr_c_4, ReprC4<$t1>, ReprC4<$t2>);
assert_abi_compatible!(repr_c_4mixed, ReprC4Mixed<$t1>, ReprC4Mixed<$t2>);
assert_abi_compatible!(repr_c_enum, ReprCEnum<$t1>, ReprCEnum<$t2>);
assert_abi_compatible!(repr_c_union, ReprCUnion<$t1>, ReprCUnion<$t2>);
}
};
}
// Compatibility of pointers is probably de-facto guaranteed,
// but that does not seem to be documented.
test_abi_compatible!(ptr_mut, *const i32, *mut i32);
test_abi_compatible!(ptr_pointee, *const i32, *const Vec<i32>);
test_abi_compatible!(ref_mut, &i32, &mut i32);
test_abi_compatible!(ref_ptr, &i32, *const i32);
test_abi_compatible!(box_ptr, Box<i32>, *const i32);
test_abi_compatible!(nonnull_ptr, NonNull<i32>, *const i32);
test_abi_compatible!(fn_fn, fn(), fn(i32) -> i32);
// Some further guarantees we will likely (have to) make.
test_abi_compatible!(zst_unit, Zst, ());
test_abi_compatible!(zst_array, Zst, [u8; 0]);
test_abi_compatible!(nonzero_int, NonZeroI32, i32);
// `repr(transparent)` compatibility.
#[repr(transparent)]
struct Wrapper1<T>(T);
#[repr(transparent)]
struct Wrapper2<T>((), Zst, T);
#[repr(transparent)]
struct Wrapper3<T>(T, [u8; 0], PhantomData<u64>);
#[repr(transparent)]
union WrapperUnion<T: Copy> {
nothing: (),
something: T,
}
macro_rules! test_transparent {
($name:ident, $t:ty) => {
mod $name {
use super::*;
test_abi_compatible!(wrap1, $t, Wrapper1<$t>);
test_abi_compatible!(wrap2, $t, Wrapper2<$t>);
test_abi_compatible!(wrap3, $t, Wrapper3<$t>);
test_abi_compatible!(wrap4, $t, WrapperUnion<$t>);
}
};
}
test_transparent!(simple, i32);
test_transparent!(reference, &'static i32);
test_transparent!(zst, Zst);
test_transparent!(unit, ());
test_transparent!(pair, (i32, f32)); // mixing in some floats since they often get special treatment
test_transparent!(triple, (i8, i16, f32)); // chosen to fit into 64bit
test_transparent!(tuple, (i32, f32, i64, f64));
test_transparent!(empty_array, [u32; 0]);
test_transparent!(empty_1zst_array, [u8; 0]);
test_transparent!(small_array, [i32; 2]); // chosen to fit into 64bit
test_transparent!(large_array, [i32; 16]);
test_transparent!(enum_, Option<i32>);
test_transparent!(enum_niched, Option<&'static i32>);
// Pure-float types that are not ScalarPair seem to be tricky.
// FIXME: <https://github.com/rust-lang/rust/issues/115664>
#[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
mod tricky {
use super::*;
test_transparent!(triple_f32, (f32, f32, f32));
test_transparent!(triple_f64, (f64, f64, f64));
}
// RFC 3391 <https://rust-lang.github.io/rfcs/3391-result_ffi_guarantees.html>.
macro_rules! test_nonnull {
($name:ident, $t:ty) => {
mod $name {
use super::*;
test_abi_compatible!(option, Option<$t>, $t);
test_abi_compatible!(result_err_unit, Result<$t, ()>, $t);
test_abi_compatible!(result_ok_unit, Result<(), $t>, $t);
test_abi_compatible!(result_err_zst, Result<$t, Zst>, $t);
test_abi_compatible!(result_ok_zst, Result<Zst, $t>, $t);
test_abi_compatible!(result_err_arr, Result<$t, [i8; 0]>, $t);
test_abi_compatible!(result_ok_arr, Result<[i8; 0], $t>, $t);
}
}
}
test_nonnull!(ref_, &i32);
test_nonnull!(mut_, &mut i32);
test_nonnull!(ref_unsized, &[i32]);
test_nonnull!(mut_unsized, &mut [i32]);
test_nonnull!(fn_, fn());
test_nonnull!(nonnull, NonNull<i32>);
test_nonnull!(nonnull_unsized, NonNull<dyn std::fmt::Debug>);
test_nonnull!(non_zero, NonZeroI32);
fn main() {}

View File

@ -9,15 +9,42 @@
#![feature(rustc_attrs)]
#![crate_type = "lib"]
struct S(u16);
#[rustc_abi(debug)]
fn test(_x: u8) -> bool { true } //~ ERROR: fn_abi
#[rustc_abi(debug)]
type TestFnPtr = fn(bool) -> u8; //~ ERROR: fn_abi
#[rustc_abi(debug)]
fn test_generic<T>(_x: *const T) { } //~ ERROR: fn_abi
struct S(u16);
#[rustc_abi(debug)]
const C: () = (); //~ ERROR: can only be applied to
impl S {
#[rustc_abi(debug)]
const C: () = (); //~ ERROR: can only be applied to
}
impl S {
#[rustc_abi(debug)]
fn assoc_test(&self) { } //~ ERROR: fn_abi
}
#[rustc_abi(assert_eq)]
type TestAbiEq = (fn(bool), fn(bool));
#[rustc_abi(assert_eq)]
type TestAbiNe = (fn(u8), fn(u32)); //~ ERROR: ABIs are not compatible
#[rustc_abi(assert_eq)]
type TestAbiNeLarger = (fn([u8; 32]), fn([u32; 32])); //~ ERROR: ABIs are not compatible
#[rustc_abi(assert_eq)]
type TestAbiNeFloat = (fn(f32), fn(u32)); //~ ERROR: ABIs are not compatible
// Sign matters on some targets (such as s390x), so let's make sure we never accept this.
#[rustc_abi(assert_eq)]
type TestAbiNeSign = (fn(i32), fn(u32)); //~ ERROR: ABIs are not compatible

View File

@ -1,4 +1,4 @@
error: fn_abi_of_instance(test) = FnAbi {
error: fn_abi_of(test) = FnAbi {
args: [
ArgAbi {
layout: TyAndLayout {
@ -87,12 +87,106 @@ error: fn_abi_of_instance(test) = FnAbi {
conv: Rust,
can_unwind: $SOME_BOOL,
}
--> $DIR/debug.rs:13:1
--> $DIR/debug.rs:15:1
|
LL | fn test(_x: u8) -> bool { true }
| ^^^^^^^^^^^^^^^^^^^^^^^
error: fn_abi_of_instance(test_generic) = FnAbi {
error: fn_abi_of(TestFnPtr) = FnAbi {
args: [
ArgAbi {
layout: TyAndLayout {
ty: bool,
layout: Layout {
size: Size(1 bytes),
align: AbiAndPrefAlign {
abi: $SOME_ALIGN,
pref: $SOME_ALIGN,
},
abi: Scalar(
Initialized {
value: Int(
I8,
false,
),
valid_range: 0..=1,
},
),
fields: Primitive,
largest_niche: Some(
Niche {
offset: Size(0 bytes),
value: Int(
I8,
false,
),
valid_range: 0..=1,
},
),
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: $SOME_ALIGN,
},
},
mode: Direct(
ArgAttributes {
regular: NoUndef,
arg_ext: Zext,
pointee_size: Size(0 bytes),
pointee_align: None,
},
),
},
],
ret: ArgAbi {
layout: TyAndLayout {
ty: u8,
layout: Layout {
size: Size(1 bytes),
align: AbiAndPrefAlign {
abi: $SOME_ALIGN,
pref: $SOME_ALIGN,
},
abi: Scalar(
Initialized {
value: Int(
I8,
false,
),
valid_range: 0..=255,
},
),
fields: Primitive,
largest_niche: None,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: $SOME_ALIGN,
},
},
mode: Direct(
ArgAttributes {
regular: NoUndef,
arg_ext: None,
pointee_size: Size(0 bytes),
pointee_align: None,
},
),
},
c_variadic: false,
fixed_count: 1,
conv: Rust,
can_unwind: $SOME_BOOL,
}
--> $DIR/debug.rs:18:1
|
LL | type TestFnPtr = fn(bool) -> u8;
| ^^^^^^^^^^^^^^
error: fn_abi_of(test_generic) = FnAbi {
args: [
ArgAbi {
layout: TyAndLayout {
@ -163,12 +257,24 @@ error: fn_abi_of_instance(test_generic) = FnAbi {
conv: Rust,
can_unwind: $SOME_BOOL,
}
--> $DIR/debug.rs:17:1
--> $DIR/debug.rs:21:1
|
LL | fn test_generic<T>(_x: *const T) { }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: fn_abi_of_instance(assoc_test) = FnAbi {
error: `#[rustc_abi]` can only be applied to function items, type aliases, and associated functions
--> $DIR/debug.rs:24:1
|
LL | const C: () = ();
| ^^^^^^^^^^^
error: `#[rustc_abi]` can only be applied to function items, type aliases, and associated functions
--> $DIR/debug.rs:28:5
|
LL | const C: () = ();
| ^^^^^^^^^^^
error: fn_abi_of(assoc_test) = FnAbi {
args: [
ArgAbi {
layout: TyAndLayout {
@ -251,10 +357,593 @@ error: fn_abi_of_instance(assoc_test) = FnAbi {
conv: Rust,
can_unwind: $SOME_BOOL,
}
--> $DIR/debug.rs:22:5
--> $DIR/debug.rs:33:5
|
LL | fn assoc_test(&self) { }
| ^^^^^^^^^^^^^^^^^^^^
error: aborting due to 3 previous errors
error: ABIs are not compatible
left ABI = FnAbi {
args: [
ArgAbi {
layout: TyAndLayout {
ty: u8,
layout: Layout {
size: Size(1 bytes),
align: AbiAndPrefAlign {
abi: $SOME_ALIGN,
pref: $SOME_ALIGN,
},
abi: Scalar(
Initialized {
value: Int(
I8,
false,
),
valid_range: 0..=255,
},
),
fields: Primitive,
largest_niche: None,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: $SOME_ALIGN,
},
},
mode: Direct(
ArgAttributes {
regular: NoUndef,
arg_ext: None,
pointee_size: Size(0 bytes),
pointee_align: None,
},
),
},
],
ret: ArgAbi {
layout: TyAndLayout {
ty: (),
layout: Layout {
size: Size(0 bytes),
align: AbiAndPrefAlign {
abi: $SOME_ALIGN,
pref: $SOME_ALIGN,
},
abi: Aggregate {
sized: true,
},
fields: Arbitrary {
offsets: [],
memory_index: [],
},
largest_niche: None,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: $SOME_ALIGN,
},
},
mode: Ignore,
},
c_variadic: false,
fixed_count: 1,
conv: Rust,
can_unwind: $SOME_BOOL,
}
right ABI = FnAbi {
args: [
ArgAbi {
layout: TyAndLayout {
ty: u32,
layout: Layout {
size: $SOME_SIZE,
align: AbiAndPrefAlign {
abi: $SOME_ALIGN,
pref: $SOME_ALIGN,
},
abi: Scalar(
Initialized {
value: Int(
I32,
false,
),
valid_range: $FULL,
},
),
fields: Primitive,
largest_niche: None,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: $SOME_ALIGN,
},
},
mode: Direct(
ArgAttributes {
regular: NoUndef,
arg_ext: None,
pointee_size: Size(0 bytes),
pointee_align: None,
},
),
},
],
ret: ArgAbi {
layout: TyAndLayout {
ty: (),
layout: Layout {
size: Size(0 bytes),
align: AbiAndPrefAlign {
abi: $SOME_ALIGN,
pref: $SOME_ALIGN,
},
abi: Aggregate {
sized: true,
},
fields: Arbitrary {
offsets: [],
memory_index: [],
},
largest_niche: None,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: $SOME_ALIGN,
},
},
mode: Ignore,
},
c_variadic: false,
fixed_count: 1,
conv: Rust,
can_unwind: $SOME_BOOL,
}
--> $DIR/debug.rs:40:1
|
LL | type TestAbiNe = (fn(u8), fn(u32));
| ^^^^^^^^^^^^^^
error: ABIs are not compatible
left ABI = FnAbi {
args: [
ArgAbi {
layout: TyAndLayout {
ty: [u8; 32],
layout: Layout {
size: Size(32 bytes),
align: AbiAndPrefAlign {
abi: $SOME_ALIGN,
pref: $SOME_ALIGN,
},
abi: Aggregate {
sized: true,
},
fields: Array {
stride: Size(1 bytes),
count: 32,
},
largest_niche: None,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: $SOME_ALIGN,
},
},
mode: Indirect {
attrs: ArgAttributes {
regular: NoAlias | NoCapture | NonNull | NoUndef,
arg_ext: None,
pointee_size: Size(32 bytes),
pointee_align: Some(
Align(1 bytes),
),
},
extra_attrs: None,
on_stack: false,
},
},
],
ret: ArgAbi {
layout: TyAndLayout {
ty: (),
layout: Layout {
size: Size(0 bytes),
align: AbiAndPrefAlign {
abi: $SOME_ALIGN,
pref: $SOME_ALIGN,
},
abi: Aggregate {
sized: true,
},
fields: Arbitrary {
offsets: [],
memory_index: [],
},
largest_niche: None,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: $SOME_ALIGN,
},
},
mode: Ignore,
},
c_variadic: false,
fixed_count: 1,
conv: Rust,
can_unwind: $SOME_BOOL,
}
right ABI = FnAbi {
args: [
ArgAbi {
layout: TyAndLayout {
ty: [u32; 32],
layout: Layout {
size: Size(128 bytes),
align: AbiAndPrefAlign {
abi: $SOME_ALIGN,
pref: $SOME_ALIGN,
},
abi: Aggregate {
sized: true,
},
fields: Array {
stride: Size(4 bytes),
count: 32,
},
largest_niche: None,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: $SOME_ALIGN,
},
},
mode: Indirect {
attrs: ArgAttributes {
regular: NoAlias | NoCapture | NonNull | NoUndef,
arg_ext: None,
pointee_size: Size(128 bytes),
pointee_align: Some(
Align(4 bytes),
),
},
extra_attrs: None,
on_stack: false,
},
},
],
ret: ArgAbi {
layout: TyAndLayout {
ty: (),
layout: Layout {
size: Size(0 bytes),
align: AbiAndPrefAlign {
abi: $SOME_ALIGN,
pref: $SOME_ALIGN,
},
abi: Aggregate {
sized: true,
},
fields: Arbitrary {
offsets: [],
memory_index: [],
},
largest_niche: None,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: $SOME_ALIGN,
},
},
mode: Ignore,
},
c_variadic: false,
fixed_count: 1,
conv: Rust,
can_unwind: $SOME_BOOL,
}
--> $DIR/debug.rs:43:1
|
LL | type TestAbiNeLarger = (fn([u8; 32]), fn([u32; 32]));
| ^^^^^^^^^^^^^^^^^^^^
error: ABIs are not compatible
left ABI = FnAbi {
args: [
ArgAbi {
layout: TyAndLayout {
ty: f32,
layout: Layout {
size: $SOME_SIZE,
align: AbiAndPrefAlign {
abi: $SOME_ALIGN,
pref: $SOME_ALIGN,
},
abi: Scalar(
Initialized {
value: F32,
valid_range: $FULL,
},
),
fields: Primitive,
largest_niche: None,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: $SOME_ALIGN,
},
},
mode: Direct(
ArgAttributes {
regular: NoUndef,
arg_ext: None,
pointee_size: Size(0 bytes),
pointee_align: None,
},
),
},
],
ret: ArgAbi {
layout: TyAndLayout {
ty: (),
layout: Layout {
size: Size(0 bytes),
align: AbiAndPrefAlign {
abi: $SOME_ALIGN,
pref: $SOME_ALIGN,
},
abi: Aggregate {
sized: true,
},
fields: Arbitrary {
offsets: [],
memory_index: [],
},
largest_niche: None,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: $SOME_ALIGN,
},
},
mode: Ignore,
},
c_variadic: false,
fixed_count: 1,
conv: Rust,
can_unwind: $SOME_BOOL,
}
right ABI = FnAbi {
args: [
ArgAbi {
layout: TyAndLayout {
ty: u32,
layout: Layout {
size: $SOME_SIZE,
align: AbiAndPrefAlign {
abi: $SOME_ALIGN,
pref: $SOME_ALIGN,
},
abi: Scalar(
Initialized {
value: Int(
I32,
false,
),
valid_range: $FULL,
},
),
fields: Primitive,
largest_niche: None,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: $SOME_ALIGN,
},
},
mode: Direct(
ArgAttributes {
regular: NoUndef,
arg_ext: None,
pointee_size: Size(0 bytes),
pointee_align: None,
},
),
},
],
ret: ArgAbi {
layout: TyAndLayout {
ty: (),
layout: Layout {
size: Size(0 bytes),
align: AbiAndPrefAlign {
abi: $SOME_ALIGN,
pref: $SOME_ALIGN,
},
abi: Aggregate {
sized: true,
},
fields: Arbitrary {
offsets: [],
memory_index: [],
},
largest_niche: None,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: $SOME_ALIGN,
},
},
mode: Ignore,
},
c_variadic: false,
fixed_count: 1,
conv: Rust,
can_unwind: $SOME_BOOL,
}
--> $DIR/debug.rs:46:1
|
LL | type TestAbiNeFloat = (fn(f32), fn(u32));
| ^^^^^^^^^^^^^^^^^^^
error: ABIs are not compatible
left ABI = FnAbi {
args: [
ArgAbi {
layout: TyAndLayout {
ty: i32,
layout: Layout {
size: $SOME_SIZE,
align: AbiAndPrefAlign {
abi: $SOME_ALIGN,
pref: $SOME_ALIGN,
},
abi: Scalar(
Initialized {
value: Int(
I32,
true,
),
valid_range: $FULL,
},
),
fields: Primitive,
largest_niche: None,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: $SOME_ALIGN,
},
},
mode: Direct(
ArgAttributes {
regular: NoUndef,
arg_ext: None,
pointee_size: Size(0 bytes),
pointee_align: None,
},
),
},
],
ret: ArgAbi {
layout: TyAndLayout {
ty: (),
layout: Layout {
size: Size(0 bytes),
align: AbiAndPrefAlign {
abi: $SOME_ALIGN,
pref: $SOME_ALIGN,
},
abi: Aggregate {
sized: true,
},
fields: Arbitrary {
offsets: [],
memory_index: [],
},
largest_niche: None,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: $SOME_ALIGN,
},
},
mode: Ignore,
},
c_variadic: false,
fixed_count: 1,
conv: Rust,
can_unwind: $SOME_BOOL,
}
right ABI = FnAbi {
args: [
ArgAbi {
layout: TyAndLayout {
ty: u32,
layout: Layout {
size: $SOME_SIZE,
align: AbiAndPrefAlign {
abi: $SOME_ALIGN,
pref: $SOME_ALIGN,
},
abi: Scalar(
Initialized {
value: Int(
I32,
false,
),
valid_range: $FULL,
},
),
fields: Primitive,
largest_niche: None,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: $SOME_ALIGN,
},
},
mode: Direct(
ArgAttributes {
regular: NoUndef,
arg_ext: None,
pointee_size: Size(0 bytes),
pointee_align: None,
},
),
},
],
ret: ArgAbi {
layout: TyAndLayout {
ty: (),
layout: Layout {
size: Size(0 bytes),
align: AbiAndPrefAlign {
abi: $SOME_ALIGN,
pref: $SOME_ALIGN,
},
abi: Aggregate {
sized: true,
},
fields: Arbitrary {
offsets: [],
memory_index: [],
},
largest_niche: None,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: $SOME_ALIGN,
},
},
mode: Ignore,
},
c_variadic: false,
fixed_count: 1,
conv: Rust,
can_unwind: $SOME_BOOL,
}
--> $DIR/debug.rs:50:1
|
LL | type TestAbiNeSign = (fn(i32), fn(u32));
| ^^^^^^^^^^^^^^^^^^
error: aborting due to 10 previous errors

View File

@ -17,6 +17,9 @@ type Test = Result<i32, i32>; //~ ERROR: layout_of
#[rustc_layout(debug)]
type T = impl std::fmt::Debug; //~ ERROR: layout_of
fn f() -> T {
0i32
}
#[rustc_layout(debug)]
pub union V { //~ ERROR: layout_of
@ -63,6 +66,10 @@ union P5 { zst: [u16; 0], byte: u8 } //~ ERROR: layout_of
#[rustc_layout(debug)]
type X = std::mem::MaybeUninit<u8>; //~ ERROR: layout_of
fn f() -> T {
0i32
#[rustc_layout(debug)]
const C: () = (); //~ ERROR: can only be applied to
impl S {
#[rustc_layout(debug)]
const C: () = (); //~ ERROR: can only be applied to
}

View File

@ -344,7 +344,7 @@ error: layout_of(V) = Layout {
max_repr_align: None,
unadjusted_abi_align: Align(2 bytes),
}
--> $DIR/debug.rs:22:1
--> $DIR/debug.rs:25:1
|
LL | pub union V {
| ^^^^^^^^^^^
@ -368,7 +368,7 @@ error: layout_of(W) = Layout {
max_repr_align: None,
unadjusted_abi_align: Align(2 bytes),
}
--> $DIR/debug.rs:28:1
--> $DIR/debug.rs:31:1
|
LL | pub union W {
| ^^^^^^^^^^^
@ -392,7 +392,7 @@ error: layout_of(Y) = Layout {
max_repr_align: None,
unadjusted_abi_align: Align(2 bytes),
}
--> $DIR/debug.rs:34:1
--> $DIR/debug.rs:37:1
|
LL | pub union Y {
| ^^^^^^^^^^^
@ -416,7 +416,7 @@ error: layout_of(P1) = Layout {
max_repr_align: None,
unadjusted_abi_align: Align(1 bytes),
}
--> $DIR/debug.rs:41:1
--> $DIR/debug.rs:44:1
|
LL | union P1 { x: u32 }
| ^^^^^^^^
@ -440,7 +440,7 @@ error: layout_of(P2) = Layout {
max_repr_align: None,
unadjusted_abi_align: Align(1 bytes),
}
--> $DIR/debug.rs:45:1
--> $DIR/debug.rs:48:1
|
LL | union P2 { x: (u32, u32) }
| ^^^^^^^^
@ -464,7 +464,7 @@ error: layout_of(P3) = Layout {
max_repr_align: None,
unadjusted_abi_align: Align(1 bytes),
}
--> $DIR/debug.rs:53:1
--> $DIR/debug.rs:56:1
|
LL | union P3 { x: F32x4 }
| ^^^^^^^^
@ -488,7 +488,7 @@ error: layout_of(P4) = Layout {
max_repr_align: None,
unadjusted_abi_align: Align(1 bytes),
}
--> $DIR/debug.rs:57:1
--> $DIR/debug.rs:60:1
|
LL | union P4 { x: E }
| ^^^^^^^^
@ -517,7 +517,7 @@ error: layout_of(P5) = Layout {
max_repr_align: None,
unadjusted_abi_align: Align(1 bytes),
}
--> $DIR/debug.rs:61:1
--> $DIR/debug.rs:64:1
|
LL | union P5 { zst: [u16; 0], byte: u8 }
| ^^^^^^^^
@ -546,10 +546,22 @@ error: layout_of(std::mem::MaybeUninit<u8>) = Layout {
max_repr_align: None,
unadjusted_abi_align: Align(1 bytes),
}
--> $DIR/debug.rs:64:1
--> $DIR/debug.rs:67:1
|
LL | type X = std::mem::MaybeUninit<u8>;
| ^^^^^^
error: aborting due to 14 previous errors
error: `#[rustc_layout]` can only be applied to `struct`/`enum`/`union` declarations and type aliases
--> $DIR/debug.rs:70:1
|
LL | const C: () = ();
| ^^^^^^^^^^^
error: `#[rustc_layout]` can only be applied to `struct`/`enum`/`union` declarations and type aliases
--> $DIR/debug.rs:74:5
|
LL | const C: () = ();
| ^^^^^^^^^^^
error: aborting due to 16 previous errors