Skip to main content

wasmtime/runtime/component/
store.rs

1use crate::prelude::*;
2#[cfg(feature = "component-model-async")]
3use crate::runtime::component::ResourceTable;
4use crate::runtime::component::concurrent::ConcurrentState;
5use crate::runtime::component::{HostResourceData, Instance};
6use crate::runtime::vm;
7#[cfg(feature = "component-model-async")]
8use crate::runtime::vm::VMStore;
9use crate::runtime::vm::component::{
10    CallContext, ComponentInstance, HandleTable, OwnedComponentInstance,
11};
12use crate::store::{StoreData, StoreId, StoreOpaque};
13use crate::{AsContext, AsContextMut, Engine, Store, StoreContextMut};
14use core::pin::Pin;
15use wasmtime_environ::PrimaryMap;
16use wasmtime_environ::component::RuntimeComponentInstanceIndex;
17
18/// Default amount of fuel allowed for all guest-to-host calls in the component
19/// model.
20///
21/// This is the maximal amount of data which will be copied from the guest to
22/// the host by default. This is set large enough as to not be hit all that
23/// often in theory but also small enough such that if left unconfigured on a
24/// host doesn't mean that it's automatically susceptible to DoS for example.
25const DEFAULT_HOSTCALL_FUEL: usize = 128 << 20;
26
27/// Extensions to `Store` which are only relevant for component-related
28/// information.
29pub struct ComponentStoreData {
30    /// All component instances, in a similar manner to how core wasm instances
31    /// are managed.
32    instances: PrimaryMap<ComponentInstanceId, Option<OwnedComponentInstance>>,
33
34    /// Whether an instance belonging to this store has trapped.
35    trapped: bool,
36
37    /// Total number of component instances in this store, used to track
38    /// resources in the instance allocator.
39    num_component_instances: usize,
40
41    /// Runtime state for components used in the handling of resources, borrow,
42    /// and calls. These also interact with the `ResourceAny` type and its
43    /// internal representation.
44    component_host_table: HandleTable,
45    host_resource_data: HostResourceData,
46
47    /// Metadata/tasks/etc related to component-model-async and concurrency
48    /// support.
49    task_state: ComponentTaskState,
50
51    /// Fuel to be used for each time the guest calls the host or transfers data
52    /// to the host.
53    ///
54    /// Caps the size of the allocations made on the host to this amount
55    /// effectively.
56    hostcall_fuel: usize,
57}
58
59/// State tracking for tasks within components.
60pub enum ComponentTaskState {
61    /// Used when `Config::concurrency_support` is disabled. Here there are no
62    /// async tasks but there's still state for borrows that needs managing.
63    NotConcurrent(ComponentTasksNotConcurrent),
64
65    /// Used when `Config::concurrency_support` is enabled and has
66    /// full state for all async tasks.
67    Concurrent(ConcurrentState),
68}
69
70#[derive(Copy, Clone, Debug, PartialEq, Eq)]
71pub struct ComponentInstanceId(u32);
72wasmtime_environ::entity_impl!(ComponentInstanceId);
73
74#[derive(Debug, Copy, Clone, PartialEq, Eq)]
75pub struct RuntimeInstance {
76    pub instance: ComponentInstanceId,
77    pub index: RuntimeComponentInstanceIndex,
78}
79
80impl ComponentStoreData {
81    pub fn new(engine: &Engine) -> ComponentStoreData {
82        ComponentStoreData {
83            instances: Default::default(),
84            trapped: false,
85            num_component_instances: 0,
86            component_host_table: Default::default(),
87            host_resource_data: Default::default(),
88            task_state: if engine.tunables().concurrency_support {
89                #[cfg(feature = "component-model-async")]
90                {
91                    ComponentTaskState::Concurrent(Default::default())
92                }
93                #[cfg(not(feature = "component-model-async"))]
94                {
95                    // This should be validated in `Config` where if
96                    // `concurrency_support` is enabled but compile time support
97                    // isn't available then an `Engine` isn't creatable.
98                    unreachable!()
99                }
100            } else {
101                ComponentTaskState::NotConcurrent(Default::default())
102            },
103            hostcall_fuel: DEFAULT_HOSTCALL_FUEL,
104        }
105    }
106
107    /// Hook used just before a `Store` is dropped to dispose of anything
108    /// necessary.
109    ///
110    /// Used at this time to deallocate fibers related to concurrency support.
111    pub fn run_manual_drop_routines<T>(store: StoreContextMut<T>) {
112        // We need to drop the fibers of each component instance before
113        // attempting to drop the instances themselves since the fibers may need
114        // to be resumed and allowed to exit cleanly before we yank the state
115        // out from under them.
116        //
117        // This will also drop any futures which might use a `&Accessor` fields
118        // in their `Drop::drop` implementations, in which case they'll need to
119        // be called from with in the context of a `tls::set` closure.
120        #[cfg(feature = "component-model-async")]
121        if store.0.component_data().task_state.is_concurrent() {
122            ComponentStoreData::drop_fibers_and_futures(store.0);
123        }
124        #[cfg(not(feature = "component-model-async"))]
125        let _ = store;
126    }
127
128    pub fn next_component_instance_id(&self) -> ComponentInstanceId {
129        self.instances.next_key()
130    }
131
132    #[cfg(feature = "component-model-async")]
133    pub(crate) fn drop_fibers_and_futures(store: &mut dyn VMStore) {
134        let mut fibers = Vec::new();
135        let mut futures = Vec::new();
136        store
137            .concurrent_state_mut()
138            .take_fibers_and_futures(&mut fibers, &mut futures);
139
140        for mut fiber in fibers {
141            fiber.dispose(store);
142        }
143
144        crate::component::concurrent::tls::set(store, move || drop(futures));
145    }
146
147    #[cfg(feature = "component-model-async")]
148    pub(crate) fn assert_instance_states_empty(&mut self) {
149        for (_, instance) in self.instances.iter_mut() {
150            let Some(instance) = instance.as_mut() else {
151                continue;
152            };
153
154            assert!(
155                instance
156                    .get_mut()
157                    .instance_states()
158                    .0
159                    .iter_mut()
160                    .all(|(_, state)| state.handle_table().is_empty()
161                        && state.concurrent_state().pending_is_empty())
162            );
163        }
164    }
165
166    pub fn decrement_allocator_resources(&mut self, allocator: &dyn vm::InstanceAllocator) {
167        for _ in 0..self.num_component_instances {
168            allocator.decrement_component_instance_count();
169        }
170    }
171}
172
173/// A type used to represent an allocated `ComponentInstance` located within a
174/// store.
175///
176/// This type is held in various locations as a "safe index" into a store. This
177/// encapsulates a `StoreId` which owns the instance as well as the index within
178/// the store's list of which instance it's pointing to.
179///
180/// This type can notably be used to index into a `StoreOpaque` to project out
181/// the `ComponentInstance` that is associated with this id.
182#[repr(C)] // used by reference in the C API
183#[derive(Copy, Clone, Debug, PartialEq, Eq)]
184pub struct StoreComponentInstanceId {
185    store_id: StoreId,
186    instance: ComponentInstanceId,
187}
188
189impl StoreComponentInstanceId {
190    pub(crate) fn new(
191        store_id: StoreId,
192        instance: ComponentInstanceId,
193    ) -> StoreComponentInstanceId {
194        StoreComponentInstanceId { store_id, instance }
195    }
196
197    #[inline]
198    pub fn assert_belongs_to(&self, store: StoreId) {
199        self.store_id.assert_belongs_to(store)
200    }
201
202    #[inline]
203    pub(crate) fn store_id(&self) -> StoreId {
204        self.store_id
205    }
206
207    #[inline]
208    pub(crate) fn instance(&self) -> ComponentInstanceId {
209        self.instance
210    }
211
212    /// Looks up the `vm::ComponentInstance` within `store` that this id points
213    /// to.
214    ///
215    /// # Panics
216    ///
217    /// Panics if `self` does not belong to `store`.
218    pub(crate) fn get<'a>(&self, store: &'a StoreOpaque) -> &'a ComponentInstance {
219        self.assert_belongs_to(store.id());
220        store.component_instance(self.instance)
221    }
222
223    /// Mutable version of `get` above.
224    ///
225    /// # Panics
226    ///
227    /// Panics if `self` does not belong to `store`.
228    pub(crate) fn get_mut<'a>(&self, store: &'a mut StoreOpaque) -> Pin<&'a mut ComponentInstance> {
229        self.from_data_get_mut(store.store_data_mut())
230    }
231
232    /// Return a mutable `ComponentInstance` and a `ModuleRegistry`
233    /// from the store.
234    ///
235    /// # Panics
236    ///
237    /// Panics if `self` does not belong to `store`.
238    #[cfg(feature = "component-model-async")]
239    pub(crate) fn get_mut_and_registry<'a>(
240        &self,
241        store: &'a mut StoreOpaque,
242    ) -> (
243        Pin<&'a mut ComponentInstance>,
244        &'a crate::module::ModuleRegistry,
245    ) {
246        let (store_data, registry) = store.store_data_mut_and_registry();
247        let instance = self.from_data_get_mut(store_data);
248        (instance, registry)
249    }
250
251    /// Same as `get_mut`, but borrows less of a store.
252    fn from_data_get_mut<'a>(&self, store: &'a mut StoreData) -> Pin<&'a mut ComponentInstance> {
253        self.assert_belongs_to(store.id());
254        store.component_instance_mut(self.instance)
255    }
256}
257
258impl StoreData {
259    pub(crate) fn push_component_instance(
260        &mut self,
261        data: OwnedComponentInstance,
262    ) -> ComponentInstanceId {
263        let expected = data.get().id();
264        let ret = self.components.instances.push(Some(data));
265        assert_eq!(expected, ret);
266        ret
267    }
268
269    pub(crate) fn component_instance(&self, id: ComponentInstanceId) -> &ComponentInstance {
270        self.components.instances[id].as_ref().unwrap().get()
271    }
272
273    pub(crate) fn component_instance_mut(
274        &mut self,
275        id: ComponentInstanceId,
276    ) -> Pin<&mut ComponentInstance> {
277        self.components.instances[id].as_mut().unwrap().get_mut()
278    }
279}
280
281impl StoreOpaque {
282    pub(crate) fn trapped(&self) -> bool {
283        self.store_data().components.trapped
284    }
285
286    pub(crate) fn set_trapped(&mut self) {
287        self.store_data_mut().components.trapped = true;
288    }
289
290    pub(crate) fn component_data(&self) -> &ComponentStoreData {
291        &self.store_data().components
292    }
293
294    pub(crate) fn component_data_mut(&mut self) -> &mut ComponentStoreData {
295        &mut self.store_data_mut().components
296    }
297
298    pub(crate) fn component_task_state_mut(&mut self) -> &mut ComponentTaskState {
299        &mut self.component_data_mut().task_state
300    }
301
302    pub(crate) fn push_component_instance(&mut self, instance: Instance) {
303        // We don't actually need the instance itself right now, but it seems
304        // like something we will almost certainly eventually want to keep
305        // around, so force callers to provide it.
306        let _ = instance;
307
308        self.component_data_mut().num_component_instances += 1;
309    }
310
311    pub(crate) fn component_instance(&self, id: ComponentInstanceId) -> &ComponentInstance {
312        self.store_data().component_instance(id)
313    }
314
315    #[cfg(feature = "component-model-async")]
316    pub(crate) fn component_instance_mut(
317        &mut self,
318        id: ComponentInstanceId,
319    ) -> Pin<&mut ComponentInstance> {
320        self.store_data_mut().component_instance_mut(id)
321    }
322
323    #[cfg(feature = "component-model-async")]
324    pub(crate) fn concurrent_state_mut(&mut self) -> &mut ConcurrentState {
325        debug_assert!(self.concurrency_support());
326        self.component_data_mut().task_state.concurrent_state_mut()
327    }
328
329    #[inline]
330    #[cfg(feature = "component-model-async")]
331    pub(crate) fn concurrency_support(&self) -> bool {
332        let support = self.component_data().task_state.is_concurrent();
333        debug_assert_eq!(support, self.engine().tunables().concurrency_support);
334        support
335    }
336
337    pub(crate) fn lift_context_parts(
338        &mut self,
339        instance: Instance,
340    ) -> (
341        &mut ComponentTaskState,
342        &mut HandleTable,
343        &mut HostResourceData,
344        Pin<&mut ComponentInstance>,
345    ) {
346        let instance = instance.id();
347        instance.assert_belongs_to(self.id());
348        let data = self.component_data_mut();
349        (
350            &mut data.task_state,
351            &mut data.component_host_table,
352            &mut data.host_resource_data,
353            data.instances[instance.instance]
354                .as_mut()
355                .unwrap()
356                .get_mut(),
357        )
358    }
359
360    pub(crate) fn component_resource_tables(
361        &mut self,
362        instance: Option<Instance>,
363    ) -> vm::component::ResourceTables<'_> {
364        self.component_resource_tables_and_host_resource_data(instance)
365            .0
366    }
367
368    pub(crate) fn component_resource_tables_and_host_resource_data(
369        &mut self,
370        instance: Option<Instance>,
371    ) -> (
372        vm::component::ResourceTables<'_>,
373        &mut crate::component::HostResourceData,
374    ) {
375        let store_id = self.id();
376        let data = self.component_data_mut();
377        let guest = instance.map(|i| {
378            let i = i.id();
379            i.assert_belongs_to(store_id);
380            data.instances[i.instance]
381                .as_mut()
382                .unwrap()
383                .get_mut()
384                .instance_states()
385        });
386
387        (
388            vm::component::ResourceTables {
389                host_table: &mut data.component_host_table,
390                task_state: &mut data.task_state,
391                guest,
392            },
393            &mut data.host_resource_data,
394        )
395    }
396
397    pub(crate) fn enter_call_not_concurrent(&mut self) {
398        let state = match &mut self.component_data_mut().task_state {
399            ComponentTaskState::NotConcurrent(state) => state,
400            ComponentTaskState::Concurrent(_) => unreachable!(),
401        };
402        state.scopes.push(CallContext::default());
403    }
404
405    pub(crate) fn exit_call_not_concurrent(&mut self) {
406        let state = match &mut self.component_data_mut().task_state {
407            ComponentTaskState::NotConcurrent(state) => state,
408            ComponentTaskState::Concurrent(_) => unreachable!(),
409        };
410        state.scopes.pop();
411    }
412
413    pub(crate) fn hostcall_fuel(&self) -> usize {
414        self.component_data().hostcall_fuel
415    }
416
417    pub(crate) fn set_hostcall_fuel(&mut self, fuel: usize) {
418        self.component_data_mut().hostcall_fuel = fuel;
419    }
420
421    #[cfg(feature = "component-model-async")]
422    fn concurrent_resource_table(&mut self) -> Option<&mut ResourceTable> {
423        if self.concurrency_support() {
424            Some(self.concurrent_state_mut().table())
425        } else {
426            None
427        }
428    }
429}
430
431impl<T> Store<T> {
432    /// Returns the amount of "hostcall fuel" used for guest-to-host component
433    /// calls.
434    ///
435    /// This is either the default amount if it hasn't been configured or
436    /// returns the last value passed to [`Store::set_hostcall_fuel`].
437    ///
438    /// See [`Store::set_hostcall_fuel`] `for more details.
439    pub fn hostcall_fuel(&self) -> usize {
440        self.as_context().0.hostcall_fuel()
441    }
442
443    /// Sets the amount of "hostcall fuel" used for guest-to-host component
444    /// calls.
445    ///
446    /// Whenever the guest calls the host it often wants to transfer some data
447    /// as well, such as strings or lists. This configured fuel value can be
448    /// used to limit the amount of data that the host allocates on behalf of
449    /// the guest. This is a DoS mitigation mechanism to prevent a malicious
450    /// guest from causing the host to allocate an unbounded amount of memory
451    /// for example.
452    ///
453    /// Fuel is considered distinct for each host call. The host is responsible
454    /// for ensuring it retains a proper amount of data between host calls if
455    /// applicable. The `fuel` provided here will be the initial value for each
456    /// time the guest calls the host.
457    ///
458    /// The `fuel` value here should roughly corresponds to the maximal number
459    /// of bytes that the guest may transfer to the host in one call.
460    ///
461    /// Note that data transferred from the host to the guest is not limited
462    /// because it's already resident on the host itself. Only data from the
463    /// guest to the host is limited.
464    ///
465    /// The default value for this is 128 MiB.
466    pub fn set_hostcall_fuel(&mut self, fuel: usize) {
467        self.as_context_mut().set_hostcall_fuel(fuel)
468    }
469
470    /// Returns the underlying [`ResourceTable`] that the implementation of
471    /// concurrency in the component model is using.
472    ///
473    /// Returns `None` if [`Config::concurrency_support`] is disabled.
474    ///
475    /// [`Config::concurrency_support`]: crate::Config::concurrency_support
476    #[cfg(feature = "component-model-async")]
477    pub fn concurrent_resource_table(&mut self) -> Option<&mut ResourceTable> {
478        self.as_context_mut().0.concurrent_resource_table()
479    }
480}
481
482impl<T> StoreContextMut<'_, T> {
483    /// See [`Store::hostcall_fuel`].
484    pub fn hostcall_fuel(&self) -> usize {
485        self.0.hostcall_fuel()
486    }
487
488    /// See [`Store::set_hostcall_fuel`].
489    pub fn set_hostcall_fuel(&mut self, fuel: usize) {
490        self.0.set_hostcall_fuel(fuel)
491    }
492
493    /// See [`Store::concurrent_resource_table`].
494    #[cfg(feature = "component-model-async")]
495    pub fn concurrent_resource_table(&mut self) -> Option<&mut ResourceTable> {
496        self.0.concurrent_resource_table()
497    }
498}
499
500#[derive(Default)]
501pub struct ComponentTasksNotConcurrent {
502    scopes: Vec<CallContext>,
503}
504
505impl ComponentTaskState {
506    pub fn call_context(&mut self, id: u32) -> &mut CallContext {
507        match self {
508            ComponentTaskState::NotConcurrent(state) => &mut state.scopes[id as usize],
509            ComponentTaskState::Concurrent(state) => state.call_context(id),
510        }
511    }
512
513    pub fn current_call_context_scope_id(&self) -> u32 {
514        match self {
515            ComponentTaskState::NotConcurrent(state) => {
516                u32::try_from(state.scopes.len() - 1).unwrap()
517            }
518            ComponentTaskState::Concurrent(state) => state.current_call_context_scope_id(),
519        }
520    }
521
522    pub fn concurrent_state_mut(&mut self) -> &mut ConcurrentState {
523        match self {
524            ComponentTaskState::Concurrent(state) => state,
525            ComponentTaskState::NotConcurrent(_) => {
526                panic!("expected concurrent state to be present")
527            }
528        }
529    }
530
531    #[cfg(feature = "component-model-async")]
532    fn is_concurrent(&self) -> bool {
533        match self {
534            ComponentTaskState::Concurrent(_) => true,
535            ComponentTaskState::NotConcurrent(_) => false,
536        }
537    }
538}