1 //! Implementation of [`FutureAny`] and [`StreamAny`].
2 
3 use crate::component::concurrent::futures_and_streams::{self, TransmitOrigin};
4 use crate::component::concurrent::{TableId, TransmitHandle};
5 use crate::component::func::{LiftContext, LowerContext, bad_type_info, desc};
6 use crate::component::matching::InstanceType;
7 use crate::component::types::{self, FutureType, StreamType};
8 use crate::component::{
9     ComponentInstanceId, ComponentType, FutureReader, Lift, Lower, StreamReader,
10 };
11 use crate::store::StoreOpaque;
12 use crate::{AsContextMut, Result, bail, error::Context};
13 use std::any::TypeId;
14 use std::mem::MaybeUninit;
15 use wasmtime_environ::component::{
16     CanonicalAbiInfo, InterfaceType, TypeFutureTableIndex, TypeStreamTableIndex,
17 };
18 
19 /// Represents a type-erased component model `future`.
20 ///
21 /// This type is similar to [`ResourceAny`](crate::component::ResourceAny)
22 /// where it's a static guarantee that it represents a component model
23 /// `future`, but it does not contain any information about the underlying type
24 /// that is associated with this future. This is intended to be used in
25 /// "dynamically typed" situations where embedders may not know ahead of time
26 /// the type of a `future` being used by component that is loaded.
27 ///
28 /// # Closing futures
29 ///
30 /// A [`FutureAny`] represents a resource that is owned by a [`Store`]. Proper
31 /// disposal of a future requires invoking the [`FutureAny::close`] method to
32 /// ensure that this handle does not leak. If [`FutureAny::close`] is not
33 /// called then memory will not be leaked once the owning [`Store`] is dropped,
34 /// but the resource handle will be leaked until the [`Store`] is dropped.
35 ///
36 /// [`Store`]: crate::Store
37 #[derive(Debug, Clone, PartialEq)]
38 pub struct FutureAny {
39     id: TableId<TransmitHandle>,
40     ty: PayloadType<FutureType>,
41 }
42 
43 impl FutureAny {
44     fn lower_to_index<T>(&self, cx: &mut LowerContext<'_, T>, ty: InterfaceType) -> Result<u32> {
45         // Note that unlike `FutureReader<T>` we need to perform an extra
46         // typecheck to ensure that the dynamic type of this future matches
47         // what the guest we're lowering into expects. This couldn't happen
48         // before this point (see the `ComponentType::typecheck` implementation
49         // for this type), so do it now.
50         let future_ty = match ty {
51             InterfaceType::Future(payload) => payload,
52             _ => bad_type_info(),
53         };
54         let payload = cx.types[cx.types[future_ty].ty].payload.as_ref();
55         self.ty.typecheck_guest(
56             &cx.instance_type(),
57             payload,
58             FutureType::equivalent_payload_guest,
59         )?;
60 
61         // Like `FutureReader<T>`, however, lowering "just" gets a u32.
62         futures_and_streams::lower_future_to_index(self.id, cx, ty)
63     }
64 
65     /// Attempts to convert this [`FutureAny`] to a [`FutureReader<T>`]
66     /// with a statically known type.
67     ///
68     /// # Errors
69     ///
70     /// This function will return an error if `T` does not match the type of
71     /// value on this future.
72     pub fn try_into_future_reader<T>(self) -> Result<FutureReader<T>>
73     where
74         T: ComponentType + 'static,
75     {
76         self.ty
77             .typecheck_host::<T>(FutureType::equivalent_payload_host::<T>)?;
78         Ok(FutureReader::new_(self.id))
79     }
80 
81     /// Attempts to convert `reader` to a [`FutureAny`], erasing its statically
82     /// known type.
83     ///
84     /// # Errors
85     ///
86     /// This function will return an error if `reader` does not belong to
87     /// `store`.
88     pub fn try_from_future_reader<T>(
89         mut store: impl AsContextMut,
90         reader: FutureReader<T>,
91     ) -> Result<Self>
92     where
93         T: ComponentType + 'static,
94     {
95         let store = store.as_context_mut();
96         let ty = match store.0.transmit_origin(reader.id())? {
97             TransmitOrigin::Host => PayloadType::new_host::<T>(),
98             TransmitOrigin::GuestFuture(id, ty) => PayloadType::new_guest_future(store.0, id, ty),
99             TransmitOrigin::GuestStream(..) => bail!("not a future"),
100         };
101         Ok(FutureAny {
102             id: reader.id(),
103             ty,
104         })
105     }
106 
107     fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result<Self> {
108         let id = futures_and_streams::lift_index_to_future(cx, ty, index)?;
109         let InterfaceType::Future(ty) = ty else {
110             unreachable!()
111         };
112         let ty = cx.types[ty].ty;
113         Ok(FutureAny {
114             id,
115             // Note that this future might actually be a host-originating
116             // future which means that this ascription of "the type is the
117             // guest" may be slightly in accurate. The guest, however, has the
118             // most accurate view of what type this future has so that should
119             // be reasonable to ascribe as the type here regardless.
120             ty: PayloadType::Guest(FutureType::from(ty, &cx.instance_type())),
121         })
122     }
123 
124     /// Close this `FutureAny`.
125     ///
126     /// This will close this future and cause any write that happens later to
127     /// returned `DROPPED`.
128     ///
129     /// # Panics
130     ///
131     /// Panics if the `store` does not own this future. Usage of this future
132     /// after calling `close` will also cause a panic.
133     pub fn close(&mut self, mut store: impl AsContextMut) {
134         futures_and_streams::future_close(store.as_context_mut().0, &mut self.id)
135     }
136 }
137 
138 unsafe impl ComponentType for FutureAny {
139     const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4;
140 
141     type Lower = <u32 as ComponentType>::Lower;
142 
143     fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> {
144         match ty {
145             InterfaceType::Future(_) => Ok(()),
146             other => bail!("expected `future`, found `{}`", desc(other)),
147         }
148     }
149 }
150 
151 unsafe impl Lower for FutureAny {
152     fn linear_lower_to_flat<T>(
153         &self,
154         cx: &mut LowerContext<'_, T>,
155         ty: InterfaceType,
156         dst: &mut MaybeUninit<Self::Lower>,
157     ) -> Result<()> {
158         self.lower_to_index(cx, ty)?
159             .linear_lower_to_flat(cx, InterfaceType::U32, dst)
160     }
161 
162     fn linear_lower_to_memory<T>(
163         &self,
164         cx: &mut LowerContext<'_, T>,
165         ty: InterfaceType,
166         offset: usize,
167     ) -> Result<()> {
168         self.lower_to_index(cx, ty)?
169             .linear_lower_to_memory(cx, InterfaceType::U32, offset)
170     }
171 }
172 
173 unsafe impl Lift for FutureAny {
174     fn linear_lift_from_flat(
175         cx: &mut LiftContext<'_>,
176         ty: InterfaceType,
177         src: &Self::Lower,
178     ) -> Result<Self> {
179         let index = u32::linear_lift_from_flat(cx, InterfaceType::U32, src)?;
180         Self::lift_from_index(cx, ty, index)
181     }
182 
183     fn linear_lift_from_memory(
184         cx: &mut LiftContext<'_>,
185         ty: InterfaceType,
186         bytes: &[u8],
187     ) -> Result<Self> {
188         let index = u32::linear_lift_from_memory(cx, InterfaceType::U32, bytes)?;
189         Self::lift_from_index(cx, ty, index)
190     }
191 }
192 
193 /// Represents a type-erased component model `stream`.
194 ///
195 /// This type is similar to [`ResourceAny`](crate::component::ResourceAny)
196 /// where it's a static guarantee that it represents a component model
197 /// `stream`, but it does not contain any information about the underlying type
198 /// that is associated with this stream. This is intended to be used in
199 /// "dynamically typed" situations where embedders may not know ahead of time
200 /// the type of a `stream` being used by component that is loaded.
201 ///
202 /// # Closing streams
203 ///
204 /// A [`StreamAny`] represents a resource that is owned by a [`Store`]. Proper
205 /// disposal of a stream requires invoking the [`StreamAny::close`] method to
206 /// ensure that this handle does not leak. If [`StreamAny::close`] is not
207 /// called then memory will not be leaked once the owning [`Store`] is dropped,
208 /// but the resource handle will be leaked until the [`Store`] is dropped.
209 ///
210 /// [`Store`]: crate::Store
211 #[derive(Debug, Clone, PartialEq)]
212 pub struct StreamAny {
213     id: TableId<TransmitHandle>,
214     ty: PayloadType<StreamType>,
215 }
216 
217 impl StreamAny {
218     fn lower_to_index<T>(&self, cx: &mut LowerContext<'_, T>, ty: InterfaceType) -> Result<u32> {
219         // See comments in `FutureAny::lower_to_index` for why this is
220         // different from `StreamReader`'s implementation.
221         let stream_ty = match ty {
222             InterfaceType::Stream(payload) => payload,
223             _ => bad_type_info(),
224         };
225         let payload = cx.types[cx.types[stream_ty].ty].payload.as_ref();
226         self.ty.typecheck_guest(
227             &cx.instance_type(),
228             payload,
229             StreamType::equivalent_payload_guest,
230         )?;
231         futures_and_streams::lower_stream_to_index(self.id, cx, ty)
232     }
233 
234     /// Attempts to convert this [`StreamAny`] to a [`StreamReader<T>`]
235     /// with a statically known type.
236     ///
237     /// # Errors
238     ///
239     /// This function will return an error if `T` does not match the type of
240     /// value on this stream.
241     pub fn try_into_stream_reader<T>(self) -> Result<StreamReader<T>>
242     where
243         T: ComponentType + 'static,
244     {
245         self.ty
246             .typecheck_host::<T>(StreamType::equivalent_payload_host::<T>)?;
247         Ok(StreamReader::new_(self.id))
248     }
249 
250     /// Attempts to convert `reader` to a [`StreamAny`], erasing its statically
251     /// known type.
252     ///
253     /// # Errors
254     ///
255     /// This function will return an error if `reader` does not belong to
256     /// `store`.
257     pub fn try_from_stream_reader<T>(
258         mut store: impl AsContextMut,
259         reader: StreamReader<T>,
260     ) -> Result<Self>
261     where
262         T: ComponentType + 'static,
263     {
264         let store = store.as_context_mut();
265         let ty = match store.0.transmit_origin(reader.id())? {
266             TransmitOrigin::Host => PayloadType::new_host::<T>(),
267             TransmitOrigin::GuestStream(id, ty) => PayloadType::new_guest_stream(store.0, id, ty),
268             TransmitOrigin::GuestFuture(..) => bail!("not a stream"),
269         };
270         Ok(StreamAny {
271             id: reader.id(),
272             ty,
273         })
274     }
275 
276     fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result<Self> {
277         let id = futures_and_streams::lift_index_to_stream(cx, ty, index)?;
278         let InterfaceType::Stream(ty) = ty else {
279             unreachable!()
280         };
281         let ty = cx.types[ty].ty;
282         Ok(StreamAny {
283             id,
284             // Note that this stream might actually be a host-originating, but
285             // see the documentation in `FutureAny::lift_from_index` for why
286             // this should be ok.
287             ty: PayloadType::Guest(StreamType::from(ty, &cx.instance_type())),
288         })
289     }
290 
291     /// Close this `StreamAny`.
292     ///
293     /// This will close this stream and cause any write that happens later to
294     /// returned `DROPPED`.
295     ///
296     /// # Panics
297     ///
298     /// Panics if the `store` does not own this stream. Usage of this stream
299     /// after calling `close` will also cause a panic.
300     pub fn close(&mut self, mut store: impl AsContextMut) {
301         futures_and_streams::future_close(store.as_context_mut().0, &mut self.id)
302     }
303 }
304 
305 unsafe impl ComponentType for StreamAny {
306     const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4;
307 
308     type Lower = <u32 as ComponentType>::Lower;
309 
310     fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> {
311         match ty {
312             InterfaceType::Stream(_) => Ok(()),
313             other => bail!("expected `stream`, found `{}`", desc(other)),
314         }
315     }
316 }
317 
318 unsafe impl Lower for StreamAny {
319     fn linear_lower_to_flat<T>(
320         &self,
321         cx: &mut LowerContext<'_, T>,
322         ty: InterfaceType,
323         dst: &mut MaybeUninit<Self::Lower>,
324     ) -> Result<()> {
325         self.lower_to_index(cx, ty)?
326             .linear_lower_to_flat(cx, InterfaceType::U32, dst)
327     }
328 
329     fn linear_lower_to_memory<T>(
330         &self,
331         cx: &mut LowerContext<'_, T>,
332         ty: InterfaceType,
333         offset: usize,
334     ) -> Result<()> {
335         self.lower_to_index(cx, ty)?
336             .linear_lower_to_memory(cx, InterfaceType::U32, offset)
337     }
338 }
339 
340 unsafe impl Lift for StreamAny {
341     fn linear_lift_from_flat(
342         cx: &mut LiftContext<'_>,
343         ty: InterfaceType,
344         src: &Self::Lower,
345     ) -> Result<Self> {
346         let index = u32::linear_lift_from_flat(cx, InterfaceType::U32, src)?;
347         Self::lift_from_index(cx, ty, index)
348     }
349 
350     fn linear_lift_from_memory(
351         cx: &mut LiftContext<'_>,
352         ty: InterfaceType,
353         bytes: &[u8],
354     ) -> Result<Self> {
355         let index = u32::linear_lift_from_memory(cx, InterfaceType::U32, bytes)?;
356         Self::lift_from_index(cx, ty, index)
357     }
358 }
359 
360 #[derive(Debug, Clone)]
361 enum PayloadType<T> {
362     Guest(T),
363     Host {
364         id: TypeId,
365         typecheck: fn(Option<&InterfaceType>, &InstanceType<'_>) -> Result<()>,
366     },
367 }
368 
369 impl<T: PartialEq> PartialEq for PayloadType<T> {
370     fn eq(&self, other: &Self) -> bool {
371         match (self, other) {
372             (PayloadType::Guest(a), PayloadType::Guest(b)) => a == b,
373             (PayloadType::Guest(_), _) => false,
374             (PayloadType::Host { id: a_id, .. }, PayloadType::Host { id: b_id, .. }) => {
375                 a_id == b_id
376             }
377             (PayloadType::Host { .. }, _) => false,
378         }
379     }
380 }
381 
382 impl PayloadType<FutureType> {
383     fn new_guest_future(
384         store: &StoreOpaque,
385         id: ComponentInstanceId,
386         ty: TypeFutureTableIndex,
387     ) -> Self {
388         let types = InstanceType::new(&store.component_instance(id));
389         let ty = types.types[ty].ty;
390         PayloadType::Guest(FutureType::from(ty, &types))
391     }
392 }
393 
394 impl PayloadType<StreamType> {
395     fn new_guest_stream(
396         store: &StoreOpaque,
397         id: ComponentInstanceId,
398         ty: TypeStreamTableIndex,
399     ) -> Self {
400         let types = InstanceType::new(&store.component_instance(id));
401         let ty = types.types[ty].ty;
402         PayloadType::Guest(StreamType::from(ty, &types))
403     }
404 }
405 
406 impl<T> PayloadType<T> {
407     fn new_host<P>() -> Self
408     where
409         P: ComponentType + 'static,
410     {
411         PayloadType::Host {
412             typecheck: types::typecheck_payload::<P>,
413             id: TypeId::of::<P>(),
414         }
415     }
416 
417     fn typecheck_guest(
418         &self,
419         types: &InstanceType<'_>,
420         payload: Option<&InterfaceType>,
421         equivalent: fn(&T, &InstanceType<'_>, Option<&InterfaceType>) -> bool,
422     ) -> Result<()> {
423         match self {
424             Self::Guest(ty) => {
425                 if equivalent(ty, types, payload) {
426                     Ok(())
427                 } else {
428                     bail!("future payload types differ")
429                 }
430             }
431             Self::Host { typecheck, .. } => {
432                 typecheck(payload, types).context("future payload types differ")
433             }
434         }
435     }
436 
437     fn typecheck_host<P>(&self, equivalent: fn(&T) -> Result<()>) -> Result<()>
438     where
439         P: ComponentType + 'static,
440     {
441         match self {
442             Self::Guest(ty) => equivalent(ty),
443             Self::Host { id, .. } => {
444                 if *id == TypeId::of::<P>() {
445                     Ok(())
446                 } else {
447                     bail!("future payload types differ")
448                 }
449             }
450         }
451     }
452 }
453