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(&mut 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 mut 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 = 287 &mut LiftContext::new(store.0.store_opaque_mut(), &options, &types, instance); 288 lift.enter_call(); 289 let params = storage.lift_params(lift, param_tys)?; 290 291 // Load the return pointer, if present. 292 let retptr = match storage.async_retptr() { 293 Some(ptr) => { 294 let mut lower = 295 LowerContext::new(store.as_context_mut(), &options, &types, instance); 296 validate_inbounds::<Return>(lower.as_slice_mut(), ptr)? 297 } 298 // If there's no return pointer then `Return` should have an 299 // empty flat representation. In this situation pretend the 300 // return pointer was 0 so we have something to shepherd along 301 // into the closure below. 302 None => { 303 assert_eq!(Return::flatten_count(), 0); 304 0 305 } 306 }; 307 308 let host_result = closure(store.as_context_mut(), instance, params); 309 310 let mut lower_result = { 311 let types = types.clone(); 312 move |store: StoreContextMut<T>, instance: Instance, ret: Return| { 313 flags.set_may_leave(false); 314 let mut lower = LowerContext::new(store, &options, &types, instance); 315 ret.linear_lower_to_memory(&mut lower, result_tys, retptr)?; 316 flags.set_may_leave(true); 317 lower.exit_call()?; 318 Ok(()) 319 } 320 }; 321 let task = match host_result { 322 HostResult::Done(result) => { 323 lower_result(store.as_context_mut(), instance, result?)?; 324 None 325 } 326 #[cfg(feature = "component-model-async")] 327 HostResult::Future(future) => instance.first_poll( 328 store.as_context_mut(), 329 future, 330 caller_instance, 331 lower_result, 332 )?, 333 }; 334 335 let status = if let Some(task) = task { 336 Status::Started.pack(Some(task)) 337 } else { 338 Status::Returned.pack(None) 339 }; 340 341 let mut lower = LowerContext::new(store, &options, &types, instance); 342 storage.lower_results(&mut lower, InterfaceType::U32, status)?; 343 } 344 #[cfg(not(feature = "component-model-async"))] 345 { 346 let _ = caller_instance; 347 unreachable!( 348 "async-lowered imports should have failed validation \ 349 when `component-model-async` feature disabled" 350 ); 351 } 352 } else { 353 let mut storage = Storage::<'_, Params, Return>::new_sync(storage); 354 let mut lift = LiftContext::new(store.0.store_opaque_mut(), &options, &types, instance); 355 lift.enter_call(); 356 let params = storage.lift_params(&mut lift, param_tys)?; 357 358 let ret = match closure(store.as_context_mut(), instance, params) { 359 HostResult::Done(result) => result?, 360 #[cfg(feature = "component-model-async")] 361 HostResult::Future(future) => { 362 instance.poll_and_block(store.0.traitobj_mut(), future, caller_instance)? 363 } 364 }; 365 366 flags.set_may_leave(false); 367 let mut lower = LowerContext::new(store, &options, &types, instance); 368 storage.lower_results(&mut lower, result_tys, ret)?; 369 flags.set_may_leave(true); 370 lower.exit_call()?; 371 } 372 373 return Ok(()); 374 375 /// Type-level representation of the matrix of possibilities of how 376 /// WebAssembly parameters and results are handled in the canonical ABI. 377 /// 378 /// Wasmtime's ABI here always works with `&mut [MaybeUninit<ValRaw>]` as the 379 /// base representation of params/results. Parameters are passed 380 /// sequentially and results are returned by overwriting the parameters. 381 /// That means both params/results start from index 0. 382 /// 383 /// The type-level representation here involves working with the typed 384 /// `P::Lower` and `R::Lower` values which is a type-level representation of 385 /// a lowered value. All lowered values are in essence a sequence of 386 /// `ValRaw` values one after the other to fit within this original array 387 /// that is the basis of Wasmtime's ABI. 388 /// 389 /// The various combinations here are cryptic, but only used in this file. 390 /// This in theory cuts down on the verbosity below, but an explanation of 391 /// the various acronyms here are: 392 /// 393 /// * Pd - params direct - means that parameters are passed directly in 394 /// their flat representation via `P::Lower`. 395 /// 396 /// * Pi - params indirect - means that parameters are passed indirectly in 397 /// linear memory and the argument here is `ValRaw` to store the pointer. 398 /// 399 /// * Rd - results direct - means that results are returned directly in 400 /// their flat representation via `R::Lower`. Note that this is always 401 /// represented as `MaybeUninit<R::Lower>` as well because the return 402 /// values may point to uninitialized memory if there were no parameters 403 /// for example. 404 /// 405 /// * Ri - results indirect - means that results are returned indirectly in 406 /// linear memory through the pointer specified. Note that this is 407 /// specified as a `ValRaw` to represent the argument that's being given 408 /// to the host from WebAssembly. 409 /// 410 /// * Ar - async results - means that the parameters to this call 411 /// additionally include an async result pointer. Async results are always 412 /// transmitted via a pointer so this is always a `ValRaw`. 413 /// 414 /// Internally this type makes liberal use of `Union` and `Pair` helpers 415 /// below which are simple `#[repr(C)]` wrappers around a pair of types that 416 /// are a union or a pair. 417 /// 418 /// Note that for any combination of `P` and `R` this `enum` is actually 419 /// pointless as a single variant will be used. In theory we should be able 420 /// to monomorphize based on `P` and `R` to a specific type. This 421 /// monomorphization depends on conditionals like `flatten_count() <= N`, 422 /// however, and I don't know how to encode that in Rust easily. In lieu of 423 /// that we assume LLVM will figure things out and boil away the actual enum 424 /// and runtime dispatch. 425 enum Storage<'a, P: ComponentType, R: ComponentType> { 426 /// Params: direct, Results: direct 427 /// 428 /// The lowered representation of params/results are overlaid on top of 429 /// each other. 430 PdRd(&'a mut Union<P::Lower, MaybeUninit<R::Lower>>), 431 432 /// Params: direct, Results: indirect 433 /// 434 /// The return pointer comes after the params so this is sequentially 435 /// laid out with one after the other. 436 PdRi(&'a Pair<P::Lower, ValRaw>), 437 438 /// Params: indirect, Results: direct 439 /// 440 /// Here the return values are overlaid on top of the pointer parameter. 441 PiRd(&'a mut Union<ValRaw, MaybeUninit<R::Lower>>), 442 443 /// Params: indirect, Results: indirect 444 /// 445 /// Here the two parameters are laid out sequentially one after the 446 /// other. 447 PiRi(&'a Pair<ValRaw, ValRaw>), 448 449 /// Params: direct + async result, Results: direct 450 /// 451 /// This is like `PdRd` except that the parameters additionally include 452 /// a pointer for where to store the result. 453 #[cfg(feature = "component-model-async")] 454 PdArRd(&'a mut Union<Pair<P::Lower, ValRaw>, MaybeUninit<R::Lower>>), 455 456 /// Params: indirect + async result, Results: direct 457 /// 458 /// This is like `PiRd` except that the parameters additionally include 459 /// a pointer for where to store the result. 460 #[cfg(feature = "component-model-async")] 461 PiArRd(&'a mut Union<Pair<ValRaw, ValRaw>, MaybeUninit<R::Lower>>), 462 } 463 464 // Helper structure used above in `Storage` to represent two consecutive 465 // values. 466 #[repr(C)] 467 #[derive(Copy, Clone)] 468 struct Pair<T, U> { 469 a: T, 470 b: U, 471 } 472 473 // Helper structure used above in `Storage` to represent two values overlaid 474 // on each other. 475 #[repr(C)] 476 union Union<T: Copy, U: Copy> { 477 a: T, 478 b: U, 479 } 480 481 /// Representation of where parameters are lifted from. 482 enum Src<'a, T> { 483 /// Parameters are directly lifted from `T`, which is under the hood a 484 /// sequence of `ValRaw`. This is `P::Lower` for example. 485 Direct(&'a T), 486 487 /// Parameters are loaded from linear memory, and this is the wasm 488 /// parameter representing the pointer into linear memory to load from. 489 Indirect(&'a ValRaw), 490 } 491 492 /// Dual of [`Src`], where to store results. 493 enum Dst<'a, T> { 494 /// Results are stored directly in this pointer. 495 /// 496 /// Note that this is a mutable pointer but it's specifically 497 /// `MaybeUninit` as trampolines do not initialize it. The `T` here will 498 /// be `R::Lower` for example. 499 Direct(&'a mut MaybeUninit<T>), 500 501 /// Results are stored in linear memory, and this value is the wasm 502 /// parameter given which represents the pointer into linear memory. 503 /// 504 /// Note that this is not mutable as the parameter is not mutated, but 505 /// memory will be mutated. 506 Indirect(&'a ValRaw), 507 } 508 509 impl<P, R> Storage<'_, P, R> 510 where 511 P: ComponentType + Lift, 512 R: ComponentType + Lower, 513 { 514 /// Classifies a new `Storage` suitable for use with sync functions. 515 /// 516 /// There's a 2x2 matrix of whether parameters and results are stored on the 517 /// stack or on the heap. Each of the 4 branches here have a different 518 /// representation of the storage of arguments/returns. 519 /// 520 /// Also note that while four branches are listed here only one is taken for 521 /// any particular `Params` and `Return` combination. This should be 522 /// trivially DCE'd by LLVM. Perhaps one day with enough const programming in 523 /// Rust we can make monomorphizations of this function codegen only one 524 /// branch, but today is not that day. 525 /// 526 /// # Safety 527 /// 528 /// Requires that the `storage` provided does indeed match an wasm 529 /// function with the signature of `P` and `R` as params/results. 530 unsafe fn new_sync(storage: &mut [MaybeUninit<ValRaw>]) -> Storage<'_, P, R> { 531 // SAFETY: this `unsafe` is due to the `slice_to_storage_*` helpers 532 // used which view the slice provided as a different type. This 533 // safety should be upheld by the contract of the `ComponentType` 534 // trait and its `Lower` type parameter meaning they're valid to 535 // view as a sequence of `ValRaw` types. Additionally the 536 // `ComponentType` trait ensures that the matching of the runtime 537 // length of `storage` should match the actual size of `P::Lower` 538 // and `R::Lower` or such as needed. 539 unsafe { 540 if P::flatten_count() <= MAX_FLAT_PARAMS { 541 if R::flatten_count() <= MAX_FLAT_RESULTS { 542 Storage::PdRd(slice_to_storage_mut(storage).assume_init_mut()) 543 } else { 544 Storage::PdRi(slice_to_storage_mut(storage).assume_init_ref()) 545 } 546 } else { 547 if R::flatten_count() <= MAX_FLAT_RESULTS { 548 Storage::PiRd(slice_to_storage_mut(storage).assume_init_mut()) 549 } else { 550 Storage::PiRi(slice_to_storage_mut(storage).assume_init_ref()) 551 } 552 } 553 } 554 } 555 556 fn lift_params(&self, cx: &mut LiftContext<'_>, ty: InterfaceType) -> Result<P> { 557 match self.lift_src() { 558 Src::Direct(storage) => P::linear_lift_from_flat(cx, ty, storage), 559 Src::Indirect(ptr) => { 560 let ptr = validate_inbounds::<P>(cx.memory(), ptr)?; 561 P::linear_lift_from_memory(cx, ty, &cx.memory()[ptr..][..P::SIZE32]) 562 } 563 } 564 } 565 566 fn lift_src(&self) -> Src<'_, P::Lower> { 567 match self { 568 // SAFETY: these `unsafe` blocks are due to accessing union 569 // fields. The safety here relies on the contract of the 570 // `ComponentType` trait which should ensure that the types 571 // projected onto a list of wasm parameters are indeed correct. 572 // That means that the projections here, if the types are 573 // correct, all line up to initialized memory that's well-typed 574 // to access. 575 Storage::PdRd(storage) => unsafe { Src::Direct(&storage.a) }, 576 Storage::PdRi(storage) => Src::Direct(&storage.a), 577 #[cfg(feature = "component-model-async")] 578 Storage::PdArRd(storage) => unsafe { Src::Direct(&storage.a.a) }, 579 Storage::PiRd(storage) => unsafe { Src::Indirect(&storage.a) }, 580 Storage::PiRi(storage) => Src::Indirect(&storage.a), 581 #[cfg(feature = "component-model-async")] 582 Storage::PiArRd(storage) => unsafe { Src::Indirect(&storage.a.a) }, 583 } 584 } 585 586 fn lower_results<T>( 587 &mut self, 588 cx: &mut LowerContext<'_, T>, 589 ty: InterfaceType, 590 ret: R, 591 ) -> Result<()> { 592 match self.lower_dst() { 593 Dst::Direct(storage) => ret.linear_lower_to_flat(cx, ty, storage), 594 Dst::Indirect(ptr) => { 595 let ptr = validate_inbounds::<R>(cx.as_slice_mut(), ptr)?; 596 ret.linear_lower_to_memory(cx, ty, ptr) 597 } 598 } 599 } 600 601 fn lower_dst(&mut self) -> Dst<'_, R::Lower> { 602 match self { 603 // SAFETY: these unsafe blocks are due to accessing fields of a 604 // `union` which is not safe in Rust. The returned value is 605 // `MaybeUninit<R::Lower>` in all cases, however, which should 606 // safely model how `union` memory is possibly uninitialized. 607 // Additionally `R::Lower` has the `unsafe` contract that all 608 // its bit patterns must be sound, which additionally should 609 // help make this safe. 610 Storage::PdRd(storage) => unsafe { Dst::Direct(&mut storage.b) }, 611 Storage::PiRd(storage) => unsafe { Dst::Direct(&mut storage.b) }, 612 #[cfg(feature = "component-model-async")] 613 Storage::PdArRd(storage) => unsafe { Dst::Direct(&mut storage.b) }, 614 #[cfg(feature = "component-model-async")] 615 Storage::PiArRd(storage) => unsafe { Dst::Direct(&mut storage.b) }, 616 Storage::PdRi(storage) => Dst::Indirect(&storage.b), 617 Storage::PiRi(storage) => Dst::Indirect(&storage.b), 618 } 619 } 620 621 #[cfg(feature = "component-model-async")] 622 fn async_retptr(&self) -> Option<&ValRaw> { 623 match self { 624 // SAFETY: like above these are `unsafe` due to accessing a 625 // `union` field. This should be safe via the construction of 626 // `Storage` which should correctly determine whether or not an 627 // async return pointer is provided and classify the args/rets 628 // appropriately. 629 Storage::PdArRd(storage) => unsafe { Some(&storage.a.b) }, 630 Storage::PiArRd(storage) => unsafe { Some(&storage.a.b) }, 631 Storage::PdRd(_) | Storage::PiRd(_) | Storage::PdRi(_) | Storage::PiRi(_) => None, 632 } 633 } 634 } 635 636 #[cfg(feature = "component-model-async")] 637 impl<P> Storage<'_, P, u32> 638 where 639 P: ComponentType + Lift, 640 { 641 /// Classifies a new `Storage` suitable for use with async functions. 642 /// 643 /// # Safety 644 /// 645 /// Requires that the `storage` provided does indeed match an `async` 646 /// wasm function with the signature of `P` and `R` as params/results. 647 unsafe fn new_async<R>(storage: &mut [MaybeUninit<ValRaw>]) -> Storage<'_, P, u32> 648 where 649 R: ComponentType + Lower, 650 { 651 // SAFETY: see `Storage::new` for discussion on why this should be 652 // safe given the unsafe contract of the `ComponentType` trait. 653 unsafe { 654 if P::flatten_count() <= wasmtime_environ::component::MAX_FLAT_ASYNC_PARAMS { 655 if R::flatten_count() == 0 { 656 Storage::PdRd(slice_to_storage_mut(storage).assume_init_mut()) 657 } else { 658 Storage::PdArRd(slice_to_storage_mut(storage).assume_init_mut()) 659 } 660 } else { 661 if R::flatten_count() == 0 { 662 Storage::PiRd(slice_to_storage_mut(storage).assume_init_mut()) 663 } else { 664 Storage::PiArRd(slice_to_storage_mut(storage).assume_init_mut()) 665 } 666 } 667 } 668 } 669 } 670 } 671 672 pub(crate) fn validate_inbounds<T: ComponentType>(memory: &[u8], ptr: &ValRaw) -> Result<usize> { 673 // FIXME(#4311): needs memory64 support 674 let ptr = usize::try_from(ptr.get_u32())?; 675 if ptr % usize::try_from(T::ALIGN32)? != 0 { 676 bail!("pointer not aligned"); 677 } 678 let end = match ptr.checked_add(T::SIZE32) { 679 Some(n) => n, 680 None => bail!("pointer size overflow"), 681 }; 682 if end > memory.len() { 683 bail!("pointer out of bounds") 684 } 685 Ok(ptr) 686 } 687 688 unsafe fn call_host_and_handle_result<T>( 689 cx: NonNull<VMOpaqueContext>, 690 func: impl FnOnce(StoreContextMut<'_, T>, Instance) -> Result<()>, 691 ) -> bool 692 where 693 T: 'static, 694 { 695 let cx = VMComponentContext::from_opaque(cx); 696 ComponentInstance::from_vmctx(cx, |store, instance| { 697 let mut store = store.unchecked_context_mut(); 698 699 crate::runtime::vm::catch_unwind_and_record_trap(|| { 700 store.0.call_hook(CallHook::CallingHost)?; 701 let res = func(store.as_context_mut(), instance); 702 store.0.call_hook(CallHook::ReturningFromHost)?; 703 res 704 }) 705 }) 706 } 707 708 unsafe fn call_host_dynamic<T, F>( 709 mut store: StoreContextMut<'_, T>, 710 instance: Instance, 711 ty: TypeFuncIndex, 712 caller_instance: RuntimeComponentInstanceIndex, 713 mut flags: InstanceFlags, 714 memory: *mut VMMemoryDefinition, 715 realloc: *mut VMFuncRef, 716 string_encoding: StringEncoding, 717 async_: bool, 718 storage: &mut [MaybeUninit<ValRaw>], 719 closure: F, 720 ) -> Result<()> 721 where 722 F: Fn( 723 StoreContextMut<'_, T>, 724 Instance, 725 Vec<Val>, 726 usize, 727 ) -> Pin<Box<dyn Future<Output = Result<Vec<Val>>> + Send + 'static>> 728 + Send 729 + Sync 730 + 'static, 731 T: 'static, 732 { 733 let options = Options::new( 734 store.0.store_opaque().id(), 735 NonNull::new(memory), 736 NonNull::new(realloc), 737 string_encoding, 738 async_, 739 None, 740 ); 741 742 // Perform a dynamic check that this instance can indeed be left. Exiting 743 // the component is disallowed, for example, when the `realloc` function 744 // calls a canonical import. 745 if !flags.may_leave() { 746 bail!("cannot leave component instance"); 747 } 748 749 let types = instance.id().get(store.0).component().types().clone(); 750 let func_ty = &types[ty]; 751 let param_tys = &types[func_ty.params]; 752 let result_tys = &types[func_ty.results]; 753 754 let mut params_and_results = Vec::new(); 755 let mut lift = &mut LiftContext::new(store.0.store_opaque_mut(), &options, &types, instance); 756 lift.enter_call(); 757 let max_flat = if async_ { 758 MAX_FLAT_ASYNC_PARAMS 759 } else { 760 MAX_FLAT_PARAMS 761 }; 762 763 let ret_index = dynamic_params_load( 764 &mut lift, 765 &types, 766 storage, 767 param_tys, 768 &mut params_and_results, 769 max_flat, 770 )?; 771 let result_start = params_and_results.len(); 772 for _ in 0..result_tys.types.len() { 773 params_and_results.push(Val::Bool(false)); 774 } 775 776 if async_ { 777 #[cfg(feature = "component-model-async")] 778 { 779 let retptr = if result_tys.types.len() == 0 { 780 0 781 } else { 782 let retptr = storage[ret_index].assume_init(); 783 let mut lower = 784 LowerContext::new(store.as_context_mut(), &options, &types, instance); 785 validate_inbounds_dynamic(&result_tys.abi, lower.as_slice_mut(), &retptr)? 786 }; 787 788 let future = closure( 789 store.as_context_mut(), 790 instance, 791 params_and_results, 792 result_start, 793 ); 794 795 let task = instance.first_poll(store, future, caller_instance, { 796 let types = types.clone(); 797 let result_tys = func_ty.results; 798 move |store: StoreContextMut<T>, instance: Instance, result_vals: Vec<Val>| { 799 let result_tys = &types[result_tys]; 800 let result_vals = &result_vals[result_start..]; 801 assert_eq!(result_vals.len(), result_tys.types.len()); 802 803 flags.set_may_leave(false); 804 805 let mut lower = LowerContext::new(store, &options, &types, instance); 806 let mut ptr = retptr; 807 for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) { 808 let offset = types.canonical_abi(ty).next_field32_size(&mut ptr); 809 val.store(&mut lower, *ty, offset)?; 810 } 811 812 flags.set_may_leave(true); 813 814 lower.exit_call()?; 815 816 Ok(()) 817 } 818 })?; 819 820 let status = if let Some(task) = task { 821 Status::Started.pack(Some(task)) 822 } else { 823 Status::Returned.pack(None) 824 }; 825 826 storage[0] = MaybeUninit::new(ValRaw::i32(status as i32)); 827 } 828 #[cfg(not(feature = "component-model-async"))] 829 { 830 unreachable!( 831 "async-lowered imports should have failed validation \ 832 when `component-model-async` feature disabled" 833 ); 834 } 835 } else { 836 let future = closure( 837 store.as_context_mut(), 838 instance, 839 params_and_results, 840 result_start, 841 ); 842 let result_vals = 843 instance.poll_and_block(store.0.traitobj_mut(), future, caller_instance)?; 844 let result_vals = &result_vals[result_start..]; 845 846 flags.set_may_leave(false); 847 848 let mut cx = LowerContext::new(store, &options, &types, instance); 849 if let Some(cnt) = result_tys.abi.flat_count(MAX_FLAT_RESULTS) { 850 let mut dst = storage[..cnt].iter_mut(); 851 for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) { 852 val.lower(&mut cx, *ty, &mut dst)?; 853 } 854 assert!(dst.next().is_none()); 855 } else { 856 let ret_ptr = storage[ret_index].assume_init_ref(); 857 let mut ptr = validate_inbounds_dynamic(&result_tys.abi, cx.as_slice_mut(), ret_ptr)?; 858 for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) { 859 let offset = types.canonical_abi(ty).next_field32_size(&mut ptr); 860 val.store(&mut cx, *ty, offset)?; 861 } 862 } 863 864 flags.set_may_leave(true); 865 866 cx.exit_call()?; 867 } 868 869 Ok(()) 870 } 871 872 /// Loads the parameters for a dynamic host function call into `params` 873 /// 874 /// Returns the number of flat `storage` values consumed. 875 /// 876 /// # Safety 877 /// 878 /// Requires that `param_tys` matches the type signature of the `storage` that 879 /// was passed in. 880 unsafe fn dynamic_params_load( 881 cx: &mut LiftContext<'_>, 882 types: &ComponentTypes, 883 storage: &[MaybeUninit<ValRaw>], 884 param_tys: &TypeTuple, 885 params: &mut Vec<Val>, 886 max_flat_params: usize, 887 ) -> Result<usize> { 888 if let Some(param_count) = param_tys.abi.flat_count(max_flat_params) { 889 // NB: can use `MaybeUninit::slice_assume_init_ref` when that's stable 890 let storage = 891 unsafe { mem::transmute::<&[MaybeUninit<ValRaw>], &[ValRaw]>(&storage[..param_count]) }; 892 let mut iter = storage.iter(); 893 for ty in param_tys.types.iter() { 894 params.push(Val::lift(cx, *ty, &mut iter)?); 895 } 896 assert!(iter.next().is_none()); 897 Ok(param_count) 898 } else { 899 let mut offset = validate_inbounds_dynamic(¶m_tys.abi, cx.memory(), unsafe { 900 storage[0].assume_init_ref() 901 })?; 902 for ty in param_tys.types.iter() { 903 let abi = types.canonical_abi(ty); 904 let size = usize::try_from(abi.size32).unwrap(); 905 let memory = &cx.memory()[abi.next_field32_size(&mut offset)..][..size]; 906 params.push(Val::load(cx, *ty, memory)?); 907 } 908 Ok(1) 909 } 910 } 911 912 pub(crate) fn validate_inbounds_dynamic( 913 abi: &CanonicalAbiInfo, 914 memory: &[u8], 915 ptr: &ValRaw, 916 ) -> Result<usize> { 917 // FIXME(#4311): needs memory64 support 918 let ptr = usize::try_from(ptr.get_u32())?; 919 if ptr % usize::try_from(abi.align32)? != 0 { 920 bail!("pointer not aligned"); 921 } 922 let end = match ptr.checked_add(usize::try_from(abi.size32).unwrap()) { 923 Some(n) => n, 924 None => bail!("pointer size overflow"), 925 }; 926 if end > memory.len() { 927 bail!("pointer out of bounds") 928 } 929 Ok(ptr) 930 } 931 932 extern "C" fn dynamic_entrypoint<T: 'static, F>( 933 cx: NonNull<VMOpaqueContext>, 934 data: NonNull<u8>, 935 ty: u32, 936 caller_instance: u32, 937 flags: NonNull<VMGlobalDefinition>, 938 memory: *mut VMMemoryDefinition, 939 realloc: *mut VMFuncRef, 940 string_encoding: u8, 941 async_: u8, 942 storage: NonNull<MaybeUninit<ValRaw>>, 943 storage_len: usize, 944 ) -> bool 945 where 946 F: Fn( 947 StoreContextMut<'_, T>, 948 Instance, 949 Vec<Val>, 950 usize, 951 ) -> Pin<Box<dyn Future<Output = Result<Vec<Val>>> + Send + 'static>> 952 + Send 953 + Sync 954 + 'static, 955 T: 'static, 956 { 957 let data = SendSyncPtr::new(NonNull::new(data.as_ptr() as *mut F).unwrap()); 958 unsafe { 959 call_host_and_handle_result(cx, |store, instance| { 960 call_host_dynamic::<T, _>( 961 store, 962 instance, 963 TypeFuncIndex::from_u32(ty), 964 RuntimeComponentInstanceIndex::from_u32(caller_instance), 965 InstanceFlags::from_raw(flags), 966 memory, 967 realloc, 968 StringEncoding::from_u8(string_encoding).unwrap(), 969 async_ != 0, 970 NonNull::slice_from_raw_parts(storage, storage_len).as_mut(), 971 move |store, instance, params, results| { 972 (*data.as_ptr())(store, instance, params, results) 973 }, 974 ) 975 }) 976 } 977 } 978