Skip to main content

wasmtime/runtime/component/
store.rs

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