Rollup merge of #60590 - petertodd:2018-test-union-nonzero, r=nikomatsakis,Centril

Test interaction of unions with non-zero/niche-filling optimization

Notably this nails down part of the behavior that MaybeUninit assumes, e.g. that a Option<MaybeUninit<&u8>> does not take advantage of non-zero optimization, and thus is a safe construct.

It also verifies the status quo: that even unions that could theoretically take advantage of niches don't. (relevant: https://github.com/rust-lang/rust/issues/36394)
This commit is contained in:
Mazdak Farrokhzad 2019-05-20 01:01:37 +02:00 committed by GitHub
commit f9d65c000d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 94 additions and 0 deletions

View File

@ -0,0 +1,51 @@
// run-pass
#![allow(dead_code)]
// Tests that unions aren't subject to unsafe non-zero/niche-filling optimizations.
//
// For example, if a union `U` can contain both a `&T` and a `*const T`, there's definitely no
// bit-value that an `Option<U>` could reuse as `None`; this test makes sure that isn't done.
//
// Secondly, this tests the status quo (not a guarantee; subject to change!) to not apply such
// optimizations to types containing unions even if they're theoretically possible. (discussion:
// https://github.com/rust-lang/rust/issues/36394)
//
// Notably this nails down part of the behavior that `MaybeUninit` assumes: that a
// `Option<MaybeUninit<&u8>>` does not take advantage of non-zero optimization, and thus is a safe
// construct.
use std::mem::{size_of, transmute};
union U1<A: Copy> {
a: A,
}
union U2<A: Copy, B: Copy> {
a: A,
b: B,
}
// Option<E> uses a value other than 0 and 1 as None
#[derive(Clone,Copy)]
enum E {
A = 0,
B = 1,
}
fn main() {
// Unions do not participate in niche-filling/non-zero optimization...
assert!(size_of::<Option<U2<&u8, u8>>>() > size_of::<U2<&u8, u8>>());
assert!(size_of::<Option<U2<&u8, ()>>>() > size_of::<U2<&u8, ()>>());
assert!(size_of::<Option<U2<u8, E>>>() > size_of::<U2<u8, E>>());
// ...even when theoretically possible:
assert!(size_of::<Option<U1<&u8>>>() > size_of::<U1<&u8>>());
assert!(size_of::<Option<U2<&u8, &u8>>>() > size_of::<U2<&u8, &u8>>());
// The unused bits of the () variant can have any value.
let zeroed: U2<&u8, ()> = unsafe { transmute(std::ptr::null::<u8>()) };
if let None = Some(zeroed) {
panic!()
}
}

View File

@ -57,6 +57,15 @@ pub enum Enum4<A, B, C, D> {
Four(D)
}
pub union Union1<A: Copy> {
a: A,
}
pub union Union2<A: Copy, B: Copy> {
a: A,
b: B,
}
#[start]
fn start(_: isize, _: *const *const u8) -> isize {
let _x: MyOption<NonZeroU32> = Default::default();
@ -69,5 +78,13 @@ fn start(_: isize, _: *const *const u8) -> isize {
let _e: Enum4<(), char, (), ()> = Enum4::One(());
let _f: Enum4<(), (), bool, ()> = Enum4::One(());
let _g: Enum4<(), (), (), MyOption<u8>> = Enum4::One(());
// Unions do not currently participate in niche filling.
let _h: MyOption<Union2<NonZeroU32, u32>> = Default::default();
// ...even when theoretically possible.
let _i: MyOption<Union1<NonZeroU32>> = Default::default();
let _j: MyOption<Union2<NonZeroU32, NonZeroU32>> = Default::default();
0
}

View File

@ -14,6 +14,21 @@ print-type-size field `.post`: 2 bytes
print-type-size field `.pre`: 1 bytes
print-type-size variant `None`: 0 bytes
print-type-size end padding: 1 bytes
print-type-size type: `MyOption<Union1<std::num::NonZeroU32>>`: 8 bytes, alignment: 4 bytes
print-type-size discriminant: 4 bytes
print-type-size variant `Some`: 4 bytes
print-type-size field `.0`: 4 bytes
print-type-size variant `None`: 0 bytes
print-type-size type: `MyOption<Union2<std::num::NonZeroU32, std::num::NonZeroU32>>`: 8 bytes, alignment: 4 bytes
print-type-size discriminant: 4 bytes
print-type-size variant `Some`: 4 bytes
print-type-size field `.0`: 4 bytes
print-type-size variant `None`: 0 bytes
print-type-size type: `MyOption<Union2<std::num::NonZeroU32, u32>>`: 8 bytes, alignment: 4 bytes
print-type-size discriminant: 4 bytes
print-type-size variant `Some`: 4 bytes
print-type-size field `.0`: 4 bytes
print-type-size variant `None`: 0 bytes
print-type-size type: `NestedNonZero`: 8 bytes, alignment: 4 bytes
print-type-size field `.val`: 4 bytes
print-type-size field `.post`: 2 bytes
@ -36,6 +51,17 @@ print-type-size type: `MyOption<std::num::NonZeroU32>`: 4 bytes, alignment: 4 by
print-type-size variant `Some`: 4 bytes
print-type-size field `.0`: 4 bytes
print-type-size variant `None`: 0 bytes
print-type-size type: `Union1<std::num::NonZeroU32>`: 4 bytes, alignment: 4 bytes
print-type-size variant `Union1`: 4 bytes
print-type-size field `.a`: 4 bytes
print-type-size type: `Union2<std::num::NonZeroU32, std::num::NonZeroU32>`: 4 bytes, alignment: 4 bytes
print-type-size variant `Union2`: 4 bytes
print-type-size field `.a`: 4 bytes
print-type-size field `.b`: 4 bytes, offset: 0 bytes, alignment: 4 bytes
print-type-size type: `Union2<std::num::NonZeroU32, u32>`: 4 bytes, alignment: 4 bytes
print-type-size variant `Union2`: 4 bytes
print-type-size field `.a`: 4 bytes
print-type-size field `.b`: 4 bytes, offset: 0 bytes, alignment: 4 bytes
print-type-size type: `std::num::NonZeroU32`: 4 bytes, alignment: 4 bytes
print-type-size field `.0`: 4 bytes
print-type-size type: `Enum4<(), (), (), MyOption<u8>>`: 2 bytes, alignment: 1 bytes