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        #[cfg(not(all(target_endian = "big", target_pointer_width = "64")))]
91        {
92            self.0.boxed.ptr as *mut _
93        }
94        #[cfg(all(target_endian = "big", target_pointer_width = "64"))]
95        {
96            self.0.boxed.ptr.swap_bytes() as *mut _
97        }
98    }
99
100    /// Returns the unpacked value, mutably.
101    pub fn unpack_mut(&mut self) -> UnpackedMut<'_, T, N, B> {
102        unsafe {
103            if self.is_boxed() {
104                UnpackedMut::Boxed(&mut *self.boxed_ptr())
105            } else {
106                UnpackedMut::Inline(&mut self.0.inl.numeric_tag, &mut self.0.inl.value)
107            }
108        }
109    }
110
111    /// Returns the unpacked value.
112    pub fn unpack(&self) -> Unpacked<'_, T, N, B> {
113        unsafe {
114            if self.is_boxed() {
115                Unpacked::Boxed(&*self.boxed_ptr())
116            } else {
117                Unpacked::Inline(self.0.inl.numeric_tag, self.0.inl.value)
118            }
119        }
120    }
121
122    /// Returns the extracted value.
123    pub fn extract(self) -> Extracted<T, N, B> {
124        let extracted = unsafe {
125            if self.is_boxed() {
126                Extracted::Boxed(Box::from_raw(self.boxed_ptr()))
127            } else {
128                Extracted::Inline(self.0.inl.numeric_tag, self.0.inl.value)
129            }
130        };
131        mem::forget(self);
132        extracted
133    }
134
135    /// Constructs an inline value.
136    pub fn inline(numeric_tag: T, value: N) -> Self {
137        Self(NumericUnionImpl {
138            inl: InlineVariant {
139                tag: NUMERIC_UNION_TAG_INLINE,
140                numeric_tag,
141                value,
142            },
143        })
144    }
145
146    /// Constructs a boxed value.
147    pub fn boxed(v: Box<B>) -> Self {
148        let ptr = Box::into_raw(v);
149
150        #[cfg(target_pointer_width = "32")]
151        let boxed = BoxedVariant {
152            tag: 0,
153            ptr,
154            _phantom: marker::PhantomData,
155        };
156
157        #[cfg(target_pointer_width = "64")]
158        let boxed = BoxedVariant {
159            #[cfg(target_endian = "little")]
160            ptr: ptr as usize,
161            #[cfg(target_endian = "big")]
162            ptr: (ptr as usize).swap_bytes(),
163            _phantom: marker::PhantomData,
164        };
165
166        let union = Self(NumericUnionImpl { boxed });
167        debug_assert!(union.is_boxed());
168        union
169    }
170}
171
172impl<T: Copy, N: Copy, B> Drop for NumericUnion<T, N, B> {
173    fn drop(&mut self) {
174        if self.is_boxed() {
175            let _ = unsafe { Box::from_raw(self.boxed_ptr()) };
176        }
177    }
178}
179
180impl<T: Copy, N: Copy, B: Clone> Clone for NumericUnion<T, N, B> {
181    fn clone(&self) -> Self {
182        match self.unpack() {
183            Unpacked::Inline(t, n) => Self::inline(t, n),
184            Unpacked::Boxed(b) => Self::boxed(Box::new(b.clone())),
185        }
186    }
187}
188
189impl<T: Copy, N: Copy, B> MallocSizeOf for NumericUnion<T, N, B> {
190    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
191        match self.unpack() {
192            Unpacked::Boxed(c) => unsafe { ops.malloc_size_of(c) },
193            Unpacked::Inline(..) => 0,
194        }
195    }
196}
197
198impl<T: Copy + ToShmem, N: Copy + ToShmem, B: ToShmem> ToShmem for NumericUnion<T, N, B> {
199    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
200        unsafe {
201            Ok(mem::ManuallyDrop::new(if self.is_inline() {
202                let inl = self.0.inl;
203                Self(NumericUnionImpl { inl })
204            } else {
205                let b = mem::ManuallyDrop::new(Box::from_raw(self.boxed_ptr()));
206                let b = (*b).to_shmem(builder)?;
207                Self::boxed(mem::ManuallyDrop::into_inner(b))
208            }))
209        }
210    }
211}
212
213impl<T: Copy + fmt::Debug, N: Copy + fmt::Debug, B: fmt::Debug> fmt::Debug
214    for NumericUnion<T, N, B>
215{
216    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
217        self.unpack().fmt(formatter)
218    }
219}
220
221impl<T: Copy + PartialEq, N: Copy + PartialEq, B: PartialEq> PartialEq for NumericUnion<T, N, B> {
222    fn eq(&self, other: &Self) -> bool {
223        self.unpack() == other.unpack()
224    }
225}
226
227/// Returns the data in a safe way.
228#[derive(Clone, Debug, PartialEq)]
229pub enum Unpacked<'a, T, N, B> {
230    /// A boxed value.
231    Boxed(&'a B),
232    /// An inline value
233    Inline(T, N),
234}
235
236/// Returns the extracted data in a safe way.
237#[derive(Clone, Debug, PartialEq)]
238pub enum Extracted<T, N, B> {
239    /// A boxed value.
240    Boxed(Box<B>),
241    /// An inline value
242    Inline(T, N),
243}
244
245/// As above, but mutable.
246pub enum UnpackedMut<'a, T, N, B> {
247    /// A boxed value
248    Boxed(&'a mut B),
249    /// An inline value
250    Inline(&'a mut T, &'a mut N),
251}