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