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 24, 2024
1 parent 86ff535 commit 713dbed
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ zerocopy-core-error = "1.81.0"
# From 1.78.0, Rust supports the `#[diagnostic::on_unimplemented]` attribute.
zerocopy-diagnostic-on-unimplemented = "1.78.0"

# From 1.65.0, Rust supports generic associated types (GATs).
zerocopy-gat = "1.65.0"

# From 1.61.0, Rust supports generic types with trait bounds in `const fn`.
zerocopy-generic-bounds-in-const-fn = "1.61.0"

Expand Down Expand Up @@ -71,6 +74,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
146 changes: 146 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,99 @@ macro_rules! try_transmute_mut {
}}
}

/// TODO
#[macro_export]
#[cfg(all(zerocopy_gat, feature = "alloc"))]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
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: $crate::util::macro_util::Container>(_c: C) -> C::Elem {
loop {}
}

fn from_elem<C: $crate::util::macro_util::Container>(_e: C::Elem) -> 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<T: ::core::marker::Sized>(T);
struct AssertSrcIsFromBytes<T: ?::core::marker::Sized + $crate::FromBytes>(T);
struct AssertSrcIsIntoBytes<T: ?::core::marker::Sized + $crate::IntoBytes>(T);
struct AssertDstIsSized<T: ::core::marker::Sized>(T);
struct AssertDstIsFromBytes<T: ?::core::marker::Sized + $crate::FromBytes>(T);
struct AssertDstIsIntoBytes<T: ?::core::marker::Sized + $crate::IntoBytes>(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::transmute_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 Expand Up @@ -949,6 +1042,45 @@ mod tests {
assert_eq!(*y, 0);
}

#[test]
#[cfg(feature = "alloc")]
fn test_transmute_container() {
use alloc::{rc::Rc, sync::Arc};

macro_rules! test {
($cont:ident) => {
let array_of_u8s = [0u8, 1, 2, 3, 4, 5, 6, 7];
let array_of_arrays = [[0, 1], [2, 3], [4, 5], [6, 7]];
let x: $cont<[[u8; 2]; 4]> = transmute_container!($cont::new(array_of_u8s));
assert_eq!(*x, array_of_arrays);
let x: $cont<[u8; 8]> = transmute_container!($cont::new(array_of_arrays));
assert_eq!(*x, array_of_u8s);
};
}

test!(Box);
test!(Rc);
test!(Arc);

#[cfg(feature = "unstable-transmute-vec")]
{
let array_of_u8s = [0u8, 1, 2, 3, 4, 5, 6, 7];
let array_of_arrays = [[0, 1], [2, 3], [4, 5], [6, 7]];

let v = std::iter::repeat(array_of_u8s).take(3).collect::<Vec<_>>();
let x: Vec<[[u8; 2]; 4]> = transmute_container!(v);
assert_eq!(x[0], array_of_arrays);
assert_eq!(x[1], array_of_arrays);
assert_eq!(x[1], array_of_arrays);

let v = std::iter::repeat(array_of_arrays).take(3).collect::<Vec<_>>();
let x: Vec<[u8; 8]> = transmute_container!(v);
assert_eq!(x[0], array_of_u8s);
assert_eq!(x[1], array_of_u8s);
assert_eq!(x[2], array_of_u8s);
}
}

#[test]
fn test_macros_evaluate_args_once() {
let mut ctr = 0;
Expand Down Expand Up @@ -981,6 +1113,20 @@ mod tests {
})
.unwrap();
assert_eq!(ctr, 1);

let mut ctr = 0;
let _: Result<&usize, _> = try_transmute_ref!({
ctr += 1;
&0usize
});
assert_eq!(ctr, 1);

let mut ctr: usize = 0;
let _: Result<&mut usize, _> = try_transmute_mut!({
ctr += 1;
&mut ctr
});
assert_eq!(ctr, 1);
}

#[test]
Expand Down
133 changes: 133 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,109 @@ pub const fn must_use<T>(t: T) -> T {
t
}

#[cfg(all(zerocopy_gat, 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 Applied<E>: Container<Elem = E>;
type Elem;
type Raw;

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

fn transmute_raw<T>(raw: <Self::Applied<T> as Container>::Raw) -> Self::Raw;

unsafe fn transmute_from_raw<T>(raw: <Self::Applied<T> as Container>::Raw) -> Self
where
Self: Sized,
{
let raw = Self::transmute_raw::<T>(raw);
unsafe { Self::from_raw(raw) }
}
}

unsafe impl<T> Container for Box<T> {
type Applied<U> = Box<U>;
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) }
}

fn transmute_raw<U>(raw: *mut U) -> *mut T {
raw.cast()
}
}

unsafe impl<T> Container for Rc<T> {
type Applied<U> = Rc<U>;
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) }
}

fn transmute_raw<U>(raw: *const U) -> *const T {
raw.cast()
}
}

unsafe impl<T> Container for Arc<T> {
type Applied<U> = Arc<U>;
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) }
}

fn transmute_raw<U>(raw: *const U) -> *const T {
raw.cast()
}
}

#[cfg(feature = "unstable-transmute-vec")]
unsafe impl<T> Container for Vec<T> {
type Applied<U> = Vec<U>;
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) }
}

fn transmute_raw<U>((ptr, size, cap): (*mut U, usize, usize)) -> (*mut T, usize, usize) {
(ptr.cast(), size, cap)
}
}
}

#[cfg(all(zerocopy_gat, 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 713dbed

Please sign in to comment.