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