From 1a6d2f9b33be5f7d7e96838e1230fb22941a3ae7 Mon Sep 17 00:00:00 2001 From: Mark Juggurnauth-Thomas Date: Wed, 11 Jan 2023 09:14:26 -0800 Subject: [PATCH] Implement abomonation support The `abomonation` crate allows transmutation of types into buffers of memory and back again. This is useful for caches, where it makes it possible to serve read-only references of the item directly out of the cache memory. The `abomonation` crate implements support for normal vectors. This change adds a new feature (`abomonation`) to the smallvec crate which provides abomonation support for `SmallVec`, too. --- Cargo.toml | 2 + src/abomonation.rs | 186 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 5 +- 3 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 src/abomonation.rs diff --git a/Cargo.toml b/Cargo.toml index 90209b7..02d1fe6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ write = [] union = [] specialization = [] may_dangle = [] +abomonation = ["dep:abomonation"] # UNSTABLE FEATURES (requires Rust nightly) # Enable to use the #[debugger_visualizer] attribute. @@ -26,6 +27,7 @@ debugger_visualizer = [] [dependencies] serde = { version = "1", optional = true, default-features = false } arbitrary = { version = "1", optional = true } +abomonation = { version = "0.7", optional = true } [dev_dependencies] bincode = "1.0.1" diff --git a/src/abomonation.rs b/src/abomonation.rs new file mode 100644 index 0000000..c42aaab --- /dev/null +++ b/src/abomonation.rs @@ -0,0 +1,186 @@ +use crate::{Array, SmallVec}; +use abomonation::Abomonation; +use std::io::Result as IOResult; +use std::io::Write; +use std::mem; + +// This method currently enables undefined behavior, by exposing padding bytes. +// Copied from abomonation. +#[inline] +unsafe fn typed_to_bytes(slice: &[T]) -> &[u8] { + std::slice::from_raw_parts( + slice.as_ptr() as *const u8, + slice.len() * mem::size_of::(), + ) +} + +impl Abomonation for SmallVec +where + A::Item: Abomonation, +{ + #[inline] + unsafe fn entomb(&self, write: &mut W) -> IOResult<()> { + if self.spilled() { + write.write_all(typed_to_bytes(&self[..]))?; + } + for element in self.iter() { + element.entomb(write)?; + } + Ok(()) + } + + #[inline] + unsafe fn exhume<'a, 'b>(&'a mut self, bytes: &'b mut [u8]) -> Option<&'b mut [u8]> { + // extract memory from bytes to back our smallvec + let binary_len = if self.spilled() { + self.len() * mem::size_of::() + } else { + 0 + }; + if binary_len > bytes.len() { + None + } else { + let (mine, mut rest) = bytes.split_at_mut(binary_len); + if self.spilled() { + let slice = + std::slice::from_raw_parts_mut(mine.as_mut_ptr() as *mut A::Item, self.len()); + // If the vector has spilled but then been truncated down to + // less than the capacity, we must lie about the capacity to + // maintain the spilled invariant. This is ok, as the + // exhumed smallvec is read-only. + let capacity = Self::inline_capacity().saturating_add(1).max(self.len()); + std::ptr::write( + self, + SmallVec::from_raw_parts(slice.as_mut_ptr(), self.len(), capacity), + ); + } + for element in self.iter_mut() { + let temp = rest; // temp variable explains lifetimes (mysterious!) + rest = element.exhume(temp)?; + } + Some(rest) + } + } + + #[inline] + fn extent(&self) -> usize { + let mut sum = 0; + if self.spilled() { + sum += mem::size_of::() * self.len(); + } + for element in self.iter() { + sum += element.extent(); + } + sum + } +} + +#[cfg(test)] +mod tests { + use crate::SmallVec; + use alloc::borrow::ToOwned; + use alloc::string::String; + use alloc::vec::Vec; + + #[test] + fn test_abomonate_empty() { + let v = SmallVec::<[String; 2]>::new(); + + let mut bytes = Vec::new(); + unsafe { + abomonation::encode(&v, &mut bytes).expect("encode should succeed"); + } + + if let Some((result, remaining)) = + unsafe { abomonation::decode::>(&mut bytes) } + { + assert!(result == &v); + assert!(remaining.len() == 0); + } + } + + #[test] + fn test_abomonate_unspilled() { + let mut v = SmallVec::<[String; 2]>::new(); + v.push("hello".to_owned()); + + let mut bytes = Vec::new(); + unsafe { + abomonation::encode(&v, &mut bytes).expect("encode should succeed"); + } + + if let Some((result, remaining)) = + unsafe { abomonation::decode::>(&mut bytes) } + { + assert!(result == &v); + assert!(remaining.len() == 0); + } + } + + #[test] + fn test_abomonate_spilled() { + let mut v = SmallVec::<[String; 2]>::new(); + v.push("hello".to_owned()); + v.push("there".to_owned()); + v.push("burma".to_owned()); + v.push("shave".to_owned()); + + let mut bytes = Vec::new(); + unsafe { + abomonation::encode(&v, &mut bytes).expect("encode should succeed"); + } + + if let Some((result, remaining)) = + unsafe { abomonation::decode::>(&mut bytes) } + { + assert!(result == &v); + assert!(remaining.len() == 0); + } + } + + #[test] + fn test_abomonate_spilled_truncated() { + let mut v = SmallVec::<[String; 2]>::new(); + v.push("hello".to_owned()); + v.push("there".to_owned()); + v.push("burma".to_owned()); + v.push("shave".to_owned()); + v.truncate(1); + + let mut bytes = Vec::new(); + unsafe { + abomonation::encode(&v, &mut bytes).expect("encode should succeed"); + } + + if let Some((result, remaining)) = + unsafe { abomonation::decode::>(&mut bytes) } + { + assert!(result == &v); + assert!(result.len() == 1); + assert!(result.capacity() == 3); + assert!(remaining.len() == 0); + } + } + + #[test] + fn test_abomonate_zst() { + let mut v = SmallVec::<[(); 2]>::new(); + v.push(()); + v.push(()); + v.push(()); + v.push(()); + + let mut bytes = Vec::new(); + unsafe { + abomonation::encode(&v, &mut bytes).expect("encode should succeed"); + } + + if let Some((result, remaining)) = + unsafe { abomonation::decode::>(&mut bytes) } + { + assert!(result == &v); + assert!(result.len() == 4); + assert!(remaining.len() == 0); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index a335ca4..7cbbc3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,7 +91,7 @@ #[doc(hidden)] pub extern crate alloc; -#[cfg(any(test, feature = "write"))] +#[cfg(any(test, feature = "write", feature = "abomonation"))] extern crate std; #[cfg(test)] @@ -2130,3 +2130,6 @@ where SmallVec::from_slice(self) } } + +#[cfg(feature = "abomonation")] +mod abomonation;