1 //! Implementation of calling Rust-defined functions from components. 2 3 #[cfg(feature = "component-model-async")] 4 use crate::component::concurrent; 5 #[cfg(feature = "component-model-async")] 6 use crate::component::concurrent::{Accessor, Status}; 7 use crate::component::func::{LiftContext, LowerContext}; 8 use crate::component::matching::InstanceType; 9 use crate::component::storage::{slice_to_storage, slice_to_storage_mut}; 10 use crate::component::types::ComponentFunc; 11 use crate::component::{ComponentNamedList, Instance, Lift, Lower, Val}; 12 use crate::prelude::*; 13 use crate::runtime::vm::component::{ 14 ComponentInstance, VMComponentContext, VMLowering, VMLoweringCallee, 15 }; 16 use crate::runtime::vm::{VMOpaqueContext, VMStore}; 17 use crate::store::Asyncness; 18 use crate::{AsContextMut, CallHook, StoreContextMut, ValRaw}; 19 use alloc::sync::Arc; 20 use core::any::Any; 21 use core::mem::{self, MaybeUninit}; 22 #[cfg(feature = "async")] 23 use core::pin::Pin; 24 use core::ptr::NonNull; 25 use wasmtime_environ::component::{ 26 CanonicalAbiInfo, InterfaceType, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS, OptionsIndex, TypeFuncIndex, 27 }; 28 29 /// A host function suitable for passing into a component. 30 /// 31 /// This structure represents a monomorphic host function that can only be used 32 /// in the specific context of a particular store. This is generally not too 33 /// too safe to use and is only meant for internal use. 34 pub struct HostFunc { 35 /// The raw function pointer which Cranelift will invoke. 36 entrypoint: VMLoweringCallee, 37 38 /// The implementation of type-checking to ensure that this function 39 /// ascribes to the provided function type. 40 /// 41 /// This is used, for example, when a component imports a host function and 42 /// this will determine if the host function can be imported with the given 43 /// type. 44 typecheck: fn(TypeFuncIndex, &InstanceType<'_>) -> Result<()>, 45 46 /// The actual host function. 47 /// 48 /// This is frequently an empty allocation in the sense that the underlying 49 /// type is a zero-sized-type. Host functions are allowed, though, to close 50 /// over the environment as well. 51 func: Box<dyn Any + Send + Sync>, 52 53 /// Whether or not this host function was defined in such a way that async 54 /// stack switching is required when calling it. 55 asyncness: Asyncness, 56 } 57 58 impl core::fmt::Debug for HostFunc { 59 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 60 f.debug_struct("HostFunc").finish_non_exhaustive() 61 } 62 } 63 64 enum HostResult<T> { 65 Done(Result<T>), 66 #[cfg(feature = "component-model-async")] 67 Future(Pin<Box<dyn Future<Output = Result<T>> + Send>>), 68 } 69 70 impl HostFunc { 71 /// Creates a new host function based on the implementation of `func`. 72 /// 73 /// The `asyncness` parameter indicates whether the `func` requires 74 /// wasm to be on a fiber. This is used to propagate to the `Store` during 75 /// instantiation to ensure that this guarantee is met. 76 /// 77 /// Note that if `asyncness` is mistaken then that'll result in panics 78 /// in Wasmtime, but not memory unsafety. 79 fn new<T, F, P, R>(asyncness: Asyncness, func: F) -> Arc<HostFunc> 80 where 81 T: 'static, 82 R: Send + Sync + 'static, 83 F: HostFn<T, P, R> + Send + Sync + 'static, 84 { 85 Arc::new(HostFunc { 86 entrypoint: F::cabi_entrypoint, 87 typecheck: F::typecheck, 88 func: Box::new(func), 89 asyncness, 90 }) 91 } 92 93 /// Equivalent for `Linker::func_wrap` 94 pub(crate) fn func_wrap<T, F, P, R>(func: F) -> Arc<HostFunc> 95 where 96 T: 'static, 97 F: Fn(StoreContextMut<T>, P) -> Result<R> + Send + Sync + 'static, 98 P: ComponentNamedList + Lift + 'static, 99 R: ComponentNamedList + Lower + 'static, 100 { 101 Self::new( 102 Asyncness::No, 103 StaticHostFn::<_, false>::new(move |store, params| { 104 HostResult::Done(func(store, params)) 105 }), 106 ) 107 } 108 109 /// Equivalent for `Linker::func_wrap_async` 110 #[cfg(feature = "async")] 111 pub(crate) fn func_wrap_async<T, F, P, R>(func: F) -> Arc<HostFunc> 112 where 113 T: 'static, 114 F: Fn(StoreContextMut<'_, T>, P) -> Box<dyn Future<Output = Result<R>> + Send + '_> 115 + Send 116 + Sync 117 + 'static, 118 P: ComponentNamedList + Lift + 'static, 119 R: ComponentNamedList + Lower + 'static, 120 { 121 Self::new( 122 Asyncness::Yes, 123 StaticHostFn::<_, false>::new(move |store, params| { 124 HostResult::Done( 125 store 126 .block_on(|store| Pin::from(func(store, params))) 127 .and_then(|r| r), 128 ) 129 }), 130 ) 131 } 132 133 /// Equivalent for `Linker::func_wrap_concurrent` 134 #[cfg(feature = "component-model-async")] 135 pub(crate) fn func_wrap_concurrent<T, F, P, R>(func: F) -> Arc<HostFunc> 136 where 137 T: 'static, 138 F: Fn(&Accessor<T>, P) -> Pin<Box<dyn Future<Output = Result<R>> + Send + '_>> 139 + Send 140 + Sync 141 + 'static, 142 P: ComponentNamedList + Lift + 'static, 143 R: ComponentNamedList + Lower + 'static, 144 { 145 let func = Arc::new(func); 146 Self::new( 147 Asyncness::Yes, 148 StaticHostFn::<_, true>::new(move |store, params| { 149 let func = func.clone(); 150 HostResult::Future(Box::pin( 151 store.wrap_call(move |accessor| func(accessor, params)), 152 )) 153 }), 154 ) 155 } 156 157 /// Equivalent of `Linker::func_new` 158 pub(crate) fn func_new<T, F>(func: F) -> Arc<HostFunc> 159 where 160 T: 'static, 161 F: Fn(StoreContextMut<'_, T>, ComponentFunc, &[Val], &mut [Val]) -> Result<()> 162 + Send 163 + Sync 164 + 'static, 165 { 166 Self::new( 167 Asyncness::No, 168 DynamicHostFn::<_, false>::new( 169 move |store, ty, mut params_and_results, result_start| { 170 let (params, results) = params_and_results.split_at_mut(result_start); 171 let result = func(store, ty, params, results).map(move |()| params_and_results); 172 HostResult::Done(result) 173 }, 174 ), 175 ) 176 } 177 178 /// Equivalent of `Linker::func_new_async` 179 #[cfg(feature = "async")] 180 pub(crate) fn func_new_async<T, F>(func: F) -> Arc<HostFunc> 181 where 182 T: 'static, 183 F: for<'a> Fn( 184 StoreContextMut<'a, T>, 185 ComponentFunc, 186 &'a [Val], 187 &'a mut [Val], 188 ) -> Box<dyn Future<Output = Result<()>> + Send + 'a> 189 + Send 190 + Sync 191 + 'static, 192 { 193 Self::new( 194 Asyncness::Yes, 195 DynamicHostFn::<_, false>::new( 196 move |store, ty, mut params_and_results, result_start| { 197 let (params, results) = params_and_results.split_at_mut(result_start); 198 let result = store 199 .with_blocking(|store, cx| { 200 cx.block_on(Pin::from(func(store, ty, params, results))) 201 }) 202 .and_then(|r| r); 203 let result = result.map(move |()| params_and_results); 204 HostResult::Done(result) 205 }, 206 ), 207 ) 208 } 209 210 /// Equivalent of `Linker::func_new_concurrent` 211 #[cfg(feature = "component-model-async")] 212 pub(crate) fn func_new_concurrent<T, F>(func: F) -> Arc<HostFunc> 213 where 214 T: 'static, 215 F: for<'a> Fn( 216 &'a Accessor<T>, 217 ComponentFunc, 218 &'a [Val], 219 &'a mut [Val], 220 ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> 221 + Send 222 + Sync 223 + 'static, 224 { 225 let func = Arc::new(func); 226 Self::new( 227 Asyncness::Yes, 228 DynamicHostFn::<_, true>::new( 229 move |store, ty, mut params_and_results, result_start| { 230 let func = func.clone(); 231 HostResult::Future(Box::pin(store.wrap_call(move |accessor| { 232 Box::pin(async move { 233 let (params, results) = params_and_results.split_at_mut(result_start); 234 func(accessor, ty, params, results).await?; 235 Ok(params_and_results) 236 }) 237 }))) 238 }, 239 ), 240 ) 241 } 242 243 pub fn typecheck(&self, ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> { 244 (self.typecheck)(ty, types) 245 } 246 247 pub fn lowering(&self) -> VMLowering { 248 let data = NonNull::from(&*self.func).cast(); 249 VMLowering { 250 callee: NonNull::new(self.entrypoint as *mut _).unwrap().into(), 251 data: data.into(), 252 } 253 } 254 255 pub fn asyncness(&self) -> Asyncness { 256 self.asyncness 257 } 258 } 259 260 /// Argument to [`HostFn::lift_params`] 261 enum Source<'a> { 262 /// The parameters come from flat wasm arguments which are provided here. 263 Flat(&'a [ValRaw]), 264 /// The parameters come from linear memory at the provided offset, which is 265 /// already validated to be in-bounds. 266 Memory(usize), 267 } 268 269 /// Argument to [`HostFn::lower_result`] 270 enum Destination<'a> { 271 /// The result is stored in flat parameters whose storage is provided here. 272 Flat(&'a mut [MaybeUninit<ValRaw>]), 273 /// The result is stored in linear memory at the provided offset, which is 274 /// already validated to be in-bounds. 275 Memory(usize), 276 } 277 278 /// Consolidation of functionality of invoking a host function. 279 /// 280 /// This trait primarily serves as a deduplication of the "static" and 281 /// "dynamic" host function paths where all default functions here are shared 282 /// (source-wise at least) across the two styles of host functions. 283 trait HostFn<T, P, R> 284 where 285 T: 'static, 286 R: Send + Sync + 'static, 287 { 288 /// Whether or not this is `async` function from the perspective of the 289 /// component model. 290 const ASYNC: bool; 291 292 /// Performs a type-check to ensure that this host function can be imported 293 /// with the provided signature that a component is using. 294 fn typecheck(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()>; 295 296 /// Execute this host function. 297 fn run(&self, store: StoreContextMut<'_, T>, params: P) -> HostResult<R>; 298 299 /// Performs the lifting operation to convert arguments from the canonical 300 /// ABI in wasm memory/arguments into their Rust representation. 301 fn lift_params(cx: &mut LiftContext<'_>, ty: TypeFuncIndex, source: Source<'_>) -> Result<P>; 302 303 /// Performs the lowering operation to convert the result from its Rust 304 /// representation to the canonical ABI representation. 305 fn lower_result( 306 cx: &mut LowerContext<'_, T>, 307 ty: TypeFuncIndex, 308 result: R, 309 dst: Destination<'_>, 310 ) -> Result<()>; 311 312 /// Raw entrypoint invoked by Cranelift. 313 /// 314 /// # Safety 315 /// 316 /// This function is only safe when called from a trusted source which 317 /// upholds at least these invariants: 318 /// 319 /// * `cx` is a valid pointer which comes from calling wasm. 320 /// * `data` is a valid pointer to `Self` 321 /// * `ty` and `options` are valid within the context of `cx` 322 /// * `storage` and `storage_len` are valid pointers and correspond to 323 /// correctly initialized wasm arguments/results according to the 324 /// canonical ABI specified by `ty` and `options`. 325 /// 326 /// The code elsewhere in this trait is all downstream of this `unsafe`, 327 /// and upholding this `unsafe` invariant requires Cranelift, function 328 /// translation, the canonical ABI, and Wasmtime to all stay in sync. 329 /// Basically we can't statically rule out this `unsafe`, we just gotta 330 /// not have bugs. 331 unsafe extern "C" fn cabi_entrypoint( 332 cx: NonNull<VMOpaqueContext>, 333 data: NonNull<u8>, 334 ty: u32, 335 options: u32, 336 storage: NonNull<MaybeUninit<ValRaw>>, 337 storage_len: usize, 338 ) -> bool 339 where 340 Self: Sized, 341 { 342 let cx = unsafe { VMComponentContext::from_opaque(cx) }; 343 unsafe { 344 ComponentInstance::enter_host_from_wasm(cx, |store, instance| { 345 let mut store = store.unchecked_context_mut(); 346 let ty = TypeFuncIndex::from_u32(ty); 347 let options = OptionsIndex::from_u32(options); 348 let storage = NonNull::slice_from_raw_parts(storage, storage_len).as_mut(); 349 let data = data.cast::<Self>().as_ref(); 350 351 store.0.call_hook(CallHook::CallingHost)?; 352 let res = data.entrypoint(store.as_context_mut(), instance, ty, options, storage); 353 store.0.call_hook(CallHook::ReturningFromHost)?; 354 355 res 356 }) 357 } 358 } 359 360 /// "Rust" entrypoint after panic-handling infrastructure is set up and raw 361 /// arguments are translated to Rust types. 362 fn entrypoint( 363 &self, 364 mut store: StoreContextMut<'_, T>, 365 instance: Instance, 366 ty: TypeFuncIndex, 367 options: OptionsIndex, 368 storage: &mut [MaybeUninit<ValRaw>], 369 ) -> Result<()> { 370 let vminstance = instance.id().get(store.0); 371 let async_ = vminstance.component().env_component().options[options].async_; 372 373 // If this is a synchronous-lower of a host-async function, then the 374 // guest is blocking. Test, in the context of the guest task, if that's 375 // allowed. 376 if !async_ && Self::ASYNC { 377 store.0.check_blocking()?; 378 } 379 380 // Enter the host by pushing a `HostTask` into the concurrent state. 381 store.0.enter_host_call()?; 382 383 let task_exited = if async_ { 384 #[cfg(feature = "component-model-async")] 385 { 386 self.call_async_lower(store.as_context_mut(), instance, ty, options, storage)? 387 } 388 #[cfg(not(feature = "component-model-async"))] 389 unreachable!( 390 "async-lowered imports should have failed validation \ 391 when `component-model-async` feature disabled" 392 ); 393 } else { 394 self.call_sync_lower(store.as_context_mut(), instance, ty, options, storage)?; 395 true 396 }; 397 398 // If the host task exited, then it's popped and deallocated. 399 // 400 // Note that if the host task did not exit then the `call_async_lower` 401 // function transitively would have updated the current guest thread to 402 // the caller of this host function. 403 if task_exited { 404 store.0.exit_host_call()?; 405 } 406 407 Ok(()) 408 } 409 410 /// Implementation of the "sync" ABI. 411 /// 412 /// This is the implementation of invoking a host function through the 413 /// synchronous ABI of the component model, or when a function doesn't have 414 /// the `async` option when lowered. Note that the host function itself 415 /// can still be async, in which case this will block here waiting for it 416 /// to finish. 417 fn call_sync_lower( 418 &self, 419 mut store: StoreContextMut<'_, T>, 420 instance: Instance, 421 ty: TypeFuncIndex, 422 options: OptionsIndex, 423 storage: &mut [MaybeUninit<ValRaw>], 424 ) -> Result<()> { 425 let mut lift = LiftContext::new(store.0.store_opaque_mut(), options, instance); 426 let (params, rest) = self.load_params(&mut lift, ty, MAX_FLAT_PARAMS, storage)?; 427 428 let ret = match self.run(store.as_context_mut(), params) { 429 HostResult::Done(result) => result?, 430 #[cfg(feature = "component-model-async")] 431 HostResult::Future(future) => concurrent::poll_and_block(store.0, future)?, 432 }; 433 434 let mut lower = LowerContext::new(store, options, instance); 435 let fty = &lower.types[ty]; 436 let result_tys = &lower.types[fty.results]; 437 let dst = if let Some(cnt) = result_tys.abi.flat_count(MAX_FLAT_RESULTS) { 438 Destination::Flat(&mut storage[..cnt]) 439 } else { 440 // SAFETY: due to the contract of `entrypoint` we know that the 441 // return pointer, located after the parameters, is initialized 442 // by wasm and safe to read. 443 let ptr = unsafe { rest[0].assume_init_ref() }; 444 Destination::Memory(validate_inbounds_dynamic( 445 &result_tys.abi, 446 lower.as_slice_mut(), 447 ptr, 448 )?) 449 }; 450 Self::lower_result_and_exit_call(&mut lower, ty, Some(ret), dst) 451 } 452 453 /// Implementation of the "async" ABI of the component model. 454 /// 455 /// This is invoked when a component has the `async` options specified on 456 /// its `canon lower` for a host function. Note that the host function may 457 /// be either sync or async, and that's handled here too. 458 #[cfg(feature = "component-model-async")] 459 fn call_async_lower( 460 &self, 461 store: StoreContextMut<'_, T>, 462 instance: Instance, 463 ty: TypeFuncIndex, 464 options: OptionsIndex, 465 storage: &mut [MaybeUninit<ValRaw>], 466 ) -> Result<bool> { 467 use wasmtime_environ::component::MAX_FLAT_ASYNC_PARAMS; 468 469 let (component, store) = instance.component_and_store_mut(store.0); 470 let mut store = StoreContextMut(store); 471 let types = component.types(); 472 let fty = &types[ty]; 473 474 // Lift the parameters, either from flat storage or from linear 475 // memory. 476 let mut lift = LiftContext::new(store.0.store_opaque_mut(), options, instance); 477 let (params, rest) = self.load_params(&mut lift, ty, MAX_FLAT_ASYNC_PARAMS, storage)?; 478 479 // Load/validate the return pointer, if present. 480 let retptr = if !lift.types[fty.results].types.is_empty() { 481 let mut lower = LowerContext::new(store.as_context_mut(), options, instance); 482 // SAFETY: see `load_params` below about how the return pointer 483 // should be safe to use. 484 let ptr = unsafe { rest[0].assume_init_ref() }; 485 let result_tys = &lower.types[fty.results]; 486 validate_inbounds_dynamic(&result_tys.abi, lower.as_slice_mut(), ptr)? 487 } else { 488 // If there's no return pointer then `R` should have an 489 // empty flat representation. In this situation pretend the return 490 // pointer was 0 so we have something to shepherd along into the 491 // closure below. 492 0 493 }; 494 495 let host_result = self.run(store.as_context_mut(), params); 496 497 let task = match host_result { 498 HostResult::Done(result) => { 499 Self::lower_result_and_exit_call( 500 &mut LowerContext::new(store, options, instance), 501 ty, 502 Some(result?), 503 Destination::Memory(retptr), 504 )?; 505 None 506 } 507 #[cfg(feature = "component-model-async")] 508 HostResult::Future(future) => { 509 instance.first_poll(store, future, move |store, ret| { 510 Self::lower_result_and_exit_call( 511 &mut LowerContext::new(store, options, instance), 512 ty, 513 ret, 514 Destination::Memory(retptr), 515 ) 516 })? 517 } 518 }; 519 520 storage[0].write(ValRaw::u32(if let Some(task) = task { 521 Status::Started.pack(Some(task)) 522 } else { 523 Status::Returned.pack(None) 524 })); 525 526 Ok(task.is_none()) 527 } 528 529 /// Loads parameters the wasm arguments `storage`. 530 /// 531 /// This will internally decide the ABI source of the parameters and use 532 /// `storage` appropriately. 533 fn load_params<'a>( 534 &self, 535 lift: &mut LiftContext<'_>, 536 ty: TypeFuncIndex, 537 max_flat_params: usize, 538 storage: &'a [MaybeUninit<ValRaw>], 539 ) -> Result<(P, &'a [MaybeUninit<ValRaw>])> { 540 let fty = &lift.types[ty]; 541 let param_tys = &lift.types[fty.params]; 542 let param_flat_count = param_tys.abi.flat_count(max_flat_params); 543 let src = match param_flat_count { 544 Some(cnt) => { 545 let params = &storage[..cnt]; 546 // SAFETY: due to the contract of `entrypoint` we are 547 // guaranteed that all flat parameters are initialized by 548 // compiled wasm. 549 Source::Flat(unsafe { mem::transmute::<&[MaybeUninit<ValRaw>], &[ValRaw]>(params) }) 550 } 551 None => { 552 // SAFETY: due to the contract of `entrypoint` we are 553 // guaranteed that the return pointer is initialized by 554 // compiled wasm. 555 let ptr = unsafe { storage[0].assume_init_ref() }; 556 Source::Memory(validate_inbounds_dynamic( 557 ¶m_tys.abi, 558 lift.memory(), 559 ptr, 560 )?) 561 } 562 }; 563 let params = Self::lift_params(lift, ty, src)?; 564 Ok((params, &storage[param_flat_count.unwrap_or(1)..])) 565 } 566 567 /// Stores the result `ret` into `dst` which is calculated per the ABI. 568 fn lower_result_and_exit_call( 569 lower: &mut LowerContext<'_, T>, 570 ty: TypeFuncIndex, 571 ret: Option<R>, 572 dst: Destination<'_>, 573 ) -> Result<()> { 574 if let Some(ret) = ret { 575 let caller_instance = lower.options().instance; 576 let mut flags = lower.instance_mut().instance_flags(caller_instance); 577 unsafe { 578 flags.set_may_leave(false); 579 } 580 Self::lower_result(lower, ty, ret, dst)?; 581 unsafe { 582 flags.set_may_leave(true); 583 } 584 } 585 lower.validate_scope_exit()?; 586 Ok(()) 587 } 588 } 589 590 /// Implementation of a "static" host function where the parameters and results 591 /// of a function are known at compile time. 592 #[repr(transparent)] 593 struct StaticHostFn<F, const ASYNC: bool>(F); 594 595 impl<F, const ASYNC: bool> StaticHostFn<F, ASYNC> { 596 fn new<T, P, R>(func: F) -> Self 597 where 598 T: 'static, 599 P: ComponentNamedList + Lift + 'static, 600 R: ComponentNamedList + Lower + 'static, 601 F: Fn(StoreContextMut<'_, T>, P) -> HostResult<R>, 602 { 603 Self(func) 604 } 605 } 606 607 impl<T, F, P, R, const ASYNC: bool> HostFn<T, P, R> for StaticHostFn<F, ASYNC> 608 where 609 T: 'static, 610 F: Fn(StoreContextMut<'_, T>, P) -> HostResult<R>, 611 P: ComponentNamedList + Lift + 'static, 612 R: ComponentNamedList + Lower + 'static, 613 { 614 const ASYNC: bool = ASYNC; 615 616 fn typecheck(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> { 617 let ty = &types.types[ty]; 618 if ASYNC != ty.async_ { 619 bail!("type mismatch with async"); 620 } 621 P::typecheck(&InterfaceType::Tuple(ty.params), types) 622 .context("type mismatch with parameters")?; 623 R::typecheck(&InterfaceType::Tuple(ty.results), types) 624 .context("type mismatch with results")?; 625 Ok(()) 626 } 627 628 fn run(&self, store: StoreContextMut<'_, T>, params: P) -> HostResult<R> { 629 (self.0)(store, params) 630 } 631 632 fn lift_params(cx: &mut LiftContext<'_>, ty: TypeFuncIndex, src: Source<'_>) -> Result<P> { 633 let ty = InterfaceType::Tuple(cx.types[ty].params); 634 match src { 635 Source::Flat(storage) => { 636 // SAFETY: the contract of `ComponentType` for `P` means that 637 // it's safe to interpret the parameters `storage` as 638 // `P::Lower`. The contract of `entrypoint` is that everything 639 // is initialized correctly internally. 640 let storage: &P::Lower = unsafe { slice_to_storage(storage) }; 641 P::linear_lift_from_flat(cx, ty, storage) 642 } 643 Source::Memory(offset) => { 644 P::linear_lift_from_memory(cx, ty, &cx.memory()[offset..][..P::SIZE32]) 645 } 646 } 647 } 648 649 fn lower_result( 650 cx: &mut LowerContext<'_, T>, 651 ty: TypeFuncIndex, 652 ret: R, 653 dst: Destination<'_>, 654 ) -> Result<()> { 655 let fty = &cx.types[ty]; 656 let ty = InterfaceType::Tuple(fty.results); 657 match dst { 658 Destination::Flat(storage) => { 659 // SAFETY: the contract of `ComponentType` for `R` means that 660 // it's safe to reinterpret `ValRaw` storage to initialize as 661 // `R::Lower`. 662 let storage: &mut MaybeUninit<R::Lower> = unsafe { slice_to_storage_mut(storage) }; 663 ret.linear_lower_to_flat(cx, ty, storage) 664 } 665 Destination::Memory(ptr) => ret.linear_lower_to_memory(cx, ty, ptr), 666 } 667 } 668 } 669 670 /// Implementation of a "dynamic" host function where the number of parameters, 671 /// types of parameters, and result type/presence, are all not known at compile 672 /// time. 673 /// 674 /// This is intended for more-dynamic use cases than `StaticHostFn` above such 675 /// as demos, gluing things together quickly, and `wast` testing. 676 struct DynamicHostFn<F, const ASYNC: bool>(F); 677 678 impl<F, const ASYNC: bool> DynamicHostFn<F, ASYNC> { 679 fn new<T>(func: F) -> Self 680 where 681 T: 'static, 682 F: Fn(StoreContextMut<'_, T>, ComponentFunc, Vec<Val>, usize) -> HostResult<Vec<Val>>, 683 { 684 Self(func) 685 } 686 } 687 688 impl<T, F, const ASYNC: bool> HostFn<T, (ComponentFunc, Vec<Val>), Vec<Val>> 689 for DynamicHostFn<F, ASYNC> 690 where 691 T: 'static, 692 F: Fn(StoreContextMut<'_, T>, ComponentFunc, Vec<Val>, usize) -> HostResult<Vec<Val>>, 693 { 694 const ASYNC: bool = ASYNC; 695 696 /// This function performs dynamic type checks on its parameters and 697 /// results and subsequently does not need to perform up-front type 698 /// checks. However, we _do_ verify async-ness here. 699 fn typecheck(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> { 700 let ty = &types.types[ty]; 701 if ASYNC != ty.async_ { 702 bail!("type mismatch with async"); 703 } 704 705 Ok(()) 706 } 707 708 fn run( 709 &self, 710 store: StoreContextMut<'_, T>, 711 (ty, mut params): (ComponentFunc, Vec<Val>), 712 ) -> HostResult<Vec<Val>> { 713 let offset = params.len(); 714 for _ in 0..ty.results().len() { 715 params.push(Val::Bool(false)); 716 } 717 (self.0)(store, ty, params, offset) 718 } 719 720 fn lift_params( 721 cx: &mut LiftContext<'_>, 722 ty: TypeFuncIndex, 723 src: Source<'_>, 724 ) -> Result<(ComponentFunc, Vec<Val>)> { 725 let param_tys = &cx.types[cx.types[ty].params]; 726 let mut params = Vec::new(); 727 match src { 728 Source::Flat(storage) => { 729 let mut iter = storage.iter(); 730 for ty in param_tys.types.iter() { 731 params.push(Val::lift(cx, *ty, &mut iter)?); 732 } 733 assert!(iter.next().is_none()); 734 } 735 Source::Memory(mut offset) => { 736 for ty in param_tys.types.iter() { 737 let abi = cx.types.canonical_abi(ty); 738 let size = usize::try_from(abi.size32).unwrap(); 739 let memory = &cx.memory()[abi.next_field32_size(&mut offset)..][..size]; 740 params.push(Val::load(cx, *ty, memory)?); 741 } 742 } 743 } 744 745 Ok((ComponentFunc::from(ty, &cx.instance_type()), params)) 746 } 747 748 fn lower_result( 749 cx: &mut LowerContext<'_, T>, 750 ty: TypeFuncIndex, 751 result_vals: Vec<Val>, 752 dst: Destination<'_>, 753 ) -> Result<()> { 754 let fty = &cx.types[ty]; 755 let param_tys = &cx.types[fty.params]; 756 let result_tys = &cx.types[fty.results]; 757 let result_vals = &result_vals[param_tys.types.len()..]; 758 match dst { 759 Destination::Flat(storage) => { 760 let mut dst = storage.iter_mut(); 761 for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) { 762 val.lower(cx, *ty, &mut dst)?; 763 } 764 assert!(dst.next().is_none()); 765 } 766 Destination::Memory(mut ptr) => { 767 for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) { 768 let offset = cx.types.canonical_abi(ty).next_field32_size(&mut ptr); 769 val.store(cx, *ty, offset)?; 770 } 771 } 772 } 773 Ok(()) 774 } 775 } 776 777 pub(crate) fn validate_inbounds_dynamic( 778 abi: &CanonicalAbiInfo, 779 memory: &[u8], 780 ptr: &ValRaw, 781 ) -> Result<usize> { 782 // FIXME(#4311): needs memory64 support 783 let ptr = usize::try_from(ptr.get_u32())?; 784 if ptr % usize::try_from(abi.align32)? != 0 { 785 bail!("pointer not aligned"); 786 } 787 let end = match ptr.checked_add(usize::try_from(abi.size32).unwrap()) { 788 Some(n) => n, 789 None => bail!("pointer size overflow"), 790 }; 791 if end > memory.len() { 792 bail!("pointer out of bounds") 793 } 794 Ok(ptr) 795 } 796