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 //! * `CallContexts` - store-local information about active calls and borrows
15 //!   and runtime state tracking that to ensure that everything is handled
16 //!   correctly.
17 //!
18 //! Individual operations are exposed through methods on `ResourceTables` for
19 //! lifting/lowering/etc. This does mean though that some other fiddly bits
20 //! about ABI details can be found in lifting/lowering throughout Wasmtime,
21 //! namely in the `Resource<T>` and `ResourceAny` types.
22 
23 use super::{HandleTable, RemovedResource};
24 use crate::prelude::*;
25 use core::error::Error;
26 use core::fmt;
27 use wasmtime_environ::PrimaryMap;
28 use wasmtime_environ::component::{
29     ComponentTypes, RuntimeComponentInstanceIndex, TypeResourceTableIndex,
30 };
31 
32 /// Contextual state necessary to perform resource-related operations.
33 ///
34 /// This state a bit odd since it has a few optional bits, but the idea is that
35 /// whenever this is constructed the bits required to perform operations are
36 /// always `Some`. For example:
37 ///
38 /// * During lifting and lowering both `guest_table` and `host_table` are
39 ///   `Some`.
40 /// * During wasm's own intrinsics only `guest_table` is `Some`.
41 /// * During embedder-invoked resource destruction calls only `host_table` is
42 ///   `Some`.
43 ///
44 /// This is all packaged up into one state though to make it easier to operate
45 /// on and to centralize handling of the state related to resources due to how
46 /// critical it is for correctness.
47 pub struct ResourceTables<'a> {
48     /// Runtime state for all resources defined in a component.
49     ///
50     /// This is required whenever a `TypeResourceTableIndex`, for example, is
51     /// provided as it's the lookup where that happens. Not present during
52     /// embedder-originating operations though such as
53     /// `ResourceAny::resource_drop` which won't consult this table as it's
54     /// only operating over the host table.
55     pub guest: Option<(
56         &'a mut PrimaryMap<RuntimeComponentInstanceIndex, HandleTable>,
57         &'a ComponentTypes,
58     )>,
59 
60     /// Runtime state for resources currently owned by the host.
61     ///
62     /// This is the single table used by the host stored within `Store<T>`. Host
63     /// resources will point into this and effectively have the same semantics
64     /// as-if they're in-component resources. The major distinction though is
65     /// that this is a heterogeneous table instead of only containing a single
66     /// type.
67     pub host_table: Option<&'a mut HandleTable>,
68 
69     /// Scope information about calls actively in use to track information such
70     /// as borrow counts.
71     pub calls: &'a mut CallContexts,
72 }
73 
74 /// Typed representation of a "rep" for a resource.
75 ///
76 /// All resources in the component model are stored in a single heterogeneous
77 /// table so this type is used to disambiguate what everything is. This is the
78 /// representation of a resource stored at-rest in memory.
79 #[derive(Debug)]
80 pub enum TypedResource {
81     /// A resource defined by the host.
82     ///
83     /// The meaning of the 32-bit integer here is up to the embedder, it
84     /// otherwise is not used within the runtime here.
85     Host(u32),
86 
87     /// A resource defined within a component.
88     Component {
89         /// This is an integer supplied by the component itself when this
90         /// resource was created. Typically this is a pointer into linear
91         /// memory for a component.
92         rep: u32,
93 
94         /// The type of this component resource.
95         ///
96         /// This is used, within the context of a component, to keep track of
97         /// what the type of `rep` is. This is then used when getting/removing
98         /// from the table to ensure that the guest does indeed have the right
99         /// permission to access this slot.
100         ty: TypeResourceTableIndex,
101     },
102 }
103 
104 impl TypedResource {
105     pub(super) fn rep(&self, access_ty: &TypedResourceIndex) -> Result<u32> {
106         match (self, access_ty) {
107             (Self::Host(rep), TypedResourceIndex::Host(_)) => Ok(*rep),
108             (Self::Host(_), expected) => bail!(ResourceTypeMismatch {
109                 expected: *expected,
110                 found: "host resource",
111             }),
112             (Self::Component { rep, ty }, TypedResourceIndex::Component { ty: expected, .. }) => {
113                 if ty == expected {
114                     Ok(*rep)
115                 } else {
116                     bail!(ResourceTypeMismatch {
117                         expected: *access_ty,
118                         found: "a different guest-defined resource",
119                     })
120                 }
121             }
122             (Self::Component { .. }, expected) => bail!(ResourceTypeMismatch {
123                 expected: *expected,
124                 found: "guest-defined resource",
125             }),
126         }
127     }
128 }
129 
130 /// An index used to access a resource.
131 ///
132 /// This reflects how index operations are always accompanied not only with a
133 /// 32-bit index but additionally with a type. For example a `resource.drop`
134 /// intrinsic in a guest takes only a 32-bit integer argument, but it
135 /// inherently is used to only drop one type of resource which is additionally
136 /// ascribed here.
137 #[derive(Debug, Copy, Clone)]
138 pub enum TypedResourceIndex {
139     /// A host resource at the given index is being accessed.
140     Host(u32),
141 
142     /// A guest resource is being accessed.
143     Component {
144         /// The index supplied by the guest being accessed.
145         index: u32,
146 
147         /// The fully-specific type of this resource.
148         ty: TypeResourceTableIndex,
149     },
150 }
151 
152 impl TypedResourceIndex {
153     pub(super) fn raw_index(&self) -> u32 {
154         match self {
155             Self::Host(index) | Self::Component { index, .. } => *index,
156         }
157     }
158 
159     fn desc(&self) -> &'static str {
160         match self {
161             Self::Host(_) => "host resource",
162             Self::Component { .. } => "guest-defined resource",
163         }
164     }
165 }
166 
167 /// State related to borrows and calls within a component.
168 ///
169 /// This is created once per `Store` and updated and modified throughout the
170 /// lifetime of the store. This primarily tracks borrow counts and what slots
171 /// should be updated when calls go out of scope.
172 #[derive(Default)]
173 pub struct CallContexts {
174     scopes: Vec<CallContext>,
175 }
176 
177 impl CallContexts {
178     pub fn push(&mut self, cx: CallContext) {
179         self.scopes.push(cx);
180     }
181 
182     pub fn pop(&mut self) -> Option<CallContext> {
183         self.scopes.pop()
184     }
185 }
186 
187 /// State related to borrows for a specific call.
188 #[derive(Default)]
189 pub struct CallContext {
190     lenders: Vec<TypedResourceIndex>,
191     borrow_count: u32,
192 }
193 
194 impl ResourceTables<'_> {
195     fn table_for_resource(&mut self, resource: &TypedResource) -> &mut HandleTable {
196         match resource {
197             TypedResource::Host(_) => self.host_table.as_mut().unwrap(),
198             TypedResource::Component { ty, .. } => {
199                 let (tables, types) = self.guest.as_mut().unwrap();
200                 &mut tables[types[*ty].instance]
201             }
202         }
203     }
204 
205     fn table_for_index(&mut self, index: &TypedResourceIndex) -> &mut HandleTable {
206         match index {
207             TypedResourceIndex::Host(_) => self.host_table.as_mut().unwrap(),
208             TypedResourceIndex::Component { ty, .. } => {
209                 let (tables, types) = self.guest.as_mut().unwrap();
210                 &mut tables[types[*ty].instance]
211             }
212         }
213     }
214 
215     /// Implementation of the `resource.new` canonical intrinsic.
216     ///
217     /// Note that this is the same as `resource_lower_own`.
218     pub fn resource_new(&mut self, resource: TypedResource) -> Result<u32> {
219         self.table_for_resource(&resource)
220             .resource_own_insert(resource)
221     }
222 
223     /// Implementation of the `resource.rep` canonical intrinsic.
224     ///
225     /// This one's one of the simpler ones: "just get the rep please"
226     pub fn resource_rep(&mut self, index: TypedResourceIndex) -> Result<u32> {
227         self.table_for_index(&index).resource_rep(index)
228     }
229 
230     /// Implementation of the `resource.drop` canonical intrinsic minus the
231     /// actual invocation of the destructor.
232     ///
233     /// This will drop the handle at the `index` specified, removing it from
234     /// the specified table. This operation can fail if:
235     ///
236     /// * The index is invalid.
237     /// * The index points to an `own` resource which has active borrows.
238     /// * The index's type is mismatched with the entry in the table's type.
239     ///
240     /// Otherwise this will return `Some(rep)` if the destructor for `rep` needs
241     /// to run. If `None` is returned then that means a `borrow` handle was
242     /// removed and no destructor is necessary.
243     pub fn resource_drop(&mut self, index: TypedResourceIndex) -> Result<Option<u32>> {
244         match self.table_for_index(&index).remove_resource(index)? {
245             RemovedResource::Own { rep } => Ok(Some(rep)),
246             RemovedResource::Borrow { scope } => {
247                 self.calls.scopes[scope].borrow_count -= 1;
248                 Ok(None)
249             }
250         }
251     }
252 
253     /// Inserts a new "own" handle into the specified table.
254     ///
255     /// This will insert the specified representation into the specified type
256     /// table.
257     ///
258     /// Note that this operation is infallible, and additionally that this is
259     /// the same as `resource_new` implementation-wise.
260     ///
261     /// This is an implementation of the canonical ABI `lower_own` function.
262     pub fn resource_lower_own(&mut self, resource: TypedResource) -> Result<u32> {
263         self.table_for_resource(&resource)
264             .resource_own_insert(resource)
265     }
266 
267     /// Attempts to remove an "own" handle from the specified table and its
268     /// index.
269     ///
270     /// This operation will fail if `index` is invalid, if it's a `borrow`
271     /// handle, if the own handle has currently been "lent" as a borrow, or if
272     /// `index` has a different type in the table than the index.
273     ///
274     /// This is an implementation of the canonical ABI `lift_own` function.
275     pub fn resource_lift_own(&mut self, index: TypedResourceIndex) -> Result<u32> {
276         match self.table_for_index(&index).remove_resource(index)? {
277             RemovedResource::Own { rep } => Ok(rep),
278             RemovedResource::Borrow { .. } => bail!("cannot lift own resource from a borrow"),
279         }
280     }
281 
282     /// Extracts the underlying resource representation by lifting a "borrow"
283     /// from the tables.
284     ///
285     /// This primarily employs dynamic tracking when a borrow is created from an
286     /// "own" handle to ensure that the "own" handle isn't dropped while the
287     /// borrow is active and additionally that when the current call scope
288     /// returns the lend operation is undone.
289     ///
290     /// This is an implementation of the canonical ABI `lift_borrow` function.
291     pub fn resource_lift_borrow(&mut self, index: TypedResourceIndex) -> Result<u32> {
292         let (rep, is_own) = self.table_for_index(&index).resource_lend(index)?;
293         if is_own {
294             let scope = self.calls.scopes.last_mut().unwrap();
295             scope.lenders.push(index);
296         }
297         Ok(rep)
298     }
299 
300     /// Records a new `borrow` resource with the given representation within the
301     /// current call scope.
302     ///
303     /// This requires that a call scope is active. Additionally the number of
304     /// active borrows in the latest scope will be increased and must be
305     /// decreased through a future use of `resource_drop` before the current
306     /// call scope exits.
307     ///
308     /// This some of the implementation of the canonical ABI `lower_borrow`
309     /// function. The other half of this implementation is located on
310     /// `VMComponentContext` which handles the special case of avoiding borrow
311     /// tracking entirely.
312     pub fn resource_lower_borrow(&mut self, resource: TypedResource) -> Result<u32> {
313         let scope = self.calls.scopes.len() - 1;
314         let borrow_count = &mut self.calls.scopes.last_mut().unwrap().borrow_count;
315         *borrow_count = borrow_count.checked_add(1).unwrap();
316         self.table_for_resource(&resource)
317             .resource_borrow_insert(resource, scope)
318     }
319 
320     /// Enters a new calling context, starting a fresh count of borrows and
321     /// such.
322     #[inline]
323     pub fn enter_call(&mut self) {
324         self.calls.scopes.push(CallContext::default());
325     }
326 
327     /// Exits the previously pushed calling context.
328     ///
329     /// This requires all information to be available within this
330     /// `ResourceTables` and is only called during lowering/lifting operations
331     /// at this time.
332     #[inline]
333     pub fn exit_call(&mut self) -> Result<()> {
334         let cx = self.calls.scopes.pop().unwrap();
335         if cx.borrow_count > 0 {
336             bail!("borrow handles still remain at the end of the call")
337         }
338         for lender in cx.lenders.iter() {
339             // Note the panics here which should never get triggered in theory
340             // due to the dynamic tracking of borrows and such employed for
341             // resources.
342             self.table_for_index(lender)
343                 .resource_undo_lend(*lender)
344                 .unwrap();
345         }
346         Ok(())
347     }
348 }
349 
350 #[derive(Debug)]
351 struct ResourceTypeMismatch {
352     expected: TypedResourceIndex,
353     found: &'static str,
354 }
355 
356 impl fmt::Display for ResourceTypeMismatch {
357     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358         write!(
359             f,
360             "handle index {} used with the wrong type, \
361              expected {} but found {}",
362             self.expected.raw_index(),
363             self.expected.desc(),
364             self.found,
365         )
366     }
367 }
368 
369 impl Error for ResourceTypeMismatch {}
370