Skip to content

Commit 19c8fc9

Browse files
committed
Add support for data-carrying enums
1 parent fb9d62b commit 19c8fc9

File tree

14 files changed

+2169
-445
lines changed

14 files changed

+2169
-445
lines changed

src/pointer/ptr.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -866,10 +866,12 @@ mod _transitions {
866866
}
867867

868868
/// Recalls that `self`'s referent is bit-valid for `T`.
869+
#[doc(hidden)]
870+
#[must_use]
869871
#[inline]
870872
// TODO(#859): Reconsider the name of this method before making it
871873
// public.
872-
pub(crate) fn bikeshed_recall_valid(self) -> Ptr<'a, T, (I::Aliasing, I::Alignment, Valid)>
874+
pub fn bikeshed_recall_valid(self) -> Ptr<'a, T, (I::Aliasing, I::Alignment, Valid)>
873875
where
874876
T: crate::FromBytes,
875877
I: Invariants<Validity = Initialized>,

src/util/macro_util.rs

Lines changed: 164 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,32 @@ macro_rules! align_of {
259259
}};
260260
}
261261

262+
mod size_to_tag {
263+
pub trait SizeToTag<const SIZE: usize> {
264+
type Tag;
265+
}
266+
267+
impl SizeToTag<1> for () {
268+
type Tag = u8;
269+
}
270+
impl SizeToTag<2> for () {
271+
type Tag = u16;
272+
}
273+
impl SizeToTag<4> for () {
274+
type Tag = u32;
275+
}
276+
impl SizeToTag<8> for () {
277+
type Tag = u64;
278+
}
279+
impl SizeToTag<16> for () {
280+
type Tag = u128;
281+
}
282+
}
283+
284+
/// An alias for the unsigned integer of the given size in bytes.
285+
#[doc(hidden)]
286+
pub type SizeToTag<const SIZE: usize> = <() as size_to_tag::SizeToTag<SIZE>>::Tag;
287+
262288
/// Does the struct type `$t` have padding?
263289
///
264290
/// `$ts` is the list of the type of every field in `$t`. `$t` must be a
@@ -274,7 +300,7 @@ macro_rules! align_of {
274300
#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
275301
#[macro_export]
276302
macro_rules! struct_has_padding {
277-
($t:ty, $($ts:ty),*) => {
303+
($t:ty, [$($ts:ty),*]) => {
278304
::zerocopy::util::macro_util::core_reexport::mem::size_of::<$t>() > 0 $(+ ::zerocopy::util::macro_util::core_reexport::mem::size_of::<$ts>())*
279305
};
280306
}
@@ -294,11 +320,41 @@ macro_rules! struct_has_padding {
294320
#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
295321
#[macro_export]
296322
macro_rules! union_has_padding {
297-
($t:ty, $($ts:ty),*) => {
323+
($t:ty, [$($ts:ty),*]) => {
298324
false $(|| ::zerocopy::util::macro_util::core_reexport::mem::size_of::<$t>() != ::zerocopy::util::macro_util::core_reexport::mem::size_of::<$ts>())*
299325
};
300326
}
301327

328+
/// Does the enum type `$t` have padding?
329+
///
330+
/// `$disc` is the type of the enum tag, and `$ts` is a list of fields in each
331+
/// square-bracket-delimited variant. `$t` must be an enum, or else
332+
/// `enum_has_padding!`'s result may be meaningless. An enum has padding if any
333+
/// of its variant structs [1][2] contain padding, and so all of the variants of
334+
/// an enum must be "full" in order for the enum to not have padding.
335+
///
336+
/// The results of `enum_has_padding!` require that the enum is not
337+
/// `repr(Rust)`, as `repr(Rust)` enums may niche the enum's tag and reduce the
338+
/// total number of bytes required to represent the enum as a result. As long as
339+
/// the enum is `repr(C)`, `repr(int)`, or `repr(C, int)`, this will
340+
/// consistently return whether the enum contains any padding bytes.
341+
///
342+
/// [1]: https://doc.rust-lang.org/1.81.0/reference/type-layout.html#reprc-enums-with-fields
343+
/// [2]: https://doc.rust-lang.org/1.81.0/reference/type-layout.html#primitive-representation-of-enums-with-fields
344+
#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
345+
#[macro_export]
346+
macro_rules! enum_has_padding {
347+
($t:ty, $disc:ty, $([$($ts:ty),*]),*) => {
348+
false $(
349+
|| ::zerocopy::util::macro_util::core_reexport::mem::size_of::<$t>()
350+
!= (
351+
::zerocopy::util::macro_util::core_reexport::mem::size_of::<$disc>()
352+
$(+ ::zerocopy::util::macro_util::core_reexport::mem::size_of::<$ts>())*
353+
)
354+
)*
355+
}
356+
}
357+
302358
/// Does `t` have alignment greater than or equal to `u`? If not, this macro
303359
/// produces a compile error. It must be invoked in a dead codepath. This is
304360
/// used in `transmute_ref!` and `transmute_mut!`.
@@ -810,6 +866,36 @@ mod tests {
810866
*/
811867
}
812868

869+
#[test]
870+
fn test_enum_casts() {
871+
// Test that casting the variants of enums with signed integer reprs to
872+
// unsigned integers obeys expected signed -> unsigned casting rules.
873+
874+
#[repr(i8)]
875+
enum ReprI8 {
876+
MinusOne = -1,
877+
Zero = 0,
878+
Min = i8::MIN,
879+
Max = i8::MAX,
880+
}
881+
882+
#[allow(clippy::as_conversions)]
883+
let x = ReprI8::MinusOne as u8;
884+
assert_eq!(x, u8::MAX);
885+
886+
#[allow(clippy::as_conversions)]
887+
let x = ReprI8::Zero as u8;
888+
assert_eq!(x, 0);
889+
890+
#[allow(clippy::as_conversions)]
891+
let x = ReprI8::Min as u8;
892+
assert_eq!(x, 128);
893+
894+
#[allow(clippy::as_conversions)]
895+
let x = ReprI8::Max as u8;
896+
assert_eq!(x, 127);
897+
}
898+
813899
#[test]
814900
fn test_struct_has_padding() {
815901
// Test that, for each provided repr, `struct_has_padding!` reports the
@@ -818,7 +904,7 @@ mod tests {
818904
(#[$cfg:meta] ($($ts:ty),*) => $expect:expr) => {{
819905
#[$cfg]
820906
struct Test($(#[allow(dead_code)] $ts),*);
821-
assert_eq!(struct_has_padding!(Test, $($ts),*), $expect);
907+
assert_eq!(struct_has_padding!(Test, [$($ts),*]), $expect);
822908
}};
823909
(#[$cfg:meta] $(#[$cfgs:meta])* ($($ts:ty),*) => $expect:expr) => {
824910
test!(#[$cfg] ($($ts),*) => $expect);
@@ -849,7 +935,7 @@ mod tests {
849935
#[$cfg]
850936
#[allow(unused)] // fields are never read
851937
union Test{ $($fs: $ts),* }
852-
assert_eq!(union_has_padding!(Test, $($ts),*), $expect);
938+
assert_eq!(union_has_padding!(Test, [$($ts),*]), $expect);
853939
}};
854940
(#[$cfg:meta] $(#[$cfgs:meta])* {$($fs:ident: $ts:ty),*} => $expect:expr) => {
855941
test!(#[$cfg] {$($fs: $ts),*} => $expect);
@@ -867,4 +953,78 @@ mod tests {
867953
// anyway.
868954
test!(#[repr(C)] #[repr(packed)] {a: u8, b: u64} => true);
869955
}
956+
957+
#[test]
958+
fn test_enum_has_padding() {
959+
// Test that, for each provided repr, `enum_has_padding!` reports the
960+
// expected value.
961+
macro_rules! test {
962+
(#[repr($disc:ident $(, $c:ident)?)] { $($vs:ident ($($ts:ty),*),)* } => $expect:expr) => {
963+
test!(@case #[repr($disc $(, $c)?)] { $($vs ($($ts),*),)* } => $expect);
964+
};
965+
(#[repr($disc:ident $(, $c:ident)?)] #[$cfg:meta] $(#[$cfgs:meta])* { $($vs:ident ($($ts:ty),*),)* } => $expect:expr) => {
966+
test!(@case #[repr($disc $(, $c)?)] #[$cfg] { $($vs ($($ts),*),)* } => $expect);
967+
test!(#[repr($disc $(, $c)?)] $(#[$cfgs])* { $($vs ($($ts),*),)* } => $expect);
968+
};
969+
(@case #[repr($disc:ident $(, $c:ident)?)] $(#[$cfg:meta])? { $($vs:ident ($($ts:ty),*),)* } => $expect:expr) => {{
970+
#[repr($disc $(, $c)?)]
971+
$(#[$cfg])?
972+
#[allow(unused)] // variants and fields are never used
973+
enum Test {
974+
$($vs ($($ts),*),)*
975+
}
976+
assert_eq!(
977+
enum_has_padding!(Test, $disc, $([$($ts),*]),*),
978+
$expect
979+
);
980+
}};
981+
}
982+
983+
#[allow(unused)]
984+
#[repr(align(2))]
985+
struct U16(u16);
986+
987+
#[allow(unused)]
988+
#[repr(align(4))]
989+
struct U32(u32);
990+
991+
test!(#[repr(u8)] #[repr(C)] {
992+
A(u8),
993+
} => false);
994+
test!(#[repr(u16)] #[repr(C)] {
995+
A(u8, u8),
996+
B(U16),
997+
} => false);
998+
test!(#[repr(u32)] #[repr(C)] {
999+
A(u8, u8, u8, u8),
1000+
B(U16, u8, u8),
1001+
C(u8, u8, U16),
1002+
D(U16, U16),
1003+
E(U32),
1004+
} => false);
1005+
1006+
// `repr(int)` can pack the discriminant more efficiently
1007+
test!(#[repr(u8)] {
1008+
A(u8, U16),
1009+
} => false);
1010+
test!(#[repr(u8)] {
1011+
A(u8, U16, U32),
1012+
} => false);
1013+
1014+
// `repr(C)` cannot
1015+
test!(#[repr(u8, C)] {
1016+
A(u8, U16),
1017+
} => true);
1018+
test!(#[repr(u8, C)] {
1019+
A(u8, u8, u8, U32),
1020+
} => true);
1021+
1022+
// And field ordering can always cause problems
1023+
test!(#[repr(u8)] #[repr(C)] {
1024+
A(U16, u8),
1025+
} => true);
1026+
test!(#[repr(u8)] #[repr(C)] {
1027+
A(U32, u8, u8, u8),
1028+
} => true);
1029+
}
8701030
}

0 commit comments

Comments
 (0)