From 22ccc09fdd2dba2228c8ac598f505c79d5dc8e8d Mon Sep 17 00:00:00 2001 From: Mark Juggurnauth-Thomas Date: Fri, 13 Jan 2023 02:21:06 -0800 Subject: [PATCH] Make smallvec abomonable Add a new feature, `smallvec`, which, when specified, provides an implementation of `Abomonation` for [smallvec][1]. The implementation is based on the implementation for `Vec`, taking into account the case where the smallvector might be unspilled, in which case we do not need to entomb or exhume the vector contents as they have been stored inline. [1]: https://crates.io/crates/smallvec --- Cargo.toml | 6 ++ src/abomonable_smallvec.rs | 182 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 6 ++ 3 files changed, 194 insertions(+) create mode 100644 src/abomonable_smallvec.rs diff --git a/Cargo.toml b/Cargo.toml index 7c2f62d..c7c2314 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,5 +12,11 @@ repository = "https://github.com/TimelyDataflow/abomonation.git" keywords = ["abomonation"] license = "MIT" +[features] +smallvec = ["dep:smallvec"] + +[dependencies] +smallvec = { version = "1.7", optional = true } + [dev-dependencies] recycler="0.1.4" diff --git a/src/abomonable_smallvec.rs b/src/abomonable_smallvec.rs new file mode 100644 index 0000000..54e9757 --- /dev/null +++ b/src/abomonable_smallvec.rs @@ -0,0 +1,182 @@ +use crate::Abomonation; +use smallvec::{Array, SmallVec}; +use std::io::Result as IOResult; +use std::io::Write; +use std::mem; + +impl Abomonation for SmallVec +where + A::Item: Abomonation, +{ + #[inline] + unsafe fn entomb(&self, write: &mut W) -> IOResult<()> { + if self.spilled() { + write.write_all(crate::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_size().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::{decode, encode, measure, Abomonation}; + use smallvec::{smallvec, SmallVec}; + + fn _test_pass(record: T) { + let mut bytes = Vec::new(); + unsafe { + encode(&record, &mut bytes).unwrap(); + } + { + let (result, rest) = unsafe { decode::(&mut bytes[..]) }.unwrap(); + assert!(&record == result); + assert!(rest.len() == 0); + } + } + + fn _test_fail(record: T) { + let mut bytes = Vec::new(); + unsafe { + encode(&record, &mut bytes).unwrap(); + } + bytes.pop(); + assert!(unsafe { decode::(&mut bytes[..]) }.is_none()); + } + + fn _test_size(record: T) { + let mut bytes = Vec::new(); + unsafe { + encode(&record, &mut bytes).unwrap(); + } + assert_eq!(bytes.len(), measure(&record)); + } + + #[test] + fn test_smallvec_empty_pass() { + _test_pass::>(smallvec![]) + } + + #[test] + fn test_smallvec_unspilled_pass() { + _test_pass::>(smallvec![(0u64, format!("meow")); 3]) + } + + #[test] + fn test_smallvec_spilled_pass() { + _test_pass::>(smallvec![(0u64, format!("meow")); 17]) + } + + #[test] + fn test_smallvec_truncated_pass() { + let mut v: SmallVec<[(u64, String); 8]> = smallvec![(0u64, format!("meow")); 17]; + v.truncate(5); + _test_pass(v) + } + + #[test] + fn test_smallvec_zst_pass() { + _test_pass::>(smallvec![(); 17]) + } + + #[test] + fn test_smallvec_empty_fail() { + _test_fail::>(smallvec![]) + } + + #[test] + fn test_smallvec_unspilled_fail() { + _test_fail::>(smallvec![(0u64, format!("meow")); 3]) + } + + #[test] + fn test_smallvec_spilled_fail() { + _test_fail::>(smallvec![(0u64, format!("meow")); 17]) + } + + #[test] + fn test_smallvec_truncated_fail() { + let mut v: SmallVec<[(u64, String); 8]> = smallvec![(0u64, format!("meow")); 17]; + v.truncate(5); + _test_fail(v) + } + + #[test] + fn test_smallvec_zst_fail() { + _test_fail::>(smallvec![(); 17]) + } + + #[test] + fn test_smallvec_empty_size() { + _test_size::>(smallvec![]) + } + + #[test] + fn test_smallvec_unspilled_size() { + _test_size::>(smallvec![(0u64, format!("meow")); 3]) + } + + #[test] + fn test_smallvec_spilled_size() { + _test_size::>(smallvec![(0u64, format!("meow")); 17]) + } + + #[test] + fn test_smallvec_truncated_size() { + let mut v: SmallVec<[(u64, String); 8]> = smallvec![(0u64, format!("meow")); 17]; + v.truncate(5); + _test_size(v) + } + + #[test] + fn test_smallvec_zst_size() { + _test_size::>(smallvec![(); 17]) + } +} diff --git a/src/lib.rs b/src/lib.rs index 016bcc8..3281ced 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,6 +43,12 @@ use std::num::*; pub mod abomonated; +#[cfg(feature = "smallvec")] +extern crate smallvec; + +#[cfg(feature = "smallvec")] +mod abomonable_smallvec; + /// Encodes a typed reference into a binary buffer. /// /// # Safety