wasmtime/runtime/vm/component/resources.rs
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
26use crate::prelude::*;
27use core::mem;
28use wasmtime_environ::component::TypeResourceTableIndex;
29use wasmtime_environ::PrimaryMap;
30
31/// The maximum handle value is specified in
32/// <https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md>
33/// currently and keeps the upper bit free for use in the component.
34const MAX_RESOURCE_HANDLE: u32 = 1 << 30;
35
36/// Contextual state necessary to perform resource-related operations.
37///
38/// This state a bit odd since it has a few optional bits, but the idea is that
39/// whenever this is constructed the bits required to perform operations are
40/// always `Some`. For example:
41///
42/// * During lifting and lowering both `tables` and `host_table` are `Some`.
43/// * During wasm's own intrinsics only `tables` is `Some`.
44/// * During embedder-invoked resource destruction calls only `host_table` is
45/// `Some`.
46///
47/// This is all packaged up into one state though to make it easier to operate
48/// on and to centralize handling of the state related to resources due to how
49/// critical it is for correctness.
50pub struct ResourceTables<'a> {
51 /// Runtime state for all resources defined in a component.
52 ///
53 /// This is required whenever a `TypeResourceTableIndex` is provided as it's
54 /// the lookup where that happens. Not present during embedder-originating
55 /// operations though such as `ResourceAny::resource_drop` which won't
56 /// consult this table as it's only operating over the host table.
57 pub tables: Option<&'a mut PrimaryMap<TypeResourceTableIndex, ResourceTable>>,
58
59 /// Runtime state for resources currently owned by the host.
60 ///
61 /// This is the single table used by the host stored within `Store<T>`. Host
62 /// resources will point into this and effectively have the same semantics
63 /// as-if they're in-component resources. The major distinction though is
64 /// that this is a heterogeneous table instead of only containing a single
65 /// type.
66 pub host_table: Option<&'a mut ResourceTable>,
67
68 /// Scope information about calls actively in use to track information such
69 /// as borrow counts.
70 pub calls: &'a mut CallContexts,
71}
72
73/// An individual slab of resources used for a single table within a component.
74/// Not much fancier than a general slab data structure.
75#[derive(Default)]
76pub struct ResourceTable {
77 /// Next slot to allocate, or `self.slots.len()` if they're all full.
78 next: u32,
79 /// Runtime state of all slots.
80 slots: Vec<Slot>,
81}
82
83enum Slot {
84 /// This slot is free and points to the next free slot, forming a linked
85 /// list of free slots.
86 Free { next: u32 },
87
88 /// This slot contains an owned resource with the listed representation.
89 ///
90 /// The `lend_count` tracks how many times this has been lent out as a
91 /// `borrow` and if nonzero this can't be removed.
92 Own { rep: u32, lend_count: u32 },
93
94 /// This slot contains a `borrow` resource that's connected to the `scope`
95 /// provided. The `rep` is listed and dropping this borrow will decrement
96 /// the borrow count of the `scope`.
97 Borrow { rep: u32, scope: usize },
98}
99
100/// State related to borrows and calls within a component.
101///
102/// This is created once per `Store` and updated and modified throughout the
103/// lifetime of the store. This primarily tracks borrow counts and what slots
104/// should be updated when calls go out of scope.
105#[derive(Default)]
106pub struct CallContexts {
107 scopes: Vec<CallContext>,
108}
109
110#[derive(Default)]
111struct CallContext {
112 lenders: Vec<Lender>,
113 borrow_count: u32,
114}
115
116#[derive(Copy, Clone)]
117struct Lender {
118 ty: Option<TypeResourceTableIndex>,
119 idx: u32,
120}
121
122impl ResourceTables<'_> {
123 fn table(&mut self, ty: Option<TypeResourceTableIndex>) -> &mut ResourceTable {
124 match ty {
125 None => self.host_table.as_mut().unwrap(),
126 Some(idx) => &mut self.tables.as_mut().unwrap()[idx],
127 }
128 }
129
130 /// Implementation of the `resource.new` canonical intrinsic.
131 ///
132 /// Note that this is the same as `resource_lower_own`.
133 pub fn resource_new(&mut self, ty: Option<TypeResourceTableIndex>, rep: u32) -> Result<u32> {
134 self.table(ty).insert(Slot::Own { rep, lend_count: 0 })
135 }
136
137 /// Implementation of the `resource.rep` canonical intrinsic.
138 ///
139 /// This one's one of the simpler ones: "just get the rep please"
140 pub fn resource_rep(&mut self, ty: Option<TypeResourceTableIndex>, idx: u32) -> Result<u32> {
141 self.table(ty).rep(idx)
142 }
143
144 /// Implementation of the `resource.drop` canonical intrinsic minus the
145 /// actual invocation of the destructor.
146 ///
147 /// This will drop the handle at the `idx` specified, removing it from the
148 /// specified table. This operation can fail if:
149 ///
150 /// * The index is invalid.
151 /// * The index points to an `own` resource which has active borrows.
152 ///
153 /// Otherwise this will return `Some(rep)` if the destructor for `rep` needs
154 /// to run. If `None` is returned then that means a `borrow` handle was
155 /// removed and no destructor is necessary.
156 pub fn resource_drop(
157 &mut self,
158 ty: Option<TypeResourceTableIndex>,
159 idx: u32,
160 ) -> Result<Option<u32>> {
161 match self.table(ty).remove(idx)? {
162 Slot::Own { rep, lend_count: 0 } => Ok(Some(rep)),
163 Slot::Own { .. } => bail!("cannot remove owned resource while borrowed"),
164 Slot::Borrow { scope, .. } => {
165 self.calls.scopes[scope].borrow_count -= 1;
166 Ok(None)
167 }
168 Slot::Free { .. } => unreachable!(),
169 }
170 }
171
172 /// Inserts a new "own" handle into the specified table.
173 ///
174 /// This will insert the specified representation into the specified type
175 /// table.
176 ///
177 /// Note that this operation is infallible, and additionally that this is
178 /// the same as `resource_new` implementation-wise.
179 ///
180 /// This is an implementation of the canonical ABI `lower_own` function.
181 pub fn resource_lower_own(
182 &mut self,
183 ty: Option<TypeResourceTableIndex>,
184 rep: u32,
185 ) -> Result<u32> {
186 self.table(ty).insert(Slot::Own { rep, lend_count: 0 })
187 }
188
189 /// Attempts to remove an "own" handle from the specified table and its
190 /// index.
191 ///
192 /// This operation will fail if `idx` is invalid, if it's a `borrow` handle,
193 /// or if the own handle has currently been "lent" as a borrow.
194 ///
195 /// This is an implementation of the canonical ABI `lift_own` function.
196 pub fn resource_lift_own(
197 &mut self,
198 ty: Option<TypeResourceTableIndex>,
199 idx: u32,
200 ) -> Result<u32> {
201 match self.table(ty).remove(idx)? {
202 Slot::Own { rep, lend_count: 0 } => Ok(rep),
203 Slot::Own { .. } => bail!("cannot remove owned resource while borrowed"),
204 Slot::Borrow { .. } => bail!("cannot lift own resource from a borrow"),
205 Slot::Free { .. } => unreachable!(),
206 }
207 }
208
209 /// Extracts the underlying resource representation by lifting a "borrow"
210 /// from the tables.
211 ///
212 /// This primarily employs dynamic tracking when a borrow is created from an
213 /// "own" handle to ensure that the "own" handle isn't dropped while the
214 /// borrow is active and additionally that when the current call scope
215 /// returns the lend operation is undone.
216 ///
217 /// This is an implementation of the canonical ABI `lift_borrow` function.
218 pub fn resource_lift_borrow(
219 &mut self,
220 ty: Option<TypeResourceTableIndex>,
221 idx: u32,
222 ) -> Result<u32> {
223 match self.table(ty).get_mut(idx)? {
224 Slot::Own { rep, lend_count } => {
225 // The decrement to this count happens in `exit_call`.
226 *lend_count = lend_count.checked_add(1).unwrap();
227 let rep = *rep;
228 let scope = self.calls.scopes.last_mut().unwrap();
229 scope.lenders.push(Lender { ty, idx });
230 Ok(rep)
231 }
232 Slot::Borrow { rep, .. } => Ok(*rep),
233 Slot::Free { .. } => unreachable!(),
234 }
235 }
236
237 /// Records a new `borrow` resource with the given representation within the
238 /// current call scope.
239 ///
240 /// This requires that a call scope is active. Additionally the number of
241 /// active borrows in the latest scope will be increased and must be
242 /// decreased through a future use of `resource_drop` before the current
243 /// call scope exits.
244 ///
245 /// This some of the implementation of the canonical ABI `lower_borrow`
246 /// function. The other half of this implementation is located on
247 /// `VMComponentContext` which handles the special case of avoiding borrow
248 /// tracking entirely.
249 pub fn resource_lower_borrow(
250 &mut self,
251 ty: Option<TypeResourceTableIndex>,
252 rep: u32,
253 ) -> Result<u32> {
254 let scope = self.calls.scopes.len() - 1;
255 let borrow_count = &mut self.calls.scopes.last_mut().unwrap().borrow_count;
256 *borrow_count = borrow_count.checked_add(1).unwrap();
257 self.table(ty).insert(Slot::Borrow { rep, scope })
258 }
259
260 /// Enters a new calling context, starting a fresh count of borrows and
261 /// such.
262 #[inline]
263 pub fn enter_call(&mut self) {
264 self.calls.scopes.push(CallContext::default());
265 }
266
267 /// Exits the previously pushed calling context.
268 ///
269 /// This requires all information to be available within this
270 /// `ResourceTables` and is only called during lowering/lifting operations
271 /// at this time.
272 #[inline]
273 pub fn exit_call(&mut self) -> Result<()> {
274 let cx = self.calls.scopes.pop().unwrap();
275 if cx.borrow_count > 0 {
276 bail!("borrow handles still remain at the end of the call")
277 }
278 for lender in cx.lenders.iter() {
279 // Note the panics here which should never get triggered in theory
280 // due to the dynamic tracking of borrows and such employed for
281 // resources.
282 match self.table(lender.ty).get_mut(lender.idx).unwrap() {
283 Slot::Own { lend_count, .. } => {
284 *lend_count -= 1;
285 }
286 _ => unreachable!(),
287 }
288 }
289 Ok(())
290 }
291}
292
293impl ResourceTable {
294 fn insert(&mut self, new: Slot) -> Result<u32> {
295 let next = self.next as usize;
296 if next == self.slots.len() {
297 self.slots.push(Slot::Free {
298 next: self.next.checked_add(1).unwrap(),
299 });
300 }
301 let ret = self.next;
302 self.next = match mem::replace(&mut self.slots[next], new) {
303 Slot::Free { next } => next,
304 _ => unreachable!(),
305 };
306
307 // The component model reserves index 0 as never allocatable so add one
308 // to the table index to start the numbering at 1 instead. Also note
309 // that the component model places an upper-limit per-table on the
310 // maximum allowed index.
311 let ret = ret + 1;
312 if ret >= MAX_RESOURCE_HANDLE {
313 bail!("cannot allocate another handle: index overflow");
314 }
315 Ok(ret)
316 }
317
318 fn handle_index_to_table_index(&self, idx: u32) -> Option<usize> {
319 // NB: `idx` is decremented by one to account for the `+1` above during
320 // allocation.
321 let idx = idx.checked_sub(1)?;
322 usize::try_from(idx).ok()
323 }
324
325 fn rep(&self, idx: u32) -> Result<u32> {
326 let slot = self
327 .handle_index_to_table_index(idx)
328 .and_then(|i| self.slots.get(i));
329 match slot {
330 None | Some(Slot::Free { .. }) => bail!("unknown handle index {idx}"),
331 Some(Slot::Own { rep, .. } | Slot::Borrow { rep, .. }) => Ok(*rep),
332 }
333 }
334
335 fn get_mut(&mut self, idx: u32) -> Result<&mut Slot> {
336 let slot = self
337 .handle_index_to_table_index(idx)
338 .and_then(|i| self.slots.get_mut(i));
339 match slot {
340 None | Some(Slot::Free { .. }) => bail!("unknown handle index {idx}"),
341 Some(other) => Ok(other),
342 }
343 }
344
345 fn remove(&mut self, idx: u32) -> Result<Slot> {
346 let to_fill = Slot::Free { next: self.next };
347 let ret = mem::replace(self.get_mut(idx)?, to_fill);
348 self.next = idx - 1;
349 Ok(ret)
350 }
351}