Skip to main content

wasmtime/runtime/component/resources/
any.rs

1//! This module defines the `ResourceAny` type in the public API of Wasmtime,
2//! which represents a dynamically typed resource handle that could either be
3//! owned by the guest or the host.
4//!
5//! This is in contrast with `Resource<T>`, for example, and `ResourceAny` has
6//! more "state" behind it. Most `ResourceAny` values have a type and a
7//! `HostResourceIndex` which points inside of a store. These must be dropped
8//! or converted to a typed resource to release that state. A synthetic borrow
9//! converted from `Resource::new_borrow` instead holds its representation
10//! directly and has no host table entry.
11
12use crate::component::func::{LiftContext, LowerContext, bad_type_info, desc};
13use crate::component::matching::InstanceType;
14use crate::component::resources::host::{HostResource, HostResourceType};
15use crate::component::resources::{HostResourceIndex, HostResourceTables};
16use crate::component::{ComponentType, Lift, Lower, Resource, ResourceDynamic, ResourceType};
17use crate::prelude::*;
18use crate::runtime::vm::ValRaw;
19use crate::{AsContextMut, StoreContextMut, Trap};
20use core::mem::MaybeUninit;
21use core::ptr::NonNull;
22use wasmtime_environ::component::{CanonicalAbiInfo, InterfaceType};
23
24#[derive(Debug, PartialEq, Eq, Copy, Clone)]
25enum ResourceAnyIndex {
26    Table(HostResourceIndex),
27    Borrow(u32),
28}
29
30/// Representation of a dynamically typed guest-defined or host-defined
31/// resource in the component model.
32///
33/// # Guest-defined resources
34///
35/// Guest-defined resources enter the host through generated bindings, such as
36/// a function that returns an owned resource. Their methods are called through
37/// the generated type for that resource, for example `GuestLogger` in the
38/// exported resources example in [`bindgen_examples`]. A guest-defined
39/// [`ResourceAny`] cannot be converted to [`Resource`] or [`ResourceDynamic`],
40/// because those types represent host-defined resources.
41///
42/// [`bindgen_examples`]: crate::component::bindgen_examples
43///
44/// # Host-defined resources
45///
46/// Convert a host-defined [`Resource<T>`](Resource) to this type with
47/// [`ResourceAny::try_from_resource`]. Convert it back with
48/// [`ResourceAny::try_into_resource`] or
49/// [`ResourceAny::try_into_resource_dynamic`]. These conversions check the
50/// resource type at runtime.
51///
52/// # Ownership and destruction
53///
54/// Like [`Resource`] this type represents either an `own` or a `borrow`
55/// resource internally, and the WIT signature controls which one a value is.
56/// Passing a resource to an `own` parameter transfers ownership to the callee,
57/// while passing it to a `borrow` parameter keeps ownership with the caller.
58/// When a function returns an `own` resource, the caller acquires ownership.
59/// The same applies to each owned resource nested in a record, variant, list,
60/// or other value.
61///
62/// A [`ResourceAny`] with a host table entry that the host still owns must
63/// eventually be passed to an `own` parameter, converted to a typed resource,
64/// or explicitly destroyed with [`ResourceAny::resource_drop`]. Destroying it
65/// updates dynamic state tracking and invokes the WebAssembly-defined
66/// destructor for a resource, if any. `ResourceAny` is `Copy`, but once
67/// ownership has been transferred the handle must not be used again.
68///
69/// `ResourceAny` has no static type parameter, so using one with the wrong
70/// generated function produces a runtime type error.
71///
72/// Borrows lifted from a component have host table state and must be dropped.
73/// Synthetic borrows converted from [`Resource::new_borrow`] have no host table
74/// state; calling `resource_drop` on one is harmless but unnecessary.
75#[derive(Debug, PartialEq, Eq, Copy, Clone)]
76pub struct ResourceAny {
77    idx: ResourceAnyIndex,
78    ty: ResourceType,
79    owned: bool,
80}
81
82impl ResourceAny {
83    pub(crate) fn new(idx: HostResourceIndex, ty: ResourceType, owned: bool) -> ResourceAny {
84        ResourceAny {
85            idx: ResourceAnyIndex::Table(idx),
86            ty,
87            owned,
88        }
89    }
90
91    pub(crate) fn new_borrow(rep: u32, ty: ResourceType) -> ResourceAny {
92        ResourceAny {
93            idx: ResourceAnyIndex::Borrow(rep),
94            ty,
95            owned: false,
96        }
97    }
98
99    /// Attempts to convert a host-defined [`Resource`] into [`ResourceAny`].
100    ///
101    /// * `resource` is the resource to convert.
102    /// * `store` is the store to place the returned resource into.
103    ///
104    /// The returned `ResourceAny` has no destructor attached to it, so
105    /// `resource_drop` will not invoke a host-defined destructor. This matches
106    /// [`Resource`], which has no associated destructor.
107    ///
108    /// # Errors
109    ///
110    /// This method will return an error if `resource` has already been "taken"
111    /// and has ownership transferred elsewhere which can happen in situations
112    /// such as when it's already lowered into a component.
113    ///
114    /// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
115    /// memory allocation fails. See the `OutOfMemory` type's documentation for
116    /// details on Wasmtime's out-of-memory handling.
117    pub fn try_from_resource<T: 'static>(
118        resource: Resource<T>,
119        store: impl AsContextMut,
120    ) -> Result<Self> {
121        resource.try_into_resource_any(store)
122    }
123
124    /// Attempts to convert this value into a statically typed, host-defined
125    /// [`Resource`].
126    ///
127    /// This conversion accepts only host-defined resources of type `T`.
128    /// Guest-defined resources must remain [`ResourceAny`] values and be used
129    /// through their generated functions and resource projection.
130    ///
131    /// # Errors
132    ///
133    /// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
134    /// memory allocation fails. See the `OutOfMemory` type's documentation for
135    /// details on Wasmtime's out-of-memory handling.
136    pub fn try_into_resource<T: 'static>(self, store: impl AsContextMut) -> Result<Resource<T>> {
137        Resource::try_from_resource_any(self, store)
138    }
139
140    /// Attempts to convert this value into a dynamically typed, host-defined
141    /// [`ResourceDynamic`].
142    ///
143    /// This conversion accepts only host-defined resources. Guest-defined
144    /// resources must remain [`ResourceAny`] values and be used through their
145    /// generated functions and resource projection.
146    pub fn try_into_resource_dynamic(self, store: impl AsContextMut) -> Result<ResourceDynamic> {
147        ResourceDynamic::try_from_resource_any(self, store)
148    }
149
150    /// See [`Resource::try_from_resource_any`]
151    pub(crate) fn try_into_host_resource<T, D>(
152        self,
153        mut store: impl AsContextMut,
154    ) -> Result<HostResource<T, D>>
155    where
156        T: HostResourceType<D>,
157        D: PartialEq + Send + Sync + Copy + 'static,
158    {
159        let ResourceAny { idx, ty, owned } = self;
160        let ty = T::typecheck(ty).ok_or_else(|| crate::format_err!("resource type mismatch"))?;
161        match idx {
162            ResourceAnyIndex::Borrow(rep) => {
163                assert!(!owned);
164                Ok(HostResource::new_borrow(rep, ty))
165            }
166            ResourceAnyIndex::Table(idx) => {
167                let store = store.as_context_mut();
168                let mut tables = HostResourceTables::new_host(store.0)?;
169                if owned {
170                    let rep = tables.host_resource_lift_own(idx)?;
171                    Ok(HostResource::new_own(rep, ty))
172                } else {
173                    // Typed borrows have no dynamic state. Remove the table
174                    // entry after lifting its representation.
175                    let rep = tables.host_resource_lift_borrow(idx)?;
176                    let res = tables.host_resource_drop(idx)?;
177                    assert!(res.is_none());
178                    Ok(HostResource::new_borrow(rep, ty))
179                }
180            }
181        }
182    }
183
184    /// Returns the corresponding type associated with this resource, either a
185    /// host-defined type or a guest-defined type.
186    ///
187    /// This can be compared against [`ResourceType::host`] for example to see
188    /// if it's a host-resource or against a type extracted with
189    /// [`Instance::get_resource`] to see if it's a guest-defined resource.
190    ///
191    /// [`Instance::get_resource`]: crate::component::Instance::get_resource
192    pub fn ty(&self) -> ResourceType {
193        self.ty
194    }
195
196    /// Returns whether this is an owned resource, and if not it's a borrowed
197    /// resource.
198    pub fn owned(&self) -> bool {
199        self.owned
200    }
201
202    /// Destroy this resource and release any state associated with it.
203    ///
204    /// This is required for resources with host table state. For synthetic
205    /// borrows converted from [`Resource::new_borrow`] it has no effect.
206    /// For owned resources this may execute the guest-defined destructor if
207    /// applicable (or the host-defined destructor if one was specified).
208    ///
209    /// Exactly one of the following must be called for each [`ResourceAny`],
210    /// depending on how the store is being driven:
211    ///
212    /// * [`ResourceAny::resource_drop`] for synchronous stores.
213    /// * `ResourceAny::resource_drop_async` for [async](crate#async) stores
214    ///   when a `StoreContextMut` is available.
215    /// * `ResourceAny::resource_drop_concurrent` when only an `Accessor` is
216    ///   available, such as inside `Store::run_concurrent` or an
217    ///   `AccessorTask`.
218    ///
219    /// # Errors
220    ///
221    /// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
222    /// memory allocation fails. See the `OutOfMemory` type's documentation for
223    /// details on Wasmtime's out-of-memory handling.
224    pub fn resource_drop(self, mut store: impl AsContextMut) -> Result<()> {
225        let mut store = store.as_context_mut();
226        store.0.validate_sync_call()?;
227        self.resource_drop_impl(&mut store)
228    }
229
230    /// Same as [`ResourceAny::resource_drop`] except for use with async stores
231    /// to execute the destructor [asynchronously](crate#async).
232    ///
233    /// # Errors
234    ///
235    /// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
236    /// memory allocation fails. See the `OutOfMemory` type's documentation for
237    /// details on Wasmtime's out-of-memory handling.
238    #[cfg(feature = "async")]
239    pub async fn resource_drop_async(self, mut store: impl AsContextMut<Data: Send>) -> Result<()> {
240        let mut store = store.as_context_mut();
241        store
242            .on_fiber(|store| self.resource_drop_impl(store))
243            .await?
244    }
245
246    /// Same as [`ResourceAny::resource_drop`] except for use with an
247    /// [`Accessor`](crate::component::Accessor) while a store is executing
248    /// [`Store::run_concurrent`](crate::Store::run_concurrent).
249    ///
250    /// The resource drop is queued for execution on the store's worker fiber.
251    /// This method must be awaited while the store's concurrent event loop is
252    /// running so the queued drop can make progress.
253    ///
254    /// Once this future has been polled and the drop has been queued, dropping
255    /// the future does not cancel the resource drop.
256    ///
257    /// # Errors
258    ///
259    /// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
260    /// memory allocation fails. See the `OutOfMemory` type's documentation for
261    /// details on Wasmtime's out-of-memory handling.
262    #[cfg(feature = "component-model-async")]
263    pub async fn resource_drop_concurrent(
264        self,
265        accessor: impl crate::component::AsAccessor,
266    ) -> Result<()> {
267        let receiver = accessor.as_accessor().with(|mut store| -> Result<_> {
268            let mut store = store.as_context_mut();
269            let (sender, receiver) = futures::channel::oneshot::channel();
270            let token = crate::store::StoreToken::new(store.as_context_mut());
271            store.0.queue_task(move |store| {
272                _ = sender.send(self.resource_drop_impl(&mut token.as_context_mut(store)));
273                Ok(())
274            })?;
275            Ok(receiver)
276        })?;
277        receiver
278            .await
279            .map_err(|_| format_err!("resource drop task canceled"))?
280    }
281
282    fn resource_drop_impl<T: 'static>(self, store: &mut StoreContextMut<'_, T>) -> Result<()> {
283        // Attempt to remove `self.idx` from the host table in `store`.
284        //
285        // This could fail if the index is invalid or if this is removing an
286        // `Own` entry which is currently being borrowed.
287        let idx = match self.idx {
288            ResourceAnyIndex::Table(idx) => idx,
289            ResourceAnyIndex::Borrow(_) => return Ok(()),
290        };
291        let pair = HostResourceTables::new_host(store.0)?.host_resource_drop(idx)?;
292
293        let (rep, slot) = match (pair, self.owned) {
294            (Some(pair), true) => pair,
295
296            // A `borrow` was removed from the table and no further
297            // destruction, e.g. the destructor, is required so we're done.
298            (None, false) => return Ok(()),
299
300            _ => unreachable!(),
301        };
302
303        if slot.instance.is_some() && !store.0.may_enter() {
304            bail!(Trap::CannotEnterComponent);
305        }
306
307        let dtor = match slot.dtor {
308            Some(dtor) => dtor.as_non_null(),
309            None => return Ok(()),
310        };
311        let mut args = [ValRaw::u32(rep)];
312
313        // Setup async-level task infrastructure for this call. This, for
314        // example, prevents the destructor from blocking.
315        //
316        // Note that if `slot.instance` is `None` then this is skipped. That
317        // means that this is a host resource being destroyed by the host. In
318        // that case restrictions around blocking and such are exempt.
319        if let Some(instance) = slot.instance {
320            store.0.enter_guest_sync_call(false, instance)?;
321        }
322
323        // This should be safe because `dtor` has been checked to belong to the
324        // `store` provided which means it's valid and still alive. Additionally
325        // destructors have al been previously type-checked and are guaranteed
326        // to take one i32 argument and return no results, so the parameters
327        // here should be configured correctly.
328        unsafe {
329            crate::Func::call_unchecked_raw(store, dtor, NonNull::from(&mut args))?;
330        }
331
332        if slot.instance.is_some() {
333            store.0.exit_guest_sync_call()?;
334        }
335
336        Ok(())
337    }
338
339    fn lower_to_index<U>(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result<u32> {
340        match ty {
341            InterfaceType::Own(t) => {
342                if cx.resource_type(t) != self.ty {
343                    bail!("mismatched resource types");
344                }
345                let rep = match self.idx {
346                    ResourceAnyIndex::Table(idx) => cx.host_resource_lift_own(idx)?,
347                    ResourceAnyIndex::Borrow(_) => {
348                        bail!("cannot lower a `borrow` resource into an `own`")
349                    }
350                };
351                cx.guest_resource_lower_own(t, rep)
352            }
353            InterfaceType::Borrow(t) => {
354                if cx.resource_type(t) != self.ty {
355                    bail!("mismatched resource types");
356                }
357                let rep = match self.idx {
358                    ResourceAnyIndex::Table(idx) => cx.host_resource_lift_borrow(idx)?,
359                    ResourceAnyIndex::Borrow(rep) => rep,
360                };
361                cx.guest_resource_lower_borrow(t, rep)
362            }
363            _ => bad_type_info(),
364        }
365    }
366
367    fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result<Self> {
368        match ty {
369            InterfaceType::Own(t) => {
370                let ty = cx.resource_type(t);
371                let (rep, dtor, flags) = cx.guest_resource_lift_own(t, index)?;
372                let idx = cx.host_resource_lower_own(rep, dtor, flags)?;
373                Ok(ResourceAny::new(idx, ty, true))
374            }
375            InterfaceType::Borrow(t) => {
376                let ty = cx.resource_type(t);
377                let rep = cx.guest_resource_lift_borrow(t, index)?;
378                let idx = cx.host_resource_lower_borrow(rep)?;
379                Ok(ResourceAny::new(idx, ty, false))
380            }
381            _ => bad_type_info(),
382        }
383    }
384}
385
386unsafe impl ComponentType for ResourceAny {
387    const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4;
388    const MAY_REQUIRE_REALLOC: bool = false;
389
390    type Lower = <u32 as ComponentType>::Lower;
391
392    fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> {
393        match ty {
394            InterfaceType::Own(_) | InterfaceType::Borrow(_) => Ok(()),
395            other => bail!("expected `own` or `borrow`, found `{}`", desc(other)),
396        }
397    }
398}
399
400unsafe impl Lower for ResourceAny {
401    fn linear_lower_to_flat<T>(
402        &self,
403        cx: &mut LowerContext<'_, T>,
404        ty: InterfaceType,
405        dst: &mut MaybeUninit<Self::Lower>,
406    ) -> Result<()> {
407        self.lower_to_index(cx, ty)?
408            .linear_lower_to_flat(cx, InterfaceType::U32, dst)
409    }
410
411    fn linear_lower_to_memory<T>(
412        &self,
413        cx: &mut LowerContext<'_, T>,
414        ty: InterfaceType,
415        offset: usize,
416    ) -> Result<()> {
417        self.lower_to_index(cx, ty)?
418            .linear_lower_to_memory(cx, InterfaceType::U32, offset)
419    }
420}
421
422unsafe impl Lift for ResourceAny {
423    fn linear_lift_from_flat(
424        cx: &mut LiftContext<'_>,
425        ty: InterfaceType,
426        src: &Self::Lower,
427    ) -> Result<Self> {
428        let index = u32::linear_lift_from_flat(cx, InterfaceType::U32, src)?;
429        ResourceAny::lift_from_index(cx, ty, index)
430    }
431
432    fn linear_lift_from_memory(
433        cx: &mut LiftContext<'_>,
434        ty: InterfaceType,
435        bytes: &[u8],
436    ) -> Result<Self> {
437        let index = u32::linear_lift_from_memory(cx, InterfaceType::U32, bytes)?;
438        ResourceAny::lift_from_index(cx, ty, index)
439    }
440}