Skip to content

Commit 1e4c0bc

Browse files
committed
Auto merge of rust-lang#115372 - RalfJung:abi-assert-eq, r=davidtwco
add rustc_abi(assert_eq) to test some guaranteed or at least highly expected ABI compatibility guarantees This new repr(transparent) test is super useful, it would have found rust-lang#115336 and found rust-lang#115404, rust-lang#115481, rust-lang#115509.
2 parents 1e746d7 + c738e70 commit 1e4c0bc

File tree

17 files changed

+1293
-170
lines changed

17 files changed

+1293
-170
lines changed

compiler/rustc_abi/src/lib.rs

+40-1
Original file line numberDiff line numberDiff line change
@@ -1300,12 +1300,18 @@ impl Abi {
13001300
matches!(*self, Abi::Uninhabited)
13011301
}
13021302

1303-
/// Returns `true` is this is a scalar type
1303+
/// Returns `true` if this is a scalar type
13041304
#[inline]
13051305
pub fn is_scalar(&self) -> bool {
13061306
matches!(*self, Abi::Scalar(_))
13071307
}
13081308

1309+
/// Returns `true` if this is a bool
1310+
#[inline]
1311+
pub fn is_bool(&self) -> bool {
1312+
matches!(*self, Abi::Scalar(s) if s.is_bool())
1313+
}
1314+
13091315
/// Returns the fixed alignment of this ABI, if any is mandated.
13101316
pub fn inherent_align<C: HasDataLayout>(&self, cx: &C) -> Option<AbiAndPrefAlign> {
13111317
Some(match *self {
@@ -1348,6 +1354,23 @@ impl Abi {
13481354
Abi::Uninhabited | Abi::Aggregate { .. } => Abi::Aggregate { sized: true },
13491355
}
13501356
}
1357+
1358+
pub fn eq_up_to_validity(&self, other: &Self) -> bool {
1359+
match (self, other) {
1360+
// Scalar, Vector, ScalarPair have `Scalar` in them where we ignore validity ranges.
1361+
// We do *not* ignore the sign since it matters for some ABIs (e.g. s390x).
1362+
(Abi::Scalar(l), Abi::Scalar(r)) => l.primitive() == r.primitive(),
1363+
(
1364+
Abi::Vector { element: element_l, count: count_l },
1365+
Abi::Vector { element: element_r, count: count_r },
1366+
) => element_l.primitive() == element_r.primitive() && count_l == count_r,
1367+
(Abi::ScalarPair(l1, l2), Abi::ScalarPair(r1, r2)) => {
1368+
l1.primitive() == r1.primitive() && l2.primitive() == r2.primitive()
1369+
}
1370+
// Everything else must be strictly identical.
1371+
_ => self == other,
1372+
}
1373+
}
13511374
}
13521375

13531376
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
@@ -1686,6 +1709,22 @@ impl LayoutS {
16861709
Abi::Aggregate { sized } => sized && self.size.bytes() == 0,
16871710
}
16881711
}
1712+
1713+
/// Checks if these two `Layout` are equal enough to be considered "the same for all function
1714+
/// call ABIs". Note however that real ABIs depend on more details that are not reflected in the
1715+
/// `Layout`; the `PassMode` need to be compared as well.
1716+
pub fn abi_eq(&self, other: &Self) -> bool {
1717+
// The one thing that we are not capturing here is that for unsized types, the metadata must
1718+
// also have the same ABI, and moreover that the same metadata leads to the same size. The
1719+
// 2nd point is quite hard to check though.
1720+
self.size == other.size
1721+
&& self.is_sized() == other.is_sized()
1722+
&& self.abi.eq_up_to_validity(&other.abi)
1723+
&& self.abi.is_bool() == other.abi.is_bool()
1724+
&& self.align.abi == other.align.abi
1725+
&& self.max_repr_align == other.max_repr_align
1726+
&& self.unadjusted_abi_align == other.unadjusted_abi_align
1727+
}
16891728
}
16901729

16911730
#[derive(Copy, Clone, Debug)]

compiler/rustc_codegen_llvm/src/abi.rs

+38-1
Original file line numberDiff line numberDiff line change
@@ -340,15 +340,50 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
340340
};
341341

