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