1 use crate::AsContextMut;
2 use crate::component::func::{LiftContext, LowerContext, bad_type_info, desc};
3 use crate::component::matching::InstanceType;
4 use crate::component::resources::{HostResourceIndex, HostResourceTables};
5 use crate::component::{ComponentType, Lift, Lower, ResourceAny, ResourceType};
6 use crate::prelude::*;
7 use core::fmt;
8 use core::marker;
9 use core::mem::MaybeUninit;
10 use core::sync::atomic::{AtomicU32, Ordering::Relaxed};
11 use wasmtime_environ::component::{CanonicalAbiInfo, InterfaceType};
12 
13 /// Internal type in Wasmtime used to represent host-defined resources that are
14 /// not tracked within the store.
15 pub struct HostResource<T: HostResourceType<D>, D> {
16     /// The host-defined 32-bit representation of this resource.
17     rep: u32,
18 
19     /// Metadata, if necessary, tracking the type of this resource.
20     ty: D,
21 
22     /// Internal state about borrows and such.
23     state: AtomicResourceState,
24 
25     _marker: marker::PhantomData<fn() -> T>,
26 }
27 
28 // FIXME(rust-lang/rust#110338) the `D` type parameter here should be an
29 // associated type. In a first attempt at doing that the `wasmtime-wasi-io`
30 // crate failed to compile with obscure errors. At least this is an internal
31 // trait for now...
32 pub trait HostResourceType<D> {
33     /// Tests whether `ty` matches this resource type.
typecheck(ty: ResourceType) -> Option<D>34     fn typecheck(ty: ResourceType) -> Option<D>;
35     /// Converts `Data` to a `ResourceType`.
resource_type(data: D) -> ResourceType36     fn resource_type(data: D) -> ResourceType;
37 }
38 
39 /// Internal dynamic state tracking for this resource. This can be one of
40 /// four different states:
41 ///
42 /// * `BORROW` / `u64::MAX` - this indicates that this is a borrowed
43 ///   resource. The `rep` doesn't live in the host table and this `Resource`
44 ///   instance is transiently available. It's the host's responsibility to
45 ///   discard this resource when the borrow duration has finished.
46 ///
47 /// * `NOT_IN_TABLE` / `u64::MAX - 1` - this indicates that this is an owned
48 ///   resource not present in any store's table. This resource is not lent
49 ///   out. It can be passed as an `(own $t)` directly into a guest's table
50 ///   or it can be passed as a borrow to a guest which will insert it into
51 ///   a host store's table for dynamic borrow tracking.
52 ///
53 /// * `TAKEN` / `u64::MAX - 2` - while the `rep` is available the resource
54 ///   has been dynamically moved into a guest and cannot be moved in again.
55 ///   This is used for example to prevent the same resource from being
56 ///   passed twice to a guest.
57 ///
58 /// * All other values - any other value indicates that the value is an
59 ///   index into a store's table of host resources. It's guaranteed that the
60 ///   table entry represents a host resource and the resource may have
61 ///   borrow tracking associated with it. The low 32-bits of the value are
62 ///   the table index and the upper 32-bits are the generation.
63 ///
64 /// Note that this is two `AtomicU32` fields but it's not intended to actually
65 /// be used in conjunction with threads as generally a `Store<T>` lives on one
66 /// thread at a time. The pair of `AtomicU32` here is used to ensure that this
67 /// type is `Send + Sync` when captured as a reference to make async
68 /// programming more ergonomic.
69 ///
70 /// Also note that two `AtomicU32` here are used instead of `AtomicU64` to be
71 /// more portable to platforms without 64-bit atomics.
72 struct AtomicResourceState {
73     index: AtomicU32,
74     generation: AtomicU32,
75 }
76 
77 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
78 enum ResourceState {
79     Borrow,
80     NotInTable,
81     Taken,
82     Index(HostResourceIndex),
83 }
84 
85 impl AtomicResourceState {
86     const BORROW: Self = AtomicResourceState::new(ResourceState::Borrow);
87     const NOT_IN_TABLE: Self = AtomicResourceState::new(ResourceState::NotInTable);
88 
new(state: ResourceState) -> AtomicResourceState89     const fn new(state: ResourceState) -> AtomicResourceState {
90         let (index, generation) = state.encode();
91         Self {
92             index: AtomicU32::new(index),
93             generation: AtomicU32::new(generation),
94         }
95     }
96 
get(&self) -> ResourceState97     fn get(&self) -> ResourceState {
98         ResourceState::decode(self.index.load(Relaxed), self.generation.load(Relaxed))
99     }
100 
swap(&self, state: ResourceState) -> ResourceState101     fn swap(&self, state: ResourceState) -> ResourceState {
102         let (index, generation) = state.encode();
103         let index_prev = self.index.load(Relaxed);
104         self.index.store(index, Relaxed);
105         let generation_prev = self.generation.load(Relaxed);
106         self.generation.store(generation, Relaxed);
107         ResourceState::decode(index_prev, generation_prev)
108     }
109 }
110 
111 impl ResourceState {
112     // See comments on `state` above for info about these values.
113     const BORROW: u32 = u32::MAX;
114     const NOT_IN_TABLE: u32 = u32::MAX - 1;
115     const TAKEN: u32 = u32::MAX - 2;
116 
decode(idx: u32, generation: u32) -> ResourceState117     fn decode(idx: u32, generation: u32) -> ResourceState {
118         match generation {
119             Self::BORROW => Self::Borrow,
120             Self::NOT_IN_TABLE => Self::NotInTable,
121             Self::TAKEN => Self::Taken,
122             _ => Self::Index(HostResourceIndex::new(idx, generation)),
123         }
124     }
125 
encode(&self) -> (u32, u32)126     const fn encode(&self) -> (u32, u32) {
127         match self {
128             Self::Borrow => (0, Self::BORROW),
129             Self::NotInTable => (0, Self::NOT_IN_TABLE),
130             Self::Taken => (0, Self::TAKEN),
131             Self::Index(index) => (index.index(), index.generation()),
132         }
133     }
134 }
135 
136 impl<T, D> HostResource<T, D>
137 where
138     T: HostResourceType<D>,
139     D: PartialEq + Send + Sync + Copy + 'static,
140 {
new_own(rep: u32, ty: D) -> Self141     pub fn new_own(rep: u32, ty: D) -> Self {
142         HostResource {
143             state: AtomicResourceState::NOT_IN_TABLE,
144             rep,
145             ty,
146             _marker: marker::PhantomData,
147         }
148     }
149 
new_borrow(rep: u32, ty: D) -> Self150     pub fn new_borrow(rep: u32, ty: D) -> Self {
151         HostResource {
152             state: AtomicResourceState::BORROW,
153             rep,
154             ty,
155             _marker: marker::PhantomData,
156         }
157     }
158 
rep(&self) -> u32159     pub fn rep(&self) -> u32 {
160         self.rep
161     }
162 
ty(&self) -> D163     pub fn ty(&self) -> D {
164         self.ty
165     }
166 
owned(&self) -> bool167     pub fn owned(&self) -> bool {
168         match self.state.get() {
169             ResourceState::Borrow => false,
170             ResourceState::Taken | ResourceState::NotInTable | ResourceState::Index(_) => true,
171         }
172     }
173 
lower_to_index<U>(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result<u32>174     fn lower_to_index<U>(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result<u32> {
175         match ty {
176             InterfaceType::Own(t) => {
177                 match T::typecheck(cx.resource_type(t)) {
178                     Some(t) if t == self.ty => {}
179                     _ => bail!("resource type mismatch"),
180                 }
181                 let rep = match self.state.get() {
182                     // If this is a borrow resource then this is a dynamic
183                     // error on behalf of the embedder.
184                     ResourceState::Borrow => {
185                         bail!("cannot lower a `borrow` resource into an `own`")
186                     }
187 
188                     // If this resource does not yet live in a table then we're
189                     // dynamically transferring ownership to wasm. Record that
190                     // it's no longer present and then pass through the
191                     // representation.
192                     ResourceState::NotInTable => {
193                         let prev = self.state.swap(ResourceState::Taken);
194                         assert_eq!(prev, ResourceState::NotInTable);
195                         self.rep
196                     }
197 
198                     // This resource has already been moved into wasm so this is
199                     // a dynamic error on behalf of the embedder.
200                     ResourceState::Taken => bail!("host resource already consumed"),
201 
202                     // If this resource lives in a host table then try to take
203                     // it out of the table, which may fail, and on success we
204                     // can move the rep into the guest table.
205                     ResourceState::Index(idx) => cx.host_resource_lift_own(idx)?,
206                 };
207                 cx.guest_resource_lower_own(t, rep)
208             }
209             InterfaceType::Borrow(t) => {
210                 match T::typecheck(cx.resource_type(t)) {
211                     Some(t) if t == self.ty => {}
212                     _ => bail!("resource type mismatch"),
213                 }
214                 let rep = match self.state.get() {
215                     // If this is already a borrowed resource, nothing else to
216                     // do and the rep is passed through.
217                     ResourceState::Borrow => self.rep,
218 
219                     // If this resource is already gone, that's a dynamic error
220                     // for the embedder.
221                     ResourceState::Taken => bail!("host resource already consumed"),
222 
223                     // If this resource is not currently in a table then it
224                     // needs to move into a table to participate in state
225                     // related to borrow tracking. Execute the
226                     // `host_resource_lower_own` operation here and update our
227                     // state.
228                     //
229                     // Afterwards this is the same as the `idx` case below.
230                     //
231                     // Note that flags/dtor are passed as `None` here since
232                     // `Resource<T>` doesn't offer destruction support.
233                     ResourceState::NotInTable => {
234                         let idx = cx.host_resource_lower_own(self.rep, None, None)?;
235                         let prev = self.state.swap(ResourceState::Index(idx));
236                         assert_eq!(prev, ResourceState::NotInTable);
237                         cx.host_resource_lift_borrow(idx)?
238                     }
239 
240                     // If this resource lives in a table then it needs to come
241                     // out of the table with borrow-tracking employed.
242                     ResourceState::Index(idx) => cx.host_resource_lift_borrow(idx)?,
243                 };
244                 cx.guest_resource_lower_borrow(t, rep)
245             }
246             _ => bad_type_info(),
247         }
248     }
249 
lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result<Self>250     fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result<Self> {
251         let (state, rep, ty) = match ty {
252             // Ownership is being transferred from a guest to the host, so move
253             // it from the guest table into a new `Resource`. Note that this
254             // isn't immediately inserted into the host table and that's left
255             // for the future if it's necessary to take a borrow from this owned
256             // resource.
257             InterfaceType::Own(t) => {
258                 let (rep, dtor, flags) = cx.guest_resource_lift_own(t, index)?;
259                 assert!(dtor.is_some());
260                 assert!(flags.is_none());
261                 (AtomicResourceState::NOT_IN_TABLE, rep, t)
262             }
263 
264             // The borrow here is lifted from the guest, but note the lack of
265             // `host_resource_lower_borrow` as it's intentional. Lowering
266             // a borrow has a special case in the canonical ABI where if the
267             // receiving module is the owner of the resource then it directly
268             // receives the `rep` and no other dynamic tracking is employed.
269             // This effectively mirrors that even though the canonical ABI
270             // isn't really all that applicable in host context here.
271             InterfaceType::Borrow(t) => {
272                 let rep = cx.guest_resource_lift_borrow(t, index)?;
273                 (AtomicResourceState::BORROW, rep, t)
274             }
275             _ => bad_type_info(),
276         };
277         let ty = T::typecheck(cx.resource_type(ty)).unwrap();
278         Ok(HostResource {
279             state,
280             rep,
281             ty,
282             _marker: marker::PhantomData,
283         })
284     }
285 
try_into_resource_any(self, mut store: impl AsContextMut) -> Result<ResourceAny>286     pub fn try_into_resource_any(self, mut store: impl AsContextMut) -> Result<ResourceAny> {
287         let HostResource {
288             rep,
289             state,
290             ty,
291             _marker: _,
292         } = self;
293         let store = store.as_context_mut();
294 
295         let mut tables = HostResourceTables::new_host(store.0);
296         let (idx, owned) = match state.get() {
297             ResourceState::Borrow => (tables.host_resource_lower_borrow(rep)?, false),
298             ResourceState::NotInTable => {
299                 let idx = tables.host_resource_lower_own(rep, None, None)?;
300                 (idx, true)
301             }
302             ResourceState::Taken => bail!("host resource already consumed"),
303             ResourceState::Index(idx) => (idx, true),
304         };
305         Ok(ResourceAny::new(idx, T::resource_type(ty), owned))
306     }
307 }
308 
309 unsafe impl<T, D> ComponentType for HostResource<T, D>
310 where
311     T: HostResourceType<D>,
312     D: PartialEq + Send + Sync + Copy + 'static,
313 {
314     const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4;
315 
316     type Lower = <u32 as ComponentType>::Lower;
317 
typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()>318     fn typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()> {
319         let resource = match ty {
320             InterfaceType::Own(t) | InterfaceType::Borrow(t) => *t,
321             other => bail!("expected `own` or `borrow`, found `{}`", desc(other)),
322         };
323         if T::typecheck(types.resource_type(resource)).is_none() {
324             bail!("resource type mismatch");
325         }
326 
327         Ok(())
328     }
329 }
330 
331 unsafe impl<T, D> Lower for HostResource<T, D>
332 where
333     T: HostResourceType<D>,
334     D: PartialEq + Send + Sync + Copy + 'static,
335 {
linear_lower_to_flat<U>( &self, cx: &mut LowerContext<'_, U>, ty: InterfaceType, dst: &mut MaybeUninit<Self::Lower>, ) -> Result<()>336     fn linear_lower_to_flat<U>(
337         &self,
338         cx: &mut LowerContext<'_, U>,
339         ty: InterfaceType,
340         dst: &mut MaybeUninit<Self::Lower>,
341     ) -> Result<()> {
342         self.lower_to_index(cx, ty)?
343             .linear_lower_to_flat(cx, InterfaceType::U32, dst)
344     }
345 
linear_lower_to_memory<U>( &self, cx: &mut LowerContext<'_, U>, ty: InterfaceType, offset: usize, ) -> Result<()>346     fn linear_lower_to_memory<U>(
347         &self,
348         cx: &mut LowerContext<'_, U>,
349         ty: InterfaceType,
350         offset: usize,
351     ) -> Result<()> {
352         self.lower_to_index(cx, ty)?
353             .linear_lower_to_memory(cx, InterfaceType::U32, offset)
354     }
355 }
356 
357 unsafe impl<T, D> Lift for HostResource<T, D>
358 where
359     T: HostResourceType<D>,
360     D: PartialEq + Send + Sync + Copy + 'static,
361 {
linear_lift_from_flat( cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower, ) -> Result<Self>362     fn linear_lift_from_flat(
363         cx: &mut LiftContext<'_>,
364         ty: InterfaceType,
365         src: &Self::Lower,
366     ) -> Result<Self> {
367         let index = u32::linear_lift_from_flat(cx, InterfaceType::U32, src)?;
368         HostResource::lift_from_index(cx, ty, index)
369     }
370 
linear_lift_from_memory( cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8], ) -> Result<Self>371     fn linear_lift_from_memory(
372         cx: &mut LiftContext<'_>,
373         ty: InterfaceType,
374         bytes: &[u8],
375     ) -> Result<Self> {
376         let index = u32::linear_lift_from_memory(cx, InterfaceType::U32, bytes)?;
377         HostResource::lift_from_index(cx, ty, index)
378     }
379 }
380 
381 impl<T, D> fmt::Debug for HostResource<T, D>
382 where
383     T: HostResourceType<D>,
384     D: PartialEq + Send + Sync + Copy + 'static,
385 {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result386     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387         let state = match self.state.get() {
388             ResourceState::Borrow => "borrow",
389             ResourceState::NotInTable => "own (not in table)",
390             ResourceState::Taken => "taken",
391             ResourceState::Index(_) => "own",
392         };
393         f.debug_struct("Resource")
394             .field("rep", &self.rep)
395             .field("state", &state)
396             .finish()
397     }
398 }
399