Skip to content

Commit 436ac89

Browse files
committed
Auto merge of rust-lang#46123 - Gankro:c-repr, r=eddyb
Implement the special repr(C)-non-clike-enum layout This is the second half of rust-lang/rfcs#2195 which specifies that ```rust #[repr(C, u8)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] enum MyEnum { A(u32), // Single primitive value B { x: u8, y: i16 }, // Composite, and the offset of `y` depends on tag being internal C, // Empty D(Option<u32>), // Contains an enum E(Duration), // Contains a struct } ``` Has the same layout as ```rust #[repr(C)] struct MyEnumRepr { tag: MyEnumTag, payload: MyEnumPayload, } #[repr(C)] #[allow(non_snake_case)] union MyEnumPayload { A: MyEnumVariantA, B: MyEnumVariantB, D: MyEnumVariantD, E: MyEnumVariantE, } #[repr(u8)] #[derive(Copy, Clone)] enum MyEnumTag { A, B, C, D, E } #[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantA(u32); #[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantB {x: u8, y: i16 } #[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantD(Option<u32>); #[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantE(Duration); ```
2 parents 7745a7a + 0e63d27 commit 436ac89

File tree

5 files changed

+454
-24
lines changed

5 files changed

+454
-24
lines changed

src/librustc/hir/check_attr.rs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ impl<'a> CheckAttrVisitor<'a> {
7575
}
7676
};
7777

78-
let mut conflicting_reprs = 0;
78+
let mut int_reprs = 0;
79+
let mut is_c = false;
80+
let mut is_simd = false;
7981

