Skip to content

Commit f9d65c0

Browse files
authored
Rollup merge of rust-lang#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: rust-lang#36394)
2 parents 6afcb56 + a91ad60 commit f9d65c0

File tree

3 files changed

+94
-0
lines changed

3 files changed

+94
-0
lines changed
+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// run-pass
2+
#![allow(dead_code)]
3+
4+
// Tests that unions aren't subject to unsafe non-zero/niche-filling optimizations.
5+
//
6+
// For example, if a union `U` can contain both a `&T` and a `*const T`, there's definitely no
7+
// bit-value that an `Option<U>` could reuse as `None`; this test makes sure that isn't done.
8+
//
9+
// Secondly, this tests the status quo (not a guarantee; subject to change!) to not apply such
10+
// optimizations to types containing unions even if they're theoretically possible. (discussion:
11+
// https://github.com/rust-lang/rust/issues/36394)
12+
//
13+
// Notably this nails down part of the behavior that `MaybeUninit` assumes: that a
14+
// `Option<MaybeUninit<&u8>>` does not take advantage of non-zero optimization, and thus is a safe
15+
// construct.
16+
17+
use std::mem::{size_of, transmute};
18+
19+
union U1<A: Copy> {
20+
a: A,
21+
}
22+
23+
union U2<A: Copy, B: Copy> {
24+
a: A,
25+
b: B,
26+
}
27+
28+
// Option<E> uses a value other than 0 and 1 as None
29+
#[derive(Clone,Copy)]
30+
enum E {
31+
A = 0,
32+
B = 1,
33+
}
34+
35+
fn main() {
36+
// Unions do not participate in niche-filling/non-zero optimization...
37+
assert!(size_of::<Option<U2<&u8, u8>>>() > size_of::<U2<&u8, u8>>());
38+
assert!(size_of::<Option<U2<&u8, ()>>>() > size_of::<U2<&u8, ()>>());
39+
assert!(size_of::<Option<U2<u8, E>>>() > size_of::<U2<u8, E>>());
40+
41+
// ...even when theoretically possible:
42+
assert!(size_of::<Option<U1<&u8>>>() > size_of::<U1<&u8>>());
43+
assert!(size_of::<Option<U2<&u8, &u8>>>() > size_of::<U2<&u8, &u8>>());
44+
45+
// The unused bits of the () variant can have any value.
46+
let zeroed: U2<&u8, ()> = unsafe { transmute(std::ptr::null::<u8>()) };
47+
48+
if let None = Some(zeroed) {
49+
panic!()
50+
}
51+
}

src/test/ui/print_type_sizes/niche-filling.rs

+17
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ pub enum Enum4<A, B, C, D> {
5757
Four(D)
5858
}
5959

60+
pub union Union1<A: Copy> {
61+
a: A,
62+
}
63+
64+
pub union Union2<A: Copy, B: Copy> {
65+
a: A,
66+
b: B,
67+
}
68+
6069
#[start]
6170
fn start(_: isize, _: *const *const u8) -> isize {
6271
let _x: MyOption<NonZeroU32> = Default::default();
@@ -69,5 +78,13 @@ fn start(_: isize, _: *const *const u8) -> isize {
6978
let _e: Enum4<(), char, (), ()> = Enum4::One(());
7079
let _f: Enum4<(), (), bool, ()> = Enum4::One(());
7180
let _g: Enum4<(), (), (), MyOption<u8>> = Enum4::One(());
81+
82+
// Unions do not currently participate in niche filling.
83+
let _h: MyOption<Union2<NonZeroU32, u32>> = Default::default();
84+
85+
// ...even when theoretically possible.
86+
let _i: MyOption<Union1<NonZeroU32>> = Default::default();
87+
let _j: MyOption<Union2<NonZeroU32, NonZeroU32>> = Default::default();
88+
7289
0
7390
}

src/test/ui/print_type_sizes/niche-filling.stdout

+26
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@ print-type-size field `.post`: 2 bytes
1414
print-type-size field `.pre`: 1 bytes
1515
print-type-size variant `None`: 0 bytes
1616
print-type-size end padding: 1 bytes
17+
print-type-size type: `MyOption<Union1<std::num::NonZeroU32>>`: 8 bytes, alignment: 4 bytes
18+
print-type-size discriminant: 4 bytes
19+
print-type-size variant `Some`: 4 bytes
20+
print-type-size field `.0`: 4 bytes
21+
print-type-size variant `None`: 0 bytes
22+
print-type-size type: `MyOption<Union2<std::num::NonZeroU32, std::num::NonZeroU32>>`: 8 bytes, alignment: 4 bytes
23+
print-type-size discriminant: 4 bytes
24+
print-type-size variant `Some`: 4 bytes
25+
print-type-size field `.0`: 4 bytes
26+
print-type-size variant `None`: 0 bytes
27+
print-type-size type: `MyOption<Union2<std::num::NonZeroU32, u32>>`: 8 bytes, alignment: 4 bytes
28+
print-type-size discriminant: 4 bytes
29+
print-type-size variant `Some`: 4 bytes
30+
print-type-size field `.0`: 4 bytes
31+
print-type-size variant `None`: 0 bytes
1732
print-type-size type: `NestedNonZero`: 8 bytes, alignment: 4 bytes
1833
print-type-size field `.val`: 4 bytes
1934
print-type-size field `.post`: 2 bytes
@@ -36,6 +51,17 @@ print-type-size type: `MyOption<std::num::NonZeroU32>`: 4 bytes, alignment: 4 by
3651
print-type-size variant `Some`: 4 bytes
3752
print-type-size field `.0`: 4 bytes
3853
print-type-size variant `None`: 0 bytes
54+
print-type-size type: `Union1<std::num::NonZeroU32>`: 4 bytes, alignment: 4 bytes
55+
print-type-size variant `Union1`: 4 bytes
56+
print-type-size field `.a`: 4 bytes
57+
print-type-size type: `Union2<std::num::NonZeroU32, std::num::NonZeroU32>`: 4 bytes, alignment: 4 bytes
58+
print-type-size variant `Union2`: 4 bytes
59+
print-type-size field `.a`: 4 bytes
60+
print-type-size field `.b`: 4 bytes, offset: 0 bytes, alignment: 4 bytes
61+
print-type-size type: `Union2<std::num::NonZeroU32, u32>`: 4 bytes, alignment: 4 bytes
62+
print-type-size variant `Union2`: 4 bytes
63+
print-type-size field `.a`: 4 bytes
64+
print-type-size field `.b`: 4 bytes, offset: 0 bytes, alignment: 4 bytes
3965
print-type-size type: `std::num::NonZeroU32`: 4 bytes, alignment: 4 bytes
4066
print-type-size field `.0`: 4 bytes
4167
print-type-size type: `Enum4<(), (), (), MyOption<u8>>`: 2 bytes, alignment: 1 bytes

0 commit comments

Comments
 (0)