1 use crate::component::concurrent; 2 #[cfg(feature = "component-model-async")] 3 use crate::component::concurrent::{Accessor, Status}; 4 use crate::component::func::{LiftContext, LowerContext, Options}; 5 use crate::component::matching::InstanceType; 6 use crate::component::storage::slice_to_storage_mut; 7 use crate::component::{ComponentNamedList, ComponentType, Instance, Lift, Lower, Val}; 8 use crate::prelude::*; 9 use crate::runtime::vm::component::{ 10 ComponentInstance, VMComponentContext, VMLowering, VMLoweringCallee, 11 }; 12 use crate::runtime::vm::{SendSyncPtr, VMOpaqueContext, VMStore}; 13 use crate::{AsContextMut, CallHook, StoreContextMut, ValRaw}; 14 use alloc::sync::Arc; 15 use core::any::Any; 16 use core::future::Future; 17 use core::mem::{self, MaybeUninit}; 18 use core::pin::Pin; 19 use core::ptr::NonNull; 20 use wasmtime_environ::component::{ 21 CanonicalAbiInfo, ComponentTypes, InterfaceType, MAX_FLAT_ASYNC_PARAMS, MAX_FLAT_PARAMS, 22 MAX_FLAT_RESULTS, OptionsIndex, TypeFuncIndex, TypeTuple, 23 }; 24 25 pub struct HostFunc { 26 entrypoint: VMLoweringCallee, 27 typecheck: Box<dyn (Fn(TypeFuncIndex, &InstanceType<'_>) -> Result<()>) + Send + Sync>, 28 func: Box<dyn Any + Send + Sync>, 29 } 30 31 impl core::fmt::Debug for HostFunc { 32 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 33 f.debug_struct("HostFunc").finish_non_exhaustive() 34 } 35 } 36 37 enum HostResult<T> { 38 Done(Result<T>), 39 #[cfg(feature = "component-model-async")] 40 Future(Pin<Box<dyn Future<Output = Result<T>> + Send>>), 41 } 42 43 impl HostFunc { 44 fn from_canonical<T, F, P, R>(func: F) -> Arc<HostFunc> 45 where 46 F: Fn(StoreContextMut<'_, T>, P) -> HostResult<R> + Send + Sync + 'static, 47 P: ComponentNamedList + Lift + 'static, 48 R: ComponentNamedList + Lower + 'static, 49 T: 'static, 50 { 51 let entrypoint = Self::entrypoint::<T, F, P, R>; 52 Arc::new(HostFunc { 53 entrypoint, 54 typecheck: Box::new(typecheck::<P, R>), 55 func: Box::new(func), 56 }) 57 } 58 59 pub(crate) fn from_closure<T, F, P, R>(func: F) -> Arc<HostFunc> 60 where 61 T: 'static, 62 F: Fn(StoreContextMut<T>, P) -> Result<R> + Send + Sync + 'static, 63 P: ComponentNamedList + Lift + 'static, 64 R: ComponentNamedList + Lower + 'static, 65 { 66 Self::from_canonical::<T, _, _, _>(move |store, params| { 67 HostResult::Done(func(store, params)) 68 }) 69 } 70 71 #[cfg(feature = "component-model-async")] 72 pub(crate) fn from_concurrent<T, F, P, R>(func: F) -> Arc<HostFunc> 73 where 74 T: 'static, 75 F: Fn(&Accessor<T>, P) -> Pin<Box<dyn Future<Output = Result<R>> + Send + '_>> 76 + Send 77 + Sync 78 + 'static, 79 P: ComponentNamedList + Lift + 'static, 80 R: ComponentNamedList + Lower + 'static, 81 { 82 let func = Arc::new(func); 83 Self::from_canonical::<T, _, _, _>(move |store, params| { 84 let func = func.clone(); 85 HostResult::Future(Box::pin( 86 store.wrap_call(move |accessor| func(accessor, params)), 87 )) 88 }) 89 } 90 91 extern "C" fn entrypoint<T, F, P, R>( 92 cx: NonNull<VMOpaqueContext>, 93 data: NonNull<u8>, 94 ty: u32, 95 options: u32, 96 storage: NonNull<MaybeUninit<ValRaw>>, 97 storage_len: usize, 98 ) -> bool 99 where 100 F: Fn(StoreContextMut<'_, T>, P) -> HostResult<R> + Send + Sync + 'static, 101 P: ComponentNamedList + Lift, 102 R: ComponentNamedList + Lower + 'static, 103 T: 'static, 104 { 105 let data = SendSyncPtr::new(NonNull::new(data.as_ptr() as *mut F).unwrap()); 106 unsafe { 107 call_host_and_handle_result::<T>(cx, |store, instance| { 108 call_host( 109 store, 110 instance, 111 TypeFuncIndex::from_u32(ty), 112 OptionsIndex::from_u32(options), 113 NonNull::slice_from_raw_parts(storage, storage_len).as_mut(), 114 move |store, args| (*data.as_ptr())(store, args), 115 ) 116 }) 117 } 118 } 119 120 fn new_dynamic_canonical<T, F>(func: F) -> Arc<HostFunc> 121 where 122 F: Fn( 123 StoreContextMut<'_, T>, 124 Vec<Val>, 125 usize, 126 ) -> Pin<Box<dyn Future<Output = Result<Vec<Val>>> + Send + 'static>> 127 + Send 128 + Sync 129 + 'static, 130 T: 'static, 131 { 132 Arc::new(HostFunc { 133 entrypoint: dynamic_entrypoint::<T, F>, 134 // This function performs dynamic type checks and subsequently does 135 // not need to perform up-front type checks. Instead everything is 136 // dynamically managed at runtime. 137 typecheck: Box::new(move |_expected_index, _expected_types| Ok(())), 138 func: Box::new(func), 139 }) 140 } 141 142 pub(crate) fn new_dynamic<T: 'static, F>(func: F) -> Arc<HostFunc> 143 where 144 F: Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static, 145 { 146 Self::new_dynamic_canonical::<T, _>(move |store, mut params_and_results, result_start| { 147 let (params, results) = params_and_results.split_at_mut(result_start); 148 let result = func(store, params, results).map(move |()| params_and_results); 149 Box::pin(async move { result }) 150 }) 151 } 152 153 #[cfg(feature = "component-model-async")] 154 pub(crate) fn new_dynamic_concurrent<T, F>(func: F) -> Arc<HostFunc> 155 where 156 T: 'static, 157 F: for<'a> Fn( 158 &'a Accessor<T>, 159 &'a [Val], 160 &'a mut [Val], 161 ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> 162 + Send 163 + Sync 164 + 'static, 165 { 166 let func = Arc::new(func); 167 Self::new_dynamic_canonical::<T, _>(move |store, mut params_and_results, result_start| { 168 let func = func.clone(); 169 Box::pin(store.wrap_call(move |accessor| { 170 Box::pin(async move { 171 let (params, results) = params_and_results.split_at_mut(result_start); 172 func(accessor, params, results).await?; 173 Ok(params_and_results) 174 }) 175 })) 176 }) 177 } 178 179 pub fn typecheck(&self, ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> { 180 (self.typecheck)(ty, types) 181 } 182 183 pub fn lowering(&self) -> VMLowering { 184 let data = NonNull::from(&*self.func).cast(); 185 VMLowering { 186 callee: NonNull::new(self.entrypoint as *mut _).unwrap().into(), 187 data: data.into(), 188 } 189 } 190 } 191 192 fn typecheck<P, R>(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> 193 where 194 P: ComponentNamedList + Lift, 195 R: ComponentNamedList + Lower, 196 { 197 let ty = &types.types[ty]; 198 P::typecheck(&InterfaceType::Tuple(ty.params), types) 199 .context("type mismatch with parameters")?; 200 R::typecheck(&InterfaceType::Tuple(ty.results), types).context("type mismatch with results")?; 201 Ok(()) 202 } 203 204 /// The "meat" of calling a host function from wasm. 205 /// 206 /// This function is delegated to from implementations of 207 /// `HostFunc::from_closure`. Most of the arguments from the `entrypoint` are 208 /// forwarded here except for the `data` pointer which is encapsulated in the 209 /// `closure` argument here. 210 /// 211 /// This function is parameterized over: 212 /// 213 /// * `T` - the type of store this function works with (an unsafe assertion) 214 /// * `Params` - the parameters to the host function, viewed as a tuple 215 /// * `Return` - the result of the host function 216 /// * `F` - the `closure` to actually receive the `Params` and return the 217 /// `Return` 218 /// 219 /// It's expected that `F` will "un-tuple" the arguments to pass to a host 220 /// closure. 221 /// 222 /// This function is in general `unsafe` as the validity of all the parameters 223 /// must be upheld. Generally that's done by ensuring this is only called from 224 /// the select few places it's intended to be called from. 225 unsafe fn call_host<T, Params, Return, F>( 226 mut store: StoreContextMut<'_, T>, 227 instance: Instance, 228 ty: TypeFuncIndex, 229 options_idx: OptionsIndex, 230 storage: &mut [MaybeUninit<ValRaw>], 231 closure: F, 232 ) -> Result<()> 233 where 234 F: Fn(StoreContextMut<'_, T>, Params) -> HostResult<Return> + Send + Sync + 'static, 235 Params: Lift, 236 Return: Lower + 'static, 237 { 238 let options = Options::new_index(store.0, instance, options_idx); 239 let vminstance = instance.id().get(store.0); 240 let opts = &vminstance.component().env_component().options[options_idx]; 241 let async_ = opts.async_; 242 let caller_instance = opts.instance; 243 let mut flags = vminstance.instance_flags(caller_instance); 244 245 // Perform a dynamic check that this instance can indeed be left. Exiting 246 // the component is disallowed, for example, when the `realloc` function 247 // calls a canonical import. 248 if unsafe { !flags.may_leave() } { 249 return Err(anyhow!(crate::Trap::CannotLeaveComponent)); 250 } 251 252 let types = vminstance.component().types().clone(); 253 let ty = &types[ty]; 254 let param_tys = InterfaceType::Tuple(ty.params); 255 let result_tys = InterfaceType::Tuple(ty.results); 256 257 if async_ { 258 #[cfg(feature = "component-model-async")] 259 { 260 let mut storage = unsafe { Storage::<'_, Params, u32>::new_async::<Return>(storage) }; 261 262 // Lift the parameters, either from flat storage or from linear 263 // memory. 264 let lift = &mut LiftContext::new(store.0.store_opaque_mut(), &options, instance); 265 lift.enter_call(); 266 let params = storage.lift_params(lift, param_tys)?; 267 268 // Load the return pointer, if present. 269 let retptr = match storage.async_retptr() { 270 Some(ptr) => { 271 let mut lower = 272 LowerContext::new(store.as_context_mut(), &options, &types, instance); 273 validate_inbounds::<Return>(lower.as_slice_mut(), ptr)? 274 } 275 // If there's no return pointer then `Return` should have an 276 // empty flat representation. In this situation pretend the 277 // return pointer was 0 so we have something to shepherd along 278 // into the closure below. 279 None => { 280 assert_eq!(Return::flatten_count(), 0); 281 0 282 } 283 }; 284 285 let host_result = closure(store.as_context_mut(), params); 286 287 let mut lower_result = { 288 let types = types.clone(); 289 move |store: StoreContextMut<T>, ret: Return| { 290 unsafe { 291 flags.set_may_leave(false); 292 } 293 let mut lower = LowerContext::new(store, &options, &types, instance); 294 ret.linear_lower_to_memory(&mut lower, result_tys, retptr)?; 295 unsafe { 296 flags.set_may_leave(true); 297 } 298 lower.exit_call()?; 299 Ok(()) 300 } 301 }; 302 let task = match host_result { 303 HostResult::Done(result) => { 304 lower_result(store.as_context_mut(), result?)?; 305 None 306 } 307 #[cfg(feature = "component-model-async")] 308 HostResult::Future(future) => instance.first_poll( 309 store.as_context_mut(), 310 future, 311 caller_instance, 312 lower_result, 313 )?, 314 }; 315 316 let status = if let Some(task) = task { 317 Status::Started.pack(Some(task)) 318 } else { 319 Status::Returned.pack(None) 320 }; 321 322 let mut lower = LowerContext::new(store, &options, &types, instance); 323 storage.lower_results(&mut lower, InterfaceType::U32, status)?; 324 } 325 #[cfg(not(feature = "component-model-async"))] 326 { 327 let _ = caller_instance; 328 unreachable!( 329 "async-lowered imports should have failed validation \ 330 when `component-model-async` feature disabled" 331 ); 332 } 333 } else { 334 let mut storage = unsafe { Storage::<'_, Params, Return>::new_sync(storage) }; 335 let mut lift = LiftContext::new(store.0.store_opaque_mut(), &options, instance); 336 lift.enter_call(); 337 let params = storage.lift_params(&mut lift, param_tys)?; 338 339 let ret = match closure(store.as_context_mut(), params) { 340 HostResult::Done(result) => result?, 341 #[cfg(feature = "component-model-async")] 342 HostResult::Future(future) => { 343 concurrent::poll_and_block(store.0, future, caller_instance)? 344 } 345 }; 346 347 unsafe { 348 flags.set_may_leave(false); 349 } 350 let mut lower = LowerContext::new(store, &options, &types, instance); 351 storage.lower_results(&mut lower, result_tys, ret)?; 352 unsafe { 353 flags.set_may_leave(true); 354 } 355 lower.exit_call()?; 356 } 357 358 return Ok(()); 359 360 /// Type-level representation of the matrix of possibilities of how 361 /// WebAssembly parameters and results are handled in the canonical ABI. 362 /// 363 /// Wasmtime's ABI here always works with `&mut [MaybeUninit<ValRaw>]` as the 364 /// base representation of params/results. Parameters are passed 365 /// sequentially and results are returned by overwriting the parameters. 366 /// That means both params/results start from index 0. 367 /// 368 /// The type-level representation here involves working with the typed 369 /// `P::Lower` and `R::Lower` values which is a type-level representation of 370 /// a lowered value. All lowered values are in essence a sequence of 371 /// `ValRaw` values one after the other to fit within this original array 372 /// that is the basis of Wasmtime's ABI. 373 /// 374 /// The various combinations here are cryptic, but only used in this file. 375 /// This in theory cuts down on the verbosity below, but an explanation of 376 /// the various acronyms here are: 377 /// 378 /// * Pd - params direct - means that parameters are passed directly in 379 /// their flat representation via `P::Lower`. 380 /// 381 /// * Pi - params indirect - means that parameters are passed indirectly in 382 /// linear memory and the argument here is `ValRaw` to store the pointer. 383 /// 384 /// * Rd - results direct - means that results are returned directly in 385 /// their flat representation via `R::Lower`. Note that this is always 386 /// represented as `MaybeUninit<R::Lower>` as well because the return 387 /// values may point to uninitialized memory if there were no parameters 388 /// for example. 389 /// 390 /// * Ri - results indirect - means that results are returned indirectly in 391 /// linear memory through the pointer specified. Note that this is 392 /// specified as a `ValRaw` to represent the argument that's being given 393 /// to the host from WebAssembly. 394 /// 395 /// * Ar - async results - means that the parameters to this call 396 /// additionally include an async result pointer. Async results are always 397 /// transmitted via a pointer so this is always a `ValRaw`. 398 /// 399 /// Internally this type makes liberal use of `Union` and `Pair` helpers 400 /// below which are simple `#[repr(C)]` wrappers around a pair of types that 401 /// are a union or a pair. 402 /// 403 /// Note that for any combination of `P` and `R` this `enum` is actually 404 /// pointless as a single variant will be used. In theory we should be able 405 /// to monomorphize based on `P` and `R` to a specific type. This 406 /// monomorphization depends on conditionals like `flatten_count() <= N`, 407 /// however, and I don't know how to encode that in Rust easily. In lieu of 408 /// that we assume LLVM will figure things out and boil away the actual enum 409 /// and runtime dispatch. 410 enum Storage<'a, P: ComponentType, R: ComponentType> { 411 /// Params: direct, Results: direct 412 /// 413 /// The lowered representation of params/results are overlaid on top of 414 /// each other. 415 PdRd(&'a mut Union<P::Lower, MaybeUninit<R::Lower>>), 416 417 /// Params: direct, Results: indirect 418 /// 419 /// The return pointer comes after the params so this is sequentially 420 /// laid out with one after the other. 421 PdRi(&'a Pair<P::Lower, ValRaw>), 422 423 /// Params: indirect, Results: direct 424 /// 425 /// Here the return values are overlaid on top of the pointer parameter. 426 PiRd(&'a mut Union<ValRaw, MaybeUninit<R::Lower>>), 427 428 /// Params: indirect, Results: indirect 429 /// 430 /// Here the two parameters are laid out sequentially one after the 431 /// other. 432 PiRi(&'a Pair<ValRaw, ValRaw>), 433 434 /// Params: direct + async result, Results: direct 435 /// 436 /// This is like `PdRd` except that the parameters additionally include 437 /// a pointer for where to store the result. 438 #[cfg(feature = "component-model-async")] 439 PdArRd(&'a mut Union<Pair<P::Lower, ValRaw>, MaybeUninit<R::Lower>>), 440 441 /// Params: indirect + async result, Results: direct 442 /// 443 /// This is like `PiRd` except that the parameters additionally include 444 /// a pointer for where to store the result. 445 #[cfg(feature = "component-model-async")] 446 PiArRd(&'a mut Union<Pair<ValRaw, ValRaw>, MaybeUninit<R::Lower>>), 447 } 448 449 // Helper structure used above in `Storage` to represent two consecutive 450 // values. 451 #[repr(C)] 452 #[derive(Copy, Clone)] 453 struct Pair<T, U> { 454 a: T, 455 b: U, 456 } 457 458 // Helper structure used above in `Storage` to represent two values overlaid 459 // on each other. 460 #[repr(C)] 461 union Union<T: Copy, U: Copy> { 462 a: T, 463 b: U, 464 } 465 466 /// Representation of where parameters are lifted from. 467 enum Src<'a, T> { 468 /// Parameters are directly lifted from `T`, which is under the hood a 469 /// sequence of `ValRaw`. This is `P::Lower` for example. 470 Direct(&'a T), 471 472 /// Parameters are loaded from linear memory, and this is the wasm 473 /// parameter representing the pointer into linear memory to load from. 474 Indirect(&'a ValRaw), 475 } 476 477 /// Dual of [`Src`], where to store results. 478 enum Dst<'a, T> { 479 /// Results are stored directly in this pointer. 480 /// 481 /// Note that this is a mutable pointer but it's specifically 482 /// `MaybeUninit` as trampolines do not initialize it. The `T` here will 483 /// be `R::Lower` for example. 484 Direct(&'a mut MaybeUninit<T>), 485 486 /// Results are stored in linear memory, and this value is the wasm 487 /// parameter given which represents the pointer into linear memory. 488 /// 489 /// Note that this is not mutable as the parameter is not mutated, but 490 /// memory will be mutated. 491 Indirect(&'a ValRaw), 492 } 493 494 impl<P, R> Storage<'_, P, R> 495 where 496 P: ComponentType + Lift, 497 R: ComponentType + Lower, 498 { 499 /// Classifies a new `Storage` suitable for use with sync functions. 500 /// 501 /// There's a 2x2 matrix of whether parameters and results are stored on the 502 /// stack or on the heap. Each of the 4 branches here have a different 503 /// representation of the storage of arguments/returns. 504 /// 505 /// Also note that while four branches are listed here only one is taken for 506 /// any particular `Params` and `Return` combination. This should be 507 /// trivially DCE'd by LLVM. Perhaps one day with enough const programming in 508 /// Rust we can make monomorphizations of this function codegen only one 509 /// branch, but today is not that day. 510 /// 511 /// # Safety 512 /// 513 /// Requires that the `storage` provided does indeed match an wasm 514 /// function with the signature of `P` and `R` as params/results. 515 unsafe fn new_sync(storage: &mut [MaybeUninit<ValRaw>]) -> Storage<'_, P, R> { 516 // SAFETY: this `unsafe` is due to the `slice_to_storage_*` helpers 517 // used which view the slice provided as a different type. This 518 // safety should be upheld by the contract of the `ComponentType` 519 // trait and its `Lower` type parameter meaning they're valid to 520 // view as a sequence of `ValRaw` types. Additionally the 521 // `ComponentType` trait ensures that the matching of the runtime 522 // length of `storage` should match the actual size of `P::Lower` 523 // and `R::Lower` or such as needed. 524 unsafe { 525 if P::flatten_count() <= MAX_FLAT_PARAMS { 526 if R::flatten_count() <= MAX_FLAT_RESULTS { 527 Storage::PdRd(slice_to_storage_mut(storage).assume_init_mut()) 528 } else { 529 Storage::PdRi(slice_to_storage_mut(storage).assume_init_ref()) 530 } 531 } else { 532 if R::flatten_count() <= MAX_FLAT_RESULTS { 533 Storage::PiRd(slice_to_storage_mut(storage).assume_init_mut()) 534 } else { 535 Storage::PiRi(slice_to_storage_mut(storage).assume_init_ref()) 536 } 537 } 538 } 539 } 540 541 fn lift_params(&self, cx: &mut LiftContext<'_>, ty: InterfaceType) -> Result<P> { 542 match self.lift_src() { 543 Src::Direct(storage) => P::linear_lift_from_flat(cx, ty, storage), 544 Src::Indirect(ptr) => { 545 let ptr = validate_inbounds::<P>(cx.memory(), ptr)?; 546 P::linear_lift_from_memory(cx, ty, &cx.memory()[ptr..][..P::SIZE32]) 547 } 548 } 549 } 550 551 fn lift_src(&self) -> Src<'_, P::Lower> { 552 match self { 553 // SAFETY: these `unsafe` blocks are due to accessing union 554 // fields. The safety here relies on the contract of the 555 // `ComponentType` trait which should ensure that the types 556 // projected onto a list of wasm parameters are indeed correct. 557 // That means that the projections here, if the types are 558 // correct, all line up to initialized memory that's well-typed 559 // to access. 560 Storage::PdRd(storage) => unsafe { Src::Direct(&storage.a) }, 561 Storage::PdRi(storage) => Src::Direct(&storage.a), 562 #[cfg(feature = "component-model-async")] 563 Storage::PdArRd(storage) => unsafe { Src::Direct(&storage.a.a) }, 564 Storage::PiRd(storage) => unsafe { Src::Indirect(&storage.a) }, 565 Storage::PiRi(storage) => Src::Indirect(&storage.a), 566 #[cfg(feature = "component-model-async")] 567 Storage::PiArRd(storage) => unsafe { Src::Indirect(&storage.a.a) }, 568 } 569 } 570 571 fn lower_results<T>( 572 &mut self, 573 cx: &mut LowerContext<'_, T>, 574 ty: InterfaceType, 575 ret: R, 576 ) -> Result<()> { 577 match self.lower_dst() { 578 Dst::Direct(storage) => ret.linear_lower_to_flat(cx, ty, storage), 579 Dst::Indirect(ptr) => { 580 let ptr = validate_inbounds::<R>(cx.as_slice_mut(), ptr)?; 581 ret.linear_lower_to_memory(cx, ty, ptr) 582 } 583 } 584 } 585 586 fn lower_dst(&mut self) -> Dst<'_, R::Lower> { 587 match self { 588 // SAFETY: these unsafe blocks are due to accessing fields of a 589 // `union` which is not safe in Rust. The returned value is 590 // `MaybeUninit<R::Lower>` in all cases, however, which should 591 // safely model how `union` memory is possibly uninitialized. 592 // Additionally `R::Lower` has the `unsafe` contract that all 593 // its bit patterns must be sound, which additionally should 594 // help make this safe. 595 Storage::PdRd(storage) => unsafe { Dst::Direct(&mut storage.b) }, 596 Storage::PiRd(storage) => unsafe { Dst::Direct(&mut storage.b) }, 597 #[cfg(feature = "component-model-async")] 598 Storage::PdArRd(storage) => unsafe { Dst::Direct(&mut storage.b) }, 599 #[cfg(feature = "component-model-async")] 600 Storage::PiArRd(storage) => unsafe { Dst::Direct(&mut storage.b) }, 601 Storage::PdRi(storage) => Dst::Indirect(&storage.b), 602 Storage::PiRi(storage) => Dst::Indirect(&storage.b), 603 } 604 } 605 606 #[cfg(feature = "component-model-async")] 607 fn async_retptr(&self) -> Option<&ValRaw> { 608 match self { 609 // SAFETY: like above these are `unsafe` due to accessing a 610 // `union` field. This should be safe via the construction of 611 // `Storage` which should correctly determine whether or not an 612 // async return pointer is provided and classify the args/rets 613 // appropriately. 614 Storage::PdArRd(storage) => unsafe { Some(&storage.a.b) }, 615 Storage::PiArRd(storage) => unsafe { Some(&storage.a.b) }, 616 Storage::PdRd(_) | Storage::PiRd(_) | Storage::PdRi(_) | Storage::PiRi(_) => None, 617 } 618 } 619 } 620 621 #[cfg(feature = "component-model-async")] 622 impl<P> Storage<'_, P, u32> 623 where 624 P: ComponentType + Lift, 625 { 626 /// Classifies a new `Storage` suitable for use with async functions. 627 /// 628 /// # Safety 629 /// 630 /// Requires that the `storage` provided does indeed match an `async` 631 /// wasm function with the signature of `P` and `R` as params/results. 632 unsafe fn new_async<R>(storage: &mut [MaybeUninit<ValRaw>]) -> Storage<'_, P, u32> 633 where 634 R: ComponentType + Lower, 635 { 636 // SAFETY: see `Storage::new` for discussion on why this should be 637 // safe given the unsafe contract of the `ComponentType` trait. 638 unsafe { 639 if P::flatten_count() <= wasmtime_environ::component::MAX_FLAT_ASYNC_PARAMS { 640 if R::flatten_count() == 0 { 641 Storage::PdRd(slice_to_storage_mut(storage).assume_init_mut()) 642 } else { 643 Storage::PdArRd(slice_to_storage_mut(storage).assume_init_mut()) 644 } 645 } else { 646 if R::flatten_count() == 0 { 647 Storage::PiRd(slice_to_storage_mut(storage).assume_init_mut()) 648 } else { 649 Storage::PiArRd(slice_to_storage_mut(storage).assume_init_mut()) 650 } 651 } 652 } 653 } 654 } 655 } 656 657 pub(crate) fn validate_inbounds<T: ComponentType>(memory: &[u8], ptr: &ValRaw) -> Result<usize> { 658 // FIXME(#4311): needs memory64 support 659 let ptr = usize::try_from(ptr.get_u32())?; 660 if ptr % usize::try_from(T::ALIGN32)? != 0 { 661 bail!("pointer not aligned"); 662 } 663 let end = match ptr.checked_add(T::SIZE32) { 664 Some(n) => n, 665 None => bail!("pointer size overflow"), 666 }; 667 if end > memory.len() { 668 bail!("pointer out of bounds") 669 } 670 Ok(ptr) 671 } 672 673 unsafe fn call_host_and_handle_result<T>( 674 cx: NonNull<VMOpaqueContext>, 675 func: impl FnOnce(StoreContextMut<'_, T>, Instance) -> Result<()>, 676 ) -> bool 677 where 678 T: 'static, 679 { 680 let cx = unsafe { VMComponentContext::from_opaque(cx) }; 681 unsafe { 682 ComponentInstance::enter_host_from_wasm(cx, |store, instance| { 683 let mut store = store.unchecked_context_mut(); 684 store.0.call_hook(CallHook::CallingHost)?; 685 let res = func(store.as_context_mut(), instance); 686 store.0.call_hook(CallHook::ReturningFromHost)?; 687 res 688 }) 689 } 690 } 691 692 unsafe fn call_host_dynamic<T, F>( 693 mut store: StoreContextMut<'_, T>, 694 instance: Instance, 695 ty: TypeFuncIndex, 696 options_idx: OptionsIndex, 697 storage: &mut [MaybeUninit<ValRaw>], 698 closure: F, 699 ) -> Result<()> 700 where 701 F: Fn( 702 StoreContextMut<'_, T>, 703 Vec<Val>, 704 usize, 705 ) -> Pin<Box<dyn Future<Output = Result<Vec<Val>>> + Send + 'static>> 706 + Send 707 + Sync 708 + 'static, 709 T: 'static, 710 { 711 let options = Options::new_index(store.0, instance, options_idx); 712 let vminstance = instance.id().get(store.0); 713 let opts = &vminstance.component().env_component().options[options_idx]; 714 let async_ = opts.async_; 715 let caller_instance = opts.instance; 716 let mut flags = vminstance.instance_flags(caller_instance); 717 718 // Perform a dynamic check that this instance can indeed be left. Exiting 719 // the component is disallowed, for example, when the `realloc` function 720 // calls a canonical import. 721 if unsafe { !flags.may_leave() } { 722 return Err(anyhow!(crate::Trap::CannotLeaveComponent)); 723 } 724 725 let types = instance.id().get(store.0).component().types().clone(); 726 let func_ty = &types[ty]; 727 let param_tys = &types[func_ty.params]; 728 let result_tys = &types[func_ty.results]; 729 730 let mut params_and_results = Vec::new(); 731 let mut lift = &mut LiftContext::new(store.0.store_opaque_mut(), &options, instance); 732 lift.enter_call(); 733 let max_flat = if async_ { 734 MAX_FLAT_ASYNC_PARAMS 735 } else { 736 MAX_FLAT_PARAMS 737 }; 738 739 let ret_index = unsafe { 740 dynamic_params_load( 741 &mut lift, 742 &types, 743 storage, 744 param_tys, 745 &mut params_and_results, 746 max_flat, 747 )? 748 }; 749 let result_start = params_and_results.len(); 750 for _ in 0..result_tys.types.len() { 751 params_and_results.push(Val::Bool(false)); 752 } 753 754 if async_ { 755 #[cfg(feature = "component-model-async")] 756 { 757 let retptr = if result_tys.types.len() == 0 { 758 0 759 } else { 760 let retptr = unsafe { storage[ret_index].assume_init() }; 761 let mut lower = 762 LowerContext::new(store.as_context_mut(), &options, &types, instance); 763 validate_inbounds_dynamic(&result_tys.abi, lower.as_slice_mut(), &retptr)? 764 }; 765 766 let future = closure(store.as_context_mut(), params_and_results, result_start); 767 768 let task = instance.first_poll(store, future, caller_instance, { 769 let types = types.clone(); 770 let result_tys = func_ty.results; 771 move |store: StoreContextMut<T>, result_vals: Vec<Val>| { 772 let result_tys = &types[result_tys]; 773 let result_vals = &result_vals[result_start..]; 774 assert_eq!(result_vals.len(), result_tys.types.len()); 775 776 unsafe { 777 flags.set_may_leave(false); 778 } 779 780 let mut lower = LowerContext::new(store, &options, &types, instance); 781 let mut ptr = retptr; 782 for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) { 783 let offset = types.canonical_abi(ty).next_field32_size(&mut ptr); 784 val.store(&mut lower, *ty, offset)?; 785 } 786 787 unsafe { 788 flags.set_may_leave(true); 789 } 790 791 lower.exit_call()?; 792 793 Ok(()) 794 } 795 })?; 796 797 let status = if let Some(task) = task { 798 Status::Started.pack(Some(task)) 799 } else { 800 Status::Returned.pack(None) 801 }; 802 803 storage[0] = MaybeUninit::new(ValRaw::i32(status as i32)); 804 } 805 #[cfg(not(feature = "component-model-async"))] 806 { 807 unreachable!( 808 "async-lowered imports should have failed validation \ 809 when `component-model-async` feature disabled" 810 ); 811 } 812 } else { 813 let future = closure(store.as_context_mut(), params_and_results, result_start); 814 let result_vals = concurrent::poll_and_block(store.0, future, caller_instance)?; 815 let result_vals = &result_vals[result_start..]; 816 817 unsafe { 818 flags.set_may_leave(false); 819 } 820 821 let mut cx = LowerContext::new(store, &options, &types, instance); 822 if let Some(cnt) = result_tys.abi.flat_count(MAX_FLAT_RESULTS) { 823 let mut dst = storage[..cnt].iter_mut(); 824 for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) { 825 val.lower(&mut cx, *ty, &mut dst)?; 826 } 827 assert!(dst.next().is_none()); 828 } else { 829 let ret_ptr = unsafe { storage[ret_index].assume_init_ref() }; 830 let mut ptr = validate_inbounds_dynamic(&result_tys.abi, cx.as_slice_mut(), ret_ptr)?; 831 for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) { 832 let offset = types.canonical_abi(ty).next_field32_size(&mut ptr); 833 val.store(&mut cx, *ty, offset)?; 834 } 835 } 836 837 unsafe { 838 flags.set_may_leave(true); 839 } 840 841 cx.exit_call()?; 842 } 843 844 Ok(()) 845 } 846 847 /// Loads the parameters for a dynamic host function call into `params` 848 /// 849 /// Returns the number of flat `storage` values consumed. 850 /// 851 /// # Safety 852 /// 853 /// Requires that `param_tys` matches the type signature of the `storage` that 854 /// was passed in. 855 unsafe fn dynamic_params_load( 856 cx: &mut LiftContext<'_>, 857 types: &ComponentTypes, 858 storage: &[MaybeUninit<ValRaw>], 859 param_tys: &TypeTuple, 860 params: &mut Vec<Val>, 861 max_flat_params: usize, 862 ) -> Result<usize> { 863 if let Some(param_count) = param_tys.abi.flat_count(max_flat_params) { 864 // NB: can use `MaybeUninit::slice_assume_init_ref` when that's stable 865 let storage = 866 unsafe { mem::transmute::<&[MaybeUninit<ValRaw>], &[ValRaw]>(&storage[..param_count]) }; 867 let mut iter = storage.iter(); 868 for ty in param_tys.types.iter() { 869 params.push(Val::lift(cx, *ty, &mut iter)?); 870 } 871 assert!(iter.next().is_none()); 872 Ok(param_count) 873 } else { 874 let mut offset = validate_inbounds_dynamic(¶m_tys.abi, cx.memory(), unsafe { 875 storage[0].assume_init_ref() 876 })?; 877 for ty in param_tys.types.iter() { 878 let abi = types.canonical_abi(ty); 879 let size = usize::try_from(abi.size32).unwrap(); 880 let memory = &cx.memory()[abi.next_field32_size(&mut offset)..][..size]; 881 params.push(Val::load(cx, *ty, memory)?); 882 } 883 Ok(1) 884 } 885 } 886 887 pub(crate) fn validate_inbounds_dynamic( 888 abi: &CanonicalAbiInfo, 889 memory: &[u8], 890 ptr: &ValRaw, 891 ) -> Result<usize> { 892 // FIXME(#4311): needs memory64 support 893 let ptr = usize::try_from(ptr.get_u32())?; 894 if ptr % usize::try_from(abi.align32)? != 0 { 895 bail!("pointer not aligned"); 896 } 897 let end = match ptr.checked_add(usize::try_from(abi.size32).unwrap()) { 898 Some(n) => n, 899 None => bail!("pointer size overflow"), 900 }; 901 if end > memory.len() { 902 bail!("pointer out of bounds") 903 } 904 Ok(ptr) 905 } 906 907 extern "C" fn dynamic_entrypoint<T, F>( 908 cx: NonNull<VMOpaqueContext>, 909 data: NonNull<u8>, 910 ty: u32, 911 options: u32, 912 storage: NonNull<MaybeUninit<ValRaw>>, 913 storage_len: usize, 914 ) -> bool 915 where 916 F: Fn( 917 StoreContextMut<'_, T>, 918 Vec<Val>, 919 usize, 920 ) -> Pin<Box<dyn Future<Output = Result<Vec<Val>>> + Send + 'static>> 921 + Send 922 + Sync 923 + 'static, 924 T: 'static, 925 { 926 let data = SendSyncPtr::new(NonNull::new(data.as_ptr() as *mut F).unwrap()); 927 unsafe { 928 call_host_and_handle_result(cx, |store, instance| { 929 call_host_dynamic::<T, _>( 930 store, 931 instance, 932 TypeFuncIndex::from_u32(ty), 933 OptionsIndex::from_u32(options), 934 NonNull::slice_from_raw_parts(storage, storage_len).as_mut(), 935 move |store, params, results| (*data.as_ptr())(store, params, results), 936 ) 937 }) 938 } 939 } 940