diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 602d614295c..9f3cd656bd1 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -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} diff --git a/compiler/rustc_passes/src/abi_test.rs b/compiler/rustc_passes/src/abi_test.rs index 4653a9c2b39..48c18912703 100644 --- a/compiler/rustc_passes/src/abi_test.rs +++ b/compiler/rustc_passes/src/abi_test.rs @@ -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 }); } diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index de55b5ec2cb..d7319ef91e0 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -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 { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 656deebb5d0..63070b67717 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -387,6 +387,7 @@ symbols! { asm_sym, asm_unwind, assert, + assert_eq, assert_eq_macro, assert_inhabited, assert_macro, diff --git a/tests/codegen/repr/transparent.rs b/tests/codegen/repr/transparent.rs index b140fc719da..c5974248bb3 100644 --- a/tests/codegen/repr/transparent.rs +++ b/tests/codegen/repr/transparent.rs @@ -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 {} } diff --git a/tests/ui/abi/debug.rs b/tests/ui/abi/debug.rs index 8575021494d..058c205abd8 100644 --- a/tests/ui/abi/debug.rs +++ b/tests/ui/abi/debug.rs @@ -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 diff --git a/tests/ui/abi/debug.stderr b/tests/ui/abi/debug.stderr index 2315288c3c7..321ec8715c6 100644 --- a/tests/ui/abi/debug.stderr +++ b/tests/ui/abi/debug.stderr @@ -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 diff --git a/tests/ui/abi/transparent.rs b/tests/ui/abi/transparent.rs new file mode 100644 index 00000000000..90bdc129a4a --- /dev/null +++ b/tests/ui/abi/transparent.rs @@ -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); +#[repr(transparent)] +struct Wrapper2((), Zst, T); +#[repr(transparent)] +struct Wrapper3(T, [u8; 0], PhantomData); + +#[repr(C)] +struct ReprCStruct(T, f32, i32, T); +#[repr(C)] +enum ReprCEnum { + Variant1, + Variant2(T), +} +#[repr(C)] +union ReprCUnion { + 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>); + assert_abi_compatible!(repr_c_enum_wrap1, ReprCEnum<$t>, ReprCEnum>); + assert_abi_compatible!(repr_c_union_wrap1, ReprCUnion<$t>, ReprCUnion>); + assert_abi_compatible!(repr_c_struct_wrap2, ReprCStruct<$t>, ReprCStruct>); + assert_abi_compatible!(repr_c_enum_wrap2, ReprCEnum<$t>, ReprCEnum>); + assert_abi_compatible!(repr_c_union_wrap2, ReprCUnion<$t>, ReprCUnion>); + assert_abi_compatible!(repr_c_struct_wrap3, ReprCStruct<$t>, ReprCStruct>); + assert_abi_compatible!(repr_c_enum_wrap3, ReprCEnum<$t>, ReprCEnum>); + assert_abi_compatible!(repr_c_union_wrap3, ReprCUnion<$t>, ReprCUnion>); + } + } +} + +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); +test_transparent!(enum_niched, Option<&'static i32>); + +fn main() {}