#![warn(missing_docs)]
#[cfg(target_endian = "big")]
compile_error!("kevy-bytes requires little-endian: heap-tag byte overlaps inline length byte");
mod traits;
use std::alloc::{Layout, alloc, dealloc, handle_alloc_error};
use std::mem::{self, ManuallyDrop};
use std::ptr::NonNull;
use std::slice;
const INLINE_CAP: usize = 23;
const INLINE_LEN_MAX: u8 = (INLINE_CAP - 1) as u8;
#[cfg(target_pointer_width = "64")]
const TAG_HEAP_BIT: usize = 0xFFusize << 56;
#[cfg(target_pointer_width = "64")]
const CAP_MASK: usize = (1usize << 56) - 1;
#[cfg(target_pointer_width = "32")]
const HEAP_TAG_BYTE: u8 = 0xFF;
#[repr(C)]
#[derive(Copy, Clone)]
struct Inline {
data: [u8; INLINE_CAP],
tag: u8,
}
#[cfg(target_pointer_width = "64")]
#[repr(C)]
#[derive(Copy, Clone)]
struct Heap {
ptr: NonNull<u8>,
len: usize,
cap_and_tag: usize,
}
#[cfg(target_pointer_width = "32")]
#[repr(C)]
#[derive(Copy, Clone)]
struct Heap {
ptr: NonNull<u8>,
len: u32,
cap: u32,
_pad: [u8; 11],
tag: u8,
}
impl Heap {
#[cfg(target_pointer_width = "64")]
#[inline]
fn new(ptr: NonNull<u8>, len: usize, cap: usize) -> Self {
debug_assert!(cap <= CAP_MASK, "kevy-bytes: capacity exceeds 56-bit field");
Self {
ptr,
len,
cap_and_tag: TAG_HEAP_BIT | (cap & CAP_MASK),
}
}
#[cfg(target_pointer_width = "32")]
#[inline]
fn new(ptr: NonNull<u8>, len: usize, cap: usize) -> Self {
debug_assert!(
len <= u32::MAX as usize && cap <= u32::MAX as usize,
"kevy-bytes: len/cap exceeds u32 on 32-bit platform"
);
Self {
ptr,
len: len as u32,
cap: cap as u32,
_pad: [0; 11],
tag: HEAP_TAG_BYTE,
}
}
#[cfg(target_pointer_width = "64")]
#[inline]
fn capacity(&self) -> usize {
self.cap_and_tag & CAP_MASK
}
#[cfg(target_pointer_width = "32")]
#[inline]
fn capacity(&self) -> usize {
self.cap as usize
}
#[cfg(target_pointer_width = "64")]
#[inline]
fn length(&self) -> usize {
self.len
}
#[cfg(target_pointer_width = "32")]
#[inline]
fn length(&self) -> usize {
self.len as usize
}
}
#[repr(C)]
pub union SmallBytes {
inline: Inline,
heap: Heap,
}
const _: () = {
assert!(mem::size_of::<SmallBytes>() == 24);
assert!(mem::align_of::<SmallBytes>() == mem::align_of::<usize>());
};
unsafe impl Send for SmallBytes {}
unsafe impl Sync for SmallBytes {}
impl SmallBytes {
pub const fn new() -> Self {
Self {
inline: Inline {
data: [0; INLINE_CAP],
tag: 0,
},
}
}
pub fn from_slice(bytes: &[u8]) -> Self {
if bytes.len() <= INLINE_LEN_MAX as usize {
let mut data = [0u8; INLINE_CAP];
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), data.as_mut_ptr(), bytes.len());
}
Self {
inline: Inline {
data,
tag: bytes.len() as u8,
},
}
} else {
Self::alloc_heap(bytes)
}
}
pub fn from_vec(vec: Vec<u8>) -> Self {
if vec.len() <= INLINE_LEN_MAX as usize {
Self::from_slice(&vec)
} else {
let mut v = ManuallyDrop::new(vec);
let ptr = unsafe { NonNull::new_unchecked(v.as_mut_ptr()) };
let len = v.len();
let cap = v.capacity();
Self {
heap: Heap::new(ptr, len, cap),
}
}
}
#[inline]
fn alloc_heap(bytes: &[u8]) -> Self {
let len = bytes.len();
let layout = unsafe { Layout::from_size_align_unchecked(len, 1) };
let raw = unsafe { alloc(layout) };
let ptr = match NonNull::new(raw) {
Some(p) => p,
None => handle_alloc_error(layout),
};
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.as_ptr(), len);
}
Self {
heap: Heap::new(ptr, len, len),
}
}
#[inline]
fn is_inline(&self) -> bool {
unsafe { self.inline.tag <= INLINE_LEN_MAX }
}
#[inline]
pub fn len(&self) -> usize {
if self.is_inline() {
unsafe { self.inline.tag as usize }
} else {
unsafe { self.heap.length() }
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
pub fn heap_bytes(&self) -> usize {
if self.is_inline() { 0 } else { self.len() }
}
#[inline]
pub fn as_slice(&self) -> &[u8] {
if self.is_inline() {
unsafe {
slice::from_raw_parts(self.inline.data.as_ptr(), self.inline.tag as usize)
}
} else {
unsafe { slice::from_raw_parts(self.heap.ptr.as_ptr(), self.heap.length()) }
}
}
pub fn to_vec(&self) -> Vec<u8> {
self.as_slice().to_vec()
}
pub fn into_vec(self) -> Vec<u8> {
if self.is_inline() {
self.as_slice().to_vec()
} else {
let (ptr, len, cap) = unsafe {
(
self.heap.ptr.as_ptr(),
self.heap.length(),
self.heap.capacity(),
)
};
let _do_not_drop = ManuallyDrop::new(self);
unsafe { Vec::from_raw_parts(ptr, len, cap) }
}
}
}
impl Default for SmallBytes {
fn default() -> Self {
Self::new()
}
}
impl Drop for SmallBytes {
fn drop(&mut self) {
if self.is_inline() {
return;
}
unsafe {
let cap = self.heap.capacity();
let layout = Layout::array::<u8>(cap).expect("kevy-bytes: drop layout");
dealloc(self.heap.ptr.as_ptr(), layout);
}
}
}
impl Clone for SmallBytes {
#[inline]
fn clone(&self) -> Self {
if self.is_inline() {
unsafe { Self { inline: self.inline } }
} else {
unsafe { self.clone_heap() }
}
}
}
impl SmallBytes {
#[inline]
unsafe fn clone_heap(&self) -> Self {
let (src_ptr, len) = unsafe { (self.heap.ptr.as_ptr(), self.heap.length()) };
let layout = unsafe { Layout::from_size_align_unchecked(len, 1) };
let raw = unsafe { alloc(layout) };
let ptr = match NonNull::new(raw) {
Some(p) => p,
None => handle_alloc_error(layout),
};
unsafe { std::ptr::copy_nonoverlapping(src_ptr, ptr.as_ptr(), len) };
Self {
heap: Heap::new(ptr, len, len),
}
}
}
impl PartialEq for SmallBytes {
#[inline]
fn eq(&self, other: &Self) -> bool {
let self_tag = unsafe { self.inline.tag };
let other_tag = unsafe { other.inline.tag };
let self_inline = self_tag <= INLINE_LEN_MAX;
let other_inline = other_tag <= INLINE_LEN_MAX;
match (self_inline, other_inline) {
(true, true) => {
let len = self_tag as usize;
if len != other_tag as usize {
return false;
}
let a = unsafe {
slice::from_raw_parts(self.inline.data.as_ptr(), len)
};
let b = unsafe {
slice::from_raw_parts(other.inline.data.as_ptr(), len)
};
a == b
}
(false, false) => {
let (a_len, b_len) =
unsafe { (self.heap.length(), other.heap.length()) };
if a_len != b_len {
return false;
}
let a = unsafe {
slice::from_raw_parts(self.heap.ptr.as_ptr(), a_len)
};
let b = unsafe {
slice::from_raw_parts(other.heap.ptr.as_ptr(), b_len)
};
a == b
}
_ => self.as_slice() == other.as_slice(),
}
}
}
impl Eq for SmallBytes {}
#[cfg(test)]
mod tests {
use super::*;
use kevy_hash::KevyHash as _;
use std::hash::{Hash, Hasher};
#[test]
fn size_and_align() {
assert_eq!(mem::size_of::<SmallBytes>(), 24);
assert_eq!(mem::align_of::<SmallBytes>(), mem::align_of::<usize>());
}
#[test]
fn empty_is_inline() {
let s = SmallBytes::new();
assert!(s.is_inline());
assert_eq!(s.len(), 0);
assert!(s.is_empty());
assert_eq!(s.as_slice(), b"");
}
#[test]
fn inline_one_byte() {
let s = SmallBytes::from_slice(b"x");
assert!(s.is_inline());
assert_eq!(s.len(), 1);
assert_eq!(s.as_slice(), b"x");
}
#[test]
fn inline_at_boundary_22() {
let v: Vec<u8> = (0u8..22).collect();
let s = SmallBytes::from_slice(&v);
assert!(s.is_inline());
assert_eq!(s.len(), 22);
assert_eq!(s.as_slice(), v);
}
#[test]
fn heap_at_boundary_23() {
let v: Vec<u8> = (0u8..23).collect();
let s = SmallBytes::from_slice(&v);
assert!(!s.is_inline());
assert_eq!(s.len(), 23);
assert_eq!(s.as_slice(), v);
}
#[test]
fn heap_large() {
let v: Vec<u8> = (0..4096).map(|i| (i & 0xFF) as u8).collect();
let s = SmallBytes::from_slice(&v);
assert!(!s.is_inline());
assert_eq!(s.len(), 4096);
assert_eq!(s.as_slice(), v.as_slice());
}
#[test]
fn from_vec_inline() {
let s = SmallBytes::from_vec(vec![1u8, 2, 3]);
assert!(s.is_inline());
assert_eq!(s.as_slice(), &[1, 2, 3]);
}
#[test]
fn from_vec_heap_reuses_alloc() {
let mut v: Vec<u8> = (0u8..100).collect();
v.reserve(200);
let ptr_before = v.as_ptr();
let cap_before = v.capacity();
let s = SmallBytes::from_vec(v);
assert!(!s.is_inline());
unsafe {
assert_eq!(s.heap.ptr.as_ptr() as *const u8, ptr_before);
assert_eq!(s.heap.capacity(), cap_before);
}
}
#[test]
fn into_vec_inline_copies() {
let s = SmallBytes::from_slice(b"hello");
let v = s.into_vec();
assert_eq!(v, b"hello");
}
#[test]
fn into_vec_heap_reuses_alloc() {
let original: Vec<u8> = (0u8..200).collect();
let ptr = original.as_ptr();
let cap = original.capacity();
let s = SmallBytes::from_vec(original);
let v = s.into_vec();
assert_eq!(v.as_ptr(), ptr);
assert_eq!(v.capacity(), cap);
assert_eq!(v.len(), 200);
}
#[test]
fn clone_inline() {
let s = SmallBytes::from_slice(b"abc");
let c = s.clone();
assert_eq!(s, c);
assert!(c.is_inline());
}
#[test]
fn clone_heap() {
let v: Vec<u8> = (0u8..50).collect();
let s = SmallBytes::from_slice(&v);
let c = s.clone();
assert_eq!(s, c);
assert!(!c.is_inline());
}
#[test]
fn eq_by_content() {
let a = SmallBytes::from_slice(b"short");
let b = SmallBytes::from_slice(b"short");
assert_eq!(a, b);
let c: Vec<u8> = (0u8..30).collect();
let d: Vec<u8> = (0u8..30).collect();
assert_eq!(SmallBytes::from_slice(&c), SmallBytes::from_slice(&d));
}
#[test]
fn ord_lex() {
let a = SmallBytes::from_slice(b"abc");
let b = SmallBytes::from_slice(b"abd");
assert!(a < b);
}
#[test]
fn debug_format_matches_slice() {
let s = SmallBytes::from_slice(&[1u8, 2, 3]);
let dbg = format!("{s:?}");
let exp = format!("{:?}", &[1u8, 2, 3][..]);
assert_eq!(dbg, exp);
}
#[test]
fn default_is_empty_inline() {
let s = SmallBytes::default();
assert!(s.is_inline());
assert_eq!(s.len(), 0);
}
#[test]
fn drop_heap_does_not_leak_or_double_free() {
for n in [23usize, 64, 1024, 65536] {
let v: Vec<u8> = (0..n).map(|i| (i & 0xFF) as u8).collect();
let s = SmallBytes::from_slice(&v);
drop(s);
}
}
#[test]
fn eq_is_reflexive_and_symmetric_inline() {
let a = SmallBytes::from_slice(b"hi");
let b = SmallBytes::from_slice(b"hi");
let c = SmallBytes::from_slice(b"no");
assert_eq!(a, a);
assert_eq!(a, b);
assert_eq!(b, a);
assert_ne!(a, c);
}
#[test]
fn eq_is_reflexive_and_symmetric_heap() {
let v: Vec<u8> = (0u8..40).collect();
let a = SmallBytes::from_slice(&v);
let b = SmallBytes::from_slice(&v);
let mut w = v.clone();
w[0] = w[0].wrapping_add(1);
let c = SmallBytes::from_slice(&w);
assert_eq!(a, a);
assert_eq!(a, b);
assert_eq!(b, a);
assert_ne!(a, c);
}
#[test]
fn partial_cmp_matches_cmp_inline() {
let a = SmallBytes::from_slice(b"abc");
let b = SmallBytes::from_slice(b"abd");
assert_eq!(a.partial_cmp(&b), Some(std::cmp::Ordering::Less));
assert_eq!(b.partial_cmp(&a), Some(std::cmp::Ordering::Greater));
assert_eq!(a.partial_cmp(&a), Some(std::cmp::Ordering::Equal));
assert_eq!(a.cmp(&b), std::cmp::Ordering::Less);
assert_eq!(a.cmp(&a), std::cmp::Ordering::Equal);
}
#[test]
fn hash_agrees_with_byte_slice() {
use std::collections::hash_map::DefaultHasher;
let v: Vec<u8> = (0u8..40).collect();
let s = SmallBytes::from_slice(&v);
let mut h_slice = DefaultHasher::new();
v.as_slice().hash(&mut h_slice);
let mut h_sb = DefaultHasher::new();
s.hash(&mut h_sb);
assert_eq!(h_slice.finish(), h_sb.finish());
}
#[test]
fn kevy_hash_agrees_with_byte_slice() {
let v: Vec<u8> = (0u8..40).collect();
let s = SmallBytes::from_slice(&v);
assert_eq!(
s.kevy_hash(),
v.as_slice().kevy_hash(),
"KevyHash impl must agree with &[u8] so a KevyMap<SmallBytes, V> can be queried by Borrow<[u8]>"
);
let small = SmallBytes::from_slice(b"foo");
assert_eq!(small.kevy_hash(), (b"foo" as &[u8]).kevy_hash());
}
#[test]
fn as_ref_is_zero_copy_view() {
let s = SmallBytes::from_slice(b"abcdef");
let r: &[u8] = s.as_ref();
assert_eq!(r, b"abcdef");
assert!(std::ptr::eq(r.as_ptr(), s.as_slice().as_ptr()));
}
#[test]
fn borrow_lookup_works_in_collection() {
use std::collections::HashMap;
let mut m: HashMap<SmallBytes, i32> = HashMap::new();
m.insert(SmallBytes::from_slice(b"key1"), 1);
m.insert(SmallBytes::from_slice(b"key2"), 2);
assert_eq!(m.get(b"key1".as_slice()), Some(&1));
assert_eq!(m.get(b"key2".as_slice()), Some(&2));
assert_eq!(m.get(b"none".as_slice()), None);
}
#[test]
fn from_byte_slice_round_trip() {
let a: SmallBytes = (&b"short"[..]).into();
assert_eq!(a.as_slice(), b"short");
let v: Vec<u8> = (0u8..40).collect();
let b: SmallBytes = v.as_slice().into();
assert_eq!(b.as_slice(), v.as_slice());
assert!(!b.is_inline());
}
#[test]
fn from_vec_dispatches_inline_or_heap() {
let inline_src: SmallBytes = vec![1u8, 2, 3].into();
assert!(inline_src.is_inline());
assert_eq!(inline_src.as_slice(), &[1, 2, 3]);
let v: Vec<u8> = (0u8..30).collect();
let heap_src: SmallBytes = v.clone().into();
assert!(!heap_src.is_inline());
assert_eq!(heap_src.as_slice(), v.as_slice());
}
#[test]
fn clone_heap_keeps_data_and_is_independent() {
let v: Vec<u8> = (0u8..50).collect();
let src = SmallBytes::from_slice(&v);
let dup = src.clone();
unsafe {
assert_ne!(
src.heap.ptr.as_ptr(),
dup.heap.ptr.as_ptr(),
"clone must allocate a fresh buffer"
);
}
drop(src);
assert_eq!(dup.as_slice(), v.as_slice());
}
#[test]
fn drop_inline_is_noop() {
for &n in &[0usize, 1, 5, 22] {
let s = SmallBytes::from_slice(&vec![b'x'; n]);
assert!(s.is_inline());
drop(s);
}
}
#[test]
fn into_vec_zero_size_path() {
let s = SmallBytes::new();
let v = s.into_vec();
assert!(v.is_empty());
}
#[test]
fn to_vec_copies_inline_and_heap() {
let inline = SmallBytes::from_slice(b"hi");
assert_eq!(inline.to_vec(), b"hi");
let v: Vec<u8> = (0u8..30).collect();
let heap = SmallBytes::from_slice(&v);
let copy = heap.to_vec();
assert_eq!(copy, v);
assert_eq!(heap.as_slice(), v.as_slice());
}
use std::alloc::{GlobalAlloc, Layout, System};
use std::cell::Cell;
struct CountingAlloc {
inner: System,
}
thread_local! {
static THREAD_RECORDING: Cell<bool> = const { Cell::new(false) };
static THREAD_ALLOC_CALLS: Cell<usize> = const { Cell::new(0) };
}
unsafe impl GlobalAlloc for CountingAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let _ = THREAD_RECORDING.try_with(|r| {
if r.get() {
let _ = THREAD_ALLOC_CALLS.try_with(|c| c.set(c.get() + 1));
}
});
unsafe { self.inner.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { self.inner.dealloc(ptr, layout) }
}
}
#[global_allocator]
static COUNTING: CountingAlloc = CountingAlloc { inner: System };
fn measure_allocs<F: FnOnce()>(f: F) -> usize {
THREAD_ALLOC_CALLS.with(|c| c.set(0));
THREAD_RECORDING.with(|r| r.set(true));
f();
THREAD_RECORDING.with(|r| r.set(false));
THREAD_ALLOC_CALLS.with(|c| c.get())
}
#[test]
fn inline_payload_does_not_allocate() {
let max_inline = INLINE_LEN_MAX as usize;
let allocs = measure_allocs(|| {
for n in 0..=max_inline {
let s = SmallBytes::from_slice(&[0u8; INLINE_CAP][..n]);
std::hint::black_box(&s);
std::hint::black_box(s.as_slice());
std::hint::black_box(s.len());
let c = s.clone(); std::hint::black_box(&c);
drop(c);
drop(s);
}
});
assert_eq!(
allocs, 0,
"expected SSO inline path to be alloc-free, got {allocs} allocs"
);
}
#[test]
fn heap_payload_does_allocate() {
let max_inline = INLINE_LEN_MAX as usize;
let allocs = measure_allocs(|| {
let s = SmallBytes::from_slice(&[7u8; INLINE_CAP + 8][..max_inline + 1]);
std::hint::black_box(&s);
drop(s);
});
assert!(
allocs >= 1,
"expected the heap path to allocate at least once, got {allocs}"
);
}
#[test]
fn partial_eq_mixed_arm_does_not_panic() {
use std::mem::ManuallyDrop;
let inline_hi = SmallBytes::from_slice(b"hi");
let inline_no = SmallBytes::from_slice(b"no");
let mut storage = ManuallyDrop::new(b"hi".to_vec());
let ptr = NonNull::new(storage.as_mut_ptr()).expect("non-null Vec");
let forged = ManuallyDrop::new(SmallBytes {
heap: Heap::new(ptr, 2, 2),
});
assert_eq!(inline_hi, *forged);
assert_eq!(*forged, inline_hi);
assert_ne!(inline_no, *forged);
assert_ne!(*forged, inline_no);
let _ = (storage, forged);
}
}