1 use crate::component::concurrent::ConcurrentState; 2 use crate::component::matching::InstanceType; 3 use crate::component::resources::{HostResourceData, HostResourceIndex, HostResourceTables}; 4 use crate::component::{Instance, ResourceType}; 5 use crate::prelude::*; 6 use crate::runtime::vm::component::{ 7 CallContexts, ComponentInstance, HandleTable, InstanceFlags, ResourceTables, 8 }; 9 use crate::runtime::vm::{VMFuncRef, VMMemoryDefinition}; 10 use crate::store::{StoreId, StoreOpaque}; 11 use crate::{FuncType, StoreContextMut}; 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, StringEncoding, 17 TypeResourceTableIndex, 18 }; 19 20 /// Runtime representation of canonical ABI options in the component model. 21 /// 22 /// This structure packages up the runtime representation of each option from 23 /// memories to reallocs to string encodings. Note that this is a "standalone" 24 /// structure which has raw pointers internally. This allows it to be created 25 /// out of thin air for a host function import, for example. The `store_id` 26 /// field, however, is what is used to pair this set of options with a store 27 /// reference to actually use the pointers. 28 #[derive(Copy, Clone)] 29 pub struct Options { 30 /// The store from which this options originated. 31 store_id: StoreId, 32 33 /// An optional pointer for the memory that this set of options is referring 34 /// to. This option is not required to be specified in the canonical ABI 35 /// hence the `Option`. 36 /// 37 /// Note that this pointer cannot be safely dereferenced unless a store, 38 /// verified with `self.store_id`, has the appropriate borrow available. 39 memory: Option<NonNull<VMMemoryDefinition>>, 40 41 /// Similar to `memory` but corresponds to the `canonical_abi_realloc` 42 /// function. 43 /// 44 /// Safely using this pointer has the same restrictions as `memory` above. 45 realloc: Option<NonNull<VMFuncRef>>, 46 47 /// The encoding used for strings, if found. 48 /// 49 /// This defaults to utf-8 but can be changed if necessary. 50 string_encoding: StringEncoding, 51 52 /// Whether or not this the async option was set when lowering. 53 async_: bool, 54 55 #[cfg(feature = "component-model-async")] 56 callback: Option<NonNull<VMFuncRef>>, 57 } 58 59 // The `Options` structure stores raw pointers but they're never used unless a 60 // `Store` is available so this should be threadsafe and largely inherit the 61 // thread-safety story of `Store<T>` itself. 62 unsafe impl Send for Options {} 63 unsafe impl Sync for Options {} 64 65 impl Options { 66 // FIXME(#4311): prevent a ctor where the memory is memory64 67 68 /// Creates a new [`Options`] from the given [`OptionsIndex`] belonging to 69 /// the specified [`Instance`] 70 /// 71 /// # Panics 72 /// 73 /// Panics if `instance` is not owned by `store` or if `index` is not valid 74 /// for `instance`'s component. 75 pub fn new_index(store: &StoreOpaque, instance: Instance, index: OptionsIndex) -> Options { 76 let instance = instance.id().get(store); 77 let CanonicalOptions { 78 string_encoding, 79 async_, 80 callback, 81 ref data_model, 82 .. 83 } = instance.component().env_component().options[index]; 84 let (memory, realloc) = match data_model { 85 CanonicalOptionsDataModel::Gc { .. } => (None, None), 86 CanonicalOptionsDataModel::LinearMemory(o) => (o.memory, o.realloc), 87 }; 88 let memory = memory.map(|i| NonNull::new(instance.runtime_memory(i)).unwrap()); 89 let realloc = realloc.map(|i| instance.runtime_realloc(i)); 90 let callback = callback.map(|i| instance.runtime_callback(i)); 91 let _ = callback; 92 93 Options { 94 store_id: store.id(), 95 memory, 96 realloc, 97 string_encoding, 98 async_, 99 #[cfg(feature = "component-model-async")] 100 callback, 101 } 102 } 103 104 fn realloc<'a, T>( 105 &self, 106 store: &'a mut StoreContextMut<'_, T>, 107 realloc_ty: &FuncType, 108 old: usize, 109 old_size: usize, 110 old_align: u32, 111 new_size: usize, 112 ) -> Result<(&'a mut [u8], usize)> { 113 self.store_id.assert_belongs_to(store.0.id()); 114 115 let realloc = self.realloc.unwrap(); 116 117 let params = ( 118 u32::try_from(old)?, 119 u32::try_from(old_size)?, 120 old_align, 121 u32::try_from(new_size)?, 122 ); 123 124 type ReallocFunc = crate::TypedFunc<(u32, u32, u32, u32), u32>; 125 126 // Invoke the wasm malloc function using its raw and statically known 127 // signature. 128 let result = unsafe { ReallocFunc::call_raw(store, realloc_ty, realloc, params)? }; 129 130 if result % old_align != 0 { 131 bail!("realloc return: result not aligned"); 132 } 133 let result = usize::try_from(result)?; 134 135 let memory = self.memory_mut(store.0); 136 137 let result_slice = match memory.get_mut(result..).and_then(|s| s.get_mut(..new_size)) { 138 Some(end) => end, 139 None => bail!("realloc return: beyond end of memory"), 140 }; 141 142 Ok((result_slice, result)) 143 } 144 145 /// Asserts that this function has an associated memory attached to it and 146 /// then returns the slice of memory tied to the lifetime of the provided 147 /// store. 148 pub fn memory<'a>(&self, store: &'a StoreOpaque) -> &'a [u8] { 149 self.store_id.assert_belongs_to(store.id()); 150 151 // The unsafety here is intended to be encapsulated by the two 152 // preceding assertions. Namely we assert that the `store` is the same 153 // as the original store of this `Options`, meaning that we safely have 154 // either a shared reference or a mutable reference (as below) which 155 // means it's safe to view the memory (aka it's not a different store 156 // where our original store is on some other thread or something like 157 // that). 158 // 159 // Additionally the memory itself is asserted to be present as memory 160 // is an optional configuration in canonical ABI options. 161 unsafe { 162 let memory = self.memory.unwrap().as_ref(); 163 core::slice::from_raw_parts(memory.base.as_ptr(), memory.current_length()) 164 } 165 } 166 167 /// Same as above, just `_mut` 168 pub fn memory_mut<'a>(&self, store: &'a mut StoreOpaque) -> &'a mut [u8] { 169 self.store_id.assert_belongs_to(store.id()); 170 171 // See comments in `memory` about the unsafety 172 unsafe { 173 let memory = self.memory.unwrap().as_ref(); 174 core::slice::from_raw_parts_mut(memory.base.as_ptr(), memory.current_length()) 175 } 176 } 177 178 /// Returns the underlying encoding used for strings in this 179 /// lifting/lowering. 180 pub fn string_encoding(&self) -> StringEncoding { 181 self.string_encoding 182 } 183 184 /// Returns the id of the store that this `Options` is connected to. 185 pub fn store_id(&self) -> StoreId { 186 self.store_id 187 } 188 189 /// Returns whether this lifting or lowering uses the async ABI. 190 pub fn async_(&self) -> bool { 191 self.async_ 192 } 193 194 #[cfg(feature = "component-model-async")] 195 pub(crate) fn callback(&self) -> Option<NonNull<VMFuncRef>> { 196 self.callback 197 } 198 199 #[cfg(feature = "component-model-async")] 200 pub(crate) fn memory_raw(&self) -> Option<NonNull<VMMemoryDefinition>> { 201 self.memory 202 } 203 } 204 205 /// A helper structure which is a "package" of the context used during lowering 206 /// values into a component (or storing them into memory). 207 /// 208 /// This type is used by the `Lower` trait extensively and contains any 209 /// contextual information necessary related to the context in which the 210 /// lowering is happening. 211 #[doc(hidden)] 212 pub struct LowerContext<'a, T: 'static> { 213 /// Lowering may involve invoking memory allocation functions so part of the 214 /// context here is carrying access to the entire store that wasm is 215 /// executing within. This store serves as proof-of-ability to actually 216 /// execute wasm safely. 217 pub store: StoreContextMut<'a, T>, 218 219 /// Lowering always happens into a function that's been `canon lift`'d or 220 /// `canon lower`'d, both of which specify a set of options for the 221 /// canonical ABI. For example details like string encoding are contained 222 /// here along with which memory pointers are relative to or what the memory 223 /// allocation function is. 224 pub options: &'a Options, 225 226 /// Lowering happens within the context of a component instance and this 227 /// field stores the type information of that component instance. This is 228 /// used for type lookups and general type queries during the 229 /// lifting/lowering process. 230 pub types: &'a ComponentTypes, 231 232 /// Index of the component instance that's being lowered into. 233 instance: Instance, 234 235 /// Whether to allow `options.realloc` to be used when lowering. 236 allow_realloc: bool, 237 } 238 239 #[doc(hidden)] 240 impl<'a, T: 'static> LowerContext<'a, T> { 241 /// Creates a new lowering context from the specified parameters. 242 pub fn new( 243 store: StoreContextMut<'a, T>, 244 options: &'a Options, 245 types: &'a ComponentTypes, 246 instance: Instance, 247 ) -> LowerContext<'a, T> { 248 #[cfg(all(debug_assertions, feature = "component-model-async"))] 249 if store.engine().config().async_support { 250 // Assert that we're running on a fiber, which is necessary in 251 // case we call the guest's realloc function. 252 store.0.with_blocking(|_, _| {}); 253 } 254 LowerContext { 255 store, 256 options, 257 types, 258 instance, 259 allow_realloc: true, 260 } 261 } 262 263 /// Like `new`, except disallows use of `options.realloc`. 264 /// 265 /// The returned object will panic if its `realloc` method is called. 266 /// 267 /// This is meant for use when lowering "flat" values (i.e. values which 268 /// require no allocations) into already-allocated memory or into stack 269 /// slots, in which case the lowering may safely be done outside of a fiber 270 /// since there is no need to make any guest calls. 271 #[cfg(feature = "component-model-async")] 272 pub(crate) fn new_without_realloc( 273 store: StoreContextMut<'a, T>, 274 options: &'a Options, 275 types: &'a ComponentTypes, 276 instance: Instance, 277 ) -> LowerContext<'a, T> { 278 LowerContext { 279 store, 280 options, 281 types, 282 instance, 283 allow_realloc: false, 284 } 285 } 286 287 /// Returns the `&ComponentInstance` that's being lowered into. 288 pub fn instance(&self) -> &ComponentInstance { 289 self.instance.id().get(self.store.0) 290 } 291 292 /// Returns the `&mut ComponentInstance` that's being lowered into. 293 pub fn instance_mut(&mut self) -> Pin<&mut ComponentInstance> { 294 self.instance.id().get_mut(self.store.0) 295 } 296 297 /// Returns a view into memory as a mutable slice of bytes. 298 /// 299 /// # Panics 300 /// 301 /// This will panic if memory has not been configured for this lowering 302 /// (e.g. it wasn't present during the specification of canonical options). 303 pub fn as_slice_mut(&mut self) -> &mut [u8] { 304 self.options.memory_mut(self.store.0) 305 } 306 307 /// Invokes the memory allocation function (which is style after `realloc`) 308 /// with the specified parameters. 309 /// 310 /// # Panics 311 /// 312 /// This will panic if realloc hasn't been configured for this lowering via 313 /// its canonical options. 314 pub fn realloc( 315 &mut self, 316 old: usize, 317 old_size: usize, 318 old_align: u32, 319 new_size: usize, 320 ) -> Result<usize> { 321 assert!(self.allow_realloc); 322 323 let realloc_func_ty = Arc::clone(self.instance().component().realloc_func_ty()); 324 self.options 325 .realloc( 326 &mut self.store, 327 &realloc_func_ty, 328 old, 329 old_size, 330 old_align, 331 new_size, 332 ) 333 .map(|(_, ptr)| ptr) 334 } 335 336 /// Returns a fixed mutable slice of memory `N` bytes large starting at 337 /// offset `N`, panicking on out-of-bounds. 338 /// 339 /// It should be previously verified that `offset` is in-bounds via 340 /// bounds-checks. 341 /// 342 /// # Panics 343 /// 344 /// This will panic if memory has not been configured for this lowering 345 /// (e.g. it wasn't present during the specification of canonical options). 346 pub fn get<const N: usize>(&mut self, offset: usize) -> &mut [u8; N] { 347 // FIXME: this bounds check shouldn't actually be necessary, all 348 // callers of `ComponentType::store` have already performed a bounds 349 // check so we're guaranteed that `offset..offset+N` is in-bounds. That 350 // being said we at least should do bounds checks in debug mode and 351 // it's not clear to me how to easily structure this so that it's 352 // "statically obvious" the bounds check isn't necessary. 353 // 354 // For now I figure we can leave in this bounds check and if it becomes 355 // an issue we can optimize further later, probably with judicious use 356 // of `unsafe`. 357 self.as_slice_mut()[offset..].first_chunk_mut().unwrap() 358 } 359 360 /// Lowers an `own` resource into the guest, converting the `rep` specified 361 /// into a guest-local index. 362 /// 363 /// The `ty` provided is which table to put this into. 364 pub fn guest_resource_lower_own( 365 &mut self, 366 ty: TypeResourceTableIndex, 367 rep: u32, 368 ) -> Result<u32> { 369 self.resource_tables().guest_resource_lower_own(rep, ty) 370 } 371 372 /// Lowers a `borrow` resource into the guest, converting the `rep` to a 373 /// guest-local index in the `ty` table specified. 374 pub fn guest_resource_lower_borrow( 375 &mut self, 376 ty: TypeResourceTableIndex, 377 rep: u32, 378 ) -> Result<u32> { 379 // Implement `lower_borrow`'s special case here where if a borrow is 380 // inserted into a table owned by the instance which implemented the 381 // original resource then no borrow tracking is employed and instead the 382 // `rep` is returned "raw". 383 // 384 // This check is performed by comparing the owning instance of `ty` 385 // against the owning instance of the resource that `ty` is working 386 // with. 387 if self.instance().resource_owned_by_own_instance(ty) { 388 return Ok(rep); 389 } 390 self.resource_tables().guest_resource_lower_borrow(rep, ty) 391 } 392 393 /// Lifts a host-owned `own` resource at the `idx` specified into the 394 /// representation of that resource. 395 pub fn host_resource_lift_own(&mut self, idx: HostResourceIndex) -> Result<u32> { 396 self.resource_tables().host_resource_lift_own(idx) 397 } 398 399 /// Lifts a host-owned `borrow` resource at the `idx` specified into the 400 /// representation of that resource. 401 pub fn host_resource_lift_borrow(&mut self, idx: HostResourceIndex) -> Result<u32> { 402 self.resource_tables().host_resource_lift_borrow(idx) 403 } 404 405 /// Lowers a resource into the host-owned table, returning the index it was 406 /// inserted at. 407 /// 408 /// Note that this is a special case for `Resource<T>`. Most of the time a 409 /// host value shouldn't be lowered with a lowering context. 410 pub fn host_resource_lower_own( 411 &mut self, 412 rep: u32, 413 dtor: Option<NonNull<VMFuncRef>>, 414 flags: Option<InstanceFlags>, 415 ) -> Result<HostResourceIndex> { 416 self.resource_tables() 417 .host_resource_lower_own(rep, dtor, flags) 418 } 419 420 /// Returns the underlying resource type for the `ty` table specified. 421 pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType { 422 self.instance_type().resource_type(ty) 423 } 424 425 /// Returns the instance type information corresponding to the instance that 426 /// this context is lowering into. 427 pub fn instance_type(&self) -> InstanceType<'_> { 428 InstanceType::new(self.instance()) 429 } 430 431 fn resource_tables(&mut self) -> HostResourceTables<'_> { 432 let (calls, host_table, host_resource_data, instance) = self 433 .store 434 .0 435 .component_resource_state_with_instance(self.instance); 436 HostResourceTables::from_parts( 437 ResourceTables { 438 host_table: Some(host_table), 439 calls, 440 guest: Some(instance.guest_tables()), 441 }, 442 host_resource_data, 443 ) 444 } 445 446 /// See [`HostResourceTables::enter_call`]. 447 #[inline] 448 pub fn enter_call(&mut self) { 449 self.resource_tables().enter_call() 450 } 451 452 /// See [`HostResourceTables::exit_call`]. 453 #[inline] 454 pub fn exit_call(&mut self) -> Result<()> { 455 self.resource_tables().exit_call() 456 } 457 } 458 459 /// Contextual information used when lifting a type from a component into the 460 /// host. 461 /// 462 /// This structure is the analogue of `LowerContext` except used during lifting 463 /// operations (or loading from memory). 464 #[doc(hidden)] 465 pub struct LiftContext<'a> { 466 /// Like lowering, lifting always has options configured. 467 pub options: &'a Options, 468 469 /// Instance type information, like with lowering. 470 pub types: &'a Arc<ComponentTypes>, 471 472 memory: Option<&'a [u8]>, 473 474 instance: Pin<&'a mut ComponentInstance>, 475 instance_handle: Instance, 476 477 host_table: &'a mut HandleTable, 478 host_resource_data: &'a mut HostResourceData, 479 480 calls: &'a mut CallContexts, 481 482 #[cfg_attr( 483 not(feature = "component-model-async"), 484 allow(unused, reason = "easier to not #[cfg] away") 485 )] 486 concurrent_state: &'a mut ConcurrentState, 487 } 488 489 #[doc(hidden)] 490 impl<'a> LiftContext<'a> { 491 /// Creates a new lifting context given the provided context. 492 #[inline] 493 pub fn new( 494 store: &'a mut StoreOpaque, 495 options: &'a Options, 496 instance_handle: Instance, 497 ) -> LiftContext<'a> { 498 // From `&mut StoreOpaque` provided the goal here is to project out 499 // three different disjoint fields owned by the store: memory, 500 // `CallContexts`, and `HandleTable`. There's no native API for that 501 // so it's hacked around a bit. This unsafe pointer cast could be fixed 502 // with more methods in more places, but it doesn't seem worth doing it 503 // at this time. 504 let memory = options 505 .memory 506 .map(|_| options.memory(unsafe { &*(store as *const StoreOpaque) })); 507 let (calls, host_table, host_resource_data, instance, concurrent_state) = 508 store.component_resource_state_with_instance_and_concurrent_state(instance_handle); 509 let (component, instance) = instance.component_and_self(); 510 511 LiftContext { 512 memory, 513 options, 514 types: component.types(), 515 instance, 516 instance_handle, 517 calls, 518 host_table, 519 host_resource_data, 520 concurrent_state, 521 } 522 } 523 524 /// Returns the entire contents of linear memory for this set of lifting 525 /// options. 526 /// 527 /// # Panics 528 /// 529 /// This will panic if memory has not been configured for this lifting 530 /// operation. 531 pub fn memory(&self) -> &'a [u8] { 532 self.memory.unwrap() 533 } 534 535 /// Returns an identifier for the store from which this `LiftContext` was 536 /// created. 537 pub fn store_id(&self) -> StoreId { 538 self.options.store_id 539 } 540 541 /// Returns the component instance that is being lifted from. 542 pub fn instance_mut(&mut self) -> Pin<&mut ComponentInstance> { 543 self.instance.as_mut() 544 } 545 /// Returns the component instance that is being lifted from. 546 pub fn instance_handle(&self) -> Instance { 547 self.instance_handle 548 } 549 550 #[cfg(feature = "component-model-async")] 551 pub(crate) fn concurrent_state_mut(&mut self) -> &mut ConcurrentState { 552 self.concurrent_state 553 } 554 555 /// Lifts an `own` resource from the guest at the `idx` specified into its 556 /// representation. 557 /// 558 /// Additionally returns a destructor/instance flags to go along with the 559 /// representation so the host knows how to destroy this resource. 560 pub fn guest_resource_lift_own( 561 &mut self, 562 ty: TypeResourceTableIndex, 563 idx: u32, 564 ) -> Result<(u32, Option<NonNull<VMFuncRef>>, Option<InstanceFlags>)> { 565 let idx = self.resource_tables().guest_resource_lift_own(idx, ty)?; 566 let (dtor, flags) = self.instance.dtor_and_flags(ty); 567 Ok((idx, dtor, flags)) 568 } 569 570 /// Lifts a `borrow` resource from the guest at the `idx` specified. 571 pub fn guest_resource_lift_borrow( 572 &mut self, 573 ty: TypeResourceTableIndex, 574 idx: u32, 575 ) -> Result<u32> { 576 self.resource_tables().guest_resource_lift_borrow(idx, ty) 577 } 578 579 /// Lowers a resource into the host-owned table, returning the index it was 580 /// inserted at. 581 pub fn host_resource_lower_own( 582 &mut self, 583 rep: u32, 584 dtor: Option<NonNull<VMFuncRef>>, 585 flags: Option<InstanceFlags>, 586 ) -> Result<HostResourceIndex> { 587 self.resource_tables() 588 .host_resource_lower_own(rep, dtor, flags) 589 } 590 591 /// Lowers a resource into the host-owned table, returning the index it was 592 /// inserted at. 593 pub fn host_resource_lower_borrow(&mut self, rep: u32) -> Result<HostResourceIndex> { 594 self.resource_tables().host_resource_lower_borrow(rep) 595 } 596 597 /// Returns the underlying type of the resource table specified by `ty`. 598 pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType { 599 self.instance_type().resource_type(ty) 600 } 601 602 /// Returns instance type information for the component instance that is 603 /// being lifted from. 604 pub fn instance_type(&self) -> InstanceType<'_> { 605 InstanceType::new(&self.instance) 606 } 607 608 fn resource_tables(&mut self) -> HostResourceTables<'_> { 609 HostResourceTables::from_parts( 610 ResourceTables { 611 host_table: Some(self.host_table), 612 calls: self.calls, 613 // Note that the unsafety here should be valid given the contract of 614 // `LiftContext::new`. 615 guest: Some(self.instance.as_mut().guest_tables()), 616 }, 617 self.host_resource_data, 618 ) 619 } 620 621 /// See [`HostResourceTables::enter_call`]. 622 #[inline] 623 pub fn enter_call(&mut self) { 624 self.resource_tables().enter_call() 625 } 626 627 /// See [`HostResourceTables::exit_call`]. 628 #[inline] 629 pub fn exit_call(&mut self) -> Result<()> { 630 self.resource_tables().exit_call() 631 } 632 } 633