342342
for arg in args {
343+
// Note that the exact number of arguments pushed here is carefully synchronized with
344+
// code all over the place, both in the codegen_llvm and codegen_ssa crates. That's how
345+
// other code then knows which LLVM argument(s) correspond to the n-th Rust argument.
343346
let llarg_ty = match &arg.mode {
344347
PassMode::Ignore => continue,
345-
PassMode::Direct(_) => arg.layout.immediate_llvm_type(cx),
348+
PassMode::Direct(_) => {
349+
// ABI-compatible Rust types have the same `layout.abi` (up to validity ranges),
350+
// and for Scalar ABIs the LLVM type is fully determined by `layout.abi`,
351+
// guarnateeing that we generate ABI-compatible LLVM IR. Things get tricky for
352+
// aggregates...
353+
if matches!(arg.layout.abi, abi::Abi::Aggregate { .. }) {
354+
// This really shouldn't happen, since `immediate_llvm_type` will use
355+
// `layout.fields` to turn this Rust type into an LLVM type. This means all
356+
// sorts of Rust type details leak into the ABI. However wasm sadly *does*
357+
// currently use this mode so we have to allow it -- but we absolutely
358+
// shouldn't let any more targets do that.
359+
// (Also see <https://github.com/rust-lang/rust/issues/83788#issuecomment-1477045004>.)
360+
assert!(
361+
matches!(&*cx.tcx.sess.target.arch, "wasm32" | "wasm64"),
362+
"`PassMode::Direct` for aggregates only allowed on wasm targets\nProblematic type: {:#?}",
363+
arg.layout,
364+
);
365+
}
366+
arg.layout.immediate_llvm_type(cx)
367+
}
346368
PassMode::Pair(..) => {
369+
// ABI-compatible Rust types have the same `layout.abi` (up to validity ranges),
370+
// so for ScalarPair we can easily be sure that we are generating ABI-compatible
371+
// LLVM IR.
372+
assert!(
373+
matches!(arg.layout.abi, abi::Abi::ScalarPair(..)),
374+
"PassMode::Pair for type {}",
375+
arg.layout.ty
376+
);
347377
llargument_tys.push(arg.layout.scalar_pair_element_llvm_type(cx, 0, true));
348378
llargument_tys.push(arg.layout.scalar_pair_element_llvm_type(cx, 1, true));
349379
continue;
350380
}
351381
PassMode::Indirect { attrs: _, extra_attrs: Some(_), on_stack: _ } => {
382+
assert!(arg.layout.is_unsized());
383+
// Construct the type of a (wide) pointer to `ty`, and pass its two fields.
384+
// Any two ABI-compatible unsized types have the same metadata type and
385+
// moreover the same metadata value leads to the same dynamic size and
386+
// alignment, so this respects ABI compatibility.
352387
let ptr_ty = Ty::new_mut_ptr(cx.tcx, arg.layout.ty);
353388
let ptr_layout = cx.layout_of(ptr_ty);
354389
llargument_tys.push(ptr_layout.scalar_pair_element_llvm_type(cx, 0, true));
@@ -360,6 +395,8 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
360395
if *pad_i32 {
361396
llargument_tys.push(Reg::i32().llvm_type(cx));
362397
}
398+
// Compute the LLVM type we use for this function from the cast type.
399+
// We assume here that ABI-compatible Rust types have the same cast type.
363400
cast.llvm_type(cx)
364401
}
365402
PassMode::Indirect { attrs: _, extra_attrs: None, on_stack: _ } => cx.type_ptr(),

compiler/rustc_const_eval/src/interpret/terminator.rs

+12-66
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,54 +322,18 @@ 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() {
385-
// Something went very wrong if our checks don't even imply that the layout is the same.
386-
assert!(
387-
caller_abi.layout.size == callee_abi.layout.size
388-
&& caller_abi.layout.align.abi == callee_abi.layout.align.abi
389-
&& caller_abi.layout.is_sized() == callee_abi.layout.is_sized()
390-
);
332+
if self.layout_compat(caller_abi.layout, callee_abi.layout)
333+
&& caller_abi.mode.eq_abi(&callee_abi.mode)
334+
{
335+
// Something went very wrong if our checks don't imply layout ABI compatibility.
336+
assert!(caller_abi.layout.abi_eq(&callee_abi.layout));
391337
return true;
392338
} else {
393339
trace!(

compiler/rustc_passes/messages.ftl

+17-14
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
-passes_see_issue =
55
see issue #{$issue} <https://github.com/rust-lang/rust/issues/{$issue}> for more information
66
7-
passes_abi =
8-
abi: {$abi}
9-
7+
passes_abi_invalid_attribute =
8+
`#[rustc_abi]` can only be applied to function items, type aliases, and associated functions
9+
passes_abi_ne =
10+
ABIs are not compatible
11+
left ABI = {$left}
12+
right ABI = {$right}
1013
passes_abi_of =
11-
fn_abi_of_instance({$fn_name}) = {$fn_abi}
12-
13-
passes_align =
14-
align: {$align}
14+
fn_abi_of({$fn_name}) = {$fn_abi}
1515
1616
passes_allow_incoherent_impl =
1717
`rustc_allow_incoherent_impl` attribute should be applied to impl items.
@@ -318,9 +318,6 @@ passes_has_incoherent_inherent_impl =
318318
`rustc_has_incoherent_inherent_impls` attribute should be applied to types or traits.
319319
.label = only adts, extern types and traits are supported
320320
321-
passes_homogeneous_aggregate =
322-
homogeneous_aggregate: {$homogeneous_aggregate}
323-
324321
passes_ignored_attr =
325322
`#[{$sym}]` is ignored on struct fields and match arms
326323
.warn = {-passes_previously_accepted}
@@ -404,9 +401,18 @@ passes_lang_item_on_incorrect_target =
404401
405402
passes_layout =
406403
layout error: {$layout_error}
407-
404+
passes_layout_abi =
405+
abi: {$abi}
406+
passes_layout_align =
407+
align: {$align}
408+
passes_layout_homogeneous_aggregate =
409+
homogeneous_aggregate: {$homogeneous_aggregate}
410+
passes_layout_invalid_attribute =
411+
`#[rustc_layout]` can only be applied to `struct`/`enum`/`union` declarations and type aliases
408412
passes_layout_of =
409413
layout_of({$normalized_ty}) = {$ty_layout}
414+
passes_layout_size =
415+
size: {$size}
410416
411417
passes_link =
412418
attribute should be applied to an `extern` block with non-Rust ABI
@@ -662,9 +668,6 @@ passes_should_be_applied_to_trait =
662668
attribute should be applied to a trait
663669
.label = not a trait
664670
665-
passes_size =
666-
size: {$size}
667-
668671
passes_skipping_const_checks = skipping const checks
669672
670673
passes_stability_promotable =

0 commit comments

Comments
 (0)