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