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