8082
for word in words {
8183

@@ -86,7 +88,7 @@ impl<'a> CheckAttrVisitor<'a> {
8688

8789
let (message, label) = match &*name.as_str() {
8890
"C" => {
89-
conflicting_reprs += 1;
91+
is_c = true;
9092
if target != Target::Struct &&
9193
target != Target::Union &&
9294
target != Target::Enum {
@@ -108,7 +110,7 @@ impl<'a> CheckAttrVisitor<'a> {
108110
}
109111
}
110112
"simd" => {
111-
conflicting_reprs += 1;
113+
is_simd = true;
112114
if target != Target::Struct {
113115
("attribute should be applied to struct",
114116
"a struct")
@@ -128,7 +130,7 @@ impl<'a> CheckAttrVisitor<'a> {
128130
"i8" | "u8" | "i16" | "u16" |
129131
"i32" | "u32" | "i64" | "u64" |
130132
"isize" | "usize" => {
131-
conflicting_reprs += 1;
133+
int_reprs += 1;
132134
if target != Target::Enum {
133135
("attribute should be applied to enum",
134136
"an enum")
@@ -142,7 +144,11 @@ impl<'a> CheckAttrVisitor<'a> {
142144
.span_label(item.span, format!("not {}", label))
143145
.emit();
144146
}
145-
if conflicting_reprs > 1 {
147+
148+
// Warn on repr(u8, u16), repr(C, simd), and c-like-enum-repr(C, u8)
149+
if (int_reprs > 1)
150+
|| (is_simd && is_c)
151+
|| (int_reprs == 1 && is_c && is_c_like_enum(item)) {
146152
span_warn!(self.sess, attr.span, E0566,
147153
"conflicting representation hints");
148154
}
@@ -162,3 +168,17 @@ impl<'a> Visitor<'a> for CheckAttrVisitor<'a> {
162168
pub fn check_crate(sess: &Session, krate: &ast::Crate) {
163169
visit::walk_crate(&mut CheckAttrVisitor { sess: sess }, krate);
164170
}
171+
172+
fn is_c_like_enum(item: &ast::Item) -> bool {
173+
if let ast::ItemKind::Enum(ref def, _) = item.node {
174+
for variant in &def.variants {
175+
match variant.node.data {
176+
ast::VariantData::Unit(_) => { /* continue */ }
177+
_ => { return false; }
178+
}
179+
}
180+
true
181+
} else {
182+
false
183+
}
184+
}

src/librustc/ty/layout.rs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -942,8 +942,8 @@ impl<'a, 'tcx> LayoutDetails {
942942
AlwaysSized,
943943
/// A univariant, the last field of which may be coerced to unsized.
944944
MaybeUnsized,
945-
/// A univariant, but part of an enum.
946-
EnumVariant(Integer),
945+
/// A univariant, but with a prefix of an arbitrary size & alignment (e.g. enum tag).
946+
Prefixed(Size, Align),
947947
}
948948
let univariant_uninterned = |fields: &[TyLayout], repr: &ReprOptions, kind| {
949949
let packed = repr.packed();
@@ -962,14 +962,11 @@ impl<'a, 'tcx> LayoutDetails {
962962
let mut inverse_memory_index: Vec<u32> = (0..fields.len() as u32).collect();
963963

964964
// Anything with repr(C) or repr(packed) doesn't optimize.
965-
let optimize = match kind {
966-
StructKind::AlwaysSized |
967-
StructKind::MaybeUnsized |
968-
StructKind::EnumVariant(I8) => {
969-
(repr.flags & ReprFlags::IS_UNOPTIMISABLE).is_empty()
970-
}
971-
StructKind::EnumVariant(_) => false
972-
};
965+
let mut optimize = (repr.flags & ReprFlags::IS_UNOPTIMISABLE).is_empty();
966+
if let StructKind::Prefixed(_, align) = kind {
967+
optimize &= align.abi() == 1;
968+
}
969+
973970
if optimize {
974971
let end = if let StructKind::MaybeUnsized = kind {
975972
fields.len() - 1
@@ -987,7 +984,7 @@ impl<'a, 'tcx> LayoutDetails {
987984
(!f.is_zst(), cmp::Reverse(f.align.abi()))
988985
})
989986
}
990-
StructKind::EnumVariant(_) => {
987+
StructKind::Prefixed(..) => {
991988
optimizing.sort_by_key(|&x| fields[x as usize].align.abi());
992989
}
993990
}
@@ -1001,12 +998,11 @@ impl<'a, 'tcx> LayoutDetails {
1001998

1002999
let mut offset = Size::from_bytes(0);
10031000

1004-
if let StructKind::EnumVariant(discr) = kind {
1005-
offset = discr.size();
1001+
if let StructKind::Prefixed(prefix_size, prefix_align) = kind {
10061002
if !packed {
1007-
let discr_align = discr.align(dl);
1008-
align = align.max(discr_align);
1003+
align = align.max(prefix_align);
10091004
}
1005+
offset = prefix_size.abi_align(prefix_align);
10101006
}
10111007

10121008
for &i in &inverse_memory_index {
@@ -1558,10 +1554,24 @@ impl<'a, 'tcx> LayoutDetails {
15581554
let mut start_align = Align::from_bytes(256, 256).unwrap();
15591555
assert_eq!(Integer::for_abi_align(dl, start_align), None);
15601556

1557+
// repr(C) on an enum tells us to make a (tag, union) layout,
1558+
// so we need to grow the prefix alignment to be at least
1559+
// the alignment of the union. (This value is used both for
1560+
// determining the alignment of the overall enum, and the
1561+
// determining the alignment of the payload after the tag.)
1562+
let mut prefix_align = min_ity.align(dl);
1563+
if def.repr.c() {
1564+
for fields in &variants {
1565+
for field in fields {
1566+
prefix_align = prefix_align.max(field.align);
1567+
}
1568+
}
1569+
}
1570+
15611571
// Create the set of structs that represent each variant.
15621572
let mut variants = variants.into_iter().enumerate().map(|(i, field_layouts)| {
15631573
let mut st = univariant_uninterned(&field_layouts,
1564-
&def.repr, StructKind::EnumVariant(min_ity))?;
1574+
&def.repr, StructKind::Prefixed(min_ity.size(), prefix_align))?;
15651575
st.variants = Variants::Single { index: i };
15661576
// Find the first field we can't move later
15671577
// to make room for a larger discriminant.
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// This test deserializes an enum in-place by transmuting to a union that
12+
// should have the same layout, and manipulating the tag and payloads
13+
// independently. This verifies that `repr(some_int)` has a stable representation,
14+
// and that we don't miscompile these kinds of manipulations.
15+
16+
use std::time::Duration;
17+
use std::mem;
18+
19+
#[repr(C, u8)]
20+
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
21+
enum MyEnum {
22+
A(u32), // Single primitive value
23+
B { x: u8, y: i16 }, // Composite, and the offset of `y` depends on tag being internal
24+
C, // Empty
25+
D(Option<u32>), // Contains an enum
26+
E(Duration), // Contains a struct
27+
}
28+
29+
#[repr(C)]
30+
struct MyEnumRepr {
31+
tag: MyEnumTag,
32+
payload: MyEnumPayload,
33+
}
34+
35+
#[repr(C)]
36+
#[allow(non_snake_case)]
37+
union MyEnumPayload {
38+
A: MyEnumVariantA,
39+
B: MyEnumVariantB,
40+
D: MyEnumVariantD,
41+
E: MyEnumVariantE,
42+
}
43+
44+
#[repr(u8)] #[derive(Copy, Clone)] enum MyEnumTag { A, B, C, D, E }
45+
#[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantA(u32);
46+
#[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantB {x: u8, y: i16 }
47+
#[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantD(Option<u32>);
48+
#[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantE(Duration);
49+
50+
fn main() {
51+
let result: Vec<Result<MyEnum, ()>> = vec![
52+
Ok(MyEnum::A(17)),
53+
Ok(MyEnum::B { x: 206, y: 1145 }),
54+
Ok(MyEnum::C),
55+
Err(()),
56+
Ok(MyEnum::D(Some(407))),
57+
Ok(MyEnum::D(None)),
58+
Ok(MyEnum::E(Duration::from_secs(100))),
59+
Err(()),
60+
];
61+
62+
// Binary serialized version of the above (little-endian)
63+
let input: Vec<u8> = vec![
64+
0, 17, 0, 0, 0,
65+
1, 206, 121, 4,
66+
2,
67+
8, /* invalid tag value */
68+
3, 0, 151, 1, 0, 0,
69+
3, 1,
70+
4, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
71+
0, /* incomplete value */
72+
];
73+
74+
let mut output = vec![];
75+
let mut buf = &input[..];
76+
77+
unsafe {
78+
// This should be safe, because we don't match on it unless it's fully formed,
79+
// and it doesn't have a destructor.
80+
let mut dest: MyEnum = mem::uninitialized();
81+
while buf.len() > 0 {
82+
match parse_my_enum(&mut dest, &mut buf) {
83+
Ok(()) => output.push(Ok(dest)),
84+
Err(()) => output.push(Err(())),
85+
}
86+
}
87+
}
88+
89+
assert_eq!(output, result);
90+
}
91+
92+
fn parse_my_enum<'a>(dest: &'a mut MyEnum, buf: &mut &[u8]) -> Result<(), ()> {
93+
unsafe {
94+
// Should be correct to do this transmute.
95+
let dest: &'a mut MyEnumRepr = mem::transmute(dest);
96+
let tag = read_u8(buf)?;
97+
98+
dest.tag = match tag {
99+
0 => MyEnumTag::A,
100+
1 => MyEnumTag::B,
101+
2 => MyEnumTag::C,
102+
3 => MyEnumTag::D,
103+
4 => MyEnumTag::E,
104+
_ => return Err(()),
105+
};
106+
107+
match dest.tag {
108+
MyEnumTag::A => {
109+
dest.payload.A.0 = read_u32_le(buf)?;
110+
}
111+
MyEnumTag::B => {
112+
dest.payload.B.x = read_u8(buf)?;
113+
dest.payload.B.y = read_u16_le(buf)? as i16;
114+
}
115+
MyEnumTag::C => {
116+
/* do nothing */
117+
}
118+
MyEnumTag::D => {
119+
let is_some = read_u8(buf)? == 0;
120+
if is_some {
121+
dest.payload.D.0 = Some(read_u32_le(buf)?);
122+
} else {
123+
dest.payload.D.0 = None;
124+
}
125+
}
126+
MyEnumTag::E => {
127+
let secs = read_u64_le(buf)?;
128+
let nanos = read_u32_le(buf)?;
129+
dest.payload.E.0 = Duration::new(secs, nanos);
130+
}
131+
}
132+
Ok(())
133+
}
134+
}
135+
136+
137+
138+
// reader helpers
139+
140+
fn read_u64_le(buf: &mut &[u8]) -> Result<u64, ()> {
141+
if buf.len() < 8 { return Err(()) }
142+
let val = (buf[0] as u64) << 0
143+
| (buf[1] as u64) << 8
144+
| (buf[2] as u64) << 16
145+
| (buf[3] as u64) << 24
146+
| (buf[4] as u64) << 32
147+
| (buf[5] as u64) << 40
148+
| (buf[6] as u64) << 48
149+
| (buf[7] as u64) << 56;
150+
*buf = &buf[8..];
151+
Ok(val)
152+
}
153+
154+
fn read_u32_le(buf: &mut &[u8]) -> Result<u32, ()> {
155+
if buf.len() < 4 { return Err(()) }
156+
let val = (buf[0] as u32) << 0
157+
| (buf[1] as u32) << 8
158+
| (buf[2] as u32) << 16
159+
| (buf[3] as u32) << 24;
160+
*buf = &buf[4..];
161+
Ok(val)
162+
}
163+
164+
fn read_u16_le(buf: &mut &[u8]) -> Result<u16, ()> {
165+
if buf.len() < 2 { return Err(()) }
166+
let val = (buf[0] as u16) << 0
167+
| (buf[1] as u16) << 8;
168+
*buf = &buf[2..];
169+
Ok(val)
170+
}
171+
172+
fn read_u8(buf: &mut &[u8]) -> Result<u8, ()> {
173+
if buf.len() < 1 { return Err(()) }
174+
let val = buf[0];
175+
*buf = &buf[1..];
176+
Ok(val)
177+
}

0 commit comments

Comments
 (0)