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. Specifically a `ResourceAny` has a type and a
7//! `HostResourceIndex` which points inside of a `HostResourceData` structure
8//! inside of a store. The `ResourceAny::resource_drop` method, or a conversion
9//! to `Resource<T>`, is required to be called to avoid leaking data within a
10//! store.
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/// Representation of a resource in the component model, either a guest-defined
25/// or a host-defined resource.
26///
27/// This type is similar to [`Resource`] except that it can be used to represent
28/// any resource, either host or guest. This type cannot be directly constructed
29/// and is only available if the guest returns it to the host (e.g. a function
30/// returning a guest-defined resource) or by a conversion from [`Resource`] via
31/// [`ResourceAny::try_from_resource`].
32/// This type also does not carry a static type parameter `T` for example and
33/// does not have as much information about its type.
34/// This means that it's possible to get runtime type-errors when
35/// using this type because it cannot statically prevent mismatching resource
36/// types.
37///
38/// Like [`Resource`] this type represents either an `own` or a `borrow`
39/// resource internally. Unlike [`Resource`], however, a [`ResourceAny`] must
40/// always be explicitly destroyed with the [`ResourceAny::resource_drop`]
41/// method. This will update internal dynamic state tracking and invoke the
42/// WebAssembly-defined destructor for a resource, if any.
43///
44/// Note that it is required to call `resource_drop` for all instances of
45/// [`ResourceAny`]: even borrows. Both borrows and own handles have state
46/// associated with them that must be discarded by the time they're done being
47/// used.
48#[derive(Debug, PartialEq, Eq, Copy, Clone)]
49pub struct ResourceAny {
50 idx: HostResourceIndex,
51 ty: ResourceType,
52 owned: bool,
53}
54
55impl ResourceAny {
56 pub(crate) fn new(idx: HostResourceIndex, ty: ResourceType, owned: bool) -> ResourceAny {
57 ResourceAny { idx, ty, owned }
58 }
59
60 /// Attempts to convert an imported [`Resource`] into [`ResourceAny`].
61 ///
62 /// * `resource` is the resource to convert.
63 /// * `store` is the store to place the returned resource into.
64 ///
65 /// The returned `ResourceAny` will not have a destructor attached to it
66 /// meaning that if `resource_drop` is called then it will not invoked a
67 /// host-defined destructor. This is similar to how `Resource<T>` does not
68 /// have a destructor associated with it.
69 ///
70 /// # Errors
71 ///
72 /// This method will return an error if `resource` has already been "taken"
73 /// and has ownership transferred elsewhere which can happen in situations
74 /// such as when it's already lowered into a component.
75 ///
76 /// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
77 /// memory allocation fails. See the `OutOfMemory` type's documentation for
78 /// details on Wasmtime's out-of-memory handling.
79 pub fn try_from_resource<T: 'static>(
80 resource: Resource<T>,
81 store: impl AsContextMut,
82 ) -> Result<Self> {
83 resource.try_into_resource_any(store)
84 }
85
86 /// See [`Resource::try_from_resource_any`]
87 ///
88 /// # Errors
89 ///
90 /// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
91 /// memory allocation fails. See the `OutOfMemory` type's documentation for
92 /// details on Wasmtime's out-of-memory handling.
93 pub fn try_into_resource<T: 'static>(self, store: impl AsContextMut) -> Result<Resource<T>> {
94 Resource::try_from_resource_any(self, store)
95 }
96
97 /// See [`ResourceDynamic::try_from_resource_any`]
98 pub fn try_into_resource_dynamic(self, store: impl AsContextMut) -> Result<ResourceDynamic> {
99 ResourceDynamic::try_from_resource_any(self, store)
100 }
101
102 /// See [`Resource::try_from_resource_any`]
103 pub(crate) fn try_into_host_resource<T, D>(
104 self,
105 mut store: impl AsContextMut,
106 ) -> Result<HostResource<T, D>>
107 where
108 T: HostResourceType<D>,
109 D: PartialEq + Send + Sync + Copy + 'static,
110 {
111 let store = store.as_context_mut();
112 let mut tables = HostResourceTables::new_host(store.0);
113 let ResourceAny { idx, ty, owned } = self;
114 let ty = T::typecheck(ty).ok_or_else(|| crate::format_err!("resource type mismatch"))?;
115 if owned {
116 let rep = tables.host_resource_lift_own(idx)?;
117 Ok(HostResource::new_own(rep, ty))
118 } else {
119 // For borrowed handles, first acquire the `rep` via lifting the
120 // borrow. Afterwards though remove any dynamic state associated
121 // with this borrow. `Resource<T>` doesn't participate in dynamic
122 // state tracking and it's assumed embedders know what they're
123 // doing, so the drop call will clear out that a borrow is active
124 //
125 // Note that the result of `drop` should always be `None` as it's a
126 // borrowed handle, so assert so.
127 let rep = tables.host_resource_lift_borrow(idx)?;
128 let res = tables.host_resource_drop(idx)?;
129 assert!(res.is_none());
130 Ok(HostResource::new_borrow(rep, ty))
131 }
132 }
133
134 /// Returns the corresponding type associated with this resource, either a
135 /// host-defined type or a guest-defined type.
136 ///
137 /// This can be compared against [`ResourceType::host`] for example to see
138 /// if it's a host-resource or against a type extracted with
139 /// [`Instance::get_resource`] to see if it's a guest-defined resource.
140 ///
141 /// [`Instance::get_resource`]: crate::component::Instance::get_resource
142 pub fn ty(&self) -> ResourceType {
143 self.ty
144 }
145
146 /// Returns whether this is an owned resource, and if not it's a borrowed
147 /// resource.
148 pub fn owned(&self) -> bool {
149 self.owned
150 }
151
152 /// Destroy this resource and release any state associated with it.
153 ///
154 /// This is required to be called (or the async version) for all instances
155 /// of [`ResourceAny`] to ensure that state associated with this resource is
156 /// properly cleaned up. For owned resources this may execute the
157 /// guest-defined destructor if applicable (or the host-defined destructor
158 /// if one was specified).
159 ///
160 /// # Errors
161 ///
162 /// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
163 /// memory allocation fails. See the `OutOfMemory` type's documentation for
164 /// details on Wasmtime's out-of-memory handling.
165 pub fn resource_drop(self, mut store: impl AsContextMut) -> Result<()> {
166 let mut store = store.as_context_mut();
167 store.0.validate_sync_call()?;
168 self.resource_drop_impl(&mut store)
169 }
170
171 /// Same as [`ResourceAny::resource_drop`] except for use with async stores
172 /// to execute the destructor [asynchronously](crate#async).
173 ///
174 /// # Errors
175 ///
176 /// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
177 /// memory allocation fails. See the `OutOfMemory` type's documentation for
178 /// details on Wasmtime's out-of-memory handling.
179 #[cfg(feature = "async")]
180 pub async fn resource_drop_async(self, mut store: impl AsContextMut<Data: Send>) -> Result<()> {
181 let mut store = store.as_context_mut();
182 store
183 .on_fiber(|store| self.resource_drop_impl(store))
184 .await?
185 }
186
187 fn resource_drop_impl<T: 'static>(self, store: &mut StoreContextMut<'_, T>) -> Result<()> {
188 // Attempt to remove `self.idx` from the host table in `store`.
189 //
190 // This could fail if the index is invalid or if this is removing an
191 // `Own` entry which is currently being borrowed.
192 let pair = HostResourceTables::new_host(store.0).host_resource_drop(self.idx)?;
193
194 let (rep, slot) = match (pair, self.owned) {
195 (Some(pair), true) => pair,
196
197 // A `borrow` was removed from the table and no further
198 // destruction, e.g. the destructor, is required so we're done.
199 (None, false) => return Ok(()),
200
201 _ => unreachable!(),
202 };
203
204 // Implement the reentrance check required by the canonical ABI. Note
205 // that this happens whether or not a destructor is present.
206 //
207 // Note that this should be safe because the raw pointer access in
208 // `flags` is valid due to `store` being the owner of the flags and
209 // flags are never destroyed within the store.
210 if let Some(instance) = slot.instance {
211 if !store.0.may_enter(instance)? {
212 bail!(Trap::CannotEnterComponent);
213 }
214 }
215
216 let dtor = match slot.dtor {
217 Some(dtor) => dtor.as_non_null(),
218 None => return Ok(()),
219 };
220 let mut args = [ValRaw::u32(rep)];
221
222 // Setup async-level task infrastructure for this call. This, for
223 // example, prevents the destructor from blocking.
224 //
225 // Note that if `slot.instance` is `None` then this is skipped. That
226 // means that this is a host resource being destroyed by the host. In
227 // that case restrictions around blocking and such are exempt.
228 if let Some(instance) = slot.instance {
229 store.0.enter_guest_sync_call(None, false, instance)?;
230 }
231
232 // This should be safe because `dtor` has been checked to belong to the
233 // `store` provided which means it's valid and still alive. Additionally
234 // destructors have al been previously type-checked and are guaranteed
235 // to take one i32 argument and return no results, so the parameters
236 // here should be configured correctly.
237 unsafe {
238 crate::Func::call_unchecked_raw(store, dtor, NonNull::from(&mut args))?;
239 }
240
241 if slot.instance.is_some() {
242 store.0.exit_guest_sync_call()?;
243 }
244
245 Ok(())
246 }
247
248 fn lower_to_index<U>(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result<u32> {
249 match ty {
250 InterfaceType::Own(t) => {
251 if cx.resource_type(t) != self.ty {
252 bail!("mismatched resource types");
253 }
254 let rep = cx.host_resource_lift_own(self.idx)?;
255 cx.guest_resource_lower_own(t, rep)
256 }
257 InterfaceType::Borrow(t) => {
258 if cx.resource_type(t) != self.ty {
259 bail!("mismatched resource types");
260 }
261 let rep = cx.host_resource_lift_borrow(self.idx)?;
262 cx.guest_resource_lower_borrow(t, rep)
263 }
264 _ => bad_type_info(),
265 }
266 }
267
268 fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result<Self> {
269 match ty {
270 InterfaceType::Own(t) => {
271 let ty = cx.resource_type(t);
272 let (rep, dtor, flags) = cx.guest_resource_lift_own(t, index)?;
273 let idx = cx.host_resource_lower_own(rep, dtor, flags)?;
274 Ok(ResourceAny {
275 idx,
276 ty,
277 owned: true,
278 })
279 }
280 InterfaceType::Borrow(t) => {
281 let ty = cx.resource_type(t);
282 let rep = cx.guest_resource_lift_borrow(t, index)?;
283 let idx = cx.host_resource_lower_borrow(rep)?;
284 Ok(ResourceAny {
285 idx,
286 ty,
287 owned: false,
288 })
289 }
290 _ => bad_type_info(),
291 }
292 }
293}
294
295unsafe impl ComponentType for ResourceAny {
296 const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4;
297 const MAY_REQUIRE_REALLOC: bool = false;
298
299 type Lower = <u32 as ComponentType>::Lower;
300
301 fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> {
302 match ty {
303 InterfaceType::Own(_) | InterfaceType::Borrow(_) => Ok(()),
304 other => bail!("expected `own` or `borrow`, found `{}`", desc(other)),
305 }
306 }
307}
308
309unsafe impl Lower for ResourceAny {
310 fn linear_lower_to_flat<T>(
311 &self,
312 cx: &mut LowerContext<'_, T>,
313 ty: InterfaceType,
314 dst: &mut MaybeUninit<Self::Lower>,
315 ) -> Result<()> {
316 self.lower_to_index(cx, ty)?
317 .linear_lower_to_flat(cx, InterfaceType::U32, dst)
318 }
319
320 fn linear_lower_to_memory<T>(
321 &self,
322 cx: &mut LowerContext<'_, T>,
323 ty: InterfaceType,
324 offset: usize,
325 ) -> Result<()> {
326 self.lower_to_index(cx, ty)?
327 .linear_lower_to_memory(cx, InterfaceType::U32, offset)
328 }
329}
330
331unsafe impl Lift for ResourceAny {
332 fn linear_lift_from_flat(
333 cx: &mut LiftContext<'_>,
334 ty: InterfaceType,
335 src: &Self::Lower,
336 ) -> Result<Self> {
337 let index = u32::linear_lift_from_flat(cx, InterfaceType::U32, src)?;
338 ResourceAny::lift_from_index(cx, ty, index)
339 }
340
341 fn linear_lift_from_memory(
342 cx: &mut LiftContext<'_>,
343 ty: InterfaceType,
344 bytes: &[u8],
345 ) -> Result<Self> {
346 let index = u32::linear_lift_from_memory(cx, InterfaceType::U32, bytes)?;
347 ResourceAny::lift_from_index(cx, ty, index)
348 }
349}