diff --git a/crates/c-api/CMakeLists.txt b/crates/c-api/CMakeLists.txt index 89953f55ca86..df203697d985 100644 --- a/crates/c-api/CMakeLists.txt +++ b/crates/c-api/CMakeLists.txt @@ -166,6 +166,7 @@ target_include_directories( if (BUILD_TESTS) message(STATUS "Building tests") + set(CMAKE_CXX_STANDARD 20) enable_language(CXX) set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) diff --git a/crates/c-api/doxygen.conf.in b/crates/c-api/doxygen.conf.in index 5ce790eb6961..960937c5edcc 100644 --- a/crates/c-api/doxygen.conf.in +++ b/crates/c-api/doxygen.conf.in @@ -949,7 +949,8 @@ EXCLUDE_SYMBOLS = assertions \ wasmtime::detail \ CASE_KIND_PRINT_NAME \ CASE_KIND_TO_C \ - CASE_C_TO_KIND + CASE_C_TO_KIND \ + DECLARE_VEC # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include diff --git a/crates/c-api/include/wasmtime/component.h b/crates/c-api/include/wasmtime/component.h index 06c5278b60d6..ccb41ca52e4e 100644 --- a/crates/c-api/include/wasmtime/component.h +++ b/crates/c-api/include/wasmtime/component.h @@ -5,5 +5,6 @@ #include #include #include +#include #endif // WASMTIME_COMPONENT_H diff --git a/crates/c-api/include/wasmtime/component/component.h b/crates/c-api/include/wasmtime/component/component.h index 743827d54cdc..b2f755d1fc7d 100644 --- a/crates/c-api/include/wasmtime/component/component.h +++ b/crates/c-api/include/wasmtime/component/component.h @@ -1,3 +1,5 @@ +/// \file wasmtime/component/component.h + #ifndef WASMTIME_COMPONENT_COMPONENT_H #define WASMTIME_COMPONENT_COMPONENT_H @@ -101,13 +103,13 @@ WASM_API_EXTERN wasmtime_component_t * wasmtime_component_clone(const wasmtime_component_t *component); /** - * \brief Deletes a #wasmtime_component_t created by - * #wasmtime_component_from_binary + * \brief Deletes a #wasmtime_component_t created by #wasmtime_component_new * * \param component the component to delete */ WASM_API_EXTERN void wasmtime_component_delete(wasmtime_component_t *component); +/// A value which represents a known export of a component. typedef struct wasmtime_component_export_index_t wasmtime_component_export_index_t; diff --git a/crates/c-api/include/wasmtime/component/func.h b/crates/c-api/include/wasmtime/component/func.h index b04ca0b76c94..7c1112320d55 100644 --- a/crates/c-api/include/wasmtime/component/func.h +++ b/crates/c-api/include/wasmtime/component/func.h @@ -1,7 +1,12 @@ +/// \file wasmtime/component/func.h + #ifndef WASMTIME_COMPONENT_FUNC_H #define WASMTIME_COMPONENT_FUNC_H +#include #include +#include +#include #ifdef WASMTIME_FEATURE_COMPONENT_MODEL @@ -23,6 +28,19 @@ typedef struct wasmtime_component_func { size_t index; } wasmtime_component_func_t; +/// \brief Invokes \p func with the \p args given and returns the result. +/// +/// The \p args provided must match the parameters that this function takes in +/// terms of their types and the number of parameters. Results will be written +/// to the \p results provided if the call completes successfully. The initial +/// types of the values in \p results are ignored and values are overwritten to +/// write the result. It's required that the \p results_size exactly matches the +/// number of results that this function produces. +WASM_API_EXTERN wasmtime_error_t *wasmtime_component_func_call( + 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); + #ifdef __cplusplus } // extern "C" #endif diff --git a/crates/c-api/include/wasmtime/component/instance.h b/crates/c-api/include/wasmtime/component/instance.h index 28576c538bee..a71134f7ed9e 100644 --- a/crates/c-api/include/wasmtime/component/instance.h +++ b/crates/c-api/include/wasmtime/component/instance.h @@ -1,3 +1,5 @@ +/// \file wasmtime/component/instance.h + #ifndef WASMTIME_COMPONENT_INSTANCE_H #define WASMTIME_COMPONENT_INSTANCE_H @@ -27,8 +29,8 @@ typedef struct wasmtime_component_instance { } wasmtime_component_instance_t; /** - * \brief A methods similar to \fn wasmtime_component_get_export_index() except - * for this instance. + * \brief A methods similar to #wasmtime_component_get_export_index except for + * this instance. * * \param instance the instance to look up \p name in * \param context the context where \p instance lives in diff --git a/crates/c-api/include/wasmtime/component/linker.h b/crates/c-api/include/wasmtime/component/linker.h index 9cfdf6e94df6..60a76c5ef531 100644 --- a/crates/c-api/include/wasmtime/component/linker.h +++ b/crates/c-api/include/wasmtime/component/linker.h @@ -1,3 +1,5 @@ +/// \file wasmtime/component/linker.h + #ifndef WASMTIME_COMPONENT_LINKER_H #define WASMTIME_COMPONENT_LINKER_H @@ -14,7 +16,10 @@ extern "C" { #endif +/// A type used to instantiate a #wasmtime_component_t. typedef struct wasmtime_component_linker_t wasmtime_component_linker_t; + +/// Structure representing an "instance" being defined within a linker. typedef struct wasmtime_component_linker_instance_t wasmtime_component_linker_instance_t; @@ -82,7 +87,8 @@ wasmtime_component_linker_delete(wasmtime_component_linker_t *linker); * \param linker_instance the linker instance from which the new one is created * \param name new instance name * \param name_len length of \p name in bytes - * \param linker_instance_out on success, the new #component_linker_instance_t + * \param linker_instance_out on success, the new + * #wasmtime_component_linker_instance_t * \return on success `NULL`, otherwise an error */ WASM_API_EXTERN wasmtime_error_t * @@ -108,6 +114,28 @@ WASM_API_EXTERN wasmtime_error_t *wasmtime_component_linker_instance_add_module( wasmtime_component_linker_instance_t *linker_instance, const char *name, size_t name_len, const wasmtime_module_t *module); +/// Type of the callback used in #wasmtime_component_linker_instance_add_func +typedef wasmtime_error_t *(*wasmtime_component_func_callback_t)( + void *, wasmtime_context_t *, const wasmtime_component_val_t *, size_t, + wasmtime_component_val_t *, size_t); + +/** + * \brief Define a function within this instance. + * + * \param linker_instance the instance to define the function in + * \param name the module name + * \param name_len length of \p name in bytes + * \param callback the 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( + wasmtime_component_linker_instance_t *linker_instance, const char *name, + size_t name_len, wasmtime_component_func_callback_t callback, void *data, + void (*finalizer)()); + /** * \brief Deletes a #wasmtime_component_linker_instance_t * diff --git a/crates/c-api/include/wasmtime/component/val.h b/crates/c-api/include/wasmtime/component/val.h new file mode 100644 index 000000000000..a1897a3afadd --- /dev/null +++ b/crates/c-api/include/wasmtime/component/val.h @@ -0,0 +1,157 @@ +/// \file wasmtime/component/val.h + +#ifndef WASMTIME_COMPONENT_VAL_H +#define WASMTIME_COMPONENT_VAL_H + +#include + +#ifdef WASMTIME_FEATURE_COMPONENT_MODEL + +#ifdef __cplusplus +extern "C" { +#endif + +/// \brief Discriminant used in #wasmtime_component_val_t::kind +typedef uint8_t wasmtime_component_valkind_t; + +/// \brief Value of #wasmtime_component_valkind_t meaning that +/// #wasmtime_component_val_t is a bool +#define WASMTIME_COMPONENT_BOOL 0 +/// \brief Value of #wasmtime_component_valkind_t meaning that +/// #wasmtime_component_val_t is a s8 +#define WASMTIME_COMPONENT_S8 1 +/// \brief Value of #wasmtime_component_valkind_t meaning that +/// #wasmtime_component_val_t is a u8 +#define WASMTIME_COMPONENT_U8 2 +/// \brief Value of #wasmtime_component_valkind_t meaning that +/// #wasmtime_component_val_t is a s16 +#define WASMTIME_COMPONENT_S16 3 +/// \brief Value of #wasmtime_component_valkind_t meaning that +/// #wasmtime_component_val_t is a u16 +#define WASMTIME_COMPONENT_U16 4 +/// \brief Value of #wasmtime_component_valkind_t meaning that +/// #wasmtime_component_val_t is a s32 +#define WASMTIME_COMPONENT_S32 5 +/// \brief Value of #wasmtime_component_valkind_t meaning that +/// #wasmtime_component_val_t is a u32 +#define WASMTIME_COMPONENT_U32 6 +/// \brief Value of #wasmtime_component_valkind_t meaning that +/// #wasmtime_component_val_t is a s64 +#define WASMTIME_COMPONENT_S64 7 +/// \brief Value of #wasmtime_component_valkind_t meaning that +/// #wasmtime_component_val_t is a u64 +#define WASMTIME_COMPONENT_U64 8 +/// \brief Value of #wasmtime_component_valkind_t meaning that +/// #wasmtime_component_val_t is a f32 +#define WASMTIME_COMPONENT_F32 9 +/// \brief Value of #wasmtime_component_valkind_t meaning that +/// #wasmtime_component_val_t is a f64 +#define WASMTIME_COMPONENT_F64 10 +/// \brief Value of #wasmtime_component_valkind_t meaning that +/// #wasmtime_component_val_t is a char +#define WASMTIME_COMPONENT_CHAR 11 +/// \brief Value of #wasmtime_component_valkind_t meaning that +/// #wasmtime_component_val_t is a string +#define WASMTIME_COMPONENT_STRING 12 +/// \brief Value of #wasmtime_component_valkind_t meaning that +/// #wasmtime_component_val_t is a list +#define WASMTIME_COMPONENT_LIST 13 +/// \brief Value of #wasmtime_component_valkind_t meaning that +/// #wasmtime_component_val_t is a record +#define WASMTIME_COMPONENT_RECORD 14 + +struct wasmtime_component_val; +struct wasmtime_component_valrecord_entry; + +#define DECLARE_VEC(name, type) \ + /** \brief A vec of a type */ \ + typedef struct name { \ + /** Length of the vec */ \ + size_t size; \ + /** Pointer to the elements */ \ + type *data; \ + } name##_t; \ + \ + /** \brief Create vec from \p ptr and \p size */ \ + WASM_API_EXTERN void name##_new(name##_t *out, size_t size, type *ptr); \ + /** \brief Create an empty vec */ \ + WASM_API_EXTERN void name##_new_empty(name##_t *out); \ + /** \brief Create a vec with length \p size */ \ + WASM_API_EXTERN void name##_new_uninit(name##_t *out, size_t size); \ + /** \brief Copy \p src to \p dst */ \ + WASM_API_EXTERN void name##_copy(name##_t *dst, const name##_t *src); \ + /** \brief Delete \p value */ \ + WASM_API_EXTERN void name##_delete(name##_t *value); + +DECLARE_VEC(wasmtime_component_vallist, struct wasmtime_component_val) +DECLARE_VEC(wasmtime_component_valrecord, + struct wasmtime_component_valrecord_entry) + +#undef DECLARE_VEC + +/// \brief Represents possible runtime values which a component function can +/// either consume or produce +typedef union { + /// Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_BOOL + bool boolean; + /// Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_S8 + int8_t s8; + /// Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_U8 + uint8_t u8; + /// Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_S16 + int16_t s16; + /// Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_U16 + uint16_t u16; + /// Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_S32 + int32_t s32; + /// Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_U32 + uint32_t u32; + /// Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_S64 + int64_t s64; + /// Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_U64 + uint64_t u64; + /// Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_F32 + float32_t f32; + /// Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_F64 + float64_t f64; + /// Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_CHAR + uint32_t character; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_STRING + wasm_name_t string; + /// Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_LIST + wasmtime_component_vallist_t list; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_RECORD + wasmtime_component_valrecord_t record; +} wasmtime_component_valunion_t; + +/// \brief Represents possible runtime values which a component function can +/// either consume or produce +typedef struct wasmtime_component_val { + /// The type discriminant + wasmtime_component_valkind_t kind; + /// Value of type \ref kind + wasmtime_component_valunion_t of; +} wasmtime_component_val_t; + +/// \brief A pair of a name and a value that represents one entry in a value +/// with kind #WASMTIME_COMPONENT_RECORD +typedef struct wasmtime_component_valrecord_entry { + /// The name of this entry + wasm_name_t name; + /// The value of this entry + wasmtime_component_val_t val; +} wasmtime_component_valrecord_entry_t; + +/// \brief Calls the destructor on \p value deallocating any owned memory +WASM_API_EXTERN void +wasmtime_component_val_delete(wasmtime_component_val_t *value); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WASMTIME_FEATURE_COMPONENT_MODEL + +#endif // WASMTIME_COMPONENT_VAL_H diff --git a/crates/c-api/src/component/func.rs b/crates/c-api/src/component/func.rs new file mode 100644 index 000000000000..102d841e1c23 --- /dev/null +++ b/crates/c-api/src/component/func.rs @@ -0,0 +1,31 @@ +use wasmtime::component::{Func, Val}; + +use crate::{WasmtimeStoreContextMut, wasmtime_error_t}; + +use super::wasmtime_component_val_t; + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wasmtime_component_func_call( + func: &Func, + mut context: WasmtimeStoreContextMut<'_>, + args: *const wasmtime_component_val_t, + args_len: usize, + results: *mut wasmtime_component_val_t, + results_len: usize, +) -> Option> { + let c_args = unsafe { std::slice::from_raw_parts(args, args_len) }; + let c_results = unsafe { std::slice::from_raw_parts_mut(results, results_len) }; + + let args = c_args.iter().map(Val::from).collect::>(); + let mut results = vec![Val::Bool(false); results_len]; + + let result = func + .call(&mut context, &args, &mut results) + .and_then(|_| func.post_return(&mut context)); + + crate::handle_result(result, |_| { + for (c_val, rust_val) in std::iter::zip(c_results, results) { + *c_val = wasmtime_component_val_t::from(&rust_val); + } + }) +} diff --git a/crates/c-api/src/component/linker.rs b/crates/c-api/src/component/linker.rs index 5f559ce9d277..b60c2a43297f 100644 --- a/crates/c-api/src/component/linker.rs +++ b/crates/c-api/src/component/linker.rs @@ -1,10 +1,12 @@ -use wasmtime::component::{Instance, Linker, LinkerInstance}; +use std::ffi::c_void; + +use wasmtime::component::{Instance, Linker, LinkerInstance, Val}; use crate::{ WasmtimeStoreContextMut, WasmtimeStoreData, wasm_engine_t, wasmtime_error_t, wasmtime_module_t, }; -use super::wasmtime_component_t; +use super::{wasmtime_component_t, wasmtime_component_val_t}; #[repr(transparent)] pub struct wasmtime_component_linker_t { @@ -90,6 +92,66 @@ pub unsafe extern "C" fn wasmtime_component_linker_instance_add_module( crate::handle_result(result, |_| ()) } +pub type wasmtime_component_func_callback_t = extern "C" fn( + *mut c_void, + WasmtimeStoreContextMut<'_>, + *const wasmtime_component_val_t, + usize, + *mut wasmtime_component_val_t, + usize, +) -> Option>; + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wasmtime_component_linker_instance_add_func( + linker_instance: &mut wasmtime_component_linker_instance_t, + name: *const u8, + name_len: usize, + callback: wasmtime_component_func_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(&name, move |ctx, args, rets| { + let _ = &foreign; + + let 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 res = callback( + foreign.data, + ctx, + args.as_ptr(), + args.len(), + c_rets.as_mut_ptr(), + c_rets.len(), + ); + + if let Some(res) = res { + return Err((*res).into()); + } + + 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)] pub unsafe extern "C" fn wasmtime_component_linker_instance_delete( _linker_instance: Box, diff --git a/crates/c-api/src/component/mod.rs b/crates/c-api/src/component/mod.rs index 45413a5e9f35..dd86c9dba2a4 100644 --- a/crates/c-api/src/component/mod.rs +++ b/crates/c-api/src/component/mod.rs @@ -1,7 +1,11 @@ mod component; +mod func; mod instance; mod linker; +mod val; pub use component::*; +pub use func::*; pub use instance::*; pub use linker::*; +pub use val::*; diff --git a/crates/c-api/src/component/val.rs b/crates/c-api/src/component/val.rs new file mode 100644 index 000000000000..e2c4beae3e7c --- /dev/null +++ b/crates/c-api/src/component/val.rs @@ -0,0 +1,181 @@ +use wasmtime::component::Val; + +use crate::wasm_name_t; + +use std::mem; +use std::mem::MaybeUninit; +use std::ptr; +use std::slice; + +crate::declare_vecs! { + ( + name: wasmtime_component_vallist_t, + ty: wasmtime_component_val_t, + new: wasmtime_component_vallist_new, + empty: wasmtime_component_vallist_new_empty, + uninit: wasmtime_component_vallist_new_uninit, + copy: wasmtime_component_vallist_copy, + delete: wasmtime_component_vallist_delete, + ) + ( + name: wasmtime_component_valrecord_t, + ty: wasmtime_component_valrecord_entry_t, + new: wasmtime_component_valrecord_new, + empty: wasmtime_component_valrecord_new_empty, + uninit: wasmtime_component_valrecord_new_uninit, + copy: wasmtime_component_valrecord_copy, + delete: wasmtime_component_valrecord_delete, + ) +} + +impl From<&wasmtime_component_vallist_t> for Vec { + fn from(value: &wasmtime_component_vallist_t) -> Self { + value.as_slice().iter().map(Val::from).collect() + } +} + +impl From<&[Val]> for wasmtime_component_vallist_t { + fn from(value: &[Val]) -> Self { + value + .iter() + .map(wasmtime_component_val_t::from) + .collect::>() + .into() + } +} + +#[derive(Clone)] +#[repr(C)] +pub struct wasmtime_component_valrecord_entry_t { + name: wasm_name_t, + val: wasmtime_component_val_t, +} + +impl Default for wasmtime_component_valrecord_entry_t { + fn default() -> Self { + Self { + name: wasm_name_t::from_name(String::new()), + val: Default::default(), + } + } +} + +impl From<&wasmtime_component_valrecord_entry_t> for (String, Val) { + fn from(value: &wasmtime_component_valrecord_entry_t) -> Self { + ( + String::from_utf8(value.name.clone().take()).unwrap(), + Val::from(&value.val), + ) + } +} + +impl From<&(String, Val)> for wasmtime_component_valrecord_entry_t { + fn from((name, val): &(String, Val)) -> Self { + Self { + name: wasm_name_t::from_name(name.clone()), + val: wasmtime_component_val_t::from(val), + } + } +} + +impl From<&wasmtime_component_valrecord_t> for Vec<(String, Val)> { + fn from(value: &wasmtime_component_valrecord_t) -> Self { + value.as_slice().iter().map(Into::into).collect() + } +} + +impl From<&[(String, Val)]> for wasmtime_component_valrecord_t { + fn from(value: &[(String, Val)]) -> Self { + value + .iter() + .map(wasmtime_component_valrecord_entry_t::from) + .collect::>() + .into() + } +} + +#[repr(C, u8)] +#[derive(Clone)] +pub enum wasmtime_component_val_t { + Bool(bool), + S8(i8), + U8(u8), + S16(i16), + U16(u16), + S32(i32), + U32(u32), + S64(i64), + U64(u64), + F32(f32), + F64(f64), + Char(u32), + String(wasm_name_t), + List(wasmtime_component_vallist_t), + Record(wasmtime_component_valrecord_t), +} + +impl Default for wasmtime_component_val_t { + fn default() -> Self { + Self::Bool(false) + } +} + +impl From<&wasmtime_component_val_t> for Val { + fn from(value: &wasmtime_component_val_t) -> Self { + match value { + wasmtime_component_val_t::Bool(x) => Val::Bool(*x), + wasmtime_component_val_t::S8(x) => Val::S8(*x), + wasmtime_component_val_t::U8(x) => Val::U8(*x), + wasmtime_component_val_t::S16(x) => Val::S16(*x), + wasmtime_component_val_t::U16(x) => Val::U16(*x), + wasmtime_component_val_t::S32(x) => Val::S32(*x), + wasmtime_component_val_t::U32(x) => Val::U32(*x), + wasmtime_component_val_t::S64(x) => Val::S64(*x), + wasmtime_component_val_t::U64(x) => Val::U64(*x), + wasmtime_component_val_t::F32(x) => Val::Float32(*x), + wasmtime_component_val_t::F64(x) => Val::Float64(*x), + wasmtime_component_val_t::Char(x) => Val::Char(char::from_u32(*x).unwrap()), + wasmtime_component_val_t::String(x) => { + Val::String(String::from_utf8(x.clone().take()).unwrap()) + } + wasmtime_component_val_t::List(x) => Val::List(x.into()), + wasmtime_component_val_t::Record(x) => Val::Record(x.into()), + } + } +} + +impl From<&Val> for wasmtime_component_val_t { + fn from(value: &Val) -> Self { + match value { + Val::Bool(x) => wasmtime_component_val_t::Bool(*x), + Val::S8(x) => wasmtime_component_val_t::S8(*x), + Val::U8(x) => wasmtime_component_val_t::U8(*x), + Val::S16(x) => wasmtime_component_val_t::S16(*x), + Val::U16(x) => wasmtime_component_val_t::U16(*x), + Val::S32(x) => wasmtime_component_val_t::S32(*x), + Val::U32(x) => wasmtime_component_val_t::U32(*x), + Val::S64(x) => wasmtime_component_val_t::S64(*x), + Val::U64(x) => wasmtime_component_val_t::U64(*x), + Val::Float32(x) => wasmtime_component_val_t::F32(*x), + Val::Float64(x) => wasmtime_component_val_t::F64(*x), + Val::Char(x) => wasmtime_component_val_t::Char(*x as _), + Val::String(x) => wasmtime_component_val_t::String(wasm_name_t::from_name(x.clone())), + Val::List(x) => wasmtime_component_val_t::List(x.as_slice().into()), + Val::Record(x) => wasmtime_component_val_t::Record(x.as_slice().into()), + Val::Tuple(_vals) => todo!(), + Val::Variant(_, _val) => todo!(), + Val::Enum(_) => todo!(), + Val::Option(_val) => todo!(), + Val::Result(_val) => todo!(), + Val::Flags(_items) => todo!(), + Val::Resource(_resource_any) => todo!(), + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wasmtime_component_val_delete(value: *mut wasmtime_component_val_t) { + unsafe { + std::ptr::drop_in_place(value); + } +} diff --git a/crates/c-api/src/vec.rs b/crates/c-api/src/vec.rs index 0477a529f777..4037794627dc 100644 --- a/crates/c-api/src/vec.rs +++ b/crates/c-api/src/vec.rs @@ -139,6 +139,8 @@ macro_rules! declare_vecs { )*}; } +pub(crate) use declare_vecs; + declare_vecs! { ( name: wasm_byte_vec_t, diff --git a/crates/c-api/tests/CMakeLists.txt b/crates/c-api/tests/CMakeLists.txt index 24d5d8c7e2a0..fdcf319989a9 100644 --- a/crates/c-api/tests/CMakeLists.txt +++ b/crates/c-api/tests/CMakeLists.txt @@ -34,6 +34,8 @@ add_capi_test(tests FILES component/instantiate.cc component/define_module.cc component/lookup_func.cc + component/call_func.cc + component/values.cc error.cc config.cc wat.cc diff --git a/crates/c-api/tests/component/call_func.cc b/crates/c-api/tests/component/call_func.cc new file mode 100644 index 000000000000..384b11326607 --- /dev/null +++ b/crates/c-api/tests/component/call_func.cc @@ -0,0 +1,81 @@ +#include "utils.h" + +#include +#include +#include + +TEST(component, call_func) { + 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", + }; + const auto engine = wasm_engine_new(); + EXPECT_NE(engine, nullptr); + + const auto store = wasmtime_store_new(engine, nullptr, nullptr); + const auto context = wasmtime_store_context(store); + + wasmtime_component_t *component = nullptr; + + auto err = wasmtime_component_new( + engine, reinterpret_cast(component_text.data()), + component_text.size(), &component); + + CHECK_ERR(err); + + const auto f = + wasmtime_component_get_export_index(component, nullptr, "f", 1); + + EXPECT_NE(f, nullptr); + + const auto linker = wasmtime_component_linker_new(engine); + + wasmtime_component_instance_t instance = {}; + err = wasmtime_component_linker_instantiate(linker, context, component, + &instance); + CHECK_ERR(err); + + wasmtime_component_func_t func = {}; + const auto found = + wasmtime_component_instance_get_func(&instance, context, f, &func); + EXPECT_TRUE(found); + + auto params = std::array{ + wasmtime_component_val_t{ + .kind = WASMTIME_COMPONENT_U32, + .of = {.u32 = 34}, + }, + wasmtime_component_val_t{ + .kind = WASMTIME_COMPONENT_U32, + .of = {.u32 = 35}, + }, + }; + + auto results = std::array{}; + + err = + wasmtime_component_func_call(&func, context, params.data(), params.size(), + results.data(), results.size()); + CHECK_ERR(err); + + EXPECT_EQ(results[0].kind, WASMTIME_COMPONENT_U32); + EXPECT_EQ(results[0].of.u32, 69); + + wasmtime_component_export_index_delete(f); + wasmtime_component_linker_delete(linker); + + wasmtime_store_delete(store); + wasm_engine_delete(engine); +} diff --git a/crates/c-api/tests/component/utils.h b/crates/c-api/tests/component/utils.h index d4ac206534ae..5405e69be255 100644 --- a/crates/c-api/tests/component/utils.h +++ b/crates/c-api/tests/component/utils.h @@ -1,4 +1,5 @@ #pragma once +#include #define CHECK_ERR(err) \ do { \ @@ -8,3 +9,96 @@ EXPECT_EQ(err, nullptr) << std::string_view{msg.data, msg.size}; \ } \ } while (false) + +// From crates/component-util/src/lib.rs +inline constexpr std::string_view REALLOC_AND_FREE = + R"END( +(global $last (mut i32) (i32.const 8)) +(func $realloc (export "realloc") + (param $old_ptr i32) + (param $old_size i32) + (param $align i32) + (param $new_size i32) + (result i32) + + (local $ret i32) + + ;; Test if the old pointer is non-null + local.get $old_ptr + if + ;; If the old size is bigger than the new size then + ;; this is a shrink and transparently allow it + local.get $old_size + local.get $new_size + i32.gt_u + if + local.get $old_ptr + return + end + + ;; otherwise fall through to allocate a new chunk which will later + ;; copy data over + end + + ;; align up `$last` + (global.set $last + (i32.and + (i32.add + (global.get $last) + (i32.add + (local.get $align) + (i32.const -1))) + (i32.xor + (i32.add + (local.get $align) + (i32.const -1)) + (i32.const -1)))) + + ;; save the current value of `$last` as the return value + global.get $last + local.set $ret + + ;; bump our pointer + (global.set $last + (i32.add + (global.get $last) + (local.get $new_size))) + + ;; while `memory.size` is less than `$last`, grow memory + ;; by one page + (loop $loop + (if + (i32.lt_u + (i32.mul (memory.size) (i32.const 65536)) + (global.get $last)) + (then + i32.const 1 + memory.grow + ;; test to make sure growth succeeded + i32.const -1 + i32.eq + if unreachable end + + br $loop))) + + + ;; ensure anything necessary is set to valid data by spraying a bit + ;; pattern that is invalid + local.get $ret + i32.const 0xde + local.get $new_size + memory.fill + + ;; If the old pointer is present then that means this was a reallocation + ;; of an existing chunk which means the existing data must be copied. + local.get $old_ptr + if + local.get $ret ;; destination + local.get $old_ptr ;; source + local.get $old_size ;; size + memory.copy + end + + local.get $ret +) +)END"; diff --git a/crates/c-api/tests/component/values.cc b/crates/c-api/tests/component/values.cc new file mode 100644 index 000000000000..b1397e5c0829 --- /dev/null +++ b/crates/c-api/tests/component/values.cc @@ -0,0 +1,389 @@ +#include "utils.h" + +#include +#include + +#include +#include +#include + +static std::string echo_component(std::string_view type, std::string_view func, + std::string_view host_params) { + return std::format( + R"END( +(component + (type $Foo' {}) + (import "foo" (type $Foo (eq $Foo'))) + (import "do" (func $do (param "a" $Foo) (result $Foo))) + (core module $libc + (memory (export "memory") 1) + {} + ) + (core instance $libc (instantiate $libc)) + (core func $do_lower (canon lower (func $do) (memory $libc "memory") (realloc (func $libc "realloc")))) + + (core module $doer + (import "host" "do" (func $do (param {}))) + (import "libc" "memory" (memory 1)) + (import "libc" "realloc" (func $realloc (param i32 i32 i32 i32) (result i32))) + + (func (export "call") + {}) + ) + (core instance $doer (instantiate $doer + (with "host" (instance (export "do" (func $do_lower)))) + (with "libc" (instance $libc)) + )) + + (func $call + (param "a" $Foo) + (result $Foo) + (canon lift + (core func $doer "call") + (memory $libc "memory") + (realloc (func $libc "realloc"))) + ) + + (export "call" (func $call)) +) + )END", + type, REALLOC_AND_FREE, host_params, func); +} + +struct Context { + wasm_engine_t *engine; + wasmtime_store_t *store; + wasmtime_context_t *context; + wasmtime_component_t *component; + wasmtime_component_instance_t instance; + wasmtime_component_func_t func; +}; + +static Context create(std::string_view type, std::string_view body, + std::string_view host_params, + wasmtime_component_func_callback_t callback) { + auto component_text = echo_component(type, body, host_params); + const auto engine = wasm_engine_new(); + EXPECT_NE(engine, nullptr); + + const auto store = wasmtime_store_new(engine, nullptr, nullptr); + const auto context = wasmtime_store_context(store); + + wasmtime_component_t *component = nullptr; + + auto err = wasmtime_component_new( + engine, reinterpret_cast(component_text.data()), + component_text.size(), &component); + + CHECK_ERR(err); + + auto f = wasmtime_component_get_export_index(component, nullptr, "call", + strlen("call")); + + EXPECT_NE(f, nullptr); + + const auto linker = wasmtime_component_linker_new(engine); + const auto root = wasmtime_component_linker_root(linker); + + wasmtime_component_linker_instance_add_func(root, "do", strlen("do"), + callback, nullptr, nullptr); + + wasmtime_component_linker_instance_delete(root); + + wasmtime_component_instance_t instance = {}; + err = wasmtime_component_linker_instantiate(linker, context, component, + &instance); + CHECK_ERR(err); + + wasmtime_component_linker_delete(linker); + + wasmtime_component_func_t func = {}; + const auto found = + wasmtime_component_instance_get_func(&instance, context, f, &func); + EXPECT_TRUE(found); + EXPECT_NE(func.store_id, 0); + + wasmtime_component_export_index_delete(f); + + return Context{ + .engine = engine, + .store = store, + .context = context, + .component = component, + .instance = instance, + .func = func, + }; +} + +static void destroy(Context &ctx) { + wasmtime_component_delete(ctx.component); + wasmtime_store_delete(ctx.store); + wasm_engine_delete(ctx.engine); +} + +TEST(component, value_record) { + static const auto check = [](const wasmtime_component_val_t &val, uint64_t x, + uint64_t y) { + EXPECT_EQ(val.kind, WASMTIME_COMPONENT_RECORD); + + EXPECT_EQ(val.of.record.size, 2); + const auto entries = val.of.record.data; + + EXPECT_EQ((std::string_view{entries[0].name.data, entries[0].name.size}), + "x"); + EXPECT_EQ(entries[0].val.kind, WASMTIME_COMPONENT_U64); + EXPECT_EQ(entries[0].val.of.u64, x); + + EXPECT_EQ((std::string_view{entries[1].name.data, entries[1].name.size}), + "y"); + EXPECT_EQ(entries[1].val.kind, WASMTIME_COMPONENT_U64); + EXPECT_EQ(entries[1].val.of.u64, y); + }; + + static const auto make = [](uint64_t x, + uint64_t y) -> wasmtime_component_val_t { + auto ret = wasmtime_component_val_t{ + .kind = WASMTIME_COMPONENT_RECORD, + }; + + wasmtime_component_valrecord_new_uninit(&ret.of.record, 2); + + const auto entries = ret.of.record.data; + wasm_name_new_from_string(&entries[0].name, "x"); + entries[0].val.kind = WASMTIME_COMPONENT_U64; + entries[0].val.of.u64 = x; + wasm_name_new_from_string(&entries[1].name, "y"); + entries[1].val.kind = WASMTIME_COMPONENT_U64; + entries[1].val.of.u64 = y; + + return ret; + }; + + auto ctx = create( + R"((record (field "x" u64) (field "y" u64)))", R"( +(param $x i64) +(param $y i64) +(result i32) +(local $res i32) +local.get $x +local.get $y +(call $realloc + (i32.const 0) + (i32.const 0) + (i32.const 4) + (i32.const 16)) +local.tee $res +call $do +local.get $res + )", + "i64 i64 i32", + +[](void *, wasmtime_context_t *, const wasmtime_component_val_t *args, + size_t args_len, wasmtime_component_val_t *rets, + size_t rets_len) -> wasmtime_error_t * { + EXPECT_EQ(args_len, 1); + check(args[0], 1, 2); + + EXPECT_EQ(rets_len, 1); + rets[0] = make(3, 4); + + return nullptr; + }); + + auto arg = make(1, 2); + auto res = wasmtime_component_val_t{}; + + auto err = + wasmtime_component_func_call(&ctx.func, ctx.context, &arg, 1, &res, 1); + CHECK_ERR(err); + + check(res, 3, 4); + + wasmtime_component_val_delete(&arg); + wasmtime_component_val_delete(&res); + + destroy(ctx); +} + +TEST(component, value_string) { + static const auto check = [](const wasmtime_component_val_t &val, + std::string_view text) { + EXPECT_EQ(val.kind, WASMTIME_COMPONENT_STRING); + EXPECT_EQ((std::string_view{val.of.string.data, val.of.string.size}), text); + }; + + static const auto make = + [](std::string_view text) -> wasmtime_component_val_t { + auto str = wasm_name_t{}; + wasm_name_new_from_string(&str, text.data()); + + return wasmtime_component_val_t{ + .kind = WASMTIME_COMPONENT_STRING, + .of = {.string = str}, + }; + }; + + auto ctx = create( + R"(string)", R"( +(param $x i32) +(param $y i32) +(result i32) +(local $res i32) +local.get $x +local.get $y +(call $realloc + (i32.const 0) + (i32.const 0) + (i32.const 4) + (i32.const 8)) +local.tee $res +call $do +local.get $res + )", + "i32 i32 i32", + +[](void *, wasmtime_context_t *, const wasmtime_component_val_t *args, + size_t args_len, wasmtime_component_val_t *rets, + size_t rets_len) -> wasmtime_error_t * { + EXPECT_EQ(args_len, 1); + check(args[0], "hello from A!"); + + EXPECT_EQ(rets_len, 1); + rets[0] = make("hello from B!"); + + return nullptr; + }); + + auto arg = make("hello from A!"); + auto res = wasmtime_component_val_t{}; + + auto err = + wasmtime_component_func_call(&ctx.func, ctx.context, &arg, 1, &res, 1); + CHECK_ERR(err); + + check(res, "hello from B!"); + + wasmtime_component_val_delete(&arg); + wasmtime_component_val_delete(&res); + + destroy(ctx); +} + +TEST(component, value_list) { + static const auto check = [](const wasmtime_component_val_t &val, + std::vector data) { + EXPECT_EQ(val.kind, WASMTIME_COMPONENT_LIST); + auto vals = std::span{val.of.list.data, val.of.list.size}; + EXPECT_EQ(vals.size(), data.size()); + for (auto i = 0; i < data.size(); i++) { + EXPECT_EQ(vals[i].kind, WASMTIME_COMPONENT_U32); + EXPECT_EQ(vals[i].of.u32, data[i]); + } + }; + + static const auto make = + [](std::vector data) -> wasmtime_component_val_t { + auto ret = wasmtime_component_val_t{ + .kind = WASMTIME_COMPONENT_LIST, + }; + + wasmtime_component_vallist_new_uninit(&ret.of.list, data.size()); + + for (auto i = 0; i < data.size(); i++) { + ret.of.list.data[i] = wasmtime_component_val_t{ + .kind = WASMTIME_COMPONENT_U32, + .of = {.u32 = data[i]}, + }; + } + + return ret; + }; + + auto ctx = create( + R"((list u32))", R"( +(param $x i32) +(param $y i32) +(result i32) +(local $res i32) +local.get $x +local.get $y +(call $realloc + (i32.const 0) + (i32.const 0) + (i32.const 4) + (i32.const 8)) +local.tee $res +call $do +local.get $res + )", + "i32 i32 i32", + +[](void *, wasmtime_context_t *, const wasmtime_component_val_t *args, + size_t args_len, wasmtime_component_val_t *rets, + size_t rets_len) -> wasmtime_error_t * { + EXPECT_EQ(args_len, 1); + check(args[0], {1, 2, 3}); + + EXPECT_EQ(rets_len, 1); + rets[0] = make({4, 5, 6, 7}); + + return nullptr; + }); + + auto arg = make({1, 2, 3}); + auto res = wasmtime_component_val_t{}; + + auto err = + wasmtime_component_func_call(&ctx.func, ctx.context, &arg, 1, &res, 1); + CHECK_ERR(err); + + check(res, {4, 5, 6, 7}); + + wasmtime_component_val_delete(&arg); + wasmtime_component_val_delete(&res); + + destroy(ctx); +} + +TEST(component, value_list_inner) { + { + auto x = wasmtime_component_val_t{ + .kind = WASMTIME_COMPONENT_LIST, + }; + wasmtime_component_vallist_new_empty(&x.of.list); + EXPECT_EQ(x.of.list.data, nullptr); + EXPECT_EQ(x.of.list.size, 0); + + wasmtime_component_vallist_new_uninit(&x.of.list, 1); + EXPECT_NE(x.of.list.data, nullptr); + EXPECT_EQ(x.of.list.size, 1); + + wasmtime_component_vallist_delete(&x.of.list); + + auto items = std::array{ + wasmtime_component_val_t{ + .kind = WASMTIME_COMPONENT_U32, + .of = {.u32 = 123}, + }, + }; + + wasmtime_component_vallist_new(&x.of.list, items.size(), items.data()); + EXPECT_NE(x.of.list.data, nullptr); + EXPECT_EQ(x.of.list.size, 1); + + EXPECT_EQ(x.of.list.data[0].kind, WASMTIME_COMPONENT_U32); + EXPECT_EQ(x.of.list.data[0].of.u32, 123); + + auto clone = wasmtime_component_val_t{ + .kind = WASMTIME_COMPONENT_LIST, + }; + + wasmtime_component_vallist_copy(&clone.of.list, &x.of.list); + wasmtime_component_vallist_delete(&x.of.list); + + EXPECT_NE(clone.of.list.data, nullptr); + EXPECT_EQ(clone.of.list.size, 1); + + EXPECT_EQ(clone.of.list.data[0].kind, WASMTIME_COMPONENT_U32); + EXPECT_EQ(clone.of.list.data[0].of.u32, 123); + + wasmtime_component_vallist_delete(&clone.of.list); + } +}