/* 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 = std::result::Result, 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(&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(&mut self) -> *mut T { self.alloc(Layout::new::()) } /// 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(&mut self, len: usize) -> *mut T { if len == 0 { return NonNull::dangling().as_ptr(); } let size = mem::size_of::(); let align = mem::align_of::(); 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(&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; } #[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 { $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 ToShmem for PhantomData { fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result { Ok(ManuallyDrop::new(*self)) } } impl ToShmem for Range { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result { 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 { Ok(ManuallyDrop::new(cssparser::UnicodeRange { start: self.start, end: self.end, })) } } impl ToShmem for (T, U) { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result { Ok(ManuallyDrop::new(( ManuallyDrop::into_inner(self.0.to_shmem(builder)?), ManuallyDrop::into_inner(self.1.to_shmem(builder)?), ))) } } impl ToShmem for Wrapping { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result { Ok(ManuallyDrop::new(Wrapping(ManuallyDrop::into_inner( self.0.to_shmem(builder)?, )))) } } impl ToShmem for Box { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result { // 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, { 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, { let dest = builder.alloc_array(src.len()); to_shmem_slice_ptr(src, dest, builder) } impl ToShmem for Box<[T]> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result { unsafe { let dest = to_shmem_slice(self.iter(), builder)?; Ok(ManuallyDrop::new(Box::from_raw(dest))) } } } impl ToShmem for Box { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result { // 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 { // 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 { 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 ToShmem for Vec { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result { 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> ToShmem for SmallVec { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result { 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 ToShmem for Option { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result { let v = match self { Some(v) => Some(ManuallyDrop::into_inner(v.to_shmem(builder)?)), None => None, }; Ok(ManuallyDrop::new(v)) } } impl ToShmem for HashSet where Self: Default, { fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result { 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 ToShmem for Arc { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result { // 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::() ); // 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 ToShmem for Arc> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result { // 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: 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 ToShmem for ThinVec { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result { assert_eq!(mem::size_of::(), 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::(); let header_align = mem::size_of::(); let item_size = mem::size_of::(); let item_align = mem::align_of::(); // 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::(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 { 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 ToShmem for string_cache::Atom { fn to_shmem(&self, _: &mut SharedMemoryBuilder) -> Result { // 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" ) } }