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 
15 extern crate cssparser;
16 extern crate servo_arc;
17 extern crate smallbitvec;
18 extern crate smallvec;
19 #[cfg(feature = "string_cache")]
20 extern crate string_cache;
21 
22 use servo_arc::{Arc, ThinArc};
23 use smallbitvec::{InternalStorage, SmallBitVec};
24 use smallvec::{Array, SmallVec};
25 use std::alloc::Layout;
26 #[cfg(debug_assertions)]
27 use std::any::TypeId;
28 #[cfg(debug_assertions)]
29 use std::collections::HashSet;
30 use std::ffi::CString;
31 use std::isize;
32 use std::marker::PhantomData;
33 use std::mem::{self, ManuallyDrop};
34 use std::num::Wrapping;
35 use std::ops::Range;
36 use std::os::raw::c_char;
37 #[cfg(debug_assertions)]
38 use std::os::raw::c_void;
39 use std::ptr::{self, NonNull};
40 use std::slice;
41 use std::str;
42 
43 /// Result type for ToShmem::to_shmem.
44 ///
45 /// The String is an error message describing why the call failed.
46 pub type Result<T> = std::result::Result<ManuallyDrop<T>, String>;
47 
48 // Various pointer arithmetic functions in this file can be replaced with
49 // functions on `Layout` once they have stabilized:
50 //
51 // https://github.com/rust-lang/rust/issues/55724
52 
53 /// A builder object that transforms and copies values into a fixed size buffer.
54 pub struct SharedMemoryBuilder {
55     /// The buffer into which values will be copied.
56     buffer: *mut u8,
57     /// The size of the buffer.
58     capacity: usize,
59     /// The current position in the buffer, where the next value will be written
60     /// at.
61     index: usize,
62     /// Pointers to every sharable value that we store in the shared memory
63     /// buffer.  We use this to assert against encountering the same value
64     /// twice, e.g. through another Arc reference, so that we don't
65     /// inadvertently store duplicate copies of values.
66     #[cfg(debug_assertions)]
67     shared_values: HashSet<*const c_void>,
68     /// Types of values that we may duplicate in the shared memory buffer when
69     /// there are shared references to them, such as in Arcs.
70     #[cfg(debug_assertions)]
71     allowed_duplication_types: HashSet<TypeId>,
72 }
73 
74 /// Amount of padding needed after `size` bytes to ensure that the following
75 /// address will satisfy `align`.
padding_needed_for(size: usize, align: usize) -> usize76 fn padding_needed_for(size: usize, align: usize) -> usize {
77     padded_size(size, align).wrapping_sub(size)
78 }
79 
80 /// Rounds up `size` so that the following address will satisfy `align`.
padded_size(size: usize, align: usize) -> usize81 fn padded_size(size: usize, align: usize) -> usize {
82     size.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1)
83 }
84 
85 impl SharedMemoryBuilder {
86     /// Creates a new SharedMemoryBuilder using the specified buffer.
new(buffer: *mut u8, capacity: usize) -> SharedMemoryBuilder87     pub unsafe fn new(buffer: *mut u8, capacity: usize) -> SharedMemoryBuilder {
88         SharedMemoryBuilder {
89             buffer,
90             capacity,
91             index: 0,
92             #[cfg(debug_assertions)]
93             shared_values: HashSet::new(),
94             #[cfg(debug_assertions)]
95             allowed_duplication_types: HashSet::new(),
96         }
97     }
98 
99     /// Notes a type as being allowed for duplication when being copied to the
100     /// shared memory buffer, such as Arcs referencing the same value.
101     #[inline]
add_allowed_duplication_type<T: 'static>(&mut self)102     pub fn add_allowed_duplication_type<T: 'static>(&mut self) {
103         #[cfg(debug_assertions)]
104         self.allowed_duplication_types.insert(TypeId::of::<T>());
105     }
106 
107     /// Returns the number of bytes currently used in the buffer.
108     #[inline]
len(&self) -> usize109     pub fn len(&self) -> usize {
110         self.index
111     }
112 
113     /// Writes a value into the shared memory buffer and returns a pointer to
114     /// it in the buffer.
115     ///
116     /// The value is cloned and converted into a form suitable for placing into
117     /// a shared memory buffer by calling ToShmem::to_shmem on it.
118     ///
119     /// Panics if there is insufficient space in the buffer.
write<T: ToShmem>(&mut self, value: &T) -> std::result::Result<*mut T, String>120     pub fn write<T: ToShmem>(&mut self, value: &T) -> std::result::Result<*mut T, String> {
121         // Reserve space for the value.
122         let dest: *mut T = self.alloc_value();
123 
124         // Make a clone of the value with all of its heap allocations
125         // placed in the shared memory buffer.
126         let value = value.to_shmem(self)?;
127 
128         unsafe {
129             // Copy the value into the buffer.
130             ptr::write(dest, ManuallyDrop::into_inner(value));
131         }
132 
133         // Return a pointer to the shared value.
134         Ok(dest)
135     }
136 
137     /// Reserves space in the shared memory buffer to fit a value of type T,
138     /// and returns a pointer to that reserved space.
139     ///
140     /// Panics if there is insufficient space in the buffer.
alloc_value<T>(&mut self) -> *mut T141     pub fn alloc_value<T>(&mut self) -> *mut T {
142         self.alloc(Layout::new::<T>())
143     }
144 
145     /// Reserves space in the shared memory buffer to fit an array of values of
146     /// type T, and returns a pointer to that reserved space.
147     ///
148     /// Panics if there is insufficient space in the buffer.
alloc_array<T>(&mut self, len: usize) -> *mut T149     pub fn alloc_array<T>(&mut self, len: usize) -> *mut T {
150         if len == 0 {
151             return NonNull::dangling().as_ptr();
152         }
153 
154         let size = mem::size_of::<T>();
155         let align = mem::align_of::<T>();
156 
157         self.alloc(Layout::from_size_align(padded_size(size, align) * len, align).unwrap())
158     }
159 
160     /// Reserves space in the shared memory buffer that conforms to the
161     /// specified layout, and returns a pointer to that reserved space.
162     ///
163     /// Panics if there is insufficient space in the buffer.
alloc<T>(&mut self, layout: Layout) -> *mut T164     pub fn alloc<T>(&mut self, layout: Layout) -> *mut T {
165         // Amount of padding to align the value.
166         //
167         // The addition can't overflow, since self.index <= self.capacity, and
168         // for us to have successfully allocated the buffer, `buffer + capacity`
169         // can't overflow.
170         let padding = padding_needed_for(self.buffer as usize + self.index, layout.align());
171 
172         // Reserve space for the padding.
173         let start = self.index.checked_add(padding).unwrap();
174         assert!(start <= std::isize::MAX as usize); // for the cast below
175 
176         // Reserve space for the value.
177         let end = start.checked_add(layout.size()).unwrap();
178         assert!(end <= self.capacity);
179 
180         self.index = end;
181         unsafe { self.buffer.add(start) as *mut T }
182     }
183 }
184 
185 /// A type that can be copied into a SharedMemoryBuilder.
186 pub trait ToShmem: Sized {
187     /// Clones this value into a form suitable for writing into a
188     /// SharedMemoryBuilder.
189     ///
190     /// If this value owns any heap allocations, they should be written into
191     /// `builder` so that the return value of this function can point to the
192     /// copy in the shared memory buffer.
193     ///
194     /// The return type is wrapped in ManuallyDrop to make it harder to
195     /// accidentally invoke the destructor of the value that is produced.
196     ///
197     /// Returns a Result so that we can gracefully recover from unexpected
198     /// content.
to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>199     fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>;
200 }
201 
202 #[macro_export]
203 macro_rules! impl_trivial_to_shmem {
204     ($($ty:ty),*) => {
205         $(
206             impl $crate::ToShmem for $ty {
207                 fn to_shmem(
208                     &self,
209                     _builder: &mut $crate::SharedMemoryBuilder,
210                 ) -> $crate::Result<Self> {
211                     $crate::Result::Ok(::std::mem::ManuallyDrop::new(*self))
212                 }
213             }
214         )*
215     };
216 }
217 
218 impl_trivial_to_shmem!(
219     (),
220     bool,
221     f32,
222     f64,
223     i8,
224     i16,
225     i32,
226     i64,
227     u8,
228     u16,
229     u32,
230     u64,
231     isize,
232     usize
233 );
234 
235 impl_trivial_to_shmem!(cssparser::RGBA);
236 impl_trivial_to_shmem!(cssparser::SourceLocation);
237 impl_trivial_to_shmem!(cssparser::TokenSerializationType);
238 
239 impl<T> ToShmem for PhantomData<T> {
to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self>240     fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self> {
241         Ok(ManuallyDrop::new(*self))
242     }
243 }
244 
245 impl<T: ToShmem> ToShmem for Range<T> {
to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>246     fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
247         Ok(ManuallyDrop::new(Range {
248             start: ManuallyDrop::into_inner(self.start.to_shmem(builder)?),
249             end: ManuallyDrop::into_inner(self.end.to_shmem(builder)?),
250         }))
251     }
252 }
253 
254 impl ToShmem for cssparser::UnicodeRange {
to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self>255     fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self> {
256         Ok(ManuallyDrop::new(cssparser::UnicodeRange {
257             start: self.start,
258             end: self.end,
259         }))
260     }
261 }
262 
263 impl<T: ToShmem, U: ToShmem> ToShmem for (T, U) {
to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>264     fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
265         Ok(ManuallyDrop::new((
266             ManuallyDrop::into_inner(self.0.to_shmem(builder)?),
267             ManuallyDrop::into_inner(self.1.to_shmem(builder)?),
268         )))
269     }
270 }
271 
272 impl<T: ToShmem> ToShmem for Wrapping<T> {
to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>273     fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
274         Ok(ManuallyDrop::new(Wrapping(ManuallyDrop::into_inner(
275             self.0.to_shmem(builder)?,
276         ))))
277     }
278 }
279 
280 impl<T: ToShmem> ToShmem for Box<T> {
to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>281     fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
282         // Reserve space for the boxed value.
283         let dest: *mut T = builder.alloc_value();
284 
285         // Make a clone of the boxed value with all of its heap allocations
286         // placed in the shared memory buffer.
287         let value = (**self).to_shmem(builder)?;
288 
289         unsafe {
290             // Copy the value into the buffer.
291             ptr::write(dest, ManuallyDrop::into_inner(value));
292 
293             Ok(ManuallyDrop::new(Box::from_raw(dest)))
294         }
295     }
296 }
297 
298 /// Converts all the items in `src` into shared memory form, writes them into
299 /// the specified buffer, and returns a pointer to the slice.
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>,300 unsafe fn to_shmem_slice_ptr<'a, T, I>(
301     src: I,
302     dest: *mut T,
303     builder: &mut SharedMemoryBuilder,
304 ) -> std::result::Result<*mut [T], String>
305 where
306     T: 'a + ToShmem,
307     I: ExactSizeIterator<Item = &'a T>,
308 {
309     let dest = slice::from_raw_parts_mut(dest, src.len());
310 
311     // Make a clone of each element from the iterator with its own heap
312     // allocations placed in the buffer, and copy that clone into the buffer.
313     for (src, dest) in src.zip(dest.iter_mut()) {
314         ptr::write(dest, ManuallyDrop::into_inner(src.to_shmem(builder)?));
315     }
316 
317     Ok(dest)
318 }
319 
320 /// Writes all the items in `src` into a slice in the shared memory buffer and
321 /// returns a pointer to the slice.
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>,322 pub unsafe fn to_shmem_slice<'a, T, I>(
323     src: I,
324     builder: &mut SharedMemoryBuilder,
325 ) -> std::result::Result<*mut [T], String>
326 where
327     T: 'a + ToShmem,
328     I: ExactSizeIterator<Item = &'a T>,
329 {
330     let dest = builder.alloc_array(src.len());
331     to_shmem_slice_ptr(src, dest, builder)
332 }
333 
334 impl<T: ToShmem> ToShmem for Box<[T]> {
to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>335     fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
336         unsafe {
337             let dest = to_shmem_slice(self.iter(), builder)?;
338             Ok(ManuallyDrop::new(Box::from_raw(dest)))
339         }
340     }
341 }
342 
343 impl ToShmem for Box<str> {
to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>344     fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
345         // Reserve space for the string bytes.
346         let dest: *mut u8 = builder.alloc_array(self.len());
347 
348         unsafe {
349             // Copy the value into the buffer.
350             ptr::copy(self.as_ptr(), dest, self.len());
351 
352             Ok(ManuallyDrop::new(Box::from_raw(
353                 str::from_utf8_unchecked_mut(slice::from_raw_parts_mut(dest, self.len())),
354             )))
355         }
356     }
357 }
358 
359 impl ToShmem for String {
to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>360     fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
361         // Reserve space for the string bytes.
362         let dest: *mut u8 = builder.alloc_array(self.len());
363 
364         unsafe {
365             // Copy the value into the buffer.
366             ptr::copy(self.as_ptr(), dest, self.len());
367 
368             Ok(ManuallyDrop::new(String::from_raw_parts(
369                 dest,
370                 self.len(),
371                 self.len(),
372             )))
373         }
374     }
375 }
376 
377 impl ToShmem for CString {
to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>378     fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
379         let len = self.as_bytes_with_nul().len();
380 
381         // Reserve space for the string bytes.
382         let dest: *mut c_char = builder.alloc_array(len);
383 
384         unsafe {
385             // Copy the value into the buffer.
386             ptr::copy(self.as_ptr(), dest, len);
387 
388             Ok(ManuallyDrop::new(CString::from_raw(dest)))
389         }
390     }
391 }
392 
393 impl<T: ToShmem> ToShmem for Vec<T> {
to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>394     fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
395         unsafe {
396             let dest = to_shmem_slice(self.iter(), builder)? as *mut T;
397             let dest_vec = Vec::from_raw_parts(dest, self.len(), self.len());
398             Ok(ManuallyDrop::new(dest_vec))
399         }
400     }
401 }
402 
403 impl<T: ToShmem, A: Array<Item = T>> ToShmem for SmallVec<A> {
to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>404     fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
405         let dest_vec = unsafe {
406             if self.spilled() {
407                 // Place the items in a separate allocation in the shared memory
408                 // buffer.
409                 let dest = to_shmem_slice(self.iter(), builder)? as *mut T;
410                 SmallVec::from_raw_parts(dest, self.len(), self.len())
411             } else {
412                 // Place the items inline.
413                 let mut s = SmallVec::new();
414                 to_shmem_slice_ptr(self.iter(), s.as_mut_ptr(), builder)?;
415                 s.set_len(self.len());
416                 s
417             }
418         };
419 
420         Ok(ManuallyDrop::new(dest_vec))
421     }
422 }
423 
424 impl<T: ToShmem> ToShmem for Option<T> {
to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>425     fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
426         let v = match self {
427             Some(v) => Some(ManuallyDrop::into_inner(v.to_shmem(builder)?)),
428             None => None,
429         };
430 
431         Ok(ManuallyDrop::new(v))
432     }
433 }
434 
435 impl<T: 'static + ToShmem> ToShmem for Arc<T> {
to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>436     fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
437         // Assert that we don't encounter any shared references to values we
438         // don't expect.  Those we expect are those noted by calling
439         // add_allowed_duplication_type, and should be types where we're fine
440         // with duplicating any shared references in the shared memory buffer.
441         //
442         // Unfortunately there's no good way to print out the exact type of T
443         // in the assertion message.
444         #[cfg(debug_assertions)]
445         assert!(
446             !builder.shared_values.contains(&self.heap_ptr()) ||
447                 builder
448                     .allowed_duplication_types
449                     .contains(&TypeId::of::<T>()),
450             "ToShmem failed for Arc<T>: encountered a value of type T with multiple references \
451              and which has not been explicitly allowed with an add_allowed_duplication_type call",
452         );
453 
454         // Make a clone of the Arc-owned value with all of its heap allocations
455         // placed in the shared memory buffer.
456         let value = (**self).to_shmem(builder)?;
457 
458         // Create a new Arc with the shared value and have it place its
459         // ArcInner in the shared memory buffer.
460         unsafe {
461             let static_arc = Arc::new_static(
462                 |layout| builder.alloc(layout),
463                 ManuallyDrop::into_inner(value),
464             );
465 
466             #[cfg(debug_assertions)]
467             builder.shared_values.insert(self.heap_ptr());
468 
469             Ok(ManuallyDrop::new(static_arc))
470         }
471     }
472 }
473 
474 impl<H: 'static + ToShmem, T: 'static + ToShmem> ToShmem for ThinArc<H, T> {
to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>475     fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
476         // We don't currently have any shared ThinArc values in stylesheets,
477         // so don't support them for now.
478         #[cfg(debug_assertions)]
479         assert!(
480             !builder.shared_values.contains(&self.heap_ptr()),
481             "ToShmem failed for ThinArc<T>: encountered a value with multiple references, which \
482              is not currently supported",
483         );
484 
485         // Make a clone of the Arc-owned header and slice values with all of
486         // their heap allocations placed in the shared memory buffer.
487         let header = self.header.header.to_shmem(builder)?;
488         let mut values = Vec::with_capacity(self.slice.len());
489         for v in self.slice.iter() {
490             values.push(v.to_shmem(builder)?);
491         }
492 
493         // Create a new ThinArc with the shared value and have it place
494         // its ArcInner in the shared memory buffer.
495         unsafe {
496             let static_arc = ThinArc::static_from_header_and_iter(
497                 |layout| builder.alloc(layout),
498                 ManuallyDrop::into_inner(header),
499                 values.into_iter().map(ManuallyDrop::into_inner),
500             );
501 
502             #[cfg(debug_assertions)]
503             builder.shared_values.insert(self.heap_ptr());
504 
505             Ok(ManuallyDrop::new(static_arc))
506         }
507     }
508 }
509 
510 impl ToShmem for SmallBitVec {
to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>511     fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
512         let storage = match self.clone().into_storage() {
513             InternalStorage::Spilled(vs) => {
514                 // Reserve space for the boxed slice values.
515                 let len = vs.len();
516                 let dest: *mut usize = builder.alloc_array(len);
517 
518                 unsafe {
519                     // Copy the value into the buffer.
520                     let src = vs.as_ptr() as *const usize;
521                     ptr::copy(src, dest, len);
522 
523                     let dest_slice =
524                         Box::from_raw(slice::from_raw_parts_mut(dest, len) as *mut [usize]);
525                     InternalStorage::Spilled(dest_slice)
526                 }
527             },
528             InternalStorage::Inline(x) => InternalStorage::Inline(x),
529         };
530         Ok(ManuallyDrop::new(unsafe {
531             SmallBitVec::from_storage(storage)
532         }))
533     }
534 }
535 
536 #[cfg(feature = "string_cache")]
537 impl<Static: string_cache::StaticAtomSet> ToShmem for string_cache::Atom<Static> {
to_shmem(&self, _: &mut SharedMemoryBuilder) -> Result<Self>538     fn to_shmem(&self, _: &mut SharedMemoryBuilder) -> Result<Self> {
539         // NOTE(emilio): In practice, this can be implemented trivially if
540         // string_cache could expose the implementation detail of static atoms
541         // being an index into the static table (and panicking in the
542         // non-static, non-inline cases).
543         unimplemented!(
544             "If servo wants to share stylesheets across processes, \
545              then ToShmem for Atom needs to be implemented"
546         )
547     }
548 }
549