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: self.entrypoint, 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 cx: 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 /// Representation of arguments to this function when a return pointer is in 169 /// use, namely the argument list is followed by a single value which is the 170 /// return pointer. 171 #[repr(C)] 172 struct ReturnPointer<T> { 173 args: T, 174 retptr: ValRaw, 175 } 176 177 /// Representation of arguments to this function when the return value is 178 /// returned directly, namely the arguments and return value all start from 179 /// the beginning (aka this is a `union`, not a `struct`). 180 #[repr(C)] 181 union ReturnStack<T: Copy, U: Copy> { 182 args: T, 183 ret: U, 184 } 185 186 let options = Options::new( 187 cx.0.id(), 188 NonNull::new(memory), 189 NonNull::new(realloc), 190 string_encoding, 191 ); 192 193 // Perform a dynamic check that this instance can indeed be left. Exiting 194 // the component is disallowed, for example, when the `realloc` function 195 // calls a canonical import. 196 if !flags.may_leave() { 197 bail!("cannot leave component instance"); 198 } 199 200 let types = cx[instance.id()].component().types().clone(); 201 let ty = &types[ty]; 202 let param_tys = InterfaceType::Tuple(ty.params); 203 let result_tys = InterfaceType::Tuple(ty.results); 204 205 // There's a 2x2 matrix of whether parameters and results are stored on the 206 // stack or on the heap. Each of the 4 branches here have a different 207 // representation of the storage of arguments/returns. 208 // 209 // Also note that while four branches are listed here only one is taken for 210 // any particular `Params` and `Return` combination. This should be 211 // trivially DCE'd by LLVM. Perhaps one day with enough const programming in 212 // Rust we can make monomorphizations of this function codegen only one 213 // branch, but today is not that day. 214 let mut storage: Storage<'_, Params, Return> = if Params::flatten_count() <= MAX_FLAT_PARAMS { 215 if Return::flatten_count() <= MAX_FLAT_RESULTS { 216 Storage::Direct(slice_to_storage_mut(storage)) 217 } else { 218 Storage::ResultsIndirect(slice_to_storage_mut(storage).assume_init_ref()) 219 } 220 } else { 221 if Return::flatten_count() <= MAX_FLAT_RESULTS { 222 Storage::ParamsIndirect(slice_to_storage_mut(storage)) 223 } else { 224 Storage::Indirect(slice_to_storage_mut(storage).assume_init_ref()) 225 } 226 }; 227 let mut lift = LiftContext::new(cx.0, &options, &types, instance); 228 lift.enter_call(); 229 let params = storage.lift_params(&mut lift, param_tys)?; 230 231 let ret = closure(cx.as_context_mut(), params)?; 232 flags.set_may_leave(false); 233 let mut lower = LowerContext::new(cx, &options, &types, instance); 234 storage.lower_results(&mut lower, result_tys, ret)?; 235 flags.set_may_leave(true); 236 237 lower.exit_call()?; 238 239 return Ok(()); 240 241 enum Storage<'a, P: ComponentType, R: ComponentType> { 242 Direct(&'a mut MaybeUninit<ReturnStack<P::Lower, R::Lower>>), 243 ParamsIndirect(&'a mut MaybeUninit<ReturnStack<ValRaw, R::Lower>>), 244 ResultsIndirect(&'a ReturnPointer<P::Lower>), 245 Indirect(&'a ReturnPointer<ValRaw>), 246 } 247 248 impl<P, R> Storage<'_, P, R> 249 where 250 P: ComponentType + Lift, 251 R: ComponentType + Lower, 252 { 253 unsafe fn lift_params(&self, cx: &mut LiftContext<'_>, ty: InterfaceType) -> Result<P> { 254 match self { 255 Storage::Direct(storage) => P::lift(cx, ty, &storage.assume_init_ref().args), 256 Storage::ResultsIndirect(storage) => P::lift(cx, ty, &storage.args), 257 Storage::ParamsIndirect(storage) => { 258 let ptr = validate_inbounds::<P>(cx.memory(), &storage.assume_init_ref().args)?; 259 P::load(cx, ty, &cx.memory()[ptr..][..P::SIZE32]) 260 } 261 Storage::Indirect(storage) => { 262 let ptr = validate_inbounds::<P>(cx.memory(), &storage.args)?; 263 P::load(cx, ty, &cx.memory()[ptr..][..P::SIZE32]) 264 } 265 } 266 } 267 268 unsafe fn lower_results<T>( 269 &mut self, 270 cx: &mut LowerContext<'_, T>, 271 ty: InterfaceType, 272 ret: R, 273 ) -> Result<()> { 274 match self { 275 Storage::Direct(storage) => ret.lower(cx, ty, map_maybe_uninit!(storage.ret)), 276 Storage::ParamsIndirect(storage) => { 277 ret.lower(cx, ty, map_maybe_uninit!(storage.ret)) 278 } 279 Storage::ResultsIndirect(storage) => { 280 let ptr = validate_inbounds::<R>(cx.as_slice_mut(), &storage.retptr)?; 281 ret.store(cx, ty, ptr) 282 } 283 Storage::Indirect(storage) => { 284 let ptr = validate_inbounds::<R>(cx.as_slice_mut(), &storage.retptr)?; 285 ret.store(cx, ty, ptr) 286 } 287 } 288 } 289 } 290 } 291 292 fn validate_inbounds<T: ComponentType>(memory: &[u8], ptr: &ValRaw) -> Result<usize> { 293 // FIXME(#4311): needs memory64 support 294 let ptr = usize::try_from(ptr.get_u32())?; 295 if ptr % usize::try_from(T::ALIGN32)? != 0 { 296 bail!("pointer not aligned"); 297 } 298 let end = match ptr.checked_add(T::SIZE32) { 299 Some(n) => n, 300 None => bail!("pointer size overflow"), 301 }; 302 if end > memory.len() { 303 bail!("pointer out of bounds") 304 } 305 Ok(ptr) 306 } 307 308 unsafe fn call_host_and_handle_result<T>( 309 cx: NonNull<VMOpaqueContext>, 310 func: impl FnOnce(StoreContextMut<'_, T>, Instance) -> Result<()>, 311 ) -> bool 312 where 313 T: 'static, 314 { 315 let cx = VMComponentContext::from_opaque(cx); 316 ComponentInstance::from_vmctx(cx, |store, instance| { 317 let mut store = store.unchecked_context_mut(); 318 319 crate::runtime::vm::catch_unwind_and_record_trap(|| { 320 store.0.call_hook(CallHook::CallingHost)?; 321 let res = func(store.as_context_mut(), instance); 322 store.0.call_hook(CallHook::ReturningFromHost)?; 323 res 324 }) 325 }) 326 } 327 328 unsafe fn call_host_dynamic<T, F>( 329 mut store: StoreContextMut<'_, T>, 330 instance: Instance, 331 ty: TypeFuncIndex, 332 mut flags: InstanceFlags, 333 memory: *mut VMMemoryDefinition, 334 realloc: *mut VMFuncRef, 335 string_encoding: StringEncoding, 336 async_: bool, 337 storage: &mut [MaybeUninit<ValRaw>], 338 closure: F, 339 ) -> Result<()> 340 where 341 F: FnOnce(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()>, 342 T: 'static, 343 { 344 if async_ { 345 todo!() 346 } 347 348 let options = Options::new( 349 store.0.id(), 350 NonNull::new(memory), 351 NonNull::new(realloc), 352 string_encoding, 353 ); 354 355 // Perform a dynamic check that this instance can indeed be left. Exiting 356 // the component is disallowed, for example, when the `realloc` function 357 // calls a canonical import. 358 if !flags.may_leave() { 359 bail!("cannot leave component instance"); 360 } 361 362 let args; 363 let ret_index; 364 365 let types = store[instance.id()].component().types().clone(); 366 let func_ty = &types[ty]; 367 let param_tys = &types[func_ty.params]; 368 let result_tys = &types[func_ty.results]; 369 let mut cx = LiftContext::new(store.0, &options, &types, instance); 370 cx.enter_call(); 371 if let Some(param_count) = param_tys.abi.flat_count(MAX_FLAT_PARAMS) { 372 // NB: can use `MaybeUninit::slice_assume_init_ref` when that's stable 373 let mut iter = 374 mem::transmute::<&[MaybeUninit<ValRaw>], &[ValRaw]>(&storage[..param_count]).iter(); 375 args = param_tys 376 .types 377 .iter() 378 .map(|ty| Val::lift(&mut cx, *ty, &mut iter)) 379 .collect::<Result<Box<[_]>>>()?; 380 ret_index = param_count; 381 assert!(iter.next().is_none()); 382 } else { 383 let mut offset = 384 validate_inbounds_dynamic(¶m_tys.abi, cx.memory(), storage[0].assume_init_ref())?; 385 args = param_tys 386 .types 387 .iter() 388 .map(|ty| { 389 let abi = types.canonical_abi(ty); 390 let size = usize::try_from(abi.size32).unwrap(); 391 let memory = &cx.memory()[abi.next_field32_size(&mut offset)..][..size]; 392 Val::load(&mut cx, *ty, memory) 393 }) 394 .collect::<Result<Box<[_]>>>()?; 395 ret_index = 1; 396 }; 397 398 let mut result_vals = Vec::with_capacity(result_tys.types.len()); 399 for _ in result_tys.types.iter() { 400 result_vals.push(Val::Bool(false)); 401 } 402 closure(store.as_context_mut(), &args, &mut result_vals)?; 403 flags.set_may_leave(false); 404 405 let mut cx = LowerContext::new(store, &options, &types, instance); 406 if let Some(cnt) = result_tys.abi.flat_count(MAX_FLAT_RESULTS) { 407 let mut dst = storage[..cnt].iter_mut(); 408 for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) { 409 val.lower(&mut cx, *ty, &mut dst)?; 410 } 411 assert!(dst.next().is_none()); 412 } else { 413 let ret_ptr = storage[ret_index].assume_init_ref(); 414 let mut ptr = validate_inbounds_dynamic(&result_tys.abi, cx.as_slice_mut(), ret_ptr)?; 415 for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) { 416 let offset = types.canonical_abi(ty).next_field32_size(&mut ptr); 417 val.store(&mut cx, *ty, offset)?; 418 } 419 } 420 421 flags.set_may_leave(true); 422 423 cx.exit_call()?; 424 425 return Ok(()); 426 } 427 428 fn validate_inbounds_dynamic(abi: &CanonicalAbiInfo, memory: &[u8], ptr: &ValRaw) -> Result<usize> { 429 // FIXME(#4311): needs memory64 support 430 let ptr = usize::try_from(ptr.get_u32())?; 431 if ptr % usize::try_from(abi.align32)? != 0 { 432 bail!("pointer not aligned"); 433 } 434 let end = match ptr.checked_add(usize::try_from(abi.size32).unwrap()) { 435 Some(n) => n, 436 None => bail!("pointer size overflow"), 437 }; 438 if end > memory.len() { 439 bail!("pointer out of bounds") 440 } 441 Ok(ptr) 442 } 443 444 extern "C" fn dynamic_entrypoint<T, F>( 445 cx: NonNull<VMOpaqueContext>, 446 data: NonNull<u8>, 447 ty: u32, 448 _caller_instance: u32, 449 flags: NonNull<VMGlobalDefinition>, 450 memory: *mut VMMemoryDefinition, 451 realloc: *mut VMFuncRef, 452 string_encoding: u8, 453 async_: u8, 454 storage: NonNull<MaybeUninit<ValRaw>>, 455 storage_len: usize, 456 ) -> bool 457 where 458 F: Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static, 459 T: 'static, 460 { 461 let data = data.as_ptr() as *const F; 462 unsafe { 463 call_host_and_handle_result(cx, |store, instance| { 464 call_host_dynamic::<T, _>( 465 store, 466 instance, 467 TypeFuncIndex::from_u32(ty), 468 InstanceFlags::from_raw(flags), 469 memory, 470 realloc, 471 StringEncoding::from_u8(string_encoding).unwrap(), 472 async_ != 0, 473 NonNull::slice_from_raw_parts(storage, storage_len).as_mut(), 474 |store, params, results| (*data)(store, params, results), 475 ) 476 }) 477 } 478 } 479