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::{HandleCounter, HandleTable, InstanceState, RemovedResource};
24use crate::bail_bug;
25use crate::component::store::ComponentTaskState;
26use crate::prelude::*;
27use core::error::Error;
28use core::fmt;
29use core::mem;
30use wasmtime_environ::component::{
31 ComponentTypes, RuntimeComponentInstanceIndex, TypeResourceTableIndex,
32};
33use wasmtime_environ::prelude::TryPrimaryMap;
34
35/// Contextual state necessary to perform resource-related operations.
36///
37/// This state a bit odd since it has a few optional bits, but the idea is that
38/// whenever this is constructed the bits required to perform operations are
39/// always `Some`. For example:
40///
41/// * During lifting and lowering both `guest_table` and `host_table` are
42/// `Some`.
43/// * During wasm's own intrinsics only `guest_table` 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`, for example, is
54 /// provided as it's the lookup where that happens. Not present during
55 /// embedder-originating operations though such as
56 /// `ResourceAny::resource_drop` which won't consult this table as it's
57 /// only operating over the host table.
58 pub guest: Option<(
59 &'a mut TryPrimaryMap<RuntimeComponentInstanceIndex, InstanceState>,
60 &'a ComponentTypes,
61 )>,
62
63 /// Runtime state for resources currently owned by the host.
64 ///
65 /// This is the single table used by the host stored within `Store<T>`. Host
66 /// resources will point into this and effectively have the same semantics
67 /// as-if they're in-component resources. The major distinction though is
68 /// that this is a heterogeneous table instead of only containing a single
69 /// type.
70 pub host_table: &'a mut HandleTable,
71
72 /// The store-wide live-handle counter, charged by every insertion into and
73 /// removal from any of the tables above.
74 pub handle_counter: &'a mut HandleCounter,
75
76 /// Task information about calls actively in use to track information such
77 /// as borrow counts.
78 pub task_state: &'a mut ComponentTaskState,
79
80 /// The current scope, used to mutate borrows and lenders for the call.
81 pub current_scope: Option<CurrentScope>,
82}
83
84/// The resource-borrow scope associated with the current component call.
85#[derive(Debug, Copy, Clone, Eq, PartialEq)]
86pub enum CurrentScope {
87 /// A scope which already has an ID in the component task state.
88 Id(Scope),
89 /// A host scope whose task has not yet been materialized.
90 #[cfg_attr(not(feature = "component-model-async"), allow(dead_code))]
91 DeferredHost,
92}
93
94/// Identifier for a component call's resource-borrow scope.
95#[derive(Debug, Copy, Clone, Eq, PartialEq)]
96pub enum Scope {
97 /// A non-concurrent scope or a concurrent guest-task scope.
98 Id(u32),
99 /// A concurrent host-task scope.
100 HostId(u32),
101}
102
103/// Typed representation of a "rep" for a resource.
104///
105/// All resources in the component model are stored in a single heterogeneous
106/// table so this type is used to disambiguate what everything is. This is the
107/// representation of a resource stored at-rest in memory.
108#[derive(Debug)]
109pub enum TypedResource {
110 /// A resource defined by the host.
111 ///
112 /// The meaning of the 32-bit integer here is up to the embedder, it
113 /// otherwise is not used within the runtime here.
114 Host(u32),
115
116 /// A resource defined within a component.
117 Component {
118 /// This is an integer supplied by the component itself when this
119 /// resource was created. Typically this is a pointer into linear
120 /// memory for a component.
121 rep: u32,
122
123 /// The type of this component resource.
124 ///
125 /// This is used, within the context of a component, to keep track of
126 /// what the type of `rep` is. This is then used when getting/removing
127 /// from the table to ensure that the guest does indeed have the right
128 /// permission to access this slot.
129 ty: TypeResourceTableIndex,
130 },
131}
132
133impl TypedResource {
134 pub(super) fn rep(&self, access_ty: &TypedResourceIndex) -> Result<u32> {
135 match (self, access_ty) {
136 (Self::Host(rep), TypedResourceIndex::Host(_)) => Ok(*rep),
137 (Self::Host(_), expected) => bail!(ResourceTypeMismatch {
138 expected: *expected,
139 found: "host resource",
140 }),
141 (Self::Component { rep, ty }, TypedResourceIndex::Component { ty: expected, .. }) => {
142 if ty == expected {
143 Ok(*rep)
144 } else {
145 bail!(ResourceTypeMismatch {
146 expected: *access_ty,
147 found: "a different guest-defined resource",
148 })
149 }
150 }
151 (Self::Component { .. }, expected) => bail!(ResourceTypeMismatch {
152 expected: *expected,
153 found: "guest-defined resource",
154 }),
155 }
156 }
157}
158
159/// An index used to access a resource.
160///
161/// This reflects how index operations are always accompanied not only with a
162/// 32-bit index but additionally with a type. For example a `resource.drop`
163/// intrinsic in a guest takes only a 32-bit integer argument, but it
164/// inherently is used to only drop one type of resource which is additionally
165/// ascribed here.
166#[derive(Debug, Copy, Clone)]
167pub enum TypedResourceIndex {
168 /// A host resource at the given index is being accessed.
169 Host(u32),
170
171 /// A guest resource is being accessed.
172 Component {
173 /// The index supplied by the guest being accessed.
174 index: u32,
175
176 /// The fully-specific type of this resource.
177 ty: TypeResourceTableIndex,
178 },
179}
180
181impl TypedResourceIndex {
182 pub(super) fn raw_index(&self) -> u32 {
183 match self {
184 Self::Host(index) | Self::Component { index, .. } => *index,
185 }
186 }
187
188 fn desc(&self) -> &'static str {
189 match self {
190 Self::Host(_) => "host resource",
191 Self::Component { .. } => "guest-defined resource",
192 }
193 }
194}
195
196/// State related to borrows for a specific call.
197#[derive(Default)]
198pub struct CallContext {
199 lenders: TryVec<TypedResourceIndex>,
200 borrow_count: u32,
201}
202
203impl CallContext {
204 #[cfg_attr(not(feature = "component-model-async"), allow(dead_code))]
205 pub(crate) fn is_empty(&self) -> bool {
206 self.lenders.is_empty() && self.borrow_count == 0
207 }
208}
209
210impl ResourceTables<'_> {
211 fn table_for_resource(
212 &mut self,
213 resource: &TypedResource,
214 ) -> (&mut HandleTable, &mut HandleCounter) {
215 let table = match resource {
216 TypedResource::Host(_) => &mut *self.host_table,
217 TypedResource::Component { ty, .. } => {
218 let (states, types) = self.guest.as_mut().unwrap();
219 states[types[*ty].unwrap_concrete_instance()].handle_table()
220 }
221 };
222 (table, &mut *self.handle_counter)
223 }
224
225 fn table_for_index(
226 &mut self,
227 index: &TypedResourceIndex,
228 ) -> (&mut HandleTable, &mut HandleCounter) {
229 let table = match index {
230 TypedResourceIndex::Host(_) => &mut *self.host_table,
231 TypedResourceIndex::Component { ty, .. } => {
232 let (states, types) = self.guest.as_mut().unwrap();
233 states[types[*ty].unwrap_concrete_instance()].handle_table()
234 }
235 };
236 (table, &mut *self.handle_counter)
237 }
238
239 /// Implementation of the `resource.new` canonical intrinsic.
240 ///
241 /// Note that this is the same as `resource_lower_own`.
242 pub fn resource_new(&mut self, resource: TypedResource) -> Result<u32> {
243 let (table, counter) = self.table_for_resource(&resource);
244 table.resource_own_insert(counter, resource)
245 }
246
247 /// Implementation of the `resource.rep` canonical intrinsic.
248 ///
249 /// This one's one of the simpler ones: "just get the rep please"
250 pub fn resource_rep(&mut self, index: TypedResourceIndex) -> Result<u32> {
251 self.table_for_index(&index).0.resource_rep(index)
252 }
253
254 /// Implementation of the `resource.drop` canonical intrinsic minus the
255 /// actual invocation of the destructor.
256 ///
257 /// This will drop the handle at the `index` specified, removing it from
258 /// the specified table. This operation can fail if:
259 ///
260 /// * The index is invalid.
261 /// * The index points to a resource which has been lent out.
262 /// * The index's type is mismatched with the entry in the table's type.
263 ///
264 /// Otherwise this will return `Some(rep)` if the destructor for `rep` needs
265 /// to run. If `None` is returned then that means a `borrow` handle was
266 /// removed and no destructor is necessary.
267 pub fn resource_drop(&mut self, index: TypedResourceIndex) -> Result<Option<u32>> {
268 match self.table_for_index(&index).0.remove_resource(index)? {
269 RemovedResource::Own { rep } => Ok(Some(rep)),
270 RemovedResource::Borrow { scope } => {
271 self.task_state.call_context(scope)?.borrow_count -= 1;
272 Ok(None)
273 }
274 }
275 }
276
277 /// Inserts a new "own" handle into the specified table.
278 ///
279 /// This will insert the specified representation into the specified type
280 /// table.
281 ///
282 /// Note that this operation is infallible, and additionally that this is
283 /// the same as `resource_new` implementation-wise.
284 ///
285 /// This is an implementation of the canonical ABI `lower_own` function.
286 pub fn resource_lower_own(&mut self, resource: TypedResource) -> Result<u32> {
287 let (table, counter) = self.table_for_resource(&resource);
288 table.resource_own_insert(counter, resource)
289 }
290
291 /// Attempts to remove an "own" handle from the specified table and its
292 /// index.
293 ///
294 /// This operation will fail if `index` is invalid, if it's a `borrow`
295 /// handle, if the own handle has currently been "lent" as a borrow, or if
296 /// `index` has a different type in the table than the index.
297 ///
298 /// This is an implementation of the canonical ABI `lift_own` function.
299 pub fn resource_lift_own(&mut self, index: TypedResourceIndex) -> Result<u32> {
300 match self.table_for_index(&index).0.remove_resource(index)? {
301 RemovedResource::Own { rep } => Ok(rep),
302 RemovedResource::Borrow { .. } => bail!("cannot lift own resource from a borrow"),
303 }
304 }
305
306 fn materialize_current_scope(&mut self) -> Result<Scope> {
307 let id = match self.current_scope {
308 Some(CurrentScope::Id(id)) => return Ok(id),
309 Some(CurrentScope::DeferredHost) => self.task_state.materialize_current_scope()?,
310 None => bail_bug!("no current scope"),
311 };
312 self.current_scope = Some(CurrentScope::Id(id));
313 Ok(id)
314 }
315
316 /// Returns the current call's resource-borrow scope.
317 ///
318 /// Unlike [`Self::materialize_current_scope`], this does not materialize a
319 /// deferred host task.
320 fn current_scope(&mut self) -> Result<&mut CallContext> {
321 let id = match self.current_scope {
322 Some(CurrentScope::Id(id)) => id,
323 Some(CurrentScope::DeferredHost) => {
324 // With a deferred host task the `CallContext` for this task
325 // could be stored in one of two locations. The primary location
326 // is `task_state.deferred_host_call_context()` but that stops
327 // being used once the host task is actually materialized. On
328 // materialization this over here isn't updated necessarily, so
329 // this'll only lazily pick up when the host task is actually
330 // materialized. In such a situation that's detected here and
331 // `self.current_scope` is mutated to match.
332 //
333 // NB: this should use `if let` when rustc is smarter about
334 // borrows.
335 if self.task_state.deferred_host_call_context().is_some() {
336 return Ok(self.task_state.deferred_host_call_context().unwrap());
337 }
338 let id = self.task_state.materialize_current_scope()?;
339 self.current_scope = Some(CurrentScope::Id(id));
340 id
341 }
342 None => bail_bug!("no current scope"),
343 };
344 self.task_state.call_context(id)
345 }
346
347 /// Extracts the underlying resource representation by lifting a "borrow"
348 /// from the tables.
349 ///
350 /// This primarily employs dynamic tracking when a borrow is created from
351 /// either an "own" or a "borrow" handle to ensure that the lending handle
352 /// isn't dropped while the borrow is active and additionally that when the
353 /// current call scope returns the lend operation is undone.
354 ///
355 /// This is an implementation of the canonical ABI `lift_borrow` function.
356 pub fn resource_lift_borrow(&mut self, index: TypedResourceIndex) -> Result<u32> {
357 // FIXME: the logic here applies to all resources, both own and borrow,
358 // although tracking `borrow` here isn't always necessary for sync
359 // tasks. Ideally the `lenders.push` would be skipped for sync tasks but
360 // that requires a bit more plumbing and also appropriately tracking the
361 // `lend_count` and such. For now this treats own/borrow the same way.
362 //
363 // Space for the lender is reserved first so that recording it can't
364 // fail after the lend.
365 self.current_scope()?.lenders.reserve(1)?;
366 let (table, counter) = self.table_for_index(&index);
367 let rep = table.resource_lend(counter, index)?;
368 self.current_scope()?.lenders.push(index)?;
369 Ok(rep)
370 }
371
372 /// Records a new `borrow` resource with the given representation within the
373 /// current call scope.
374 ///
375 /// This requires that a call scope is active. Additionally the number of
376 /// active borrows in the latest scope will be increased and must be
377 /// decreased through a future use of `resource_drop` before the current
378 /// call scope exits.
379 ///
380 /// This some of the implementation of the canonical ABI `lower_borrow`
381 /// function. The other half of this implementation is located on
382 /// `VMComponentContext` which handles the special case of avoiding borrow
383 /// tracking entirely.
384 pub fn resource_lower_borrow(&mut self, resource: TypedResource) -> Result<u32> {
385 let scope = self.materialize_current_scope()?;
386 let cx = self.task_state.call_context(scope)?;
387 cx.borrow_count = cx.borrow_count.checked_add(1).unwrap();
388 let (table, counter) = self.table_for_resource(&resource);
389 table.resource_borrow_insert(counter, resource, scope)
390 }
391
392 /// Validates that the current scope can be exited.
393 ///
394 /// This will ensure that this context's active borrows have all been
395 /// dropped. This will then commit the lend decrements back to the owned
396 /// resources that were originally passed in.
397 #[inline]
398 pub fn validate_scope_exit(&mut self) -> Result<()> {
399 let cx = self.current_scope()?;
400 if cx.borrow_count > 0 {
401 bail!("borrow handles still remain at the end of the call")
402 }
403 for lender in mem::take(&mut cx.lenders) {
404 // Note the panics here which should never get triggered in theory
405 // due to the dynamic tracking of borrows and such employed for
406 // resources.
407 let (table, counter) = self.table_for_index(&lender);
408 table.resource_undo_lend(counter, lender).unwrap();
409 }
410 Ok(())
411 }
412}
413
414#[derive(Debug)]
415struct ResourceTypeMismatch {
416 expected: TypedResourceIndex,
417 found: &'static str,
418}
419
420impl fmt::Display for ResourceTypeMismatch {
421 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422 write!(
423 f,
424 "handle index {} used with the wrong type, \
425 expected {} but found {}",
426 self.expected.raw_index(),
427 self.expected.desc(),
428 self.found,
429 )
430 }
431}
432
433impl Error for ResourceTypeMismatch {}