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::{HandleCounter, HandleTable, InstanceState, RemovedResource};
24use crate::bail_bug;
25use crate::component::store::ComponentTaskState;
26use crate::prelude::*;
27use core::error::Error;
28use core::fmt;
29use core::mem;
30use wasmtime_environ::component::{
31    ComponentTypes, RuntimeComponentInstanceIndex, TypeResourceTableIndex,
32};
33use wasmtime_environ::prelude::TryPrimaryMap;
34
35/// Contextual state necessary to perform resource-related operations.
36///
37/// This state a bit odd since it has a few optional bits, but the idea is that
38/// whenever this is constructed the bits required to perform operations are
39/// always `Some`. For example:
40///
41/// * During lifting and lowering both `guest_table` and `host_table` are
42///   `Some`.
43/// * During wasm's own intrinsics only `guest_table` 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`, for example, is
54    /// provided as it's the lookup where that happens. Not present during
55    /// embedder-originating operations though such as
56    /// `ResourceAny::resource_drop` which won't consult this table as it's
57    /// only operating over the host table.
58    pub guest: Option<(
59        &'a mut TryPrimaryMap<RuntimeComponentInstanceIndex, InstanceState>,
60        &'a ComponentTypes,
61    )>,
62
63    /// Runtime state for resources currently owned by the host.
64    ///
65    /// This is the single table used by the host stored within `Store<T>`. Host
66    /// resources will point into this and effectively have the same semantics
67    /// as-if they're in-component resources. The major distinction though is
68    /// that this is a heterogeneous table instead of only containing a single
69    /// type.
70    pub host_table: &'a mut HandleTable,
71
72    /// The store-wide live-handle counter, charged by every insertion into and
73    /// removal from any of the tables above.
74    pub handle_counter: &'a mut HandleCounter,
75
76    /// Task information about calls actively in use to track information such
77    /// as borrow counts.
78    pub task_state: &'a mut ComponentTaskState,
79
80    /// The current scope, used to mutate borrows and lenders for the call.
81    pub current_scope: Option<CurrentScope>,
82}
83
84/// The resource-borrow scope associated with the current component call.
85#[derive(Debug, Copy, Clone, Eq, PartialEq)]
86pub enum CurrentScope {
87    /// A scope which already has an ID in the component task state.
88    Id(Scope),
89    /// A host scope whose task has not yet been materialized.
90    #[cfg_attr(not(feature = "component-model-async"), allow(dead_code))]
91    DeferredHost,
92}
93
94/// Identifier for a component call's resource-borrow scope.
95#[derive(Debug, Copy, Clone, Eq, PartialEq)]
96pub enum Scope {
97    /// A non-concurrent scope or a concurrent guest-task scope.
98    Id(u32),
99    /// A concurrent host-task scope.
100    HostId(u32),
101}
102
103/// Typed representation of a "rep" for a resource.
104///
105/// All resources in the component model are stored in a single heterogeneous
106/// table so this type is used to disambiguate what everything is. This is the
107/// representation of a resource stored at-rest in memory.
108#[derive(Debug)]
109pub enum TypedResource {
110    /// A resource defined by the host.
111    ///
112    /// The meaning of the 32-bit integer here is up to the embedder, it
113    /// otherwise is not used within the runtime here.
114    Host(u32),
115
116    /// A resource defined within a component.
117    Component {
118        /// This is an integer supplied by the component itself when this
119        /// resource was created. Typically this is a pointer into linear
120        /// memory for a component.
121        rep: u32,
122
123        /// The type of this component resource.
124        ///
125        /// This is used, within the context of a component, to keep track of
126        /// what the type of `rep` is. This is then used when getting/removing
127        /// from the table to ensure that the guest does indeed have the right
128        /// permission to access this slot.
129        ty: TypeResourceTableIndex,
130    },
131}
132
133impl TypedResource {
134    pub(super) fn rep(&self, access_ty: &TypedResourceIndex) -> Result<u32> {
135        match (self, access_ty) {
136            (Self::Host(rep), TypedResourceIndex::Host(_)) => Ok(*rep),
137            (Self::Host(_), expected) => bail!(ResourceTypeMismatch {
138                expected: *expected,
139                found: "host resource",
140            }),
141            (Self::Component { rep, ty }, TypedResourceIndex::Component { ty: expected, .. }) => {
142                if ty == expected {
143                    Ok(*rep)
144                } else {
145                    bail!(ResourceTypeMismatch {
146                        expected: *access_ty,
147                        found: "a different guest-defined resource",
148                    })
149                }
150            }
151            (Self::Component { .. }, expected) => bail!(ResourceTypeMismatch {
152                expected: *expected,
153                found: "guest-defined resource",
154            }),
155        }
156    }
157}
158
159/// An index used to access a resource.
160///
161/// This reflects how index operations are always accompanied not only with a
162/// 32-bit index but additionally with a type. For example a `resource.drop`
163/// intrinsic in a guest takes only a 32-bit integer argument, but it
164/// inherently is used to only drop one type of resource which is additionally
165/// ascribed here.
166#[derive(Debug, Copy, Clone)]
167pub enum TypedResourceIndex {
168    /// A host resource at the given index is being accessed.
169    Host(u32),
170
171    /// A guest resource is being accessed.
172    Component {
173        /// The index supplied by the guest being accessed.
174        index: u32,
175
176        /// The fully-specific type of this resource.
177        ty: TypeResourceTableIndex,
178    },
179}
180
181impl TypedResourceIndex {
182    pub(super) fn raw_index(&self) -> u32 {
183        match self {
184            Self::Host(index) | Self::Component { index, .. } => *index,
185        }
186    }
187
188    fn desc(&self) -> &'static str {
189        match self {
190            Self::Host(_) => "host resource",
191            Self::Component { .. } => "guest-defined resource",
192        }
193    }
194}
195
196/// State related to borrows for a specific call.
197#[derive(Default)]
198pub struct CallContext {
199    lenders: TryVec<TypedResourceIndex>,
200    borrow_count: u32,
201}
202
203impl CallContext {
204    #[cfg_attr(not(feature = "component-model-async"), allow(dead_code))]
205    pub(crate) fn is_empty(&self) -> bool {
206        self.lenders.is_empty() && self.borrow_count == 0
207    }
208}
209
210impl ResourceTables<'_> {
211    fn table_for_resource(
212        &mut self,
213        resource: &TypedResource,
214    ) -> (&mut HandleTable, &mut HandleCounter) {
215        let table = match resource {
216            TypedResource::Host(_) => &mut *self.host_table,
217            TypedResource::Component { ty, .. } => {
218                let (states, types) = self.guest.as_mut().unwrap();
219                states[types[*ty].unwrap_concrete_instance()].handle_table()
220            }
221        };
222        (table, &mut *self.handle_counter)
223    }
224
225    fn table_for_index(
226        &mut self,
227        index: &TypedResourceIndex,
228    ) -> (&mut HandleTable, &mut HandleCounter) {
229        let table = match index {
230            TypedResourceIndex::Host(_) => &mut *self.host_table,
231            TypedResourceIndex::Component { ty, .. } => {
232                let (states, types) = self.guest.as_mut().unwrap();
233                states[types[*ty].unwrap_concrete_instance()].handle_table()
234            }
235        };
236        (table, &mut *self.handle_counter)
237    }
238
239    /// Implementation of the `resource.new` canonical intrinsic.
240    ///
241    /// Note that this is the same as `resource_lower_own`.
242    pub fn resource_new(&mut self, resource: TypedResource) -> Result<u32> {
243        let (table, counter) = self.table_for_resource(&resource);
244        table.resource_own_insert(counter, resource)
245    }
246
247    /// Implementation of the `resource.rep` canonical intrinsic.
248    ///
249    /// This one's one of the simpler ones: "just get the rep please"
250    pub fn resource_rep(&mut self, index: TypedResourceIndex) -> Result<u32> {
251        self.table_for_index(&index).0.resource_rep(index)
252    }
253
254    /// Implementation of the `resource.drop` canonical intrinsic minus the
255    /// actual invocation of the destructor.
256    ///
257    /// This will drop the handle at the `index` specified, removing it from
258    /// the specified table. This operation can fail if:
259    ///
260    /// * The index is invalid.
261    /// * The index points to a resource which has been lent out.
262    /// * The index's type is mismatched with the entry in the table's type.
263    ///
264    /// Otherwise this will return `Some(rep)` if the destructor for `rep` needs
265    /// to run. If `None` is returned then that means a `borrow` handle was
266    /// removed and no destructor is necessary.
267    pub fn resource_drop(&mut self, index: TypedResourceIndex) -> Result<Option<u32>> {
268        match self.table_for_index(&index).0.remove_resource(index)? {
269            RemovedResource::Own { rep } => Ok(Some(rep)),
270            RemovedResource::Borrow { scope } => {
271                self.task_state.call_context(scope)?.borrow_count -= 1;
272                Ok(None)
273            }
274        }
275    }
276
277    /// Inserts a new "own" handle into the specified table.
278    ///
279    /// This will insert the specified representation into the specified type
280    /// table.
281    ///
282    /// Note that this operation is infallible, and additionally that this is
283    /// the same as `resource_new` implementation-wise.
284    ///
285    /// This is an implementation of the canonical ABI `lower_own` function.
286    pub fn resource_lower_own(&mut self, resource: TypedResource) -> Result<u32> {
287        let (table, counter) = self.table_for_resource(&resource);
288        table.resource_own_insert(counter, resource)
289    }
290
291    /// Attempts to remove an "own" handle from the specified table and its
292    /// index.
293    ///
294    /// This operation will fail if `index` is invalid, if it's a `borrow`
295    /// handle, if the own handle has currently been "lent" as a borrow, or if
296    /// `index` has a different type in the table than the index.
297    ///
298    /// This is an implementation of the canonical ABI `lift_own` function.
299    pub fn resource_lift_own(&mut self, index: TypedResourceIndex) -> Result<u32> {
300        match self.table_for_index(&index).0.remove_resource(index)? {
301            RemovedResource::Own { rep } => Ok(rep),
302            RemovedResource::Borrow { .. } => bail!("cannot lift own resource from a borrow"),
303        }
304    }
305
306    fn materialize_current_scope(&mut self) -> Result<Scope> {
307        let id = match self.current_scope {
308            Some(CurrentScope::Id(id)) => return Ok(id),
309            Some(CurrentScope::DeferredHost) => self.task_state.materialize_current_scope()?,
310            None => bail_bug!("no current scope"),
311        };
312        self.current_scope = Some(CurrentScope::Id(id));
313        Ok(id)
314    }
315
316    /// Returns the current call's resource-borrow scope.
317    ///
318    /// Unlike [`Self::materialize_current_scope`], this does not materialize a
319    /// deferred host task.
320    fn current_scope(&mut self) -> Result<&mut CallContext> {
321        let id = match self.current_scope {
322            Some(CurrentScope::Id(id)) => id,
323            Some(CurrentScope::DeferredHost) => {
324                // With a deferred host task the `CallContext` for this task
325                // could be stored in one of two locations. The primary location
326                // is `task_state.deferred_host_call_context()` but that stops
327                // being used once the host task is actually materialized. On
328                // materialization this over here isn't updated necessarily, so
329                // this'll only lazily pick up when the host task is actually
330                // materialized. In such a situation that's detected here and
331                // `self.current_scope` is mutated to match.
332                //
333                // NB: this should use `if let` when rustc is smarter about
334                // borrows.
335                if self.task_state.deferred_host_call_context().is_some() {
336                    return Ok(self.task_state.deferred_host_call_context().unwrap());
337                }
338                let id = self.task_state.materialize_current_scope()?;
339                self.current_scope = Some(CurrentScope::Id(id));
340                id
341            }
342            None => bail_bug!("no current scope"),
343        };
344        self.task_state.call_context(id)
345    }
346
347    /// Extracts the underlying resource representation by lifting a "borrow"
348    /// from the tables.
349    ///
350    /// This primarily employs dynamic tracking when a borrow is created from
351    /// either an "own" or a "borrow" handle to ensure that the lending handle
352    /// isn't dropped while the borrow is active and additionally that when the
353    /// current call scope returns the lend operation is undone.
354    ///
355    /// This is an implementation of the canonical ABI `lift_borrow` function.
356    pub fn resource_lift_borrow(&mut self, index: TypedResourceIndex) -> Result<u32> {
357        // FIXME: the logic here applies to all resources, both own and borrow,
358        // although tracking `borrow` here isn't always necessary for sync
359        // tasks. Ideally the `lenders.push` would be skipped for sync tasks but
360        // that requires a bit more plumbing and also appropriately tracking the
361        // `lend_count` and such. For now this treats own/borrow the same way.
362        //
363        // Space for the lender is reserved first so that recording it can't
364        // fail after the lend.
365        self.current_scope()?.lenders.reserve(1)?;
366        let (table, counter) = self.table_for_index(&index);
367        let rep = table.resource_lend(counter, index)?;
368        self.current_scope()?.lenders.push(index)?;
369        Ok(rep)
370    }
371
372    /// Records a new `borrow` resource with the given representation within the
373    /// current call scope.
374    ///
375    /// This requires that a call scope is active. Additionally the number of
376    /// active borrows in the latest scope will be increased and must be
377    /// decreased through a future use of `resource_drop` before the current
378    /// call scope exits.
379    ///
380    /// This some of the implementation of the canonical ABI `lower_borrow`
381    /// function. The other half of this implementation is located on
382    /// `VMComponentContext` which handles the special case of avoiding borrow
383    /// tracking entirely.
384    pub fn resource_lower_borrow(&mut self, resource: TypedResource) -> Result<u32> {
385        let scope = self.materialize_current_scope()?;
386        let cx = self.task_state.call_context(scope)?;
387        cx.borrow_count = cx.borrow_count.checked_add(1).unwrap();
388        let (table, counter) = self.table_for_resource(&resource);
389        table.resource_borrow_insert(counter, resource, scope)
390    }
391
392    /// Validates that the current scope can be exited.
393    ///
394    /// This will ensure that this context's active borrows have all been
395    /// dropped. This will then commit the lend decrements back to the owned
396    /// resources that were originally passed in.
397    #[inline]
398    pub fn validate_scope_exit(&mut self) -> Result<()> {
399        let cx = self.current_scope()?;
400        if cx.borrow_count > 0 {
401            bail!("borrow handles still remain at the end of the call")
402        }
403        for lender in mem::take(&mut cx.lenders) {
404            // Note the panics here which should never get triggered in theory
405            // due to the dynamic tracking of borrows and such employed for
406            // resources.
407            let (table, counter) = self.table_for_index(&lender);
408            table.resource_undo_lend(counter, lender).unwrap();
409        }
410        Ok(())
411    }
412}
413
414#[derive(Debug)]
415struct ResourceTypeMismatch {
416    expected: TypedResourceIndex,
417    found: &'static str,
418}
419
420impl fmt::Display for ResourceTypeMismatch {
421    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422        write!(
423            f,
424            "handle index {} used with the wrong type, \
425             expected {} but found {}",
426            self.expected.raw_index(),
427            self.expected.desc(),
428            self.found,
429        )
430    }
431}
432
433impl Error for ResourceTypeMismatch {}