add support for rustc_abi(assert_eq) and use it to test some repr(transparent) cases

This commit is contained in:
Ralf Jung 2023-09-06 11:13:20 +02:00
parent c981026195
commit 8922c0c541
8 changed files with 322 additions and 4 deletions

View File

@ -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}

View File

@ -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 });
}

View File

@ -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 {

View File

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

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

@ -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

View File

@ -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

View 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() {}