1 //! Implementation of the canonical-ABI related intrinsics for resources in the
2 //! component model.
3 //!
4 //! This module contains all the relevant gory details of the
5 //! component model related to lifting and lowering resources. For example
6 //! intrinsics like `resource.new` will bottom out in calling this file, and
7 //! this is where resource tables are actually defined and modified.
8 //!
9 //! The main types in this file are:
10 //!
11 //! * `ResourceTables` - the "here's everything" context which is required to
12 //!   perform canonical ABI operations.
13 //!
14 //! * `ResourceTable` - an individual instance of a table of resources,
15 //!   basically "just a slab" though.
16 //!
17 //! * `CallContexts` - store-local information about active calls and borrows
18 //!   and runtime state tracking that to ensure that everything is handled
19 //!   correctly.
20 //!
21 //! Individual operations are exposed through methods on `ResourceTables` for
22 //! lifting/lowering/etc. This does mean though that some other fiddly bits
23 //! about ABI details can be found in lifting/lowering throughout Wasmtime,
24 //! namely in the `Resource<T>` and `ResourceAny` types.
25 
26 use crate::prelude::*;
27 use core::error::Error;
28 use core::fmt;
29 use core::mem;
30 use wasmtime_environ::PrimaryMap;
31 use wasmtime_environ::component::{
32     ComponentTypes, RuntimeComponentInstanceIndex, TypeResourceTableIndex,
33 };
34 
35 /// The maximum handle value is specified in
36 /// <https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md>
37 /// currently and keeps the upper bit free for use in the component.
38 const MAX_RESOURCE_HANDLE: u32 = 1 << 30;
39 
40 /// Contextual state necessary to perform resource-related operations.
41 ///
42 /// This state a bit odd since it has a few optional bits, but the idea is that
43 /// whenever this is constructed the bits required to perform operations are
44 /// always `Some`. For example:
45 ///
46 /// * During lifting and lowering both `guest_table` and `host_table` are
47 ///   `Some`.
48 /// * During wasm's own intrinsics only `guest_table` is `Some`.
49 /// * During embedder-invoked resource destruction calls only `host_table` is
50 ///   `Some`.
51 ///
52 /// This is all packaged up into one state though to make it easier to operate
53 /// on and to centralize handling of the state related to resources due to how
54 /// critical it is for correctness.
55 pub struct ResourceTables<'a> {
56     /// Runtime state for all resources defined in a component.
57     ///
58     /// This is required whenever a `TypeResourceTableIndex`, for example, is
59     /// provided as it's the lookup where that happens. Not present during
60     /// embedder-originating operations though such as
61     /// `ResourceAny::resource_drop` which won't consult this table as it's
62     /// only operating over the host table.
63     pub guest: Option<(
64         &'a mut PrimaryMap<RuntimeComponentInstanceIndex, ResourceTable>,
65         &'a ComponentTypes,
66     )>,
67 
68     /// Runtime state for resources currently owned by the host.
69     ///
70     /// This is the single table used by the host stored within `Store<T>`. Host
71     /// resources will point into this and effectively have the same semantics
72     /// as-if they're in-component resources. The major distinction though is
73     /// that this is a heterogeneous table instead of only containing a single
74     /// type.
75     pub host_table: Option<&'a mut ResourceTable>,
76 
77     /// Scope information about calls actively in use to track information such
78     /// as borrow counts.
79     pub calls: &'a mut CallContexts,
80 }
81 
82 /// An individual slab of resources used for a single table within a component.
83 /// Not much fancier than a general slab data structure.
84 #[derive(Default)]
85 pub struct ResourceTable {
86     /// Next slot to allocate, or `self.slots.len()` if they're all full.
87     next: u32,
88     /// Runtime state of all slots.
89     slots: Vec<Slot>,
90 }
91 
92 /// Typed representation of a "rep" for a resource.
93 ///
94 /// All resources in the component model are stored in a single heterogeneous
95 /// table so this type is used to disambiguate what everything is. This is the
96 /// representation of a resource stored at-rest in memory.
97 #[derive(Debug)]
98 pub enum TypedResource {
99     /// A resource defined by the host.
100     ///
101     /// The meaning of the 32-bit integer here is up to the embedder, it
102     /// otherwise is not used within the runtime here.
103     Host(u32),
104 
105     /// A resource defined within a component.
106     Component {
107         /// This is an integer supplied by the component itself when this
108         /// resource was created. Typically this is a pointer into linear
109         /// memory for a component.
110         rep: u32,
111 
112         /// The type of this component resource.
113         ///
114         /// This is used, within the context of a component, to keep track of
115         /// what the type of `rep` is. This is then used when getting/removing
116         /// from the table to ensure that the guest does indeed have the right
117         /// permission to access this slot.
118         ty: TypeResourceTableIndex,
119     },
120 }
121 
122 impl TypedResource {
123     fn rep(&self, access_ty: &TypedResourceIndex) -> Result<u32> {
124         match (self, access_ty) {
125             (Self::Host(rep), TypedResourceIndex::Host(_)) => Ok(*rep),
126             (Self::Host(_), expected) => bail!(ResourceTypeMismatch {
127                 expected: *expected,
128                 found: "host resource",
129             }),
130             (Self::Component { rep, ty }, TypedResourceIndex::Component { ty: expected, .. }) => {
131                 if ty == expected {
132                     Ok(*rep)
133                 } else {
134                     bail!(ResourceTypeMismatch {
135                         expected: *access_ty,
136                         found: "a different guest-defined resource",
137                     })
138                 }
139             }
140             (Self::Component { .. }, expected) => bail!(ResourceTypeMismatch {
141                 expected: *expected,
142                 found: "guest-defined resource",
143             }),
144         }
145     }
146 }
147 
148 /// An index used to access a resource.
149 ///
150 /// This reflects how index operations are always accompanied not only with a
151 /// 32-bit index but additionally with a type. For example a `resource.drop`
152 /// intrinsic in a guest takes only a 32-bit integer argument, but it
153 /// inherently is used to only drop one type of resource which is additionally
154 /// ascribed here.
155 #[derive(Debug, Copy, Clone)]
156 pub enum TypedResourceIndex {
157     /// A host resource at the given index is being accessed.
158     Host(u32),
159 
160     /// A guest resource is being accessed.
161     Component {
162         /// The index supplied by the guest being accessed.
163         index: u32,
164 
165         /// The fully-specific type of this resource.
166         ty: TypeResourceTableIndex,
167     },
168 }
169 
170 impl TypedResourceIndex {
171     fn raw_index(&self) -> u32 {
172         match self {
173             Self::Host(index) | Self::Component { index, .. } => *index,
174         }
175     }
176 
177     fn desc(&self) -> &'static str {
178         match self {
179             Self::Host(_) => "host resource",
180             Self::Component { .. } => "guest-defined resource",
181         }
182     }
183 }
184 
185 enum Slot {
186     /// This slot is free and points to the next free slot, forming a linked
187     /// list of free slots.
188     Free { next: u32 },
189 
190     /// This slot contains an owned resource with the listed representation.
191     ///
192     /// The `lend_count` tracks how many times this has been lent out as a
193     /// `borrow` and if nonzero this can't be removed.
194     Own {
195         resource: TypedResource,
196         lend_count: u32,
197     },
198 
199     /// This slot contains a `borrow` resource that's connected to the `scope`
200     /// provided. The `rep` is listed and dropping this borrow will decrement
201     /// the borrow count of the `scope`.
202     Borrow {
203         resource: TypedResource,
204         scope: usize,
205     },
206 }
207 
208 /// State related to borrows and calls within a component.
209 ///
210 /// This is created once per `Store` and updated and modified throughout the
211 /// lifetime of the store. This primarily tracks borrow counts and what slots
212 /// should be updated when calls go out of scope.
213 #[derive(Default)]
214 pub struct CallContexts {
215     scopes: Vec<CallContext>,
216 }
217 
218 #[derive(Default)]
219 struct CallContext {
220     lenders: Vec<TypedResourceIndex>,
221     borrow_count: u32,
222 }
223 
224 impl ResourceTables<'_> {
225     fn table_for_resource(&mut self, resource: &TypedResource) -> &mut ResourceTable {
226         match resource {
227             TypedResource::Host(_) => self.host_table.as_mut().unwrap(),
228             TypedResource::Component { ty, .. } => {
229                 let (tables, types) = self.guest.as_mut().unwrap();
230                 &mut tables[types[*ty].instance]
231             }
232         }
233     }
234 
235     fn table_for_index(&mut self, index: &TypedResourceIndex) -> &mut ResourceTable {
236         match index {
237             TypedResourceIndex::Host(_) => self.host_table.as_mut().unwrap(),
238             TypedResourceIndex::Component { ty, .. } => {
239                 let (tables, types) = self.guest.as_mut().unwrap();
240                 &mut tables[types[*ty].instance]
241             }
242         }
243     }
244 
245     /// Implementation of the `resource.new` canonical intrinsic.
246     ///
247     /// Note that this is the same as `resource_lower_own`.
248     pub fn resource_new(&mut self, resource: TypedResource) -> Result<u32> {
249         self.table_for_resource(&resource).insert(Slot::Own {
250             resource,
251             lend_count: 0,
252         })
253     }
254 
255     /// Implementation of the `resource.rep` canonical intrinsic.
256     ///
257     /// This one's one of the simpler ones: "just get the rep please"
258     pub fn resource_rep(&mut self, index: TypedResourceIndex) -> Result<u32> {
259         self.table_for_index(&index).rep(index)
260     }
261 
262     /// Implementation of the `resource.drop` canonical intrinsic minus the
263     /// actual invocation of the destructor.
264     ///
265     /// This will drop the handle at the `index` specified, removing it from
266     /// the specified table. This operation can fail if:
267     ///
268     /// * The index is invalid.
269     /// * The index points to an `own` resource which has active borrows.
270     /// * The index's type is mismatched with the entry in the table's type.
271     ///
272     /// Otherwise this will return `Some(rep)` if the destructor for `rep` needs
273     /// to run. If `None` is returned then that means a `borrow` handle was
274     /// removed and no destructor is necessary.
275     pub fn resource_drop(&mut self, index: TypedResourceIndex) -> Result<Option<u32>> {
276         match self.table_for_index(&index).remove(index)? {
277             Slot::Own {
278                 resource,
279                 lend_count: 0,
280             } => resource.rep(&index).map(Some),
281             Slot::Own { .. } => bail!("cannot remove owned resource while borrowed"),
282             Slot::Borrow {
283                 scope, resource, ..
284             } => {
285                 // Validate that this borrow has the correct type to ensure a
286                 // trap is returned if this is a mis-typed `resource.drop`.
287                 resource.rep(&index)?;
288                 self.calls.scopes[scope].borrow_count -= 1;
289                 Ok(None)
290             }
291             Slot::Free { .. } => unreachable!(),
292         }
293     }
294 
295     /// Inserts a new "own" handle into the specified table.
296     ///
297     /// This will insert the specified representation into the specified type
298     /// table.
299     ///
300     /// Note that this operation is infallible, and additionally that this is
301     /// the same as `resource_new` implementation-wise.
302     ///
303     /// This is an implementation of the canonical ABI `lower_own` function.
304     pub fn resource_lower_own(&mut self, resource: TypedResource) -> Result<u32> {
305         self.table_for_resource(&resource).insert(Slot::Own {
306             resource,
307             lend_count: 0,
308         })
309     }
310 
311     /// Attempts to remove an "own" handle from the specified table and its
312     /// index.
313     ///
314     /// This operation will fail if `index` is invalid, if it's a `borrow`
315     /// handle, if the own handle has currently been "lent" as a borrow, or if
316     /// `index` has a different type in the table than the index.
317     ///
318     /// This is an implementation of the canonical ABI `lift_own` function.
319     pub fn resource_lift_own(&mut self, index: TypedResourceIndex) -> Result<u32> {
320         match self.table_for_index(&index).remove(index)? {
321             Slot::Own {
322                 resource,
323                 lend_count: 0,
324             } => resource.rep(&index),
325             Slot::Own { .. } => bail!("cannot remove owned resource while borrowed"),
326             Slot::Borrow { .. } => bail!("cannot lift own resource from a borrow"),
327             Slot::Free { .. } => unreachable!(),
328         }
329     }
330 
331     /// Extracts the underlying resource representation by lifting a "borrow"
332     /// from the tables.
333     ///
334     /// This primarily employs dynamic tracking when a borrow is created from an
335     /// "own" handle to ensure that the "own" handle isn't dropped while the
336     /// borrow is active and additionally that when the current call scope
337     /// returns the lend operation is undone.
338     ///
339     /// This is an implementation of the canonical ABI `lift_borrow` function.
340     pub fn resource_lift_borrow(&mut self, index: TypedResourceIndex) -> Result<u32> {
341         match self.table_for_index(&index).get_mut(index)? {
342             Slot::Own {
343                 resource,
344                 lend_count,
345             } => {
346                 let rep = resource.rep(&index)?;
347                 // The decrement to this count happens in `exit_call`.
348                 *lend_count = lend_count.checked_add(1).unwrap();
349                 let scope = self.calls.scopes.last_mut().unwrap();
350                 scope.lenders.push(index);
351                 Ok(rep)
352             }
353             Slot::Borrow { resource, .. } => resource.rep(&index),
354             Slot::Free { .. } => unreachable!(),
355         }
356     }
357 
358     /// Records a new `borrow` resource with the given representation within the
359     /// current call scope.
360     ///
361     /// This requires that a call scope is active. Additionally the number of
362     /// active borrows in the latest scope will be increased and must be
363     /// decreased through a future use of `resource_drop` before the current
364     /// call scope exits.
365     ///
366     /// This some of the implementation of the canonical ABI `lower_borrow`
367     /// function. The other half of this implementation is located on
368     /// `VMComponentContext` which handles the special case of avoiding borrow
369     /// tracking entirely.
370     pub fn resource_lower_borrow(&mut self, resource: TypedResource) -> Result<u32> {
371         let scope = self.calls.scopes.len() - 1;
372         let borrow_count = &mut self.calls.scopes.last_mut().unwrap().borrow_count;
373         *borrow_count = borrow_count.checked_add(1).unwrap();
374         self.table_for_resource(&resource)
375             .insert(Slot::Borrow { resource, scope })
376     }
377 
378     /// Enters a new calling context, starting a fresh count of borrows and
379     /// such.
380     #[inline]
381     pub fn enter_call(&mut self) {
382         self.calls.scopes.push(CallContext::default());
383     }
384 
385     /// Exits the previously pushed calling context.
386     ///
387     /// This requires all information to be available within this
388     /// `ResourceTables` and is only called during lowering/lifting operations
389     /// at this time.
390     #[inline]
391     pub fn exit_call(&mut self) -> Result<()> {
392         let cx = self.calls.scopes.pop().unwrap();
393         if cx.borrow_count > 0 {
394             bail!("borrow handles still remain at the end of the call")
395         }
396         for lender in cx.lenders.iter() {
397             // Note the panics here which should never get triggered in theory
398             // due to the dynamic tracking of borrows and such employed for
399             // resources.
400             match self.table_for_index(lender).get_mut(*lender).unwrap() {
401                 Slot::Own { lend_count, .. } => {
402                     *lend_count -= 1;
403                 }
404                 _ => unreachable!(),
405             }
406         }
407         Ok(())
408     }
409 }
410 
411 impl ResourceTable {
412     fn insert(&mut self, new: Slot) -> Result<u32> {
413         let next = self.next as usize;
414         if next == self.slots.len() {
415             self.slots.push(Slot::Free {
416                 next: self.next.checked_add(1).unwrap(),
417             });
418         }
419         let ret = self.next;
420         self.next = match mem::replace(&mut self.slots[next], new) {
421             Slot::Free { next } => next,
422             _ => unreachable!(),
423         };
424 
425         // The component model reserves index 0 as never allocatable so add one
426         // to the table index to start the numbering at 1 instead. Also note
427         // that the component model places an upper-limit per-table on the
428         // maximum allowed index.
429         let ret = ret + 1;
430         if ret >= MAX_RESOURCE_HANDLE {
431             bail!("cannot allocate another handle: index overflow");
432         }
433         Ok(ret)
434     }
435 
436     fn handle_index_to_table_index(&self, idx: u32) -> Option<usize> {
437         // NB: `idx` is decremented by one to account for the `+1` above during
438         // allocation.
439         let idx = idx.checked_sub(1)?;
440         usize::try_from(idx).ok()
441     }
442 
443     fn rep(&self, idx: TypedResourceIndex) -> Result<u32> {
444         let slot = self
445             .handle_index_to_table_index(idx.raw_index())
446             .and_then(|i| self.slots.get(i));
447         match slot {
448             None | Some(Slot::Free { .. }) => bail!(UnknownHandleIndex(idx)),
449             Some(Slot::Own { resource, .. } | Slot::Borrow { resource, .. }) => resource.rep(&idx),
450         }
451     }
452 
453     fn get_mut(&mut self, idx: TypedResourceIndex) -> Result<&mut Slot> {
454         let slot = self
455             .handle_index_to_table_index(idx.raw_index())
456             .and_then(|i| self.slots.get_mut(i));
457         match slot {
458             None | Some(Slot::Free { .. }) => bail!(UnknownHandleIndex(idx)),
459             Some(other) => Ok(other),
460         }
461     }
462 
463     fn remove(&mut self, idx: TypedResourceIndex) -> Result<Slot> {
464         let to_fill = Slot::Free { next: self.next };
465         let ret = mem::replace(self.get_mut(idx)?, to_fill);
466         self.next = idx.raw_index() - 1;
467         Ok(ret)
468     }
469 }
470 
471 #[derive(Debug)]
472 struct UnknownHandleIndex(TypedResourceIndex);
473 
474 impl fmt::Display for UnknownHandleIndex {
475     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
476         write!(f, "unknown handle index {}", self.0.raw_index())
477     }
478 }
479 
480 impl Error for UnknownHandleIndex {}
481 
482 #[derive(Debug)]
483 struct ResourceTypeMismatch {
484     expected: TypedResourceIndex,
485     found: &'static str,
486 }
487 
488 impl fmt::Display for ResourceTypeMismatch {
489     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
490         write!(
491             f,
492             "handle index {} used with the wrong type, \
493              expected {} but found {}",
494             self.expected.raw_index(),
495             self.expected.desc(),
496             self.found,
497         )
498     }
499 }
500 
501 impl Error for ResourceTypeMismatch {}
502