Skip to main content

kevy_bytes/
lib.rs

1//! `SmallBytes` — a 24-byte small-byte-string with inline-SSO optimization.
2//!
3//! Layout (**little-endian only**): a union of two 24-byte variants, distinguished
4//! by the byte at offset 23:
5//!
6//! - **Inline**: `[u8; 23]` data, then `u8` tag holding the inline length
7//!   (0..=22). The whole string lives in the value, no allocation.
8//! - **Heap (64-bit)**: `NonNull<u8>` ptr (8) + `usize` len (8) + `usize`
9//!   cap_and_tag (8). The high byte of `cap_and_tag` overlaps byte 23 of
10//!   the union and is fixed at `0xFF` (> 22) as the heap discriminator. The
11//!   low 56 bits hold the heap capacity (up to 72 PB).
12//! - **Heap (32-bit)**: `NonNull<u8>` ptr (4) + `u32` len (4) + `u32`
13//!   cap (4) + 11-byte pad, then `u8` tag fixed at `0xFF`. Same 24-byte
14//!   total, same discriminator byte at offset 23 — pointer / len fields
15//!   are 32-bit-native so a `wasm32-unknown-unknown` build picks up the
16//!   right size without shifting a `usize` past its bit width.
17//!
18//! The 64-bit layout is the one the kevy server runs on, and is locked
19//! against perf-affecting changes (cfg-gated 32-bit alternative lives
20//! alongside it without touching any 64-bit code path).
21//!
22//! This lets us store every byte string up to 22 bytes — covering the vast
23//! majority of Redis-style values — without any pointer-chase, while keeping
24//! `size_of::<SmallBytes>() == 24` (same as `Vec<u8>`). Used by `kevy-store`
25//! to make `Value::Str(SmallBytes)` fit alongside the boxed collection
26//! variants and keep `Entry` at 48 B.
27
28#![warn(missing_docs)]
29#![cfg_attr(not(feature = "std"), no_std)]
30
31extern crate alloc;
32
33#[cfg(target_endian = "big")]
34compile_error!("kevy-bytes requires little-endian: heap-tag byte overlaps inline length byte");
35
36mod find_crlf;
37mod traits;
38
39pub use find_crlf::find_crlf;
40
41use alloc::alloc::{Layout, alloc, dealloc, handle_alloc_error};
42use alloc::vec::Vec;
43use core::mem::{self, ManuallyDrop};
44use core::ptr::NonNull;
45use core::slice;
46
47pub(crate) const INLINE_CAP: usize = 23;
48pub(crate) const INLINE_LEN_MAX: u8 = (INLINE_CAP - 1) as u8;
49
50#[cfg(target_pointer_width = "64")]
51const TAG_HEAP_BIT: usize = 0xFFusize << 56;
52#[cfg(target_pointer_width = "64")]
53const CAP_MASK: usize = (1usize << 56) - 1;
54
55/// Heap-rep marker byte at offset 23. Used by the 32-bit `Heap::new` to
56/// set its dedicated `tag` field; the 64-bit path encodes the same byte
57/// implicitly via the high byte of `cap_and_tag`.
58#[cfg(target_pointer_width = "32")]
59const HEAP_TAG_BYTE: u8 = 0xFF;
60
61#[repr(C)]
62#[derive(Copy, Clone)]
63struct Inline {
64    data: [u8; INLINE_CAP],
65    /// 0..=22 = inline length. The heap rep sets this byte to 0xFF either via
66    /// the high byte of `Heap::cap_and_tag` (64-bit, little-endian overlap)
67    /// or as a dedicated `tag` field at offset 23 (32-bit).
68    tag: u8,
69}
70
71/// 64-bit Heap rep — `ptr|len|cap_and_tag` × usize. High byte of
72/// `cap_and_tag` shadows `Inline::tag` (LE) so the discriminator byte at
73/// offset 23 = `0xFF`. Locked layout: the kevy server runs here and the
74/// perf budget assumes this exact shape.
75#[cfg(target_pointer_width = "64")]
76#[repr(C)]
77#[derive(Copy, Clone)]
78pub(crate) struct Heap {
79    pub(crate) ptr: NonNull<u8>,
80    pub(crate) len: usize,
81    /// High byte = 0xFF (heap marker, shadows `Inline::tag`); low 56 bits =
82    /// capacity (from the source `Vec<u8>` or our own alloc; ≥ len).
83    pub(crate) cap_and_tag: usize,
84}
85
86/// 32-bit Heap rep — `ptr(4)|len(4)|cap(4)|pad(11)|tag(1)`. The dedicated
87/// `tag` byte at offset 23 (= `0xFF`) plays the role the 64-bit `cap_and_tag`
88/// high byte does, so the discriminator check at offset 23 stays identical
89/// across both layouts. Unlocks `wasm32-unknown-unknown` (Wave 3 #7) without
90/// touching the 64-bit hot path.
91#[cfg(target_pointer_width = "32")]
92#[repr(C)]
93#[derive(Copy, Clone)]
94pub(crate) struct Heap {
95    pub(crate) ptr: NonNull<u8>,
96    pub(crate) len: u32,
97    pub(crate) cap: u32,
98    pub(crate) _pad: [u8; 11],
99    pub(crate) tag: u8,
100}
101
102impl Heap {
103    /// Build a Heap rep tagging the discriminator byte to `0xFF`. cfg-gated
104    /// so each pointer-width hits its native fields without runtime cost.
105    #[cfg(target_pointer_width = "64")]
106    #[inline]
107    pub(crate) fn new(ptr: NonNull<u8>, len: usize, cap: usize) -> Self {
108        debug_assert!(cap <= CAP_MASK, "kevy-bytes: capacity exceeds 56-bit field");
109        Self {
110            ptr,
111            len,
112            cap_and_tag: TAG_HEAP_BIT | (cap & CAP_MASK),
113        }
114    }
115    #[cfg(target_pointer_width = "32")]
116    #[inline]
117    pub(crate) fn new(ptr: NonNull<u8>, len: usize, cap: usize) -> Self {
118        // On 32-bit, `Vec<u8>` is bounded by the 4 GiB address space, so
119        // any source `len`/`cap` already fits in `u32`. Debug-assert to
120        // catch unexpected callers.
121        debug_assert!(
122            len <= u32::MAX as usize && cap <= u32::MAX as usize,
123            "kevy-bytes: len/cap exceeds u32 on 32-bit platform"
124        );
125        Self {
126            ptr,
127            len: len as u32,
128            cap: cap as u32,
129            _pad: [0; 11],
130            tag: HEAP_TAG_BYTE,
131        }
132    }
133
134    /// Live capacity (always returned as `usize` regardless of underlying
135    /// field width).
136    #[cfg(target_pointer_width = "64")]
137    #[inline]
138    fn capacity(&self) -> usize {
139        self.cap_and_tag & CAP_MASK
140    }
141    #[cfg(target_pointer_width = "32")]
142    #[inline]
143    fn capacity(&self) -> usize {
144        self.cap as usize
145    }
146
147    /// Live length (always `usize`).
148    #[cfg(target_pointer_width = "64")]
149    #[inline]
150    fn length(&self) -> usize {
151        self.len
152    }
153    #[cfg(target_pointer_width = "32")]
154    #[inline]
155    fn length(&self) -> usize {
156        self.len as usize
157    }
158}
159
160/// A 24-byte owned byte string with inline small-string optimization.
161///
162/// Strings of up to 22 bytes live entirely inside the value (no allocation,
163/// no pointer chase); larger strings spill to a heap buffer. The
164/// discriminator is a single byte at offset 23 (the tag, which doubles as
165/// the inline length 0..=22 OR equals 0xFF when the heap variant is active).
166///
167/// See the crate root for layout details.
168#[repr(C)]
169pub union SmallBytes {
170    inline: Inline,
171    heap: Heap,
172}
173
174const _: () = {
175    assert!(mem::size_of::<SmallBytes>() == 24);
176    assert!(mem::align_of::<SmallBytes>() == mem::align_of::<usize>());
177};
178
179unsafe impl Send for SmallBytes {}
180unsafe impl Sync for SmallBytes {}
181
182impl SmallBytes {
183    /// Empty inline `SmallBytes` (zero allocation).
184    pub const fn new() -> Self {
185        Self {
186            inline: Inline {
187                data: [0; INLINE_CAP],
188                tag: 0,
189            },
190        }
191    }
192
193    /// Construct from a byte slice — inline if `bytes.len() <= 22`, else heap.
194    pub fn from_slice(bytes: &[u8]) -> Self {
195        if bytes.len() <= INLINE_LEN_MAX as usize {
196            let mut data = [0u8; INLINE_CAP];
197            // SAFETY: bytes.len() ≤ 22 ≤ data.len(); non-overlapping regions.
198            unsafe {
199                core::ptr::copy_nonoverlapping(bytes.as_ptr(), data.as_mut_ptr(), bytes.len());
200            }
201            Self {
202                inline: Inline {
203                    data,
204                    tag: bytes.len() as u8,
205                },
206            }
207        } else {
208            Self::alloc_heap(bytes)
209        }
210    }
211
212    /// Take ownership of a `Vec<u8>` — inline if `vec.len() <= 22`, else **reuse
213    /// the vec's allocation** (no copy on the heap path).
214    pub fn from_vec(vec: Vec<u8>) -> Self {
215        if vec.len() <= INLINE_LEN_MAX as usize {
216            Self::from_slice(&vec)
217        } else {
218            let mut v = ManuallyDrop::new(vec);
219            // SAFETY: len > 22 ⇒ cap > 0 ⇒ Vec has an allocation, so the pointer
220            // is non-null. Vec guarantees a non-null pointer for any allocated
221            // Vec (and a dangling-but-non-null for empty, which we don't hit here).
222            let ptr = unsafe { NonNull::new_unchecked(v.as_mut_ptr()) };
223            let len = v.len();
224            let cap = v.capacity();
225            Self {
226                heap: Heap::new(ptr, len, cap),
227            }
228        }
229    }
230
231    #[inline]
232    fn alloc_heap(bytes: &[u8]) -> Self {
233        let len = bytes.len();
234        // `len > 22` (caller has already taken the heap branch) and `len` is
235        // a slice length ⇒ ≤ `isize::MAX` ⇒ well below the `usize::MAX -
236        // (align - 1)` bound `from_size_align_unchecked` needs. u8's align is 1.
237        // SAFETY: see above.
238        let layout = unsafe { Layout::from_size_align_unchecked(len, 1) };
239        // SAFETY: layout.size() > 0 (caller's heap branch guarantees len > 22).
240        let raw = unsafe { alloc(layout) };
241        let Some(ptr) = NonNull::new(raw) else {
242            handle_alloc_error(layout)
243        };
244        // SAFETY: alloc returned a writable region of `len` bytes; source is a
245        // disjoint slice.
246        unsafe {
247            core::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.as_ptr(), len);
248        }
249        Self {
250            heap: Heap::new(ptr, len, len),
251        }
252    }
253
254    /// True when stored inline; the byte at index 23 is the deciding tag in
255    /// either rep, so the check is a single load + compare.
256    #[inline]
257    fn is_inline(&self) -> bool {
258        // SAFETY: byte 23 is always initialised — either as Inline::tag (0..=22)
259        // or as the high byte of Heap::cap_and_tag (= 0xFF). Reading it through
260        // the Inline view is valid in either case (the union is `repr(C)`).
261        unsafe { self.inline.tag <= INLINE_LEN_MAX }
262    }
263
264    /// Number of bytes stored.
265    #[inline]
266    pub fn len(&self) -> usize {
267        if self.is_inline() {
268            // SAFETY: just verified `inline.tag` ≤ 22.
269            unsafe { self.inline.tag as usize }
270        } else {
271            // SAFETY: tag > 22 ⇒ heap variant is active.
272            unsafe { self.heap.length() }
273        }
274    }
275
276    /// Whether `len() == 0`.
277    #[inline]
278    pub fn is_empty(&self) -> bool {
279        self.len() == 0
280    }
281
282    /// Bytes this value holds on the heap (0 when inline). Lets memory-accounting
283    /// callers (e.g. `maxmemory` enforcement) charge only the off-stack footprint
284    /// without re-deriving the inline-length threshold.
285    #[inline]
286    pub fn heap_bytes(&self) -> usize {
287        if self.is_inline() { 0 } else { self.len() }
288    }
289
290    /// Borrow the bytes (no allocation; same for inline and heap variants).
291    #[inline]
292    pub fn as_slice(&self) -> &[u8] {
293        if self.is_inline() {
294            // SAFETY: first `tag` bytes of `data` are valid (zero-init at construction).
295            unsafe {
296                slice::from_raw_parts(self.inline.data.as_ptr(), self.inline.tag as usize)
297            }
298        } else {
299            // SAFETY: heap variant active; ptr/len originate from a Vec or our own alloc.
300            unsafe { slice::from_raw_parts(self.heap.ptr.as_ptr(), self.heap.length()) }
301        }
302    }
303
304    /// Copy into a fresh `Vec<u8>` (clone semantics).
305    pub fn to_vec(&self) -> Vec<u8> {
306        self.as_slice().to_vec()
307    }
308
309    /// Consume self and return an owned `Vec<u8>`. The heap path reuses the
310    /// existing allocation; the inline path copies into a new vec.
311    pub fn into_vec(self) -> Vec<u8> {
312        if self.is_inline() {
313            self.as_slice().to_vec()
314            // self drops as inline — nothing to free.
315        } else {
316            // SAFETY: heap variant active.
317            let (ptr, len, cap) = unsafe {
318                (
319                    self.heap.ptr.as_ptr(),
320                    self.heap.length(),
321                    self.heap.capacity(),
322                )
323            };
324            // Skip our Drop to avoid double-free; Vec::from_raw_parts now owns it.
325            let _do_not_drop = ManuallyDrop::new(self);
326            // SAFETY: ptr/len/cap originated from either a Vec<u8> (from_vec)
327            // or our own `alloc(Layout::array::<u8>(cap))` (alloc_heap, where
328            // cap == len) — both meet Vec::from_raw_parts' requirements.
329            unsafe { Vec::from_raw_parts(ptr, len, cap) }
330        }
331    }
332}
333
334impl Default for SmallBytes {
335    fn default() -> Self {
336        Self::new()
337    }
338}
339
340impl Drop for SmallBytes {
341    fn drop(&mut self) {
342        if self.is_inline() {
343            return;
344        }
345        // SAFETY: heap variant active; layout matches the one used at alloc
346        // time (either from Vec — Vec uses `Layout::array::<u8>(cap)` — or our
347        // own alloc_heap which used the same layout).
348        unsafe {
349            let cap = self.heap.capacity();
350            let layout = Layout::array::<u8>(cap).expect("kevy-bytes: drop layout");
351            dealloc(self.heap.ptr.as_ptr(), layout);
352        }
353    }
354}
355
356impl Clone for SmallBytes {
357    /// Specialised clone that bypasses `as_slice → from_slice → alloc_heap`'s
358    /// two layered length checks. Inline variant is a bitwise union copy (no
359    /// branch through the slice path); heap variant goes straight to a single
360    /// `alloc + memcpy` keyed on the already-known heap length.
361    #[inline]
362    fn clone(&self) -> Self {
363        if self.is_inline() {
364            // SAFETY: `Inline` is `repr(C)` + `Copy`; bitwise copy is sound
365            // when the source is currently in the inline variant (the tag
366            // byte ≤ 22 is part of the bit pattern we're copying, so the
367            // discriminator stays correct).
368            unsafe { Self { inline: self.inline } }
369        } else {
370            // SAFETY: tag > 22 ⇒ heap variant is active.
371            unsafe { self.clone_heap() }
372        }
373    }
374}
375
376impl SmallBytes {
377    /// Heap-fast-path clone. Caller must have established that `self` is in
378    /// the heap variant.
379    ///
380    /// # Safety
381    /// `self.heap` must be the active union variant (i.e. `is_inline()` is
382    /// false). `self.heap.ptr` must point to `self.heap.len` valid bytes.
383    #[inline]
384    unsafe fn clone_heap(&self) -> Self {
385        // SAFETY (covers the three `self.heap.*` reads): caller asserts the
386        // heap variant is active.
387        let (src_ptr, len) = unsafe { (self.heap.ptr.as_ptr(), self.heap.length()) };
388        // `len > 22 ⇒ len > 0`, and the high bits are guarded by `CAP_MASK`
389        // never letting cap exceed 2^56, well below `isize::MAX`, so the
390        // unchecked layout is sound. Allocator alignment for `u8` is 1.
391        let layout = unsafe { Layout::from_size_align_unchecked(len, 1) };
392        // SAFETY: layout.size() > 0.
393        let raw = unsafe { alloc(layout) };
394        let Some(ptr) = NonNull::new(raw) else {
395            handle_alloc_error(layout)
396        };
397        // SAFETY: src has `len` valid bytes; dst is freshly-allocated for `len`
398        // bytes; regions are disjoint.
399        unsafe { core::ptr::copy_nonoverlapping(src_ptr, ptr.as_ptr(), len) };
400        Self {
401            heap: Heap::new(ptr, len, len),
402        }
403    }
404}
405
406// `Debug`, `PartialOrd`, `Ord`, `Hash`, `AsRef<[u8]>`, `Borrow<[u8]>`,
407// `KevyHash`, `From<&[u8]>`, `From<Vec<u8>>` live in `crate::traits` —
408// they only need the public `as_slice()` view. `PartialEq` / `Eq` stay
409// here because the same-variant fast paths reach into `self.inline` /
410// `self.heap` directly.
411
412impl SmallBytes {
413    /// Both sides inline: compare tag-lengths, then the inline bytes.
414    /// Single call site in [`PartialEq::eq`]; `inline(always)` keeps the
415    /// split codegen-identical to the pre-split fused body.
416    #[allow(clippy::inline_always)] // see doc above: codegen parity with the pre-split body
417    #[inline(always)]
418    fn eq_inline_inline(&self, other: &Self, self_tag: u8, other_tag: u8) -> bool {
419        let len = self_tag as usize;
420        if len != other_tag as usize {
421            return false;
422        }
423        // SAFETY: both in inline variant; first `len` bytes valid.
424        let a = unsafe { slice::from_raw_parts(self.inline.data.as_ptr(), len) };
425        let b = unsafe { slice::from_raw_parts(other.inline.data.as_ptr(), len) };
426        a == b
427    }
428
429    /// Both sides heap: compare stored lengths, then the heap bytes.
430    /// Single call site in [`PartialEq::eq`]; `inline(always)` as above.
431    #[allow(clippy::inline_always)] // see doc above: codegen parity with the pre-split body
432    #[inline(always)]
433    fn eq_heap_heap(&self, other: &Self) -> bool {
434        // SAFETY: both in heap variant.
435        let (a_len, b_len) = unsafe { (self.heap.length(), other.heap.length()) };
436        if a_len != b_len {
437            return false;
438        }
439        // SAFETY: heap pointers + len are valid.
440        let a = unsafe { slice::from_raw_parts(self.heap.ptr.as_ptr(), a_len) };
441        let b = unsafe { slice::from_raw_parts(other.heap.ptr.as_ptr(), b_len) };
442        a == b
443    }
444}
445
446impl PartialEq for SmallBytes {
447    /// Specialised over the slice form (`as_slice == as_slice`) by branching
448    /// on variant **once** and reading the relevant length / pointer pair
449    /// directly. Same-variant cases (inline/inline + heap/heap, which are the
450    /// only ones produced by a single allocator) skip a redundant `as_slice`
451    /// dispatch on each side; the mixed case falls back to the slice form.
452    #[inline]
453    fn eq(&self, other: &Self) -> bool {
454        // SAFETY: byte 23 (`inline.tag`) is always a valid load in either
455        // variant — it's either the inline-length 0..=22 or 0xFF as the
456        // heap-discriminator overlap (see crate doc).
457        let self_tag = unsafe { self.inline.tag };
458        let other_tag = unsafe { other.inline.tag };
459        let self_inline = self_tag <= INLINE_LEN_MAX;
460        let other_inline = other_tag <= INLINE_LEN_MAX;
461        match (self_inline, other_inline) {
462            (true, true) => self.eq_inline_inline(other, self_tag, other_tag),
463            (false, false) => self.eq_heap_heap(other),
464            // Mixed inline/heap: this IS reachable in normal operation.
465            // It happens whenever HashMap (or any `==` consumer) compares
466            // an inline-length value (len ≤ 22) against a heap-length
467            // value (len > 22). Two SmallBytes of different lengths can
468            // *collide* on hashbrown's hash + quadratic probe, and the
469            // probe checks equality even though the lengths differ. The
470            // pre-fix `unreachable!()` here was a logic bug — it assumed
471            // the same-arm short-circuits cover all cases, but they only
472            // fire when both sides land in the same arm. Different-length
473            // collisions correctly fall through here. The right answer
474            // is just slice-form equality (which short-circuits on `len`
475            // internally), giving `false` whenever the lengths differ.
476            _ => self.as_slice() == other.as_slice(),
477        }
478    }
479}
480impl Eq for SmallBytes {}
481
482
483#[cfg(test)]
484mod tests;