Skip to content

Commit 191dc54

Browse files
committed
Auto merge of #115182 - RalfJung:abi-compat-sign, r=b-naber
miri ABI compatibility check: accept u32 and i32 If only the sign differs, then surely these types are compatible. (We do still check that `arg_ext` is the same, just in case.) Also I made it so that the ABI check must *imply* that size and alignment are the same, but it doesn't actively check that itself. With how crazy ABI constraints get, having equal size and align really shouldn't be used as a signal for anything I think...
2 parents 4e78abb + 5194060 commit 191dc54

File tree

2 files changed

+56
-15
lines changed

2 files changed

+56
-15
lines changed

compiler/rustc_const_eval/src/interpret/terminator.rs

+29-15
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,16 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
255255
caller_abi: &ArgAbi<'tcx, Ty<'tcx>>,
256256
callee_abi: &ArgAbi<'tcx, Ty<'tcx>>,
257257
) -> bool {
258+
let primitive_abi_compat = |a1: abi::Primitive, a2: abi::Primitive| -> bool {
259+
match (a1, a2) {
260+
// For integers, ignore the sign.
261+
(abi::Primitive::Int(int_ty1, _sign1), abi::Primitive::Int(int_ty2, _sign2)) => {
262+
int_ty1 == int_ty2
263+
}
264+
// For everything else we require full equality.
265+
_ => a1 == a2,
266+
}
267+
};
258268
// Heuristic for type comparison.
259269
let layout_compat = || {
260270
if caller_abi.layout.ty == callee_abi.layout.ty {
@@ -267,28 +277,24 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
267277
// then who knows what happens.
268278
return false;
269279
}
270-
if caller_abi.layout.size != callee_abi.layout.size
271-
|| caller_abi.layout.align.abi != callee_abi.layout.align.abi
272-
{
273-
// This cannot go well...
274-
return false;
275-
}
276-
// The rest *should* be okay, but we are extra conservative.
280+
// This is tricky. Some ABIs split aggregates up into multiple registers etc, so we have
281+
// to be super careful here. For the scalar ABIs we conveniently already have all the
282+
// newtypes unwrapped etc, so in those cases we can just compare the scalar components.
283+
// Everything else we just reject for now.
277284
match (caller_abi.layout.abi, callee_abi.layout.abi) {
278-
// Different valid ranges are okay (once we enforce validity,
279-
// that will take care to make it UB to leave the range, just
280-
// like for transmute).
285+
// Different valid ranges are okay (the validity check will complain if this leads
286+
// to invalid transmutes).
281287
(abi::Abi::Scalar(caller), abi::Abi::Scalar(callee)) => {
282-
caller.primitive() == callee.primitive()
288+
primitive_abi_compat(caller.primitive(), callee.primitive())
283289
}
284290
(
285291
abi::Abi::ScalarPair(caller1, caller2),
286292
abi::Abi::ScalarPair(callee1, callee2),
287293
) => {
288-
caller1.primitive() == callee1.primitive()
289-
&& caller2.primitive() == callee2.primitive()
294+
primitive_abi_compat(caller1.primitive(), callee1.primitive())
295+
&& primitive_abi_compat(caller2.primitive(), callee2.primitive())
290296
}
291-
// Be conservative
297+
// Be conservative.
292298
_ => false,
293299
}
294300
};
@@ -309,7 +315,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
309315
return true;
310316
};
311317
let mode_compat = || match (&caller_abi.mode, &callee_abi.mode) {
312-
(PassMode::Ignore, PassMode::Ignore) => true,
318+
(PassMode::Ignore, PassMode::Ignore) => true, // can still be reached for the return type
313319
(PassMode::Direct(a1), PassMode::Direct(a2)) => arg_attr_compat(a1, a2),
314320
(PassMode::Pair(a1, b1), PassMode::Pair(a2, b2)) => {
315321
arg_attr_compat(a1, a2) && arg_attr_compat(b1, b2)
@@ -326,7 +332,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
326332
_ => false,
327333
};
328334

335+
// We have to check both. `layout_compat` is needed to reject e.g. `i32` vs `f32`,
336+
// which is not reflected in `PassMode`. `mode_compat` is needed to reject `u8` vs `bool`,
337+
// which have the same `abi::Primitive` but different `arg_ext`.
329338
if layout_compat() && mode_compat() {
339+
// Something went very wrong if our checks don't even imply that the layout is the same.
340+
assert!(
341+
caller_abi.layout.size == callee_abi.layout.size
342+
&& caller_abi.layout.align.abi == callee_abi.layout.align.abi
343+
);
330344
return true;
331345
}
332346
trace!(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use std::num;
2+
use std::mem;
3+
4+
fn test_abi_compat<T, U>(t: T, u: U) {
5+
fn id<T>(x: T) -> T { x }
6+
7+
// This checks ABI compatibility both for arguments and return values,
8+
// in both directions.
9+
let f: fn(T) -> T = id;
10+
let f: fn(U) -> U = unsafe { std::mem::transmute(f) };
11+
drop(f(u));
12+
13+
let f: fn(U) -> U = id;
14+
let f: fn(T) -> T = unsafe { std::mem::transmute(f) };
15+
drop(f(t));
16+
}
17+
18+
fn main() {
19+
test_abi_compat(0u32, 'x');
20+
test_abi_compat(&0u32, &([true; 4], [0u32; 0]));
21+
test_abi_compat(0u32, mem::MaybeUninit::new(0u32));
22+
test_abi_compat(42u32, num::NonZeroU32::new(1).unwrap());
23+
test_abi_compat(0u32, Some(num::NonZeroU32::new(1).unwrap()));
24+
test_abi_compat(0u32, 0i32);
25+
// Note that `bool` and `u8` are *not* compatible!
26+
// One of them has `arg_ext: Zext`, the other does not.
27+
}

0 commit comments

Comments
 (0)