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