Skip to content

Commit

Permalink
Add transmute_container! macro
Browse files Browse the repository at this point in the history
  • Loading branch information
joshlf committed Sep 23, 2024
1 parent 73b15e5 commit f555762
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ derive = ["zerocopy-derive"]
simd = []
simd-nightly = ["simd"]
std = ["alloc"]
unstable-transmute-vec = ["alloc"]
# This feature depends on all other features that work on the stable compiler.
# We make no stability guarantees about this feature; it may be modified or
# removed at any time.
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@
__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS,
feature(layout_for_ptr, strict_provenance, coverage_attribute)
)]
#![cfg_attr(feature = "unstable-transmute-vec", feature(vec_into_raw_parts))]

// This is a hack to allow zerocopy-derive derives to work in this crate. They
// assume that zerocopy is linked as an extern crate, so they access items from
Expand Down
90 changes: 90 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,96 @@ macro_rules! try_transmute_mut {
}}
}

#[macro_export]
macro_rules! transmute_container {
($e:expr) => {{
// NOTE: This must be a macro (rather than a function with trait bounds)
// because there's no way, in a generic context, to enforce that two
// types have the same size or alignment.

let e = $e;

fn into_elem<C: Container>(c: C) -> C::Element {
loop {}
}

fn from_elem<C: Container>(e: C::Element) -> C {
loop {}
}

#[allow(unused, clippy::diverging_sub_expression)]
if false {
// This branch, though never taken, ensures that the type of `e` is
// `&mut T` where `T: 't + Sized + FromBytes + IntoBytes + Immutable`
// and that the type of this macro expression is `&mut U` where `U:
// 'u + Sized + FromBytes + IntoBytes + Immutable`.

// We use immutable references here rather than mutable so that, if
// this macro is used in a const context (in which, as of this
// writing, mutable references are banned), the error message
// appears to originate in the user's code rather than in the
// internals of this macro.
struct AssertSrcIsSized<'a, T: ::core::marker::Sized>(&'a T);
struct AssertSrcIsFromBytes<'a, T: ?::core::marker::Sized + $crate::FromBytes>(&'a T);
struct AssertSrcIsIntoBytes<'a, T: ?::core::marker::Sized + $crate::IntoBytes>(&'a T);
struct AssertDstIsSized<'a, T: ::core::marker::Sized>(&'a T);
struct AssertDstIsFromBytes<'a, T: ?::core::marker::Sized + $crate::FromBytes>(&'a T);
struct AssertDstIsIntoBytes<'a, T: ?::core::marker::Sized + $crate::IntoBytes>(&'a T);

let src_elem = into_elem(e);

if true {
let _ = AssertSrcIsSized(src_elem);
} else if true {
let _ = AssertSrcIsFromBytes(src_elem);
} else {
let _ = AssertSrcIsIntoBytes(src_elem);
}

if true {
#[allow(unused, unreachable_code)]
let u = AssertDstIsSized(loop {});
from_elem(u.0)
} else if true {
#[allow(unused, unreachable_code)]
let u = AssertDstIsFromBytes(loop {});
from_elem(u.0)
} else {
#[allow(unused, unreachable_code)]
let u = AssertDstIsIntoBytes(loop {});
from_elem(u.0)
}
} else if false {
// This branch, though never taken, ensures that `size_of::<T>() ==
// size_of::<U>()` and that that `align_of::<T>() >=
// align_of::<U>()`.

// `t` is inferred to have type `T` because it's assigned to `e` (of
// type `&mut T`) as `&mut t`.
let mut t = loop {};
e = from_elem(t);

// `u` is inferred to have type `U` because it's used as `&mut u` as
// the value returned from this branch.
let u;

$crate::assert_size_eq!(t, u);
$crate::assert_align_eq!(t, u);

from_elem(u)
} else {
// SAFETY: For source type `Src` and destination type `Dst`:
// - We know that `size_of::<Src>() == size_of::<Dst>()` thanks to
// the use of `assert_size_eq!` above.
// - We know that `align_of::<Src>() == align_of::<Dst>()` thanks to
// the use of `assert_align_eq!` above.
let raw = $crate::util::macro_util::Container::into_raw(e);
let u = unsafe { $crate::util::macro_util::Container::from_raw(raw) };
$crate::util::macro_util::must_use(u)
}
}}
}

