summaryrefslogtreecommitdiffstats
path: root/servo/components/to_shmem/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--servo/components/to_shmem/lib.rs597
1 files changed, 597 insertions, 0 deletions
diff --git a/servo/components/to_shmem/lib.rs b/servo/components/to_shmem/lib.rs
new file mode 100644
index 0000000000..1e70697165
--- /dev/null
+++ b/servo/components/to_shmem/lib.rs
@@ -0,0 +1,597 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! Trait for cloning data into a shared memory buffer.
+//!
+//! This module contains the SharedMemoryBuilder type and ToShmem trait.
+//!
+//! We put them here (and not in style_traits) so that we can derive ToShmem
+//! from the selectors and style crates.
+
+#![crate_name = "to_shmem"]
+#![crate_type = "rlib"]
+
+extern crate cssparser;
+extern crate servo_arc;
+extern crate smallbitvec;
+extern crate smallvec;
+#[cfg(feature = "string_cache")]
+extern crate string_cache;
+extern crate thin_vec;
+
+use servo_arc::{Arc, HeaderSlice};
+use smallbitvec::{InternalStorage, SmallBitVec};
+use smallvec::{Array, SmallVec};
+use std::alloc::Layout;
+use std::collections::HashSet;
+use std::ffi::CString;
+use std::isize;
+use std::marker::PhantomData;
+use std::mem::{self, ManuallyDrop};
+use std::num::Wrapping;
+use std::ops::Range;
+use std::os::raw::c_char;
+#[cfg(debug_assertions)]
+use std::os::raw::c_void;
+use std::ptr::{self, NonNull};
+use std::slice;
+use std::str;
+use thin_vec::ThinVec;
+
+/// Result type for ToShmem::to_shmem.
+///
+/// The String is an error message describing why the call failed.
+pub type Result<T> = std::result::Result<ManuallyDrop<T>, String>;
+
+// Various pointer arithmetic functions in this file can be replaced with
+// functions on `Layout` once they have stabilized:
+//
+// https://github.com/rust-lang/rust/issues/55724
+
+/// A builder object that transforms and copies values into a fixed size buffer.
+pub struct SharedMemoryBuilder {
+ /// The buffer into which values will be copied.
+ buffer: *mut u8,
+ /// The size of the buffer.
+ capacity: usize,
+ /// The current position in the buffer, where the next value will be written
+ /// at.
+ index: usize,
+ /// Pointers to every shareable value that we store in the shared memory
+ /// buffer. We use this to assert against encountering the same value
+ /// twice, e.g. through another Arc reference, so that we don't
+ /// inadvertently store duplicate copies of values.
+ #[cfg(debug_assertions)]
+ shared_values: HashSet<*const c_void>,
+}
+
+/// Amount of padding needed after `size` bytes to ensure that the following
+/// address will satisfy `align`.
+fn padding_needed_for(size: usize, align: usize) -> usize {
+ padded_size(size, align).wrapping_sub(size)
+}
+
+/// Rounds up `size` so that the following address will satisfy `align`.
+fn padded_size(size: usize, align: usize) -> usize {
+ size.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1)
+}
+
+impl SharedMemoryBuilder {
+ /// Creates a new SharedMemoryBuilder using the specified buffer.
+ pub unsafe fn new(buffer: *mut u8, capacity: usize) -> SharedMemoryBuilder {
+ SharedMemoryBuilder {
+ buffer,
+ capacity,
+ index: 0,
+ #[cfg(debug_assertions)]
+ shared_values: HashSet::new(),
+ }
+ }
+
+ /// Returns the number of bytes currently used in the buffer.
+ #[inline]
+ pub fn len(&self) -> usize {
+ self.index
+ }
+
+ /// Writes a value into the shared memory buffer and returns a pointer to
+ /// it in the buffer.
+ ///
+ /// The value is cloned and converted into a form suitable for placing into
+ /// a shared memory buffer by calling ToShmem::to_shmem on it.
+ ///
+ /// Panics if there is insufficient space in the buffer.
+ pub fn write<T: ToShmem>(&mut self, value: &T) -> std::result::Result<*mut T, String> {
+ // Reserve space for the value.
+ let dest: *mut T = self.alloc_value();
+
+ // Make a clone of the value with all of its heap allocations
+ // placed in the shared memory buffer.
+ let value = value.to_shmem(self)?;
+
+ unsafe {
+ // Copy the value into the buffer.
+ ptr::write(dest, ManuallyDrop::into_inner(value));
+ }
+
+ // Return a pointer to the shared value.
+ Ok(dest)
+ }
+
+ /// Reserves space in the shared memory buffer to fit a value of type T,
+ /// and returns a pointer to that reserved space.
+ ///
+ /// Panics if there is insufficient space in the buffer.
+ pub fn alloc_value<T>(&mut self) -> *mut T {
+ self.alloc(Layout::new::<T>())
+ }
+
+ /// Reserves space in the shared memory buffer to fit an array of values of
+ /// type T, and returns a pointer to that reserved space.
+ ///
+ /// Panics if there is insufficient space in the buffer.
+ pub fn alloc_array<T>(&mut self, len: usize) -> *mut T {
+ if len == 0 {
+ return NonNull::dangling().as_ptr();
+ }
+
+ let size = mem::size_of::<T>();
+ let align = mem::align_of::<T>();
+
+ self.alloc(Layout::from_size_align(padded_size(size, align) * len, align).unwrap())
+ }
+
+ /// Reserves space in the shared memory buffer that conforms to the
+ /// specified layout, and returns a pointer to that reserved space.
+ ///
+ /// Panics if there is insufficient space in the buffer.
+ pub fn alloc<T>(&mut self, layout: Layout) -> *mut T {
+ // Amount of padding to align the value.
+ //
+ // The addition can't overflow, since self.index <= self.capacity, and
+ // for us to have successfully allocated the buffer, `buffer + capacity`
+ // can't overflow.
+ let padding = padding_needed_for(self.buffer as usize + self.index, layout.align());
+
+ // Reserve space for the padding.
+ let start = self.index.checked_add(padding).unwrap();
+ assert!(start <= std::isize::MAX as usize); // for the cast below
+
+ // Reserve space for the value.
+ let end = start.checked_add(layout.size()).unwrap();
+ assert!(end <= self.capacity);
+
+ self.index = end;
+ unsafe { self.buffer.add(start) as *mut T }
+ }
+}
+
+/// A type that can be copied into a SharedMemoryBuilder.
+pub trait ToShmem: Sized {
+ /// Clones this value into a form suitable for writing into a
+ /// SharedMemoryBuilder.
+ ///
+ /// If this value owns any heap allocations, they should be written into
+ /// `builder` so that the return value of this function can point to the
+ /// copy in the shared memory buffer.
+ ///
+ /// The return type is wrapped in ManuallyDrop to make it harder to
+ /// accidentally invoke the destructor of the value that is produced.
+ ///
+ /// Returns a Result so that we can gracefully recover from unexpected
+ /// content.
+ fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>;
+}
+
+#[macro_export]
+macro_rules! impl_trivial_to_shmem {
+ ($($ty:ty),*) => {
+ $(
+ impl $crate::ToShmem for $ty {
+ fn to_shmem(
+ &self,
+ _builder: &mut $crate::SharedMemoryBuilder,
+ ) -> $crate::Result<Self> {
+ $crate::Result::Ok(::std::mem::ManuallyDrop::new(*self))
+ }
+ }
+ )*
+ };
+}
+
+impl_trivial_to_shmem!(
+ (),
+ bool,
+ f32,
+ f64,
+ i8,
+ i16,
+ i32,
+ i64,
+ u8,
+ u16,
+ u32,
+ u64,
+ isize,
+ usize
+);
+
+impl_trivial_to_shmem!(cssparser::SourceLocation);
+impl_trivial_to_shmem!(cssparser::TokenSerializationType);
+
+impl<T> ToShmem for PhantomData<T> {
+ fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self> {
+ Ok(ManuallyDrop::new(*self))
+ }
+}
+
+impl<T: ToShmem> ToShmem for Range<T> {
+ fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
+ Ok(ManuallyDrop::new(Range {
+ start: ManuallyDrop::into_inner(self.start.to_shmem(builder)?),
+ end: ManuallyDrop::into_inner(self.end.to_shmem(builder)?),
+ }))
+ }
+}
+
+impl ToShmem for cssparser::UnicodeRange {
+ fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self> {
+ Ok(ManuallyDrop::new(cssparser::UnicodeRange {
+ start: self.start,
+ end: self.end,
+ }))
+ }
+}
+
+impl<T: ToShmem, U: ToShmem> ToShmem for (T, U) {
+ fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
+ Ok(ManuallyDrop::new((
+ ManuallyDrop::into_inner(self.0.to_shmem(builder)?),
+ ManuallyDrop::into_inner(self.1.to_shmem(builder)?),
+ )))
+ }
+}
+
+impl<T: ToShmem> ToShmem for Wrapping<T> {
+ fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
+ Ok(ManuallyDrop::new(Wrapping(ManuallyDrop::into_inner(
+ self.0.to_shmem(builder)?,
+ ))))
+ }
+}
+
+impl<T: ToShmem> ToShmem for Box<T> {
+ fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
+ // Reserve space for the boxed value.
+ let dest: *mut T = builder.alloc_value();
+
+ // Make a clone of the boxed value with all of its heap allocations
+ // placed in the shared memory buffer.
+ let value = (**self).to_shmem(builder)?;
+
+ unsafe {
+ // Copy the value into the buffer.
+ ptr::write(dest, ManuallyDrop::into_inner(value));
+
+ Ok(ManuallyDrop::new(Box::from_raw(dest)))
+ }
+ }
+}
+
+/// Converts all the items in `src` into shared memory form, writes them into
+/// the specified buffer, and returns a pointer to the slice.
+unsafe fn to_shmem_slice_ptr<'a, T, I>(
+ src: I,
+ dest: *mut T,
+ builder: &mut SharedMemoryBuilder,
+) -> std::result::Result<*mut [T], String>
+where
+ T: 'a + ToShmem,
+ I: ExactSizeIterator<Item = &'a T>,
+{
+ let dest = slice::from_raw_parts_mut(dest, src.len());
+
+ // Make a clone of each element from the iterator with its own heap
+ // allocations placed in the buffer, and copy that clone into the buffer.
+ for (src, dest) in src.zip(dest.iter_mut()) {
+ ptr::write(dest, ManuallyDrop::into_inner(src.to_shmem(builder)?));
+ }
+
+ Ok(dest)
+}
+
+/// Writes all the items in `src` into a slice in the shared memory buffer and
+/// returns a pointer to the slice.
+pub unsafe fn to_shmem_slice<'a, T, I>(
+ src: I,
+ builder: &mut SharedMemoryBuilder,
+) -> std::result::Result<*mut [T], String>
+where
+ T: 'a + ToShmem,
+ I: ExactSizeIterator<Item = &'a T>,
+{
+ let dest = builder.alloc_array(src.len());
+ to_shmem_slice_ptr(src, dest, builder)
+}
+
+impl<T: ToShmem> ToShmem for Box<[T]> {
+ fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
+ unsafe {
+ let dest = to_shmem_slice(self.iter(), builder)?;
+ Ok(ManuallyDrop::new(Box::from_raw(dest)))
+ }
+ }
+}
+
+impl ToShmem for Box<str> {
+ fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
+ // Reserve space for the string bytes.
+ let dest: *mut u8 = builder.alloc_array(self.len());
+
+ unsafe {
+ // Copy the value into the buffer.
+ ptr::copy(self.as_ptr(), dest, self.len());
+
+ Ok(ManuallyDrop::new(Box::from_raw(
+ str::from_utf8_unchecked_mut(slice::from_raw_parts_mut(dest, self.len())),
+ )))
+ }
+ }
+}
+
+impl ToShmem for String {
+ fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
+ // Reserve space for the string bytes.
+ let dest: *mut u8 = builder.alloc_array(self.len());
+
+ unsafe {
+ // Copy the value into the buffer.
+ ptr::copy(self.as_ptr(), dest, self.len());
+
+ Ok(ManuallyDrop::new(String::from_raw_parts(
+ dest,
+ self.len(),
+ self.len(),
+ )))
+ }
+ }
+}
+
+impl ToShmem for CString {
+ fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
+ let len = self.as_bytes_with_nul().len();
+
+ // Reserve space for the string bytes.
+ let dest: *mut c_char = builder.alloc_array(len);
+
+ unsafe {
+ // Copy the value into the buffer.
+ ptr::copy(self.as_ptr(), dest, len);
+
+ Ok(ManuallyDrop::new(CString::from_raw(dest)))
+ }
+ }
+}
+
+impl<T: ToShmem> ToShmem for Vec<T> {
+ fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
+ unsafe {
+ let dest = to_shmem_slice(self.iter(), builder)? as *mut T;
+ let dest_vec = Vec::from_raw_parts(dest, self.len(), self.len());
+ Ok(ManuallyDrop::new(dest_vec))
+ }
+ }
+}
+
+impl<T: ToShmem, A: Array<Item = T>> ToShmem for SmallVec<A> {
+ fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
+ let dest_vec = unsafe {
+ if self.spilled() {
+ // Place the items in a separate allocation in the shared memory
+ // buffer.
+ let dest = to_shmem_slice(self.iter(), builder)? as *mut T;
+ SmallVec::from_raw_parts(dest, self.len(), self.len())
+ } else {
+ // Place the items inline.
+ let mut s = SmallVec::new();
+ to_shmem_slice_ptr(self.iter(), s.as_mut_ptr(), builder)?;
+ s.set_len(self.len());
+ s
+ }
+ };
+
+ Ok(ManuallyDrop::new(dest_vec))
+ }
+}
+
+impl<T: ToShmem> ToShmem for Option<T> {
+ fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
+ let v = match self {
+ Some(v) => Some(ManuallyDrop::into_inner(v.to_shmem(builder)?)),
+ None => None,
+ };
+
+ Ok(ManuallyDrop::new(v))
+ }
+}
+
+impl<T: ToShmem, S> ToShmem for HashSet<T, S>
+where
+ Self: Default,
+{
+ fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self> {
+ if !self.is_empty() {
+ return Err(format!(
+ "ToShmem failed for HashSet: We only support empty sets \
+ (we don't expect custom properties in UA sheets, they're observable by content)",
+ ));
+ }
+ Ok(ManuallyDrop::new(Self::default()))
+ }
+}
+
+impl<T: ToShmem> ToShmem for Arc<T> {
+ fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
+ // Assert that we don't encounter any shared references to values we
+ // don't expect.
+ #[cfg(debug_assertions)]
+ assert!(
+ !builder.shared_values.contains(&self.heap_ptr()),
+ "ToShmem failed for Arc<{}>: encountered a value with multiple \
+ references.",
+ std::any::type_name::<T>()
+ );
+
+ // Make a clone of the Arc-owned value with all of its heap allocations
+ // placed in the shared memory buffer.
+ let value = (**self).to_shmem(builder)?;
+
+ // Create a new Arc with the shared value and have it place its
+ // ArcInner in the shared memory buffer.
+ unsafe {
+ let static_arc = Arc::new_static(
+ |layout| builder.alloc(layout),
+ ManuallyDrop::into_inner(value),
+ );
+
+ #[cfg(debug_assertions)]
+ builder.shared_values.insert(self.heap_ptr());
+
+ Ok(ManuallyDrop::new(static_arc))
+ }
+ }
+}
+
+impl<H: ToShmem, T: ToShmem> ToShmem for Arc<HeaderSlice<H, T>> {
+ fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
+ // We don't currently have any shared ThinArc values in stylesheets,
+ // so don't support them for now.
+ #[cfg(debug_assertions)]
+ assert!(
+ !builder.shared_values.contains(&self.heap_ptr()),
+ "ToShmem failed for ThinArc<T>: encountered a value with multiple references, which \
+ is not currently supported",
+ );
+
+ // Make a clone of the Arc-owned header and slice values with all of
+ // their heap allocations placed in the shared memory buffer.
+ let header = self.header.to_shmem(builder)?;
+ let mut values = Vec::with_capacity(self.len());
+ for v in self.slice().iter() {
+ values.push(v.to_shmem(builder)?);
+ }
+
+ // Create a new ThinArc with the shared value and have it place
+ // its ArcInner in the shared memory buffer.
+ let len = values.len();
+ let static_arc = Self::from_header_and_iter_alloc(
+ |layout| builder.alloc(layout),
+ ManuallyDrop::into_inner(header),
+ values.into_iter().map(ManuallyDrop::into_inner),
+ len,
+ /* is_static = */ true,
+ );
+
+ #[cfg(debug_assertions)]
+ builder.shared_values.insert(self.heap_ptr());
+
+ Ok(ManuallyDrop::new(static_arc))
+ }
+}
+
+impl<T: ToShmem> ToShmem for ThinVec<T> {
+ fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
+ assert_eq!(mem::size_of::<Self>(), mem::size_of::<*const ()>());
+
+ // NOTE: We need to do the work of allocating the header in shared memory even if the
+ // length is zero, because an empty ThinVec, even though it doesn't allocate, references
+ // static memory which will not be mapped to other processes, see bug 1841011.
+ let len = self.len();
+
+ // nsTArrayHeader size.
+ // FIXME: Would be nice not to hard-code this, but in practice thin-vec crate also relies
+ // on this.
+ let header_size = 2 * mem::size_of::<u32>();
+ let header_align = mem::size_of::<u32>();
+
+ let item_size = mem::size_of::<T>();
+ let item_align = mem::align_of::<T>();
+
+ // We don't need to support underalignment for now, this could be supported if needed.
+ assert!(item_align >= header_align);
+
+ // This is explicitly unsupported by ThinVec, see:
+ // https://searchfox.org/mozilla-central/rev/ad732108b073742d7324f998c085f459674a6846/third_party/rust/thin-vec/src/lib.rs#375-386
+ assert!(item_align <= header_size);
+ let header_padding = 0;
+
+ let layout = Layout::from_size_align(
+ header_size + header_padding + padded_size(item_size, item_align) * len,
+ item_align,
+ )
+ .unwrap();
+
+ let shmem_header_ptr = builder.alloc::<u8>(layout);
+ let shmem_data_ptr = unsafe { shmem_header_ptr.add(header_size + header_padding) };
+
+ let data_ptr = self.as_ptr() as *const T as *const u8;
+ let header_ptr = unsafe { data_ptr.sub(header_size + header_padding) };
+
+ unsafe {
+ // Copy the header. Note this might copy a wrong capacity, but it doesn't matter,
+ // because shared memory ptrs are immutable anyways, and we can't relocate.
+ ptr::copy(header_ptr, shmem_header_ptr, header_size);
+ // ToShmem + copy the contents into the shared buffer.
+ to_shmem_slice_ptr(self.iter(), shmem_data_ptr as *mut T, builder)?;
+ // Return the new ThinVec, which is just a pointer to the shared memory buffer.
+ let shmem_thinvec: Self = mem::transmute(shmem_header_ptr);
+
+ // Sanity-check that the ptr and length match.
+ debug_assert_eq!(shmem_thinvec.as_ptr(), shmem_data_ptr as *const T);
+ debug_assert_eq!(shmem_thinvec.len(), len);
+
+ Ok(ManuallyDrop::new(shmem_thinvec))
+ }
+ }
+}
+
+impl ToShmem for SmallBitVec {
+ fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
+ let storage = match self.clone().into_storage() {
+ InternalStorage::Spilled(vs) => {
+ // Reserve space for the boxed slice values.
+ let len = vs.len();
+ let dest: *mut usize = builder.alloc_array(len);
+
+ unsafe {
+ // Copy the value into the buffer.
+ let src = vs.as_ptr() as *const usize;
+ ptr::copy(src, dest, len);
+
+ let dest_slice =
+ Box::from_raw(slice::from_raw_parts_mut(dest, len) as *mut [usize]);
+ InternalStorage::Spilled(dest_slice)
+ }
+ },
+ InternalStorage::Inline(x) => InternalStorage::Inline(x),
+ };
+ Ok(ManuallyDrop::new(unsafe {
+ SmallBitVec::from_storage(storage)
+ }))
+ }
+}
+
+#[cfg(feature = "string_cache")]
+impl<Static: string_cache::StaticAtomSet> ToShmem for string_cache::Atom<Static> {
+ fn to_shmem(&self, _: &mut SharedMemoryBuilder) -> Result<Self> {
+ // NOTE(emilio): In practice, this can be implemented trivially if
+ // string_cache could expose the implementation detail of static atoms
+ // being an index into the static table (and panicking in the
+ // non-static, non-inline cases).
+ unimplemented!(
+ "If servo wants to share stylesheets across processes, \
+ then ToShmem for Atom needs to be implemented"
+ )
+ }
+}