add support for rustc_abi(assert_eq) and use it to test some repr(transparent) cases
This commit is contained in:
parent
c981026195
commit
8922c0c541
@ -6,6 +6,10 @@
|
||||
|
||||
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({$fn_name}) = {$fn_abi}
|
||||
|
||||
|
@ -5,9 +5,9 @@ use rustc_middle::ty::layout::{FnAbiError, LayoutError};
|
||||
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 rustc_target::abi::call::{ArgAbi, FnAbi};
|
||||
|
||||
use crate::errors::{AbiInvalidAttribute, AbiOf, UnrecognizedField};
|
||||
use crate::errors::{AbiInvalidAttribute, AbiNe, AbiOf, UnrecognizedField};
|
||||
|
||||
pub fn test_abi(tcx: TyCtxt<'_>) {
|
||||
if !tcx.features().rustc_attrs {
|
||||
@ -114,6 +114,32 @@ fn dump_abi_of_fn_item(tcx: TyCtxt<'_>, item_def_id: DefId, attr: &Attribute) {
|
||||
}
|
||||
}
|
||||
|
||||
fn test_arg_abi_eq<'tcx>(
|
||||
abi1: &'tcx ArgAbi<'tcx, Ty<'tcx>>,
|
||||
abi2: &'tcx ArgAbi<'tcx, Ty<'tcx>>,
|
||||
) -> bool {
|
||||
// Ideally we'd just compare the `mode`, but that is not enough -- for some modes LLVM will look
|
||||
// at the type. Comparing the `mode` and `layout.abi` should catch basically everything though
|
||||
// (except for tricky cases around unized types).
|
||||
// This *is* overly strict (e.g. we compare the sign of integer `Primitive`s, or parts of `ArgAttributes` that do not affect ABI),
|
||||
// but for the purpose of ensuring repr(transparent) ABI compatibility that is fine.
|
||||
abi1.mode == abi2.mode && abi1.layout.abi == abi2.layout.abi
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
test_arg_abi_eq(&abi1.ret, &abi2.ret)
|
||||
&& abi1.args.iter().zip(abi2.args.iter()).all(|(arg1, arg2)| test_arg_abi_eq(arg1, 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();
|
||||
@ -140,6 +166,54 @@ fn dump_abi_of_fn_type(tcx: TyCtxt<'_>, item_def_id: DefId, attr: &Attribute) {
|
||||
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 });
|
||||
}
|
||||
|
@ -929,6 +929,15 @@ 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 {
|
||||
|
@ -387,6 +387,7 @@ symbols! {
|
||||
asm_sym,
|
||||
asm_unwind,
|
||||
assert,
|
||||
assert_eq,
|
||||
assert_eq_macro,
|
||||
assert_inhabited,
|
||||
assert_macro,
|
||||
|
@ -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 {} }
|
||||
|
@ -32,3 +32,9 @@ 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
|
||||
|
@ -362,5 +362,151 @@ error: fn_abi_of(assoc_test) = FnAbi {
|
||||
LL | fn assoc_test(&self) { }
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: aborting due to 6 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: aborting due to 7 previous errors
|
||||
|
||||
|
79
tests/ui/abi/transparent.rs
Normal file
79
tests/ui/abi/transparent.rs
Normal file
@ -0,0 +1,79 @@
|
||||
// check-pass
|
||||
#![feature(rustc_attrs)]
|
||||
#![allow(unused, improper_ctypes_definitions)]
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
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;
|
||||
|
||||
// Check that various `transparent` wrappers result in equal ABIs.
|
||||
#[repr(transparent)]
|
||||
struct Wrapper1<T>(T);
|
||||
#[repr(transparent)]
|
||||
struct Wrapper2<T>((), Zst, T);
|
||||
#[repr(transparent)]
|
||||
struct Wrapper3<T>(T, [u8; 0], PhantomData<u64>);
|
||||
|
||||
#[repr(C)]
|
||||
struct ReprCStruct<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_transparent {
|
||||
($name:ident, $t:ty) => {
|
||||
mod $name {
|
||||
use super::*;
|
||||
assert_abi_compatible!(wrap1, $t, Wrapper1<$t>);
|
||||
assert_abi_compatible!(wrap2, $t, Wrapper2<$t>);
|
||||
assert_abi_compatible!(wrap3, $t, Wrapper3<$t>);
|
||||
// Also try adding some surrounding `repr(C)` types.
|
||||
assert_abi_compatible!(repr_c_struct_wrap1, ReprCStruct<$t>, ReprCStruct<Wrapper1<$t>>);
|
||||
assert_abi_compatible!(repr_c_enum_wrap1, ReprCEnum<$t>, ReprCEnum<Wrapper1<$t>>);
|
||||
assert_abi_compatible!(repr_c_union_wrap1, ReprCUnion<$t>, ReprCUnion<Wrapper1<$t>>);
|
||||
assert_abi_compatible!(repr_c_struct_wrap2, ReprCStruct<$t>, ReprCStruct<Wrapper2<$t>>);
|
||||
assert_abi_compatible!(repr_c_enum_wrap2, ReprCEnum<$t>, ReprCEnum<Wrapper2<$t>>);
|
||||
assert_abi_compatible!(repr_c_union_wrap2, ReprCUnion<$t>, ReprCUnion<Wrapper2<$t>>);
|
||||
assert_abi_compatible!(repr_c_struct_wrap3, ReprCStruct<$t>, ReprCStruct<Wrapper3<$t>>);
|
||||
assert_abi_compatible!(repr_c_enum_wrap3, ReprCEnum<$t>, ReprCEnum<Wrapper3<$t>>);
|
||||
assert_abi_compatible!(repr_c_union_wrap3, ReprCUnion<$t>, ReprCUnion<Wrapper3<$t>>);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test_transparent!(simple, i32);
|
||||
test_transparent!(reference, &'static i32);
|
||||
test_transparent!(zst, Zst);
|
||||
test_transparent!(unit, ());
|
||||
test_transparent!(pair, (i32, f32));
|
||||
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>);
|
||||
|
||||
fn main() {}
|
Loading…
x
Reference in New Issue
Block a user