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::VMResourceIndex;
5use crate::runtime::vm::component::{
6    CallContext, ComponentInstance, HandleCounter, HandleTable, OwnedComponentInstance,
7};
8use crate::store::{StoreData, StoreId, StoreOpaque};
9use crate::vm::VMScope;
10use crate::vm::component::ScopeKind;
11use crate::{AsContext, AsContextMut, Engine, Store, StoreContextMut, bail_bug};
12use core::pin::Pin;
13use wasmtime_environ::LAZY_LENDERS_CAPACITY;
14use wasmtime_environ::component::RuntimeComponentInstanceIndex;
15use wasmtime_environ::prelude::TryPrimaryMap;
16
17#[cfg(feature = "component-model-async")]
18use crate::{
19    component::ResourceTable,
20    component::concurrent::ConcurrentState,
21    runtime::vm::{VMStore, component::InstanceState},
22};
23
24/// Default amount of fuel allowed for all guest-to-host calls in the component
25/// model.
26///
27/// This is the maximal amount of data which will be copied from the guest to
28/// the host by default. This is set large enough as to not be hit all that
29/// often in theory but also small enough such that if left unconfigured on a
30/// host doesn't mean that it's automatically susceptible to DoS for example.
31const DEFAULT_HOSTCALL_FUEL: usize = 128 << 20;
32
33/// Default per-store cap on component-model handle-table slots.
34///
35/// A guest creates these directly (e.g. `resource.new`), so leaving them
36/// unbounded is a DoS vector.
37const DEFAULT_MAX_COMPONENT_HANDLES: usize = 100_000;
38
39/// Extensions to `Store` which are only relevant for component-related
40/// information.
41pub struct ComponentStoreData {
42    /// All component instances, in a similar manner to how core wasm instances
43    /// are managed.
44    instances: TryPrimaryMap<ComponentInstanceId, Option<OwnedComponentInstance>>,
45
46    /// Whether an component instance belonging to this store has trapped. This
47    /// can be used to determine if the `Store` is poisoned.
48    ///
49    /// This is only set if a trap occurs while `has_components` is also set.
50    /// Pure core Wasm instances are required to continue running after traps
51    /// so `Store`s with only core modules are never considered poisoned.
52    component_trapped: bool,
53
54    /// Whether a component instance has ever been created in this store.
55    has_components: bool,
56
57    /// Total number of component instances in this store, used to track
58    /// resources in the instance allocator.
59    num_component_instances: usize,
60
61    /// Live-handle counter for every `HandleTable` in this store (the
62    /// `component_host_table` below and each instance's tables). See
63    /// `HandleCounter`.
64    handle_counter: HandleCounter,
65
66    /// Runtime state for components used in the handling of resources, borrow,
67    /// and calls. These also interact with the `ResourceAny` type and its
68    /// internal representation.
69    component_host_table: HandleTable,
70    host_resource_data: HostResourceData,
71
72    /// Backing storage for `VMStoreContext::lazy_lenders`; see
73    /// `ResourceTables::resource_lift_borrow_lazy`.
74    ///
75    /// Boxed and never resized, since JIT code holds a raw pointer to it.
76    lazy_lenders: Box<[VMResourceIndex; LAZY_LENDERS_CAPACITY]>,
77
78    /// Metadata/tasks/etc related to component-model-async and concurrency
79    /// support.
80    task_state: ComponentTaskState,
81
82    /// Fuel to be used for each time the guest calls the host or transfers data
83    /// to the host.
84    ///
85    /// Caps the size of the allocations made on the host to this amount
86    /// effectively.
87    hostcall_fuel: usize,
88}
89
90/// State tracking for tasks within components.
91pub enum ComponentTaskState {
92    /// Used when `Config::concurrency_support` is disabled. Here there are no
93    /// async tasks but there's still state for borrows that needs managing.
94    NotConcurrent(ComponentTasksNotConcurrent),
95
96    /// Used when `Config::concurrency_support` is enabled and has
97    /// full state for all async tasks.
98    #[cfg(feature = "component-model-async")]
99    Concurrent(ConcurrentState),
100}
101
102#[derive(Copy, Clone, Debug, PartialEq, Eq)]
103pub struct ComponentInstanceId(u32);
104wasmtime_environ::entity_impl!(ComponentInstanceId);
105
106#[derive(Debug, Copy, Clone, PartialEq, Eq)]
107pub struct RuntimeInstance {
108    pub instance: ComponentInstanceId,
109    pub index: RuntimeComponentInstanceIndex,
110}
111
112impl ComponentStoreData {
113    pub fn new(engine: &Engine) -> Result<ComponentStoreData> {
114        Ok(ComponentStoreData {
115            instances: Default::default(),
116            component_trapped: false,
117            has_components: false,
118            num_component_instances: 0,
119            component_host_table: Default::default(),
120            handle_counter: HandleCounter::new(DEFAULT_MAX_COMPONENT_HANDLES),
121            host_resource_data: Default::default(),
122            task_state: if engine.tunables().concurrency_support {
123                #[cfg(feature = "component-model-async")]
124                {
125                    ComponentTaskState::Concurrent(Default::default())
126                }
127                #[cfg(not(feature = "component-model-async"))]
128                {
129                    // This should be validated in `Config` where if
130                    // `concurrency_support` is enabled but compile time support
131                    // isn't available then an `Engine` isn't creatable.
132                    unreachable!()
133                }
134            } else {
135                ComponentTaskState::NotConcurrent(Default::default())
136            },
137            hostcall_fuel: DEFAULT_HOSTCALL_FUEL,
138            lazy_lenders: try_new::<Box<_>>([VMResourceIndex::EMPTY; LAZY_LENDERS_CAPACITY])?,
139        })
140    }
141
142    /// Builds the resource tables for `instance` with an explicit
143    /// `current_scope`.
144    ///
145    /// Also returns the host resource data and the `lazy_lenders` buffer, which
146    /// live alongside the tables and so can only be borrowed together with
147    /// them.
148    fn resource_tables(
149        &mut self,
150        store_id: StoreId,
151        instance: Option<Instance>,
152        current_scope: Option<vm::component::CurrentScope>,
153    ) -> (
154        vm::component::ResourceTables<'_>,
155        &mut HostResourceData,
156        &mut [VMResourceIndex],
157    ) {
158        let guest = instance.map(|i| {
159            let i = i.id();
160            i.assert_belongs_to(store_id);
161            self.instances[i.instance]
162                .as_mut()
163                .unwrap()
164                .get_mut()
165                .instance_states_and_published()
166        });
167        (
168            vm::component::ResourceTables {
169                host_table: &mut self.component_host_table,
170                handle_counter: &mut self.handle_counter,
171                task_state: &mut self.task_state,
172                guest,
173                current_scope,
174            },
175            &mut self.host_resource_data,
176            &mut self.lazy_lenders[..],
177        )
178    }
179
180    /// Points `vm_store_context` at this store's `lazy_lenders` buffer.
181    ///
182    /// # Safety
183    ///
184    /// `self` must outlive `vm_store_context` and must not move while it is in
185    /// use, since JIT code reads the buffer through the published pointer.
186    pub(crate) unsafe fn publish_lazy_lenders(
187        &mut self,
188        vm_store_context: &mut vm::VMStoreContext,
189    ) {
190        let lazy_lenders = core::ptr::NonNull::from(&mut *self.lazy_lenders);
191        // SAFETY: the buffer is boxed, so it stays put as long as `self` does,
192        // which the caller guarantees.
193        unsafe { vm_store_context.set_lazy_lenders(lazy_lenders) }
194    }
195
196    /// Hook used just before a `Store` is dropped to dispose of anything
197    /// necessary.
198    ///
199    /// Used at this time to deallocate fibers related to concurrency support.
200    pub fn run_manual_drop_routines<T>(store: StoreContextMut<T>) {
201        // We need to drop the fibers of each component instance before
202        // attempting to drop the instances themselves since the fibers may need
203        // to be resumed and allowed to exit cleanly before we yank the state
204        // out from under them.
205        //
206        // This will also drop any futures which might use a `&Accessor` fields
207        // in their `Drop::drop` implementations, in which case they'll need to
208        // be called from with in the context of a `tls::set` closure.
209        #[cfg(feature = "component-model-async")]
210        if store.0.component_data().task_state.is_concurrent() {
211            ComponentStoreData::drop_fibers_and_futures(store.0);
212            store.0.clean_up_task_groups();
213        }
214        #[cfg(not(feature = "component-model-async"))]
215        let _ = store;
216    }
217
218    pub fn next_component_instance_id(&self) -> ComponentInstanceId {
219        self.instances.next_key()
220    }
221
222    #[cfg(feature = "component-model-async")]
223    pub(crate) fn drop_fibers_and_futures(store: &mut dyn VMStore) {
224        let mut fibers = Vec::new();
225        let mut futures = Vec::new();
226        store
227            .concurrent_state_mut_without_forcing_current_thread()
228            .take_fibers_and_futures(&mut fibers, &mut futures);
229
230        for mut fiber in fibers {
231            fiber.dispose(store);
232        }
233
234        crate::component::concurrent::tls::set(store, move || drop(futures));
235    }
236
237    #[cfg(feature = "component-model-async")]
238    pub(crate) fn assert_instance_states_empty(&mut self) {
239        for (_, instance) in self.instances.iter_mut() {
240            let Some(instance) = instance.as_mut() else {
241                continue;
242            };
243
244            assert!(instance.get_mut().instance_states().0.iter_mut().all(
245                |(_, state): (_, &mut InstanceState)| state.handle_table().is_empty()
246                    && state.concurrent_state().pending_is_empty()
247            ));
248        }
249    }
250
251    pub fn decrement_allocator_resources(&mut self, allocator: &dyn vm::InstanceAllocator) {
252        for _ in 0..self.num_component_instances {
253            allocator.decrement_component_instance_count();
254        }
255    }
256
257    #[cfg(all(feature = "component-model-async", feature = "gc"))]
258    pub fn task_state_mut(&mut self) -> &mut ComponentTaskState {
259        &mut self.task_state
260    }
261}
262
263/// A type used to represent an allocated `ComponentInstance` located within a
264/// store.
265///
266/// This type is held in various locations as a "safe index" into a store. This
267/// encapsulates a `StoreId` which owns the instance as well as the index within
268/// the store's list of which instance it's pointing to.
269///
270/// This type can notably be used to index into a `StoreOpaque` to project out
271/// the `ComponentInstance` that is associated with this id.
272#[repr(C)] // used by reference in the C API
273#[derive(Copy, Clone, Debug, PartialEq, Eq)]
274pub struct StoreComponentInstanceId {
275    store_id: StoreId,
276    instance: ComponentInstanceId,
277}
278
279impl StoreComponentInstanceId {
280    pub(crate) fn new(
281        store_id: StoreId,
282        instance: ComponentInstanceId,
283    ) -> StoreComponentInstanceId {
284        StoreComponentInstanceId { store_id, instance }
285    }
286
287    #[inline]
288    pub fn assert_belongs_to(&self, store: StoreId) {
289        self.store_id.assert_belongs_to(store)
290    }
291
292    #[inline]
293    pub(crate) fn store_id(&self) -> StoreId {
294        self.store_id
295    }
296
297    #[inline]
298    pub(crate) fn instance(&self) -> ComponentInstanceId {
299        self.instance
300    }
301
302    /// Looks up the `vm::ComponentInstance` within `store` that this id points
303    /// to.
304    ///
305    /// # Panics
306    ///
307    /// Panics if `self` does not belong to `store`.
308    pub(crate) fn get<'a>(&self, store: &'a StoreOpaque) -> &'a ComponentInstance {
309        self.assert_belongs_to(store.id());
310        store.component_instance(self.instance)
311    }
312
313    /// Mutable version of `get` above.
314    ///
315    /// # Panics
316    ///
317    /// Panics if `self` does not belong to `store`.
318    pub(crate) fn get_mut<'a>(&self, store: &'a mut StoreOpaque) -> Pin<&'a mut ComponentInstance> {
319        self.from_data_get_mut(store.store_data_mut())
320    }
321
322    /// Same as `get_mut`, but also returns the store's `HandleCounter`.
323    ///
324    /// # Panics
325    ///
326    /// Panics if `self` does not belong to `store`.
327    #[cfg(feature = "component-model-async")]
328    pub(crate) fn get_mut_and_handle_counter<'a>(
329        &self,
330        store: &'a mut StoreOpaque,
331    ) -> (Pin<&'a mut ComponentInstance>, &'a mut HandleCounter) {
332        let store = store.store_data_mut();
333        self.assert_belongs_to(store.id());
334        store.component_instance_and_handle_counter_mut(self.instance)
335    }
336
337    /// Return a mutable `ComponentInstance` and a `ModuleRegistry`
338    /// from the store.
339    ///
340    /// # Panics
341    ///
342    /// Panics if `self` does not belong to `store`.
343    #[cfg(feature = "component-model-async")]
344    pub(crate) fn get_mut_and_registry<'a>(
345        &self,
346        store: &'a mut StoreOpaque,
347    ) -> (
348        Pin<&'a mut ComponentInstance>,
349        &'a crate::module::ModuleRegistry,
350    ) {
351        let (store_data, registry) = store.store_data_mut_and_registry();
352        let instance = self.from_data_get_mut(store_data);
353        (instance, registry)
354    }
355
356    /// Same as `get_mut`, but borrows less of a store.
357    fn from_data_get_mut<'a>(&self, store: &'a mut StoreData) -> Pin<&'a mut ComponentInstance> {
358        self.assert_belongs_to(store.id());
359        store.component_instance_mut(self.instance)
360    }
361}
362
363impl StoreData {
364    pub(crate) fn push_component_instance(
365        &mut self,
366        data: OwnedComponentInstance,
367    ) -> Result<ComponentInstanceId, OutOfMemory> {
368        let expected = data.get().id();
369        let ret = self.components.instances.push(Some(data))?;
370        assert_eq!(expected, ret);
371        self.components.has_components = true;
372        Ok(ret)
373    }
374
375    pub(crate) fn component_instance(&self, id: ComponentInstanceId) -> &ComponentInstance {
376        self.components.instances[id].as_ref().unwrap().get()
377    }
378
379    pub(crate) fn component_instance_mut(
380        &mut self,
381        id: ComponentInstanceId,
382    ) -> Pin<&mut ComponentInstance> {
383        self.components.instances[id].as_mut().unwrap().get_mut()
384    }
385
386    #[cfg(feature = "component-model-async")]
387    pub(crate) fn component_instance_and_handle_counter_mut(
388        &mut self,
389        id: ComponentInstanceId,
390    ) -> (Pin<&mut ComponentInstance>, &mut HandleCounter) {
391        let data = &mut self.components;
392        (
393            data.instances[id].as_mut().unwrap().get_mut(),
394            &mut data.handle_counter,
395        )
396    }
397}
398
399impl StoreOpaque {
400    #[inline]
401    pub(crate) fn component_trapped(&self) -> bool {
402        self.store_data().components.component_trapped
403    }
404
405    pub(crate) fn set_trapped(&mut self) {
406        let component_data = &mut self.store_data_mut().components;
407        if component_data.has_components {
408            component_data.component_trapped = true;
409        }
410    }
411
412    /// Determine whether an instance may be entered from the host.
413    ///
414    /// We return `false` here only `self` has been poisoned due to a trap.
415    #[inline]
416    pub(crate) fn may_enter(&mut self) -> bool {
417        !self.component_trapped()
418    }
419
420    pub(crate) fn component_data(&self) -> &ComponentStoreData {
421        &self.store_data().components
422    }
423
424    pub(crate) fn component_data_mut(&mut self) -> &mut ComponentStoreData {
425        &mut self.store_data_mut().components
426    }
427
428    pub(crate) fn push_component_instance(&mut self, instance: Instance) {
429        // We don't actually need the instance itself right now, but it seems
430        // like something we will almost certainly eventually want to keep
431        // around, so force callers to provide it.
432        let _ = instance;
433
434        self.component_data_mut().num_component_instances += 1;
435    }
436
437    pub(crate) fn component_instance(&self, id: ComponentInstanceId) -> &ComponentInstance {
438        self.store_data().component_instance(id)
439    }
440
441    #[cfg(feature = "component-model-async")]
442    pub(crate) fn component_instance_mut(
443        &mut self,
444        id: ComponentInstanceId,
445    ) -> Pin<&mut ComponentInstance> {
446        self.store_data_mut().component_instance_mut(id)
447    }
448
449    #[cfg(feature = "component-model-async")]
450    pub(crate) fn concurrent_state_mut_without_forcing_current_thread(
451        &mut self,
452    ) -> &mut ConcurrentState {
453        debug_assert!(self.concurrency_support());
454        self.component_data_mut().task_state.concurrent_state_mut()
455    }
456
457    #[cfg(feature = "component-model-async")]
458    pub(crate) fn concurrent_state_mut_already_forced_current_thread(
459        &mut self,
460    ) -> &mut ConcurrentState {
461        debug_assert!(self.concurrency_support());
462        debug_assert!(
463            !self
464                .vm_store_context_mut()
465                .current_thread_mut()
466                .is_deferred()
467        );
468        self.concurrent_state_mut_without_forcing_current_thread()
469    }
470
471    #[cfg(feature = "component-model-async")]
472    pub(crate) fn concurrent_state_mut(&mut self) -> Result<&mut ConcurrentState> {
473        debug_assert!(self.concurrency_support());
474        self.current_thread()?;
475        Ok(self.component_data_mut().task_state.concurrent_state_mut())
476    }
477
478    #[inline]
479    #[cfg(feature = "component-model-async")]
480    pub(crate) fn concurrency_support(&self) -> bool {
481        let support = self.component_data().task_state.is_concurrent();
482        debug_assert_eq!(support, self.engine().tunables().concurrency_support);
483        support
484    }
485
486    pub(crate) fn lift_context_parts(
487        &mut self,
488        instance: Instance,
489    ) -> (
490        &mut ComponentTaskState,
491        &mut HandleTable,
492        &mut HandleCounter,
493        &mut HostResourceData,
494        Pin<&mut ComponentInstance>,
495    ) {
496        let instance = instance.id();
497        instance.assert_belongs_to(self.id());
498        let data = self.component_data_mut();
499        (
500            &mut data.task_state,
501            &mut data.component_host_table,
502            &mut data.handle_counter,
503            &mut data.host_resource_data,
504            data.instances[instance.instance]
505                .as_mut()
506                .unwrap()
507                .get_mut(),
508        )
509    }
510
511    pub(crate) fn component_resource_tables(
512        &mut self,
513        instance: Option<Instance>,
514    ) -> Result<vm::component::ResourceTables<'_>> {
515        Ok(self
516            .component_resource_tables_and_host_resource_data(instance)?
517            .0)
518    }
519
520    pub(crate) fn component_resource_tables_and_host_resource_data(
521        &mut self,
522        instance: Option<Instance>,
523    ) -> Result<(
524        vm::component::ResourceTables<'_>,
525        &mut crate::component::HostResourceData,
526    )> {
527        let current_scope = self.current_scope()?;
528        let store_id = self.id();
529        let (tables, host_resource_data, _) =
530            self.component_data_mut()
531                .resource_tables(store_id, instance, current_scope);
532        Ok((tables, host_resource_data))
533    }
534
535    /// Records a borrow of `index` within `instance` without forcing an
536    /// unforced `VMDeferredThread`, falling back to forcing it when that is
537    /// not possible.
538    ///
539    /// See `ResourceTables::resource_lift_borrow_lazy`.
540    pub(crate) fn resource_lift_borrow_maybe_lazy(
541        &mut self,
542        instance: Instance,
543        index: vm::component::TypedResourceIndex,
544    ) -> Result<u32> {
545        // NB: this check must happen before anything calls `current_scope()`,
546        // which is itself the call that forces the thread.
547        if self
548            .vm_store_context_mut()
549            .current_thread_mut()
550            .is_deferred()
551        {
552            let store_id = self.id();
553            let (vm_store_context, store_data) = self.vm_store_context_and_store_data_mut();
554            let (mut tables, _, lazy_lenders) =
555                store_data
556                    .components
557                    .resource_tables(store_id, Some(instance), None);
558            match tables.resource_lift_borrow_lazy(index, lazy_lenders, vm_store_context)? {
559                Some(rep) => return Ok(rep),
560                None => {
561                    // Fallthrough: the buffer is full and nothing was lent.
562                }
563            }
564        }
565
566        // The path for forcing the `VMDeferredThread`, or using an
567        // already-forced one.
568        self.component_resource_tables(Some(instance))?
569            .resource_lift_borrow(index)
570    }
571
572    /// Promotes the lazy lends in `start..end` into real lends owned by
573    /// `scope`; see `ResourceTables::promote_lazy_lends`.
574    #[cfg_attr(
575        not(feature = "component-model-async"),
576        expect(
577            dead_code,
578            reason = "deferred threads are only created with `component-model-async`, \
579                      so nothing promotes lazy lends without it"
580        )
581    )]
582    pub(crate) fn promote_lazy_lends(
583        &mut self,
584        instance: Instance,
585        scope: vm::VMScope,
586        start: usize,
587        end: usize,
588    ) -> Result<()> {
589        debug_assert!(start <= end && end <= *self.vm_store_context_mut().lazy_lenders_len_mut());
590        let store_id = self.id();
591        let (mut tables, _, lazy_lenders) =
592            self.component_data_mut()
593                .resource_tables(store_id, Some(instance), None);
594        tables.promote_lazy_lends(scope, &lazy_lenders[start..end])
595    }
596
597    pub(crate) fn enter_call_not_concurrent(&mut self) -> Result<()> {
598        let state = match &mut self.component_data_mut().task_state {
599            ComponentTaskState::NotConcurrent(state) => state,
600            #[cfg(feature = "component-model-async")]
601            ComponentTaskState::Concurrent(_) => unreachable!(),
602        };
603        state.scopes.push(CallContext::default())?;
604        Ok(())
605    }
606
607    pub(crate) fn exit_call_not_concurrent(&mut self) {
608        let state = match &mut self.component_data_mut().task_state {
609            ComponentTaskState::NotConcurrent(state) => state,
610            #[cfg(feature = "component-model-async")]
611            ComponentTaskState::Concurrent(_) => unreachable!(),
612        };
613        state.scopes.pop();
614    }
615
616    pub(crate) fn hostcall_fuel(&self) -> usize {
617        self.component_data().hostcall_fuel
618    }
619
620    pub(crate) fn set_hostcall_fuel(&mut self, fuel: usize) {
621        self.component_data_mut().hostcall_fuel = fuel;
622    }
623
624    pub(crate) fn max_component_handles(&self) -> usize {
625        self.component_data().handle_counter.limit()
626    }
627
628    pub(crate) fn set_max_component_handles(&mut self, limit: usize) {
629        self.component_data_mut().handle_counter.set_limit(limit);
630    }
631
632    #[cfg(feature = "component-model-async")]
633    fn concurrent_resource_table(&mut self) -> Option<&mut ResourceTable> {
634        if self.concurrency_support() {
635            Some(
636                self.concurrent_state_mut_without_forcing_current_thread()
637                    .table(),
638            )
639        } else {
640            None
641        }
642    }
643
644    pub(crate) fn current_scope_id_not_concurrent(&mut self) -> Result<Option<u32>> {
645        match &mut self.component_data_mut().task_state {
646            ComponentTaskState::NotConcurrent(state) => match state.scopes.len().checked_sub(1) {
647                Some(i) => Ok(Some(u32::try_from(i)?)),
648                None => Ok(None),
649            },
650            #[cfg(feature = "component-model-async")]
651            ComponentTaskState::Concurrent(_) => crate::bail_bug!("should not be reachable"),
652        }
653    }
654}
655
656impl<T> Store<T> {
657    /// Returns the amount of "hostcall fuel" used for guest-to-host component
658    /// calls.
659    ///
660    /// This is either the default amount if it hasn't been configured or
661    /// returns the last value passed to [`Store::set_hostcall_fuel`].
662    ///
663    /// See [`Store::set_hostcall_fuel`] `for more details.
664    pub fn hostcall_fuel(&self) -> usize {
665        self.as_context().0.hostcall_fuel()
666    }
667
668    /// Sets the amount of "hostcall fuel" used for guest-to-host component
669    /// calls.
670    ///
671    /// Whenever the guest calls the host it often wants to transfer some data
672    /// as well, such as strings or lists. This configured fuel value can be
673    /// used to limit the amount of data that the host allocates on behalf of
674    /// the guest. This is a DoS mitigation mechanism to prevent a malicious
675    /// guest from causing the host to allocate an unbounded amount of memory
676    /// for example.
677    ///
678    /// Fuel is considered distinct for each host call. The host is responsible
679    /// for ensuring it retains a proper amount of data between host calls if
680    /// applicable. The `fuel` provided here will be the initial value for each
681    /// time the guest calls the host.
682    ///
683    /// The `fuel` value here should roughly corresponds to the maximal number
684    /// of bytes that the guest may transfer to the host in one call.
685    ///
686    /// Note that data transferred from the host to the guest is not limited
687    /// because it's already resident on the host itself. Only data from the
688    /// guest to the host is limited.
689    ///
690    /// The default value for this is 128 MiB.
691    pub fn set_hostcall_fuel(&mut self, fuel: usize) {
692        self.as_context_mut().set_hostcall_fuel(fuel)
693    }
694
695    /// Returns the maximum number of component-model handle-table slots
696    /// permitted in this store.
697    ///
698    /// This is either the default amount if it hasn't been configured or
699    /// returns the last value passed to [`Store::set_max_component_handles`].
700    ///
701    /// See [`Store::set_max_component_handles`] for more details.
702    pub fn max_component_handles(&self) -> usize {
703        self.as_context().0.max_component_handles()
704    }
705
706    /// Sets the maximum number of component-model handle-table slots
707    /// permitted in this store.
708    ///
709    /// Operations such as `resource.new` let a guest allocate handles in
710    /// host-side tables without host code in the loop, across as many instances
711    /// as it likes. This store-wide limit caps that as a DoS mitigation;
712    /// allocating past it traps.
713    ///
714    /// The limit counts the slots allocated in each component instance's
715    /// handle table, which is what costs host memory. Dropping a handle (e.g.
716    /// via `resource.drop`) frees its slot for the next handle allocated in
717    /// the same instance, so steady-state churn is unaffected, but tables
718    /// don't shrink: a slot freed in one instance isn't returned for other
719    /// instances to use. Lowering the limit below the current count does not
720    /// trap existing handles, only blocks tables from growing.
721    ///
722    /// The default value for this is 100,000 handles.
723    pub fn set_max_component_handles(&mut self, limit: usize) {
724        self.as_context_mut().set_max_component_handles(limit)
725    }
726
727    /// Returns the underlying [`ResourceTable`] that the implementation of
728    /// concurrency in the component model is using.
729    ///
730    /// Returns `None` if [`Config::concurrency_support`] is disabled.
731    ///
732    /// Use [`ResourceTable::set_max_capacity`] on this table to bound component
733    /// async allocations, including guest-created streams, futures, and waitable
734    /// sets. Configure the capacity before instantiating components or starting
735    /// tasks: lowering it later does not evict entries or prevent reuse of
736    /// existing slots. This table is separate from any resource table maintained
737    /// by the embedder for host resources.
738    ///
739    /// [`ResourceTable::len`] returns the number of live entries in constant
740    /// time. Internal tasks, threads, and scratch waitable sets also occupy
741    /// entries; some are allocated lazily as execution requires them.
742    ///
743    /// [`Config::concurrency_support`]: crate::Config::concurrency_support
744    #[cfg(feature = "component-model-async")]
745    pub fn concurrent_resource_table(&mut self) -> Option<&mut ResourceTable> {
746        self.as_context_mut().0.concurrent_resource_table()
747    }
748}
749
750impl<T> StoreContextMut<'_, T> {
751    /// See [`Store::hostcall_fuel`].
752    pub fn hostcall_fuel(&self) -> usize {
753        self.0.hostcall_fuel()
754    }
755
756    /// See [`Store::set_hostcall_fuel`].
757    pub fn set_hostcall_fuel(&mut self, fuel: usize) {
758        self.0.set_hostcall_fuel(fuel)
759    }
760
761    /// See [`Store::max_component_handles`].
762    pub fn max_component_handles(&self) -> usize {
763        self.0.max_component_handles()
764    }
765
766    /// See [`Store::set_max_component_handles`].
767    pub fn set_max_component_handles(&mut self, limit: usize) {
768        self.0.set_max_component_handles(limit)
769    }
770
771    /// See [`Store::concurrent_resource_table`].
772    #[cfg(feature = "component-model-async")]
773    pub fn concurrent_resource_table(&mut self) -> Option<&mut ResourceTable> {
774        self.0.concurrent_resource_table()
775    }
776}
777
778#[derive(Default)]
779pub struct ComponentTasksNotConcurrent {
780    scopes: TryVec<CallContext>,
781}
782
783impl ComponentTaskState {
784    pub fn call_context(&mut self, id: VMScope) -> Result<&mut CallContext> {
785        match self {
786            ComponentTaskState::NotConcurrent(state) => match id.kind() {
787                ScopeKind::Id(id) => Ok(&mut state.scopes[id as usize]),
788                ScopeKind::HostId(_) => bail_bug!("non-concurrent scope cannot be a host ID"),
789            },
790            #[cfg(feature = "component-model-async")]
791            ComponentTaskState::Concurrent(state) => state.call_context(id),
792        }
793    }
794
795    pub(crate) fn materialize_current_scope(&mut self) -> Result<VMScope> {
796        match self {
797            ComponentTaskState::NotConcurrent(_) => {
798                bail_bug!("a non-concurrent scope cannot be deferred")
799            }
800            #[cfg(feature = "component-model-async")]
801            ComponentTaskState::Concurrent(state) => state.materialize_current_scope(),
802        }
803    }
804
805    pub(crate) fn deferred_host_call_context(&mut self) -> Option<&mut CallContext> {
806        match self {
807            ComponentTaskState::NotConcurrent(_) => None,
808            #[cfg(feature = "component-model-async")]
809            ComponentTaskState::Concurrent(state) => state.deferred_host_call_context(),
810        }
811    }
812
813    #[cfg(feature = "component-model-async")]
814    pub fn concurrent_state_mut(&mut self) -> &mut ConcurrentState {
815        match self {
816            ComponentTaskState::Concurrent(state) => state,
817            ComponentTaskState::NotConcurrent(_) => {
818                panic!("expected concurrent state to be present")
819            }
820        }
821    }
822
823    #[cfg(feature = "component-model-async")]
824    fn is_concurrent(&self) -> bool {
825        match self {
826            ComponentTaskState::Concurrent(_) => true,
827            ComponentTaskState::NotConcurrent(_) => false,
828        }
829    }
830}