1 use crate::{ 2 prelude::*, 3 runtime::vm::{GcHeap, GcStore, VMGcRef}, 4 store::AutoAssertNoGc, 5 vm::GcStructLayout, 6 AnyRef, ExternRef, HeapType, RootedGcRefImpl, StorageType, Val, ValType, V128, 7 }; 8 use core::{fmt, mem}; 9 use wasmtime_environ::VMGcKind; 10 11 /// A `VMGcRef` that we know points to a `struct`. 12 /// 13 /// Create a `VMStructRef` via `VMGcRef::into_structref` and 14 /// `VMGcRef::as_structref`, or their untyped equivalents 15 /// `VMGcRef::into_structref_unchecked` and `VMGcRef::as_structref_unchecked`. 16 /// 17 /// Note: This is not a `TypedGcRef<_>` because each collector can have a 18 /// different concrete representation of `structref` that they allocate inside 19 /// their heaps. 20 #[derive(Debug, PartialEq, Eq, Hash)] 21 #[repr(transparent)] 22 pub struct VMStructRef(VMGcRef); 23 24 impl fmt::Pointer for VMStructRef { 25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 26 fmt::Pointer::fmt(&self.0, f) 27 } 28 } 29 30 impl From<VMStructRef> for VMGcRef { 31 #[inline] 32 fn from(x: VMStructRef) -> Self { 33 x.0 34 } 35 } 36 37 impl VMGcRef { 38 /// Is this `VMGcRef` pointing to a `struct`? 39 pub fn is_structref(&self, gc_heap: &(impl GcHeap + ?Sized)) -> bool { 40 if self.is_i31() { 41 return false; 42 } 43 44 let header = gc_heap.header(&self); 45 header.kind().matches(VMGcKind::StructRef) 46 } 47 48 /// Create a new `VMStructRef` from the given `gc_ref`. 49 /// 50 /// If this is not a GC reference to an `structref`, `Err(self)` is 51 /// returned. 52 pub fn into_structref(self, gc_heap: &impl GcHeap) -> Result<VMStructRef, VMGcRef> { 53 if self.is_structref(gc_heap) { 54 Ok(self.into_structref_unchecked()) 55 } else { 56 Err(self) 57 } 58 } 59 60 /// Create a new `VMStructRef` from `self` without actually checking that 61 /// `self` is an `structref`. 62 /// 63 /// This method does not check that `self` is actually an `structref`, but 64 /// it should be. Failure to uphold this invariant is memory safe but will 65 /// result in general incorrectness down the line such as panics or wrong 66 /// results. 67 #[inline] 68 pub fn into_structref_unchecked(self) -> VMStructRef { 69 debug_assert!(!self.is_i31()); 70 VMStructRef(self) 71 } 72 73 /// Get this GC reference as an `structref` reference, if it actually is an 74 /// `structref` reference. 75 pub fn as_structref(&self, gc_heap: &(impl GcHeap + ?Sized)) -> Option<&VMStructRef> { 76 if self.is_structref(gc_heap) { 77 Some(self.as_structref_unchecked()) 78 } else { 79 None 80 } 81 } 82 83 /// Get this GC reference as an `structref` reference without checking if it 84 /// actually is an `structref` reference. 85 /// 86 /// Calling this method on a non-`structref` reference is memory safe, but 87 /// will lead to general incorrectness like panics and wrong results. 88 pub fn as_structref_unchecked(&self) -> &VMStructRef { 89 debug_assert!(!self.is_i31()); 90 let ptr = self as *const VMGcRef; 91 let ret = unsafe { &*ptr.cast() }; 92 assert!(matches!(ret, VMStructRef(VMGcRef { .. }))); 93 ret 94 } 95 } 96 97 impl VMStructRef { 98 /// Get the underlying `VMGcRef`. 99 pub fn as_gc_ref(&self) -> &VMGcRef { 100 &self.0 101 } 102 103 /// Clone this `VMStructRef`, running any GC barriers as necessary. 104 pub fn clone(&self, gc_store: &mut GcStore) -> Self { 105 Self(gc_store.clone_gc_ref(&self.0)) 106 } 107 108 /// Explicitly drop this `structref`, running GC drop barriers as necessary. 109 pub fn drop(self, gc_store: &mut GcStore) { 110 gc_store.drop_gc_ref(self.0); 111 } 112 113 /// Copy this `VMStructRef` without running the GC's clone barriers. 114 /// 115 /// Prefer calling `clone(&mut GcStore)` instead! This is mostly an internal 116 /// escape hatch for collector implementations. 117 /// 118 /// Failure to run GC barriers when they would otherwise be necessary can 119 /// lead to leaks, panics, and wrong results. It cannot lead to memory 120 /// unsafety, however. 121 pub fn unchecked_copy(&self) -> Self { 122 Self(self.0.unchecked_copy()) 123 } 124 125 /// Read a field of the given `StorageType` into a `Val`. 126 /// 127 /// `i8` and `i16` fields are zero-extended into `Val::I32(_)`s. 128 /// 129 /// Does not check that the field is actually of type `ty`. That is the 130 /// caller's responsibility. Failure to do so is memory safe, but will lead 131 /// to general incorrectness such as panics and wrong results. 132 /// 133 /// Panics on out-of-bounds accesses. 134 pub fn read_field( 135 &self, 136 store: &mut AutoAssertNoGc, 137 layout: &GcStructLayout, 138 ty: &StorageType, 139 field: usize, 140 ) -> Val { 141 let offset = layout.fields[field]; 142 let data = store.unwrap_gc_store_mut().struct_data(self, layout.size); 143 match ty { 144 StorageType::I8 => Val::I32(data.read_u8(offset).into()), 145 StorageType::I16 => Val::I32(data.read_u16(offset).into()), 146 StorageType::ValType(ValType::I32) => Val::I32(data.read_i32(offset)), 147 StorageType::ValType(ValType::I64) => Val::I64(data.read_i64(offset)), 148 StorageType::ValType(ValType::F32) => Val::F32(data.read_u32(offset)), 149 StorageType::ValType(ValType::F64) => Val::F64(data.read_u64(offset)), 150 StorageType::ValType(ValType::V128) => Val::V128(data.read_v128(offset)), 151 StorageType::ValType(ValType::Ref(r)) => match r.heap_type().top() { 152 HeapType::Extern => { 153 let raw = data.read_u32(offset); 154 Val::ExternRef(ExternRef::_from_raw(store, raw)) 155 } 156 HeapType::Any => { 157 let raw = data.read_u32(offset); 158 Val::AnyRef(AnyRef::_from_raw(store, raw)) 159 } 160 HeapType::Func => todo!("funcrefs inside gc objects not yet implemented"), 161 otherwise => unreachable!("not a top type: {otherwise:?}"), 162 }, 163 } 164 } 165 166 /// Write the given value into this struct at the given offset. 167 /// 168 /// Returns an error if `val` is a GC reference that has since been 169 /// unrooted. 170 /// 171 /// Does not check that `val` matches `ty`, nor that the field is actually 172 /// of type `ty`. Checking those things is the caller's responsibility. 173 /// Failure to do so is memory safe, but will lead to general incorrectness 174 /// such as panics and wrong results. 175 /// 176 /// Panics on out-of-bounds accesses. 177 pub fn write_field( 178 &self, 179 store: &mut AutoAssertNoGc, 180 layout: &GcStructLayout, 181 ty: &StorageType, 182 field: usize, 183 val: Val, 184 ) -> Result<()> { 185 debug_assert!(val._matches_ty(&store, &ty.unpack())?); 186 187 let offset = layout.fields[field]; 188 let mut data = store.gc_store_mut()?.struct_data(self, layout.size); 189 match val { 190 Val::I32(i) if ty.is_i8() => data.write_i8(offset, i as i8), 191 Val::I32(i) if ty.is_i16() => data.write_i16(offset, i as i16), 192 Val::I32(i) => data.write_i32(offset, i), 193 Val::I64(i) => data.write_i64(offset, i), 194 Val::F32(f) => data.write_u32(offset, f), 195 Val::F64(f) => data.write_u64(offset, f), 196 Val::V128(v) => data.write_v128(offset, v), 197 198 // For GC-managed references, we need to take care to run the 199 // appropriate barriers, even when we are writing null references 200 // into the struct. 201 // 202 // POD-read the old value into a local copy, run the GC write 203 // barrier on that local copy, and then POD-write the updated 204 // value back into the struct. This avoids transmuting the inner 205 // data, which would probably be fine, but this approach is 206 // Obviously Correct and should get us by for now. If LLVM isn't 207 // able to elide some of these unnecessary copies, and this 208 // method is ever hot enough, we can always come back and clean 209 // it up in the future. 210 Val::ExternRef(e) => { 211 let raw = data.read_u32(offset); 212 let mut gc_ref = VMGcRef::from_raw_u32(raw); 213 let e = match e { 214 Some(e) => Some(e.try_gc_ref(store)?.unchecked_copy()), 215 None => None, 216 }; 217 store.gc_store_mut()?.write_gc_ref(&mut gc_ref, e.as_ref()); 218 let mut data = store.gc_store_mut()?.struct_data(self, layout.size); 219 data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32())); 220 } 221 Val::AnyRef(a) => { 222 let raw = data.read_u32(offset); 223 let mut gc_ref = VMGcRef::from_raw_u32(raw); 224 let a = match a { 225 Some(a) => Some(a.try_gc_ref(store)?.unchecked_copy()), 226 None => None, 227 }; 228 store.gc_store_mut()?.write_gc_ref(&mut gc_ref, a.as_ref()); 229 let mut data = store.gc_store_mut()?.struct_data(self, layout.size); 230 data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32())); 231 } 232 233 Val::FuncRef(_) => todo!("funcrefs inside gc objects not yet implemented"), 234 } 235 Ok(()) 236 } 237 238 /// Initialize a field in this structref that is currently uninitialized. 239 /// 240 /// The difference between this method and `write_field` is that GC barriers 241 /// are handled differently. When overwriting an initialized field (aka 242 /// `write_field`) we need to call the full write GC write barrier, which 243 /// logically drops the old GC reference and clones the new GC 244 /// reference. When we are initializing a field for the first time, there is 245 /// no old GC reference that is being overwritten and which we need to drop, 246 /// so we only need to clone the new GC reference. 247 /// 248 /// Calling this method on a structref that has already had the associated 249 /// field initialized will result in GC bugs. These are memory safe but will 250 /// lead to generally incorrect behavior such as panics, leaks, and 251 /// incorrect results. 252 /// 253 /// Does not check that `val` matches `ty`, nor that the field is actually 254 /// of type `ty`. Checking those things is the caller's responsibility. 255 /// Failure to do so is memory safe, but will lead to general incorrectness 256 /// such as panics and wrong results. 257 /// 258 /// Returns an error if `val` is a GC reference that has since been 259 /// unrooted. 260 /// 261 /// Panics on out-of-bounds accesses. 262 pub fn initialize_field( 263 &self, 264 store: &mut AutoAssertNoGc, 265 layout: &GcStructLayout, 266 ty: &StorageType, 267 field: usize, 268 val: Val, 269 ) -> Result<()> { 270 debug_assert!(val._matches_ty(&store, &ty.unpack())?); 271 let offset = layout.fields[field]; 272 match val { 273 Val::I32(i) if ty.is_i8() => store 274 .gc_store_mut()? 275 .struct_data(self, layout.size) 276 .write_i8(offset, i as i8), 277 Val::I32(i) if ty.is_i16() => store 278 .gc_store_mut()? 279 .struct_data(self, layout.size) 280 .write_i16(offset, i as i16), 281 Val::I32(i) => store 282 .gc_store_mut()? 283 .struct_data(self, layout.size) 284 .write_i32(offset, i), 285 Val::I64(i) => store 286 .gc_store_mut()? 287 .struct_data(self, layout.size) 288 .write_i64(offset, i), 289 Val::F32(f) => store 290 .gc_store_mut()? 291 .struct_data(self, layout.size) 292 .write_u32(offset, f), 293 Val::F64(f) => store 294 .gc_store_mut()? 295 .struct_data(self, layout.size) 296 .write_u64(offset, f), 297 Val::V128(v) => store 298 .gc_store_mut()? 299 .struct_data(self, layout.size) 300 .write_v128(offset, v), 301 302 // NB: We don't need to do a write barrier when initializing a 303 // field, because there is nothing being overwritten. Therefore, we 304 // just the clone barrier. 305 Val::ExternRef(x) => { 306 let x = match x { 307 None => 0, 308 Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(), 309 }; 310 store 311 .gc_store_mut()? 312 .struct_data(self, layout.size) 313 .write_u32(offset, x); 314 } 315 Val::AnyRef(x) => { 316 let x = match x { 317 None => 0, 318 Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(), 319 }; 320 store 321 .gc_store_mut()? 322 .struct_data(self, layout.size) 323 .write_u32(offset, x); 324 } 325 326 Val::FuncRef(_) => { 327 // TODO: we can't trust the GC heap, which means we can't read 328 // native VMFuncRef pointers out of it and trust them. That 329 // means we need to do the same side table kind of thing we do 330 // with `externref` host data here. This isn't implemented yet. 331 todo!("funcrefs in GC objects") 332 } 333 } 334 Ok(()) 335 } 336 } 337 338 /// A plain-old-data type that can be stored in a `ValType` or a `StorageType`. 339 /// 340 /// Safety: implementations must be POD and all bit patterns must be valid. 341 pub trait PodValType<const SIZE: usize>: Copy { 342 /// Read an instance of `Self` from the given little-endian bytes. 343 fn read_le(le_bytes: &[u8; SIZE]) -> Self; 344 345 /// Write `self` into the given memory location, as little-endian bytes. 346 fn write_le(&self, into: &mut [u8; SIZE]); 347 } 348 349 macro_rules! impl_pod_val_type { 350 ( $( $t:ty , )* ) => { 351 $( 352 impl PodValType<{core::mem::size_of::<$t>()}> for $t { 353 fn read_le(le_bytes: &[u8; core::mem::size_of::<$t>()]) -> Self { 354 <$t>::from_le_bytes(*le_bytes) 355 } 356 fn write_le(&self, into: &mut [u8; core::mem::size_of::<$t>()]) { 357 *into = self.to_le_bytes(); 358 } 359 } 360 )* 361 }; 362 } 363 364 impl_pod_val_type! { 365 u8, 366 u16, 367 u32, 368 u64, 369 i8, 370 i16, 371 i32, 372 i64, 373 } 374 375 impl PodValType<{ mem::size_of::<V128>() }> for V128 { 376 fn read_le(le_bytes: &[u8; mem::size_of::<V128>()]) -> Self { 377 u128::from_le_bytes(*le_bytes).into() 378 } 379 fn write_le(&self, into: &mut [u8; mem::size_of::<V128>()]) { 380 *into = self.as_u128().to_le_bytes(); 381 } 382 } 383 384 /// The backing storage for a GC-managed struct. 385 /// 386 /// Methods on this type do not, generally, check against things like type 387 /// mismatches or that the given offset to read from even falls on a field 388 /// boundary. Omitting these checks is memory safe, due to our untrusted, 389 /// indexed GC heaps. Providing incorrect offsets will result in general 390 /// incorrectness, such as wrong answers or even panics, however. 391 /// 392 /// Finally, these methods *will* panic on out-of-bounds accesses, either out of 393 /// the GC heap's bounds or out of this struct's bounds. The former is necessary 394 /// for preserving the memory safety of indexed GC heaps in the face of (for 395 /// example) collector bugs, but the latter is just a defensive technique to 396 /// catch bugs early and prevent action at a distance as much as possible. 397 pub struct VMStructDataMut<'a> { 398 data: &'a mut [u8], 399 } 400 401 macro_rules! impl_pod_methods { 402 ( $( $t:ty, $read:ident, $write:ident; )* ) => { 403 $( 404 /// Read from a ` 405 #[doc = stringify!($t)] 406 /// ` field in this struct. 407 /// 408 /// Panics on out-of-bounds accesses. 409 #[inline] 410 pub fn $read(&self, offset: u32) -> $t { 411 self.read_pod::<{ mem::size_of::<$t>() }, $t>(offset) 412 } 413 414 /// Write to a ` 415 #[doc = stringify!($t)] 416 /// ` field in this struct. 417 /// 418 /// Panics on out-of-bounds accesses. 419 #[inline] 420 pub fn $write(&mut self, offset: u32, val: $t) { 421 self.write_pod::<{ mem::size_of::<$t>() }, $t>(offset, val); 422 } 423 )* 424 }; 425 } 426 427 impl<'a> VMStructDataMut<'a> { 428 /// Construct a `VMStructDataMut` from the given slice of bytes. 429 #[inline] 430 pub fn new(data: &'a mut [u8]) -> Self { 431 Self { data } 432 } 433 434 /// Read a POD field out of this struct. 435 /// 436 /// Panics on out-of-bounds accesses. 437 /// 438 /// Don't generally use this method, use `read_u8`, `read_i64`, 439 /// etc... instead. 440 #[inline] 441 fn read_pod<const N: usize, T>(&self, offset: u32) -> T 442 where 443 T: PodValType<N>, 444 { 445 assert_eq!(N, mem::size_of::<T>()); 446 let offset = usize::try_from(offset).unwrap(); 447 let end = offset.checked_add(N).unwrap(); 448 let bytes = self.data.get(offset..end).expect("out of bounds field"); 449 T::read_le(bytes.try_into().unwrap()) 450 } 451 452 /// Read a POD field out of this struct. 453 /// 454 /// Panics on out-of-bounds accesses. 455 /// 456 /// Don't generally use this method, use `write_u8`, `write_i64`, 457 /// etc... instead. 458 #[inline] 459 fn write_pod<const N: usize, T>(&mut self, offset: u32, val: T) 460 where 461 T: PodValType<N>, 462 { 463 assert_eq!(N, mem::size_of::<T>()); 464 let offset = usize::try_from(offset).unwrap(); 465 let end = offset.checked_add(N).unwrap(); 466 let into = self.data.get_mut(offset..end).expect("out of bounds field"); 467 val.write_le(into.try_into().unwrap()); 468 } 469 470 impl_pod_methods! { 471 u8, read_u8, write_u8; 472 u16, read_u16, write_u16; 473 u32, read_u32, write_u32; 474 u64, read_u64, write_u64; 475 i8, read_i8, write_i8; 476 i16, read_i16, write_i16; 477 i32, read_i32, write_i32; 478 i64, read_i64, write_i64; 479 V128, read_v128, write_v128; 480 } 481 } 482