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