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
8 changed files with 322 additions and 4 deletions
|
@ -6,6 +6,10 @@
|
||||||
|
|
||||||
passes_abi_invalid_attribute =
|
passes_abi_invalid_attribute =
|
||||||
`#[rustc_abi]` can only be applied to function items, type aliases, and associated functions
|
`#[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 =
|
passes_abi_of =
|
||||||
fn_abi_of({$fn_name}) = {$fn_abi}
|
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_middle::ty::{self, GenericArgs, Instance, Ty, TyCtxt};
|
||||||
use rustc_span::source_map::Spanned;
|
use rustc_span::source_map::Spanned;
|
||||||
use rustc_span::symbol::sym;
|
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<'_>) {
|
pub fn test_abi(tcx: TyCtxt<'_>) {
|
||||||
if !tcx.features().rustc_attrs {
|
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) {
|
fn dump_abi_of_fn_type(tcx: TyCtxt<'_>, item_def_id: DefId, attr: &Attribute) {
|
||||||
let param_env = tcx.param_env(item_def_id);
|
let param_env = tcx.param_env(item_def_id);
|
||||||
let ty = tcx.type_of(item_def_id).instantiate_identity();
|
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),
|
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 => {
|
name => {
|
||||||
tcx.sess.emit_err(UnrecognizedField { span: meta_item.span(), name });
|
tcx.sess.emit_err(UnrecognizedField { span: meta_item.span(), name });
|
||||||
}
|
}
|
||||||
|
|
|
@ -929,6 +929,15 @@ pub struct AbiOf {
|
||||||
pub fn_abi: String,
|
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)]
|
#[derive(Diagnostic)]
|
||||||
#[diag(passes_abi_invalid_attribute)]
|
#[diag(passes_abi_invalid_attribute)]
|
||||||
pub struct AbiInvalidAttribute {
|
pub struct AbiInvalidAttribute {
|
||||||
|
|
|
@ -387,6 +387,7 @@ symbols! {
|
||||||
asm_sym,
|
asm_sym,
|
||||||
asm_unwind,
|
asm_unwind,
|
||||||
assert,
|
assert,
|
||||||
|
assert_eq,
|
||||||
assert_eq_macro,
|
assert_eq_macro,
|
||||||
assert_inhabited,
|
assert_inhabited,
|
||||||
assert_macro,
|
assert_macro,
|
||||||
|
|
|
@ -43,7 +43,6 @@ pub extern "C" fn test_WithZst(_: WithZst) -> WithZst { loop {} }
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct WithZeroSizedArray(*const f32, [i8; 0]);
|
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)
|
// CHECK: define{{.*}}ptr @test_WithZeroSizedArray(ptr noundef %_1)
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn test_WithZeroSizedArray(_: WithZeroSizedArray) -> WithZeroSizedArray { loop {} }
|
pub extern "C" fn test_WithZeroSizedArray(_: WithZeroSizedArray) -> WithZeroSizedArray { loop {} }
|
||||||
|
|
|
@ -32,3 +32,9 @@ impl S {
|
||||||
#[rustc_abi(debug)]
|
#[rustc_abi(debug)]
|
||||||
fn assoc_test(&self) { } //~ ERROR: fn_abi
|
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) { }
|
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…
Add table
Add a link
Reference in a new issue