Skip to main content

hipstr/bytes/
raw.rs

1//! Raw representations of [`HipByt`].
2//!
3//! Provides only the core features for the sequence of bytes.
4
5use alloc::vec::Vec;
6use core::hint::unreachable_unchecked;
7use core::marker::PhantomData;
8use core::mem::{align_of, forget, replace, size_of, transmute, ManuallyDrop, MaybeUninit};
9use core::num::NonZeroU8;
10use core::ops::Range;
11
12use allocated::Allocated;
13use borrowed::Borrowed;
14
15use crate::Backend;
16
17pub mod allocated;
18pub mod borrowed;
19pub mod inline;
20#[cfg(test)]
21mod tests;
22
23/// Width (in bits) of the tag
24const TAG_BITS: u8 = 2;
25
26/// Mask to extract the tag bits
27const MASK: u8 = (1 << TAG_BITS) - 1;
28
29/// Tag for the inline repr
30const TAG_INLINE: u8 = 1;
31
32/// Tag for the borrowed repr
33const TAG_BORROWED: u8 = 2;
34
35/// Tag for the allocated repr
36const TAG_ALLOCATED: u8 = 3;
37
38/// Maximal byte capacity of an inline [`HipByt`].
39const INLINE_CAPACITY: usize = size_of::<Borrowed>() - 1;
40
41/// Size of word minus a tagged byte.
42const WORD_SIZE_M1: usize = size_of::<usize>() - 1;
43
44/// Alias type for `Inline` with set inline capacity
45pub type Inline = inline::Inline<INLINE_CAPACITY>;
46
47/// Smart bytes, i.e. cheaply clonable and sliceable byte string.
48///
49/// # Examples
50///
51/// You can create a `HipStr` from a [byte slice (&`[u8]`)][slice], an owned
52/// byte string ([`Vec<u8>`], [`Box<[u8]>`][std::boxed::Box]), or a
53/// clone-on-write smart pointer ([`Cow<[u8]>`][std::borrow::Cow]) with
54/// [`From`]:
55///
56/// ```
57/// # use hipstr::HipByt;
58/// let hello = HipByt::from(b"Hello".as_slice());
59/// ```
60///
61/// When possible, `HipStr::from` takes ownership of the underlying buffer:
62///
63/// ```
64/// # use hipstr::HipByt;
65/// let vec = Vec::from(b"World".as_slice());
66/// let world = HipByt::from(vec);
67/// ```
68///
69/// To borrow a string slice, you can also use the no-copy constructor
70/// [`HipByt::borrowed`]:
71///
72/// ```
73/// # use hipstr::HipByt;
74/// let hello = HipByt::borrowed(b"Hello, world!");
75/// ```
76///
77/// # Representations
78///
79/// `HipByt` has three possible internal representations:
80///
81/// * borrow
82/// * inline string
83/// * shared heap allocated string
84///
85/// # Notable features
86///
87/// `HipByt` dereferences through the [`Deref`] trait to either `&[u8]` ot
88/// [`&bstr::BStr`] if the feature flag `bstr` is set. [`bstr`] allows for
89/// efficient string-like manipulation on non-guaranteed UTF-8 data.
90///
91/// In the same manner, [`HipByt::mutate`] returns a mutable handle [`RefMut`]
92/// to a `Vec<[u8]>` or a [`bstr::BString`] if the flag `bstr` is set.
93///
94/// [`bstr`]: https://crates.io/crates/bstr
95/// [`&bstr::BStr`]: https://docs.rs/bstr/latest/bstr/struct.BStr.html
96/// [`bstr::BString`]: https://docs.rs/bstr/latest/bstr/struct.BString.html
97/// [`Deref`]: core::ops::Deref
98/// [`RefMut`]: super::RefMut
99#[repr(C)]
100pub struct HipByt<'borrow, B: Backend> {
101    pivot: Pivot,
102    _marker: PhantomData<&'borrow B>,
103}
104
105#[derive(Clone, Copy)]
106#[repr(C)]
107pub(super) struct Pivot {
108    #[cfg(target_endian = "little")]
109    tag_byte: NonZeroU8,
110    #[cfg(target_endian = "little")]
111    _word_remainder: MaybeUninit<[u8; WORD_SIZE_M1]>,
112    #[cfg(target_endian = "little")]
113    _word1: MaybeUninit<*mut ()>,
114
115    _word2: MaybeUninit<*mut ()>,
116
117    #[cfg(target_endian = "big")]
118    _word1: MaybeUninit<*mut ()>,
119    #[cfg(target_endian = "big")]
120    _word_remainder: MaybeUninit<[u8; WORD_SIZE_M1]>,
121    #[cfg(target_endian = "big")]
122    tag_byte: NonZeroU8,
123}
124
125unsafe impl<B: Backend + Sync> Sync for HipByt<'_, B> {}
126unsafe impl<B: Backend + Send> Send for HipByt<'_, B> {}
127
128/// Equivalent union representation.
129///
130/// NOTE: Cannot be used directly to keep the niche for `Option<HipByt<_,_>>`
131#[repr(C)]
132pub union Union<'borrow, B: Backend> {
133    /// Inline representation
134    pub inline: Inline,
135
136    /// Allocated and shared representation
137    pub allocated: Allocated<B>,
138
139    /// Borrowed slice representation
140    pub borrowed: Borrowed<'borrow>,
141
142    /// Pivot representation with niche
143    pivot: Pivot,
144}
145
146impl<'borrow, B: Backend> Union<'borrow, B> {
147    const ASSERTS: () = {
148        assert!(size_of::<Self>() == size_of::<HipByt<'borrow, B>>());
149        assert!(align_of::<Self>() == align_of::<HipByt<'borrow, B>>());
150    };
151
152    #[inline]
153    pub const fn into_raw(self) -> HipByt<'borrow, B> {
154        // statically checks the layout
155        let () = Self::ASSERTS;
156
157        // SAFETY: same layout and same niche hopefully
158        let pivot = unsafe { self.pivot };
159        HipByt {
160            pivot,
161            _marker: PhantomData,
162        }
163    }
164}
165
166/// Repr tag.
167///
168/// Cannot be used directly to keep the niche.
169#[repr(u8)]
170#[derive(Clone, Copy, Debug, PartialEq, Eq)]
171pub enum Tag {
172    Inline = TAG_INLINE,
173    Borrowed = TAG_BORROWED,
174    Allocated = TAG_ALLOCATED,
175}
176
177/// Helper enum to split this raw byte string into its possible representation.
178pub enum Split<'a, 'borrow, B: Backend> {
179    /// Inline representation
180    Inline(&'a Inline),
181    /// Allocated and shared representation
182    Allocated(&'a Allocated<B>),
183    /// Borrowed slice representation
184    Borrowed(&'a Borrowed<'borrow>),
185}
186
187/// Helper enum to split this raw byte string into its possible representation mutably.
188pub enum SplitMut<'a, 'borrow, B: Backend> {
189    /// Inline representation
190    Inline(&'a mut Inline),
191    /// Allocated and shared representation
192    Allocated(&'a mut Allocated<B>),
193    /// Borrowed slice representation
194    Borrowed(&'a mut Borrowed<'borrow>),
195}
196
197impl<'borrow, B: Backend> HipByt<'borrow, B> {
198    /// Retrieves a reference on the union.
199    #[inline]
200    pub(super) const fn union(&self) -> &Union<'borrow, B> {
201        let raw_ptr: *const _ = &self.pivot;
202        let union_ptr: *const Union<'borrow, B> = raw_ptr.cast();
203        // SAFETY: same layout and same niche hopefully, same immutability
204        unsafe { &*union_ptr }
205    }
206
207    /// Retrieves a mutable reference on the union.
208    #[inline]
209    pub(super) const fn union_mut(&mut self) -> &mut Union<'borrow, B> {
210        let raw_ptr: *mut _ = &mut self.pivot;
211        let union_ptr: *mut Union<'borrow, B> = raw_ptr.cast();
212        // SAFETY: same layout and same niche hopefully, same mutability
213        unsafe { &mut *union_ptr }
214    }
215
216    /// Extracts the union without dropping the `HipByt`.
217    pub(super) fn union_move(self) -> Union<'borrow, B> {
218        // Do not drop free!
219        let this = ManuallyDrop::new(self);
220        Union { pivot: this.pivot }
221    }
222
223    // basic constructors
224
225    /// Creates a new `HipByt` from an allocated internal representation.
226    ///
227    /// To be normalized, the allocated length should be strictly greater than
228    /// `INLINE_CAPACITY`.
229    #[inline]
230    pub(super) const fn from_allocated(allocated: Allocated<B>) -> Self {
231        Union { allocated }.into_raw()
232    }
233
234    /// Creates a new `HipByt` from an inline representation.
235    #[inline]
236    pub(super) const fn from_inline(inline: Inline) -> Self {
237        Union { inline }.into_raw()
238    }
239
240    /// Creates a new `HipByt` from a borrowed representation.
241    #[inline]
242    pub(super) const fn from_borrowed(borrowed: Borrowed<'borrow>) -> Self {
243        Union { borrowed }.into_raw()
244    }
245
246    /// Retrieves the tag.
247    pub(super) const fn tag(&self) -> Tag {
248        match self.pivot.tag_byte.get() & MASK {
249            TAG_INLINE => Tag::Inline,
250            TAG_BORROWED => Tag::Borrowed,
251            TAG_ALLOCATED => Tag::Allocated,
252            // SAFETY: type invariant
253            _ => unsafe { unreachable_unchecked() },
254        }
255    }
256
257    /// Splits this raw into its possible representation.
258    #[inline]
259    pub(super) const fn split(&self) -> Split<'_, 'borrow, B> {
260        let tag = self.tag();
261        let union = self.union();
262        match tag {
263            Tag::Inline => {
264                // SAFETY: representation checked
265                Split::Inline(unsafe { &union.inline })
266            }
267            Tag::Borrowed => {
268                // SAFETY: representation checked
269                Split::Borrowed(unsafe { &union.borrowed })
270            }
271            Tag::Allocated => {
272                // SAFETY: representation checked
273                Split::Allocated(unsafe { &union.allocated })
274            }
275        }
276    }
277
278    /// Splits this raw into its possible representation.
279    #[inline]
280    pub(super) const fn split_mut(&mut self) -> SplitMut<'_, 'borrow, B> {
281        let tag = self.tag();
282        let union = self.union_mut();
283        match tag {
284            Tag::Inline => {
285                // SAFETY: representation checked
286                SplitMut::Inline(unsafe { &mut union.inline })
287            }
288            Tag::Borrowed => {
289                // SAFETY: representation checked
290                SplitMut::Borrowed(unsafe { &mut union.borrowed })
291            }
292            Tag::Allocated => {
293                // SAFETY: representation checked
294                SplitMut::Allocated(unsafe { &mut union.allocated })
295            }
296        }
297    }
298
299    /// Creates a new `HipByt` from a vector.
300    pub(super) fn from_vec(vec: Vec<u8>) -> Self {
301        let allocated = Allocated::new(vec);
302        Self::from_allocated(allocated)
303    }
304
305    /// Creates a new empty inline `HipByt`.
306    #[inline]
307    pub(super) const fn inline_empty() -> Self {
308        const { Self::from_inline(Inline::empty()) }
309    }
310
311    /// Creates a new `HipByt` from a short slice.
312    ///
313    /// # Safety
314    ///
315    /// The input slice's length MUST be at most `INLINE_CAPACITY`.
316    pub(super) const unsafe fn inline_unchecked(bytes: &[u8]) -> Self {
317        // SAFETY: see function precondition
318        let inline = unsafe { Inline::new_unchecked(bytes) };
319        Self::from_inline(inline)
320    }
321
322    // derived constructors
323
324    /// Creates a new `HipByt` from a vector.
325    ///
326    /// Will normalize the representation depending on the size of the vector.
327    #[inline]
328    pub(crate) fn normalized_from_vec(vec: Vec<u8>) -> Self {
329        let len = vec.len();
330        if len <= INLINE_CAPACITY {
331            // SAFETY: length checked above
332            unsafe { Self::inline_unchecked(&vec) }
333        } else {
334            Self::from_vec(vec)
335        }
336    }
337
338    /// Creates a new `HipByt` from a slice.
339    ///
340    /// Will normalize the representation depending on the size of the slice.
341    pub(crate) fn from_slice(bytes: &[u8]) -> Self {
342        let len = bytes.len();
343        if len == 0 {
344            Self::inline_empty()
345        } else if len <= INLINE_CAPACITY {
346            // SAFETY: length checked above
347            unsafe { Self::inline_unchecked(bytes) }
348        } else {
349            Self::from_allocated(Allocated::from_slice(bytes))
350        }
351    }
352
353    /// Slices the raw byte sequence.
354    ///
355    /// # Safety
356    ///
357    /// `range` must be a range `a..b` with `a <= b <= len`.
358    ///
359    /// Panics in debug build, UB in release.
360    #[inline]
361    pub(super) unsafe fn range_unchecked(&self, range: Range<usize>) -> Self {
362        debug_assert!(range.start <= range.end);
363        debug_assert!(range.end <= self.len());
364
365        let result = match self.split() {
366            Split::Inline(inline) => {
367                // SAFETY: by `slice_unchecked` safety precondition and `split`
368                // range must be of a length <= self.len() <= `INLINE_CAPACITY`
369                unsafe { Self::inline_unchecked(&inline.as_slice()[range]) }
370            }
371            Split::Borrowed(borrowed) => Self::borrowed(&borrowed.as_slice()[range]),
372            Split::Allocated(allocated) => {
373                // normalize to inline if possible
374                if range.len() <= INLINE_CAPACITY {
375                    // SAFETY: length is checked above
376                    unsafe { Self::inline_unchecked(&allocated.as_slice()[range]) }
377                } else {
378                    // SAFETY: length is checked above
379                    unsafe {
380                        let allocated = allocated.slice_unchecked(range);
381                        Self::from_allocated(allocated)
382                    }
383                }
384            }
385        };
386
387        debug_assert!(self.is_normalized());
388        result
389    }
390
391    /// Extracts a slice as its own `HipByt` based on the given subslice `&[u8]`.
392    ///
393    /// # Safety
394    ///
395    /// The slice MUST be a part of this `HipByt`
396    ///
397    /// # Panics
398    ///
399    /// When in debug build, panics if the slice is not a part of this `HipByt`.
400    #[must_use]
401    pub unsafe fn slice_ref_unchecked(&self, slice: &[u8]) -> Self {
402        #[cfg(debug_assertions)]
403        {
404            let range = self.as_slice().as_ptr_range();
405            let slice_range = slice.as_ptr_range();
406            assert!(range.contains(&slice_range.start) || range.end == slice_range.start);
407            assert!(range.contains(&slice_range.end) || range.end == slice_range.end);
408        }
409
410        let result = match self.split() {
411            Split::Inline(_) => {
412                // SAFETY: by the function precondition and the test above
413                // slice.len() <= self.len() <= INLINE_CAPACITY
414                unsafe { Self::inline_unchecked(slice) }
415            }
416            Split::Borrowed(_) => {
417                // SAFETY: by the function precondition and the type invariant
418                // slice must have at least the same dynamic lifetime
419                let sl: &'borrow [u8] = unsafe { transmute(slice) };
420                Self::borrowed(sl)
421            }
422            Split::Allocated(allocated) => {
423                // normalize to inline if possible
424                if slice.len() <= INLINE_CAPACITY {
425                    // SAFETY: length checked above
426                    unsafe { Self::inline_unchecked(slice) }
427                } else {
428                    // SAFETY: by the function precondition
429                    let range = unsafe { range_of_unchecked(self.as_slice(), slice) };
430                    // SAFETY: length checked above
431                    unsafe {
432                        let allocated = allocated.slice_unchecked(range);
433                        Self::from_allocated(allocated)
434                    }
435                }
436            }
437        };
438
439        debug_assert!(self.is_normalized());
440        result
441    }
442
443    /// Takes a vector representation of this raw byte string.
444    ///
445    /// Will only allocate if needed.
446    #[inline]
447    pub(crate) fn take_vec(&mut self) -> Vec<u8> {
448        if self.is_allocated() {
449            // SAFETY: representation is checked, copy without ownership
450            let allocated = unsafe { self.union_mut().allocated };
451            if let Ok(owned) = allocated.try_into_vec() {
452                // SAFETY: ownership is taken, replace with empty
453                // and forget old value (otherwise double drop!!)
454                forget(replace(self, Self::new()));
455                return owned;
456            }
457        }
458        let owned = Vec::from(self.as_slice());
459        *self = Self::new();
460        owned
461    }
462
463    /// Takes the allocated representation if any, replacing it with an empty
464    /// byte string.
465    ///
466    /// # Errors
467    ///
468    /// Returns `None` if this byte string is not allocated.
469    #[inline]
470    pub(super) const fn take_allocated(&mut self) -> Option<Allocated<B>> {
471        match self.split() {
472            Split::Allocated(&allocated) => {
473                // Takes a copy of allocated
474
475                // replace `self` one by an empty raw
476                // forget the old value, we have `allocated` as a valid handle
477                forget(replace(self, Self::new()));
478
479                Some(allocated)
480            }
481            _ => None,
482        }
483    }
484
485    /// Makes the underlying data uniquely owned, copying if needed.
486    #[inline]
487    pub(super) fn make_unique(&mut self) {
488        let tag = self.tag();
489        match tag {
490            Tag::Inline => {}
491            Tag::Borrowed => {
492                let old = replace(self, Self::new()).union_move();
493
494                // SAFETY: representation is checked above
495                let borrowed = unsafe { old.borrowed };
496
497                *self = Self::from_slice(borrowed.as_slice());
498            }
499            Tag::Allocated => {
500                // SAFETY: representation checked above
501                if unsafe { self.union().allocated }.is_unique() {
502                    return;
503                }
504
505                let old = replace(self, Self::new());
506
507                // SAFETY: representation checked above
508                let allocated = unsafe { old.union_move().allocated };
509
510                // SAFETY: by the type invariant
511                // allocated len must be > INLINE_CAPACITY
512                let new = Self::from_vec(allocated.as_slice().to_vec());
513
514                // manual decrement of the reference count
515                allocated.explicit_drop();
516
517                *self = new;
518            }
519        }
520    }
521
522    /// Returns `true` it `self` is equal byte for byte to `other`.
523    #[inline(never)]
524    pub(crate) fn inherent_eq<B2: Backend>(&self, other: &HipByt<B2>) -> bool {
525        // use memcmp directly to squeeze one more comparison
526        extern "C" {
527            fn memcmp(a: *const u8, b: *const u8, size: usize) -> core::ffi::c_int;
528        }
529
530        let len = self.len();
531        if len != other.len() {
532            return false;
533        }
534
535        let self_ptr = self.as_ptr();
536        let other_ptr = other.as_ptr();
537        if core::ptr::eq(self_ptr, other_ptr) {
538            return true;
539        }
540
541        // use element size (just a remainder for now)
542        let size = len * size_of::<u8>();
543
544        // SAFETY: size checked above
545        unsafe { memcmp(self_ptr, other_ptr, size) == 0 }
546    }
547}
548
549impl<B: Backend> Drop for HipByt<'_, B> {
550    #[inline]
551    fn drop(&mut self) {
552        // formally drops the allocated decreasing the ref count if needed
553        if let Some(allocated) = self.take_allocated() {
554            allocated.explicit_drop();
555        }
556    }
557}
558
559impl<B: Backend> Clone for HipByt<'_, B> {
560    fn clone(&self) -> Self {
561        match self.split() {
562            Split::Inline(&inline) => Self::from_inline(inline),
563            Split::Borrowed(&borrowed) => Self::from_borrowed(borrowed),
564            Split::Allocated(allocated) => {
565                // increase the ref count or clone if overflow
566                let clone = allocated.explicit_clone();
567                Self::from_allocated(clone)
568            }
569        }
570    }
571}
572
573/// Computes the range in `whole` corresponding to the given `slice`.
574///
575/// # Safety
576///
577/// `slice` must be part of `whole`.
578unsafe fn range_of_unchecked(whole: &[u8], slice: &[u8]) -> Range<usize> {
579    unsafe {
580        let offset = slice.as_ptr().offset_from(whole.as_ptr());
581        let offset: usize = offset.try_into().unwrap_unchecked();
582        offset..offset + slice.len()
583    }
584}
585
586pub fn try_range_of(whole: &[u8], slice: &[u8]) -> Option<Range<usize>> {
587    let len = whole.len();
588    let Range { start, end } = whole.as_ptr_range();
589    let slice_len = slice.len();
590    let slice_start = slice.as_ptr();
591
592    // checks that slice_start in whole
593    if slice_start < start || slice_start > end {
594        return None;
595    }
596
597    // SAFETY: `offset_from` requires both pointers to be in the same allocated object (+1).
598    // that is checked above: slice_ptr is in self
599    let offset = unsafe { slice_start.offset_from(start) };
600    // SAFETY: offset is between 0 and slice_len included
601    let offset: usize = unsafe { offset.try_into().unwrap_unchecked() };
602    if offset + slice_len > len {
603        None
604    } else {
605        Some(offset..offset + slice_len)
606    }
607}