Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit c3e14ed

Browse files
committed
accept some differences for rustc_abi(assert_eq), so that we can test more things to be compatible
1 parent 8922c0c commit c3e14ed

File tree

7 files changed

+449
-64
lines changed

7 files changed

+449
-64
lines changed

compiler/rustc_abi/src/lib.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1348,6 +1348,23 @@ impl Abi {
13481348
Abi::Uninhabited | Abi::Aggregate { .. } => Abi::Aggregate { sized: true },
13491349
}
13501350
}
1351+
1352+
pub fn eq_up_to_validity(&self, other: &Self) -> bool {
1353+
match (self, other) {
1354+
// Scalar, Vector, ScalarPair have `Scalar` in them where we ignore validity ranges.
1355+
// We do *not* ignore the sign since it matters for some ABIs (e.g. s390x).
1356+
(Abi::Scalar(l), Abi::Scalar(r)) => l.primitive() == r.primitive(),
1357+
(
1358+
Abi::Vector { element: element_l, count: count_l },
1359+
Abi::Vector { element: element_r, count: count_r },
1360+
) => element_l.primitive() == element_r.primitive() && count_l == count_r,
1361+
(Abi::ScalarPair(l1, l2), Abi::ScalarPair(r1, r2)) => {
1362+
l1.primitive() == r1.primitive() && l2.primitive() == r2.primitive()
1363+
}
1364+
// Everything else must be strictly identical.
1365+
_ => self == other,
1366+
}
1367+
}
13511368
}
13521369

13531370
#[derive(PartialEq, Eq, Hash, Clone, Debug)]

compiler/rustc_const_eval/src/interpret/terminator.rs

Lines changed: 10 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use rustc_middle::{
1010
Instance, Ty,
1111
},
1212
};
13-
use rustc_target::abi::call::{ArgAbi, ArgAttribute, ArgAttributes, FnAbi, PassMode};
13+
use rustc_target::abi::call::{ArgAbi, FnAbi, PassMode};
1414
use rustc_target::abi::{self, FieldIdx};
1515
use rustc_target::spec::abi::Abi;
1616

@@ -291,32 +291,17 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
291291
return true;
292292
}
293293

