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::error::Error; 28 use core::fmt; 29 use core::mem; 30 use wasmtime_environ::PrimaryMap; 31 use wasmtime_environ::component::{ 32 ComponentTypes, RuntimeComponentInstanceIndex, TypeResourceTableIndex, 33 }; 34 35 /// The maximum handle value is specified in 36 /// <https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md> 37 /// currently and keeps the upper bit free for use in the component. 38 const MAX_RESOURCE_HANDLE: u32 = 1 << 30; 39 40 /// Contextual state necessary to perform resource-related operations. 41 /// 42 /// This state a bit odd since it has a few optional bits, but the idea is that 43 /// whenever this is constructed the bits required to perform operations are 44 /// always `Some`. For example: 45 /// 46 /// * During lifting and lowering both `guest_table` and `host_table` are 47 /// `Some`. 48 /// * During wasm's own intrinsics only `guest_table` is `Some`. 49 /// * During embedder-invoked resource destruction calls only `host_table` is 50 /// `Some`. 51 /// 52 /// This is all packaged up into one state though to make it easier to operate 53 /// on and to centralize handling of the state related to resources due to how 54 /// critical it is for correctness. 55 pub struct ResourceTables<'a> { 56 /// Runtime state for all resources defined in a component. 57 /// 58 /// This is required whenever a `TypeResourceTableIndex`, for example, is 59 /// provided as it's the lookup where that happens. Not present during 60 /// embedder-originating operations though such as 61 /// `ResourceAny::resource_drop` which won't consult this table as it's 62 /// only operating over the host table. 63 pub guest: Option<( 64 &'a mut PrimaryMap<RuntimeComponentInstanceIndex, ResourceTable>, 65 &'a ComponentTypes, 66 )>, 67 68 /// Runtime state for resources currently owned by the host. 69 /// 70 /// This is the single table used by the host stored within `Store<T>`. Host 71 /// resources will point into this and effectively have the same semantics 72 /// as-if they're in-component resources. The major distinction though is 73 /// that this is a heterogeneous table instead of only containing a single 74 /// type. 75 pub host_table: Option<&'a mut ResourceTable>, 76 77 /// Scope information about calls actively in use to track information such 78 /// as borrow counts. 79 pub calls: &'a mut CallContexts, 80 } 81 82 /// An individual slab of resources used for a single table within a component. 83 /// Not much fancier than a general slab data structure. 84 #[derive(Default)] 85 pub struct ResourceTable { 86 /// Next slot to allocate, or `self.slots.len()` if they're all full. 87 next: u32, 88 /// Runtime state of all slots. 89 slots: Vec<Slot>, 90 } 91 92 /// Typed representation of a "rep" for a resource. 93 /// 94 /// All resources in the component model are stored in a single heterogeneous 95 /// table so this type is used to disambiguate what everything is. This is the 96 /// representation of a resource stored at-rest in memory. 97 #[derive(Debug)] 98 pub enum TypedResource { 99 /// A resource defined by the host. 100 /// 101 /// The meaning of the 32-bit integer here is up to the embedder, it 102 /// otherwise is not used within the runtime here. 103 Host(u32), 104 105 /// A resource defined within a component. 106 Component { 107 /// This is an integer supplied by the component itself when this 108 /// resource was created. Typically this is a pointer into linear 109 /// memory for a component. 110 rep: u32, 111 112 /// The type of this component resource. 113 /// 114 /// This is used, within the context of a component, to keep track of 115 /// what the type of `rep` is. This is then used when getting/removing 116 /// from the table to ensure that the guest does indeed have the right 117 /// permission to access this slot. 118 ty: TypeResourceTableIndex, 119 }, 120 } 121 122 impl TypedResource { 123 fn rep(&self, access_ty: &TypedResourceIndex) -> Result<u32> { 124 match (self, access_ty) { 125 (Self::Host(rep), TypedResourceIndex::Host(_)) => Ok(*rep), 126 (Self::Host(_), expected) => bail!(ResourceTypeMismatch { 127 expected: *expected, 128 found: "host resource", 129 }), 130 (Self::Component { rep, ty }, TypedResourceIndex::Component { ty: expected, .. }) => { 131 if ty == expected { 132 Ok(*rep) 133 } else { 134 bail!(ResourceTypeMismatch { 135 expected: *access_ty, 136 found: "a different guest-defined resource", 137 }) 138 } 139 } 140 (Self::Component { .. }, expected) => bail!(ResourceTypeMismatch { 141 expected: *expected, 142 found: "guest-defined resource", 143 }), 144 } 145 } 146 } 147 148 /// An index used to access a resource. 149 /// 150 /// This reflects how index operations are always accompanied not only with a 151 /// 32-bit index but additionally with a type. For example a `resource.drop` 152 /// intrinsic in a guest takes only a 32-bit integer argument, but it 153 /// inherently is used to only drop one type of resource which is additionally 154 /// ascribed here. 155 #[derive(Debug, Copy, Clone)] 156 pub enum TypedResourceIndex { 157 /// A host resource at the given index is being accessed. 158 Host(u32), 159 160 /// A guest resource is being accessed. 161 Component { 162 /// The index supplied by the guest being accessed. 163 index: u32, 164 165 /// The fully-specific type of this resource. 166 ty: TypeResourceTableIndex, 167 }, 168 } 169 170 impl TypedResourceIndex { 171 fn raw_index(&self) -> u32 { 172 match self { 173 Self::Host(index) | Self::Component { index, .. } => *index, 174 } 175 } 176 177 fn desc(&self) -> &'static str { 178 match self { 179 Self::Host(_) => "host resource", 180 Self::Component { .. } => "guest-defined resource", 181 } 182 } 183 } 184 185 enum Slot { 186 /// This slot is free and points to the next free slot, forming a linked 187 /// list of free slots. 188 Free { next: u32 }, 189 190 /// This slot contains an owned resource with the listed representation. 191 /// 192 /// The `lend_count` tracks how many times this has been lent out as a 193 /// `borrow` and if nonzero this can't be removed. 194 Own { 195 resource: TypedResource, 196 lend_count: u32, 197 }, 198 199 /// This slot contains a `borrow` resource that's connected to the `scope` 200 /// provided. The `rep` is listed and dropping this borrow will decrement 201 /// the borrow count of the `scope`. 202 Borrow { 203 resource: TypedResource, 204 scope: usize, 205 }, 206 } 207 208 /// State related to borrows and calls within a component. 209 /// 210 /// This is created once per `Store` and updated and modified throughout the 211 /// lifetime of the store. This primarily tracks borrow counts and what slots 212 /// should be updated when calls go out of scope. 213 #[derive(Default)] 214 pub struct CallContexts { 215 scopes: Vec<CallContext>, 216 } 217 218 #[derive(Default)] 219 struct CallContext { 220 lenders: Vec<TypedResourceIndex>, 221 borrow_count: u32, 222 } 223 224 impl ResourceTables<'_> { 225 fn table_for_resource(&mut self, resource: &TypedResource) -> &mut ResourceTable { 226 match resource { 227 TypedResource::Host(_) => self.host_table.as_mut().unwrap(), 228 TypedResource::Component { ty, .. } => { 229 let (tables, types) = self.guest.as_mut().unwrap(); 230 &mut tables[types[*ty].instance] 231 } 232 } 233 } 234 235 fn table_for_index(&mut self, index: &TypedResourceIndex) -> &mut ResourceTable { 236 match index { 237 TypedResourceIndex::Host(_) => self.host_table.as_mut().unwrap(), 238 TypedResourceIndex::Component { ty, .. } => { 239 let (tables, types) = self.guest.as_mut().unwrap(); 240 &mut tables[types[*ty].instance] 241 } 242 } 243 } 244 245 /// Implementation of the `resource.new` canonical intrinsic. 246 /// 247 /// Note that this is the same as `resource_lower_own`. 248 pub fn resource_new(&mut self, resource: TypedResource) -> Result<u32> { 249 self.table_for_resource(&resource).insert(Slot::Own { 250 resource, 251 lend_count: 0, 252 }) 253 } 254 255 /// Implementation of the `resource.rep` canonical intrinsic. 256 /// 257 /// This one's one of the simpler ones: "just get the rep please" 258 pub fn resource_rep(&mut self, index: TypedResourceIndex) -> Result<u32> { 259 self.table_for_index(&index).rep(index) 260 } 261 262 /// Implementation of the `resource.drop` canonical intrinsic minus the 263 /// actual invocation of the destructor. 264 /// 265 /// This will drop the handle at the `index` specified, removing it from 266 /// the specified table. This operation can fail if: 267 /// 268 /// * The index is invalid. 269 /// * The index points to an `own` resource which has active borrows. 270 /// * The index's type is mismatched with the entry in the table's type. 271 /// 272 /// Otherwise this will return `Some(rep)` if the destructor for `rep` needs 273 /// to run. If `None` is returned then that means a `borrow` handle was 274 /// removed and no destructor is necessary. 275 pub fn resource_drop(&mut self, index: TypedResourceIndex) -> Result<Option<u32>> { 276 match self.table_for_index(&index).remove(index)? { 277 Slot::Own { 278 resource, 279 lend_count: 0, 280 } => resource.rep(&index).map(Some), 281 Slot::Own { .. } => bail!("cannot remove owned resource while borrowed"), 282 Slot::Borrow { 283 scope, resource, .. 284 } => { 285 // Validate that this borrow has the correct type to ensure a 286 // trap is returned if this is a mis-typed `resource.drop`. 287 resource.rep(&index)?; 288 self.calls.scopes[scope].borrow_count -= 1; 289 Ok(None) 290 } 291 Slot::Free { .. } => unreachable!(), 292 } 293 } 294 295 /// Inserts a new "own" handle into the specified table. 296 /// 297 /// This will insert the specified representation into the specified type 298 /// table. 299 /// 300 /// Note that this operation is infallible, and additionally that this is 301 /// the same as `resource_new` implementation-wise. 302 /// 303 /// This is an implementation of the canonical ABI `lower_own` function. 304 pub fn resource_lower_own(&mut self, resource: TypedResource) -> Result<u32> { 305 self.table_for_resource(&resource).insert(Slot::Own { 306 resource, 307 lend_count: 0, 308 }) 309 } 310 311 /// Attempts to remove an "own" handle from the specified table and its 312 /// index. 313 /// 314 /// This operation will fail if `index` is invalid, if it's a `borrow` 315 /// handle, if the own handle has currently been "lent" as a borrow, or if 316 /// `index` has a different type in the table than the index. 317 /// 318 /// This is an implementation of the canonical ABI `lift_own` function. 319 pub fn resource_lift_own(&mut self, index: TypedResourceIndex) -> Result<u32> { 320 match self.table_for_index(&index).remove(index)? { 321 Slot::Own { 322 resource, 323 lend_count: 0, 324 } => resource.rep(&index), 325 Slot::Own { .. } => bail!("cannot remove owned resource while borrowed"), 326 Slot::Borrow { .. } => bail!("cannot lift own resource from a borrow"), 327 Slot::Free { .. } => unreachable!(), 328 } 329 } 330 331 /// Extracts the underlying resource representation by lifting a "borrow" 332 /// from the tables. 333 /// 334 /// This primarily employs dynamic tracking when a borrow is created from an 335 /// "own" handle to ensure that the "own" handle isn't dropped while the 336 /// borrow is active and additionally that when the current call scope 337 /// returns the lend operation is undone. 338 /// 339 /// This is an implementation of the canonical ABI `lift_borrow` function. 340 pub fn resource_lift_borrow(&mut self, index: TypedResourceIndex) -> Result<u32> { 341 match self.table_for_index(&index).get_mut(index)? { 342 Slot::Own { 343 resource, 344 lend_count, 345 } => { 346 let rep = resource.rep(&index)?; 347 // The decrement to this count happens in `exit_call`. 348 *lend_count = lend_count.checked_add(1).unwrap(); 349 let scope = self.calls.scopes.last_mut().unwrap(); 350 scope.lenders.push(index); 351 Ok(rep) 352 } 353 Slot::Borrow { resource, .. } => resource.rep(&index), 354 Slot::Free { .. } => unreachable!(), 355 } 356 } 357 358 /// Records a new `borrow` resource with the given representation within the 359 /// current call scope. 360 /// 361 /// This requires that a call scope is active. Additionally the number of 362 /// active borrows in the latest scope will be increased and must be 363 /// decreased through a future use of `resource_drop` before the current 364 /// call scope exits. 365 /// 366 /// This some of the implementation of the canonical ABI `lower_borrow` 367 /// function. The other half of this implementation is located on 368 /// `VMComponentContext` which handles the special case of avoiding borrow 369 /// tracking entirely. 370 pub fn resource_lower_borrow(&mut self, resource: TypedResource) -> Result<u32> { 371 let scope = self.calls.scopes.len() - 1; 372 let borrow_count = &mut self.calls.scopes.last_mut().unwrap().borrow_count; 373 *borrow_count = borrow_count.checked_add(1).unwrap(); 374 self.table_for_resource(&resource) 375 .insert(Slot::Borrow { resource, scope }) 376 } 377 378 /// Enters a new calling context, starting a fresh count of borrows and 379 /// such. 380 #[inline] 381 pub fn enter_call(&mut self) { 382 self.calls.scopes.push(CallContext::default()); 383 } 384 385 /// Exits the previously pushed calling context. 386 /// 387 /// This requires all information to be available within this 388 /// `ResourceTables` and is only called during lowering/lifting operations 389 /// at this time. 390 #[inline] 391 pub fn exit_call(&mut self) -> Result<()> { 392 let cx = self.calls.scopes.pop().unwrap(); 393 if cx.borrow_count > 0 { 394 bail!("borrow handles still remain at the end of the call") 395 } 396 for lender in cx.lenders.iter() { 397 // Note the panics here which should never get triggered in theory 398 // due to the dynamic tracking of borrows and such employed for 399 // resources. 400 match self.table_for_index(lender).get_mut(*lender).unwrap() { 401 Slot::Own { lend_count, .. } => { 402 *lend_count -= 1; 403 } 404 _ => unreachable!(), 405 } 406 } 407 Ok(()) 408 } 409 } 410 411 impl ResourceTable { 412 fn insert(&mut self, new: Slot) -> Result<u32> { 413 let next = self.next as usize; 414 if next == self.slots.len() { 415 self.slots.push(Slot::Free { 416 next: self.next.checked_add(1).unwrap(), 417 }); 418 } 419 let ret = self.next; 420 self.next = match mem::replace(&mut self.slots[next], new) { 421 Slot::Free { next } => next, 422 _ => unreachable!(), 423 }; 424 425 // The component model reserves index 0 as never allocatable so add one 426 // to the table index to start the numbering at 1 instead. Also note 427 // that the component model places an upper-limit per-table on the 428 // maximum allowed index. 429 let ret = ret + 1; 430 if ret >= MAX_RESOURCE_HANDLE { 431 bail!("cannot allocate another handle: index overflow"); 432 } 433 Ok(ret) 434 } 435 436 fn handle_index_to_table_index(&self, idx: u32) -> Option<usize> { 437 // NB: `idx` is decremented by one to account for the `+1` above during 438 // allocation. 439 let idx = idx.checked_sub(1)?; 440 usize::try_from(idx).ok() 441 } 442 443 fn rep(&self, idx: TypedResourceIndex) -> Result<u32> { 444 let slot = self 445 .handle_index_to_table_index(idx.raw_index()) 446 .and_then(|i| self.slots.get(i)); 447 match slot { 448 None | Some(Slot::Free { .. }) => bail!(UnknownHandleIndex(idx)), 449 Some(Slot::Own { resource, .. } | Slot::Borrow { resource, .. }) => resource.rep(&idx), 450 } 451 } 452 453 fn get_mut(&mut self, idx: TypedResourceIndex) -> Result<&mut Slot> { 454 let slot = self 455 .handle_index_to_table_index(idx.raw_index()) 456 .and_then(|i| self.slots.get_mut(i)); 457 match slot { 458 None | Some(Slot::Free { .. }) => bail!(UnknownHandleIndex(idx)), 459 Some(other) => Ok(other), 460 } 461 } 462 463 fn remove(&mut self, idx: TypedResourceIndex) -> Result<Slot> { 464 let to_fill = Slot::Free { next: self.next }; 465 let ret = mem::replace(self.get_mut(idx)?, to_fill); 466 self.next = idx.raw_index() - 1; 467 Ok(ret) 468 } 469 } 470 471 #[derive(Debug)] 472 struct UnknownHandleIndex(TypedResourceIndex); 473 474 impl fmt::Display for UnknownHandleIndex { 475 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 476 write!(f, "unknown handle index {}", self.0.raw_index()) 477 } 478 } 479 480 impl Error for UnknownHandleIndex {} 481 482 #[derive(Debug)] 483 struct ResourceTypeMismatch { 484 expected: TypedResourceIndex, 485 found: &'static str, 486 } 487 488 impl fmt::Display for ResourceTypeMismatch { 489 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 490 write!( 491 f, 492 "handle index {} used with the wrong type, \ 493 expected {} but found {}", 494 self.expected.raw_index(), 495 self.expected.desc(), 496 self.found, 497 ) 498 } 499 } 500 501 impl Error for ResourceTypeMismatch {} 502