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 {}