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