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, 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, ComponentTypes, InterfaceType, StringEncoding, TypeFuncIndex, 17 MAX_FLAT_PARAMS, MAX_FLAT_RESULTS, 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 { 39 let entrypoint = Self::entrypoint::<T, F, P, R>; 40 Arc::new(HostFunc { 41 entrypoint, 42 typecheck: Box::new(typecheck::<P, R>), 43 func: Box::new(func), 44 }) 45 } 46 47 extern "C" fn entrypoint<T, F, P, R>( 48 cx: NonNull<VMOpaqueContext>, 49 data: NonNull<u8>, 50 ty: u32, 51 flags: NonNull<VMGlobalDefinition>, 52 memory: *mut VMMemoryDefinition, 53 realloc: *mut VMFuncRef, 54 string_encoding: u8, 55 async_: u8, 56 storage: NonNull<MaybeUninit<ValRaw>>, 57 storage_len: usize, 58 ) -> bool 59 where 60 F: Fn(StoreContextMut<T>, P) -> Result<R>, 61 P: ComponentNamedList + Lift + 'static, 62 R: ComponentNamedList + Lower + 'static, 63 { 64 let data = data.as_ptr() as *const F; 65 unsafe { 66 call_host_and_handle_result::<T>(cx, |instance, types, store| { 67 call_host::<_, _, _, _>( 68 instance, 69 types, 70 store, 71 TypeFuncIndex::from_u32(ty), 72 InstanceFlags::from_raw(flags), 73 memory, 74 realloc, 75 StringEncoding::from_u8(string_encoding).unwrap(), 76 async_ != 0, 77 NonNull::slice_from_raw_parts(storage, storage_len).as_mut(), 78 |store, args| (*data)(store, args), 79 ) 80 }) 81 } 82 } 83 84 pub(crate) fn new_dynamic<T, F>(func: F) -> Arc<HostFunc> 85 where 86 F: Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static, 87 { 88 Arc::new(HostFunc { 89 entrypoint: dynamic_entrypoint::<T, F>, 90 // This function performs dynamic type checks and subsequently does 91 // not need to perform up-front type checks. Instead everything is 92 // dynamically managed at runtime. 93 typecheck: Box::new(move |_expected_index, _expected_types| Ok(())), 94 func: Box::new(func), 95 }) 96 } 97 98 pub fn typecheck(&self, ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> { 99 (self.typecheck)(ty, types) 100 } 101 102 pub fn lowering(&self) -> VMLowering { 103 let data = NonNull::from(&*self.func).cast(); 104 VMLowering { 105 callee: self.entrypoint, 106 data: data.into(), 107 } 108 } 109 } 110 111 fn typecheck<P, R>(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> 112 where 113 P: ComponentNamedList + Lift, 114 R: ComponentNamedList + Lower, 115 { 116 let ty = &types.types[ty]; 117 P::typecheck(&InterfaceType::Tuple(ty.params), types) 118 .context("type mismatch with parameters")?; 119 R::typecheck(&InterfaceType::Tuple(ty.results), types).context("type mismatch with results")?; 120 Ok(()) 121 } 122 123 /// The "meat" of calling a host function from wasm. 124 /// 125 /// This function is delegated to from implementations of 126 /// `HostFunc::from_closure`. Most of the arguments from the `entrypoint` are 127 /// forwarded here except for the `data` pointer which is encapsulated in the 128 /// `closure` argument here. 129 /// 130 /// This function is parameterized over: 131 /// 132 /// * `T` - the type of store this function works with (an unsafe assertion) 133 /// * `Params` - the parameters to the host function, viewed as a tuple 134 /// * `Return` - the result of the host function 135 /// * `F` - the `closure` to actually receive the `Params` and return the 136 /// `Return` 137 /// 138 /// It's expected that `F` will "un-tuple" the arguments to pass to a host 139 /// closure. 140 /// 141 /// This function is in general `unsafe` as the validity of all the parameters 142 /// must be upheld. Generally that's done by ensuring this is only called from 143 /// the select few places it's intended to be called from. 144 unsafe fn call_host<T, Params, Return, F>( 145 instance: *mut ComponentInstance, 146 types: &Arc<ComponentTypes>, 147 mut cx: StoreContextMut<'_, T>, 148 ty: TypeFuncIndex, 149 mut flags: InstanceFlags, 150 memory: *mut VMMemoryDefinition, 151 realloc: *mut VMFuncRef, 152 string_encoding: StringEncoding, 153 async_: bool, 154 storage: &mut [MaybeUninit<ValRaw>], 155 closure: F, 156 ) -> Result<()> 157 where 158 Params: Lift, 159 Return: Lower, 160 F: FnOnce(StoreContextMut<'_, T>, Params) -> Result<Return>, 161 { 162 if async_ { 163 todo!() 164 } 165 166 /// Representation of arguments to this function when a return pointer is in 167 /// use, namely the argument list is followed by a single value which is the 168 /// return pointer. 169 #[repr(C)] 170 struct ReturnPointer<T> { 171 args: T, 172 retptr: ValRaw, 173 } 174 175 /// Representation of arguments to this function when the return value is 176 /// returned directly, namely the arguments and return value all start from 177 /// the beginning (aka this is a `union`, not a `struct`). 178 #[repr(C)] 179 union ReturnStack<T: Copy, U: Copy> { 180 args: T, 181 ret: U, 182 } 183 184 let options = Options::new( 185 cx.0.id(), 186 NonNull::new(memory), 187 NonNull::new(realloc), 188 string_encoding, 189 ); 190 191 // Perform a dynamic check that this instance can indeed be left. Exiting 192 // the component is disallowed, for example, when the `realloc` function 193 // calls a canonical import. 194 if !flags.may_leave() { 195 bail!("cannot leave component instance"); 196 } 197 198 let ty = &types[ty]; 199 let param_tys = InterfaceType::Tuple(ty.params); 200 let result_tys = InterfaceType::Tuple(ty.results); 201 202 // There's a 2x2 matrix of whether parameters and results are stored on the 203 // stack or on the heap. Each of the 4 branches here have a different 204 // representation of the storage of arguments/returns. 205 // 206 // Also note that while four branches are listed here only one is taken for 207 // any particular `Params` and `Return` combination. This should be 208 // trivially DCE'd by LLVM. Perhaps one day with enough const programming in 209 // Rust we can make monomorphizations of this function codegen only one 210 // branch, but today is not that day. 211 let mut storage: Storage<'_, Params, Return> = if Params::flatten_count() <= MAX_FLAT_PARAMS { 212 if Return::flatten_count() <= MAX_FLAT_RESULTS { 213 Storage::Direct(slice_to_storage_mut(storage)) 214 } else { 215 Storage::ResultsIndirect(slice_to_storage_mut(storage).assume_init_ref()) 216 } 217 } else { 218 if Return::flatten_count() <= MAX_FLAT_RESULTS { 219 Storage::ParamsIndirect(slice_to_storage_mut(storage)) 220 } else { 221 Storage::Indirect(slice_to_storage_mut(storage).assume_init_ref()) 222 } 223 }; 224 let mut lift = LiftContext::new(cx.0, &options, types, instance); 225 lift.enter_call(); 226 let params = storage.lift_params(&mut lift, param_tys)?; 227 228 let ret = closure(cx.as_context_mut(), params)?; 229 flags.set_may_leave(false); 230 let mut lower = LowerContext::new(cx, &options, types, instance); 231 storage.lower_results(&mut lower, result_tys, ret)?; 232 flags.set_may_leave(true); 233 234 lower.exit_call()?; 235 236 return Ok(()); 237 238 enum Storage<'a, P: ComponentType, R: ComponentType> { 239 Direct(&'a mut MaybeUninit<ReturnStack<P::Lower, R::Lower>>), 240 ParamsIndirect(&'a mut MaybeUninit<ReturnStack<ValRaw, R::Lower>>), 241 ResultsIndirect(&'a ReturnPointer<P::Lower>), 242 Indirect(&'a ReturnPointer<ValRaw>), 243 } 244 245 impl<P, R> Storage<'_, P, R> 246 where 247 P: ComponentType + Lift, 248 R: ComponentType + Lower, 249 { 250 unsafe fn lift_params(&self, cx: &mut LiftContext<'_>, ty: InterfaceType) -> Result<P> { 251 match self { 252 Storage::Direct(storage) => P::lift(cx, ty, &storage.assume_init_ref().args), 253 Storage::ResultsIndirect(storage) => P::lift(cx, ty, &storage.args), 254 Storage::ParamsIndirect(storage) => { 255 let ptr = validate_inbounds::<P>(cx.memory(), &storage.assume_init_ref().args)?; 256 P::load(cx, ty, &cx.memory()[ptr..][..P::SIZE32]) 257 } 258 Storage::Indirect(storage) => { 259 let ptr = validate_inbounds::<P>(cx.memory(), &storage.args)?; 260 P::load(cx, ty, &cx.memory()[ptr..][..P::SIZE32]) 261 } 262 } 263 } 264 265 unsafe fn lower_results<T>( 266 &mut self, 267 cx: &mut LowerContext<'_, T>, 268 ty: InterfaceType, 269 ret: R, 270 ) -> Result<()> { 271 match self { 272 Storage::Direct(storage) => ret.lower(cx, ty, map_maybe_uninit!(storage.ret)), 273 Storage::ParamsIndirect(storage) => { 274 ret.lower(cx, ty, map_maybe_uninit!(storage.ret)) 275 } 276 Storage::ResultsIndirect(storage) => { 277 let ptr = validate_inbounds::<R>(cx.as_slice_mut(), &storage.retptr)?; 278 ret.store(cx, ty, ptr) 279 } 280 Storage::Indirect(storage) => { 281 let ptr = validate_inbounds::<R>(cx.as_slice_mut(), &storage.retptr)?; 282 ret.store(cx, ty, ptr) 283 } 284 } 285 } 286 } 287 } 288 289 fn validate_inbounds<T: ComponentType>(memory: &[u8], ptr: &ValRaw) -> Result<usize> { 290 // FIXME(#4311): needs memory64 support 291 let ptr = usize::try_from(ptr.get_u32())?; 292 if ptr % usize::try_from(T::ALIGN32)? != 0 { 293 bail!("pointer not aligned"); 294 } 295 let end = match ptr.checked_add(T::SIZE32) { 296 Some(n) => n, 297 None => bail!("pointer size overflow"), 298 }; 299 if end > memory.len() { 300 bail!("pointer out of bounds") 301 } 302 Ok(ptr) 303 } 304 305 unsafe fn call_host_and_handle_result<T>( 306 cx: NonNull<VMOpaqueContext>, 307 func: impl FnOnce( 308 *mut ComponentInstance, 309 &Arc<ComponentTypes>, 310 StoreContextMut<'_, T>, 311 ) -> Result<()>, 312 ) -> bool { 313 let cx = VMComponentContext::from_opaque(cx); 314 let instance = cx.as_ref().instance(); 315 let types = (*instance).component_types(); 316 let raw_store = (*instance).store(); 317 let mut store = StoreContextMut(&mut *raw_store.cast()); 318 319 crate::runtime::vm::catch_unwind_and_record_trap(|| { 320 store.0.call_hook(CallHook::CallingHost)?; 321 let res = func(instance, types, store.as_context_mut()); 322 store.0.call_hook(CallHook::ReturningFromHost)?; 323 res 324 }) 325 } 326 327 unsafe fn call_host_dynamic<T, F>( 328 instance: *mut ComponentInstance, 329 types: &Arc<ComponentTypes>, 330 mut store: StoreContextMut<'_, T>, 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 { 343 if async_ { 344 todo!() 345 } 346 347 let options = Options::new( 348 store.0.id(), 349 NonNull::new(memory), 350 NonNull::new(realloc), 351 string_encoding, 352 ); 353 354 // Perform a dynamic check that this instance can indeed be left. Exiting 355 // the component is disallowed, for example, when the `realloc` function 356 // calls a canonical import. 357 if !flags.may_leave() { 358 bail!("cannot leave component instance"); 359 } 360 361 let args; 362 let ret_index; 363 364 let func_ty = &types[ty]; 365 let param_tys = &types[func_ty.params]; 366 let result_tys = &types[func_ty.results]; 367 let mut cx = LiftContext::new(store.0, &options, types, instance); 368 cx.enter_call(); 369 if let Some(param_count) = param_tys.abi.flat_count(MAX_FLAT_PARAMS) { 370 // NB: can use `MaybeUninit::slice_assume_init_ref` when that's stable 371 let mut iter = 372 mem::transmute::<&[MaybeUninit<ValRaw>], &[ValRaw]>(&storage[..param_count]).iter(); 373 args = param_tys 374 .types 375 .iter() 376 .map(|ty| Val::lift(&mut cx, *ty, &mut iter)) 377 .collect::<Result<Box<[_]>>>()?; 378 ret_index = param_count; 379 assert!(iter.next().is_none()); 380 } else { 381 let mut offset = 382 validate_inbounds_dynamic(¶m_tys.abi, cx.memory(), storage[0].assume_init_ref())?; 383 args = param_tys 384 .types 385 .iter() 386 .map(|ty| { 387 let abi = types.canonical_abi(ty); 388 let size = usize::try_from(abi.size32).unwrap(); 389 let memory = &cx.memory()[abi.next_field32_size(&mut offset)..][..size]; 390 Val::load(&mut cx, *ty, memory) 391 }) 392 .collect::<Result<Box<[_]>>>()?; 393 ret_index = 1; 394 }; 395 396 let mut result_vals = Vec::with_capacity(result_tys.types.len()); 397 for _ in result_tys.types.iter() { 398 result_vals.push(Val::Bool(false)); 399 } 400 closure(store.as_context_mut(), &args, &mut result_vals)?; 401 flags.set_may_leave(false); 402 403 let mut cx = LowerContext::new(store, &options, types, instance); 404 if let Some(cnt) = result_tys.abi.flat_count(MAX_FLAT_RESULTS) { 405 let mut dst = storage[..cnt].iter_mut(); 406 for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) { 407 val.lower(&mut cx, *ty, &mut dst)?; 408 } 409 assert!(dst.next().is_none()); 410 } else { 411 let ret_ptr = storage[ret_index].assume_init_ref(); 412 let mut ptr = validate_inbounds_dynamic(&result_tys.abi, cx.as_slice_mut(), ret_ptr)?; 413 for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) { 414 let offset = types.canonical_abi(ty).next_field32_size(&mut ptr); 415 val.store(&mut cx, *ty, offset)?; 416 } 417 } 418 419 flags.set_may_leave(true); 420 421 cx.exit_call()?; 422 423 return Ok(()); 424 } 425 426 fn validate_inbounds_dynamic(abi: &CanonicalAbiInfo, memory: &[u8], ptr: &ValRaw) -> Result<usize> { 427 // FIXME(#4311): needs memory64 support 428 let ptr = usize::try_from(ptr.get_u32())?; 429 if ptr % usize::try_from(abi.align32)? != 0 { 430 bail!("pointer not aligned"); 431 } 432 let end = match ptr.checked_add(usize::try_from(abi.size32).unwrap()) { 433 Some(n) => n, 434 None => bail!("pointer size overflow"), 435 }; 436 if end > memory.len() { 437 bail!("pointer out of bounds") 438 } 439 Ok(ptr) 440 } 441 442 extern "C" fn dynamic_entrypoint<T, F>( 443 cx: NonNull<VMOpaqueContext>, 444 data: NonNull<u8>, 445 ty: u32, 446 flags: NonNull<VMGlobalDefinition>, 447 memory: *mut VMMemoryDefinition, 448 realloc: *mut VMFuncRef, 449 string_encoding: u8, 450 async_: u8, 451 storage: NonNull<MaybeUninit<ValRaw>>, 452 storage_len: usize, 453 ) -> bool 454 where 455 F: Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static, 456 { 457 let data = data.as_ptr() as *const F; 458 unsafe { 459 call_host_and_handle_result(cx, |instance, types, store| { 460 call_host_dynamic::<T, _>( 461 instance, 462 types, 463 store, 464 TypeFuncIndex::from_u32(ty), 465 InstanceFlags::from_raw(flags), 466 memory, 467 realloc, 468 StringEncoding::from_u8(string_encoding).unwrap(), 469 async_ != 0, 470 NonNull::slice_from_raw_parts(storage, storage_len).as_mut(), 471 |store, params, results| (*data)(store, params, results), 472 ) 473 }) 474 } 475 } 476