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}