Skip to content

Commit 87d1730

Browse files
authored
Supply type information in func_new for components (#11944)
Originally the `Linker::func_new` type for components was modeled after core wasm where a type was provided when the type was defined. This was difficult, however, because there's no way to construct types right now and instead a component had to be created and compiled to acquire type information from it. Later various refactorings meant that it was possible to drop the type information entirely from `func_new` meaning that it was "typeless" in a sense, and the functionality relied on the tagged nature of `Val` which always knows its type. This has proven a bit difficult to integrate into Python in some work I've been doing. Namely in Python component values are (at least IMO) best represented as native Python values where possible. Native Python doesn't distinguish, however, between integer bit widths (or signededness). This means that converting a Python value to a component value requires type information to guide the conversion process. Currently when defining a function in a linker there's no way to get at this type information meaning that the types would need to be supplied in Python, bringing up the same shortcomings of not being able to create types. In lieu of filling out type constructors, which is itself quite nontrivial, I've opted to do the next-most-easiest thing which is to supply the type to the callee when an import is invoked. This means that the callee has all the type information necessary. While this is somewhat costly in that it clones a few arcs it's expected to be a drop in the bucket compared to the inefficiencies of `Val` so it's at least in the same spirit of the cost of the API right now. My intention is to use this to update the C API for components and eventually end up with type information when Python is invoked in wasmtime-py. Also while I'm using wasmtime-py as a motivating case here that's sort of just one example of a host embedding which doesn't have the full fidelity of Rust's type system and might benefit from this.
1 parent a7e5141 commit 87d1730

11 files changed

Lines changed: 86 additions & 60 deletions

File tree

benches/call.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -797,10 +797,10 @@ mod component {
797797
bench_instance(group, store, &instance, "typed", is_async);
798798

799799
let mut untyped = component::Linker::new(&engine);
800-
untyped.root().func_new("nop", |_, _, _| Ok(())).unwrap();
800+
untyped.root().func_new("nop", |_, _, _, _| Ok(())).unwrap();
801801
untyped
802802
.root()
803-
.func_new("nop-params-and-results", |_caller, params, results| {
803+
.func_new("nop-params-and-results", |_caller, _ty, params, results| {
804804
assert_eq!(params.len(), 2);
805805
match params[0] {
806806
component::Val::U32(0) => {}

crates/c-api/src/component/linker.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ pub unsafe extern "C" fn wasmtime_component_linker_instance_add_func(
123123

124124
let result = linker_instance
125125
.linker_instance
126-
.func_new(&name, move |ctx, args, rets| {
126+
.func_new(&name, move |ctx, _ty, args, rets| {
127127
let _ = &foreign;
128128

129129
let args = args

crates/fuzzing/src/oracles.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,7 @@ pub fn dynamic_component_api_target(input: &mut arbitrary::Unstructured) -> arbi
10901090
.root()
10911091
.func_new(IMPORT_FUNCTION, {
10921092
move |mut cx: StoreContextMut<'_, (Vec<Val>, Option<Vec<Val>>)>,
1093+
_,
10931094
params: &[Val],
10941095
results: &mut [Val]|
10951096
-> Result<()> {

crates/misc/component-async-tests/tests/scenario/round_trip.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ pub async fn test_round_trip(
352352
linker
353353
.root()
354354
.instance("local:local/baz")?
355-
.func_new_concurrent("[async]foo", |_, params, results| {
355+
.func_new_concurrent("[async]foo", |_, _, params, results| {
356356
Box::pin(async move {
357357
sleep(Duration::from_millis(10)).await;
358358
let Some(Val::String(s)) = params.into_iter().next() else {

crates/misc/component-async-tests/tests/scenario/round_trip_direct.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ async fn test_round_trip_direct(
9494
wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
9595
linker
9696
.root()
97-
.func_new_concurrent("[async]foo", |_, params, results| {
97+
.func_new_concurrent("[async]foo", |_, _, params, results| {
9898
Box::pin(async move {
9999
sleep(Duration::from_millis(10)).await;
100100
let Some(Val::String(s)) = params.into_iter().next() else {

crates/misc/component-async-tests/tests/scenario/round_trip_many.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ async fn test_round_trip_many(
341341
linker
342342
.root()
343343
.instance("local:local/many")?
344-
.func_new_concurrent("[async]foo", |_, params, results| {
344+
.func_new_concurrent("[async]foo", |_, _, params, results| {
345345
Box::pin(async move {
346346
sleep(Duration::from_millis(10)).await;
347347
let mut params = params.into_iter();

crates/wasmtime/src/runtime/component/func/host.rs

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::component::concurrent::{Accessor, Status};
44
use crate::component::func::{LiftContext, LowerContext, Options};
55
use crate::component::matching::InstanceType;
66
use crate::component::storage::slice_to_storage_mut;
7+
use crate::component::types::ComponentFunc;
78
use crate::component::{ComponentNamedList, ComponentType, Instance, Lift, Lower, Val};
89
use crate::prelude::*;
910
use crate::runtime::vm::component::{
@@ -121,6 +122,7 @@ impl HostFunc {
121122
where
122123
F: Fn(
123124
StoreContextMut<'_, T>,
125+
ComponentFunc,
124126
Vec<Val>,
125127
usize,
126128
) -> Pin<Box<dyn Future<Output = Result<Vec<Val>>> + Send + 'static>>
@@ -141,13 +143,18 @@ impl HostFunc {
141143

142144
pub(crate) fn new_dynamic<T: 'static, F>(func: F) -> Arc<HostFunc>
143145
where
144-
F: Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
146+
F: Fn(StoreContextMut<'_, T>, ComponentFunc, &[Val], &mut [Val]) -> Result<()>
147+
+ Send
148+
+ Sync
149+
+ 'static,
145150
{
146-
Self::new_dynamic_canonical::<T, _>(move |store, mut params_and_results, result_start| {
147-
let (params, results) = params_and_results.split_at_mut(result_start);
148-
let result = func(store, params, results).map(move |()| params_and_results);
149-
Box::pin(async move { result })
150-
})
151+
Self::new_dynamic_canonical::<T, _>(
152+
move |store, ty, mut params_and_results, result_start| {
153+
let (params, results) = params_and_results.split_at_mut(result_start);
154+
let result = func(store, ty, params, results).map(move |()| params_and_results);
155+
Box::pin(async move { result })
156+
},
157+
)
151158
}
152159

153160
#[cfg(feature = "component-model-async")]
@@ -156,6 +163,7 @@ impl HostFunc {
156163
T: 'static,
157164
F: for<'a> Fn(
158165
&'a Accessor<T>,
166+
ComponentFunc,
159167
&'a [Val],
160168
&'a mut [Val],
161169
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>
@@ -164,16 +172,18 @@ impl HostFunc {
164172
+ 'static,
165173
{
166174
let func = Arc::new(func);
167-
Self::new_dynamic_canonical::<T, _>(move |store, mut params_and_results, result_start| {
168-
let func = func.clone();
169-
Box::pin(store.wrap_call(move |accessor| {
170-
Box::pin(async move {
171-
let (params, results) = params_and_results.split_at_mut(result_start);
172-
func(accessor, params, results).await?;
173-
Ok(params_and_results)
174-
})
175-
}))
176-
})
175+
Self::new_dynamic_canonical::<T, _>(
176+
move |store, ty, mut params_and_results, result_start| {
177+
let func = func.clone();
178+
Box::pin(store.wrap_call(move |accessor| {
179+
Box::pin(async move {
180+
let (params, results) = params_and_results.split_at_mut(result_start);
181+
func(accessor, ty, params, results).await?;
182+
Ok(params_and_results)
183+
})
184+
}))
185+
},
186+
)
177187
}
178188

179189
pub fn typecheck(&self, ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> {
@@ -700,6 +710,7 @@ unsafe fn call_host_dynamic<T, F>(
700710
where
701711
F: Fn(
702712
StoreContextMut<'_, T>,
713+
ComponentFunc,
703714
Vec<Val>,
704715
usize,
705716
) -> Pin<Box<dyn Future<Output = Result<Vec<Val>>> + Send + 'static>>
@@ -735,6 +746,7 @@ where
735746
} else {
736747
MAX_FLAT_PARAMS
737748
};
749+
let ty = ComponentFunc::from(ty, &lift.instance_type());
738750

739751
let ret_index = unsafe {
740752
dynamic_params_load(
@@ -763,7 +775,7 @@ where
763775
validate_inbounds_dynamic(&result_tys.abi, lower.as_slice_mut(), &retptr)?
764776
};
765777

766-
let future = closure(store.as_context_mut(), params_and_results, result_start);
778+
let future = closure(store.as_context_mut(), ty, params_and_results, result_start);
767779

768780
let task = instance.first_poll(store, future, caller_instance, {
769781
let types = types.clone();
@@ -810,7 +822,7 @@ where
810822
);
811823
}
812824
} else {
813-
let future = closure(store.as_context_mut(), params_and_results, result_start);
825+
let future = closure(store.as_context_mut(), ty, params_and_results, result_start);
814826
let result_vals = concurrent::poll_and_block(store.0, future, caller_instance)?;
815827
let result_vals = &result_vals[result_start..];
816828

@@ -915,6 +927,7 @@ extern "C" fn dynamic_entrypoint<T, F>(
915927
where
916928
F: Fn(
917929
StoreContextMut<'_, T>,
930+
ComponentFunc,
918931
Vec<Val>,
919932
usize,
920933
) -> Pin<Box<dyn Future<Output = Result<Vec<Val>>> + Send + 'static>>
@@ -932,7 +945,7 @@ where
932945
TypeFuncIndex::from_u32(ty),
933946
OptionsIndex::from_u32(options),
934947
NonNull::slice_from_raw_parts(storage, storage_len).as_mut(),
935-
move |store, params, results| (*data.as_ptr())(store, params, results),
948+
&*data.as_ptr(),
936949
)
937950
})
938951
}

crates/wasmtime/src/runtime/component/linker.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ impl<T: 'static> Linker<T> {
337337
let fully_qualified_name = parent_instance
338338
.map(|parent| format!("{parent}#{item_name}"))
339339
.unwrap_or_else(|| item_name.to_owned());
340-
linker.func_new(&item_name, move |_, _, _| {
340+
linker.func_new(&item_name, move |_, _, _, _| {
341341
bail!("unknown import: `{fully_qualified_name}` has not been defined")
342342
})?;
343343
}
@@ -635,15 +635,15 @@ impl<T: 'static> LinkerInstance<'_, T> {
635635
/// let mut linker = Linker::<()>::new(&engine);
636636
///
637637
/// // Sample function that takes no arguments.
638-
/// linker.root().func_new("thunk", |_store, params, results| {
638+
/// linker.root().func_new("thunk", |_store, _ty, params, results| {
639639
/// assert!(params.is_empty());
640640
/// assert!(results.is_empty());
641641
/// println!("Look ma, host hands!");
642642
/// Ok(())
643643
/// })?;
644644
///
645645
/// // This function takes one argument and returns one result.
646-
/// linker.root().func_new("is-even", |_store, params, results| {
646+
/// linker.root().func_new("is-even", |_store, _ty, params, results| {
647647
/// assert_eq!(params.len(), 1);
648648
/// let param = match params[0] {
649649
/// Val::U32(n) => n,
@@ -665,7 +665,10 @@ impl<T: 'static> LinkerInstance<'_, T> {
665665
pub fn func_new(
666666
&mut self,
667667
name: &str,
668-
func: impl Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
668+
func: impl Fn(StoreContextMut<'_, T>, types::ComponentFunc, &[Val], &mut [Val]) -> Result<()>
669+
+ Send
670+
+ Sync
671+
+ 'static,
669672
) -> Result<()> {
670673
self.insert(name, Definition::Func(HostFunc::new_dynamic(func)))?;
671674
Ok(())
@@ -682,6 +685,7 @@ impl<T: 'static> LinkerInstance<'_, T> {
682685
where
683686
F: for<'a> Fn(
684687
StoreContextMut<'a, T>,
688+
types::ComponentFunc,
685689
&'a [Val],
686690
&'a mut [Val],
687691
) -> Box<dyn Future<Output = Result<()>> + Send + 'a>
@@ -693,8 +697,9 @@ impl<T: 'static> LinkerInstance<'_, T> {
693697
self.engine.config().async_support,
694698
"cannot use `func_new_async` without enabling async support in the config"
695699
);
696-
let ff = move |store: StoreContextMut<'_, T>, params: &[Val], results: &mut [Val]| {
697-
store.with_blocking(|store, cx| cx.block_on(Pin::from(f(store, params, results)))?)
700+
let ff = move |store: StoreContextMut<'_, T>, ty, params: &[Val], results: &mut [Val]| {
701+
store
702+
.with_blocking(|store, cx| cx.block_on(Pin::from(f(store, ty, params, results)))?)
698703
};
699704
return self.func_new(name, ff);
700705
}
@@ -712,6 +717,7 @@ impl<T: 'static> LinkerInstance<'_, T> {
712717
T: 'static,
713718
F: for<'a> Fn(
714719
&'a Accessor<T>,
720+
types::ComponentFunc,
715721
&'a [Val],
716722
&'a mut [Val],
717723
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>

tests/all/component_model/import.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ fn simple() -> Result<()> {
148148
let mut linker = Linker::new(&engine);
149149
linker.root().func_new(
150150
"a",
151-
|mut store: StoreContextMut<'_, Option<String>>, args, _results| {
151+
|mut store: StoreContextMut<'_, Option<String>>, _, args, _results| {
152152
if let Val::String(s) = &args[0] {
153153
assert!(store.data().is_none());
154154
*store.data_mut() = Some(s.to_string());
@@ -249,7 +249,7 @@ fn functions_in_instances() -> Result<()> {
249249
let mut linker = Linker::new(&engine);
250250
linker.instance("test:test/foo")?.func_new(
251251
"a",
252-
|mut store: StoreContextMut<'_, Option<String>>, args, _results| {
252+
|mut store: StoreContextMut<'_, Option<String>>, _, args, _results| {
253253
if let Val::String(s) = &args[0] {
254254
assert!(store.data().is_none());
255255
*store.data_mut() = Some(s.to_string());
@@ -462,7 +462,7 @@ fn attempt_to_reenter_during_host() -> Result<()> {
462462
let mut linker = Linker::new(&engine);
463463
linker.root().func_new(
464464
"thunk",
465-
|mut store: StoreContextMut<'_, DynamicState>, _, _| {
465+
|mut store: StoreContextMut<'_, DynamicState>, _, _, _| {
466466
let func = store.data_mut().func.take().unwrap();
467467
let trap = func.call(&mut store, &[], &mut []).unwrap_err();
468468
assert_eq!(
@@ -848,7 +848,7 @@ async fn test_stack_and_heap_args_and_rets(concurrent: bool) -> Result<()> {
848848
if concurrent {
849849
linker
850850
.root()
851-
.func_new_concurrent("f1", |_, args, results| {
851+
.func_new_concurrent("f1", |_, _, args, results| {
852852
if let Val::U32(x) = &args[0] {
853853
assert_eq!(*x, 1);
854854
Box::pin(async {
@@ -861,7 +861,7 @@ async fn test_stack_and_heap_args_and_rets(concurrent: bool) -> Result<()> {
861861
})?;
862862
linker
863863
.root()
864-
.func_new_concurrent("f2", |_, args, results| {
864+
.func_new_concurrent("f2", |_, _, args, results| {
865865
if let Val::Tuple(tuple) = &args[0] {
866866
if let Val::String(s) = &tuple[0] {
867867
assert_eq!(s.deref(), "abc");
@@ -878,7 +878,7 @@ async fn test_stack_and_heap_args_and_rets(concurrent: bool) -> Result<()> {
878878
})?;
879879
linker
880880
.root()
881-
.func_new_concurrent("f3", |_, args, results| {
881+
.func_new_concurrent("f3", |_, _, args, results| {
882882
if let Val::U32(x) = &args[0] {
883883
assert_eq!(*x, 8);
884884
Box::pin(async {
@@ -891,7 +891,7 @@ async fn test_stack_and_heap_args_and_rets(concurrent: bool) -> Result<()> {
891891
})?;
892892
linker
893893
.root()
894-
.func_new_concurrent("f4", |_, args, results| {
894+
.func_new_concurrent("f4", |_, _, args, results| {
895895
if let Val::Tuple(tuple) = &args[0] {
896896
if let Val::String(s) = &tuple[0] {
897897
assert_eq!(s.deref(), "abc");
@@ -907,7 +907,7 @@ async fn test_stack_and_heap_args_and_rets(concurrent: bool) -> Result<()> {
907907
}
908908
})?;
909909
} else {
910-
linker.root().func_new("f1", |_, args, results| {
910+
linker.root().func_new("f1", |_, _, args, results| {
911911
if let Val::U32(x) = &args[0] {
912912
assert_eq!(*x, 1);
913913
results[0] = Val::U32(2);
@@ -916,7 +916,7 @@ async fn test_stack_and_heap_args_and_rets(concurrent: bool) -> Result<()> {
916916
panic!()
917917
}
918918
})?;
919-
linker.root().func_new("f2", |_, args, results| {
919+
linker.root().func_new("f2", |_, _, args, results| {
920920
if let Val::Tuple(tuple) = &args[0] {
921921
if let Val::String(s) = &tuple[0] {
922922
assert_eq!(s.deref(), "abc");
@@ -929,7 +929,7 @@ async fn test_stack_and_heap_args_and_rets(concurrent: bool) -> Result<()> {
929929
panic!()
930930
}
931931
})?;
932-
linker.root().func_new("f3", |_, args, results| {
932+
linker.root().func_new("f3", |_, _, args, results| {
933933
if let Val::U32(x) = &args[0] {
934934
assert_eq!(*x, 8);
935935
results[0] = Val::String("xyz".into());
@@ -938,7 +938,7 @@ async fn test_stack_and_heap_args_and_rets(concurrent: bool) -> Result<()> {
938938
panic!();
939939
}
940940
})?;
941-
linker.root().func_new("f4", |_, args, results| {
941+
linker.root().func_new("f4", |_, _, args, results| {
942942
if let Val::Tuple(tuple) = &args[0] {
943943
if let Val::String(s) = &tuple[0] {
944944
assert_eq!(s.deref(), "abc");
@@ -1126,7 +1126,7 @@ fn no_actual_wasm_code() -> Result<()> {
11261126
let mut linker = Linker::new(&engine);
11271127
linker
11281128
.root()
1129-
.func_new("f", |mut store: StoreContextMut<'_, u32>, _, _| {
1129+
.func_new("f", |mut store: StoreContextMut<'_, u32>, _, _, _| {
11301130
*store.data_mut() += 1;
11311131
Ok(())
11321132
})?;

tests/all/intrinsics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ fn native_loads_and_stores() -> Result<()> {
9393
linker.instance("host")?.func_new("get-pointer", {
9494
let ptr = ptr as usize;
9595
let ptr = u64::try_from(ptr).unwrap();
96-
move |_cx, _args, results| {
96+
move |_cx, _, _args, results| {
9797
results[0] = component::Val::U64(ptr);
9898
Ok(())
9999
}

0 commit comments

Comments
 (0)