Skip to main content

style/values/
tagged_numeric.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 https://mozilla.org/MPL/2.0/. */
4
5//! Generic helper to support a tagged numeric value of 4 bytes and a boxed pointer in the same
6//! pointer value in 64-bit builds.
7//!
8//! The over-all design is a tagged pointer, with the low bit of the pointer being non-zero if it is
9//! a non-boxed value. We need to pass the numeric type and tag as separate parameters to make sure
10//! that they pack along the tag that we use internally in InlineVariant.
11
12use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
13use std::{fmt, marker, mem};
14use to_shmem::{SharedMemoryBuilder, ToShmem};
15
16// NOTE(emilio): cbindgen only understands the #[cfg] on the top level definition.
17#[doc(hidden)]
18#[repr(C)]
19#[cfg(target_pointer_width = "32")]
20pub struct BoxedVariant<B> {
21    tag: u8,
22    ptr: *mut B,
23    _phantom: marker::PhantomData<B>,
24}
25
26#[doc(hidden)]
27#[repr(C)]
28#[cfg(target_pointer_width = "64")]
29pub struct BoxedVariant<B> {
30    ptr: usize, // In little-endian byte order
31    _phantom: marker::PhantomData<B>,
32}
33
34impl<B> Copy for BoxedVariant<B> {}
35impl<B> Clone for BoxedVariant<B> {
36    fn clone(&self) -> Self {
37        *self
38    }
39}
40
41unsafe impl<B: Send> Send for BoxedVariant<B> {}
42unsafe impl<B: Sync> Sync for BoxedVariant<B> {}
43
44#[doc(hidden)]
45#[derive(Clone, Copy)]
46#[repr(C)]
47pub struct TagVariant {
48    tag: u8,
49}
50
51#[doc(hidden)]
52#[derive(Clone, Copy)]
53#[repr(C)]
54pub struct InlineVariant<T, N> {
55    tag: u8,
56    numeric_tag: T,
57    value: N,
58}
59
60#[doc(hidden)]
61#[repr(C)]
62pub union NumericUnionImpl<T: Copy, N: Copy, B> {
63    inl: InlineVariant<T, N>,
64    boxed: BoxedVariant<B>,
65    tag: TagVariant,
66}
67
68/// cbindgen:derive-eq=false
69/// cbindgen:derive-neq=false
70#[repr(C)]
71pub struct NumericUnion<T: Copy, N: Copy, B>(NumericUnionImpl<T, N, B>);
72
73#[doc(hidden)] // Need to be public so that cbindgen generates it.
74pub const NUMERIC_UNION_TAG_INLINE: u8 = 0b1;
75
76impl<T: Copy, N: Copy, B> NumericUnion<T, N, B> {
77    /// Whether we hold an inline value.
78    pub fn is_inline(&self) -> bool {
79        unsafe { (self.0.tag.tag & NUMERIC_UNION_TAG_INLINE) != 0 }
80    }
81
82    /// Whether we hold a boxed value.
83    pub fn is_boxed(&self) -> bool {
84        !self.is_inline()
85    }
86
87    #[inline]
88    unsafe fn boxed_ptr(&self) -> *mut B {
89        debug_assert!(self.is_boxed());
90        unsafe {
91            #[cfg(not(all(target_endian = "big", target_pointer_width = "64")))]
92            {
93                self.0.boxed.ptr as *mut _
94            }
95            #[cfg(all(target_endian = "big", target_pointer_width = "64"))]
96            {
97                self.0.boxed.ptr.swap_bytes() as *mut _
98            }
99        }
100    }
101
102    /// Returns the unpacked value, mutably.
103    pub fn unpack_mut(&mut self) -> UnpackedMut<'_, T, N, B> {
104        unsafe {
105            if self.is_boxed() {
106                UnpackedMut::Boxed(&mut *self.boxed_ptr())
107            } else {
108                UnpackedMut::Inline(&mut self.0.inl.numeric_tag, &mut self.0.inl.value)
109            }
110        }
111    }
112
113    /// Returns the unpacked value.
114    pub fn unpack(&self) -> Unpacked<'_, T, N, B> {
115        unsafe {
116            if self.is_boxed() {
117                Unpacked::Boxed(&*self.boxed_ptr())
118            } else {
119                Unpacked::Inline(self.0.inl.numeric_tag, self.0.inl.value)
120            }
121        }
122    }
123
124    /// Returns the extracted value.
125    pub fn extract(self) -> Extracted<T, N, B> {
126        let extracted = unsafe {
127            if self.is_boxed() {
128                Extracted::Boxed(Box::from_raw(self.boxed_ptr()))
129            } else {
130                Extracted::Inline(self.0.inl.numeric_tag, self.0.inl.value)
131            }
132        };
133        mem::forget(self);
134        extracted
135    }
136
137    /// Constructs an inline value.
138    pub fn inline(numeric_tag: T, value: N) -> Self {
139        Self(NumericUnionImpl {
140            inl: InlineVariant {
141                tag: NUMERIC_UNION_TAG_INLINE,
142                numeric_tag,
143                value,
144            },
145        })
146    }
147
148    /// Constructs a boxed value.
149    pub fn boxed(v: Box<B>) -> Self {
150        let ptr = Box::into_raw(v);
151
152        #[cfg(target_pointer_width = "32")]
153        let boxed = BoxedVariant {
154            tag: 0,
155            ptr,
156            _phantom: marker::PhantomData,
157        };
158
159        #[cfg(target_pointer_width = "64")]
160        let boxed = BoxedVariant {
161            #[cfg(target_endian = "little")]
162            ptr: ptr as usize,
163            #[cfg(target_endian = "big")]
164            ptr: (ptr as usize).swap_bytes(),
165            _phantom: marker::PhantomData,
166        };
167
168        let union = Self(NumericUnionImpl { boxed });
169        debug_assert!(union.is_boxed());
170        union
171    }
172}
173
174impl<T: Copy, N: Copy, B> Drop for NumericUnion<T, N, B> {
175    fn drop(&mut self) {
176        if self.is_boxed() {
177            let _ = unsafe { Box::from_raw(self.boxed_ptr()) };
178        }
179    }
180}
181
182impl<T: Copy, N: Copy, B: Clone> Clone for NumericUnion<T, N, B> {
183    fn clone(&self) -> Self {
184        match self.unpack() {
185            Unpacked::Inline(t, n) => Self::inline(t, n),
186            Unpacked::Boxed(b) => Self::boxed(Box::new(b.clone())),
187        }
188    }
189}
190
191impl<T: Copy, N: Copy, B> MallocSizeOf for NumericUnion<T, N, B> {
192    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
193        match self.unpack() {
194            Unpacked::Boxed(c) => unsafe { ops.malloc_size_of(c) },
195            Unpacked::Inline(..) => 0,
196        }
197    }
198}
199
200impl<T: Copy + ToShmem, N: Copy + ToShmem, B: ToShmem> ToShmem for NumericUnion<T, N, B> {
201    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
202        unsafe {
203            Ok(mem::ManuallyDrop::new(if self.is_inline() {
204                let inl = self.0.inl;
205                Self(NumericUnionImpl { inl })
206            } else {
207                let b = mem::ManuallyDrop::new(Box::from_raw(self.boxed_ptr()));
208                let b = (*b).to_shmem(builder)?;
209                Self::boxed(mem::ManuallyDrop::into_inner(b))
210            }))
211        }
212    }
213}
214
215impl<T: Copy + fmt::Debug, N: Copy + fmt::Debug, B: fmt::Debug> fmt::Debug
216    for NumericUnion<T, N, B>
217{
218    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
219        self.unpack().fmt(formatter)
220    }
221}
222
223impl<T: Copy + PartialEq, N: Copy + PartialEq, B: PartialEq> PartialEq for NumericUnion<T, N, B> {
224    fn eq(&self, other: &Self) -> bool {
225        self.unpack() == other.unpack()
226    }
227}
228
229/// Returns the data in a safe way.
230#[derive(Clone, Debug, PartialEq)]
231pub enum Unpacked<'a, T, N, B> {
232    /// A boxed value.
233    Boxed(&'a B),
234    /// An inline value
235    Inline(T, N),
236}
237
238/// Returns the extracted data in a safe way.
239#[derive(Clone, Debug, PartialEq)]
240pub enum Extracted<T, N, B> {
241    /// A boxed value.
242    Boxed(Box<B>),
243    /// An inline value
244    Inline(T, N),
245}
246
247/// As above, but mutable.
248pub enum UnpackedMut<'a, T, N, B> {
249    /// A boxed value
250    Boxed(&'a mut B),
251    /// An inline value
252    Inline(&'a mut T, &'a mut N),
253}