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::mem;
28use wasmtime_environ::component::TypeResourceTableIndex;
29use 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.
34const 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.
50pub 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)]
76pub 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
83enum 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)]
106pub struct CallContexts {
107    scopes: Vec<CallContext>,
108}
109
110#[derive(Default)]
111struct CallContext {
112    lenders: Vec<Lender>,
113    borrow_count: u32,
114}
115
116#[derive(Copy, Clone)]
117struct Lender {
118    ty: Option<TypeResourceTableIndex>,
119    idx: u32,
120}
121
122impl 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
293impl 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}