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