1#[cfg(feature = "component-model-async")]
4use crate::component::concurrent;
5#[cfg(feature = "component-model-async")]
6use crate::component::concurrent::{Accessor, Status};
7use crate::component::func::{LiftContext, LowerContext};
8use crate::component::matching::InstanceType;
9use crate::component::storage::{slice_to_storage, slice_to_storage_mut};
10use crate::component::types::ComponentFunc;
11use crate::component::{ComponentNamedList, Instance, Lift, Lower, Val};
12use crate::prelude::*;
13use crate::runtime::vm::component::{
14 ComponentInstance, VMComponentContext, VMLowering, VMLoweringCallee,
15};
16use crate::runtime::vm::{VMOpaqueContext, VMStore};
17use crate::{AsContextMut, CallHook, StoreContextMut, ValRaw};
18use alloc::sync::Arc;
19use core::any::Any;
20use core::mem::{self, MaybeUninit};
21#[cfg(feature = "component-model-async")]
22use core::pin::Pin;
23use core::ptr::NonNull;
24use wasmtime_environ::component::{
25 CanonicalAbiInfo, InterfaceType, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS, OptionsIndex, TypeFuncIndex,
26};
27
28pub struct HostFunc {
34 entrypoint: VMLoweringCallee,
36
37 typecheck: fn(TypeFuncIndex, &InstanceType<'_>) -> Result<()>,
44
45 func: Box<dyn Any + Send + Sync>,
51}
52
53impl core::fmt::Debug for HostFunc {
54 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
55 f.debug_struct("HostFunc").finish_non_exhaustive()
56 }
57}
58
59enum HostResult<T> {
60 Done(Result<T>),
61 #[cfg(feature = "component-model-async")]
62 Future(Pin<Box<dyn Future<Output = Result<T>> + Send>>),
63}
64
65impl HostFunc {
66 fn new<T, F, P, R>(func: F) -> Arc<HostFunc>
67 where
68 T: 'static,
69 R: Send + Sync + 'static,
70 F: HostFn<T, P, R> + Send + Sync + 'static,
71 {
72 Arc::new(HostFunc {
73 entrypoint: F::cabi_entrypoint,
74 typecheck: F::typecheck,
75 func: Box::new(func),
76 })
77 }
78
79 pub(crate) fn from_closure<T, F, P, R>(func: F) -> Arc<HostFunc>
82 where
83 T: 'static,
84 F: Fn(StoreContextMut<T>, P) -> Result<R> + Send + Sync + 'static,
85 P: ComponentNamedList + Lift + 'static,
86 R: ComponentNamedList + Lower + 'static,
87 {
88 Self::new(StaticHostFn::<_, false>::new(move |store, params| {
89 HostResult::Done(func(store, params))
90 }))
91 }
92
93 #[cfg(feature = "component-model-async")]
96 pub(crate) fn from_concurrent<T, F, P, R>(func: F) -> Arc<HostFunc>
97 where
98 T: 'static,
99 F: Fn(&Accessor<T>, P) -> Pin<Box<dyn Future<Output = Result<R>> + Send + '_>>
100 + Send
101 + Sync
102 + 'static,
103 P: ComponentNamedList + Lift + 'static,
104 R: ComponentNamedList + Lower + 'static,
105 {
106 let func = Arc::new(func);
107 Self::new(StaticHostFn::<_, true>::new(move |store, params| {
108 let func = func.clone();
109 HostResult::Future(Box::pin(
110 store.wrap_call(move |accessor| func(accessor, params)),
111 ))
112 }))
113 }
114
115 pub(crate) fn new_dynamic<T: 'static, F>(func: F) -> Arc<HostFunc>
118 where
119 F: Fn(StoreContextMut<'_, T>, ComponentFunc, &[Val], &mut [Val]) -> Result<()>
120 + Send
121 + Sync
122 + 'static,
123 {
124 Self::new(DynamicHostFn::<_, false>::new(
125 move |store, ty, mut params_and_results, result_start| {
126 let (params, results) = params_and_results.split_at_mut(result_start);
127 let result = func(store, ty, params, results).map(move |()| params_and_results);
128 HostResult::Done(result)
129 },
130 ))
131 }
132
133 #[cfg(feature = "component-model-async")]
136 pub(crate) fn new_dynamic_concurrent<T, F>(func: F) -> Arc<HostFunc>
137 where
138 T: 'static,
139 F: for<'a> Fn(
140 &'a Accessor<T>,
141 ComponentFunc,
142 &'a [Val],
143 &'a mut [Val],
144 ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>
145 + Send
146 + Sync
147 + 'static,
148 {
149 let func = Arc::new(func);
150 Self::new(DynamicHostFn::<_, true>::new(
151 move |store, ty, mut params_and_results, result_start| {
152 let func = func.clone();
153 HostResult::Future(Box::pin(store.wrap_call(move |accessor| {
154 Box::pin(async move {
155 let (params, results) = params_and_results.split_at_mut(result_start);
156 func(accessor, ty, params, results).await?;
157 Ok(params_and_results)
158 })
159 })))
160 },
161 ))
162 }
163
164 pub fn typecheck(&self, ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> {
165 (self.typecheck)(ty, types)
166 }
167
168 pub fn lowering(&self) -> VMLowering {
169 let data = NonNull::from(&*self.func).cast();
170 VMLowering {
171 callee: NonNull::new(self.entrypoint as *mut _).unwrap().into(),
172 data: data.into(),
173 }
174 }
175}
176
177enum Source<'a> {
179 Flat(&'a [ValRaw]),
181 Memory(usize),
184}
185
186enum Destination<'a> {
188 Flat(&'a mut [MaybeUninit<ValRaw>]),
190 Memory(usize),
193}
194
195trait HostFn<T, P, R>
201where
202 T: 'static,
203 R: Send + Sync + 'static,
204{
205 const ASYNC: bool;
208
209 fn typecheck(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()>;
212
213 fn run(&self, store: StoreContextMut<'_, T>, params: P) -> HostResult<R>;
215
216 fn lift_params(cx: &mut LiftContext<'_>, ty: TypeFuncIndex, source: Source<'_>) -> Result<P>;
219
220 fn lower_result(
223 cx: &mut LowerContext<'_, T>,
224 ty: TypeFuncIndex,
225 result: R,
226 dst: Destination<'_>,
227 ) -> Result<()>;
228
229 unsafe extern "C" fn cabi_entrypoint(
249 cx: NonNull<VMOpaqueContext>,
250 data: NonNull<u8>,
251 ty: u32,
252 options: u32,
253 storage: NonNull<MaybeUninit<ValRaw>>,
254 storage_len: usize,
255 ) -> bool
256 where
257 Self: Sized,
258 {
259 let cx = unsafe { VMComponentContext::from_opaque(cx) };
260 unsafe {
261 ComponentInstance::enter_host_from_wasm(cx, |store, instance| {
262 let mut store = store.unchecked_context_mut();
263 let ty = TypeFuncIndex::from_u32(ty);
264 let options = OptionsIndex::from_u32(options);
265 let storage = NonNull::slice_from_raw_parts(storage, storage_len).as_mut();
266 let data = data.cast::<Self>().as_ref();
267
268 store.0.call_hook(CallHook::CallingHost)?;
269 let res = data.entrypoint(store.as_context_mut(), instance, ty, options, storage);
270 store.0.call_hook(CallHook::ReturningFromHost)?;
271
272 res
273 })
274 }
275 }
276
277 fn entrypoint(
280 &self,
281 store: StoreContextMut<'_, T>,
282 instance: Instance,
283 ty: TypeFuncIndex,
284 options: OptionsIndex,
285 storage: &mut [MaybeUninit<ValRaw>],
286 ) -> Result<()> {
287 let vminstance = instance.id().get(store.0);
288 let opts = &vminstance.component().env_component().options[options];
289 let caller_instance = opts.instance;
290 let flags = vminstance.instance_flags(caller_instance);
291
292 if unsafe { !flags.may_leave() } {
296 return Err(format_err!(crate::Trap::CannotLeaveComponent));
297 }
298
299 if opts.async_ {
300 #[cfg(feature = "component-model-async")]
301 return self.call_async_lower(store, instance, ty, options, storage);
302 #[cfg(not(feature = "component-model-async"))]
303 unreachable!(
304 "async-lowered imports should have failed validation \
305 when `component-model-async` feature disabled"
306 );
307 } else {
308 self.call_sync_lower(store, instance, ty, options, storage)
309 }
310 }
311
312 fn call_sync_lower(
320 &self,
321 mut store: StoreContextMut<'_, T>,
322 instance: Instance,
323 ty: TypeFuncIndex,
324 options: OptionsIndex,
325 storage: &mut [MaybeUninit<ValRaw>],
326 ) -> Result<()> {
327 if Self::ASYNC {
328 store.0.check_blocking()?;
332 }
333
334 let mut lift = LiftContext::new(store.0.store_opaque_mut(), options, instance);
335 let (params, rest) = self.load_params(&mut lift, ty, MAX_FLAT_PARAMS, storage)?;
336 #[cfg(feature = "component-model-async")]
337 let caller_instance = lift.options().instance;
338
339 let ret = match self.run(store.as_context_mut(), params) {
340 HostResult::Done(result) => result?,
341 #[cfg(feature = "component-model-async")]
342 HostResult::Future(future) => {
343 concurrent::poll_and_block(store.0, future, caller_instance)?
344 }
345 };
346
347 let mut lower = LowerContext::new(store, options, instance);
348 let fty = &lower.types[ty];
349 let result_tys = &lower.types[fty.results];
350 let dst = if let Some(cnt) = result_tys.abi.flat_count(MAX_FLAT_RESULTS) {
351 Destination::Flat(&mut storage[..cnt])
352 } else {
353 let ptr = unsafe { rest[0].assume_init_ref() };
357 Destination::Memory(validate_inbounds_dynamic(
358 &result_tys.abi,
359 lower.as_slice_mut(),
360 ptr,
361 )?)
362 };
363 Self::lower_result_and_exit_call(&mut lower, ty, ret, dst)
364 }
365
366 #[cfg(feature = "component-model-async")]
372 fn call_async_lower(
373 &self,
374 store: StoreContextMut<'_, T>,
375 instance: Instance,
376 ty: TypeFuncIndex,
377 options: OptionsIndex,
378 storage: &mut [MaybeUninit<ValRaw>],
379 ) -> Result<()> {
380 use wasmtime_environ::component::MAX_FLAT_ASYNC_PARAMS;
381
382 let (component, store) = instance.component_and_store_mut(store.0);
383 let mut store = StoreContextMut(store);
384 let types = component.types();
385 let fty = &types[ty];
386
387 let mut lift = LiftContext::new(store.0.store_opaque_mut(), options, instance);
390 let caller_instance = lift.options().instance;
391 let (params, rest) = self.load_params(&mut lift, ty, MAX_FLAT_ASYNC_PARAMS, storage)?;
392
393 let retptr = if !lift.types[fty.results].types.is_empty() {
395 let mut lower = LowerContext::new(store.as_context_mut(), options, instance);
396 let ptr = unsafe { rest[0].assume_init_ref() };
399 let result_tys = &lower.types[fty.results];
400 validate_inbounds_dynamic(&result_tys.abi, lower.as_slice_mut(), ptr)?
401 } else {
402 0
407 };
408
409 let host_result = self.run(store.as_context_mut(), params);
410
411 let task = match host_result {
412 HostResult::Done(result) => {
413 Self::lower_result_and_exit_call(
414 &mut LowerContext::new(store, options, instance),
415 ty,
416 result?,
417 Destination::Memory(retptr),
418 )?;
419 None
420 }
421 #[cfg(feature = "component-model-async")]
422 HostResult::Future(future) => {
423 instance.first_poll(store, future, caller_instance, move |store, ret| {
424 Self::lower_result_and_exit_call(
425 &mut LowerContext::new(store, options, instance),
426 ty,
427 ret,
428 Destination::Memory(retptr),
429 )
430 })?
431 }
432 };
433
434 storage[0].write(ValRaw::u32(if let Some(task) = task {
435 Status::Started.pack(Some(task))
436 } else {
437 Status::Returned.pack(None)
438 }));
439
440 Ok(())
441 }
442
443 fn load_params<'a>(
448 &self,
449 lift: &mut LiftContext<'_>,
450 ty: TypeFuncIndex,
451 max_flat_params: usize,
452 storage: &'a [MaybeUninit<ValRaw>],
453 ) -> Result<(P, &'a [MaybeUninit<ValRaw>])> {
454 let fty = &lift.types[ty];
455 let param_tys = &lift.types[fty.params];
456 let param_flat_count = param_tys.abi.flat_count(max_flat_params);
457 lift.enter_call();
458 let src = match param_flat_count {
459 Some(cnt) => {
460 let params = &storage[..cnt];
461 Source::Flat(unsafe { mem::transmute::<&[MaybeUninit<ValRaw>], &[ValRaw]>(params) })
465 }
466 None => {
467 let ptr = unsafe { storage[0].assume_init_ref() };
471 Source::Memory(validate_inbounds_dynamic(
472 ¶m_tys.abi,
473 lift.memory(),
474 ptr,
475 )?)
476 }
477 };
478 let params = Self::lift_params(lift, ty, src)?;
479 Ok((params, &storage[param_flat_count.unwrap_or(1)..]))
480 }
481
482 fn lower_result_and_exit_call(
484 lower: &mut LowerContext<'_, T>,
485 ty: TypeFuncIndex,
486 ret: R,
487 dst: Destination<'_>,
488 ) -> Result<()> {
489 let caller_instance = lower.options().instance;
490 let mut flags = lower.instance_mut().instance_flags(caller_instance);
491 unsafe {
492 flags.set_may_leave(false);
493 }
494 Self::lower_result(lower, ty, ret, dst)?;
495 unsafe {
496 flags.set_may_leave(true);
497 }
498 lower.exit_call()?;
499 Ok(())
500 }
501}
502
503#[repr(transparent)]
506struct StaticHostFn<F, const ASYNC: bool>(F);
507
508impl<F, const ASYNC: bool> StaticHostFn<F, ASYNC> {
509 fn new<T, P, R>(func: F) -> Self
510 where
511 T: 'static,
512 P: ComponentNamedList + Lift + 'static,
513 R: ComponentNamedList + Lower + 'static,
514 F: Fn(StoreContextMut<'_, T>, P) -> HostResult<R>,
515 {
516 Self(func)
517 }
518}
519
520impl<T, F, P, R, const ASYNC: bool> HostFn<T, P, R> for StaticHostFn<F, ASYNC>
521where
522 T: 'static,
523 F: Fn(StoreContextMut<'_, T>, P) -> HostResult<R>,
524 P: ComponentNamedList + Lift + 'static,
525 R: ComponentNamedList + Lower + 'static,
526{
527 const ASYNC: bool = ASYNC;
528
529 fn typecheck(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> {
530 let ty = &types.types[ty];
531 if ASYNC != ty.async_ {
532 bail!("type mismatch with async");
533 }
534 P::typecheck(&InterfaceType::Tuple(ty.params), types)
535 .context("type mismatch with parameters")?;
536 R::typecheck(&InterfaceType::Tuple(ty.results), types)
537 .context("type mismatch with results")?;
538 Ok(())
539 }
540
541 fn run(&self, store: StoreContextMut<'_, T>, params: P) -> HostResult<R> {
542 (self.0)(store, params)
543 }
544
545 fn lift_params(cx: &mut LiftContext<'_>, ty: TypeFuncIndex, src: Source<'_>) -> Result<P> {
546 let ty = InterfaceType::Tuple(cx.types[ty].params);
547 match src {
548 Source::Flat(storage) => {
549 let storage: &P::Lower = unsafe { slice_to_storage(storage) };
554 P::linear_lift_from_flat(cx, ty, storage)
555 }
556 Source::Memory(offset) => {
557 P::linear_lift_from_memory(cx, ty, &cx.memory()[offset..][..P::SIZE32])
558 }
559 }
560 }
561
562 fn lower_result(
563 cx: &mut LowerContext<'_, T>,
564 ty: TypeFuncIndex,
565 ret: R,
566 dst: Destination<'_>,
567 ) -> Result<()> {
568 let fty = &cx.types[ty];
569 let ty = InterfaceType::Tuple(fty.results);
570 match dst {
571 Destination::Flat(storage) => {
572 let storage: &mut MaybeUninit<R::Lower> = unsafe { slice_to_storage_mut(storage) };
576 ret.linear_lower_to_flat(cx, ty, storage)
577 }
578 Destination::Memory(ptr) => ret.linear_lower_to_memory(cx, ty, ptr),
579 }
580 }
581}
582
583struct DynamicHostFn<F, const ASYNC: bool>(F);
590
591impl<F, const ASYNC: bool> DynamicHostFn<F, ASYNC> {
592 fn new<T>(func: F) -> Self
593 where
594 T: 'static,
595 F: Fn(StoreContextMut<'_, T>, ComponentFunc, Vec<Val>, usize) -> HostResult<Vec<Val>>,
596 {
597 Self(func)
598 }
599}
600
601impl<T, F, const ASYNC: bool> HostFn<T, (ComponentFunc, Vec<Val>), Vec<Val>>
602 for DynamicHostFn<F, ASYNC>
603where
604 T: 'static,
605 F: Fn(StoreContextMut<'_, T>, ComponentFunc, Vec<Val>, usize) -> HostResult<Vec<Val>>,
606{
607 const ASYNC: bool = ASYNC;
608
609 fn typecheck(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> {
613 let ty = &types.types[ty];
614 if ASYNC != ty.async_ {
615 bail!("type mismatch with async");
616 }
617
618 Ok(())
619 }
620
621 fn run(
622 &self,
623 store: StoreContextMut<'_, T>,
624 (ty, mut params): (ComponentFunc, Vec<Val>),
625 ) -> HostResult<Vec<Val>> {
626 let offset = params.len();
627 for _ in 0..ty.results().len() {
628 params.push(Val::Bool(false));
629 }
630 (self.0)(store, ty, params, offset)
631 }
632
633 fn lift_params(
634 cx: &mut LiftContext<'_>,
635 ty: TypeFuncIndex,
636 src: Source<'_>,
637 ) -> Result<(ComponentFunc, Vec<Val>)> {
638 let param_tys = &cx.types[cx.types[ty].params];
639 let mut params = Vec::new();
640 match src {
641 Source::Flat(storage) => {
642 let mut iter = storage.iter();
643 for ty in param_tys.types.iter() {
644 params.push(Val::lift(cx, *ty, &mut iter)?);
645 }
646 assert!(iter.next().is_none());
647 }
648 Source::Memory(mut offset) => {
649 for ty in param_tys.types.iter() {
650 let abi = cx.types.canonical_abi(ty);
651 let size = usize::try_from(abi.size32).unwrap();
652 let memory = &cx.memory()[abi.next_field32_size(&mut offset)..][..size];
653 params.push(Val::load(cx, *ty, memory)?);
654 }
655 }
656 }
657
658 Ok((ComponentFunc::from(ty, &cx.instance_type()), params))
659 }
660
661 fn lower_result(
662 cx: &mut LowerContext<'_, T>,
663 ty: TypeFuncIndex,
664 result_vals: Vec<Val>,
665 dst: Destination<'_>,
666 ) -> Result<()> {
667 let fty = &cx.types[ty];
668 let param_tys = &cx.types[fty.params];
669 let result_tys = &cx.types[fty.results];
670 let result_vals = &result_vals[param_tys.types.len()..];
671 match dst {
672 Destination::Flat(storage) => {
673 let mut dst = storage.iter_mut();
674 for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) {
675 val.lower(cx, *ty, &mut dst)?;
676 }
677 assert!(dst.next().is_none());
678 }
679 Destination::Memory(mut ptr) => {
680 for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) {
681 let offset = cx.types.canonical_abi(ty).next_field32_size(&mut ptr);
682 val.store(cx, *ty, offset)?;
683 }
684 }
685 }
686 Ok(())
687 }
688}
689
690pub(crate) fn validate_inbounds_dynamic(
691 abi: &CanonicalAbiInfo,
692 memory: &[u8],
693 ptr: &ValRaw,
694) -> Result<usize> {
695 let ptr = usize::try_from(ptr.get_u32())?;
697 if ptr % usize::try_from(abi.align32)? != 0 {
698 bail!("pointer not aligned");
699 }
700 let end = match ptr.checked_add(usize::try_from(abi.size32).unwrap()) {
701 Some(n) => n,
702 None => bail!("pointer size overflow"),
703 };
704 if end > memory.len() {
705 bail!("pointer out of bounds")
706 }
707 Ok(ptr)
708}