1 //! Infrastructure for managing "host tables" of resources in the component
2 //! model.
3 //!
4 //! This module is mostly an implementation detail of `ResourceAny` where it
5 //! tracks the ability for the host to have a handle to any resource, be it
6 //! either guest-defined or host-defined. The `HostResourceData` type is
7 //! stored inside of a `Store<T>` and `HostResourceTables` provides temporary
8 //! access to this information.
9 //!
10 //! This provides operations in terms of "lift" and "lower" terminology where
11 //! the idea is that a resource is "lowered" to the host meaning that metadata
12 //! about the resource is inserted into `HostResourceData` through
13 //! `HostResourceTables`. This produces a `HostResourceIndex` which can
14 //! then be used to access it later on. The `HostResourceIndex` can later be
15 //! used to "lift" the resource from the table back into its constituent parts
16 //! or remove it from the table.
17 //!
18 //! This tracks borrow state where you can, for example, lift a borrow from a
19 //! `HostResourceIndex` that represents an "own". This will activate
20 //! borrow-tracking infrastructure to ensure that the borrow is deleted before
21 //! the own is removed or otherwise taken out.
22 
23 use crate::prelude::*;
24 use crate::runtime::component::RuntimeInstance;
25 use crate::runtime::vm::component::{ResourceTables, TypedResource, TypedResourceIndex};
26 use crate::runtime::vm::{SendSyncPtr, VMFuncRef};
27 use crate::store::StoreOpaque;
28 use core::ptr::NonNull;
29 use wasmtime_environ::component::TypeResourceTableIndex;
30 
31 /// Metadata tracking the state of resources within a `Store`.
32 ///
33 /// This is a borrowed structure created from a `Store` piecemeal from below.
34 /// The `ResourceTables` type holds most of the raw information and this
35 /// structure tacks on a reference to `HostResourceData` to track generation
36 /// numbers of host indices.
37 pub struct HostResourceTables<'a> {
38     tables: ResourceTables<'a>,
39     host_resource_data: &'a mut HostResourceData,
40 }
41 
42 /// Metadata for host-owned resources owned within a `Store`.
43 ///
44 /// This metadata is used to prevent the ABA problem with indices handed out as
45 /// part of `Resource` and `ResourceAny`. Those structures are `Copy` meaning
46 /// that it's easy to reuse them, possibly accidentally. To prevent issues in
47 /// the host Wasmtime attaches both an index (within `ResourceTables`) as well
48 /// as a 32-bit generation counter onto each `HostResourceIndex` which the host
49 /// actually holds in `Resource` and `ResourceAny`.
50 ///
51 /// This structure holds a list which is a parallel list to the "list of reps"
52 /// that's stored within `ResourceTables` elsewhere in the `Store`. This
53 /// parallel list holds the last known generation of each element in the table.
54 /// The generation is then compared on access to make sure it's the same.
55 ///
56 /// Whenever a slot in the table is allocated the `cur_generation` field is
57 /// pushed at the corresponding index of `generation_of_table_slot`. Whenever
58 /// a field is accessed the current value of `generation_of_table_slot` is
59 /// checked against the generation of the index. Whenever a slot is deallocated
60 /// the generation is incremented. Put together this means that any access of a
61 /// deallocated slot should deterministically provide an error.
62 #[derive(Default)]
63 pub struct HostResourceData {
64     cur_generation: u32,
65     table_slot_metadata: Vec<TableSlot>,
66 }
67 
68 #[derive(Copy, Clone)]
69 pub struct TableSlot {
70     generation: u32,
71     pub(super) instance: Option<RuntimeInstance>,
72     pub(super) dtor: Option<SendSyncPtr<VMFuncRef>>,
73 }
74 
75 /// Host representation of an index into a table slot.
76 ///
77 /// This is morally (u32, u32) but is encoded as a 64-bit integer. The low
78 /// 32-bits are the table index and the upper 32-bits are the generation
79 /// counter.
80 #[derive(PartialEq, Eq, Debug, Copy, Clone)]
81 #[repr(transparent)]
82 pub struct HostResourceIndex(u64);
83 
84 impl HostResourceIndex {
85     pub(super) fn new(idx: u32, generation: u32) -> HostResourceIndex {
86         HostResourceIndex(u64::from(idx) | (u64::from(generation) << 32))
87     }
88 
89     pub(super) const fn index(&self) -> u32 {
90         (self.0 & 0xffffffff) as u32
91     }
92 
93     pub(super) const fn generation(&self) -> u32 {
94         (self.0 >> 32) as u32
95     }
96 }
97 
98 impl<'a> HostResourceTables<'a> {
99     pub fn new_host(store: &'a mut StoreOpaque) -> HostResourceTables<'a> {
100         let (tables, data) = store.component_resource_tables_and_host_resource_data(None);
101         HostResourceTables::from_parts(tables, data)
102     }
103 
104     pub fn from_parts(
105         tables: ResourceTables<'a>,
106         host_resource_data: &'a mut HostResourceData,
107     ) -> Self {
108         HostResourceTables {
109             tables,
110             host_resource_data,
111         }
112     }
113 
114     /// Lifts an `own` resource that resides in the host's tables at the `idx`
115     /// specified into its `rep`.
116     ///
117     /// # Errors
118     ///
119     /// Returns an error if `idx` doesn't point to a valid owned resource, or
120     /// if `idx` can't be lifted as an `own` (e.g. it has active borrows).
121     pub fn host_resource_lift_own(&mut self, idx: HostResourceIndex) -> Result<u32> {
122         let (idx, _) = self.validate_host_index(idx, true)?;
123         self.tables.resource_lift_own(TypedResourceIndex::Host(idx))
124     }
125 
126     /// See [`HostResourceTables::host_resource_lift_own`].
127     pub fn host_resource_lift_borrow(&mut self, idx: HostResourceIndex) -> Result<u32> {
128         let (idx, _) = self.validate_host_index(idx, false)?;
129         self.tables
130             .resource_lift_borrow(TypedResourceIndex::Host(idx))
131     }
132 
133     /// Lowers an `own` resource to be owned by the host.
134     ///
135     /// This returns a new index into the host's set of resource tables which
136     /// will point to the `rep` specified. The returned index is suitable for
137     /// conversion into either [`Resource`] or [`ResourceAny`].
138     ///
139     /// The `dtor` and instance `instance` are specified as well to know what
140     /// destructor to run when this resource is destroyed.
141     pub fn host_resource_lower_own(
142         &mut self,
143         rep: u32,
144         dtor: Option<NonNull<VMFuncRef>>,
145         instance: Option<RuntimeInstance>,
146     ) -> Result<HostResourceIndex> {
147         let idx = self.tables.resource_lower_own(TypedResource::Host(rep))?;
148         Ok(self.new_host_index(idx, dtor, instance))
149     }
150 
151     /// See [`HostResourceTables::host_resource_lower_own`].
152     pub fn host_resource_lower_borrow(&mut self, rep: u32) -> Result<HostResourceIndex> {
153         let idx = self
154             .tables
155             .resource_lower_borrow(TypedResource::Host(rep))?;
156         Ok(self.new_host_index(idx, None, None))
157     }
158 
159     /// Validates that `idx` is still valid for the host tables, notably
160     /// ensuring that the generation listed in `idx` is the same as the
161     /// last recorded generation of the slot itself.
162     ///
163     /// The `is_removal` option indicates whether or not this table access will
164     /// end up removing the element from the host table. In such a situation the
165     /// current generation number is incremented.
166     fn validate_host_index(
167         &mut self,
168         idx: HostResourceIndex,
169         is_removal: bool,
170     ) -> Result<(u32, Option<TableSlot>)> {
171         let actual = usize::try_from(idx.index())
172             .ok()
173             .and_then(|i| self.host_resource_data.table_slot_metadata.get(i).copied());
174 
175         // If `idx` is out-of-bounds then skip returning an error. In such a
176         // situation the operation that this is guarding will return a more
177         // precise error, such as a lift operation.
178         if let Some(actual) = actual {
179             if actual.generation != idx.generation() {
180                 bail!("host-owned resource is being used with the wrong type");
181             }
182         }
183 
184         // Bump the current generation of this is a removal to ensure any
185         // future item placed in this slot can't be pointed to by the `idx`
186         // provided above.
187         if is_removal {
188             self.host_resource_data.cur_generation += 1;
189         }
190 
191         Ok((idx.index(), actual))
192     }
193 
194     /// Creates a new `HostResourceIndex` which will point to the raw table
195     /// slot provided by `idx`.
196     ///
197     /// This will register metadata necessary to track the current generation
198     /// in the returned `HostResourceIndex` as well.
199     fn new_host_index(
200         &mut self,
201         idx: u32,
202         dtor: Option<NonNull<VMFuncRef>>,
203         instance: Option<RuntimeInstance>,
204     ) -> HostResourceIndex {
205         let list = &mut self.host_resource_data.table_slot_metadata;
206         let info = TableSlot {
207             generation: self.host_resource_data.cur_generation,
208             instance,
209             dtor: dtor.map(SendSyncPtr::new),
210         };
211         match list.get_mut(idx as usize) {
212             Some(slot) => *slot = info,
213             None => {
214                 // Resource handles start at 1, not zero, so push two elements
215                 // for the first resource handle.
216                 if list.is_empty() {
217                     assert_eq!(idx, 1);
218                     list.push(TableSlot {
219                         generation: 0,
220                         instance: None,
221                         dtor: None,
222                     });
223                 }
224                 assert_eq!(idx as usize, list.len());
225                 list.push(info);
226             }
227         }
228 
229         HostResourceIndex::new(idx, info.generation)
230     }
231 
232     /// Drops a host-owned resource from host tables.
233     ///
234     /// This method will attempt to interpret `idx` as pointing to either a
235     /// `borrow` or `own` resource with the `expected` type specified. This
236     /// method will then return the underlying `rep` if it points to an `own`
237     /// resource which can then be further processed for destruction.
238     ///
239     /// # Errors
240     ///
241     /// Returns an error if `idx` doesn't point to a valid resource, points to
242     /// an `own` with active borrows, or if it doesn't have the type `expected`
243     /// in the host tables.
244     pub(super) fn host_resource_drop(
245         &mut self,
246         idx: HostResourceIndex,
247     ) -> Result<Option<(u32, TableSlot)>> {
248         let (idx, slot) = self.validate_host_index(idx, true)?;
249         match self.tables.resource_drop(TypedResourceIndex::Host(idx))? {
250             Some(rep) => Ok(Some((rep, slot.unwrap()))),
251             None => Ok(None),
252         }
253     }
254 
255     /// Lowers an `own` resource into the guest, converting the `rep` specified
256     /// into a guest-local index.
257     ///
258     /// The `ty` provided is which table to put this into.
259     pub fn guest_resource_lower_own(
260         &mut self,
261         rep: u32,
262         ty: TypeResourceTableIndex,
263     ) -> Result<u32> {
264         self.tables
265             .resource_lower_own(TypedResource::Component { ty, rep })
266     }
267 
268     /// Lowers a `borrow` resource into the guest, converting the `rep`
269     /// specified into a guest-local index.
270     ///
271     /// The `ty` provided is which table to put this into.
272     ///
273     /// Note that this cannot be used in isolation because lowering a borrow
274     /// into a guest has a special case where `rep` is returned directly if `ty`
275     /// belongs to the component being lowered into. That property must be
276     /// handled by the caller of this function.
277     pub fn guest_resource_lower_borrow(
278         &mut self,
279         rep: u32,
280         ty: TypeResourceTableIndex,
281     ) -> Result<u32> {
282         self.tables
283             .resource_lower_borrow(TypedResource::Component { ty, rep })
284     }
285 
286     /// Lifts an `own` resource from the `idx` specified from the table `ty`.
287     ///
288     /// This will lookup the appropriate table in the guest and return the `rep`
289     /// corresponding to `idx` if it's valid.
290     pub fn guest_resource_lift_own(
291         &mut self,
292         index: u32,
293         ty: TypeResourceTableIndex,
294     ) -> Result<u32> {
295         self.tables
296             .resource_lift_own(TypedResourceIndex::Component { ty, index })
297     }
298 
299     /// Lifts a `borrow` resource from the `idx` specified from the table `ty`.
300     ///
301     /// This will lookup the appropriate table in the guest and return the `rep`
302     /// corresponding to `idx` if it's valid.
303     pub fn guest_resource_lift_borrow(
304         &mut self,
305         index: u32,
306         ty: TypeResourceTableIndex,
307     ) -> Result<u32> {
308         self.tables
309             .resource_lift_borrow(TypedResourceIndex::Component { ty, index })
310     }
311 
312     /// Completes a call into the component instance, validating that it's ok to
313     /// complete by ensuring the are no remaining active borrows.
314     #[inline]
315     pub fn validate_scope_exit(&mut self) -> Result<()> {
316         self.tables.validate_scope_exit()
317     }
318 }
319