1 use crate::StoreContextMut; 2 #[cfg(feature = "component-model-async")] 3 use crate::component::concurrent::ConcurrentState; 4 use crate::component::matching::InstanceType; 5 use crate::component::resources::{HostResourceData, HostResourceIndex, HostResourceTables}; 6 use crate::component::store::ComponentTaskState; 7 use crate::component::{Instance, ResourceType, RuntimeInstance}; 8 use crate::prelude::*; 9 use crate::runtime::vm::VMFuncRef; 10 use crate::runtime::vm::component::{ComponentInstance, HandleTable, ResourceTables}; 11 use crate::store::{StoreId, StoreOpaque}; 12 use alloc::sync::Arc; 13 use core::pin::Pin; 14 use core::ptr::NonNull; 15 use wasmtime_environ::component::{ 16 CanonicalOptions, CanonicalOptionsDataModel, ComponentTypes, OptionsIndex, 17 TypeResourceTableIndex, 18 }; 19 20 /// A helper structure which is a "package" of the context used during lowering 21 /// values into a component (or storing them into memory). 22 /// 23 /// This type is used by the `Lower` trait extensively and contains any 24 /// contextual information necessary related to the context in which the 25 /// lowering is happening. 26 #[doc(hidden)] 27 pub struct LowerContext<'a, T: 'static> { 28 /// Lowering may involve invoking memory allocation functions so part of the 29 /// context here is carrying access to the entire store that wasm is 30 /// executing within. This store serves as proof-of-ability to actually 31 /// execute wasm safely. 32 pub store: StoreContextMut<'a, T>, 33 34 /// Lowering always happens into a function that's been `canon lift`'d or 35 /// `canon lower`'d, both of which specify a set of options for the 36 /// canonical ABI. For example details like string encoding are contained 37 /// here along with which memory pointers are relative to or what the memory 38 /// allocation function is. 39 options: OptionsIndex, 40 41 /// Lowering happens within the context of a component instance and this 42 /// field stores the type information of that component instance. This is 43 /// used for type lookups and general type queries during the 44 /// lifting/lowering process. 45 pub types: &'a ComponentTypes, 46 47 /// Index of the component instance that's being lowered into. 48 instance: Instance, 49 50 /// Whether to allow `options.realloc` to be used when lowering. 51 allow_realloc: bool, 52 } 53 54 #[doc(hidden)] 55 impl<'a, T: 'static> LowerContext<'a, T> { 56 /// Creates a new lowering context from the specified parameters. 57 pub fn new( 58 store: StoreContextMut<'a, T>, 59 options: OptionsIndex, 60 instance: Instance, 61 ) -> LowerContext<'a, T> { 62 // Debug-assert that if we can't block that blocking is indeed allowed. 63 // This'll catch when this is accidentally created outside of a fiber 64 // when we need to be on a fiber. 65 if cfg!(debug_assertions) && !store.0.can_block() { 66 store.0.validate_sync_call().unwrap(); 67 } 68 let (component, store) = instance.component_and_store_mut(store.0); 69 LowerContext { 70 store: StoreContextMut(store), 71 options, 72 types: component.types(), 73 instance, 74 allow_realloc: true, 75 } 76 } 77 78 /// Like `new`, except disallows use of `options.realloc`. 79 /// 80 /// The returned object will panic if its `realloc` method is called. 81 /// 82 /// This is meant for use when lowering "flat" values (i.e. values which 83 /// require no allocations) into already-allocated memory or into stack 84 /// slots, in which case the lowering may safely be done outside of a fiber 85 /// since there is no need to make any guest calls. 86 #[cfg(feature = "component-model-async")] 87 pub(crate) fn new_without_realloc( 88 store: StoreContextMut<'a, T>, 89 options: OptionsIndex, 90 instance: Instance, 91 ) -> LowerContext<'a, T> { 92 let (component, store) = instance.component_and_store_mut(store.0); 93 LowerContext { 94 store: StoreContextMut(store), 95 options, 96 types: component.types(), 97 instance, 98 allow_realloc: false, 99 } 100 } 101 102 /// Returns the `&ComponentInstance` that's being lowered into. 103 pub fn instance(&self) -> &ComponentInstance { 104 self.instance.id().get(self.store.0) 105 } 106 107 /// Returns the `&mut ComponentInstance` that's being lowered into. 108 pub fn instance_mut(&mut self) -> Pin<&mut ComponentInstance> { 109 self.instance.id().get_mut(self.store.0) 110 } 111 112 /// Returns the canonical options that are being used during lifting. 113 pub fn options(&self) -> &CanonicalOptions { 114 &self.instance().component().env_component().options[self.options] 115 } 116 117 /// Returns a view into memory as a mutable slice of bytes. 118 /// 119 /// # Panics 120 /// 121 /// This will panic if memory has not been configured for this lowering 122 /// (e.g. it wasn't present during the specification of canonical options). 123 pub fn as_slice_mut(&mut self) -> &mut [u8] { 124 self.instance.options_memory_mut(self.store.0, self.options) 125 } 126 127 /// Invokes the memory allocation function (which is style after `realloc`) 128 /// with the specified parameters. 129 /// 130 /// # Panics 131 /// 132 /// This will panic if realloc hasn't been configured for this lowering via 133 /// its canonical options. 134 pub fn realloc( 135 &mut self, 136 old: usize, 137 old_size: usize, 138 old_align: u32, 139 new_size: usize, 140 ) -> Result<usize> { 141 assert!(self.allow_realloc); 142 143 let (component, store) = self.instance.component_and_store_mut(self.store.0); 144 let instance = self.instance.id().get(store); 145 let options = &component.env_component().options[self.options]; 146 let realloc_ty = component.realloc_func_ty(); 147 let realloc = match options.data_model { 148 CanonicalOptionsDataModel::Gc {} => unreachable!(), 149 CanonicalOptionsDataModel::LinearMemory(m) => m.realloc.unwrap(), 150 }; 151 let realloc = instance.runtime_realloc(realloc); 152 153 let params = ( 154 u32::try_from(old)?, 155 u32::try_from(old_size)?, 156 old_align, 157 u32::try_from(new_size)?, 158 ); 159 160 type ReallocFunc = crate::TypedFunc<(u32, u32, u32, u32), u32>; 161 162 // Invoke the wasm malloc function using its raw and statically known 163 // signature. 164 let result = unsafe { 165 ReallocFunc::call_raw(&mut StoreContextMut(store), &realloc_ty, realloc, params)? 166 }; 167 168 if result % old_align != 0 { 169 bail!("realloc return: result not aligned"); 170 } 171 let result = usize::try_from(result)?; 172 173 if self 174 .as_slice_mut() 175 .get_mut(result..) 176 .and_then(|s| s.get_mut(..new_size)) 177 .is_none() 178 { 179 bail!("realloc return: beyond end of memory") 180 } 181 182 Ok(result) 183 } 184 185 /// Returns a fixed mutable slice of memory `N` bytes large starting at 186 /// offset `N`, panicking on out-of-bounds. 187 /// 188 /// It should be previously verified that `offset` is in-bounds via 189 /// bounds-checks. 190 /// 191 /// # Panics 192 /// 193 /// This will panic if memory has not been configured for this lowering 194 /// (e.g. it wasn't present during the specification of canonical options). 195 pub fn get<const N: usize>(&mut self, offset: usize) -> &mut [u8; N] { 196 // FIXME: this bounds check shouldn't actually be necessary, all 197 // callers of `ComponentType::store` have already performed a bounds 198 // check so we're guaranteed that `offset..offset+N` is in-bounds. That 199 // being said we at least should do bounds checks in debug mode and 200 // it's not clear to me how to easily structure this so that it's 201 // "statically obvious" the bounds check isn't necessary. 202 // 203 // For now I figure we can leave in this bounds check and if it becomes 204 // an issue we can optimize further later, probably with judicious use 205 // of `unsafe`. 206 self.as_slice_mut()[offset..].first_chunk_mut().unwrap() 207 } 208 209 /// Lowers an `own` resource into the guest, converting the `rep` specified 210 /// into a guest-local index. 211 /// 212 /// The `ty` provided is which table to put this into. 213 pub fn guest_resource_lower_own( 214 &mut self, 215 ty: TypeResourceTableIndex, 216 rep: u32, 217 ) -> Result<u32> { 218 self.resource_tables().guest_resource_lower_own(rep, ty) 219 } 220 221 /// Lowers a `borrow` resource into the guest, converting the `rep` to a 222 /// guest-local index in the `ty` table specified. 223 pub fn guest_resource_lower_borrow( 224 &mut self, 225 ty: TypeResourceTableIndex, 226 rep: u32, 227 ) -> Result<u32> { 228 // Implement `lower_borrow`'s special case here where if a borrow is 229 // inserted into a table owned by the instance which implemented the 230 // original resource then no borrow tracking is employed and instead the 231 // `rep` is returned "raw". 232 // 233 // This check is performed by comparing the owning instance of `ty` 234 // against the owning instance of the resource that `ty` is working 235 // with. 236 if self.instance().resource_owned_by_own_instance(ty) { 237 return Ok(rep); 238 } 239 self.resource_tables().guest_resource_lower_borrow(rep, ty) 240 } 241 242 /// Lifts a host-owned `own` resource at the `idx` specified into the 243 /// representation of that resource. 244 pub fn host_resource_lift_own(&mut self, idx: HostResourceIndex) -> Result<u32> { 245 self.resource_tables().host_resource_lift_own(idx) 246 } 247 248 /// Lifts a host-owned `borrow` resource at the `idx` specified into the 249 /// representation of that resource. 250 pub fn host_resource_lift_borrow(&mut self, idx: HostResourceIndex) -> Result<u32> { 251 self.resource_tables().host_resource_lift_borrow(idx) 252 } 253 254 /// Lowers a resource into the host-owned table, returning the index it was 255 /// inserted at. 256 /// 257 /// Note that this is a special case for `Resource<T>`. Most of the time a 258 /// host value shouldn't be lowered with a lowering context. 259 pub fn host_resource_lower_own( 260 &mut self, 261 rep: u32, 262 dtor: Option<NonNull<VMFuncRef>>, 263 instance: Option<RuntimeInstance>, 264 ) -> Result<HostResourceIndex> { 265 self.resource_tables() 266 .host_resource_lower_own(rep, dtor, instance) 267 } 268 269 /// Returns the underlying resource type for the `ty` table specified. 270 pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType { 271 self.instance_type().resource_type(ty) 272 } 273 274 /// Returns the instance type information corresponding to the instance that 275 /// this context is lowering into. 276 pub fn instance_type(&self) -> InstanceType<'_> { 277 InstanceType::new(self.instance()) 278 } 279 280 fn resource_tables(&mut self) -> HostResourceTables<'_> { 281 let (tables, data) = self 282 .store 283 .0 284 .component_resource_tables_and_host_resource_data(Some(self.instance)); 285 HostResourceTables::from_parts(tables, data) 286 } 287 288 /// See [`HostResourceTables::validate_scope_exit`]. 289 #[inline] 290 pub fn validate_scope_exit(&mut self) -> Result<()> { 291 self.resource_tables().validate_scope_exit() 292 } 293 } 294 295 /// Contextual information used when lifting a type from a component into the 296 /// host. 297 /// 298 /// This structure is the analogue of `LowerContext` except used during lifting 299 /// operations (or loading from memory). 300 #[doc(hidden)] 301 pub struct LiftContext<'a> { 302 store_id: StoreId, 303 /// Like lowering, lifting always has options configured. 304 options: OptionsIndex, 305 306 /// Instance type information, like with lowering. 307 pub types: &'a Arc<ComponentTypes>, 308 309 memory: &'a [u8], 310 311 instance: Pin<&'a mut ComponentInstance>, 312 instance_handle: Instance, 313 314 host_table: &'a mut HandleTable, 315 host_resource_data: &'a mut HostResourceData, 316 317 task_state: &'a mut ComponentTaskState, 318 319 /// Remaining fuel for this hostcall/lift operation. 320 /// 321 /// This is decremented for strings/lists, for example, to cap the size of 322 /// data the host allocates on behalf of the guest. 323 hostcall_fuel: usize, 324 } 325 326 #[doc(hidden)] 327 impl<'a> LiftContext<'a> { 328 /// Creates a new lifting context given the provided context. 329 #[inline] 330 pub fn new( 331 store: &'a mut StoreOpaque, 332 options: OptionsIndex, 333 instance_handle: Instance, 334 ) -> LiftContext<'a> { 335 let store_id = store.id(); 336 let hostcall_fuel = store.hostcall_fuel(); 337 // From `&mut StoreOpaque` provided the goal here is to project out 338 // three different disjoint fields owned by the store: memory, 339 // `CallContexts`, and `HandleTable`. There's no native API for that 340 // so it's hacked around a bit. This unsafe pointer cast could be fixed 341 // with more methods in more places, but it doesn't seem worth doing it 342 // at this time. 343 let memory = 344 instance_handle.options_memory(unsafe { &*(store as *const StoreOpaque) }, options); 345 let (task_state, host_table, host_resource_data, instance) = 346 store.lift_context_parts(instance_handle); 347 let (component, instance) = instance.component_and_self(); 348 349 LiftContext { 350 store_id, 351 memory, 352 options, 353 types: component.types(), 354 instance, 355 instance_handle, 356 task_state, 357 host_table, 358 host_resource_data, 359 hostcall_fuel, 360 } 361 } 362 363 /// Returns the canonical options that are being used during lifting. 364 pub fn options(&self) -> &CanonicalOptions { 365 &self.instance.component().env_component().options[self.options] 366 } 367 368 /// Returns the `OptionsIndex` being used during lifting. 369 pub fn options_index(&self) -> OptionsIndex { 370 self.options 371 } 372 373 /// Returns the entire contents of linear memory for this set of lifting 374 /// options. 375 /// 376 /// # Panics 377 /// 378 /// This will panic if memory has not been configured for this lifting 379 /// operation. 380 pub fn memory(&self) -> &'a [u8] { 381 self.memory 382 } 383 384 /// Returns an identifier for the store from which this `LiftContext` was 385 /// created. 386 pub fn store_id(&self) -> StoreId { 387 self.store_id 388 } 389 390 /// Returns the component instance that is being lifted from. 391 pub fn instance_mut(&mut self) -> Pin<&mut ComponentInstance> { 392 self.instance.as_mut() 393 } 394 /// Returns the component instance that is being lifted from. 395 pub fn instance_handle(&self) -> Instance { 396 self.instance_handle 397 } 398 399 #[cfg(feature = "component-model-async")] 400 pub(crate) fn concurrent_state_mut(&mut self) -> &mut ConcurrentState { 401 self.task_state.concurrent_state_mut() 402 } 403 404 /// Lifts an `own` resource from the guest at the `idx` specified into its 405 /// representation. 406 /// 407 /// Additionally returns a destructor/instance flags to go along with the 408 /// representation so the host knows how to destroy this resource. 409 pub fn guest_resource_lift_own( 410 &mut self, 411 ty: TypeResourceTableIndex, 412 idx: u32, 413 ) -> Result<(u32, Option<NonNull<VMFuncRef>>, Option<RuntimeInstance>)> { 414 let idx = self.resource_tables().guest_resource_lift_own(idx, ty)?; 415 let (dtor, instance) = self.instance.dtor_and_instance(ty); 416 Ok((idx, dtor, instance)) 417 } 418 419 /// Lifts a `borrow` resource from the guest at the `idx` specified. 420 pub fn guest_resource_lift_borrow( 421 &mut self, 422 ty: TypeResourceTableIndex, 423 idx: u32, 424 ) -> Result<u32> { 425 self.resource_tables().guest_resource_lift_borrow(idx, ty) 426 } 427 428 /// Lowers a resource into the host-owned table, returning the index it was 429 /// inserted at. 430 pub fn host_resource_lower_own( 431 &mut self, 432 rep: u32, 433 dtor: Option<NonNull<VMFuncRef>>, 434 instance: Option<RuntimeInstance>, 435 ) -> Result<HostResourceIndex> { 436 self.resource_tables() 437 .host_resource_lower_own(rep, dtor, instance) 438 } 439 440 /// Lowers a resource into the host-owned table, returning the index it was 441 /// inserted at. 442 pub fn host_resource_lower_borrow(&mut self, rep: u32) -> Result<HostResourceIndex> { 443 self.resource_tables().host_resource_lower_borrow(rep) 444 } 445 446 /// Returns the underlying type of the resource table specified by `ty`. 447 pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType { 448 self.instance_type().resource_type(ty) 449 } 450 451 /// Returns instance type information for the component instance that is 452 /// being lifted from. 453 pub fn instance_type(&self) -> InstanceType<'_> { 454 InstanceType::new(&self.instance) 455 } 456 457 fn resource_tables(&mut self) -> HostResourceTables<'_> { 458 HostResourceTables::from_parts( 459 ResourceTables { 460 host_table: self.host_table, 461 task_state: self.task_state, 462 guest: Some(self.instance.as_mut().instance_states()), 463 }, 464 self.host_resource_data, 465 ) 466 } 467 468 /// See [`HostResourceTables::validate_scope_exit`]. 469 #[inline] 470 pub fn validate_scope_exit(&mut self) -> Result<()> { 471 self.resource_tables().validate_scope_exit() 472 } 473 474 /// Consumes `amt` units of fuel, typically a number of bytes, from this 475 /// context. 476 /// 477 /// Returns an error if the fuel is exhausted which will cause a trap in the 478 /// guest. Note that this is distinct from Wasm's fuel, this is just for 479 /// keeping track of data flowing from the guest to the host. 480 pub fn consume_fuel(&mut self, amt: usize) -> Result<()> { 481 match self.hostcall_fuel.checked_sub(amt) { 482 Some(new) => self.hostcall_fuel = new, 483 None => bail!( 484 "too much data is being copied between the host and the guest: \ 485 fuel allocated for hostcalls has been exhausted" 486 ), 487 } 488 Ok(()) 489 } 490 } 491