Skip to main content

wasmtime/runtime/component/
store.rs

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