wasmtime/runtime/vm/component/resources.rs
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
26use crate::prelude::*;
27use core::error::Error;
28use core::fmt;
29use core::mem;
30use wasmtime_environ::PrimaryMap;
31use 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.
38const 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.
55pub 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)]
85pub 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)]
98pub 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
122impl 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)]
156pub 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
170impl 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
185enum 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)]
214pub struct CallContexts {
215 scopes: Vec<CallContext>,
216}
217
218impl CallContexts {
219 pub fn push(&mut self, cx: CallContext) {
220 self.scopes.push(cx);
221 }
222
223 pub fn pop(&mut self) -> Option<CallContext> {
224 self.scopes.pop()
225 }
226}
227
228/// State related to borrows for a specific call.
229#[derive(Default)]
230pub struct CallContext {
231 lenders: Vec<TypedResourceIndex>,
232 borrow_count: u32,
233}
234
235impl ResourceTables<'_> {
236 fn table_for_resource(&mut self, resource: &TypedResource) -> &mut ResourceTable {
237 match resource {
238 TypedResource::Host(_) => self.host_table.as_mut().unwrap(),
239 TypedResource::Component { ty, .. } => {
240 let (tables, types) = self.guest.as_mut().unwrap();
241 &mut tables[types[*ty].instance]
242 }
243 }
244 }
245
246 fn table_for_index(&mut self, index: &TypedResourceIndex) -> &mut ResourceTable {
247 match index {
248 TypedResourceIndex::Host(_) => self.host_table.as_mut().unwrap(),
249 TypedResourceIndex::Component { ty, .. } => {
250 let (tables, types) = self.guest.as_mut().unwrap();
251 &mut tables[types[*ty].instance]
252 }
253 }
254 }
255
256 /// Implementation of the `resource.new` canonical intrinsic.
257 ///
258 /// Note that this is the same as `resource_lower_own`.
259 pub fn resource_new(&mut self, resource: TypedResource) -> Result<u32> {
260 self.table_for_resource(&resource).insert(Slot::Own {
261 resource,
262 lend_count: 0,
263 })
264 }
265
266 /// Implementation of the `resource.rep` canonical intrinsic.
267 ///
268 /// This one's one of the simpler ones: "just get the rep please"
269 pub fn resource_rep(&mut self, index: TypedResourceIndex) -> Result<u32> {
270 self.table_for_index(&index).rep(index)
271 }
272
273 /// Implementation of the `resource.drop` canonical intrinsic minus the
274 /// actual invocation of the destructor.
275 ///
276 /// This will drop the handle at the `index` specified, removing it from
277 /// the specified table. This operation can fail if:
278 ///
279 /// * The index is invalid.
280 /// * The index points to an `own` resource which has active borrows.
281 /// * The index's type is mismatched with the entry in the table's type.
282 ///
283 /// Otherwise this will return `Some(rep)` if the destructor for `rep` needs
284 /// to run. If `None` is returned then that means a `borrow` handle was
285 /// removed and no destructor is necessary.
286 pub fn resource_drop(&mut self, index: TypedResourceIndex) -> Result<Option<u32>> {
287 match self.table_for_index(&index).remove(index)? {
288 Slot::Own {
289 resource,
290 lend_count: 0,
291 } => resource.rep(&index).map(Some),
292 Slot::Own { .. } => bail!("cannot remove owned resource while borrowed"),
293 Slot::Borrow {
294 scope, resource, ..
295 } => {
296 // Validate that this borrow has the correct type to ensure a
297 // trap is returned if this is a mis-typed `resource.drop`.
298 resource.rep(&index)?;
299 self.calls.scopes[scope].borrow_count -= 1;
300 Ok(None)
301 }
302 Slot::Free { .. } => unreachable!(),
303 }
304 }
305
306 /// Inserts a new "own" handle into the specified table.
307 ///
308 /// This will insert the specified representation into the specified type
309 /// table.
310 ///
311 /// Note that this operation is infallible, and additionally that this is
312 /// the same as `resource_new` implementation-wise.
313 ///
314 /// This is an implementation of the canonical ABI `lower_own` function.
315 pub fn resource_lower_own(&mut self, resource: TypedResource) -> Result<u32> {
316 self.table_for_resource(&resource).insert(Slot::Own {
317 resource,
318 lend_count: 0,
319 })
320 }
321
322 /// Attempts to remove an "own" handle from the specified table and its
323 /// index.
324 ///
325 /// This operation will fail if `index` is invalid, if it's a `borrow`
326 /// handle, if the own handle has currently been "lent" as a borrow, or if
327 /// `index` has a different type in the table than the index.
328 ///
329 /// This is an implementation of the canonical ABI `lift_own` function.
330 pub fn resource_lift_own(&mut self, index: TypedResourceIndex) -> Result<u32> {
331 match self.table_for_index(&index).remove(index)? {
332 Slot::Own {
333 resource,
334 lend_count: 0,
335 } => resource.rep(&index),
336 Slot::Own { .. } => bail!("cannot remove owned resource while borrowed"),
337 Slot::Borrow { .. } => bail!("cannot lift own resource from a borrow"),
338 Slot::Free { .. } => unreachable!(),
339 }
340 }
341
342 /// Extracts the underlying resource representation by lifting a "borrow"
343 /// from the tables.
344 ///
345 /// This primarily employs dynamic tracking when a borrow is created from an
346 /// "own" handle to ensure that the "own" handle isn't dropped while the
347 /// borrow is active and additionally that when the current call scope
348 /// returns the lend operation is undone.
349 ///
350 /// This is an implementation of the canonical ABI `lift_borrow` function.
351 pub fn resource_lift_borrow(&mut self, index: TypedResourceIndex) -> Result<u32> {
352 match self.table_for_index(&index).get_mut(index)? {
353 Slot::Own {
354 resource,
355 lend_count,
356 } => {
357 let rep = resource.rep(&index)?;
358 // The decrement to this count happens in `exit_call`.
359 *lend_count = lend_count.checked_add(1).unwrap();
360 let scope = self.calls.scopes.last_mut().unwrap();
361 scope.lenders.push(index);
362 Ok(rep)
363 }
364 Slot::Borrow { resource, .. } => resource.rep(&index),
365 Slot::Free { .. } => unreachable!(),
366 }
367 }
368
369 /// Records a new `borrow` resource with the given representation within the
370 /// current call scope.
371 ///
372 /// This requires that a call scope is active. Additionally the number of
373 /// active borrows in the latest scope will be increased and must be
374 /// decreased through a future use of `resource_drop` before the current
375 /// call scope exits.
376 ///
377 /// This some of the implementation of the canonical ABI `lower_borrow`
378 /// function. The other half of this implementation is located on
379 /// `VMComponentContext` which handles the special case of avoiding borrow
380 /// tracking entirely.
381 pub fn resource_lower_borrow(&mut self, resource: TypedResource) -> Result<u32> {
382 let scope = self.calls.scopes.len() - 1;
383 let borrow_count = &mut self.calls.scopes.last_mut().unwrap().borrow_count;
384 *borrow_count = borrow_count.checked_add(1).unwrap();
385 self.table_for_resource(&resource)
386 .insert(Slot::Borrow { resource, scope })
387 }
388
389 /// Enters a new calling context, starting a fresh count of borrows and
390 /// such.
391 #[inline]
392 pub fn enter_call(&mut self) {
393 self.calls.scopes.push(CallContext::default());
394 }
395
396 /// Exits the previously pushed calling context.
397 ///
398 /// This requires all information to be available within this
399 /// `ResourceTables` and is only called during lowering/lifting operations
400 /// at this time.
401 #[inline]
402 pub fn exit_call(&mut self) -> Result<()> {
403 let cx = self.calls.scopes.pop().unwrap();
404 if cx.borrow_count > 0 {
405 bail!("borrow handles still remain at the end of the call")
406 }
407 for lender in cx.lenders.iter() {
408 // Note the panics here which should never get triggered in theory
409 // due to the dynamic tracking of borrows and such employed for
410 // resources.
411 match self.table_for_index(lender).get_mut(*lender).unwrap() {
412 Slot::Own { lend_count, .. } => {
413 *lend_count -= 1;
414 }
415 _ => unreachable!(),
416 }
417 }
418 Ok(())
419 }
420}
421
422impl ResourceTable {
423 fn insert(&mut self, new: Slot) -> Result<u32> {
424 let next = self.next as usize;
425 if next == self.slots.len() {
426 self.slots.push(Slot::Free {
427 next: self.next.checked_add(1).unwrap(),
428 });
429 }
430 let ret = self.next;
431 self.next = match mem::replace(&mut self.slots[next], new) {
432 Slot::Free { next } => next,
433 _ => unreachable!(),
434 };
435
436 // The component model reserves index 0 as never allocatable so add one
437 // to the table index to start the numbering at 1 instead. Also note
438 // that the component model places an upper-limit per-table on the
439 // maximum allowed index.
440 let ret = ret + 1;
441 if ret >= MAX_RESOURCE_HANDLE {
442 bail!("cannot allocate another handle: index overflow");
443 }
444 Ok(ret)
445 }
446
447 fn handle_index_to_table_index(&self, idx: u32) -> Option<usize> {
448 // NB: `idx` is decremented by one to account for the `+1` above during
449 // allocation.
450 let idx = idx.checked_sub(1)?;
451 usize::try_from(idx).ok()
452 }
453
454 fn rep(&self, idx: TypedResourceIndex) -> Result<u32> {
455 let slot = self
456 .handle_index_to_table_index(idx.raw_index())
457 .and_then(|i| self.slots.get(i));
458 match slot {
459 None | Some(Slot::Free { .. }) => bail!(UnknownHandleIndex(idx)),
460 Some(Slot::Own { resource, .. } | Slot::Borrow { resource, .. }) => resource.rep(&idx),
461 }
462 }
463
464 fn get_mut(&mut self, idx: TypedResourceIndex) -> Result<&mut Slot> {
465 let slot = self
466 .handle_index_to_table_index(idx.raw_index())
467 .and_then(|i| self.slots.get_mut(i));
468 match slot {
469 None | Some(Slot::Free { .. }) => bail!(UnknownHandleIndex(idx)),
470 Some(other) => Ok(other),
471 }
472 }
473
474 fn remove(&mut self, idx: TypedResourceIndex) -> Result<Slot> {
475 let to_fill = Slot::Free { next: self.next };
476 let ret = mem::replace(self.get_mut(idx)?, to_fill);
477 self.next = idx.raw_index() - 1;
478 Ok(ret)
479 }
480}
481
482#[derive(Debug)]
483struct UnknownHandleIndex(TypedResourceIndex);
484
485impl fmt::Display for UnknownHandleIndex {
486 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487 write!(f, "unknown handle index {}", self.0.raw_index())
488 }
489}
490
491impl Error for UnknownHandleIndex {}
492
493#[derive(Debug)]
494struct ResourceTypeMismatch {
495 expected: TypedResourceIndex,
496 found: &'static str,
497}
498
499impl fmt::Display for ResourceTypeMismatch {
500 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501 write!(
502 f,
503 "handle index {} used with the wrong type, \
504 expected {} but found {}",
505 self.expected.raw_index(),
506 self.expected.desc(),
507 self.found,
508 )
509 }
510}
511
512impl Error for ResourceTypeMismatch {}