1 use crate::component::func::{LiftContext, LowerContext, Options}; 2 use crate::component::matching::InstanceType; 3 use crate::component::storage::slice_to_storage_mut; 4 use crate::component::{ComponentNamedList, ComponentType, Instance, Lift, Lower, Val}; 5 use crate::prelude::*; 6 use crate::runtime::vm::component::{ 7 ComponentInstance, InstanceFlags, VMComponentContext, VMLowering, VMLoweringCallee, 8 }; 9 use crate::runtime::vm::{VMFuncRef, VMGlobalDefinition, VMMemoryDefinition, VMOpaqueContext}; 10 use crate::{AsContextMut, CallHook, StoreContextMut, ValRaw}; 11 use alloc::sync::Arc; 12 use core::any::Any; 13 use core::mem::{self, MaybeUninit}; 14 use core::ptr::NonNull; 15 use wasmtime_environ::component::{ 16 CanonicalAbiInfo, InterfaceType, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS, StringEncoding, 17 TypeFuncIndex, 18 }; 19 20 pub struct HostFunc { 21 entrypoint: VMLoweringCallee, 22 typecheck: Box<dyn (Fn(TypeFuncIndex, &InstanceType<'_>) -> Result<()>) + Send + Sync>, 23 func: Box<dyn Any + Send + Sync>, 24 } 25 26 impl core::fmt::Debug for HostFunc { 27 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 28 f.debug_struct("HostFunc").finish_non_exhaustive() 29 } 30 } 31 32 impl HostFunc { 33 pub(crate) fn from_closure<T, F, P, R>(func: F) -> Arc<HostFunc> 34 where 35 F: Fn(StoreContextMut<T>, P) -> Result<R> + Send + Sync + 'static, 36 P: ComponentNamedList + Lift + 'static, 37 R: ComponentNamedList + Lower + 'static, 38 T: 'static, 39 { 40 let entrypoint = Self::entrypoint::<T, F, P, R>; 41 Arc::new(HostFunc { 42 entrypoint, 43 typecheck: Box::new(typecheck::<P, R>), 44 func: Box::new(func), 45 }) 46 } 47 48 extern "C" fn entrypoint<T, F, P, R>( 49 cx: NonNull<VMOpaqueContext>, 50 data: NonNull<u8>, 51 ty: u32, 52 _caller_instance: u32, 53 flags: NonNull<VMGlobalDefinition>, 54 memory: *mut VMMemoryDefinition, 55 realloc: *mut VMFuncRef, 56 string_encoding: u8, 57 async_: u8, 58 storage: NonNull<MaybeUninit<ValRaw>>, 59 storage_len: usize, 60 ) -> bool 61 where 62 F: Fn(StoreContextMut<T>, P) -> Result<R>, 63 P: ComponentNamedList + Lift + 'static, 64 R: ComponentNamedList + Lower + 'static, 65 T: 'static, 66 { 67 let data = data.as_ptr() as *const F; 68 unsafe { 69 call_host_and_handle_result::<T>(cx, |store, instance| { 70 call_host( 71 store, 72 instance, 73 TypeFuncIndex::from_u32(ty), 74 InstanceFlags::from_raw(flags), 75 memory, 76 realloc, 77 StringEncoding::from_u8(string_encoding).unwrap(), 78 async_ != 0, 79 NonNull::slice_from_raw_parts(storage, storage_len).as_mut(), 80 |store, args| (*data)(store, args), 81 ) 82 }) 83 } 84 } 85 86 pub(crate) fn new_dynamic<T, F>(func: F) -> Arc<HostFunc> 87 where 88 F: Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static, 89 T: 'static, 90 { 91 Arc::new(HostFunc { 92 entrypoint: dynamic_entrypoint::<T, F>, 93 // This function performs dynamic type checks and subsequently does 94 // not need to perform up-front type checks. Instead everything is 95 // dynamically managed at runtime. 96 typecheck: Box::new(move |_expected_index, _expected_types| Ok(())), 97 func: Box::new(func), 98 }) 99 } 100 101 pub fn typecheck(&self, ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> { 102 (self.typecheck)(ty, types) 103 } 104 105 pub fn lowering(&self) -> VMLowering { 106 let data = NonNull::from(&*self.func).cast(); 107 VMLowering { 108 callee: NonNull::new(self.entrypoint as *mut _).unwrap().into(), 109 data: data.into(), 110 } 111 } 112 } 113 114 fn typecheck<P, R>(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> 115 where 116 P: ComponentNamedList + Lift, 117 R: ComponentNamedList + Lower, 118 { 119 let ty = &types.types[ty]; 120 P::typecheck(&InterfaceType::Tuple(ty.params), types) 121 .context("type mismatch with parameters")?; 122 R::typecheck(&InterfaceType::Tuple(ty.results), types).context("type mismatch with results")?; 123 Ok(()) 124 } 125 126 /// The "meat" of calling a host function from wasm. 127 /// 128 /// This function is delegated to from implementations of 129 /// `HostFunc::from_closure`. Most of the arguments from the `entrypoint` are 130 /// forwarded here except for the `data` pointer which is encapsulated in the 131 /// `closure` argument here. 132 /// 133 /// This function is parameterized over: 134 /// 135 /// * `T` - the type of store this function works with (an unsafe assertion) 136 /// * `Params` - the parameters to the host function, viewed as a tuple 137 /// * `Return` - the result of the host function 138 /// * `F` - the `closure` to actually receive the `Params` and return the 139 /// `Return` 140 /// 141 /// It's expected that `F` will "un-tuple" the arguments to pass to a host 142 /// closure. 143 /// 144 /// This function is in general `unsafe` as the validity of all the parameters 145 /// must be upheld. Generally that's done by ensuring this is only called from 146 /// the select few places it's intended to be called from. 147 unsafe fn call_host<T, Params, Return, F>( 148 mut store: StoreContextMut<'_, T>, 149 instance: Instance, 150 ty: TypeFuncIndex, 151 mut flags: InstanceFlags, 152 memory: *mut VMMemoryDefinition, 153 realloc: *mut VMFuncRef, 154 string_encoding: StringEncoding, 155 async_: bool, 156 storage: &mut [MaybeUninit<ValRaw>], 157 closure: F, 158 ) -> Result<()> 159 where 160 Params: Lift, 161 Return: Lower, 162 F: FnOnce(StoreContextMut<'_, T>, Params) -> Result<Return>, 163 { 164 if async_ { 165 todo!() 166 } 167 168 let options = Options::new( 169 store.0.id(), 170 NonNull::new(memory), 171 NonNull::new(realloc), 172 string_encoding, 173 ); 174 175 // Perform a dynamic check that this instance can indeed be left. Exiting 176 // the component is disallowed, for example, when the `realloc` function 177 // calls a canonical import. 178 if !flags.may_leave() { 179 bail!("cannot leave component instance"); 180 } 181 182 let types = instance.id().get(store.0).component().types().clone(); 183 let ty = &types[ty]; 184 let param_tys = InterfaceType::Tuple(ty.params); 185 let result_tys = InterfaceType::Tuple(ty.results); 186 187 let mut storage = Storage::<'_, Params, Return>::new_sync(storage); 188 let mut lift = LiftContext::new(store.0, &options, &types, instance); 189 lift.enter_call(); 190 let params = storage.lift_params(&mut lift, param_tys)?; 191 192 let ret = closure(store.as_context_mut(), params)?; 193 194 flags.set_may_leave(false); 195 let mut lower = LowerContext::new(store, &options, &types, instance); 196 storage.lower_results(&mut lower, result_tys, ret)?; 197 flags.set_may_leave(true); 198 lower.exit_call()?; 199 200 return Ok(()); 201 202 /// Type-level representation of the matrix of possibilities of how 203 /// WebAssembly parameters and results are handled in the canonical ABI. 204 /// 205 /// Wasmtime's ABI here always works with `&mut [MaybeUninit<ValRaw>]` as the 206 /// base representation of params/results. Parameters are passed 207 /// sequentially and results are returned by overwriting the parameters. 208 /// That means both params/results start from index 0. 209 /// 210 /// The type-level representation here involves working with the typed 211 /// `P::Lower` and `R::Lower` values which is a type-level representation of 212 /// a lowered value. All lowered values are in essence a sequence of 213 /// `ValRaw` values one after the other to fit within this original array 214 /// that is the basis of Wasmtime's ABI. 215 /// 216 /// The various combinations here are cryptic, but only used in this file. 217 /// This in theory cuts down on the verbosity below, but an explanation of 218 /// the various acronyms here are: 219 /// 220 /// * Pd - params direct - means that parameters are passed directly in 221 /// their flat representation via `P::Lower`. 222 /// 223 /// * Pi - params indirect - means that parameters are passed indirectly in 224 /// linear memory and the argument here is `ValRaw` to store the pointer. 225 /// 226 /// * Rd - results direct - means that results are returned directly in 227 /// their flat representation via `R::Lower`. Note that this is always 228 /// represented as `MaybeUninit<R::Lower>` as well because the return 229 /// values may point to uninitialized memory if there were no parameters 230 /// for example. 231 /// 232 /// * Ri - results indirect - means that results are returned indirectly in 233 /// linear memory through the pointer specified. Note that this is 234 /// specified as a `ValRaw` to represent the argument that's being given 235 /// to the host from WebAssembly. 236 /// 237 /// Internally this type makes liberal use of `Union` and `Pair` helpers 238 /// below which are simple `#[repr(C)]` wrappers around a pair of types that 239 /// are a union or a pair. 240 /// 241 /// Note that for any combination of `P` and `R` this `enum` is actually 242 /// pointless as a single variant will be used. In theory we should be able 243 /// to monomorphize based on `P` and `R` to a specific type. This 244 /// monomorphization depends on conditionals like `flatten_count() <= N`, 245 /// however, and I don't know how to encode that in Rust easily. In lieu of 246 /// that we assume LLVM will figure things out and boil away the actual enum 247 /// and runtime dispatch. 248 enum Storage<'a, P: ComponentType, R: ComponentType> { 249 /// Params: direct, Results: direct 250 /// 251 /// The lowered representation of params/results are overlaid on top of 252 /// each other. 253 PdRd(&'a mut Union<P::Lower, MaybeUninit<R::Lower>>), 254 255 /// Params: direct, Results: indirect 256 /// 257 /// The return pointer comes after the params so this is sequentially 258 /// laid out with one after the other. 259 PdRi(&'a Pair<P::Lower, ValRaw>), 260 261 /// Params: indirect, Results: direct 262 /// 263 /// Here the return values are overlaid on top of the pointer parameter. 264 PiRd(&'a mut Union<ValRaw, MaybeUninit<R::Lower>>), 265 266 /// Params: indirect, Results: indirect 267 /// 268 /// Here the two parameters are laid out sequentially one after the 269 /// other. 270 PiRi(&'a Pair<ValRaw, ValRaw>), 271 } 272 273 // Helper structure used above in `Storage` to represent two consecutive 274 // values. 275 #[repr(C)] 276 #[derive(Copy, Clone)] 277 struct Pair<T, U> { 278 a: T, 279 b: U, 280 } 281 282 // Helper structure used above in `Storage` to represent two values overlaid 283 // on each other. 284 #[repr(C)] 285 union Union<T: Copy, U: Copy> { 286 a: T, 287 b: U, 288 } 289 290 /// Representation of where parameters are lifted from. 291 enum Src<'a, T> { 292 /// Parameters are directly lifted from `T`, which is under the hood a 293 /// sequence of `ValRaw`. This is `P::Lower` for example. 294 Direct(&'a T), 295 296 /// Parameters are loaded from linear memory, and this is the wasm 297 /// parameter representing the pointer into linear memory to load from. 298 Indirect(&'a ValRaw), 299 } 300 301 /// Dual of [`Src`], where to store results. 302 enum Dst<'a, T> { 303 /// Results are stored directly in this pointer. 304 /// 305 /// Note that this is a mutable pointer but it's specifically 306 /// `MaybeUninit` as trampolines do not initialize it. The `T` here will 307 /// be `R::Lower` for example. 308 Direct(&'a mut MaybeUninit<T>), 309 310 /// Results are stored in linear memory, and this value is the wasm 311 /// parameter given which represents the pointer into linear memory. 312 /// 313 /// Note that this is not mutable as the parameter is not mutated, but 314 /// memory will be mutated. 315 Indirect(&'a ValRaw), 316 } 317 318 impl<P, R> Storage<'_, P, R> 319 where 320 P: ComponentType + Lift, 321 R: ComponentType + Lower, 322 { 323 /// Classifies a new `Storage` suitable for use with sync functions. 324 /// 325 /// There's a 2x2 matrix of whether parameters and results are stored on the 326 /// stack or on the heap. Each of the 4 branches here have a different 327 /// representation of the storage of arguments/returns. 328 /// 329 /// Also note that while four branches are listed here only one is taken for 330 /// any particular `Params` and `Return` combination. This should be 331 /// trivially DCE'd by LLVM. Perhaps one day with enough const programming in 332 /// Rust we can make monomorphizations of this function codegen only one 333 /// branch, but today is not that day. 334 /// 335 /// # Safety 336 /// 337 /// Requires that the `storage` provided does indeed match an wasm 338 /// function with the signature of `P` and `R` as params/results. 339 unsafe fn new_sync(storage: &mut [MaybeUninit<ValRaw>]) -> Storage<'_, P, R> { 340 // SAFETY: this `unsafe` is due to the `slice_to_storage_*` helpers 341 // used which view the slice provided as a different type. This 342 // safety should be upheld by the contract of the `ComponentType` 343 // trait and its `Lower` type parameter meaning they're valid to 344 // view as a sequence of `ValRaw` types. Additionally the 345 // `ComponentType` trait ensures that the matching of the runtime 346 // length of `storage` should match the actual size of `P::Lower` 347 // and `R::Lower` or such as needed. 348 unsafe { 349 if P::flatten_count() <= MAX_FLAT_PARAMS { 350 if R::flatten_count() <= MAX_FLAT_RESULTS { 351 Storage::PdRd(slice_to_storage_mut(storage).assume_init_mut()) 352 } else { 353 Storage::PdRi(slice_to_storage_mut(storage).assume_init_ref()) 354 } 355 } else { 356 if R::flatten_count() <= MAX_FLAT_RESULTS { 357 Storage::PiRd(slice_to_storage_mut(storage).assume_init_mut()) 358 } else { 359 Storage::PiRi(slice_to_storage_mut(storage).assume_init_ref()) 360 } 361 } 362 } 363 } 364 365 fn lift_params(&self, cx: &mut LiftContext<'_>, ty: InterfaceType) -> Result<P> { 366 match self.lift_src() { 367 Src::Direct(storage) => P::lift(cx, ty, storage), 368 Src::Indirect(ptr) => { 369 let ptr = validate_inbounds::<P>(cx.memory(), ptr)?; 370 P::load(cx, ty, &cx.memory()[ptr..][..P::SIZE32]) 371 } 372 } 373 } 374 375 fn lift_src(&self) -> Src<'_, P::Lower> { 376 match self { 377 // SAFETY: these `unsafe` blocks are due to accessing union 378 // fields. The safety here relies on the contract of the 379 // `ComponentType` trait which should ensure that the types 380 // projected onto a list of wasm parameters are indeed correct. 381 // That means that the projections here, if the types are 382 // correct, all line up to initialized memory that's well-typed 383 // to access. 384 Storage::PdRd(storage) => unsafe { Src::Direct(&storage.a) }, 385 Storage::PdRi(storage) => Src::Direct(&storage.a), 386 Storage::PiRd(storage) => unsafe { Src::Indirect(&storage.a) }, 387 Storage::PiRi(storage) => Src::Indirect(&storage.a), 388 } 389 } 390 391 fn lower_results<T>( 392 &mut self, 393 cx: &mut LowerContext<'_, T>, 394 ty: InterfaceType, 395 ret: R, 396 ) -> Result<()> { 397 match self.lower_dst() { 398 Dst::Direct(storage) => ret.lower(cx, ty, storage), 399 Dst::Indirect(ptr) => { 400 let ptr = validate_inbounds::<R>(cx.as_slice_mut(), ptr)?; 401 ret.store(cx, ty, ptr) 402 } 403 } 404 } 405 406 fn lower_dst(&mut self) -> Dst<'_, R::Lower> { 407 match self { 408 // SAFETY: these unsafe blocks are due to accessing fields of a 409 // `union` which is not safe in Rust. The returned value is 410 // `MaybeUninit<R::Lower>` in all cases, however, which should 411 // safely model how `union` memory is possibly uninitialized. 412 // Additionally `R::Lower` has the `unsafe` contract that all 413 // its bit patterns must be sound, which additionally should 414 // help make this safe. 415 Storage::PdRd(storage) => unsafe { Dst::Direct(&mut storage.b) }, 416 Storage::PiRd(storage) => unsafe { Dst::Direct(&mut storage.b) }, 417 Storage::PdRi(storage) => Dst::Indirect(&storage.b), 418 Storage::PiRi(storage) => Dst::Indirect(&storage.b), 419 } 420 } 421 } 422 } 423 424 fn validate_inbounds<T: ComponentType>(memory: &[u8], ptr: &ValRaw) -> Result<usize> { 425 // FIXME(#4311): needs memory64 support 426 let ptr = usize::try_from(ptr.get_u32())?; 427 if ptr % usize::try_from(T::ALIGN32)? != 0 { 428 bail!("pointer not aligned"); 429 } 430 let end = match ptr.checked_add(T::SIZE32) { 431 Some(n) => n, 432 None => bail!("pointer size overflow"), 433 }; 434 if end > memory.len() { 435 bail!("pointer out of bounds") 436 } 437 Ok(ptr) 438 } 439 440 unsafe fn call_host_and_handle_result<T>( 441 cx: NonNull<VMOpaqueContext>, 442 func: impl FnOnce(StoreContextMut<'_, T>, Instance) -> Result<()>, 443 ) -> bool 444 where 445 T: 'static, 446 { 447 let cx = VMComponentContext::from_opaque(cx); 448 ComponentInstance::from_vmctx(cx, |store, instance| { 449 let mut store = store.unchecked_context_mut(); 450 451 crate::runtime::vm::catch_unwind_and_record_trap(|| { 452 store.0.call_hook(CallHook::CallingHost)?; 453 let res = func(store.as_context_mut(), instance); 454 store.0.call_hook(CallHook::ReturningFromHost)?; 455 res 456 }) 457 }) 458 } 459 460 unsafe fn call_host_dynamic<T, F>( 461 mut store: StoreContextMut<'_, T>, 462 instance: Instance, 463 ty: TypeFuncIndex, 464 mut flags: InstanceFlags, 465 memory: *mut VMMemoryDefinition, 466 realloc: *mut VMFuncRef, 467 string_encoding: StringEncoding, 468 async_: bool, 469 storage: &mut [MaybeUninit<ValRaw>], 470 closure: F, 471 ) -> Result<()> 472 where 473 F: FnOnce(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()>, 474 T: 'static, 475 { 476 if async_ { 477 todo!() 478 } 479 480 let options = Options::new( 481 store.0.id(), 482 NonNull::new(memory), 483 NonNull::new(realloc), 484 string_encoding, 485 ); 486 487 // Perform a dynamic check that this instance can indeed be left. Exiting 488 // the component is disallowed, for example, when the `realloc` function 489 // calls a canonical import. 490 if !flags.may_leave() { 491 bail!("cannot leave component instance"); 492 } 493 494 let args; 495 let ret_index; 496 497 let types = instance.id().get(store.0).component().types().clone(); 498 let func_ty = &types[ty]; 499 let param_tys = &types[func_ty.params]; 500 let result_tys = &types[func_ty.results]; 501 let mut cx = LiftContext::new(store.0, &options, &types, instance); 502 cx.enter_call(); 503 if let Some(param_count) = param_tys.abi.flat_count(MAX_FLAT_PARAMS) { 504 // NB: can use `MaybeUninit::slice_assume_init_ref` when that's stable 505 let mut iter = 506 mem::transmute::<&[MaybeUninit<ValRaw>], &[ValRaw]>(&storage[..param_count]).iter(); 507 args = param_tys 508 .types 509 .iter() 510 .map(|ty| Val::lift(&mut cx, *ty, &mut iter)) 511 .collect::<Result<Box<[_]>>>()?; 512 ret_index = param_count; 513 assert!(iter.next().is_none()); 514 } else { 515 let mut offset = 516 validate_inbounds_dynamic(¶m_tys.abi, cx.memory(), storage[0].assume_init_ref())?; 517 args = param_tys 518 .types 519 .iter() 520 .map(|ty| { 521 let abi = types.canonical_abi(ty); 522 let size = usize::try_from(abi.size32).unwrap(); 523 let memory = &cx.memory()[abi.next_field32_size(&mut offset)..][..size]; 524 Val::load(&mut cx, *ty, memory) 525 }) 526 .collect::<Result<Box<[_]>>>()?; 527 ret_index = 1; 528 }; 529 530 let mut result_vals = Vec::with_capacity(result_tys.types.len()); 531 for _ in result_tys.types.iter() { 532 result_vals.push(Val::Bool(false)); 533 } 534 closure(store.as_context_mut(), &args, &mut result_vals)?; 535 flags.set_may_leave(false); 536 537 let mut cx = LowerContext::new(store, &options, &types, instance); 538 if let Some(cnt) = result_tys.abi.flat_count(MAX_FLAT_RESULTS) { 539 let mut dst = storage[..cnt].iter_mut(); 540 for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) { 541 val.lower(&mut cx, *ty, &mut dst)?; 542 } 543 assert!(dst.next().is_none()); 544 } else { 545 let ret_ptr = storage[ret_index].assume_init_ref(); 546 let mut ptr = validate_inbounds_dynamic(&result_tys.abi, cx.as_slice_mut(), ret_ptr)?; 547 for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) { 548 let offset = types.canonical_abi(ty).next_field32_size(&mut ptr); 549 val.store(&mut cx, *ty, offset)?; 550 } 551 } 552 553 flags.set_may_leave(true); 554 555 cx.exit_call()?; 556 557 return Ok(()); 558 } 559 560 fn validate_inbounds_dynamic(abi: &CanonicalAbiInfo, memory: &[u8], ptr: &ValRaw) -> Result<usize> { 561 // FIXME(#4311): needs memory64 support 562 let ptr = usize::try_from(ptr.get_u32())?; 563 if ptr % usize::try_from(abi.align32)? != 0 { 564 bail!("pointer not aligned"); 565 } 566 let end = match ptr.checked_add(usize::try_from(abi.size32).unwrap()) { 567 Some(n) => n, 568 None => bail!("pointer size overflow"), 569 }; 570 if end > memory.len() { 571 bail!("pointer out of bounds") 572 } 573 Ok(ptr) 574 } 575 576 extern "C" fn dynamic_entrypoint<T, F>( 577 cx: NonNull<VMOpaqueContext>, 578 data: NonNull<u8>, 579 ty: u32, 580 _caller_instance: u32, 581 flags: NonNull<VMGlobalDefinition>, 582 memory: *mut VMMemoryDefinition, 583 realloc: *mut VMFuncRef, 584 string_encoding: u8, 585 async_: u8, 586 storage: NonNull<MaybeUninit<ValRaw>>, 587 storage_len: usize, 588 ) -> bool 589 where 590 F: Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static, 591 T: 'static, 592 { 593 let data = data.as_ptr() as *const F; 594 unsafe { 595 call_host_and_handle_result(cx, |store, instance| { 596 call_host_dynamic::<T, _>( 597 store, 598 instance, 599 TypeFuncIndex::from_u32(ty), 600 InstanceFlags::from_raw(flags), 601 memory, 602 realloc, 603 StringEncoding::from_u8(string_encoding).unwrap(), 604 async_ != 0, 605 NonNull::slice_from_raw_parts(storage, storage_len).as_mut(), 606 |store, params, results| (*data)(store, params, results), 607 ) 608 }) 609 } 610 } 611