diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml index eccea9cc7602..0cfd1d7fc2ac 100644 --- a/crates/c-api/Cargo.toml +++ b/crates/c-api/Cargo.toml @@ -64,6 +64,7 @@ debug-builtins = ['wasmtime/debug-builtins'] wat = ['dep:wat', 'wasmtime/wat'] pooling-allocator = ["wasmtime/pooling-allocator"] component-model = ["wasmtime/component-model"] +component-model-async = ["component-model", "async", "wasmtime/component-model-async"] pulley = ["wasmtime/pulley"] all-arch = ["wasmtime/all-arch"] # ... if you add a line above this be sure to change the other locations diff --git a/crates/c-api/artifact/Cargo.toml b/crates/c-api/artifact/Cargo.toml index bf23c85472fd..f1b3e82b4fc7 100644 --- a/crates/c-api/artifact/Cargo.toml +++ b/crates/c-api/artifact/Cargo.toml @@ -42,6 +42,7 @@ default = [ 'debug-builtins', 'pooling-allocator', 'component-model', + 'component-model-async', 'pulley', # 'all-arch', # intentionally off-by-default # ... if you add a line above this be sure to change the other locations @@ -68,6 +69,7 @@ winch = ["wasmtime-c-api/winch"] debug-builtins = ["wasmtime-c-api/debug-builtins"] pooling-allocator = ["wasmtime-c-api/pooling-allocator"] component-model = ["wasmtime-c-api/component-model"] +component-model-async = ["wasmtime-c-api/component-model-async"] pulley = ["wasmtime-c-api/pulley"] all-arch = ["wasmtime-c-api/all-arch"] # ... if you add a line above this be sure to read the comment at the end of diff --git a/crates/c-api/build.rs b/crates/c-api/build.rs index 211f2c4d0efc..a5ca742ea0d1 100644 --- a/crates/c-api/build.rs +++ b/crates/c-api/build.rs @@ -24,6 +24,7 @@ const FEATURES: &[&str] = &[ "WAT", "POOLING_ALLOCATOR", "COMPONENT_MODEL", + "COMPONENT_MODEL_ASYNC", "PULLEY", "ALL_ARCH", ]; diff --git a/crates/c-api/cmake/features.cmake b/crates/c-api/cmake/features.cmake index c487ea1eb693..fd4d4dcda274 100644 --- a/crates/c-api/cmake/features.cmake +++ b/crates/c-api/cmake/features.cmake @@ -47,6 +47,7 @@ feature(winch ON) feature(debug-builtins ON) feature(pooling-allocator ON) feature(component-model ON) +feature(component-model-async ON) feature(pulley ON) feature(all-arch OFF) # ... if you add a line above this be sure to change the other locations diff --git a/crates/c-api/include/wasmtime/component/func.h b/crates/c-api/include/wasmtime/component/func.h index a0fd8e30ab9a..2811610e4416 100644 --- a/crates/c-api/include/wasmtime/component/func.h +++ b/crates/c-api/include/wasmtime/component/func.h @@ -3,6 +3,7 @@ #ifndef WASMTIME_COMPONENT_FUNC_H #define WASMTIME_COMPONENT_FUNC_H +#include #include #include #include @@ -69,6 +70,30 @@ WASM_API_EXTERN wasmtime_error_t * wasmtime_component_func_post_return(const wasmtime_component_func_t *func, wasmtime_context_t *context); +#ifdef WASMTIME_FEATURE_COMPONENT_MODEL_ASYNC + +/** + * \brief Invokes \p func with the \p args given, returning the results + * asynchronously. + * + * This is the same as #wasmtime_component_func_call except that it is + * asynchronous. This is only compatible with stores associated with an + * asynchronous config. + * + * The result is a future that is owned by the caller and must be deleted via + * #wasmtime_call_future_delete. + * + * All parameters to this function must be kept alive and not modified until the + * returned #wasmtime_call_future_t is deleted. + */ +WASM_API_EXTERN wasmtime_call_future_t *wasmtime_component_func_call_async( + const wasmtime_component_func_t *func, wasmtime_context_t *context, + const wasmtime_component_val_t *args, size_t args_size, + wasmtime_component_val_t *results, size_t results_size, + wasmtime_error_t **error_ret); + +#endif // WASMTIME_FEATURE_COMPONENT_MODEL_ASYNC + #ifdef __cplusplus } // extern "C" #endif diff --git a/crates/c-api/include/wasmtime/component/linker.h b/crates/c-api/include/wasmtime/component/linker.h index 117acdcc2a81..aa6f76aa9893 100644 --- a/crates/c-api/include/wasmtime/component/linker.h +++ b/crates/c-api/include/wasmtime/component/linker.h @@ -4,6 +4,7 @@ #define WASMTIME_COMPONENT_LINKER_H #include +#include #include #include #include @@ -209,6 +210,87 @@ wasmtime_component_linker_instance_add_resource( WASM_API_EXTERN void wasmtime_component_linker_instance_delete( wasmtime_component_linker_instance_t *linker_instance); +#ifdef WASMTIME_FEATURE_COMPONENT_MODEL_ASYNC + +/** + * \brief Instantiate a component in \p linker asynchronously. + * + * This is the same as #wasmtime_component_linker_instantiate except that it + * is asynchronous. This is only compatible with stores associated with an + * asynchronous config. + * + * The result is a future that is owned by the caller and must be deleted via + * #wasmtime_call_future_delete. + * + * All parameters to this function must be kept alive and not modified until the + * returned #wasmtime_call_future_t is deleted. + * + * \param linker the linker to instantiate with + * \param context the store context + * \param component the component to instantiate + * \param instance_out where to store the returned instance + * \param error_ret where to store the returned error + */ +WASM_API_EXTERN wasmtime_call_future_t * +wasmtime_component_linker_instantiate_async( + const wasmtime_component_linker_t *linker, wasmtime_context_t *context, + const wasmtime_component_t *component, + wasmtime_component_instance_t *instance_out, wasmtime_error_t **error_ret); + +/// Type of the async callback used in +/// #wasmtime_component_linker_instance_add_func_async +typedef void (*wasmtime_component_func_async_callback_t)( + void *env, wasmtime_context_t *context, + const wasmtime_component_func_type_t *ty, wasmtime_component_val_t *args, + size_t nargs, wasmtime_component_val_t *results, size_t nresults, + wasmtime_error_t **error_ret, + wasmtime_async_continuation_t *continuation_ret); + +/** + * \brief Define an async function within this instance. + * + * This is the same as #wasmtime_component_linker_instance_add_func except + * that it supports async callbacks. + * + * \param linker_instance the instance to define the function in + * \param name the function name + * \param name_len length of \p name in bytes + * \param callback the async callback when this function gets called + * \param data host-specific data passed to the callback invocation, can be + * `NULL` + * \param finalizer optional finalizer for \p data, can be `NULL` + * \return on success `NULL`, otherwise an error + */ +WASM_API_EXTERN wasmtime_error_t * +wasmtime_component_linker_instance_add_func_async( + wasmtime_component_linker_instance_t *linker_instance, const char *name, + size_t name_len, wasmtime_component_func_async_callback_t callback, + void *data, void (*finalizer)(void *)); + +#ifdef WASMTIME_FEATURE_WASI + +/** + * \brief Add all WASI interfaces into the \p linker provided using async + * bindings. + */ +WASM_API_EXTERN wasmtime_error_t * +wasmtime_component_linker_add_wasip2_async(wasmtime_component_linker_t *linker); + +#endif // WASMTIME_FEATURE_WASI + +#ifdef WASMTIME_FEATURE_WASI_HTTP + +/** + * \brief Add WASI HTTP interfaces into the \p linker provided using async + * bindings. + */ +WASM_API_EXTERN wasmtime_error_t *wasmtime_component_linker_add_wasi_http_async( + wasmtime_component_linker_t *linker); + +#endif // WASMTIME_FEATURE_WASI_HTTP + +#endif // WASMTIME_FEATURE_COMPONENT_MODEL_ASYNC + #ifdef __cplusplus } // extern "C" #endif diff --git a/crates/c-api/include/wasmtime/component/linker.hh b/crates/c-api/include/wasmtime/component/linker.hh index 74d896f65eed..f0f847ed514d 100644 --- a/crates/c-api/include/wasmtime/component/linker.hh +++ b/crates/c-api/include/wasmtime/component/linker.hh @@ -232,6 +232,44 @@ class Linker { return std::monostate(); } #endif // WASMTIME_FEATURE_WASI_HTTP + +#ifdef WASMTIME_FEATURE_COMPONENT_MODEL_ASYNC +#ifdef WASMTIME_FEATURE_WASI + /** + * \brief Adds WASIp2 API definitions to this linker using async bindings. + * + * This is the same as \ref add_wasip2 except that it adds *asynchronous* + * versions of WASIp2 definitions. Only compatible with stores associated + * with an asynchronous config. + */ + Result add_wasip2_async() { + wasmtime_error_t *error = + wasmtime_component_linker_add_wasip2_async(ptr.get()); + if (error != nullptr) { + return Error(error); + } + return std::monostate(); + } +#endif // WASMTIME_FEATURE_WASI + +#ifdef WASMTIME_FEATURE_WASI_HTTP + /** + * \brief Adds WASI HTTP definitions to this linker using async bindings. + * + * This is the same as \ref add_wasi_http except that it adds + * *asynchronous* versions. Only compatible with stores associated with an + * asynchronous config. + */ + Result add_wasi_http_async() { + wasmtime_error_t *error = + wasmtime_component_linker_add_wasi_http_async(ptr.get()); + if (error != nullptr) { + return Error(error); + } + return std::monostate(); + } +#endif // WASMTIME_FEATURE_WASI_HTTP +#endif // WASMTIME_FEATURE_COMPONENT_MODEL_ASYNC }; } // namespace component diff --git a/crates/c-api/include/wasmtime/conf.h.in b/crates/c-api/include/wasmtime/conf.h.in index 56d6c2514a2a..e3c3d7621c7e 100644 --- a/crates/c-api/include/wasmtime/conf.h.in +++ b/crates/c-api/include/wasmtime/conf.h.in @@ -29,6 +29,7 @@ #cmakedefine WASMTIME_FEATURE_DEBUG_BUILTINS #cmakedefine WASMTIME_FEATURE_POOLING_ALLOCATOR #cmakedefine WASMTIME_FEATURE_COMPONENT_MODEL +#cmakedefine WASMTIME_FEATURE_COMPONENT_MODEL_ASYNC #cmakedefine WASMTIME_FEATURE_PULLEY #cmakedefine WASMTIME_FEATURE_ALL_ARCH // ... if you add a line above this be sure to change the other locations diff --git a/crates/c-api/include/wasmtime/config.h b/crates/c-api/include/wasmtime/config.h index d7788a694b1d..3f86854b2deb 100644 --- a/crates/c-api/include/wasmtime/config.h +++ b/crates/c-api/include/wasmtime/config.h @@ -844,6 +844,37 @@ WASMTIME_CONFIG_PROP(void, wasm_component_model_map, bool) #endif // WASMTIME_FEATURE_COMPONENT_MODEL +#ifdef WASMTIME_FEATURE_COMPONENT_MODEL_ASYNC + +/** + * \brief Configures whether the WebAssembly component-model async support will + * be enabled. + * + * For more information see the Rust documentation at + * https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.wasm_component_model_async. + */ +WASMTIME_CONFIG_PROP(void, wasm_component_model_async, bool) + +/** + * \brief Configures whether async built-in intrinsics are enabled for the + * component model. + * + * For more information see the Rust documentation at + * https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.wasm_component_model_async_builtins. + */ +WASMTIME_CONFIG_PROP(void, wasm_component_model_async_builtins, bool) + +/** + * \brief Configures whether stackful coroutine support is enabled for async + * components. + * + * For more information see the Rust documentation at + * https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.wasm_component_model_async_stackful. + */ +WASMTIME_CONFIG_PROP(void, wasm_component_model_async_stackful, bool) + +#endif // WASMTIME_FEATURE_COMPONENT_MODEL_ASYNC + #ifdef __cplusplus } // extern "C" #endif diff --git a/crates/c-api/src/async.rs b/crates/c-api/src/async.rs index 22f83106c7f5..cad337e92700 100644 --- a/crates/c-api/src/async.rs +++ b/crates/c-api/src/async.rs @@ -189,6 +189,12 @@ pub struct wasmtime_call_future_t<'a> { underlying: Pin + 'a>>, } +impl<'a> wasmtime_call_future_t<'a> { + pub(crate) fn new(future: Pin + 'a>>) -> Self { + Self { underlying: future } + } +} + #[unsafe(no_mangle)] pub extern "C" fn wasmtime_call_future_delete(_future: Box) {} @@ -265,7 +271,7 @@ pub unsafe extern "C" fn wasmtime_func_call_async<'a>( trap_ret, err_ret, )); - Box::new(wasmtime_call_future_t { underlying: fut }) + Box::new(wasmtime_call_future_t::new(fut)) } #[unsafe(no_mangle)] @@ -323,7 +329,7 @@ pub extern "C" fn wasmtime_linker_instantiate_async<'a>( trap_ret, err_ret, )); - Box::new(crate::wasmtime_call_future_t { underlying: fut }) + Box::new(crate::wasmtime_call_future_t::new(fut)) } async fn do_instance_pre_instantiate_async( @@ -355,7 +361,7 @@ pub extern "C" fn wasmtime_instance_pre_instantiate_async<'a>( trap_ret, err_ret, )); - Box::new(crate::wasmtime_call_future_t { underlying: fut }) + Box::new(crate::wasmtime_call_future_t::new(fut)) } pub type wasmtime_stack_memory_get_callback_t = diff --git a/crates/c-api/src/component/component.rs b/crates/c-api/src/component/component.rs index 1db3dbaa55d5..0124e49cfd9b 100644 --- a/crates/c-api/src/component/component.rs +++ b/crates/c-api/src/component/component.rs @@ -18,6 +18,33 @@ pub extern "C" fn wasmtime_config_wasm_component_model_map_set( c.config.wasm_component_model_map(enable); } +#[unsafe(no_mangle)] +#[cfg(feature = "component-model-async")] +pub extern "C" fn wasmtime_config_wasm_component_model_async_set( + c: &mut wasm_config_t, + enable: bool, +) { + c.config.wasm_component_model_async(enable); +} + +#[unsafe(no_mangle)] +#[cfg(feature = "component-model-async")] +pub extern "C" fn wasmtime_config_wasm_component_model_async_builtins_set( + c: &mut wasm_config_t, + enable: bool, +) { + c.config.wasm_component_model_async_builtins(enable); +} + +#[unsafe(no_mangle)] +#[cfg(feature = "component-model-async")] +pub extern "C" fn wasmtime_config_wasm_component_model_async_stackful_set( + c: &mut wasm_config_t, + enable: bool, +) { + c.config.wasm_component_model_async_stackful(enable); +} + #[derive(Clone)] #[repr(transparent)] pub struct wasmtime_component_t { diff --git a/crates/c-api/src/component/func.rs b/crates/c-api/src/component/func.rs index a540b079a7d8..b879a564471a 100644 --- a/crates/c-api/src/component/func.rs +++ b/crates/c-api/src/component/func.rs @@ -26,6 +26,37 @@ pub unsafe extern "C" fn wasmtime_component_func_call( }) } +#[unsafe(no_mangle)] +#[cfg(feature = "component-model-async")] +pub unsafe extern "C" fn wasmtime_component_func_call_async<'a>( + func: &'a Func, + mut context: WasmtimeStoreContextMut<'a>, + args: *const wasmtime_component_val_t, + args_len: usize, + results: *mut wasmtime_component_val_t, + results_len: usize, + err_ret: &'a mut *mut wasmtime_error_t, +) -> Box> { + let c_args = unsafe { crate::slice_from_raw_parts(args, args_len) }; + let c_results = unsafe { crate::slice_from_raw_parts_mut(results, results_len) }; + let fut = Box::pin(async move { + let args = c_args.iter().map(Val::from).collect::>(); + let mut results = vec![Val::Bool(false); c_results.len()]; + + match func.call_async(&mut context, &args, &mut results).await { + Ok(()) => { + for (c_val, rust_val) in std::iter::zip(c_results.iter_mut(), results) { + *c_val = wasmtime_component_val_t::from(&rust_val); + } + } + Err(err) => { + *err_ret = Box::into_raw(Box::new(wasmtime_error_t::from(err))); + } + } + }); + Box::new(crate::wasmtime_call_future_t::new(fut)) +} + #[deprecated(note = "no longer has any effect")] #[unsafe(no_mangle)] pub unsafe extern "C" fn wasmtime_component_func_post_return( diff --git a/crates/c-api/src/component/linker.rs b/crates/c-api/src/component/linker.rs index 9af8c82c9ce7..3823b8b69324 100644 --- a/crates/c-api/src/component/linker.rs +++ b/crates/c-api/src/component/linker.rs @@ -158,6 +158,86 @@ pub unsafe extern "C" fn wasmtime_component_linker_instance_add_func( crate::handle_result(result, |_| ()) } +#[cfg(feature = "component-model-async")] +pub type wasmtime_component_func_async_callback_t = extern "C" fn( + *mut c_void, + WasmtimeStoreContextMut<'_>, + &wasmtime_component_func_type_t, + *mut wasmtime_component_val_t, + usize, + *mut wasmtime_component_val_t, + usize, + &mut Option>, + &mut crate::wasmtime_async_continuation_t, +); + +#[unsafe(no_mangle)] +#[cfg(feature = "component-model-async")] +pub unsafe extern "C" fn wasmtime_component_linker_instance_add_func_async( + linker_instance: &mut wasmtime_component_linker_instance_t, + name: *const u8, + name_len: usize, + callback: wasmtime_component_func_async_callback_t, + data: *mut c_void, + finalizer: Option, +) -> Option> { + let name = unsafe { std::slice::from_raw_parts(name, name_len) }; + let Ok(name) = std::str::from_utf8(name) else { + return crate::bad_utf8(); + }; + + let foreign = crate::ForeignData { data, finalizer }; + + let result = + linker_instance + .linker_instance + .func_new_async(&name, move |ctx, ty, args, rets| { + let _ = &foreign; + + let mut c_args = args + .iter() + .map(|x| wasmtime_component_val_t::from(x)) + .collect::>(); + + let mut c_rets = vec![wasmtime_component_val_t::Bool(false); rets.len()]; + + let mut err = None; + extern "C" fn panic_callback(_: *mut c_void) -> bool { + panic!("callback must be set") + } + let mut continuation = crate::wasmtime_async_continuation_t { + callback: panic_callback, + env: std::ptr::null_mut(), + finalizer: None, + }; + callback( + foreign.data, + ctx, + &ty.into(), + c_args.as_mut_ptr(), + c_args.len(), + c_rets.as_mut_ptr(), + c_rets.len(), + &mut err, + &mut continuation, + ); + + if let Some(err) = err { + return Box::new(async { Err((*err).into()) }); + } + + Box::new(async move { + continuation.await; + for (rust_val, c_val) in std::iter::zip(rets, c_rets) { + *rust_val = Val::from(&c_val); + } + Ok(()) + }) + }); + + crate::handle_result(result, |_| ()) +} + #[unsafe(no_mangle)] #[cfg(feature = "wasi")] pub unsafe extern "C" fn wasmtime_component_linker_add_wasip2( @@ -176,6 +256,48 @@ pub unsafe extern "C" fn wasmtime_component_linker_add_wasi_http( crate::handle_result(result, |_| ()) } +#[unsafe(no_mangle)] +#[cfg(feature = "component-model-async")] +pub unsafe extern "C" fn wasmtime_component_linker_instantiate_async<'a>( + linker: &'a wasmtime_component_linker_t, + mut context: WasmtimeStoreContextMut<'a>, + component: &'a wasmtime_component_t, + instance_out: &'a mut Instance, + err_ret: &'a mut *mut wasmtime_error_t, +) -> Box> { + let fut = Box::pin(async move { + match linker + .linker + .instantiate_async(&mut context, &component.component) + .await + { + Ok(instance) => *instance_out = instance, + Err(err) => { + *err_ret = Box::into_raw(Box::new(wasmtime_error_t::from(err))); + } + } + }); + Box::new(crate::wasmtime_call_future_t::new(fut)) +} + +#[unsafe(no_mangle)] +#[cfg(all(feature = "wasi", feature = "component-model-async"))] +pub unsafe extern "C" fn wasmtime_component_linker_add_wasip2_async( + linker: &mut wasmtime_component_linker_t, +) -> Option> { + let result = wasmtime_wasi::p2::add_to_linker_async(&mut linker.linker); + crate::handle_result(result, |_| ()) +} + +#[unsafe(no_mangle)] +#[cfg(all(feature = "wasi-http", feature = "component-model-async"))] +pub unsafe extern "C" fn wasmtime_component_linker_add_wasi_http_async( + linker: &mut wasmtime_component_linker_t, +) -> Option> { + let result = wasmtime_wasi_http::p2::add_only_http_to_linker_async(&mut linker.linker); + crate::handle_result(result, |_| ()) +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn wasmtime_component_linker_define_unknown_imports_as_traps( linker: &mut wasmtime_component_linker_t, diff --git a/crates/c-api/src/component/val.rs b/crates/c-api/src/component/val.rs index 60177f2d2dc6..98bbaef07018 100644 --- a/crates/c-api/src/component/val.rs +++ b/crates/c-api/src/component/val.rs @@ -276,6 +276,10 @@ pub enum wasmtime_component_val_t { Map(wasmtime_component_valmap_t), } +// Safety: the C API async contract (documented in async.h) guarantees that +// values are not concurrently accessed while a future is alive. +unsafe impl Send for wasmtime_component_val_t {} + impl Default for wasmtime_component_val_t { fn default() -> Self { Self::Bool(false) diff --git a/crates/c-api/tests/CMakeLists.txt b/crates/c-api/tests/CMakeLists.txt index 6e8a6b0b70e7..adb87c584f10 100644 --- a/crates/c-api/tests/CMakeLists.txt +++ b/crates/c-api/tests/CMakeLists.txt @@ -40,6 +40,8 @@ add_capi_test(tests FILES component/values.cc component/linker.cc component/types.cc + component/async.cc + async.cc error.cc config.cc wat.cc diff --git a/crates/c-api/tests/async.cc b/crates/c-api/tests/async.cc new file mode 100644 index 000000000000..7f396187835e --- /dev/null +++ b/crates/c-api/tests/async.cc @@ -0,0 +1,68 @@ +#include +#include +#include + +#ifdef WASMTIME_FEATURE_ASYNC + +using namespace wasmtime; + +TEST(async, call_func_async) { + Engine engine; + Store store(engine); + auto context = store.context(); + + Module m = Module::compile( + engine, "(module" + " (func (export \"f\") (param i32 i32) (result i32)" + " local.get 0" + " local.get 1" + " i32.add))") + .unwrap(); + + Instance instance = Instance::create(store, m, {}).unwrap(); + auto f = std::get(*instance.get(store, "f")); + + wasmtime_val_t params[2] = {{.kind = WASMTIME_I32, .of = {.i32 = 34}}, + {.kind = WASMTIME_I32, .of = {.i32 = 35}}}; + wasmtime_val_t results[1] = {}; + wasm_trap_t *trap = nullptr; + wasmtime_error_t *error = nullptr; + + auto *future = wasmtime_func_call_async(context.capi(), &f.capi(), params, 2, + results, 1, &trap, &error); + + while (!wasmtime_call_future_poll(future)) { + } + wasmtime_call_future_delete(future); + + EXPECT_EQ(trap, nullptr); + EXPECT_EQ(error, nullptr); + EXPECT_EQ(results[0].kind, WASMTIME_I32); + EXPECT_EQ(results[0].of.i32, 69); +} + +TEST(async, instantiate_async) { + Engine engine; + Store store(engine); + + Module m = Module::compile(engine, "(module)").unwrap(); + + Linker linker(engine); + + wasmtime_instance_t instance; + wasm_trap_t *trap = nullptr; + wasmtime_error_t *error = nullptr; + + auto *future = + wasmtime_linker_instantiate_async(linker.capi(), store.context().capi(), + m.capi(), &instance, &trap, &error); + + while (!wasmtime_call_future_poll(future)) { + } + wasmtime_call_future_delete(future); + + EXPECT_EQ(trap, nullptr); + EXPECT_EQ(error, nullptr); +} + +#endif // WASMTIME_FEATURE_ASYNC diff --git a/crates/c-api/tests/component/async.cc b/crates/c-api/tests/component/async.cc new file mode 100644 index 000000000000..aafdd7a0b5a1 --- /dev/null +++ b/crates/c-api/tests/component/async.cc @@ -0,0 +1,185 @@ +#include +#include +#include +#include +#include + +#ifdef WASMTIME_FEATURE_COMPONENT_MODEL_ASYNC + +using namespace wasmtime::component; +using wasmtime::Config; +using wasmtime::Engine; +using wasmtime::Store; + +TEST(component_async, config) { + Config config; + wasmtime_config_wasm_component_model_async_set(config.capi(), true); + wasmtime_config_wasm_component_model_async_builtins_set(config.capi(), true); + wasmtime_config_wasm_component_model_async_stackful_set(config.capi(), true); + Engine engine(std::move(config)); +} + +TEST(component_async, call_func_async) { + static constexpr auto component_text = std::string_view{ + R"END( +(component + (core module $m + (func (export "f") (param $x i32) (param $y i32) (result i32) + (local.get $x) + (local.get $y) + (i32.add) + ) + ) + (core instance $i (instantiate $m)) + (func $f (param "x" u32) (param "y" u32) (result u32) (canon lift (core func $i "f"))) + (export "f" (func $f)) +) + )END", + }; + + Engine engine; + Store store(engine); + auto context = store.context(); + auto component = Component::compile(engine, component_text).unwrap(); + auto f = *component.export_index(nullptr, "f"); + + Linker linker(engine); + + auto instance = linker.instantiate(context, component).unwrap(); + auto func = *instance.get_func(context, f); + + auto params = std::array{ + uint32_t(34), + uint32_t(35), + }; + + auto results = std::array{false}; + + wasmtime_error_t *error = nullptr; + auto *future = wasmtime_component_func_call_async( + func.capi(), context.capi(), Val::to_capi(params.data()), params.size(), + Val::to_capi(results.data()), results.size(), &error); + + while (!wasmtime_call_future_poll(future)) { + } + wasmtime_call_future_delete(future); + + EXPECT_EQ(error, nullptr); + EXPECT_TRUE(results[0].is_u32()); + EXPECT_EQ(results[0].get_u32(), 69); +} + +TEST(component_async, instantiate_async) { + static constexpr auto component_text = std::string_view{ + R"END( +(component + (core module) +) + )END", + }; + + Engine engine; + Store store(engine); + auto context = store.context(); + auto component = Component::compile(engine, component_text).unwrap(); + + Linker linker(engine); + + wasmtime_component_instance_t instance; + wasmtime_error_t *error = nullptr; + auto *future = wasmtime_component_linker_instantiate_async( + linker.capi(), context.capi(), component.capi(), &instance, &error); + + while (!wasmtime_call_future_poll(future)) { + } + wasmtime_call_future_delete(future); + + EXPECT_EQ(error, nullptr); +} + +TEST(component_async, host_func_async) { + static constexpr auto component_text = std::string_view{ + R"END( +(component + (import "add" (func $add (param "x" u32) (param "y" u32) (result u32))) + (core module $m + (import "host" "add" (func $add (param i32 i32) (result i32))) + (func (export "f") (param $x i32) (param $y i32) (result i32) + (call $add (local.get $x) (local.get $y)) + ) + ) + (core func $add_lower (canon lower (func $add))) + (core instance $i (instantiate $m + (with "host" (instance (export "add" (func $add_lower)))) + )) + (func $f (param "x" u32) (param "y" u32) (result u32) (canon lift (core func $i "f"))) + (export "f" (func $f)) +) + )END", + }; + + Engine engine; + Store store(engine); + auto context = store.context(); + auto component = Component::compile(engine, component_text).unwrap(); + auto f = *component.export_index(nullptr, "f"); + + Linker linker(engine); + + // Define an async host function that adds two u32s + auto *root = wasmtime_component_linker_root(linker.capi()); + + wasmtime_component_func_async_callback_t callback = + [](void *, wasmtime_context_t *, const wasmtime_component_func_type_t *, + wasmtime_component_val_t *args, size_t, + wasmtime_component_val_t *results, size_t, wasmtime_error_t **, + wasmtime_async_continuation_t *cont) { + results[0].kind = WASMTIME_COMPONENT_U32; + results[0].of.u32 = args[0].of.u32 + args[1].of.u32; + // Signal immediate completion + cont->callback = [](void *) -> bool { return true; }; + cont->env = nullptr; + cont->finalizer = nullptr; + }; + + auto *err = wasmtime_component_linker_instance_add_func_async( + root, "add", 3, callback, nullptr, nullptr); + EXPECT_EQ(err, nullptr); + wasmtime_component_linker_instance_delete(root); + + wasmtime_component_instance_t raw_instance; + wasmtime_error_t *inst_error = nullptr; + auto *inst_future = wasmtime_component_linker_instantiate_async( + linker.capi(), context.capi(), component.capi(), &raw_instance, + &inst_error); + + while (!wasmtime_call_future_poll(inst_future)) { + } + wasmtime_call_future_delete(inst_future); + EXPECT_EQ(inst_error, nullptr); + + auto instance = Instance(raw_instance); + auto func = *instance.get_func(context, f); + + auto params = std::array{ + uint32_t(34), + uint32_t(35), + }; + + auto results = std::array{false}; + + wasmtime_error_t *error = nullptr; + auto *future = wasmtime_component_func_call_async( + func.capi(), context.capi(), Val::to_capi(params.data()), params.size(), + Val::to_capi(results.data()), results.size(), &error); + + while (!wasmtime_call_future_poll(future)) { + } + wasmtime_call_future_delete(future); + + EXPECT_EQ(error, nullptr); + EXPECT_TRUE(results[0].is_u32()); + EXPECT_EQ(results[0].get_u32(), 69); +} + +#endif // WASMTIME_FEATURE_COMPONENT_MODEL_ASYNC