/// Includes a file and safely transmutes it to a value of an arbitrary type.
///
/// The file will be included as a byte array, `[u8; N]`, which will be
Expand Down
102 changes: 102 additions & 0 deletions src/util/macro_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,36 @@ macro_rules! assert_align_gt_eq {
}};
}

#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
#[macro_export]
macro_rules! assert_align_eq {
($t:ident, $u: ident) => {{
// The comments here should be read in the context of this macro's
// invocations in `transmute_ref!` and `transmute_mut!`.
if false {
let align_t: $crate::util::macro_util::AlignOf<_> = unreachable!();
$t = align_t.into_t();

let mut align_u: $crate::util::macro_util::AlignOf<_> = unreachable!();
$u = align_u.into_t();

// This transmute will only compile successfully if
// `align_of::<T>() == max(align_of::<T>(), align_of::<U>())` - in
// other words, if `align_of::<T>() >= align_of::<U>()`.
//
// SAFETY: This code is never run.
align_u = unsafe {
// Clippy: We can't annotate the types; this macro is designed
// to infer the types from the calling context.
#[allow(clippy::missing_transmute_annotations)]
$crate::util::macro_util::core_reexport::mem::transmute(align_t)
};
} else {
loop {}
}
}};
}

/// Do `t` and `u` have the same size? If not, this macro produces a compile
/// error. It must be invoked in a dead codepath. This is used in
/// `transmute_ref!` and `transmute_mut!`.
Expand Down Expand Up @@ -683,6 +713,78 @@ pub const fn must_use<T>(t: T) -> T {
t
}

#[cfg(feature = "alloc")]
mod container {
use alloc::{boxed::Box, rc::Rc, sync::Arc};

#[cfg(feature = "unstable-transmute-vec")]
use alloc::vec::Vec;

pub unsafe trait Container {
type Elem;
type Raw;

fn into_raw(slf: Self) -> Self::Raw;
unsafe fn from_raw(raw: Self::Raw) -> Self;
}

unsafe impl<T> Container for Box<T> {
type Elem = T;
type Raw = *mut T;

fn into_raw(b: Box<T>) -> *mut T {
Box::into_raw(b)
}

unsafe fn from_raw(ptr: *mut T) -> Box<T> {
unsafe { Box::from_raw(ptr) }
}
}

unsafe impl<T> Container for Rc<T> {
type Elem = T;
type Raw = *const T;

fn into_raw(r: Rc<T>) -> *const T {
Rc::into_raw(r)
}

unsafe fn from_raw(ptr: *const T) -> Rc<T> {
unsafe { Rc::from_raw(ptr) }
}
}

unsafe impl<T> Container for Arc<T> {
type Elem = T;
type Raw = *const T;

fn into_raw(a: Arc<T>) -> *const T {
Arc::into_raw(a)
}

unsafe fn from_raw(ptr: *const T) -> Arc<T> {
unsafe { Arc::from_raw(ptr) }
}
}

#[cfg(feature = "unstable-transmute-vec")]
unsafe impl<T> Container for Vec<T> {
type Elem = T;
type Raw = (*mut T, usize, usize);

fn into_raw(v: Vec<T>) -> (*mut T, usize, usize) {
Vec::into_raw_parts(v)
}

unsafe fn from_raw((ptr, size, cap): (*mut T, usize, usize)) -> Vec<T> {
unsafe { Vec::from_raw_parts(ptr, size, cap) }
}
}
}

#[cfg(feature = "alloc")]
pub use container::*;

// NOTE: We can't change this to a `pub use core as core_reexport` until [1] is
// fixed or we update to a semver-breaking version (as of this writing, 0.8.0)
// on the `main` branch.
Expand Down

0 comments on commit f555762

Please sign in to comment.