Skip to main content

wasmtime/runtime/component/
store.rs

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