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//! * `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
23use super::{HandleTable, InstanceState, RemovedResource};
24use crate::component::store::ComponentTaskState;
25use crate::prelude::*;
26use core::error::Error;
27use core::fmt;
28use core::mem;
29use wasmtime_environ::component::{
30 ComponentTypes, RuntimeComponentInstanceIndex, TypeResourceTableIndex,
31};
32use wasmtime_environ::prelude::TryPrimaryMap;
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.
49pub 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 TryPrimaryMap<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)]
82pub 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
106impl TypedResource {
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)]
140pub 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
154impl TypedResourceIndex {
155 pub(super) fn raw_index(&self) -> u32 {
156 match self {
157 Self::Host(index) | Self::Component { index, .. } => *index,
158 }
159 }
160
161 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)]
171pub struct CallContext {
172 lenders: Vec<TypedResourceIndex>,
173 borrow_count: u32,
174}
175
176impl ResourceTables<'_> {
177 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
187 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`.
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"
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.
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.
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.
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.
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.
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]
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)]
327struct ResourceTypeMismatch {
328 expected: TypedResourceIndex,
329 found: &'static str,
330}
331
332impl fmt::Display for ResourceTypeMismatch {
333 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
345impl Error for ResourceTypeMismatch {}