Skip to content

Commit db6ae07

Browse files
authored
Rollup merge of #115411 - RalfJung:miri-abi, r=oli-obk
miri ABI check: fix handling of 1-ZST; don't accept sign differences r? `@oli-obk`
2 parents 361f8ba + efc7592 commit db6ae07

File tree

2 files changed

+39
-45
lines changed

2 files changed

+39
-45
lines changed

compiler/rustc_const_eval/src/interpret/terminator.rs

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
254254
}
255255

256256
/// Find the wrapped inner type of a transparent wrapper.
257+
/// Must not be called on 1-ZST (as they don't have a uniquely defined "wrapped field").
257258
fn unfold_transparent(&self, layout: TyAndLayout<'tcx>) -> TyAndLayout<'tcx> {
258259
match layout.ty.kind() {
259260
ty::Adt(adt_def, _) if adt_def.repr().transparent() => {
@@ -263,11 +264,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
263264
let field = layout.field(self, idx);
264265
if field.is_1zst() { None } else { Some(field) }
265266
});
266-
let Some(first) = non_1zst_fields.next() else {
267-
// All fields are 1-ZST, so this is basically the same as `()`.
268-
// (We still also compare the `PassMode`, so if this target does something strange with 1-ZST there, we'll know.)
269-
return self.layout_of(self.tcx.types.unit).unwrap();
270-
};
267+
let first = non_1zst_fields.next().expect("`unfold_transparent` called on 1-ZST");
271268
assert!(
272269
non_1zst_fields.next().is_none(),
273270
"more than one non-1-ZST field in a transparent type"
@@ -289,17 +286,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
289286
caller_layout: TyAndLayout<'tcx>,
290287
callee_layout: TyAndLayout<'tcx>,
291288
) -> bool {
292-
fn primitive_abi_compat(a1: abi::Primitive, a2: abi::Primitive) -> bool {
293-
match (a1, a2) {
294-
// For integers, ignore the sign.
295-
(abi::Primitive::Int(int_ty1, _sign1), abi::Primitive::Int(int_ty2, _sign2)) => {
296-
int_ty1 == int_ty2
297-
}
298-
// For everything else we require full equality.
299-
_ => a1 == a2,
300-
}
301-
}
302-
303289
if caller_layout.ty == callee_layout.ty {
304290
// Fast path: equal types are definitely compatible.
305291
return true;
@@ -308,27 +294,40 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
308294
match (caller_layout.abi, callee_layout.abi) {
309295
// If both sides have Scalar/Vector/ScalarPair ABI, we can easily directly compare them.
310296
// Different valid ranges are okay (the validity check will complain if this leads to
311-
// invalid transmutes).
297+
// invalid transmutes). Different signs are *not* okay on some targets (e.g. `extern
298+
// "C"` on `s390x` where small integers are passed zero/sign-extended in large
299+
// registers), so we generally reject them to increase portability.
300+
// NOTE: this is *not* a stable guarantee! It just reflects a property of our current
301+
// ABIs. It's also fragile; the same pair of types might be considered ABI-compatible
302+
// when used directly by-value but not considered compatible as a struct field or array
303+
// element.
312304
(abi::Abi::Scalar(caller), abi::Abi::Scalar(callee)) => {
313-
primitive_abi_compat(caller.primitive(), callee.primitive())
305+
caller.primitive() == callee.primitive()
314306
}
315307
(
316308
abi::Abi::Vector { element: caller_element, count: caller_count },
317309
abi::Abi::Vector { element: callee_element, count: callee_count },
318310
) => {
319-
primitive_abi_compat(caller_element.primitive(), callee_element.primitive())
311+
caller_element.primitive() == callee_element.primitive()
320312
&& caller_count == callee_count
321313
}
322314
(abi::Abi::ScalarPair(caller1, caller2), abi::Abi::ScalarPair(callee1, callee2)) => {
323-
primitive_abi_compat(caller1.primitive(), callee1.primitive())
324-
&& primitive_abi_compat(caller2.primitive(), callee2.primitive())
315+
caller1.primitive() == callee1.primitive()
316+
&& caller2.primitive() == callee2.primitive()
325317
}
326318
(abi::Abi::Aggregate { .. }, abi::Abi::Aggregate { .. }) => {
327-
// Aggregates are compatible only if they newtype-wrap the same type.
319+
// Aggregates are compatible only if they newtype-wrap the same type, or if they are both 1-ZST.
320+
// (The latter part is needed to ensure e.g. that `struct Zst` is compatible with `struct Wrap((), Zst)`.)
328321
// This is conservative, but also means that our check isn't quite so heavily dependent on the `PassMode`,
329322
// which means having ABI-compatibility on one target is much more likely to imply compatibility for other targets.
330-
self.unfold_transparent(caller_layout).ty
331-
== self.unfold_transparent(callee_layout).ty
323+
if caller_layout.is_1zst() || callee_layout.is_1zst() {
324+
// If either is a 1-ZST, both must be.
325+
caller_layout.is_1zst() && callee_layout.is_1zst()
326+
} else {
327+
// Neither is a 1-ZST, so we can check what they are wrapping.
328+
self.unfold_transparent(caller_layout).ty
329+
== self.unfold_transparent(callee_layout).ty
330+
}
332331
}
333332
// What remains is `Abi::Uninhabited` (which can never be passed anyway) and
334333
// mismatching ABIs, that should all be rejected.

src/tools/miri/tests/pass/function_calls/abi_compat.rs

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
#![feature(portable_simd)]
21
use std::mem;
32
use std::num;
4-
use std::simd;
53

6-
#[derive(Copy, Clone)]
4+
#[derive(Copy, Clone, Default)]
75
struct Zst;
86

97
fn test_abi_compat<T: Copy, U: Copy>(t: T, u: U) {
@@ -33,7 +31,7 @@ fn test_abi_compat<T: Copy, U: Copy>(t: T, u: U) {
3331
}
3432

3533
/// Ensure that `T` is compatible with various repr(transparent) wrappers around `T`.
36-
fn test_abi_newtype<T: Copy>(t: T) {
34+
fn test_abi_newtype<T: Copy + Default>() {
3735
#[repr(transparent)]
3836
#[derive(Copy, Clone)]
3937
struct Wrapper1<T>(T);
@@ -47,6 +45,7 @@ fn test_abi_newtype<T: Copy>(t: T) {
4745
#[derive(Copy, Clone)]
4846
struct Wrapper3<T>(Zst, T, [u8; 0]);
4947

48+
let t = T::default();
5049
test_abi_compat(t, Wrapper1(t));
5150
test_abi_compat(t, Wrapper2(t, ()));
5251
test_abi_compat(t, Wrapper2a((), t));
@@ -56,36 +55,32 @@ fn test_abi_newtype<T: Copy>(t: T) {
5655

5756
fn main() {
5857
// Here we check:
59-
// - unsigned vs signed integer is allowed
60-
// - u32/i32 vs char is allowed
58+
// - u32 vs char is allowed
6159
// - u32 vs NonZeroU32/Option<NonZeroU32> is allowed
6260
// - reference vs raw pointer is allowed
6361
// - references to things of the same size and alignment are allowed
6462
// These are very basic tests that should work on all ABIs. However it is not clear that any of
6563
// these would be stably guaranteed. Code that relies on this is equivalent to code that relies
6664
// on the layout of `repr(Rust)` types. They are also fragile: the same mismatches in the fields
6765
// of a struct (even with `repr(C)`) will not always be accepted by Miri.
68-
test_abi_compat(0u32, 0i32);
69-
test_abi_compat(simd::u32x8::splat(1), simd::i32x8::splat(1));
66+
// Note that `bool` and `u8` are *not* compatible, at least on x86-64!
67+
// One of them has `arg_ext: Zext`, the other does not.
68+
// Similarly, `i32` and `u32` are not compatible on s390x due to different `arg_ext`.
7069
test_abi_compat(0u32, 'x');
71-
test_abi_compat(0i32, 'x');
7270
test_abi_compat(42u32, num::NonZeroU32::new(1).unwrap());
7371
test_abi_compat(0u32, Some(num::NonZeroU32::new(1).unwrap()));
7472
test_abi_compat(&0u32, &0u32 as *const u32);
7573
test_abi_compat(&0u32, &([true; 4], [0u32; 0]));
76-
// Note that `bool` and `u8` are *not* compatible, at least on x86-64!
77-
// One of them has `arg_ext: Zext`, the other does not.
7874

7975
// These must work for *any* type, since we guarantee that `repr(transparent)` is ABI-compatible
8076
// with the wrapped field.
81-
test_abi_newtype(());
82-
// FIXME: this still fails! test_abi_newtype(Zst);
83-
test_abi_newtype(0u32);
84-
test_abi_newtype(0f32);
85-
test_abi_newtype((0u32, 1u32, 2u32));
86-
// FIXME: skipping the array tests on mips64 due to https://github.com/rust-lang/rust/issues/115404
87-
if !cfg!(target_arch = "mips64") {
88-
test_abi_newtype([0u32, 1u32, 2u32]);
89-
test_abi_newtype([0i32; 0]);
90-
}
77+
test_abi_newtype::<()>();
78+
test_abi_newtype::<Zst>();
79+
test_abi_newtype::<u32>();
80+
test_abi_newtype::<f32>();
81+
test_abi_newtype::<(u8, u16, f32)>();
82+
test_abi_newtype::<[u8; 0]>();
83+
test_abi_newtype::<[u32; 0]>();
84+
test_abi_newtype::<[u32; 2]>();
85+
test_abi_newtype::<[u32; 32]>();
9186
}

0 commit comments

Comments
 (0)