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