lib.rs (21384B)
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 use std::alloc::Layout; 16 use std::collections::HashSet; 17 use std::ffi::CString; 18 use std::isize; 19 use std::marker::PhantomData; 20 use std::mem::{self, ManuallyDrop}; 21 use std::num::Wrapping; 22 use std::ops::Range; 23 use std::os::raw::c_char; 24 use std::ptr::{self, NonNull}; 25 use std::slice; 26 use std::str; 27 28 /// Result type for ToShmem::to_shmem. 29 /// 30 /// The String is an error message describing why the call failed. 31 pub 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. 39 pub 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`. 57 fn 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`. 62 fn padded_size(size: usize, align: usize) -> usize { 63 size.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1) 64 } 65 66 impl 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. 157 pub 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] 174 macro_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 189 impl_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 207 impl<T> ToShmem for PhantomData<T> { 208 fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self> { 209 Ok(ManuallyDrop::new(*self)) 210 } 211 } 212 213 impl<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 222 impl<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 231 impl<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 239 impl<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. 259 unsafe 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> 264 where 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. 281 pub unsafe fn to_shmem_slice<'a, T, I>( 282 src: I, 283 builder: &mut SharedMemoryBuilder, 284 ) -> std::result::Result<*mut [T], String> 285 where 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 293 impl<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 302 impl 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 318 impl 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 336 impl 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 352 impl<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 362 impl<T: ToShmem, S> ToShmem for HashSet<T, S> 363 where 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 377 impl<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")] 389 impl<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")] 411 impl<A: 'static, B: 'static> ToShmem for servo_arc::ArcUnion<A, B> 412 where 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")] 430 impl<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")] 462 impl<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")] 500 impl<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")] 557 impl 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")] 584 impl<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")] 598 impl_trivial_to_shmem!( 599 cssparser::SourceLocation, 600 cssparser::SourcePosition, 601 cssparser::TokenSerializationType 602 ); 603 #[cfg(feature = "cssparser")] 604 impl 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 }