Skip to main content

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