1 //! Implementation of the canonical-ABI related intrinsics for resources in the 2 //! component model. 3 //! 4 //! This module contains all the relevant gory details of the 5 //! component model related to lifting and lowering resources. For example 6 //! intrinsics like `resource.new` will bottom out in calling this file, and 7 //! this is where resource tables are actually defined and modified. 8 //! 9 //! The main types in this file are: 10 //! 11 //! * `ResourceTables` - the "here's everything" context which is required to 12 //! perform canonical ABI operations. 13 //! 14 //! * `ResourceTable` - an individual instance of a table of resources, 15 //! basically "just a slab" though. 16 //! 17 //! * `CallContexts` - store-local information about active calls and borrows 18 //! and runtime state tracking that to ensure that everything is handled 19 //! correctly. 20 //! 21 //! Individual operations are exposed through methods on `ResourceTables` for 22 //! lifting/lowering/etc. This does mean though that some other fiddly bits 23 //! about ABI details can be found in lifting/lowering throughout Wasmtime, 24 //! namely in the `Resource<T>` and `ResourceAny` types. 25 26 use crate::prelude::*; 27 use core::mem; 28 use wasmtime_environ::component::TypeResourceTableIndex; 29 use wasmtime_environ::PrimaryMap; 30 31 /// The maximum handle value is specified in 32 /// <https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md> 33 /// currently and keeps the upper bit free for use in the component. 34 const MAX_RESOURCE_HANDLE: u32 = 1 << 30; 35 36 /// Contextual state necessary to perform resource-related operations. 37 /// 38 /// This state a bit odd since it has a few optional bits, but the idea is that 39 /// whenever this is constructed the bits required to perform operations are 40 /// always `Some`. For example: 41 /// 42 /// * During lifting and lowering both `tables` and `host_table` are `Some`. 43 /// * During wasm's own intrinsics only `tables` is `Some`. 44 /// * During embedder-invoked resource destruction calls only `host_table` is 45 /// `Some`. 46 /// 47 /// This is all packaged up into one state though to make it easier to operate 48 /// on and to centralize handling of the state related to resources due to how 49 /// critical it is for correctness. 50 pub struct ResourceTables<'a> { 51 /// Runtime state for all resources defined in a component. 52 /// 53 /// This is required whenever a `TypeResourceTableIndex` is provided as it's 54 /// the lookup where that happens. Not present during embedder-originating 55 /// operations though such as `ResourceAny::resource_drop` which won't 56 /// consult this table as it's only operating over the host table. 57 pub tables: Option<&'a mut PrimaryMap<TypeResourceTableIndex, ResourceTable>>, 58 59 /// Runtime state for resources currently owned by the host. 60 /// 61 /// This is the single table used by the host stored within `Store<T>`. Host 62 /// resources will point into this and effectively have the same semantics 63 /// as-if they're in-component resources. The major distinction though is 64 /// that this is a heterogeneous table instead of only containing a single 65 /// type. 66 pub host_table: Option<&'a mut ResourceTable>, 67 68 /// Scope information about calls actively in use to track information such 69 /// as borrow counts. 70 pub calls: &'a mut CallContexts, 71 } 72 73 /// An individual slab of resources used for a single table within a component. 74 /// Not much fancier than a general slab data structure. 75 #[derive(Default)] 76 pub struct ResourceTable { 77 /// Next slot to allocate, or `self.slots.len()` if they're all full. 78 next: u32, 79 /// Runtime state of all slots. 80 slots: Vec<Slot>, 81 } 82 83 enum Slot { 84 /// This slot is free and points to the next free slot, forming a linked 85 /// list of free slots. 86 Free { next: u32 }, 87 88 /// This slot contains an owned resource with the listed representation. 89 /// 90 /// The `lend_count` tracks how many times this has been lent out as a 91 /// `borrow` and if nonzero this can't be removed. 92 Own { rep: u32, lend_count: u32 }, 93 94 /// This slot contains a `borrow` resource that's connected to the `scope` 95 /// provided. The `rep` is listed and dropping this borrow will decrement 96 /// the borrow count of the `scope`. 97 Borrow { rep: u32, scope: usize }, 98 } 99 100 /// State related to borrows and calls within a component. 101 /// 102 /// This is created once per `Store` and updated and modified throughout the 103 /// lifetime of the store. This primarily tracks borrow counts and what slots 104 /// should be updated when calls go out of scope. 105 #[derive(Default)] 106 pub struct CallContexts { 107 scopes: Vec<CallContext>, 108 } 109 110 #[derive(Default)] 111 struct CallContext { 112 lenders: Vec<Lender>, 113 borrow_count: u32, 114 } 115 116 #[derive(Copy, Clone)] 117 struct Lender { 118 ty: Option<TypeResourceTableIndex>, 119 idx: u32, 120 } 121 122 impl ResourceTables<'_> { 123 fn table(&mut self, ty: Option<TypeResourceTableIndex>) -> &mut ResourceTable { 124 match ty { 125 None => self.host_table.as_mut().unwrap(), 126 Some(idx) => &mut self.tables.as_mut().unwrap()[idx], 127 } 128 } 129 130 /// Implementation of the `resource.new` canonical intrinsic. 131 /// 132 /// Note that this is the same as `resource_lower_own`. 133 pub fn resource_new(&mut self, ty: Option<TypeResourceTableIndex>, rep: u32) -> Result<u32> { 134 self.table(ty).insert(Slot::Own { rep, lend_count: 0 }) 135 } 136 137 /// Implementation of the `resource.rep` canonical intrinsic. 138 /// 139 /// This one's one of the simpler ones: "just get the rep please" 140 pub fn resource_rep(&mut self, ty: Option<TypeResourceTableIndex>, idx: u32) -> Result<u32> { 141 self.table(ty).rep(idx) 142 } 143 144 /// Implementation of the `resource.drop` canonical intrinsic minus the 145 /// actual invocation of the destructor. 146 /// 147 /// This will drop the handle at the `idx` specified, removing it from the 148 /// specified table. This operation can fail if: 149 /// 150 /// * The index is invalid. 151 /// * The index points to an `own` resource which has active borrows. 152 /// 153 /// Otherwise this will return `Some(rep)` if the destructor for `rep` needs 154 /// to run. If `None` is returned then that means a `borrow` handle was 155 /// removed and no destructor is necessary. 156 pub fn resource_drop( 157 &mut self, 158 ty: Option<TypeResourceTableIndex>, 159 idx: u32, 160 ) -> Result<Option<u32>> { 161 match self.table(ty).remove(idx)? { 162 Slot::Own { rep, lend_count: 0 } => Ok(Some(rep)), 163 Slot::Own { .. } => bail!("cannot remove owned resource while borrowed"), 164 Slot::Borrow { scope, .. } => { 165 self.calls.scopes[scope].borrow_count -= 1; 166 Ok(None) 167 } 168 Slot::Free { .. } => unreachable!(), 169 } 170 } 171 172 /// Inserts a new "own" handle into the specified table. 173 /// 174 /// This will insert the specified representation into the specified type 175 /// table. 176 /// 177 /// Note that this operation is infallible, and additionally that this is 178 /// the same as `resource_new` implementation-wise. 179 /// 180 /// This is an implementation of the canonical ABI `lower_own` function. 181 pub fn resource_lower_own( 182 &mut self, 183 ty: Option<TypeResourceTableIndex>, 184 rep: u32, 185 ) -> Result<u32> { 186 self.table(ty).insert(Slot::Own { rep, lend_count: 0 }) 187 } 188 189 /// Attempts to remove an "own" handle from the specified table and its 190 /// index. 191 /// 192 /// This operation will fail if `idx` is invalid, if it's a `borrow` handle, 193 /// or if the own handle has currently been "lent" as a borrow. 194 /// 195 /// This is an implementation of the canonical ABI `lift_own` function. 196 pub fn resource_lift_own( 197 &mut self, 198 ty: Option<TypeResourceTableIndex>, 199 idx: u32, 200 ) -> Result<u32> { 201 match self.table(ty).remove(idx)? { 202 Slot::Own { rep, lend_count: 0 } => Ok(rep), 203 Slot::Own { .. } => bail!("cannot remove owned resource while borrowed"), 204 Slot::Borrow { .. } => bail!("cannot lift own resource from a borrow"), 205 Slot::Free { .. } => unreachable!(), 206 } 207 } 208 209 /// Extracts the underlying resource representation by lifting a "borrow" 210 /// from the tables. 211 /// 212 /// This primarily employs dynamic tracking when a borrow is created from an 213 /// "own" handle to ensure that the "own" handle isn't dropped while the 214 /// borrow is active and additionally that when the current call scope 215 /// returns the lend operation is undone. 216 /// 217 /// This is an implementation of the canonical ABI `lift_borrow` function. 218 pub fn resource_lift_borrow( 219 &mut self, 220 ty: Option<TypeResourceTableIndex>, 221 idx: u32, 222 ) -> Result<u32> { 223 match self.table(ty).get_mut(idx)? { 224 Slot::Own { rep, lend_count } => { 225 // The decrement to this count happens in `exit_call`. 226 *lend_count = lend_count.checked_add(1).unwrap(); 227 let rep = *rep; 228 let scope = self.calls.scopes.last_mut().unwrap(); 229 scope.lenders.push(Lender { ty, idx }); 230 Ok(rep) 231 } 232 Slot::Borrow { rep, .. } => Ok(*rep), 233 Slot::Free { .. } => unreachable!(), 234 } 235 } 236 237 /// Records a new `borrow` resource with the given representation within the 238 /// current call scope. 239 /// 240 /// This requires that a call scope is active. Additionally the number of 241 /// active borrows in the latest scope will be increased and must be 242 /// decreased through a future use of `resource_drop` before the current 243 /// call scope exits. 244 /// 245 /// This some of the implementation of the canonical ABI `lower_borrow` 246 /// function. The other half of this implementation is located on 247 /// `VMComponentContext` which handles the special case of avoiding borrow 248 /// tracking entirely. 249 pub fn resource_lower_borrow( 250 &mut self, 251 ty: Option<TypeResourceTableIndex>, 252 rep: u32, 253 ) -> Result<u32> { 254 let scope = self.calls.scopes.len() - 1; 255 let borrow_count = &mut self.calls.scopes.last_mut().unwrap().borrow_count; 256 *borrow_count = borrow_count.checked_add(1).unwrap(); 257 self.table(ty).insert(Slot::Borrow { rep, scope }) 258 } 259 260 /// Enters a new calling context, starting a fresh count of borrows and 261 /// such. 262 #[inline] 263 pub fn enter_call(&mut self) { 264 self.calls.scopes.push(CallContext::default()); 265 } 266 267 /// Exits the previously pushed calling context. 268 /// 269 /// This requires all information to be available within this 270 /// `ResourceTables` and is only called during lowering/lifting operations 271 /// at this time. 272 #[inline] 273 pub fn exit_call(&mut self) -> Result<()> { 274 let cx = self.calls.scopes.pop().unwrap(); 275 if cx.borrow_count > 0 { 276 bail!("borrow handles still remain at the end of the call") 277 } 278 for lender in cx.lenders.iter() { 279 // Note the panics here which should never get triggered in theory 280 // due to the dynamic tracking of borrows and such employed for 281 // resources. 282 match self.table(lender.ty).get_mut(lender.idx).unwrap() { 283 Slot::Own { lend_count, .. } => { 284 *lend_count -= 1; 285 } 286 _ => unreachable!(), 287 } 288 } 289 Ok(()) 290 } 291 } 292 293 impl ResourceTable { 294 fn insert(&mut self, new: Slot) -> Result<u32> { 295 let next = self.next as usize; 296 if next == self.slots.len() { 297 self.slots.push(Slot::Free { 298 next: self.next.checked_add(1).unwrap(), 299 }); 300 } 301 let ret = self.next; 302 self.next = match mem::replace(&mut self.slots[next], new) { 303 Slot::Free { next } => next, 304 _ => unreachable!(), 305 }; 306 307 // The component model reserves index 0 as never allocatable so add one 308 // to the table index to start the numbering at 1 instead. Also note 309 // that the component model places an upper-limit per-table on the 310 // maximum allowed index. 311 let ret = ret + 1; 312 if ret >= MAX_RESOURCE_HANDLE { 313 bail!("cannot allocate another handle: index overflow"); 314 } 315 Ok(ret) 316 } 317 318 fn handle_index_to_table_index(&self, idx: u32) -> Option<usize> { 319 // NB: `idx` is decremented by one to account for the `+1` above during 320 // allocation. 321 let idx = idx.checked_sub(1)?; 322 usize::try_from(idx).ok() 323 } 324 325 fn rep(&self, idx: u32) -> Result<u32> { 326 let slot = self 327 .handle_index_to_table_index(idx) 328 .and_then(|i| self.slots.get(i)); 329 match slot { 330 None | Some(Slot::Free { .. }) => bail!("unknown handle index {idx}"), 331 Some(Slot::Own { rep, .. } | Slot::Borrow { rep, .. }) => Ok(*rep), 332 } 333 } 334 335 fn get_mut(&mut self, idx: u32) -> Result<&mut Slot> { 336 let slot = self 337 .handle_index_to_table_index(idx) 338 .and_then(|i| self.slots.get_mut(i)); 339 match slot { 340 None | Some(Slot::Free { .. }) => bail!("unknown handle index {idx}"), 341 Some(other) => Ok(other), 342 } 343 } 344 345 fn remove(&mut self, idx: u32) -> Result<Slot> { 346 let to_fill = Slot::Free { next: self.next }; 347 let ret = mem::replace(self.get_mut(idx)?, to_fill); 348 self.next = idx - 1; 349 Ok(ret) 350 } 351 } 352