1 //! Infrastructure for managing "host tables" of resources in the component 2 //! model. 3 //! 4 //! This module is mostly an implementation detail of `ResourceAny` where it 5 //! tracks the ability for the host to have a handle to any resource, be it 6 //! either guest-defined or host-defined. The `HostResourceData` type is 7 //! stored inside of a `Store<T>` and `HostResourceTables` provides temporary 8 //! access to this information. 9 //! 10 //! This provides operations in terms of "lift" and "lower" terminology where 11 //! the idea is that a resource is "lowered" to the host meaning that metadata 12 //! about the resource is inserted into `HostResourceData` through 13 //! `HostResourceTables`. This produces a `HostResourceIndex` which can 14 //! then be used to access it later on. The `HostResourceIndex` can later be 15 //! used to "lift" the resource from the table back into its constituent parts 16 //! or remove it from the table. 17 //! 18 //! This tracks borrow state where you can, for example, lift a borrow from a 19 //! `HostResourceIndex` that represents an "own". This will activate 20 //! borrow-tracking infrastructure to ensure that the borrow is deleted before 21 //! the own is removed or otherwise taken out. 22 23 use crate::prelude::*; 24 use crate::runtime::component::RuntimeInstance; 25 use crate::runtime::vm::component::{ResourceTables, TypedResource, TypedResourceIndex}; 26 use crate::runtime::vm::{SendSyncPtr, VMFuncRef}; 27 use crate::store::StoreOpaque; 28 use core::ptr::NonNull; 29 use wasmtime_environ::component::TypeResourceTableIndex; 30 31 /// Metadata tracking the state of resources within a `Store`. 32 /// 33 /// This is a borrowed structure created from a `Store` piecemeal from below. 34 /// The `ResourceTables` type holds most of the raw information and this 35 /// structure tacks on a reference to `HostResourceData` to track generation 36 /// numbers of host indices. 37 pub struct HostResourceTables<'a> { 38 tables: ResourceTables<'a>, 39 host_resource_data: &'a mut HostResourceData, 40 } 41 42 /// Metadata for host-owned resources owned within a `Store`. 43 /// 44 /// This metadata is used to prevent the ABA problem with indices handed out as 45 /// part of `Resource` and `ResourceAny`. Those structures are `Copy` meaning 46 /// that it's easy to reuse them, possibly accidentally. To prevent issues in 47 /// the host Wasmtime attaches both an index (within `ResourceTables`) as well 48 /// as a 32-bit generation counter onto each `HostResourceIndex` which the host 49 /// actually holds in `Resource` and `ResourceAny`. 50 /// 51 /// This structure holds a list which is a parallel list to the "list of reps" 52 /// that's stored within `ResourceTables` elsewhere in the `Store`. This 53 /// parallel list holds the last known generation of each element in the table. 54 /// The generation is then compared on access to make sure it's the same. 55 /// 56 /// Whenever a slot in the table is allocated the `cur_generation` field is 57 /// pushed at the corresponding index of `generation_of_table_slot`. Whenever 58 /// a field is accessed the current value of `generation_of_table_slot` is 59 /// checked against the generation of the index. Whenever a slot is deallocated 60 /// the generation is incremented. Put together this means that any access of a 61 /// deallocated slot should deterministically provide an error. 62 #[derive(Default)] 63 pub struct HostResourceData { 64 cur_generation: u32, 65 table_slot_metadata: Vec<TableSlot>, 66 } 67 68 #[derive(Copy, Clone)] 69 pub struct TableSlot { 70 generation: u32, 71 pub(super) instance: Option<RuntimeInstance>, 72 pub(super) dtor: Option<SendSyncPtr<VMFuncRef>>, 73 } 74 75 /// Host representation of an index into a table slot. 76 /// 77 /// This is morally (u32, u32) but is encoded as a 64-bit integer. The low 78 /// 32-bits are the table index and the upper 32-bits are the generation 79 /// counter. 80 #[derive(PartialEq, Eq, Debug, Copy, Clone)] 81 #[repr(transparent)] 82 pub struct HostResourceIndex(u64); 83 84 impl HostResourceIndex { 85 pub(super) fn new(idx: u32, generation: u32) -> HostResourceIndex { 86 HostResourceIndex(u64::from(idx) | (u64::from(generation) << 32)) 87 } 88 89 pub(super) const fn index(&self) -> u32 { 90 (self.0 & 0xffffffff) as u32 91 } 92 93 pub(super) const fn generation(&self) -> u32 { 94 (self.0 >> 32) as u32 95 } 96 } 97 98 impl<'a> HostResourceTables<'a> { 99 pub fn new_host(store: &'a mut StoreOpaque) -> HostResourceTables<'a> { 100 let (tables, data) = store.component_resource_tables_and_host_resource_data(None); 101 HostResourceTables::from_parts(tables, data) 102 } 103 104 pub fn from_parts( 105 tables: ResourceTables<'a>, 106 host_resource_data: &'a mut HostResourceData, 107 ) -> Self { 108 HostResourceTables { 109 tables, 110 host_resource_data, 111 } 112 } 113 114 /// Lifts an `own` resource that resides in the host's tables at the `idx` 115 /// specified into its `rep`. 116 /// 117 /// # Errors 118 /// 119 /// Returns an error if `idx` doesn't point to a valid owned resource, or 120 /// if `idx` can't be lifted as an `own` (e.g. it has active borrows). 121 pub fn host_resource_lift_own(&mut self, idx: HostResourceIndex) -> Result<u32> { 122 let (idx, _) = self.validate_host_index(idx, true)?; 123 self.tables.resource_lift_own(TypedResourceIndex::Host(idx)) 124 } 125 126 /// See [`HostResourceTables::host_resource_lift_own`]. 127 pub fn host_resource_lift_borrow(&mut self, idx: HostResourceIndex) -> Result<u32> { 128 let (idx, _) = self.validate_host_index(idx, false)?; 129 self.tables 130 .resource_lift_borrow(TypedResourceIndex::Host(idx)) 131 } 132 133 /// Lowers an `own` resource to be owned by the host. 134 /// 135 /// This returns a new index into the host's set of resource tables which 136 /// will point to the `rep` specified. The returned index is suitable for 137 /// conversion into either [`Resource`] or [`ResourceAny`]. 138 /// 139 /// The `dtor` and instance `instance` are specified as well to know what 140 /// destructor to run when this resource is destroyed. 141 pub fn host_resource_lower_own( 142 &mut self, 143 rep: u32, 144 dtor: Option<NonNull<VMFuncRef>>, 145 instance: Option<RuntimeInstance>, 146 ) -> Result<HostResourceIndex> { 147 let idx = self.tables.resource_lower_own(TypedResource::Host(rep))?; 148 Ok(self.new_host_index(idx, dtor, instance)) 149 } 150 151 /// See [`HostResourceTables::host_resource_lower_own`]. 152 pub fn host_resource_lower_borrow(&mut self, rep: u32) -> Result<HostResourceIndex> { 153 let idx = self 154 .tables 155 .resource_lower_borrow(TypedResource::Host(rep))?; 156 Ok(self.new_host_index(idx, None, None)) 157 } 158 159 /// Validates that `idx` is still valid for the host tables, notably 160 /// ensuring that the generation listed in `idx` is the same as the 161 /// last recorded generation of the slot itself. 162 /// 163 /// The `is_removal` option indicates whether or not this table access will 164 /// end up removing the element from the host table. In such a situation the 165 /// current generation number is incremented. 166 fn validate_host_index( 167 &mut self, 168 idx: HostResourceIndex, 169 is_removal: bool, 170 ) -> Result<(u32, Option<TableSlot>)> { 171 let actual = usize::try_from(idx.index()) 172 .ok() 173 .and_then(|i| self.host_resource_data.table_slot_metadata.get(i).copied()); 174 175 // If `idx` is out-of-bounds then skip returning an error. In such a 176 // situation the operation that this is guarding will return a more 177 // precise error, such as a lift operation. 178 if let Some(actual) = actual { 179 if actual.generation != idx.generation() { 180 bail!("host-owned resource is being used with the wrong type"); 181 } 182 } 183 184 // Bump the current generation of this is a removal to ensure any 185 // future item placed in this slot can't be pointed to by the `idx` 186 // provided above. 187 if is_removal { 188 self.host_resource_data.cur_generation += 1; 189 } 190 191 Ok((idx.index(), actual)) 192 } 193 194 /// Creates a new `HostResourceIndex` which will point to the raw table 195 /// slot provided by `idx`. 196 /// 197 /// This will register metadata necessary to track the current generation 198 /// in the returned `HostResourceIndex` as well. 199 fn new_host_index( 200 &mut self, 201 idx: u32, 202 dtor: Option<NonNull<VMFuncRef>>, 203 instance: Option<RuntimeInstance>, 204 ) -> HostResourceIndex { 205 let list = &mut self.host_resource_data.table_slot_metadata; 206 let info = TableSlot { 207 generation: self.host_resource_data.cur_generation, 208 instance, 209 dtor: dtor.map(SendSyncPtr::new), 210 }; 211 match list.get_mut(idx as usize) { 212 Some(slot) => *slot = info, 213 None => { 214 // Resource handles start at 1, not zero, so push two elements 215 // for the first resource handle. 216 if list.is_empty() { 217 assert_eq!(idx, 1); 218 list.push(TableSlot { 219 generation: 0, 220 instance: None, 221 dtor: None, 222 }); 223 } 224 assert_eq!(idx as usize, list.len()); 225 list.push(info); 226 } 227 } 228 229 HostResourceIndex::new(idx, info.generation) 230 } 231 232 /// Drops a host-owned resource from host tables. 233 /// 234 /// This method will attempt to interpret `idx` as pointing to either a 235 /// `borrow` or `own` resource with the `expected` type specified. This 236 /// method will then return the underlying `rep` if it points to an `own` 237 /// resource which can then be further processed for destruction. 238 /// 239 /// # Errors 240 /// 241 /// Returns an error if `idx` doesn't point to a valid resource, points to 242 /// an `own` with active borrows, or if it doesn't have the type `expected` 243 /// in the host tables. 244 pub(super) fn host_resource_drop( 245 &mut self, 246 idx: HostResourceIndex, 247 ) -> Result<Option<(u32, TableSlot)>> { 248 let (idx, slot) = self.validate_host_index(idx, true)?; 249 match self.tables.resource_drop(TypedResourceIndex::Host(idx))? { 250 Some(rep) => Ok(Some((rep, slot.unwrap()))), 251 None => Ok(None), 252 } 253 } 254 255 /// Lowers an `own` resource into the guest, converting the `rep` specified 256 /// into a guest-local index. 257 /// 258 /// The `ty` provided is which table to put this into. 259 pub fn guest_resource_lower_own( 260 &mut self, 261 rep: u32, 262 ty: TypeResourceTableIndex, 263 ) -> Result<u32> { 264 self.tables 265 .resource_lower_own(TypedResource::Component { ty, rep }) 266 } 267 268 /// Lowers a `borrow` resource into the guest, converting the `rep` 269 /// specified into a guest-local index. 270 /// 271 /// The `ty` provided is which table to put this into. 272 /// 273 /// Note that this cannot be used in isolation because lowering a borrow 274 /// into a guest has a special case where `rep` is returned directly if `ty` 275 /// belongs to the component being lowered into. That property must be 276 /// handled by the caller of this function. 277 pub fn guest_resource_lower_borrow( 278 &mut self, 279 rep: u32, 280 ty: TypeResourceTableIndex, 281 ) -> Result<u32> { 282 self.tables 283 .resource_lower_borrow(TypedResource::Component { ty, rep }) 284 } 285 286 /// Lifts an `own` resource from the `idx` specified from the table `ty`. 287 /// 288 /// This will lookup the appropriate table in the guest and return the `rep` 289 /// corresponding to `idx` if it's valid. 290 pub fn guest_resource_lift_own( 291 &mut self, 292 index: u32, 293 ty: TypeResourceTableIndex, 294 ) -> Result<u32> { 295 self.tables 296 .resource_lift_own(TypedResourceIndex::Component { ty, index }) 297 } 298 299 /// Lifts a `borrow` resource from the `idx` specified from the table `ty`. 300 /// 301 /// This will lookup the appropriate table in the guest and return the `rep` 302 /// corresponding to `idx` if it's valid. 303 pub fn guest_resource_lift_borrow( 304 &mut self, 305 index: u32, 306 ty: TypeResourceTableIndex, 307 ) -> Result<u32> { 308 self.tables 309 .resource_lift_borrow(TypedResourceIndex::Component { ty, index }) 310 } 311 312 /// Completes a call into the component instance, validating that it's ok to 313 /// complete by ensuring the are no remaining active borrows. 314 #[inline] 315 pub fn validate_scope_exit(&mut self) -> Result<()> { 316 self.tables.validate_scope_exit() 317 } 318 } 319