294-
match (caller_layout.abi, callee_layout.abi) {
295-
// If both sides have Scalar/Vector/ScalarPair ABI, we can easily directly compare them.
296-
// Different valid ranges are okay (the validity check will complain if this leads to
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.
294+
match caller_layout.abi {
295+
// For Scalar/Vector/ScalarPair ABI, we directly compare them.
300296
// NOTE: this is *not* a stable guarantee! It just reflects a property of our current
301297
// ABIs. It's also fragile; the same pair of types might be considered ABI-compatible
302298
// when used directly by-value but not considered compatible as a struct field or array
303299
// element.
304-
(abi::Abi::Scalar(caller), abi::Abi::Scalar(callee)) => {
305-
caller.primitive() == callee.primitive()
300+
abi::Abi::Scalar(..) | abi::Abi::ScalarPair(..) | abi::Abi::Vector { .. } => {
301+
caller_layout.abi.eq_up_to_validity(&callee_layout.abi)
306302
}
307-
(
308-
abi::Abi::Vector { element: caller_element, count: caller_count },
309-
abi::Abi::Vector { element: callee_element, count: callee_count },
310-
) => {
311-
caller_element.primitive() == callee_element.primitive()
312-
&& caller_count == callee_count
313-
}
314-
(abi::Abi::ScalarPair(caller1, caller2), abi::Abi::ScalarPair(callee1, callee2)) => {
315-
caller1.primitive() == callee1.primitive()
316-
&& caller2.primitive() == callee2.primitive()
317-
}
318-
(abi::Abi::Aggregate { .. }, abi::Abi::Aggregate { .. }) => {
319-
// Aggregates are compatible only if they newtype-wrap the same type, or if they are both 1-ZST.
303+
_ => {
304+
// Everything else is compatible only if they newtype-wrap the same type, or if they are both 1-ZST.
320305
// (The latter part is needed to ensure e.g. that `struct Zst` is compatible with `struct Wrap((), Zst)`.)
321306
// This is conservative, but also means that our check isn't quite so heavily dependent on the `PassMode`,
322307
// which means having ABI-compatibility on one target is much more likely to imply compatibility for other targets.
@@ -329,9 +314,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
329314
== self.unfold_transparent(callee_layout).ty
330315
}
331316
}
332-
// What remains is `Abi::Uninhabited` (which can never be passed anyway) and
333-
// mismatching ABIs, that should all be rejected.
334-
_ => false,
335317
}
336318
}
337319

@@ -340,48 +322,16 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
340322
caller_abi: &ArgAbi<'tcx, Ty<'tcx>>,
341323
callee_abi: &ArgAbi<'tcx, Ty<'tcx>>,
342324
) -> bool {
343-
// When comparing the PassMode, we have to be smart about comparing the attributes.
344-
let arg_attr_compat = |a1: &ArgAttributes, a2: &ArgAttributes| {
345-
// There's only one regular attribute that matters for the call ABI: InReg.
346-
// Everything else is things like noalias, dereferenceable, nonnull, ...
347-
// (This also applies to pointee_size, pointee_align.)
348-
if a1.regular.contains(ArgAttribute::InReg) != a2.regular.contains(ArgAttribute::InReg)
349-
{
350-
return false;
351-
}
352-
// We also compare the sign extension mode -- this could let the callee make assumptions
353-
// about bits that conceptually were not even passed.
354-
if a1.arg_ext != a2.arg_ext {
355-
return false;
356-
}
357-
return true;
358-
};
359-
let mode_compat = || match (&caller_abi.mode, &callee_abi.mode) {
360-
(PassMode::Ignore, PassMode::Ignore) => true, // can still be reached for the return type
361-
(PassMode::Direct(a1), PassMode::Direct(a2)) => arg_attr_compat(a1, a2),
362-
(PassMode::Pair(a1, b1), PassMode::Pair(a2, b2)) => {
363-
arg_attr_compat(a1, a2) && arg_attr_compat(b1, b2)
364-
}
365-
(PassMode::Cast(c1, pad1), PassMode::Cast(c2, pad2)) => c1 == c2 && pad1 == pad2,
366-
(
367-
PassMode::Indirect { attrs: a1, extra_attrs: None, on_stack: s1 },
368-
PassMode::Indirect { attrs: a2, extra_attrs: None, on_stack: s2 },
369-
) => arg_attr_compat(a1, a2) && s1 == s2,
370-
(
371-
PassMode::Indirect { attrs: a1, extra_attrs: Some(e1), on_stack: s1 },
372-
PassMode::Indirect { attrs: a2, extra_attrs: Some(e2), on_stack: s2 },
373-
) => arg_attr_compat(a1, a2) && arg_attr_compat(e1, e2) && s1 == s2,
374-
_ => false,
375-
};
376-
377325
// Ideally `PassMode` would capture everything there is about argument passing, but that is
378326
// not the case: in `FnAbi::llvm_type`, also parts of the layout and type information are
379327
// used. So we need to check that *both* sufficiently agree to ensures the arguments are
380328
// compatible.
381329
// For instance, `layout_compat` is needed to reject `i32` vs `f32`, which is not reflected
382330
// in `PassMode`. `mode_compat` is needed to reject `u8` vs `bool`, which have the same
383331
// `abi::Primitive` but different `arg_ext`.
384-
if self.layout_compat(caller_abi.layout, callee_abi.layout) && mode_compat() {
332+
if self.layout_compat(caller_abi.layout, callee_abi.layout)
333+
&& caller_abi.mode.eq_abi(&callee_abi.mode)
334+
{
385335
// Something went very wrong if our checks don't even imply that the layout is the same.
386336
assert!(
387337
caller_abi.layout.size == callee_abi.layout.size

compiler/rustc_passes/src/abi_test.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,7 @@ fn test_arg_abi_eq<'tcx>(
121121
// Ideally we'd just compare the `mode`, but that is not enough -- for some modes LLVM will look
122122
// at the type. Comparing the `mode` and `layout.abi` should catch basically everything though
123123
// (except for tricky cases around unized types).
124-
// This *is* overly strict (e.g. we compare the sign of integer `Primitive`s, or parts of `ArgAttributes` that do not affect ABI),
125-
// but for the purpose of ensuring repr(transparent) ABI compatibility that is fine.
126-
abi1.mode == abi2.mode && abi1.layout.abi == abi2.layout.abi
124+
abi1.mode.eq_abi(&abi2.mode) && abi1.layout.abi.eq_up_to_validity(&abi2.layout.abi)
127125
}
128126

129127
fn test_abi_eq<'tcx>(abi1: &'tcx FnAbi<'tcx, Ty<'tcx>>, abi2: &'tcx FnAbi<'tcx, Ty<'tcx>>) -> bool {

compiler/rustc_target/src/abi/call/mod.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,28 @@ pub enum PassMode {
5555
Indirect { attrs: ArgAttributes, extra_attrs: Option<ArgAttributes>, on_stack: bool },
5656
}
5757

58+
impl PassMode {
59+
/// Checks if these two `PassMode` are equal enough to be considered "the same for all
60+
/// function call ABIs".
61+
pub fn eq_abi(&self, other: &Self) -> bool {
62+
match (self, other) {
63+
(PassMode::Ignore, PassMode::Ignore) => true, // can still be reached for the return type
64+
(PassMode::Direct(a1), PassMode::Direct(a2)) => a1.eq_abi(a2),
65+
(PassMode::Pair(a1, b1), PassMode::Pair(a2, b2)) => a1.eq_abi(a2) && b1.eq_abi(b2),
66+
(PassMode::Cast(c1, pad1), PassMode::Cast(c2, pad2)) => c1.eq_abi(c2) && pad1 == pad2,
67+
(
68+
PassMode::Indirect { attrs: a1, extra_attrs: None, on_stack: s1 },
69+
PassMode::Indirect { attrs: a2, extra_attrs: None, on_stack: s2 },
70+
) => a1.eq_abi(a2) && s1 == s2,
71+
(
72+
PassMode::Indirect { attrs: a1, extra_attrs: Some(e1), on_stack: s1 },
73+
PassMode::Indirect { attrs: a2, extra_attrs: Some(e2), on_stack: s2 },
74+
) => a1.eq_abi(a2) && e1.eq_abi(e2) && s1 == s2,
75+
_ => false,
76+
}
77+
}
78+
}
79+
5880
// Hack to disable non_upper_case_globals only for the bitflags! and not for the rest
5981
// of this module
6082
pub use attr_impl::ArgAttribute;
@@ -127,6 +149,24 @@ impl ArgAttributes {
127149
pub fn contains(&self, attr: ArgAttribute) -> bool {
128150
self.regular.contains(attr)
129151
}
152+
153+
/// Checks if these two `ArgAttributes` are equal enough to be considered "the same for all
154+
/// function call ABIs".
155+
pub fn eq_abi(&self, other: &Self) -> bool {
156+
// There's only one regular attribute that matters for the call ABI: InReg.
157+
// Everything else is things like noalias, dereferenceable, nonnull, ...
158+
// (This also applies to pointee_size, pointee_align.)
159+
if self.regular.contains(ArgAttribute::InReg) != other.regular.contains(ArgAttribute::InReg)
160+
{
161+
return false;
162+
}
163+
// We also compare the sign extension mode -- this could let the callee make assumptions
164+
// about bits that conceptually were not even passed.
165+
if self.arg_ext != other.arg_ext {
166+
return false;
167+
}
168+
return true;
169+
}
130170
}
131171

132172
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, HashStable_Generic)]
@@ -272,6 +312,14 @@ impl CastTarget {
272312
acc.max(align)
273313
})
274314
}
315+
316+
/// Checks if these two `CastTarget` are equal enough to be considered "the same for all
317+
/// function call ABIs".
318+
pub fn eq_abi(&self, other: &Self) -> bool {
319+
let CastTarget { prefix: prefix_l, rest: rest_l, attrs: attrs_l } = self;
320+
let CastTarget { prefix: prefix_r, rest: rest_r, attrs: attrs_r } = other;
321+
prefix_l == prefix_r && rest_l == rest_r && attrs_l.eq_abi(attrs_r)
322+
}
275323
}
276324

