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//! * `CallContexts` - store-local information about active calls and borrows
15//!   and runtime state tracking that to ensure that everything is handled
16//!   correctly.
17//!
18//! Individual operations are exposed through methods on `ResourceTables` for
19//! lifting/lowering/etc. This does mean though that some other fiddly bits
20//! about ABI details can be found in lifting/lowering throughout Wasmtime,
21//! namely in the `Resource<T>` and `ResourceAny` types.
22
23use super::{HandleTable, RemovedResource};
24use crate::prelude::*;
25use core::error::Error;
26use core::fmt;
27use wasmtime_environ::PrimaryMap;
28use wasmtime_environ::component::{
29    ComponentTypes, RuntimeComponentInstanceIndex, TypeResourceTableIndex,
30};
31
32/// Contextual state necessary to perform resource-related operations.
33///
34/// This state a bit odd since it has a few optional bits, but the idea is that
35/// whenever this is constructed the bits required to perform operations are
36/// always `Some`. For example:
37///
38/// * During lifting and lowering both `guest_table` and `host_table` are
39///   `Some`.
40/// * During wasm's own intrinsics only `guest_table` is `Some`.
41/// * During embedder-invoked resource destruction calls only `host_table` is
42///   `Some`.
43///
44/// This is all packaged up into one state though to make it easier to operate
45/// on and to centralize handling of the state related to resources due to how
46/// critical it is for correctness.
47pub struct ResourceTables<'a> {
48    /// Runtime state for all resources defined in a component.
49    ///
50    /// This is required whenever a `TypeResourceTableIndex`, for example, is
51    /// provided as it's the lookup where that happens. Not present during
52    /// embedder-originating operations though such as
53    /// `ResourceAny::resource_drop` which won't consult this table as it's
54    /// only operating over the host table.
55    pub guest: Option<(
56        &'a mut PrimaryMap<RuntimeComponentInstanceIndex, HandleTable>,
57        &'a ComponentTypes,
58    )>,
59
60    /// Runtime state for resources currently owned by the host.
61    ///
62    /// This is the single table used by the host stored within `Store<T>`. Host
63    /// resources will point into this and effectively have the same semantics
64    /// as-if they're in-component resources. The major distinction though is
65    /// that this is a heterogeneous table instead of only containing a single
66    /// type.
67    pub host_table: Option<&'a mut HandleTable>,
68
69    /// Scope information about calls actively in use to track information such
70    /// as borrow counts.
71    pub calls: &'a mut CallContexts,
72}
73
74/// Typed representation of a "rep" for a resource.
75///
76/// All resources in the component model are stored in a single heterogeneous
77/// table so this type is used to disambiguate what everything is. This is the
78/// representation of a resource stored at-rest in memory.
79#[derive(Debug)]
80pub enum TypedResource {
81    /// A resource defined by the host.
82    ///
83    /// The meaning of the 32-bit integer here is up to the embedder, it
84    /// otherwise is not used within the runtime here.
85    Host(u32),
86
87    /// A resource defined within a component.
88    Component {
89        /// This is an integer supplied by the component itself when this
90        /// resource was created. Typically this is a pointer into linear
91        /// memory for a component.
92        rep: u32,
93
94        /// The type of this component resource.
95        ///
96        /// This is used, within the context of a component, to keep track of
97        /// what the type of `rep` is. This is then used when getting/removing
98        /// from the table to ensure that the guest does indeed have the right
99        /// permission to access this slot.
100        ty: TypeResourceTableIndex,
101    },
102}
103
104impl TypedResource {
105    pub(super) fn rep(&self, access_ty: &TypedResourceIndex) -> Result<u32> {
106        match (self, access_ty) {
107            (Self::Host(rep), TypedResourceIndex::Host(_)) => Ok(*rep),
108            (Self::Host(_), expected) => bail!(ResourceTypeMismatch {
109                expected: *expected,
110                found: "host resource",
111            }),
112            (Self::Component { rep, ty }, TypedResourceIndex::Component { ty: expected, .. }) => {
113                if ty == expected {
114                    Ok(*rep)
115                } else {
116                    bail!(ResourceTypeMismatch {
117                        expected: *access_ty,
118                        found: "a different guest-defined resource",
119                    })
120                }
121            }
122            (Self::Component { .. }, expected) => bail!(ResourceTypeMismatch {
123                expected: *expected,
124                found: "guest-defined resource",
125            }),
126        }
127    }
128}
129
130/// An index used to access a resource.
131///
132/// This reflects how index operations are always accompanied not only with a
133/// 32-bit index but additionally with a type. For example a `resource.drop`
134/// intrinsic in a guest takes only a 32-bit integer argument, but it
135/// inherently is used to only drop one type of resource which is additionally
136/// ascribed here.
137#[derive(Debug, Copy, Clone)]
138pub enum TypedResourceIndex {
139    /// A host resource at the given index is being accessed.
140    Host(u32),
141
142    /// A guest resource is being accessed.
143    Component {
144        /// The index supplied by the guest being accessed.
145        index: u32,
146
147        /// The fully-specific type of this resource.
148        ty: TypeResourceTableIndex,
149    },
150}
151
152impl TypedResourceIndex {
153    pub(super) fn raw_index(&self) -> u32 {
154        match self {
155            Self::Host(index) | Self::Component { index, .. } => *index,
156        }
157    }
158
159    fn desc(&self) -> &'static str {
160        match self {
161            Self::Host(_) => "host resource",
162            Self::Component { .. } => "guest-defined resource",
163        }
164    }
165}
166
167/// State related to borrows and calls within a component.
168///
169/// This is created once per `Store` and updated and modified throughout the
170/// lifetime of the store. This primarily tracks borrow counts and what slots
171/// should be updated when calls go out of scope.
172#[derive(Default)]
173pub struct CallContexts {
174    scopes: Vec<CallContext>,
175}
176
177impl CallContexts {
178    pub fn push(&mut self, cx: CallContext) {
179        self.scopes.push(cx);
180    }
181
182    pub fn pop(&mut self) -> Option<CallContext> {
183        self.scopes.pop()
184    }
185}
186
187/// State related to borrows for a specific call.
188#[derive(Default)]
189pub struct CallContext {
190    lenders: Vec<TypedResourceIndex>,
191    borrow_count: u32,
192}
193
194impl ResourceTables<'_> {
195    fn table_for_resource(&mut self, resource: &TypedResource) -> &mut HandleTable {
196        match resource {
197            TypedResource::Host(_) => self.host_table.as_mut().unwrap(),
198            TypedResource::Component { ty, .. } => {
199                let (tables, types) = self.guest.as_mut().unwrap();
200                &mut tables[types[*ty].instance]
201            }
202        }
203    }
204
205    fn table_for_index(&mut self, index: &TypedResourceIndex) -> &mut HandleTable {
206        match index {
207            TypedResourceIndex::Host(_) => self.host_table.as_mut().unwrap(),
208            TypedResourceIndex::Component { ty, .. } => {
209                let (tables, types) = self.guest.as_mut().unwrap();
210                &mut tables[types[*ty].instance]
211            }
212        }
213    }
214
215    /// Implementation of the `resource.new` canonical intrinsic.
216    ///
217    /// Note that this is the same as `resource_lower_own`.
218    pub fn resource_new(&mut self, resource: TypedResource) -> Result<u32> {
219        self.table_for_resource(&resource)
220            .resource_own_insert(resource)
221    }
222
223    /// Implementation of the `resource.rep` canonical intrinsic.
224    ///
225    /// This one's one of the simpler ones: "just get the rep please"
226    pub fn resource_rep(&mut self, index: TypedResourceIndex) -> Result<u32> {
227        self.table_for_index(&index).resource_rep(index)
228    }
229
230    /// Implementation of the `resource.drop` canonical intrinsic minus the
231    /// actual invocation of the destructor.
232    ///
233    /// This will drop the handle at the `index` specified, removing it from
234    /// the specified table. This operation can fail if:
235    ///
236    /// * The index is invalid.
237    /// * The index points to an `own` resource which has active borrows.
238    /// * The index's type is mismatched with the entry in the table's type.
239    ///
240    /// Otherwise this will return `Some(rep)` if the destructor for `rep` needs
241    /// to run. If `None` is returned then that means a `borrow` handle was
242    /// removed and no destructor is necessary.
243    pub fn resource_drop(&mut self, index: TypedResourceIndex) -> Result<Option<u32>> {
244        match self.table_for_index(&index).remove_resource(index)? {
245            RemovedResource::Own { rep } => Ok(Some(rep)),
246            RemovedResource::Borrow { scope } => {
247                self.calls.scopes[scope].borrow_count -= 1;
248                Ok(None)
249            }
250        }
251    }
252
253    /// Inserts a new "own" handle into the specified table.
254    ///
255    /// This will insert the specified representation into the specified type
256    /// table.
257    ///
258    /// Note that this operation is infallible, and additionally that this is
259    /// the same as `resource_new` implementation-wise.
260    ///
261    /// This is an implementation of the canonical ABI `lower_own` function.
262    pub fn resource_lower_own(&mut self, resource: TypedResource) -> Result<u32> {
263        self.table_for_resource(&resource)
264            .resource_own_insert(resource)
265    }
266
267    /// Attempts to remove an "own" handle from the specified table and its
268    /// index.
269    ///
270    /// This operation will fail if `index` is invalid, if it's a `borrow`
271    /// handle, if the own handle has currently been "lent" as a borrow, or if
272    /// `index` has a different type in the table than the index.
273    ///
274    /// This is an implementation of the canonical ABI `lift_own` function.
275    pub fn resource_lift_own(&mut self, index: TypedResourceIndex) -> Result<u32> {
276        match self.table_for_index(&index).remove_resource(index)? {
277            RemovedResource::Own { rep } => Ok(rep),
278            RemovedResource::Borrow { .. } => bail!("cannot lift own resource from a borrow"),
279        }
280    }
281
282    /// Extracts the underlying resource representation by lifting a "borrow"
283    /// from the tables.
284    ///
285    /// This primarily employs dynamic tracking when a borrow is created from an
286    /// "own" handle to ensure that the "own" handle isn't dropped while the
287    /// borrow is active and additionally that when the current call scope
288    /// returns the lend operation is undone.
289    ///
290    /// This is an implementation of the canonical ABI `lift_borrow` function.
291    pub fn resource_lift_borrow(&mut self, index: TypedResourceIndex) -> Result<u32> {
292        let (rep, is_own) = self.table_for_index(&index).resource_lend(index)?;
293        if is_own {
294            let scope = self.calls.scopes.last_mut().unwrap();
295            scope.lenders.push(index);
296        }
297        Ok(rep)
298    }
299
300    /// Records a new `borrow` resource with the given representation within the
301    /// current call scope.
302    ///
303    /// This requires that a call scope is active. Additionally the number of
304    /// active borrows in the latest scope will be increased and must be
305    /// decreased through a future use of `resource_drop` before the current
306    /// call scope exits.
307    ///
308    /// This some of the implementation of the canonical ABI `lower_borrow`
309    /// function. The other half of this implementation is located on
310    /// `VMComponentContext` which handles the special case of avoiding borrow
311    /// tracking entirely.
312    pub fn resource_lower_borrow(&mut self, resource: TypedResource) -> Result<u32> {
313        let scope = self.calls.scopes.len() - 1;
314        let borrow_count = &mut self.calls.scopes.last_mut().unwrap().borrow_count;
315        *borrow_count = borrow_count.checked_add(1).unwrap();
316        self.table_for_resource(&resource)
317            .resource_borrow_insert(resource, scope)
318    }
319
320    /// Enters a new calling context, starting a fresh count of borrows and
321    /// such.
322    #[inline]
323    pub fn enter_call(&mut self) {
324        self.calls.scopes.push(CallContext::default());
325    }
326
327    /// Exits the previously pushed calling context.
328    ///
329    /// This requires all information to be available within this
330    /// `ResourceTables` and is only called during lowering/lifting operations
331    /// at this time.
332    #[inline]
333    pub fn exit_call(&mut self) -> Result<()> {
334        let cx = self.calls.scopes.pop().unwrap();
335        if cx.borrow_count > 0 {
336            bail!("borrow handles still remain at the end of the call")
337        }
338        for lender in cx.lenders.iter() {
339            // Note the panics here which should never get triggered in theory
340            // due to the dynamic tracking of borrows and such employed for
341            // resources.
342            self.table_for_index(lender)
343                .resource_undo_lend(*lender)
344                .unwrap();
345        }
346        Ok(())
347    }
348}
349
350#[derive(Debug)]
351struct ResourceTypeMismatch {
352    expected: TypedResourceIndex,
353    found: &'static str,
354}
355
356impl fmt::Display for ResourceTypeMismatch {
357    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358        write!(
359            f,
360            "handle index {} used with the wrong type, \
361             expected {} but found {}",
362            self.expected.raw_index(),
363            self.expected.desc(),
364            self.found,
365        )
366    }
367}
368
369impl Error for ResourceTypeMismatch {}