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}