Skip to content

Commit ce95a44

Browse files
committed
improve TagEncoding::Niche docs and sanity check
1 parent 76f3ff6 commit ce95a44

File tree

4 files changed

+74
-8
lines changed

4 files changed

+74
-8
lines changed

compiler/rustc_abi/src/lib.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,6 +1215,15 @@ impl Scalar {
12151215
Scalar::Union { .. } => true,
12161216
}
12171217
}
1218+
1219+
/// Returns `true` if this is a signed integer scalar
1220+
#[inline]
1221+
pub fn is_signed(&self) -> bool {
1222+
match self.primitive() {
1223+
Primitive::Int(_, signed) => signed,
1224+
_ => false,
1225+
}
1226+
}
12181227
}
12191228

12201229
// NOTE: This struct is generic over the FieldIdx for rust-analyzer usage.
@@ -1401,10 +1410,7 @@ impl BackendRepr {
14011410
#[inline]
14021411
pub fn is_signed(&self) -> bool {
14031412
match self {
1404-
BackendRepr::Scalar(scal) => match scal.primitive() {
1405-
Primitive::Int(_, signed) => signed,
1406-
_ => false,
1407-
},
1413+
BackendRepr::Scalar(scal) => scal.is_signed(),
14081414
_ => panic!("`is_signed` on non-scalar ABI {self:?}"),
14091415
}
14101416
}
@@ -1528,14 +1534,22 @@ pub enum TagEncoding<VariantIdx: Idx> {
15281534
/// The variant `untagged_variant` contains a niche at an arbitrary
15291535
/// offset (field `tag_field` of the enum), which for a variant with
15301536
/// discriminant `d` is set to
1531-
/// `(d - niche_variants.start).wrapping_add(niche_start)`.
1537+
/// `(d - niche_variants.start).wrapping_add(niche_start)`
1538+
/// (this is wrapping arithmetic using the type of the niche field).
15321539
///
15331540
/// For example, `Option<(usize, &T)>` is represented such that
15341541
/// `None` has a null pointer for the second tuple field, and
15351542
/// `Some` is the identity function (with a non-null reference).
1543+
///
1544+
/// Other variants that are not `untagged_variant` and that are outside the `niche_variants`
1545+
/// range cannot be represented; they must be uninhabited.
15361546
Niche {
15371547
untagged_variant: VariantIdx,
1548+
/// This range *may* contain `untagged_variant`; that is then just a "dead value" and
1549+
/// not used to encode anything.
15381550
niche_variants: RangeInclusive<VariantIdx>,
1551+
/// This is inbounds of the type of the niche field
1552+
/// (not sign-extended, i.e., all bits beyond the niche field size are 0).
15391553
niche_start: u128,
15401554
},
15411555
}

compiler/rustc_ty_utils/src/layout/invariant.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
use std::assert_matches::assert_matches;
22

3-
use rustc_abi::{BackendRepr, FieldsShape, Scalar, Size, Variants};
3+
use rustc_abi::{BackendRepr, FieldsShape, Scalar, Size, TagEncoding, Variants};
44
use rustc_middle::bug;
55
use rustc_middle::ty::layout::{HasTyCtxt, LayoutCx, TyAndLayout};
66

77
/// Enforce some basic invariants on layouts.
8-
pub(super) fn partially_check_layout<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLayout<'tcx>) {
8+
pub(super) fn layout_sanity_check<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLayout<'tcx>) {
99
let tcx = cx.tcx();
1010

1111
// Type-level uninhabitedness should always imply ABI uninhabitedness.
@@ -241,7 +241,17 @@ pub(super) fn partially_check_layout<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLa
241241

242242
check_layout_abi(cx, layout);
243243

244-
if let Variants::Multiple { variants, .. } = &layout.variants {
244+
if let Variants::Multiple { variants, tag, tag_encoding, .. } = &layout.variants {
245+
if let TagEncoding::Niche { niche_start, untagged_variant, niche_variants } = tag_encoding {
246+
let niche_size = tag.size(cx);
247+
assert!(*niche_start <= niche_size.unsigned_int_max());
248+
for (idx, variant) in variants.iter_enumerated() {
249+
// Ensure all inhabited variants are accounted for.
250+
if !variant.is_uninhabited() {
251+
assert!(idx == *untagged_variant || niche_variants.contains(&idx));
252+
}
253+
}
254+
}
245255
for variant in variants.iter() {
246256
// No nested "multiple".
247257
assert_matches!(variant.variants, Variants::Single { .. });
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Validity makes this fail at the wrong place.
2+
//@compile-flags: -Zmiri-disable-validation
3+
use std::mem;
4+
5+
// This enum has untagged variant idx 1, with niche_variants being 0..=2
6+
// and niche_start being 2.
7+
// That means the untagged variants is in the niche variant range!
8+
// However, using the corresponding value (2+1 = 3) is not a valid encoding of this variant.
9+
#[derive(Copy, Clone, PartialEq)]
10+
enum Foo {
11+
Var1,
12+
Var2(bool),
13+
Var3,
14+
}
15+
16+
fn main() {
17+
unsafe {
18+
assert!(Foo::Var2(false) == mem::transmute(0u8));
19+
assert!(Foo::Var2(true) == mem::transmute(1u8));
20+
assert!(Foo::Var1 == mem::transmute(2u8));
21+
assert!(Foo::Var3 == mem::transmute(4u8));
22+
23+
let invalid: Foo = mem::transmute(3u8);
24+
assert!(matches!(invalid, Foo::Var2(_)));
25+
//~^ ERROR: invalid tag
26+
}
27+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: Undefined Behavior: enum value has invalid tag: 0x03
2+
--> tests/fail/enum-untagged-variant-invalid-encoding.rs:LL:CC
3+
|
4+
LL | assert!(matches!(invalid, Foo::Var2(_)));
5+
| ^^^^^^^ enum value has invalid tag: 0x03
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `main` at tests/fail/enum-untagged-variant-invalid-encoding.rs:LL:CC
11+
12+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
13+
14+
error: aborting due to 1 previous error
15+

0 commit comments

Comments
 (0)