277325
/// Return value from the `homogeneous_aggregate` test function.

tests/ui/abi/compatibility.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// check-pass
2+
#![feature(rustc_attrs)]
3+
#![allow(unused, improper_ctypes_definitions)]
4+
use std::num::NonZeroI32;
5+
use std::ptr::NonNull;
6+
7+
macro_rules! assert_abi_compatible {
8+
($name:ident, $t1:ty, $t2:ty) => {
9+
mod $name {
10+
use super::*;
11+
// Test argument and return value, `Rust` and `C` ABIs.
12+
#[rustc_abi(assert_eq)]
13+
type TestRust = (fn($t1) -> $t1, fn($t2) -> $t2);
14+
#[rustc_abi(assert_eq)]
15+
type TestC = (extern "C" fn($t1) -> $t1, extern "C" fn($t2) -> $t2);
16+
}
17+
};
18+
}
19+
20+
#[derive(Copy, Clone)]
21+
struct Zst;
22+
23+
#[repr(C)]
24+
struct ReprC1<T>(T);
25+
#[repr(C)]
26+
struct ReprC2Int<T>(i32, T);
27+
#[repr(C)]
28+
struct ReprC2Float<T>(f32, T);
29+
#[repr(C)]
30+
struct ReprC4<T>(T, T, T, T);
31+
#[repr(C)]
32+
struct ReprC4Mixed<T>(T, f32, i32, T);
33+
#[repr(C)]
34+
enum ReprCEnum<T> {
35+
Variant1,
36+
Variant2(T),
37+
}
38+
#[repr(C)]
39+
union ReprCUnion<T: Copy> {
40+
nothing: (),
41+
something: T,
42+
}
43+
44+
macro_rules! test_abi_compatible {
45+
($name:ident, $t1:ty, $t2:ty) => {
46+
mod $name {
47+
use super::*;
48+
assert_abi_compatible!(plain, $t1, $t2);
49+
// We also do some tests with differences in fields of `repr(C)` types.
50+
assert_abi_compatible!(repr_c_1, ReprC1<$t1>, ReprC1<$t2>);
51+
assert_abi_compatible!(repr_c_2_int, ReprC2Int<$t1>, ReprC2Int<$t2>);
52+
assert_abi_compatible!(repr_c_2_float, ReprC2Float<$t1>, ReprC2Float<$t2>);
53+
assert_abi_compatible!(repr_c_4, ReprC4<$t1>, ReprC4<$t2>);
54+
assert_abi_compatible!(repr_c_4mixed, ReprC4Mixed<$t1>, ReprC4Mixed<$t2>);
55+
assert_abi_compatible!(repr_c_enum, ReprCEnum<$t1>, ReprCEnum<$t2>);
56+
assert_abi_compatible!(repr_c_union, ReprCUnion<$t1>, ReprCUnion<$t2>);
57+
}
58+
};
59+
}
60+
61+
// Compatibility of pointers is probably de-facto guaranteed,
62+
// but that does not seem to be documented.
63+
test_abi_compatible!(ptr_mut, *const i32, *mut i32);
64+
test_abi_compatible!(ptr_pointee, *const i32, *const Vec<i32>);
65+
test_abi_compatible!(ref_mut, &i32, &mut i32);
66+
test_abi_compatible!(ref_ptr, &i32, *const i32);
67+
test_abi_compatible!(box_ptr, Box<i32>, *const i32);
68+
test_abi_compatible!(nonnull_ptr, NonNull<i32>, *const i32);
69+
test_abi_compatible!(fn_fn, fn(), fn(i32) -> i32);
70+
71+
// Some further guarantees we will likely (have to) make.
72+
test_abi_compatible!(zst_unit, Zst, ());
73+
test_abi_compatible!(zst_array, Zst, [u8; 0]);
74+
test_abi_compatible!(nonzero_int, NonZeroI32, i32);
75+
76+
fn main() {}

tests/ui/abi/debug.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,10 @@ type TestAbiEq = (fn(bool), fn(bool));
3838

3939
#[rustc_abi(assert_eq)]
4040
type TestAbiNe = (fn(u8), fn(u32)); //~ ERROR: ABIs are not compatible
41+
42+
#[rustc_abi(assert_eq)]
43+
type TestAbiNeFloat = (fn(f32), fn(u32)); //~ ERROR: ABIs are not compatible
44+
45+
// Sign matters on some targets (such as s390x), so let's make sure we never accept this.
46+
#[rustc_abi(assert_eq)]
47+
type TestAbiNeSign = (fn(i32), fn(u32)); //~ ERROR: ABIs are not compatible

0 commit comments

Comments
 (0)