-
Notifications
You must be signed in to change notification settings - Fork 105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
added prefix transmute at transmute! #2024
base: main
Are you sure you want to change the base?
Conversation
7c86112
to
7ace93d
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2024 +/- ##
==========================================
- Coverage 89.46% 89.30% -0.17%
==========================================
Files 16 16
Lines 5838 5945 +107
==========================================
+ Hits 5223 5309 +86
- Misses 615 636 +21 ☔ View full report in Codecov by Sentry. |
@joshlf , please review it |
src/macros.rs
Outdated
/// Conditionally transmutes a value of one type to a prefix-compatible value of | ||
/// another type, allowing the destination type to be smaller than the source type. | ||
/// | ||
/// This macro provides prefix transmutation functionality, where the destination | ||
/// type's size can be smaller than or equal to the source type's size. The types | ||
/// `Src` and `Dst` must be explicitly specified in the macro invocation, allowing | ||
/// it to verify that the prefix of `Src` is compatible with `Dst`. | ||
/// | ||
/// # Syntax | ||
/// ```ignore | ||
/// try_transmute_prefix!(SrcType, DstType, value_expr) | ||
/// ``` | ||
/// | ||
/// # Parameters | ||
/// - `SrcType`: The source type of the transmute. Must implement `IntoBytes`. | ||
/// - `DstType`: The destination type for the transmute. Must implement `FromBytes`. | ||
/// - `value_expr`: The expression producing the `SrcType` value to be transmuted. | ||
/// | ||
/// In this prefix transmutation, the `SrcType` value will not be dropped. | ||
/// Instead, its bits will be copied into a new value of type `DstType`, | ||
/// allowing for compatibility when `size_of::<SrcType>() >= size_of::<DstType>()`. | ||
/// | ||
/// # Safety | ||
/// This macro performs an unsafe operation, using a union to verify size | ||
/// relationships. It will only compile if `size_of::<SrcType>() >= size_of::<DstType>()`, | ||
/// ensuring safe prefix compatibility. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// # use zerocopy::*; | ||
/// let large_array: [u8; 5] = [0, 1, 2, 3, 4]; | ||
/// let result: Result<[u8; 2], _> = try_transmute_prefix!([u8; 5], [u8; 2], large_array); | ||
/// assert!(result.is_ok()); | ||
/// assert_eq!(result.unwrap(), [0, 1]); | ||
/// | ||
/// let large_num: u64 = 0x0102030405060708; | ||
/// let result: Result<u32, _> = try_transmute_prefix!(u64, u32, large_num); | ||
/// assert!(result.is_ok()); | ||
/// assert_eq!(result.unwrap(), 0x05060708); | ||
/// ``` | ||
/// | ||
#[macro_export] | ||
macro_rules! try_transmute_prefix { | ||
($src_type:ty, $dest_type:ty, $e:expr) => {{ | ||
let e: $src_type = $e; | ||
if false { | ||
// Check that the source size is greater than or equal to destination size | ||
|
||
// SAFETY: This code is never executed. | ||
Ok(unsafe { | ||
let union_val = $crate::util::macro_util::core_reexport::mem::transmute::<$src_type, $crate::util::macro_util::MaxSizesOf<$src_type, $dest_type>>(e); | ||
$crate::util::macro_util::core_reexport::mem::transmute_copy(&union_val.source) | ||
}) | ||
} else { | ||
$crate::util::macro_util::try_transmute_prefix::<$src_type, $dest_type>(e) | ||
} | ||
}}; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we want to introduce another distinct macro here; rather, we want try_transmute
to support the @prefix
notation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this just to test the changes and I thought it would be better to keep this.
src/macros.rs
Outdated
// Create a union to verify size relationships at compile time | ||
#[repr(C)] | ||
union MaxSizesOf<T, U> { | ||
source: T, | ||
_dest: U, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You define MaxSizesOf
this in macro_util.rs
, so you don't need to redefine it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay
src/macros.rs
Outdated
// First transmute to union to verify size relationships | ||
let union_val = $crate::util::macro_util::core_reexport::mem::transmute::<_, MaxSizesOf<_, _>>(e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can size verification go in a else if false
branch so we don't pay the runtime cost of a double transmutation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I will resolve these changes.
src/macros.rs
Outdated
// #[macro_export] | ||
// macro_rules! transmute { | ||
// ($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. `core::mem::transmute` uses compiler magic | ||
// // to enforce this so long as the types are concrete. | ||
// let e = $e; | ||
// if false { | ||
// // This branch, though never taken, ensures that the type of `e` is | ||
// // `IntoBytes` and that the type of this macro invocation expression | ||
// // is `FromBytes`. | ||
// struct AssertIsIntoBytes<T: $crate::IntoBytes>(T); | ||
// let _ = AssertIsIntoBytes(e); | ||
// struct AssertIsFromBytes<U: $crate::FromBytes>(U); | ||
// #[allow(unused, unreachable_code)] | ||
// let u = AssertIsFromBytes(loop {}); | ||
// u.0 | ||
// } else { | ||
// // SAFETY: `core::mem::transmute` ensures that the type of `e` and | ||
// // the type of this macro invocation expression have the same size. | ||
// // We know this transmute is safe thanks to the `IntoBytes` and | ||
// // `FromBytes` bounds enforced by the `false` branch. | ||
// // | ||
// // We use this reexport of `core::mem::transmute` because we know it | ||
// // will always be available for crates which are using the 2015 | ||
// // edition of Rust. By contrast, if we were to use | ||
// // `std::mem::transmute`, this macro would not work for such crates | ||
// // in `no_std` contexts, and if we were to use | ||
// // `core::mem::transmute`, this macro would not work in `std` | ||
// // contexts in which `core` was not manually imported. This is not a | ||
// // problem for 2018 edition crates. | ||
// let 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(e) | ||
// }; | ||
// $crate::util::macro_util::must_use(u) | ||
// } | ||
// }} | ||
// } | ||
/// | ||
#[macro_export] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean to delete this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am changing this also.
95bff4f
to
6738594
Compare
@jswrenn , I request you to review this pr. |
This PR has huge swaths of commented-out code. As a rule, we don't leave commented-out code in the zerocopy codebase. |
4bf9ee7
to
5cd9285
Compare
5cd9285
to
92f1b6d
Compare
}} | ||
}}; | ||
|
||
(@prefix $src_type:ty, $dest_type:ty, $e:expr) => {{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'd like the types to be inferred at the callsite (consistent with our non-prefix transmutations), rather than supplied explicitly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made try_transmute as
#[macro_export]
macro_rules! try_transmute {
($e:expr) => {{
let e = $e;
if false {
// SAFETY: `core::mem::transmute` ensures that the type of `e` and
// the type of this macro invocation expression have the same size.
// This check is enforced by the false branch, so the actual transmute
// operation is safe.
Ok(unsafe {
#[allow(clippy::missing_transmute_annotations)]
$crate::util::macro_util::core_reexport::mem::transmute(e)
})
} else {
$crate::util::macro_util::try_transmute(e)
}
}};
(@prefix $e:expr) => {{
let e = $e;
if false {
#[allow(clippy::missing_transmute_annotations)]
// SAFETY: This code is never executed, and only verifies size
// compatibility at compile time using `MaxSizesOf`. Thus, no runtime
// transmute is actually performed here.
let _ = unsafe {
$crate::util::macro_util::core_reexport::mem::transmute::<_, $crate::util::macro_util::MaxSizesOf<_, _>>(e)
};
// SAFETY: The above transmute check ensures that the prefix transmute
// is safe, as `core::mem::transmute_copy` will only copy valid bytes.
Ok(unsafe {
$crate::util::macro_util::core_reexport::mem::transmute_copy(&e)
})
} else {
$crate::util::macro_util::try_transmute(e)
}
}};
}
When I write a test to try it out Type annotations needed cannot infer type
error is occuring.
let _ = unsafe {
$crate::util::macro_util::core_reexport::mem::transmute::<_, $crate::util::macro_util::MaxSizesOf<_, _>>(e)
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jswrenn , should I avoid writing a test?
// Helper trait for static assertions | ||
#[allow(dead_code)] | ||
trait StaticAssert { | ||
fn assert() -> bool; | ||
} | ||
|
||
// Implementation to verify size relationships | ||
impl<Src, Dst> StaticAssert for (Src, Dst) | ||
where | ||
Src: Sized, | ||
Dst: Sized, | ||
{ | ||
fn assert() -> bool { | ||
mem::size_of::<Src>() >= mem::size_of::<Dst>() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is dead code; remove it.
// Macro for static assertions | ||
#[inline] | ||
pub fn try_transmute_prefix<Src, Dst>(src: Src) -> Result<Dst, ValidityError<Src, Dst>> | ||
where | ||
Src: IntoBytes, | ||
Dst: TryFromBytes, | ||
{ | ||
// Ensure source size is greater than or equal to destination size | ||
if mem::size_of::<Src>() < mem::size_of::<Dst>() { | ||
return Err(ValidityError::new(src)); | ||
} | ||
|
||
let mut src = ManuallyDrop::new(src); | ||
let ptr = Ptr::from_mut(&mut src); | ||
|
||
// Similar to try_transmute, but we handle partial reads | ||
match try_cast_prefix_or_pme::<_, ManuallyDrop<Unalign<Dst>>, _, BecauseExclusive>(ptr) { | ||
Ok(ptr) => { | ||
let dst = ptr.bikeshed_recall_aligned().as_mut(); | ||
// SAFETY: By shadowing `dst`, we ensure that `dst` is not re-used | ||
// after taking its inner value. | ||
let dst = unsafe { ManuallyDrop::take(dst) }; | ||
Ok(dst.into_inner()) | ||
} | ||
Err(_) => Err(ValidityError::new(ManuallyDrop::into_inner(src))), | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want to introduce any new public functions or macros in our API for prefix transmutation.
@@ -619,7 +719,7 @@ where | |||
{ | |||
match try_cast_or_pme::<Src, Dst, _, BecauseImmutable>(Ptr::from_ref(src)) { | |||
Ok(ptr) => { | |||
static_assert!(Src, Dst => mem::align_of::<Dst>() <= mem::align_of::<Src>()); | |||
assert!(mem::align_of::<Dst>() <= mem::align_of::<Src>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This changes the semantics of the function. It's important that the assertion as at post-monomorphization time.
@@ -649,7 +749,7 @@ where | |||
{ | |||
match try_cast_or_pme::<Src, Dst, _, BecauseExclusive>(Ptr::from_mut(src)) { | |||
Ok(ptr) => { | |||
static_assert!(Src, Dst => mem::align_of::<Dst>() <= mem::align_of::<Src>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This changes the semantics of the function. It's important that the assertion as at post-monomorphization time.
closes #1852