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