thin_slice/
lib.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5//! An owned slice that tries to use only one word of storage.
6//!
7//! `ThinBoxedSlice<T>` can be used in place of `Box<[T]>` on the `x86_64`
8//! architecture to hold ownership of a slice when it's important to reduce
9//! memory usage of the box itself. When the slice length is less than
10//! `0xffff`, a single word is used to encode the slice pointer and length.
11//! When it is greater than `0xffff`, a heap allocation is used to store the
12//! fat pointer representing the slice.
13//!
14//! A `ThinBoxedSlice<T>` is always created by converting from a `Box<[T]>`.
15//!
16//! On any architecture other than `x86_64`, a `ThinBoxedSlice<T>` will simply
17//! use a `Box<[T]>` internally.
18//!
19//! # Examples
20//!
21//! Creating a `ThinBoxedSlice`:
22//!
23//! ```
24//! # use thin_slice::ThinBoxedSlice;
25//! let fat_pointer = vec![10, 20, 30].into_boxed_slice();
26//! let thin_pointer: ThinBoxedSlice<_> = fat_pointer.into();
27//! ```
28
29use std::cmp::Ordering;
30use std::fmt;
31use std::hash::{Hash, Hasher};
32#[cfg(target_arch = "x86_64")]
33use std::marker::PhantomData;
34#[cfg(target_arch = "x86_64")]
35use std::mem;
36use std::ops::{Deref, DerefMut};
37#[cfg(target_arch = "x86_64")]
38use std::ptr::NonNull;
39#[cfg(target_arch = "x86_64")]
40use std::slice;
41
42/// An owned slice that tries to use only one word of storage.
43///
44/// See the [module-level documentation](index.html) for more.
45pub struct ThinBoxedSlice<T> {
46    /// Storage for the slice.
47    ///
48    /// Once `std::num::NonZeroUsize` has stabilized, we can switch to that.
49    ///
50    /// The value stored here depends on the length of the slice.
51    ///
52    /// If len = 0, `data` will be `1usize`.
53    ///
54    /// If 0 < len < 0xffff, then len will be stored in the top 16 bits of
55    /// data, and the lower 48 bits will be the pointer to the elements.
56    ///
57    /// If len >= 0xffff, then the top 16 bits of data will be 0xffff, and
58    /// the lower 48 bits will be a pointer to a heap allocated `Box<[T]>`.
59    #[cfg(target_arch = "x86_64")]
60    data: NonNull<()>,
61
62    #[cfg(not(target_arch = "x86_64"))]
63    data: Box<[T]>,
64
65    #[cfg(target_arch = "x86_64")]
66    _phantom: PhantomData<Box<[T]>>,
67}
68
69#[cfg(target_arch = "x86_64")]
70const TAG_MASK: usize = 0xffff000000000000;
71
72#[cfg(target_arch = "x86_64")]
73const PTR_MASK: usize = 0x0000ffffffffffff;
74
75#[cfg(target_arch = "x86_64")]
76const PTR_HIGH: usize = 0x0000800000000000;
77
78#[cfg(target_arch = "x86_64")]
79const TAG_SHIFT: usize = 48;
80
81#[cfg(target_arch = "x86_64")]
82const TAG_LIMIT: usize = TAG_MASK >> TAG_SHIFT;
83
84#[cfg(target_arch = "x86_64")]
85enum Storage<T> {
86    Inline(*mut T, usize),
87    Spilled(*mut Box<[T]>),
88}
89
90#[cfg(target_arch = "x86_64")]
91impl<T> ThinBoxedSlice<T> {
92    /// Constructs a `ThinBoxedSlice` from a raw pointer.
93    ///
94    /// Like `Box::from_raw`, after calling this function, the raw pointer is
95    /// owned by the resulting `ThinBoxedSlice`.
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// # use thin_slice::ThinBoxedSlice;
101    /// let x = vec![10, 20, 30].into_boxed_slice();       // a Box<[i32]>
102    /// let ptr = Box::into_raw(x);                        // a *mut [i32]
103    /// let x = unsafe { ThinBoxedSlice::from_raw(ptr) };  // a ThinBoxedSlice<i32>
104    /// ```
105    #[inline]
106    pub unsafe fn from_raw(raw: *mut [T]) -> ThinBoxedSlice<T> {
107        let len = (*raw).len();
108        let ptr = (*raw).as_mut_ptr();
109        let storage = if len == 0 {
110            Storage::Inline(1usize as *mut _, 0)
111        } else if len < TAG_LIMIT {
112            Storage::Inline(ptr, len)
113        } else {
114            let boxed_slice = Box::from_raw(raw);
115            Storage::Spilled(Box::into_raw(Box::new(boxed_slice)))
116        };
117        ThinBoxedSlice {
118            data: storage.into_data(),
119            _phantom: PhantomData,
120        }
121    }
122
123    /// Consumes the `ThinBoxedSlice`, returning a raw pointer to the slice
124    /// it owned.
125    ///
126    /// Like `Box::into_raw`, after calling this function, the caller is
127    /// responsible for the memory previously managed by the `ThinBoxedSlice`.
128    /// In particular, the caller should properly destroy the `[T]` and release
129    /// the memory. The proper way to do so is to convert the raw pointer back
130    /// into a `Box` or a `ThinBoxedSlice`, with either the `Box::from_raw` or
131    /// `ThinBoxedSlice::from_raw` functions.
132    ///
133    /// # Examples
134    ///
135    /// ```
136    /// # use thin_slice::ThinBoxedSlice;
137    /// let x = vec![10, 20, 30].into_boxed_slice();
138    /// let x = ThinBoxedSlice::from(x);
139    /// let ptr = ThinBoxedSlice::into_raw(x);
140    /// ```
141    #[inline]
142    pub fn into_raw(b: ThinBoxedSlice<T>) -> *mut [T] {
143        unsafe {
144            match b.into_storage() {
145                Storage::Inline(ptr, len) => {
146                    slice::from_raw_parts_mut(ptr, len)
147                }
148                Storage::Spilled(ptr) => {
149                    Box::into_raw(*Box::from_raw(ptr))
150                }
151            }
152        }
153    }
154
155    /// Consumes and leaks the `ThinBoxedSlice`, returning a mutable reference,
156    /// `&'a mut [T]`. Here, the lifetime `'a` may be chosen to be `'static`.
157    ///
158    /// Like `Box::leak`, this function is mainly useful for data that lives
159    /// for the remainder of the program's life. Dropping the returned
160    /// reference will cause a memory leak. If this is not acceptable, the
161    /// reference should first be wrapped with the `Box::from_raw` function
162    /// producing a `Box`, or with the `ThinBoxedSlice::from_raw` function
163    /// producing a `ThinBoxedSlice`. This value can then be dropped which will
164    /// properly destroy `[T]` and release the allocated memory.
165    ///
166    /// # Examples
167    ///
168    /// ```
169    /// # use thin_slice::ThinBoxedSlice;
170    /// fn main() {
171    ///     let x = ThinBoxedSlice::from(vec![1, 2, 3].into_boxed_slice());
172    ///     let static_ref = ThinBoxedSlice::leak(x);
173    ///     static_ref[0] = 4;
174    ///     assert_eq!(*static_ref, [4, 2, 3]);
175    /// }
176    /// ```
177    #[inline]
178    pub fn leak(b: ThinBoxedSlice<T>) -> &'static mut [T] {
179        unsafe { &mut *ThinBoxedSlice::into_raw(b) }
180    }
181
182    /// Returns a pointer to the heap allocation that stores the fat pointer
183    /// to the slice, if any. This is useful for systems that need to measure
184    /// memory allocation, but is otherwise an opaque pointer.
185    #[inline]
186    pub fn spilled_storage(&self) -> Option<*const ()> {
187        match self.storage() {
188            Storage::Inline(..) => None,
189            Storage::Spilled(ptr) => Some(ptr as *const ()),
190        }
191    }
192
193    #[inline]
194    fn storage(&self) -> Storage<T> {
195        Storage::from_data(self.data.clone())
196    }
197
198    #[inline]
199    fn into_storage(self) -> Storage<T> {
200        let storage = self.storage();
201        mem::forget(self);
202        storage
203    }
204}
205
206#[cfg(not(target_arch = "x86_64"))]
207impl<T> ThinBoxedSlice<T> {
208    /// Constructs a `ThinBoxedSlice` from a raw pointer.
209    ///
210    /// Like `Box::from_raw`, after calling this function, the raw pointer is
211    /// owned by the resulting `ThinBoxedSlice`.
212    ///
213    /// # Examples
214    ///
215    /// ```
216    /// # use thin_slice::ThinBoxedSlice;
217    /// let x = vec![10, 20, 30].into_boxed_slice();       // a Box<[i32]>
218    /// let ptr = Box::into_raw(x);                        // a *mut [i32]
219    /// let x = unsafe { ThinBoxedSlice::from_raw(ptr) };  // a ThinBoxedSlice<i32>
220    /// ```
221    #[inline]
222    pub unsafe fn from_raw(raw: *mut [T]) -> ThinBoxedSlice<T> {
223        ThinBoxedSlice {
224            data: Box::from_raw(raw),
225        }
226    }
227
228    /// Consumes the `ThinBoxedSlice`, returning a raw pointer to the slice
229    /// it owned.
230    ///
231    /// Like `Box::into_raw`, after calling this function, the caller is
232    /// responsible for the memory previously managed by the `ThinBoxedSlice`.
233    /// In particular, the caller should properly destroy the `[T]` and release
234    /// the memory. The proper way to do so is to convert the raw pointer back
235    /// into a `Box` or a `ThinBoxedSlice`, with either the `Box::from_raw` or
236    /// `ThinBoxedSlice::from_raw` functions.
237    ///
238    /// # Examples
239    ///
240    /// ```
241    /// # use thin_slice::ThinBoxedSlice;
242    /// let x = vec![10, 20, 30].into_boxed_slice();
243    /// let x = ThinBoxedSlice::from(x);
244    /// let ptr = ThinBoxedSlice::into_raw(x);
245    /// ```
246    #[inline]
247    pub fn into_raw(b: ThinBoxedSlice<T>) -> *mut [T] {
248        Box::into_raw(b.data)
249    }
250
251    /// Consumes and leaks the `ThinBoxedSlice`, returning a mutable reference,
252    /// `&'a mut [T]`. Here, the lifetime `'a` may be chosen to be `'static`.
253    ///
254    /// Like `Box::leak`, this function is mainly useful for data that lives
255    /// for the remainder of the program's life. Dropping the returned
256    /// reference will cause a memory leak. If this is not acceptable, the
257    /// reference should first be wrapped with the `Box::from_raw` function
258    /// producing a `Box`, or with the `ThinBoxedSlice::from_raw` function
259    /// producing a `ThinBoxedSlice`. This value can then be dropped which will
260    /// properly destroy `[T]` and release the allocated memory.
261    ///
262    /// # Examples
263    ///
264    /// ```
265    /// # use thin_slice::ThinBoxedSlice;
266    /// fn main() {
267    ///     let x = ThinBoxedSlice::from(vec![1, 2, 3].into_boxed_slice());
268    ///     let static_ref = ThinBoxedSlice::leak(x);
269    ///     static_ref[0] = 4;
270    ///     assert_eq!(*static_ref, [4, 2, 3]);
271    /// }
272    /// ```
273    #[inline]
274    pub fn leak<'a>(b: ThinBoxedSlice<T>) -> &'a mut [T] where T: 'a {
275        Box::leak(b.data)
276    }
277
278    /// Returns a pointer to the heap allocation that stores the fat pointer
279    /// to the slice, if any. This is useful for systems that need to measure
280    /// memory allocation, but is otherwise an opaque pointer.
281    #[inline]
282    pub fn spilled_storage(&self) -> Option<*const ()> {
283        None
284    }
285}
286
287#[cfg(target_arch = "x86_64")]
288impl<T> Storage<T> {
289    #[inline]
290    fn from_data(data: NonNull<()>) -> Storage<T> {
291        let data = data.as_ptr() as usize;
292
293        let len = (data & TAG_MASK) >> TAG_SHIFT;
294        let mut ptr = data & PTR_MASK;
295
296        if (ptr & PTR_HIGH) == PTR_HIGH {
297            // Canonical linear addresses on x86_64 are sign extended from
298            // bit 48.
299            ptr |= TAG_MASK;
300        }
301
302        if len < TAG_LIMIT {
303            Storage::Inline(ptr as *mut T, len)
304        } else {
305            Storage::Spilled(ptr as *mut Box<[T]>)
306        }
307    }
308
309    #[inline]
310    fn into_data(self) -> NonNull<()> {
311        let data = match self {
312            Storage::Inline(ptr, len) => {
313                (len << TAG_SHIFT) | ((ptr as usize) & PTR_MASK)
314            }
315            Storage::Spilled(ptr) => {
316                TAG_MASK | ((ptr as usize) & PTR_MASK)
317            }
318        };
319        unsafe {
320            NonNull::new_unchecked(data as *mut _)
321        }
322    }
323}
324
325impl<T> From<Box<[T]>> for ThinBoxedSlice<T> {
326    fn from(value: Box<[T]>) -> ThinBoxedSlice<T> {
327        let ptr = Box::into_raw(value);
328        unsafe {
329            ThinBoxedSlice::from_raw(ptr)
330        }
331    }
332}
333
334impl<T> Into<Box<[T]>> for ThinBoxedSlice<T> {
335    fn into(self) -> Box<[T]> {
336        let ptr = ThinBoxedSlice::into_raw(self);
337        unsafe {
338            Box::from_raw(ptr)
339        }
340    }
341}
342
343unsafe impl<T: Send> Send for ThinBoxedSlice<T> {}
344unsafe impl<T: Sync> Sync for ThinBoxedSlice<T> {}
345
346#[cfg(target_arch = "x86_64")]
347impl<T> Drop for ThinBoxedSlice<T> {
348    fn drop(&mut self) {
349        let _ = Into::<Box<[T]>>::into(
350            ThinBoxedSlice {
351                data: self.data.clone(),
352                _phantom: PhantomData,
353            }
354        );
355    }
356}
357
358impl<T: Clone> Clone for ThinBoxedSlice<T> {
359    #[cfg(target_arch = "x86_64")]
360    fn clone(&self) -> Self {
361        unsafe {
362            match self.storage() {
363                Storage::Inline(ptr, len) => {
364                    slice::from_raw_parts_mut(ptr, len)
365                        .to_vec()
366                        .into_boxed_slice()
367                        .into()
368                }
369                Storage::Spilled(ptr) => {
370                    (*ptr).clone().into()
371                }
372            }
373        }
374    }
375
376    #[cfg(not(target_arch = "x86_64"))]
377    fn clone(&self) -> Self {
378        ThinBoxedSlice {
379            data: self.data.clone(),
380        }
381    }
382}
383
384impl<T> AsRef<[T]> for ThinBoxedSlice<T> {
385    fn as_ref(&self) -> &[T] {
386        &**self
387    }
388}
389
390impl<T> AsMut<[T]> for ThinBoxedSlice<T> {
391    fn as_mut(&mut self) -> &mut [T] {
392        &mut **self
393    }
394}
395
396impl<T> Deref for ThinBoxedSlice<T> {
397    type Target = [T];
398
399    #[cfg(target_arch = "x86_64")]
400    fn deref(&self) -> &[T] {
401        unsafe {
402            match self.storage() {
403                Storage::Inline(ptr, len) => {
404                    slice::from_raw_parts(ptr, len)
405                }
406                Storage::Spilled(ptr) => {
407                    &**ptr
408                }
409            }
410        }
411    }
412
413    #[cfg(not(target_arch = "x86_64"))]
414    fn deref(&self) -> &[T] {
415        &*self.data
416    }
417}
418
419impl<T> DerefMut for ThinBoxedSlice<T> {
420    #[cfg(target_arch = "x86_64")]
421    fn deref_mut(&mut self) -> &mut [T] {
422        unsafe {
423            match self.storage() {
424                Storage::Inline(ptr, len) => {
425                    slice::from_raw_parts_mut(ptr, len)
426                }
427                Storage::Spilled(ptr) => {
428                    &mut **ptr
429                }
430            }
431        }
432    }
433
434    #[cfg(not(target_arch = "x86_64"))]
435    fn deref_mut(&mut self) -> &mut [T] {
436        &mut *self.data
437    }
438}
439
440impl<T> Default for ThinBoxedSlice<T> {
441    fn default() -> Self {
442        Box::<[T]>::default().into()
443    }
444}
445
446impl<T: fmt::Debug> fmt::Debug for ThinBoxedSlice<T> {
447    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
448        fmt::Debug::fmt(&**self, f)
449    }
450}
451
452impl<T: Eq> Eq for ThinBoxedSlice<T> {}
453
454impl<T: Hash> Hash for ThinBoxedSlice<T> {
455    fn hash<H: Hasher>(&self, state: &mut H) {
456        (**self).hash(state);
457    }
458}
459
460impl<T: PartialEq> PartialEq for ThinBoxedSlice<T> {
461    #[inline]
462    fn eq(&self, other: &ThinBoxedSlice<T>) -> bool {
463        PartialEq::eq(&**self, &**other)
464    }
465    #[inline]
466    fn ne(&self, other: &ThinBoxedSlice<T>) -> bool {
467        PartialEq::ne(&**self, &**other)
468    }
469}
470
471impl<T: PartialOrd> PartialOrd for ThinBoxedSlice<T> {
472    #[inline]
473    fn partial_cmp(&self, other: &ThinBoxedSlice<T>) -> Option<Ordering> {
474        PartialOrd::partial_cmp(&**self, &**other)
475    }
476    #[inline]
477    fn lt(&self, other: &ThinBoxedSlice<T>) -> bool {
478        PartialOrd::lt(&**self, &**other)
479    }
480    #[inline]
481    fn le(&self, other: &ThinBoxedSlice<T>) -> bool {
482        PartialOrd::le(&**self, &**other)
483    }
484    #[inline]
485    fn ge(&self, other: &ThinBoxedSlice<T>) -> bool {
486        PartialOrd::ge(&**self, &**other)
487    }
488    #[inline]
489    fn gt(&self, other: &ThinBoxedSlice<T>) -> bool {
490        PartialOrd::gt(&**self, &**other)
491    }
492}
493
494impl<T> fmt::Pointer for ThinBoxedSlice<T> {
495    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
496        let ptr = &**self;
497        fmt::Pointer::fmt(&ptr, f)
498    }
499}
500
501#[cfg(target_arch = "x86_64")]
502#[test]
503fn test_spilled_storage() {
504    let x = ThinBoxedSlice::from(vec![0; TAG_LIMIT - 1].into_boxed_slice());
505    assert!(x.spilled_storage().is_none());
506
507    let x = ThinBoxedSlice::from(vec![0; TAG_LIMIT].into_boxed_slice());
508    assert!(x.spilled_storage().is_some());
509}
510
511#[cfg(target_arch = "x86_64")]
512#[test]
513fn test_from_raw_large() {
514    let mut vec = vec![0; TAG_LIMIT];
515    vec[123] = 456;
516
517    let ptr = Box::into_raw(vec.into_boxed_slice());
518    let x = unsafe { ThinBoxedSlice::from_raw(ptr) };
519    assert_eq!(x[123], 456);
520}
521
522#[cfg(target_arch = "x86_64")]
523#[test]
524fn test_into_raw_large() {
525    let mut vec = vec![0; TAG_LIMIT];
526    vec[123] = 456;
527
528    let x = ThinBoxedSlice::from(vec.into_boxed_slice());
529    let ptr = ThinBoxedSlice::into_raw(x);
530    let y = unsafe { Box::from_raw(ptr) };
531    assert_eq!(y[123], 456);
532}
533
534#[cfg(target_arch = "x86_64")]
535#[test]
536fn test_leak_large() {
537    let mut vec = vec![0; TAG_LIMIT];
538    vec[123] = 456;
539
540    let x = ThinBoxedSlice::from(vec.into_boxed_slice());
541    let static_ref = ThinBoxedSlice::leak(x);
542    static_ref[123] *= 1000;
543    assert_eq!(static_ref[123], 456000);
544}