Skip to main content

to_shmem/
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 https://mozilla.org/MPL/2.0/. */
4
5//! Trait for cloning data into a shared memory buffer.
6//!
7//! This module contains the SharedMemoryBuilder type and ToShmem trait.
8//!
9//! We put them here (and not in style_traits) so that we can derive ToShmem
10//! from the selectors and style crates.
11
12#![crate_name = "to_shmem"]
13#![crate_type = "rlib"]
14
15use hashbrown::{HashMap, HashSet};
16use std::alloc::Layout;
17use std::ffi::CString;
18use std::marker::PhantomData;
19use std::mem::{self, ManuallyDrop};
20use std::num::Wrapping;
21use std::ops::Range;
22use std::os::raw::c_char;
23use std::ptr::{self, NonNull};
24use std::slice;
25use std::str;
26
27/// Result type for ToShmem::to_shmem.
28///
29/// The String is an error message describing why the call failed.
30pub type Result<T> = std::result::Result<ManuallyDrop<T>, String>;
31
32// Various pointer arithmetic functions in this file can be replaced with
33// functions on `Layout` once they have stabilized:
34//
35// https://github.com/rust-lang/rust/issues/55724
36
37/// A builder object that transforms and copies values into a fixed size buffer.
38pub struct SharedMemoryBuilder {
39    /// The buffer into which values will be copied.
40    buffer: *mut u8,
41    /// The size of the buffer.
42    capacity: usize,
43    /// The current position in the buffer, where the next value will be written
44    /// at.
45    index: usize,
46    /// Pointers to every shareable value that we store in the shared memory
47    /// buffer.  We use this to assert against encountering the same value
48    /// twice, e.g. through another Arc reference, so that we don't
49    /// inadvertently store duplicate copies of values.
50    #[cfg(all(debug_assertions, feature = "servo_arc"))]
51    shared_values: HashSet<*const std::os::raw::c_void>,
52}
53
54/// Amount of padding needed after `size` bytes to ensure that the following
55/// address will satisfy `align`.
56fn padding_needed_for(size: usize, align: usize) -> usize {
57    padded_size(size, align).wrapping_sub(size)
58}
59
60/// Rounds up `size` so that the following address will satisfy `align`.
61fn padded_size(size: usize, align: usize) -> usize {
62    size.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1)
63}
64
65impl SharedMemoryBuilder {
66    /// Creates a new SharedMemoryBuilder using the specified buffer.
67    ///
68    /// # Safety
69    ///
70    /// `buffer` must be a pointer with at least `capacity` bytes.
71    pub unsafe fn new(buffer: *mut u8, capacity: usize) -> Self {
72        Self {
73            buffer,
74            capacity,
75            index: 0,
76            #[cfg(all(debug_assertions, feature = "servo_arc"))]
77            shared_values: HashSet::new(),
78        }
79    }
80
81    /// Returns the number of bytes currently used in the buffer.
82    #[inline]
83    pub fn len(&self) -> usize {
84        self.index
85    }
86
87    /// Returns whether the buffer is empty
88    #[inline]
89    pub fn is_empty(&self) -> bool {
90        self.index == 0
91    }
92
93    /// Writes a value into the shared memory buffer and returns a pointer to
94    /// it in the buffer.
95    ///
96    /// The value is cloned and converted into a form suitable for placing into
97    /// a shared memory buffer by calling ToShmem::to_shmem on it.
98    ///
99    /// Panics if there is insufficient space in the buffer.
100    pub fn write<T: ToShmem>(&mut self, value: &T) -> std::result::Result<*mut T, String> {
101        // Reserve space for the value.
102        let dest: *mut T = self.alloc_value();
103
104        // Make a clone of the value with all of its heap allocations
105        // placed in the shared memory buffer.
106        let value = value.to_shmem(self)?;
107
108        unsafe {
109            // Copy the value into the buffer.
110            ptr::write(dest, ManuallyDrop::into_inner(value));
111        }
112
113        // Return a pointer to the shared value.
114        Ok(dest)
115    }
116
117    /// Reserves space in the shared memory buffer to fit a value of type T,
118    /// and returns a pointer to that reserved space.
119    ///
120    /// Panics if there is insufficient space in the buffer.
121    pub fn alloc_value<T>(&mut self) -> *mut T {
122        self.alloc(Layout::new::<T>())
123    }
124
125    /// Reserves space in the shared memory buffer to fit an array of values of
126    /// type T, and returns a pointer to that reserved space.
127    ///
128    /// Panics if there is insufficient space in the buffer.
129    pub fn alloc_array<T>(&mut self, len: usize) -> *mut T {
130        if len == 0 {
131            return NonNull::dangling().as_ptr();
132        }
133
134        let size = mem::size_of::<T>();
135        let align = mem::align_of::<T>();
136
137        self.alloc(Layout::from_size_align(padded_size(size, align) * len, align).unwrap())
138    }
139
140    /// Reserves space in the shared memory buffer that conforms to the
141    /// specified layout, and returns a pointer to that reserved space.
142    ///
143    /// Panics if there is insufficient space in the buffer.
144    pub fn alloc<T>(&mut self, layout: Layout) -> *mut T {
145        // Amount of padding to align the value.
146        //
147        // The addition can't overflow, since self.index <= self.capacity, and
148        // for us to have successfully allocated the buffer, `buffer + capacity`
149        // can't overflow.
150        let padding = padding_needed_for(self.buffer as usize + self.index, layout.align());
151
152        // Reserve space for the padding.
153        let start = self.index.checked_add(padding).unwrap();
154        assert!(start <= isize::MAX as usize); // for the cast below
155
156        // Reserve space for the value.
157        let end = start.checked_add(layout.size()).unwrap();
158        assert!(end <= self.capacity);
159
160        self.index = end;
161        unsafe { self.buffer.add(start) as *mut T }
162    }
163}
164
165/// A type that can be copied into a SharedMemoryBuilder.
166pub trait ToShmem: Sized {
167    /// Clones this value into a form suitable for writing into a
168    /// SharedMemoryBuilder.
169    ///
170    /// If this value owns any heap allocations, they should be written into
171    /// `builder` so that the return value of this function can point to the
172    /// copy in the shared memory buffer.
173    ///
174    /// The return type is wrapped in ManuallyDrop to make it harder to
175    /// accidentally invoke the destructor of the value that is produced.
176    ///
177    /// Returns a Result so that we can gracefully recover from unexpected
178    /// content.
179    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>;
180}
181
182#[macro_export]
183macro_rules! impl_trivial_to_shmem {
184    ($($ty:ty),*) => {
185        $(
186            impl $crate::ToShmem for $ty {
187                fn to_shmem(
188                    &self,
189                    _builder: &mut $crate::SharedMemoryBuilder,
190                ) -> $crate::Result<Self> {
191                    $crate::Result::Ok(::std::mem::ManuallyDrop::new(*self))
192                }
193            }
194        )*
195    };
196}
197
198impl_trivial_to_shmem!(
199    (),
200    bool,
201    f32,
202    f64,
203    i8,
204    i16,
205    i32,
206    i64,
207    u8,
208    u16,
209    u32,
210    u64,
211    isize,
212    usize,
213    std::num::NonZeroUsize
214);
215
216impl<T> ToShmem for PhantomData<T> {
217    fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self> {
218        Ok(ManuallyDrop::new(*self))
219    }
220}
221
222impl<T: ToShmem> ToShmem for Range<T> {
223    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
224        Ok(ManuallyDrop::new(Range {
225            start: ManuallyDrop::into_inner(self.start.to_shmem(builder)?),
226            end: ManuallyDrop::into_inner(self.end.to_shmem(builder)?),
227        }))
228    }
229}
230
231impl<T: ToShmem, U: ToShmem> ToShmem for (T, U) {
232    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
233        Ok(ManuallyDrop::new((
234            ManuallyDrop::into_inner(self.0.to_shmem(builder)?),
235            ManuallyDrop::into_inner(self.1.to_shmem(builder)?),
236        )))
237    }
238}
239
240impl<T: ToShmem> ToShmem for Wrapping<T> {
241    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
242        Ok(ManuallyDrop::new(Wrapping(ManuallyDrop::into_inner(
243            self.0.to_shmem(builder)?,
244        ))))
245    }
246}
247
248impl<T: ToShmem> ToShmem for Box<T> {
249    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
250        // Reserve space for the boxed value.
251        let dest: *mut T = builder.alloc_value();
252
253        // Make a clone of the boxed value with all of its heap allocations
254        // placed in the shared memory buffer.
255        let value = (**self).to_shmem(builder)?;
256
257        unsafe {
258            // Copy the value into the buffer.
259            ptr::write(dest, ManuallyDrop::into_inner(value));
260
261            Ok(ManuallyDrop::new(Box::from_raw(dest)))
262        }
263    }
264}
265
266/// Converts all the items in `src` into shared memory form, writes them into the specified buffer,
267/// and returns a pointer to the slice.
268///
269/// # Safety
270///
271/// Dest must have enough space for all elements in `src`.
272unsafe fn to_shmem_slice_ptr<'a, T>(
273    src: impl ExactSizeIterator<Item = &'a T>,
274    dest: *mut T,
275    builder: &mut SharedMemoryBuilder,
276) -> std::result::Result<*mut [T], String>
277where
278    T: ToShmem + 'a,
279{
280    unsafe {
281        let dest = slice::from_raw_parts_mut(dest, src.len());
282
283        // Make a clone of each element from the iterator with its own heap
284        // allocations placed in the buffer, and copy that clone into the buffer.
285        for (src, dest) in src.zip(dest.iter_mut()) {
286            ptr::write(dest, ManuallyDrop::into_inner(src.to_shmem(builder)?));
287        }
288
289        Ok(dest)
290    }
291}
292
293/// Writes all the items in `src` into a slice in the shared memory buffer and returns a pointer to
294/// the slice.
295///
296/// # Safety
297///
298/// ExactSizeIterator must not lie about its length. TODO(emilio): Use TrustedLen eventually.
299pub unsafe fn to_shmem_slice<'a, T>(
300    src: impl ExactSizeIterator<Item = &'a T>,
301    builder: &mut SharedMemoryBuilder,
302) -> std::result::Result<*mut [T], String>
303where
304    T: ToShmem + 'a,
305{
306    let dest = builder.alloc_array(src.len());
307    unsafe { to_shmem_slice_ptr(src, dest, builder) }
308}
309
310impl<T: ToShmem> ToShmem for Box<[T]> {
311    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
312        unsafe {
313            let dest = to_shmem_slice(self.iter(), builder)?;
314            Ok(ManuallyDrop::new(Box::from_raw(dest)))
315        }
316    }
317}
318
319impl ToShmem for Box<str> {
320    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
321        // Reserve space for the string bytes.
322        let dest: *mut u8 = builder.alloc_array(self.len());
323
324        unsafe {
325            // Copy the value into the buffer.
326            ptr::copy(self.as_ptr(), dest, self.len());
327
328            Ok(ManuallyDrop::new(Box::from_raw(
329                str::from_utf8_unchecked_mut(slice::from_raw_parts_mut(dest, self.len())),
330            )))
331        }
332    }
333}
334
335impl ToShmem for String {
336    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
337        // Reserve space for the string bytes.
338        let dest: *mut u8 = builder.alloc_array(self.len());
339
340        unsafe {
341            // Copy the value into the buffer.
342            ptr::copy(self.as_ptr(), dest, self.len());
343
344            Ok(ManuallyDrop::new(String::from_raw_parts(
345                dest,
346                self.len(),
347                self.len(),
348            )))
349        }
350    }
351}
352
353impl ToShmem for CString {
354    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
355        let len = self.as_bytes_with_nul().len();
356
357        // Reserve space for the string bytes.
358        let dest: *mut c_char = builder.alloc_array(len);
359
360        unsafe {
361            // Copy the value into the buffer.
362            ptr::copy(self.as_ptr(), dest, len);
363
364            Ok(ManuallyDrop::new(CString::from_raw(dest)))
365        }
366    }
367}
368
369impl<T: ToShmem> ToShmem for Vec<T> {
370    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
371        unsafe {
372            let dest = to_shmem_slice(self.iter(), builder)? as *mut T;
373            let dest_vec = Vec::from_raw_parts(dest, self.len(), self.len());
374            Ok(ManuallyDrop::new(dest_vec))
375        }
376    }
377}
378
379impl<T: ToShmem, S> ToShmem for HashSet<T, S>
380where
381    Self: Default,
382{
383    fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self> {
384        if !self.is_empty() {
385            return Err("ToShmem failed for HashSet: We only support empty sets \
386                 (we don't expect custom properties in UA sheets, they're observable by content)"
387                .to_string());
388        }
389        Ok(ManuallyDrop::new(Self::default()))
390    }
391}
392
393impl<K: ToShmem, V: ToShmem, S> ToShmem for HashMap<K, V, S>
394where
395    Self: Default,
396{
397    fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self> {
398        if !self.is_empty() {
399            return Err("ToShmem failed for HashMap: We only support empty maps".to_string());
400        }
401        Ok(ManuallyDrop::new(Self::default()))
402    }
403}
404
405impl<T: ToShmem> ToShmem for Option<T> {
406    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
407        let v = match self {
408            Some(v) => Some(ManuallyDrop::into_inner(v.to_shmem(builder)?)),
409            None => None,
410        };
411
412        Ok(ManuallyDrop::new(v))
413    }
414}
415
416#[cfg(feature = "smallvec")]
417impl<T: ToShmem, A: smallvec::Array<Item = T>> ToShmem for smallvec::SmallVec<A> {
418    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
419        let dest_vec = unsafe {
420            if self.spilled() {
421                // Place the items in a separate allocation in the shared memory
422                // buffer.
423                let dest = to_shmem_slice(self.iter(), builder)? as *mut T;
424                Self::from_raw_parts(dest, self.len(), self.len())
425            } else {
426                // Place the items inline.
427                let mut s = Self::new();
428                to_shmem_slice_ptr(self.iter(), s.as_mut_ptr(), builder)?;
429                s.set_len(self.len());
430                s
431            }
432        };
433
434        Ok(ManuallyDrop::new(dest_vec))
435    }
436}
437
438#[cfg(feature = "servo_arc")]
439impl<A: 'static, B: 'static> ToShmem for servo_arc::ArcUnion<A, B>
440where
441    servo_arc::Arc<A>: ToShmem,
442    servo_arc::Arc<B>: ToShmem,
443{
444    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
445        use servo_arc::ArcUnionBorrow;
446
447        Ok(ManuallyDrop::new(match self.borrow() {
448            ArcUnionBorrow::First(first) => Self::from_first(ManuallyDrop::into_inner(
449                first.with_arc(|a| a.to_shmem(builder))?,
450            )),
451            ArcUnionBorrow::Second(second) => Self::from_second(ManuallyDrop::into_inner(
452                second.with_arc(|a| a.to_shmem(builder))?,
453            )),
454        }))
455    }
456}
457#[cfg(feature = "servo_arc")]
458impl<T: ToShmem> ToShmem for servo_arc::Arc<T> {
459    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
460        // Assert that we don't encounter any shared references to values we
461        // don't expect.
462        #[cfg(debug_assertions)]
463        assert!(
464            !builder.shared_values.contains(&self.heap_ptr()),
465            "ToShmem failed for Arc<{}>: encountered a value with multiple \
466            references.",
467            std::any::type_name::<T>()
468        );
469
470        // Make a clone of the Arc-owned value with all of its heap allocations
471        // placed in the shared memory buffer.
472        let value = (**self).to_shmem(builder)?;
473
474        // Create a new Arc with the shared value and have it place its
475        // ArcInner in the shared memory buffer.
476        unsafe {
477            let static_arc = Self::new_static(
478                |layout| builder.alloc(layout),
479                ManuallyDrop::into_inner(value),
480            );
481
482            #[cfg(debug_assertions)]
483            builder.shared_values.insert(self.heap_ptr());
484
485            Ok(ManuallyDrop::new(static_arc))
486        }
487    }
488}
489#[cfg(feature = "servo_arc")]
490impl<H: ToShmem, T: ToShmem> ToShmem for servo_arc::Arc<servo_arc::HeaderSlice<H, T>> {
491    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
492        // We don't currently have any shared ThinArc values in stylesheets,
493        // so don't support them for now.
494        #[cfg(debug_assertions)]
495        assert!(
496            !builder.shared_values.contains(&self.heap_ptr()),
497            "ToShmem failed for ThinArc<T>: encountered a value with multiple references, which \
498             is not currently supported",
499        );
500
501        // Make a clone of the Arc-owned header and slice values with all of
502        // their heap allocations placed in the shared memory buffer.
503        let header = self.header.to_shmem(builder)?;
504        let mut values = Vec::with_capacity(self.len());
505        for v in self.slice().iter() {
506            values.push(v.to_shmem(builder)?);
507        }
508
509        // Create a new ThinArc with the shared value and have it place
510        // its ArcInner in the shared memory buffer.
511        let len = values.len();
512        let static_arc = Self::from_header_and_iter_alloc(
513            |layout| builder.alloc(layout),
514            ManuallyDrop::into_inner(header),
515            values.into_iter().map(ManuallyDrop::into_inner),
516            len,
517            /* is_static = */ true,
518        );
519
520        #[cfg(debug_assertions)]
521        builder.shared_values.insert(self.heap_ptr());
522
523        Ok(ManuallyDrop::new(static_arc))
524    }
525}
526
527#[cfg(feature = "thin-vec")]
528impl<T: ToShmem> ToShmem for thin_vec::ThinVec<T> {
529    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
530        assert_eq!(mem::size_of::<Self>(), mem::size_of::<*const ()>());
531
532        // NOTE: We need to do the work of allocating the header in shared memory even if the
533        // length is zero, because an empty ThinVec, even though it doesn't allocate, references
534        // static memory which will not be mapped to other processes, see bug 1841011.
535        let len = self.len();
536
537        // nsTArrayHeader size.
538        // FIXME: Would be nice not to hard-code this, but in practice thin-vec crate also relies
539        // on this.
540        let header_size = 2 * mem::size_of::<u32>();
541        let header_align = mem::size_of::<u32>();
542
543        let item_size = mem::size_of::<T>();
544        let item_align = mem::align_of::<T>();
545
546        // We don't need to support underalignment for now, this could be supported if needed.
547        assert!(item_align >= header_align);
548
549        // This is explicitly unsupported by ThinVec, see:
550        // https://searchfox.org/mozilla-central/rev/ad732108b073742d7324f998c085f459674a6846/third_party/rust/thin-vec/src/lib.rs#375-386
551        assert!(item_align <= header_size);
552        let header_padding = 0;
553
554        let layout = Layout::from_size_align(
555            header_size + header_padding + padded_size(item_size, item_align) * len,
556            item_align,
557        )
558        .unwrap();
559
560        let shmem_header_ptr = builder.alloc::<u8>(layout);
561        let shmem_data_ptr = unsafe { shmem_header_ptr.add(header_size + header_padding) };
562
563        let data_ptr = self.as_ptr() as *const u8;
564        let header_ptr = unsafe { data_ptr.sub(header_size + header_padding) };
565
566        unsafe {
567            // Copy the header. Note this might copy a wrong capacity, but it doesn't matter,
568            // because shared memory ptrs are immutable anyways, and we can't relocate.
569            ptr::copy(header_ptr, shmem_header_ptr, header_size);
570            // ToShmem + copy the contents into the shared buffer.
571            to_shmem_slice_ptr(self.iter(), shmem_data_ptr as *mut T, builder)?;
572            // Return the new ThinVec, which is just a pointer to the shared memory buffer.
573            let shmem_thinvec: Self = mem::transmute(shmem_header_ptr);
574
575            // Sanity-check that the ptr and length match.
576            debug_assert_eq!(shmem_thinvec.as_ptr(), shmem_data_ptr as *const T);
577            debug_assert_eq!(shmem_thinvec.len(), len);
578
579            Ok(ManuallyDrop::new(shmem_thinvec))
580        }
581    }
582}
583
584#[cfg(feature = "smallbitvec")]
585impl ToShmem for smallbitvec::SmallBitVec {
586    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
587        use smallbitvec::InternalStorage;
588
589        let storage = match self.clone().into_storage() {
590            InternalStorage::Spilled(vs) => {
591                // Reserve space for the boxed slice values.
592                let len = vs.len();
593                let dest: *mut usize = builder.alloc_array(len);
594
595                unsafe {
596                    // Copy the value into the buffer.
597                    let src = vs.as_ptr();
598                    ptr::copy(src, dest, len);
599
600                    let dest_slice = Box::from_raw(std::ptr::slice_from_raw_parts_mut(dest, len));
601                    InternalStorage::Spilled(dest_slice)
602                }
603            },
604            InternalStorage::Inline(x) => InternalStorage::Inline(x),
605        };
606        Ok(ManuallyDrop::new(unsafe { Self::from_storage(storage) }))
607    }
608}
609
610#[cfg(feature = "string_cache")]
611impl<Static: string_cache::StaticAtomSet> ToShmem for string_cache::Atom<Static> {
612    fn to_shmem(&self, _: &mut SharedMemoryBuilder) -> Result<Self> {
613        // NOTE(emilio): In practice, this can be implemented trivially if
614        // string_cache could expose the implementation detail of static atoms
615        // being an index into the static table (and panicking in the
616        // non-static, non-inline cases).
617        unimplemented!(
618            "If servo wants to share stylesheets across processes, \
619             then ToShmem for Atom needs to be implemented"
620        )
621    }
622}
623
624impl ToShmem for std::sync::atomic::AtomicBool {
625    fn to_shmem(&self, _: &mut SharedMemoryBuilder) -> Result<Self> {
626        use std::sync::atomic::Ordering;
627        Ok(ManuallyDrop::new(Self::new(self.load(Ordering::Relaxed))))
628    }
629}
630
631#[cfg(feature = "cssparser")]
632impl_trivial_to_shmem!(
633    cssparser::SourceLocation,
634    cssparser::SourcePosition,
635    cssparser::TokenSerializationType
636);
637#[cfg(feature = "cssparser")]
638impl ToShmem for cssparser::UnicodeRange {
639    fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self> {
640        Ok(ManuallyDrop::new(Self {
641            start: self.start,
642            end: self.end,
643        }))
644    }
645}