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, 7 }; 8 use core::fmt; 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().gc_object_data(self.as_gc_ref()); 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()?.gc_object_data(self.as_gc_ref()); 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()?.gc_object_data(self.as_gc_ref()); 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()?.gc_object_data(self.as_gc_ref()); 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 .gc_object_data(self.as_gc_ref()) 276 .write_i8(offset, i as i8), 277 Val::I32(i) if ty.is_i16() => store 278 .gc_store_mut()? 279 .gc_object_data(self.as_gc_ref()) 280 .write_i16(offset, i as i16), 281 Val::I32(i) => store 282 .gc_store_mut()? 283 .gc_object_data(self.as_gc_ref()) 284 .write_i32(offset, i), 285 Val::I64(i) => store 286 .gc_store_mut()? 287 .gc_object_data(self.as_gc_ref()) 288 .write_i64(offset, i), 289 Val::F32(f) => store 290 .gc_store_mut()? 291 .gc_object_data(self.as_gc_ref()) 292 .write_u32(offset, f), 293 Val::F64(f) => store 294 .gc_store_mut()? 295 .gc_object_data(self.as_gc_ref()) 296 .write_u64(offset, f), 297 Val::V128(v) => store 298 .gc_store_mut()? 299 .gc_object_data(self.as_gc_ref()) 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 .gc_object_data(self.as_gc_ref()) 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 .gc_object_data(self.as_gc_ref()) 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