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