about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2023-09-06 11:13:20 +0200
committerRalf Jung <post@ralfj.de>2023-09-07 09:14:29 +0200
commit8922c0c541c0fbfe6d0465eed6362a2bdbe6a0f0 (patch)
treed5f98127c941d3603c63094adc091891906e8fb5
parentc9810261956faa1152ade6a37efe6583b2caaa20 (diff)
downloadrust-8922c0c541c0fbfe6d0465eed6362a2bdbe6a0f0.tar.gz
rust-8922c0c541c0fbfe6d0465eed6362a2bdbe6a0f0.zip
add support for rustc_abi(assert_eq) and use it to test some repr(transparent) cases
-rw-r--r--compiler/rustc_passes/messages.ftl4
-rw-r--r--compiler/rustc_passes/src/abi_test.rs78
-rw-r--r--compiler/rustc_passes/src/errors.rs9
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--tests/codegen/repr/transparent.rs1
-rw-r--r--tests/ui/abi/debug.rs6
-rw-r--r--tests/ui/abi/debug.stderr148
-rw-r--r--tests/ui/abi/transparent.rs79
8 files changed, 322 insertions, 4 deletions
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
@@ -930,6 +930,15 @@ pub struct AbiOf {
 }
 
 #[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]
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>(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() {}