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