wasmtime/runtime/component/
store.rs

1#[cfg(feature = "component-model-async")]
2use crate::runtime::vm::VMStore;
3use crate::runtime::vm::component::{ComponentInstance, OwnedComponentInstance};
4use crate::store::{StoreData, StoreId, StoreOpaque};
5use crate::{AsContext, AsContextMut, Store, StoreContextMut};
6#[cfg(feature = "component-model-async")]
7use alloc::vec::Vec;
8use core::pin::Pin;
9use wasmtime_environ::PrimaryMap;
10
11/// Default amount of fuel allowed for all guest-to-host calls in the component
12/// model.
13///
14/// This is the maximal amount of data which will be copied from the guest to
15/// the host by default. This is set to a very large value to avoid breaking
16/// existing embeddings for when this feature was backported.
17const DEFAULT_HOSTCALL_FUEL: usize = 2 << 30;
18
19pub struct ComponentStoreData {
20    instances: PrimaryMap<ComponentInstanceId, Option<OwnedComponentInstance>>,
21
22    /// Fuel to be used for each time the guest calls the host or transfers data
23    /// to the host.
24    ///
25    /// Caps the size of the allocations made on the host to this amount
26    /// effectively.
27    hostcall_fuel: usize,
28}
29
30impl Default for ComponentStoreData {
31    fn default() -> Self {
32        Self {
33            instances: PrimaryMap::default(),
34            hostcall_fuel: DEFAULT_HOSTCALL_FUEL,
35        }
36    }
37}
38
39#[derive(Copy, Clone, Debug, PartialEq, Eq)]
40pub struct ComponentInstanceId(u32);
41wasmtime_environ::entity_impl!(ComponentInstanceId);
42
43impl StoreData {
44    pub(crate) fn push_component_instance(
45        &mut self,
46        data: OwnedComponentInstance,
47    ) -> ComponentInstanceId {
48        let expected = data.get().id();
49        let ret = self.components.instances.push(Some(data));
50        assert_eq!(expected, ret);
51        ret
52    }
53}
54
55impl ComponentStoreData {
56    pub fn next_component_instance_id(&self) -> ComponentInstanceId {
57        self.instances.next_key()
58    }
59
60    #[cfg(feature = "component-model-async")]
61    pub(crate) fn drop_fibers_and_futures(store: &mut dyn VMStore) {
62        let mut fibers = Vec::new();
63        let mut futures = Vec::new();
64        store
65            .concurrent_state_mut()
66            .take_fibers_and_futures(&mut fibers, &mut futures);
67
68        for mut fiber in fibers {
69            fiber.dispose(store);
70        }
71
72        crate::component::concurrent::tls::set(store, move || drop(futures));
73    }
74
75    #[cfg(feature = "component-model-async")]
76    pub(crate) fn assert_instance_states_empty(&mut self) {
77        for (_, instance) in self.instances.iter_mut() {
78            let Some(instance) = instance.as_mut() else {
79                continue;
80            };
81
82            assert!(
83                instance
84                    .get_mut()
85                    .instance_states()
86                    .0
87                    .iter_mut()
88                    .all(|(_, state)| state.handle_table().is_empty()
89                        && state.concurrent_state().pending_is_empty())
90            );
91        }
92    }
93}
94
95impl StoreData {
96    pub(crate) fn component_instance(&self, id: ComponentInstanceId) -> &ComponentInstance {
97        self.components.instances[id].as_ref().unwrap().get()
98    }
99
100    pub(crate) fn component_instance_mut(
101        &mut self,
102        id: ComponentInstanceId,
103    ) -> Pin<&mut ComponentInstance> {
104        self.components.instances[id].as_mut().unwrap().get_mut()
105    }
106}
107
108impl StoreOpaque {
109    pub(crate) fn component_instance(&self, id: ComponentInstanceId) -> &ComponentInstance {
110        self.store_data().component_instance(id)
111    }
112
113    pub(crate) fn hostcall_fuel(&self) -> usize {
114        self.store_data().components.hostcall_fuel
115    }
116
117    pub(crate) fn set_hostcall_fuel(&mut self, fuel: usize) {
118        self.store_data_mut().components.hostcall_fuel = fuel;
119    }
120}
121
122/// A type used to represent an allocated `ComponentInstance` located within a
123/// store.
124///
125/// This type is held in various locations as a "safe index" into a store. This
126/// encapsulates a `StoreId` which owns the instance as well as the index within
127/// the store's list of which instance it's pointing to.
128///
129/// This type can notably be used to index into a `StoreOpaque` to project out
130/// the `ComponentInstance` that is associated with this id.
131#[repr(C)] // used by reference in the C API
132#[derive(Copy, Clone, Debug, PartialEq, Eq)]
133pub struct StoreComponentInstanceId {
134    store_id: StoreId,
135    instance: ComponentInstanceId,
136}
137
138impl StoreComponentInstanceId {
139    pub(crate) fn new(
140        store_id: StoreId,
141        instance: ComponentInstanceId,
142    ) -> StoreComponentInstanceId {
143        StoreComponentInstanceId { store_id, instance }
144    }
145
146    #[inline]
147    pub fn assert_belongs_to(&self, store: StoreId) {
148        self.store_id.assert_belongs_to(store)
149    }
150
151    #[inline]
152    pub(crate) fn store_id(&self) -> StoreId {
153        self.store_id
154    }
155
156    #[inline]
157    pub(crate) fn instance(&self) -> ComponentInstanceId {
158        self.instance
159    }
160
161    /// Looks up the `vm::ComponentInstance` within `store` that this id points
162    /// to.
163    ///
164    /// # Panics
165    ///
166    /// Panics if `self` does not belong to `store`.
167    pub(crate) fn get<'a>(&self, store: &'a StoreOpaque) -> &'a ComponentInstance {
168        self.assert_belongs_to(store.id());
169        store.component_instance(self.instance)
170    }
171
172    /// Mutable version of `get` above.
173    ///
174    /// # Panics
175    ///
176    /// Panics if `self` does not belong to `store`.
177    pub(crate) fn get_mut<'a>(&self, store: &'a mut StoreOpaque) -> Pin<&'a mut ComponentInstance> {
178        self.from_data_get_mut(store.store_data_mut())
179    }
180
181    /// Return a mutable `ComponentInstance` and a `ModuleRegistry`
182    /// from the store.
183    ///
184    /// # Panics
185    ///
186    /// Panics if `self` does not belong to `store`.
187    #[cfg(feature = "component-model-async")]
188    pub(crate) fn get_mut_and_registry<'a>(
189        &self,
190        store: &'a mut StoreOpaque,
191    ) -> (
192        Pin<&'a mut ComponentInstance>,
193        &'a crate::module::ModuleRegistry,
194    ) {
195        let (store_data, registry) = store.store_data_mut_and_registry();
196        let instance = self.from_data_get_mut(store_data);
197        (instance, registry)
198    }
199
200    /// Same as `get_mut`, but borrows less of a store.
201    pub(crate) fn from_data_get_mut<'a>(
202        &self,
203        store: &'a mut StoreData,
204    ) -> Pin<&'a mut ComponentInstance> {
205        self.assert_belongs_to(store.id());
206        store.component_instance_mut(self.instance)
207    }
208}
209
210impl<T> Store<T> {
211    /// Returns the amount of "hostcall fuel" used for guest-to-host component
212    /// calls.
213    ///
214    /// This is either the default amount if it hasn't been configured or
215    /// returns the last value passed to [`Store::set_hostcall_fuel`].
216    ///
217    /// See [`Store::set_hostcall_fuel`] `for more details.
218    pub fn hostcall_fuel(&self) -> usize {
219        self.as_context().0.hostcall_fuel()
220    }
221
222    /// Sets the amount of "hostcall fuel" used for guest-to-host component
223    /// calls.
224    ///
225    /// Whenever the guest calls the host it often wants to transfer some data
226    /// as well, such as strings or lists. This configured fuel value can be
227    /// used to limit the amount of data that the host allocates on behalf of
228    /// the guest. This is a DoS mitigation mechanism to prevent a malicious
229    /// guest from causing the host to allocate an unbounded amount of memory
230    /// for example.
231    ///
232    /// Fuel is considered distinct for each host call. The host is responsible
233    /// for ensuring it retains a proper amount of data between host calls if
234    /// applicable. The `fuel` provided here will be the initial value for each
235    /// time the guest calls the host.
236    ///
237    /// The `fuel` value here should roughly corresponds to the maximal number
238    /// of bytes that the guest may transfer to the host in one call.
239    ///
240    /// Note that data transferred from the host to the guest is not limited
241    /// because it's already resident on the host itself. Only data from the
242    /// guest to the host is limited.
243    ///
244    /// The default value for this is 128 MiB.
245    pub fn set_hostcall_fuel(&mut self, fuel: usize) {
246        self.as_context_mut().set_hostcall_fuel(fuel)
247    }
248}
249
250impl<T> StoreContextMut<'_, T> {
251    /// See [`Store::hostcall_fuel`].
252    pub fn hostcall_fuel(&self) -> usize {
253        self.0.hostcall_fuel()
254    }
255
256    /// See [`Store::set_hostcall_fuel`].
257    pub fn set_hostcall_fuel(&mut self, fuel: usize) {
258        self.0.set_hostcall_fuel(fuel)
259    }
260}