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::{UncaughtException, 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///
76/// Note that special care should be taken when handling `ResourceAny` in async
77/// functions. In this scenario an async host function can be cancelled
78/// (dropped) at any time and if `ResourceAny` isn't destroyed then what it's
79/// referring to can leak within the store. If a `ResourceAny` is a borrow
80/// internally then failing to drop it, even on cancellation, will lead to a
81/// trap being raised and poisoning the store.
82///
83/// [`LinkerInstance::func_wrap_concurrent`]: crate::component::LinkerInstance::func_wrap_concurrent
84#[derive(Debug, PartialEq, Eq, Copy, Clone)]
85pub struct ResourceAny {
86    idx: ResourceAnyIndex,
87    ty: ResourceType,
88    owned: bool,
89}
90
91impl ResourceAny {
92    pub(crate) fn new(idx: HostResourceIndex, ty: ResourceType, owned: bool) -> ResourceAny {
93        ResourceAny {
94            idx: ResourceAnyIndex::Table(idx),
95            ty,
96            owned,
97        }
98    }
99
100    pub(crate) fn new_borrow(rep: u32, ty: ResourceType) -> ResourceAny {
101        ResourceAny {
102            idx: ResourceAnyIndex::Borrow(rep),
103            ty,
104            owned: false,
105        }
106    }
107
108    /// Attempts to convert a host-defined [`Resource`] into [`ResourceAny`].
109    ///
110    /// * `resource` is the resource to convert.
111    /// * `store` is the store to place the returned resource into.
112    ///
113    /// The returned `ResourceAny` has no destructor attached to it, so
114    /// `resource_drop` will not invoke a host-defined destructor. This matches
115    /// [`Resource`], which has no associated destructor.
116    ///
117    /// # Errors
118    ///
119    /// This method will return an error if `resource` has already been "taken"
120    /// and has ownership transferred elsewhere which can happen in situations
121    /// such as when it's already lowered into a component.
122    ///
123    /// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
124    /// memory allocation fails. See the `OutOfMemory` type's documentation for
125    /// details on Wasmtime's out-of-memory handling.
126    pub fn try_from_resource<T: 'static>(
127        resource: Resource<T>,
128        store: impl AsContextMut,
129    ) -> Result<Self> {
130        resource.try_into_resource_any(store)
131    }
132
133    /// Creates a new owned resource of type `ty` with the `rep` specified.
134    ///
135    /// Unlike `try_from_resource`, which derives the resource type from a Rust
136    /// type parameter, this takes the type dynamically. It is used to
137    /// synthesize a handle whose type is only known at runtime, such as a
138    /// default value for an `own` result; see
139    /// `Linker::define_unknown_imports_as_default_values`.
140    ///
141    /// No destructor is attached.
142    ///
143    /// # Errors
144    ///
145    /// Returns an error if `ty` is not host-defined. A guest resource's
146    /// representation belongs to the guest that defines it, so the host
147    /// cannot conjure an instance of one.
148    pub(crate) fn new_own_dynamic(
149        ty: ResourceType,
150        rep: u32,
151        mut store: impl AsContextMut,
152    ) -> Result<Self> {
153        if !ty.is_host_defined() {
154            bail!("cannot synthesize a resource of a non-host-defined type");
155        }
156        let store = store.as_context_mut();
157        let idx =
158            HostResourceTables::new_host(store.0)?.host_resource_lower_own(rep, None, None)?;
159        Ok(ResourceAny::new(idx, ty, true))
160    }
161
162    /// Attempts to convert this value into a statically typed, host-defined
163    /// [`Resource`].
164    ///
165    /// This conversion accepts only host-defined resources of type `T`.
166    /// Guest-defined resources must remain [`ResourceAny`] values and be used
167    /// through their generated functions and resource projection.
168    ///
169    /// # Errors
170    ///
171    /// See [`Resource::try_from_resource_any`] for the conditions under which
172    /// this conversion fails.
173    ///
174    /// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
175    /// memory allocation fails. See the `OutOfMemory` type's documentation for
176    /// details on Wasmtime's out-of-memory handling.
177    pub fn try_into_resource<T: 'static>(self, store: impl AsContextMut) -> Result<Resource<T>> {
178        Resource::try_from_resource_any(self, store)
179    }
180
181    /// Attempts to convert this value into a dynamically typed, host-defined
182    /// [`ResourceDynamic`].
183    ///
184    /// This conversion accepts only host-defined resources. Guest-defined
185    /// resources must remain [`ResourceAny`] values and be used through their
186    /// generated functions and resource projection.
187    ///
188    /// # Errors
189    ///
190    /// See [`Resource::try_from_resource_any`] for the conditions under which
191    /// this conversion fails.
192    pub fn try_into_resource_dynamic(self, store: impl AsContextMut) -> Result<ResourceDynamic> {
193        ResourceDynamic::try_from_resource_any(self, store)
194    }
195
196    /// See [`Resource::try_from_resource_any`]
197    pub(crate) fn try_into_host_resource<T, D>(
198        self,
199        mut store: impl AsContextMut,
200    ) -> Result<HostResource<T, D>>
201    where
202        T: HostResourceType<D>,
203        D: PartialEq + Send + Sync + Copy + 'static,
204    {
205        let ResourceAny { idx, ty, owned } = self;
206        let ty = T::typecheck(ty).ok_or_else(|| crate::format_err!("resource type mismatch"))?;
207        match idx {
208            ResourceAnyIndex::Borrow(rep) => {
209                assert!(!owned);
210                Ok(HostResource::new_borrow(rep, ty))
211            }
212            ResourceAnyIndex::Table(idx) => {
213                let store = store.as_context_mut();
214                let mut tables = HostResourceTables::new_host(store.0)?;
215                if owned {
216                    let rep = tables.host_resource_lift_own(idx)?;
217                    Ok(HostResource::new_own(rep, ty))
218                } else {
219                    // Typed borrows have no dynamic state. Remove the table
220                    // entry after reading its representation.
221                    let rep = tables.host_resource_rep(idx)?;
222                    let res = tables.host_resource_drop(idx)?;
223                    assert!(res.is_none());
224                    Ok(HostResource::new_borrow(rep, ty))
225                }
226            }
227        }
228    }
229
230    /// Returns the corresponding type associated with this resource, either a
231    /// host-defined type or a guest-defined type.
232    ///
233    /// This can be compared against [`ResourceType::host`] for example to see
234    /// if it's a host-resource or against a type extracted with
235    /// [`Instance::get_resource`] to see if it's a guest-defined resource.
236    ///
237    /// [`Instance::get_resource`]: crate::component::Instance::get_resource
238    pub fn ty(&self) -> ResourceType {
239        self.ty
240    }
241
242    /// Returns whether this is an owned resource, and if not it's a borrowed
243    /// resource.
244    pub fn owned(&self) -> bool {
245        self.owned
246    }
247
248    /// Destroy this resource and release any state associated with it.
249    ///
250    /// This is required for resources with host table state. For synthetic
251    /// borrows converted from [`Resource::new_borrow`] it has no effect.
252    /// For owned resources this may execute the guest-defined destructor if
253    /// applicable (or the host-defined destructor if one was specified).
254    ///
255    /// Exactly one of the following must be called for each [`ResourceAny`],
256    /// depending on how the store is being driven:
257    ///
258    /// * [`ResourceAny::resource_drop`] for synchronous stores.
259    /// * `ResourceAny::resource_drop_async` for [async](crate#async) stores
260    ///   when a `StoreContextMut` is available.
261    /// * `ResourceAny::resource_drop_concurrent` when only an `Accessor` is
262    ///   available, such as inside `Store::run_concurrent` or an
263    ///   `AccessorTask`.
264    ///
265    /// # Errors
266    ///
267    /// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
268    /// memory allocation fails. See the `OutOfMemory` type's documentation for
269    /// details on Wasmtime's out-of-memory handling.
270    ///
271    /// This will also return an error if called recursively from within e.g. a
272    /// `call_async` invocation.
273    pub fn resource_drop(self, mut store: impl AsContextMut) -> Result<()> {
274        let mut store = store.as_context_mut();
275        store.0.validate_sync_call()?;
276        self.resource_drop_impl(&mut store)
277    }
278
279    /// Drops this resource without the `validate_sync_call` check that the
280    /// public [`ResourceAny::resource_drop`] performs.
281    ///
282    /// This is for dropping handles that are already owned by the host while it
283    /// is servicing a guest call, such as the parameters handed to an unknown
284    /// import stubbed by
285    /// [`Linker::define_unknown_imports_as_default_values`](crate::component::Linker::define_unknown_imports_as_default_values).
286    /// Such a stub always runs on a fiber, where `validate_sync_call` would
287    /// reject the public entry point even though the drop itself is the same
288    /// bookkeeping the runtime does whenever a guest drops a handle mid-call.
289    pub(crate) fn resource_drop_in_guest_call<T: 'static>(
290        self,
291        store: &mut StoreContextMut<'_, T>,
292    ) -> Result<()> {
293        self.resource_drop_impl(store)
294    }
295
296    /// Same as [`ResourceAny::resource_drop`] except for use with async stores
297    /// to execute the destructor [asynchronously](crate#async).
298    ///
299    /// # Errors
300    ///
301    /// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
302    /// memory allocation fails. See the `OutOfMemory` type's documentation for
303    /// details on Wasmtime's out-of-memory handling.
304    #[cfg(feature = "async")]
305    pub async fn resource_drop_async(self, mut store: impl AsContextMut<Data: Send>) -> Result<()> {
306        let mut store = store.as_context_mut();
307
308        // Use the event loop if concurrency support is enabled to handle thread
309        // switches, for example, if wasm is run.
310        #[cfg(feature = "component-model-async")]
311        if store.0.concurrency_support() {
312            return store
313                .run_concurrent_trap_on_idle(async |store| {
314                    self.resource_drop_concurrent(store).await
315                })
316                .await?;
317        }
318
319        store
320            .on_fiber(|store| self.resource_drop_impl(store))
321            .await?
322    }
323
324    /// Same as [`ResourceAny::resource_drop`] except for use with an
325    /// [`Accessor`](crate::component::Accessor) while a store is executing
326    /// [`Store::run_concurrent`](crate::Store::run_concurrent).
327    ///
328    /// The resource drop is queued for execution on the store's worker fiber.
329    /// This method must be awaited while the store's concurrent event loop is
330    /// running so the queued drop can make progress.
331    ///
332    /// Once this future has been polled and the drop has been queued, dropping
333    /// the future does not cancel the resource drop.
334    ///
335    /// # Errors
336    ///
337    /// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
338    /// memory allocation fails. See the `OutOfMemory` type's documentation for
339    /// details on Wasmtime's out-of-memory handling.
340    #[cfg(feature = "component-model-async")]
341    pub async fn resource_drop_concurrent(
342        self,
343        accessor: impl crate::component::AsAccessor,
344    ) -> Result<()> {
345        let receiver = accessor.as_accessor().with(|mut store| -> Result<_> {
346            let mut store = store.as_context_mut();
347            let (sender, receiver) = futures::channel::oneshot::channel();
348            let token = crate::store::StoreToken::new(store.as_context_mut());
349            store.0.queue_task(move |store| {
350                _ = sender.send(self.resource_drop_impl(&mut token.as_context_mut(store)));
351                Ok(())
352            })?;
353            Ok(receiver)
354        })?;
355        receiver
356            .await
357            .map_err(|_| format_err!("resource drop task canceled"))?
358    }
359
360    fn resource_drop_impl<T: 'static>(self, store: &mut StoreContextMut<'_, T>) -> Result<()> {
361        // Attempt to remove `self.idx` from the host table in `store`.
362        //
363        // This could fail if the index is invalid or if this is removing an
364        // `Own` entry which is currently being borrowed.
365        let idx = match self.idx {
366            ResourceAnyIndex::Table(idx) => idx,
367            ResourceAnyIndex::Borrow(_) => return Ok(()),
368        };
369        let pair = HostResourceTables::new_host(store.0)?.host_resource_drop(idx)?;
370
371        let (rep, slot) = match (pair, self.owned) {
372            (Some(pair), true) => pair,
373
374            // A `borrow` was removed from the table and no further
375            // destruction, e.g. the destructor, is required so we're done.
376            (None, false) => return Ok(()),
377
378            _ => unreachable!(),
379        };
380
381        if slot.instance.is_some() && !store.0.may_enter() {
382            bail!(Trap::CannotEnterComponent);
383        }
384
385        let dtor = match slot.dtor {
386            Some(dtor) => dtor.as_non_null(),
387            None => return Ok(()),
388        };
389        let mut args = [ValRaw::u32(rep)];
390
391        // Setup async-level task infrastructure for this call. This, for
392        // example, prevents the destructor from blocking.
393        //
394        // Note that if `slot.instance` is `None` then this is skipped. That
395        // means that this is a host resource being destroyed by the host. In
396        // that case restrictions around blocking and such are exempt.
397        if let Some(instance) = slot.instance {
398            store.0.enter_guest_sync_call(false, instance)?;
399        }
400
401        // This should be safe because `dtor` has been checked to belong to the
402        // `store` provided which means it's valid and still alive. Additionally
403        // destructors have al been previously type-checked and are guaranteed
404        // to take one i32 argument and return no results, so the parameters
405        // here should be configured correctly.
406        let result = unsafe {
407            crate::Func::call_unchecked_raw(
408                store,
409                dtor,
410                NonNull::from(&mut args),
411                UncaughtException::Trap,
412            )
413        };
414        if result.is_err() {
415            store.0.set_trapped();
416        }
417        result?;
418
419        if slot.instance.is_some() {
420            store.0.exit_guest_sync_call()?;
421        }
422
423        Ok(())
424    }
425
426    fn lower_to_index<U>(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result<u32> {
427        match ty {
428            InterfaceType::Own(t) => {
429                if cx.resource_type(t) != self.ty {
430                    bail!("mismatched resource types");
431                }
432                let rep = match self.idx {
433                    ResourceAnyIndex::Table(idx) => cx.host_resource_lift_own(idx)?,
434                    ResourceAnyIndex::Borrow(_) => {
435                        bail!("cannot lower a `borrow` resource into an `own`")
436                    }
437                };
438                cx.guest_resource_lower_own(t, rep)
439            }
440            InterfaceType::Borrow(t) => {
441                if cx.resource_type(t) != self.ty {
442                    bail!("mismatched resource types");
443                }
444                let rep = match self.idx {
445                    ResourceAnyIndex::Table(idx) => cx.host_resource_lift_borrow(idx)?,
446                    ResourceAnyIndex::Borrow(rep) => rep,
447                };
448                cx.guest_resource_lower_borrow(t, rep)
449            }
450            _ => bad_type_info(),
451        }
452    }
453
454    fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result<Self> {
455        match ty {
456            InterfaceType::Own(t) => {
457                let ty = cx.resource_type(t);
458                let (rep, dtor, flags) = cx.guest_resource_lift_own(t, index)?;
459                let idx = cx.host_resource_lower_own(rep, dtor, flags)?;
460                Ok(ResourceAny::new(idx, ty, true))
461            }
462            InterfaceType::Borrow(t) => {
463                let ty = cx.resource_type(t);
464                let rep = cx.guest_resource_lift_borrow(t, index)?;
465                let idx = cx.host_resource_lower_borrow(rep)?;
466                Ok(ResourceAny::new(idx, ty, false))
467            }
468            _ => bad_type_info(),
469        }
470    }
471}
472
473unsafe impl ComponentType for ResourceAny {
474    const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4;
475    const MAY_REQUIRE_REALLOC: bool = false;
476
477    type Lower = <u32 as ComponentType>::Lower;
478
479    fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> {
480        match ty {
481            InterfaceType::Own(_) | InterfaceType::Borrow(_) => Ok(()),
482            other => bail!("expected `own` or `borrow`, found `{}`", desc(other)),
483        }
484    }
485}
486
487unsafe impl Lower for ResourceAny {
488    fn linear_lower_to_flat<T>(
489        &self,
490        cx: &mut LowerContext<'_, T>,
491        ty: InterfaceType,
492        dst: &mut MaybeUninit<Self::Lower>,
493    ) -> Result<()> {
494        self.lower_to_index(cx, ty)?
495            .linear_lower_to_flat(cx, InterfaceType::U32, dst)
496    }
497
498    fn linear_lower_to_memory<T>(
499        &self,
500        cx: &mut LowerContext<'_, T>,
501        ty: InterfaceType,
502        offset: usize,
503    ) -> Result<()> {
504        self.lower_to_index(cx, ty)?
505            .linear_lower_to_memory(cx, InterfaceType::U32, offset)
506    }
507}
508
509unsafe impl Lift for ResourceAny {
510    fn linear_lift_from_flat(
511        cx: &mut LiftContext<'_>,
512        ty: InterfaceType,
513        src: &Self::Lower,
514    ) -> Result<Self> {
515        let index = u32::linear_lift_from_flat(cx, InterfaceType::U32, src)?;
516        ResourceAny::lift_from_index(cx, ty, index)
517    }
518
519    fn linear_lift_from_memory(
520        cx: &mut LiftContext<'_>,
521        ty: InterfaceType,
522        bytes: &[u8],
523    ) -> Result<Self> {
524        let index = u32::linear_lift_from_memory(cx, InterfaceType::U32, bytes)?;
525        ResourceAny::lift_from_index(cx, ty, index)
526    }
527}