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}