From cf8daf7ebf30b6d73fa26a7c165b4affd59e1b77 Mon Sep 17 00:00:00 2001 From: Vladislav Kuzkokov Date: Thu, 29 Feb 2024 00:28:44 +0100 Subject: [PATCH] Optimize transmute Don't use boxes: Not only this avoids allocation but also makes no_std implementation possible. I didn't bother to rewrite the comment (it's not like I entirely understand how it works anyway). but there are 2 main issues to consider: 1) Alignment: union Blank has both the max size and max alignment of its arguments 2) Niches: for some implementation-defined reason this combination of Options and Result works. We likely use the fact that union have no niches. Together with removing black_box and inline(never) this achieves performance identical with std on my ARMv8. --- src/lifetime_expansion.rs | 1 - src/transmute.rs | 28 +++++++++++++--------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/lifetime_expansion.rs b/src/lifetime_expansion.rs index 867b643..fc1b6ca 100644 --- a/src/lifetime_expansion.rs +++ b/src/lifetime_expansion.rs @@ -30,7 +30,6 @@ pub const fn lifetime_translator<'a, 'b, T>(_val_a: &'a &'b (), val_b: &'b T) -> } /// This does the same thing as [`lifetime_translator`], just for mutable refs. -#[inline(never)] pub fn lifetime_translator_mut<'a, 'b, T>(_val_a: &'a &'b (), val_b: &'b mut T) -> &'a mut T { val_b } diff --git a/src/transmute.rs b/src/transmute.rs index 2d12261..572cef9 100644 --- a/src/transmute.rs +++ b/src/transmute.rs @@ -22,9 +22,8 @@ /// # Safety /// lol /// +#[allow(unused_assignments)] pub fn transmute(obj: A) -> B { - use std::hint::black_box; - // The layout of `DummyEnum` is approximately // DummyEnum { // is_a_or_b: u8, @@ -34,23 +33,22 @@ pub fn transmute(obj: A) -> B { // This should hopefully be more reliable than spamming the stack with a value and hoping the memory // is placed correctly by the compiler. enum DummyEnum { - A(Option>), - B(Option>), + A(Result>>), + B(Result>>), } - #[inline(never)] - fn transmute_inner(dummy: &mut DummyEnum, obj: A) -> B { - let DummyEnum::B(ref_to_b) = dummy else { - unreachable!() - }; - let ref_to_b = crate::lifetime_expansion::expand_mut(ref_to_b); - *dummy = DummyEnum::A(Some(Box::new(obj))); - black_box(dummy); - - *ref_to_b.take().unwrap() + union Blank { + _a: std::mem::ManuallyDrop, + _b: std::mem::ManuallyDrop, } - transmute_inner(black_box(&mut DummyEnum::B(None)), obj) + let mut res = DummyEnum::B(Err(None)); + let DummyEnum::B(ref_to_b) = &mut res else { + unreachable!() + }; + let ref_to_b = crate::lifetime_expansion::expand_mut(ref_to_b); + res = DummyEnum::A(Ok(obj)); + std::mem::replace(ref_to_b, Err(None)).ok().unwrap() } #[cfg(test)]