diff --git a/crates/c-api/include/wasmtime/component.h b/crates/c-api/include/wasmtime/component.h index c074da38a3e9..4daf02a3b77f 100644 --- a/crates/c-api/include/wasmtime/component.h +++ b/crates/c-api/include/wasmtime/component.h @@ -1,7 +1,7 @@ /** * The component model * - * TODO: Write some more documentation here like in the Rust API. + * Wasmtime APIs for interacting with WebAssembly Component Model. * */ @@ -25,32 +25,59 @@ extern "C" { */ WASMTIME_CONFIG_PROP(void, component_model, bool) -// The tag part of wasmtime_component_val_t that specifies what variant is -// populated in wasmtime_component_val_payload_t. +/** + * \brief The tag part of #wasmtime_component_val_t or #wasmtime_component_type_t + * + * Specifies what variant is populated in #wasmtime_component_val_payload_t + * or #wasmtime_component_type_payload_t. + */ typedef uint8_t wasmtime_component_kind_t; +/// \brief Value of #wasmtime_component_kind_t indicating a boolean #define WASMTIME_COMPONENT_KIND_BOOL 0 +/// \brief Value of #wasmtime_component_kind_t indicating a signed 8-bit integer #define WASMTIME_COMPONENT_KIND_S8 1 +/// \brief Value of #wasmtime_component_kind_t indicating an unsigned 8-bit integer #define WASMTIME_COMPONENT_KIND_U8 2 +/// \brief Value of #wasmtime_component_kind_t indicating a signed 16-bit integer #define WASMTIME_COMPONENT_KIND_S16 3 +/// \brief Value of #wasmtime_component_kind_t indicating an unsigned 16-bit integer #define WASMTIME_COMPONENT_KIND_U16 4 +/// \brief Value of #wasmtime_component_kind_t indicating a signed 32-bit integer #define WASMTIME_COMPONENT_KIND_S32 5 +/// \brief Value of #wasmtime_component_kind_t indicating an unsigned 32-bit integer #define WASMTIME_COMPONENT_KIND_U32 6 +/// \brief Value of #wasmtime_component_kind_t indicating a signed 64-bit integer #define WASMTIME_COMPONENT_KIND_S64 7 +/// \brief Value of #wasmtime_component_kind_t indicating an unsigned 64-bit integer #define WASMTIME_COMPONENT_KIND_U64 8 +/// \brief Value of #wasmtime_component_kind_t indicating an 32-bit floating point number #define WASMTIME_COMPONENT_KIND_F32 9 +/// \brief Value of #wasmtime_component_kind_t indicating an 64-bit floating point number #define WASMTIME_COMPONENT_KIND_F64 10 +/// \brief Value of #wasmtime_component_kind_t indicating a unicode character #define WASMTIME_COMPONENT_KIND_CHAR 11 +/// \brief Value of #wasmtime_component_kind_t indicating a unicode string #define WASMTIME_COMPONENT_KIND_STRING 12 +/// \brief Value of #wasmtime_component_kind_t indicating a list #define WASMTIME_COMPONENT_KIND_LIST 13 +/// \brief Value of #wasmtime_component_kind_t indicating a record #define WASMTIME_COMPONENT_KIND_RECORD 14 +/// \brief Value of #wasmtime_component_kind_t indicating a tuple #define WASMTIME_COMPONENT_KIND_TUPLE 15 +/// \brief Value of #wasmtime_component_kind_t indicating a variant #define WASMTIME_COMPONENT_KIND_VARIANT 16 +/// \brief Value of #wasmtime_component_kind_t indicating an enum #define WASMTIME_COMPONENT_KIND_ENUM 17 +/// \brief Value of #wasmtime_component_kind_t indicating an option #define WASMTIME_COMPONENT_KIND_OPTION 18 +/// \brief Value of #wasmtime_component_kind_t indicating a result #define WASMTIME_COMPONENT_KIND_RESULT 19 +/// \brief Value of #wasmtime_component_kind_t indicating a set of flags #define WASMTIME_COMPONENT_KIND_FLAGS 20 + +// forward declarations typedef struct wasmtime_component_val_t wasmtime_component_val_t; typedef struct wasmtime_component_val_record_field_t wasmtime_component_val_record_field_t; typedef struct wasmtime_component_type_t wasmtime_component_type_t; @@ -99,102 +126,206 @@ WASMTIME_COMPONENT_DECLARE_VEC_NEW(string_vec, wasm_name_t); #undef WASMTIME_COMPONENT_DECLARE_VEC -// A variant contains the discriminant index and an optional value that is held. +/// \brief Representation of a variant value typedef struct wasmtime_component_val_variant_t { + /// \brief Discriminant indicating the index of the variant case of this value uint32_t discriminant; + /// \brief #wasmtime_component_val_t value of the variant case of this value if it has one wasmtime_component_val_t *val; } wasmtime_component_val_variant_t; -// A result is an either type holding a value and a bit if is it an ok or error -// variant. +/** + * \brief Representation of a result value + * + * A result is an either type holding an ok value or an error + */ typedef struct wasmtime_component_val_result_t { + /// \brief Value of the result, either of the ok type or of the error type, depending on #error wasmtime_component_val_t *val; + /// \brief Discriminant indicating if this result is an ok value or an error bool error; } wasmtime_component_val_result_t; -// Which value within an enumeration is selected. +/// \brief Representation of an enum value typedef struct wasmtime_component_val_enum_t { + /// \brief Discriminant indicating the index of this value in the enum uint32_t discriminant; } wasmtime_component_val_enum_t; +/** + * \brief Container for different kind of component model value data + * + * Setting any dynamic data to one of this field means that the payload now owns it + */ typedef union wasmtime_component_val_payload_t { + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_BOOLEAN bool boolean; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_S8 int8_t s8; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_U8 uint8_t u8; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_S16 int16_t s16; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_U16 uint16_t u16; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_S32 int32_t s32; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_U32 uint32_t u32; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_S64 int64_t s64; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_U64 uint64_t u64; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_F32 float f32; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_F64 double f64; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_CHARACTER uint8_t character; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_STRING wasm_name_t string; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_LIST wasmtime_component_val_vec_t list; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_RECORD wasmtime_component_val_record_t record; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_TUPLE wasmtime_component_val_vec_t tuple; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_VARIANT wasmtime_component_val_variant_t variant; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_ENUMERATION wasmtime_component_val_enum_t enumeration; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_OPTION wasmtime_component_val_t *option; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_RESULT wasmtime_component_val_result_t result; + /// \brief Field used if #wasmtime_component_val_t::kind is #WASMTIME_COMPONENT_KIND_FLAGS wasmtime_component_val_flags_t flags; } wasmtime_component_val_payload_t; -// The tagged union for a value within the component model. +/** + * \brief Representation of a component model value + * + * Many kind of values own data, so those need to be properly dropped in wasmtime, + * and therefore should not live on the stack. + */ typedef struct wasmtime_component_val_t { + /// \brief Discriminant indicating which field of #payload is valid wasmtime_component_kind_t kind; + /// \brief Container for the value data wasmtime_component_val_payload_t payload; } wasmtime_component_val_t; WASMTIME_COMPONENT_DECLARE_VEC_NEW(val_vec, wasmtime_component_val_t); -// A record is a series of named fields, which are values with a string name. +/// \brief Representation of record field. typedef struct wasmtime_component_val_record_field_t { + /// \brief Name of the field wasm_name_t name; + /// \brief Value of the field wasmtime_component_val_t val; } wasmtime_component_val_record_field_t; WASMTIME_COMPONENT_DECLARE_VEC_NEW(val_record, wasmtime_component_val_record_field_t); -// Set a value within this bitset. -// -// If this bit set is too small to hold a value at `index` it will be resized. +/** + * \brief Sets the value of a flag within within a #wasmtime_component_val_flags_t. + * + * If this bit set is too small to hold a value at `index` it will be resized. + + * \param flags the #wasmtime_component_val_flags_t to modify + * \param index the index of the flag to modify + * \param enabled the value to set the flag to + */ void wasmtime_component_val_flags_set(wasmtime_component_val_flags_t *flags, uint32_t index, bool enabled); -// Test if this bitset holds a value at `index`. +/** + * \brief Tests the value of a flag within within a #wasmtime_component_val_flags_t. + * + * If this bit set is too small to hold a value at `index` it will be resized. + + * \param flags the #wasmtime_component_val_flags_t to test + * \param index the index of the flag to test + * \return true if the flag is set, else false + */ bool wasmtime_component_val_flags_test(const wasmtime_component_val_flags_t* flags, uint32_t index); +/** + * \brief Creates a new #wasmtime_component_val_t + * + * This is usually used to create inner values, typically as part of an option or result. + * In this case, ownership is given out to the outer value. + * + * In case where a top level #wasmtime_component_val_t is created (for example to be passed directly + * to #wasmtime_component_func_call), then it should be deleted with #wasmtime_component_val_delete + * + * \return a pointer to the newly created #wasmtime_component_val_t + */ wasmtime_component_val_t* wasmtime_component_val_new(); +/** + * \brief Deletes a #wasmtime_component_val_t previously created by #wasmtime_component_val_new + * + * This should not be called if the value has been given out as part of an outer #wasmtime_component_val_t + * + * \param val the #wasmtime_component_val_t to delete + */ void wasmtime_component_val_delete(wasmtime_component_val_t* val); +/** + * \brief Representation of a field in a record type or a case in a variant type + */ typedef struct wasmtime_component_type_field_t { + /// \brief Name of the record field or variant case wasm_name_t name; + /// \brief Type of the record field or variant case (may be null for variant case) wasmtime_component_type_t* ty; } wasmtime_component_type_field_t; WASMTIME_COMPONENT_DECLARE_VEC_NEW(type_field_vec, wasmtime_component_type_field_t); +/** + * \brief Representation of a result type + */ typedef struct wasmtime_component_type_result_t { + /// \brief Type of the ok value (if there is one) wasmtime_component_type_t* ok_ty; + /// \brief Type of the error value (if there is one) wasmtime_component_type_t* err_ty; } wasmtime_component_type_result_t; +/** + * \brief Container for different kind of component model type data + */ typedef union wasmtime_component_type_payload_t { + /// \brief Field used if #wasmtime_component_type_t::kind is #WASMTIME_COMPONENT_KIND_LIST wasmtime_component_type_t* list; + /// \brief Field used if #wasmtime_component_type_t::kind is #WASMTIME_COMPONENT_KIND_RECORD wasmtime_component_type_field_vec_t record; + /// \brief Field used if #wasmtime_component_type_t::kind is #WASMTIME_COMPONENT_KIND_TUPLE wasmtime_component_type_vec_t tuple; + /// \brief Field used if #wasmtime_component_type_t::kind is #WASMTIME_COMPONENT_KIND_VARIANT wasmtime_component_type_field_vec_t variant; + /// \brief Field used if #wasmtime_component_type_t::kind is #WASMTIME_COMPONENT_KIND_ENUM wasmtime_component_string_vec_t enumeration; + /// \brief Field used if #wasmtime_component_type_t::kind is #WASMTIME_COMPONENT_KIND_OPTION wasmtime_component_type_t* option; + /// \brief Field used if #wasmtime_component_type_t::kind is #WASMTIME_COMPONENT_KIND_RESULT wasmtime_component_type_result_t result; + /// \brief Field used if #wasmtime_component_type_t::kind is #WASMTIME_COMPONENT_KIND_FLAGS wasmtime_component_string_vec_t flags; } wasmtime_component_type_payload_t; +/** + * \brief Representation of a component model type + * + * Many kind of types own data, so those need to be properly dropped in wasmtime, + * and therefore should not live on the stack. + */ typedef struct wasmtime_component_type_t { + /// \brief Discriminant indicating what kind of type it is, and which field of #payload is valid, if any wasmtime_component_kind_t kind; + /// \brief Container for the type data, if any wasmtime_component_type_payload_t payload; } wasmtime_component_type_t; @@ -202,33 +333,142 @@ WASMTIME_COMPONENT_DECLARE_VEC_NEW(type_vec, wasmtime_component_type_t); #undef WASMTIME_COMPONENT_DECLARE_VEC_NEW +/** + * \brief Creates a new #wasmtime_component_type_t + * + * This is usually used to create inner types, typically as part of an option or result. + * In this case, ownership is given out to the outer value. + * + * In case where a top level #wasmtime_component_type_t is created (for example to be passed directly to + * #wasmtime_component_linker_define_func), then it should be deleted with #wasmtime_component_type_delete + * + * \return a pointer to the newly created #wasmtime_component_type_t + */ wasmtime_component_type_t* wasmtime_component_type_new(); +/** + * \brief Deletes a #wasmtime_component_type_t previously created by #wasmtime_component_type_new + * + * This should not be called if the type has been given out as part of an outer #wasmtime_component_type_t + * + * \param val the #wasmtime_component_type_t to delete + */ void wasmtime_component_type_delete(wasmtime_component_type_t* ty); +/** + * \brief Representation of a component in the component model. + */ typedef struct wasmtime_component_t wasmtime_component_t; +/** + * \brief Compiles a WebAssembly binary into a #wasmtime_component_t + * + * This function will compile a WebAssembly binary into an owned #wasmtime_component_t. + * + * It requires a component binary, such as what is produced by Rust `cargo component` tooling. + * + * This function does not take ownership of any of its arguments, but the + * returned error and component are owned by the caller. + + * \param engine the #wasm_engine_t that will create the component + * \param buf the address of the buffer containing the WebAssembly binary + * \param len the length of the buffer containing the WebAssembly binary + * \param component_out on success, contains the address of the created component + * \return NULL on success, else a #wasmtime_error_t describing the error + */ wasmtime_error_t * wasmtime_component_from_binary(const wasm_engine_t *engine, const uint8_t *buf, size_t len, wasmtime_component_t **component_out); +/** + * \brief Deletes a #wasmtime_component_t created by #wasmtime_component_from_binary + * + * \param component the component to delete + */ void wasmtime_component_delete(wasmtime_component_t *component); +/** + * \brief Representation of a component linker + * + * This type corresponds to a `wasmtime::component::Linker`. + * + * Due to the interaction between `wasmtime::component::Linker` and + * `wasmtime::component::LinkerInstance`, the latter being more of a builder, + * it is expected to first define the host functions through calls to + * #wasmtime_component_linker_define_func, then call #wasmtime_component_linker_build + * to create and populate the root `wasmtime::component::LinkerInstance` and it's + * descendants (if any). + */ typedef struct wasmtime_component_linker_t wasmtime_component_linker_t; +/** + * \brief Creates a new #wasmtime_component_linker_t for the specified engine. + * + * \param engine the compilation environment and configuration + * \return a pointer to the newly created #wasmtime_component_linker_t + */ wasmtime_component_linker_t *wasmtime_component_linker_new(const wasm_engine_t *engine); +/** + * \brief Deletes a #wasmtime_component_linker_t created by #wasmtime_component_linker_new + * + * \param linker the #wasmtime_component_linker_t to delete + */ void wasmtime_component_linker_delete(wasmtime_component_linker_t *linker); +/// \brief Representation of a component instance typedef struct wasmtime_component_instance_t wasmtime_component_instance_t; // declaration from store.h typedef struct wasmtime_context wasmtime_context_t; +/** + * \brief Callback signature for #wasmtime_component_linker_define_func. + * + * This is the function signature for host functions that can be made accessible + * to WebAssembly components. The arguments to this function are: + * + * \param env a user-provided argument passed to #wasmtime_component_linker_define_func + * \param context a #wasmtime_context_t, the context of this call + * \param args the arguments provided to this function invocation + * \param nargs how many arguments are provided + * \param results where to write the results of this function + * \param nresults how many results must be produced + * + * Callbacks are guaranteed to get called with the right types of arguments, but + * they must produce the correct number and types of results. Failure to do so + * will cause traps to get raised on the wasm side. + * + * This callback can optionally return a #wasm_trap_t indicating that a trap + * should be raised in WebAssembly. It's expected that in this case the caller + * relinquishes ownership of the trap and it is passed back to the engine. + */ typedef wasm_trap_t *(*wasmtime_component_func_callback_t)( void *env, wasmtime_context_t *context, const wasmtime_component_val_t *args, size_t nargs, wasmtime_component_val_t *results, size_t nresults); +/** + * \brief Defines a host function in a linker + * + * Must be done before calling #wasmtime_component_linker_build + * + * Does not take ownership of the #wasmtime_component_type_t arguments. + * + * \param linker the #wasmtime_component_linker_t in which the function should be defined + * \param path the dot-separated path of the package where the function is defined + * \param path_len the byte length of `path` + * \param name the name of the function + * \param name_len the byte length of `name` + * \param params_types_buf a pointer to an array of #wasmtime_component_type_t describing the function's parameters + * \param params_types_len the length of `params_types_buf` + * \param outputs_types_buf a pointer to an array of #wasmtime_component_type_t describing the function's outputs + * \param outputs_types_len the length of `outputs_types_buf` + * \param cb a pointer to the actual functions, a #wasmtime_component_func_callback_t + * \param data the host-provided data to provide as the first argument to the callback + * \param finalizer an optional finalizer for the `data` argument. + * \return wasmtime_error_t* on success `NULL` is returned, otherwise an error is returned which + * describes why the definition failed. + */ wasmtime_error_t *wasmtime_component_linker_define_func( wasmtime_component_linker_t *linker, const char *path, size_t path_len, const char *name, size_t name_len, @@ -236,18 +476,72 @@ wasmtime_error_t *wasmtime_component_linker_define_func( wasmtime_component_type_t* outputs_types_buf, size_t outputs_types_len, wasmtime_component_func_callback_t cb, void *data, void (*finalizer)(void *)); +/** + * \brief Builds the linker, providing the host functions defined by calls to #wasmtime_component_linker_define_func + * + * \param linker the #wasmtime_component_linker_t to build + * \return wasmtime_error_t* On success `NULL` is returned, otherwise an error is returned which + * describes why the build failed. + */ wasmtime_error_t *wasmtime_component_linker_build(wasmtime_component_linker_t *linker); +/** + * \brief Instantiates a component instance in a given #wasmtime_context_t + * + * \param linker a #wasmtime_component_linker_t that will help provide host functions + * \param context the #wasmtime_context_t in which the instance should be created + * \param component the #wasmtime_component_t to instantiate + * \param instance_out on success, the instantiated #wasmtime_component_instance_t + * \return wasmtime_error_t* on success `NULL` is returned, otherwise an error is returned which + * describes why the build failed. + */ wasmtime_error_t *wasmtime_component_linker_instantiate( const wasmtime_component_linker_t *linker, wasmtime_context_t *context, const wasmtime_component_t *component, wasmtime_component_instance_t **instance_out); +/// \brief Representation of an exported function in Wasmtime component model. typedef struct wasmtime_component_func_t wasmtime_component_func_t; +/** + * \brief Looks for an exported function in the given component instance + * + * \param instance the #wasmtime_component_instance_t in which the function should be looked for + * \param context the #wasmtime_context_t that contains `instance` + * \param name the name of function to look for + * \param name_len the byte length of `name` + * \param item_out the wasmtime_component_func_t that was found, if any + * \return true if the function was found, else false + */ bool wasmtime_component_instance_get_func( const wasmtime_component_instance_t *instance, wasmtime_context_t *context, const char *name, size_t name_len, wasmtime_component_func_t **item_out); +/** + * \brief Calls an exported function of a component + * + * It is the responsibility of the caller to make sure that `params` has the expected + * length, and the correct types, else the call will error out. `results` must have the + * expected length, but the values will be written with the correct types. + * + * This can fail in two ways : either a non-NULL #wasmtime_error_t is returned, for example if the + * parameters are incorrect (and `trap_out` will be NULL), or the call may trap, in which case + * NULL is returned, but `trap_out` will be non-NULL. + * + * Does not take ownership of #wasmtime_component_val_t arguments. Gives ownership of + * #wasmtime_component_val_t results. As such, if those are data-owning values, they + * should be created and deleted through this api, either directly with #wasmtime_component_val_new, + * or through a #wasmtime_component_val_vec_t, using #wasmtime_component_val_vec_new and + * #wasmtime_component_val_vec_delete + * + * \param func the function to call, typically found with #wasmtime_component_instance_get_func + * \param context the #wasmtime_context_t that contains `func` + * \param params the parameters of `func`, as an array of #wasmtime_component_val_t + * \param params_len the length of `params` + * \param results the results of `func`, as an array of #wasmtime_component_val_t that will be written + * \param results_len the length of `results` + * \param trap_out NULL if the call completed successfully or couldn't be made, otherwise the trap that was raised + * \return wasmtime_error_t* NULL on success or a description of the error calling the function + */ wasmtime_error_t *wasmtime_component_func_call( const wasmtime_component_func_t *func, wasmtime_context_t *context, const wasmtime_component_val_t *params, size_t params_len, diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 84a02e46fb56..927b66d57867 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -58,6 +58,7 @@ create_target(multimemory multimemory.c) create_target(serialize serialize.c) create_target(threads threads.c) create_target(wasi wasi/main.c) +create_target(component component/main.c) # Add rust tests create_rust_test(anyref) diff --git a/examples/component/convert.wit b/examples/component/convert.wit index 52f7c012516a..a41d901eaeef 100644 --- a/examples/component/convert.wit +++ b/examples/component/convert.wit @@ -1,11 +1,23 @@ package local:demo; world convert { + variant temperature { + celsius(f32), + fahrenheit(f32), + } /// This interface needs to be provided by the host import host: interface { + enum binary-operation { + add, + multiply, + } /// Example function that does a simple a × b operation multiply: func(a: f32, b: f32) -> f32; + /// Example function that does a simple operation 'op' + apply: func(a: f32, b: f32, op: binary-operation) -> f32; } /// Exported function for computing: (°C × 9/5) + 32 = °F export convert-celsius-to-fahrenheit: func(x: f32) -> f32; + /// Exported function for converting from one temperature scale to the other + export convert: func(t: temperature) -> temperature; } diff --git a/examples/component/main.c b/examples/component/main.c new file mode 100644 index 000000000000..ffb54cca59b5 --- /dev/null +++ b/examples/component/main.c @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include +#include +#include + +const char* multiply_data = "hello multiply"; +const char* apply_data = "hello apply"; +const char* context_data = "context data"; + +static void exit_with_error(const char *message, wasmtime_error_t *error, + wasm_trap_t *trap); + +wasm_trap_t *mult( + void *env, wasmtime_context_t *context, const wasmtime_component_val_t *args, + size_t nargs, wasmtime_component_val_t *results, size_t nresults) +{ + assert(env == (void*)multiply_data); + const char* exec_env = (const char*)wasmtime_context_get_data(context); + assert(exec_env == context_data); + assert(nargs == 2); + assert(nresults == 1); + assert(args[0].kind == WASMTIME_COMPONENT_KIND_F32); + assert(args[1].kind == WASMTIME_COMPONENT_KIND_F32); + float res = args[0].payload.f32 * args[1].payload.f32; + results[0].kind = WASMTIME_COMPONENT_KIND_F32; + results[0].payload.f32 = res; + return NULL; +} + +wasm_trap_t *apply( + void *env, wasmtime_context_t *context, const wasmtime_component_val_t *args, + size_t nargs, wasmtime_component_val_t *results, size_t nresults) +{ + assert(env == (void*)apply_data); + const char* exec_env = (const char*)wasmtime_context_get_data(context); + assert(exec_env == context_data); + assert(nargs == 3); + assert(nresults == 1); + assert(args[0].kind == WASMTIME_COMPONENT_KIND_F32); + assert(args[1].kind == WASMTIME_COMPONENT_KIND_F32); + assert(args[2].kind == WASMTIME_COMPONENT_KIND_ENUM); + float res = args[2].payload.enumeration.discriminant == 0 ? + args[0].payload.f32 + args[1].payload.f32 : args[0].payload.f32 * args[1].payload.f32; + results[0].kind = WASMTIME_COMPONENT_KIND_F32; + results[0].payload.f32 = res; + return NULL; +} + +int main() { + wasm_engine_t *engine = wasm_engine_new(); + + // Create a component linker with host functions defined + wasmtime_component_linker_t *linker = wasmtime_component_linker_new(engine); + // `multiply` only uses types without additional owned data, that can live on the stack + wasmtime_component_type_t mult_param_types[2]; + mult_param_types[0].kind = WASMTIME_COMPONENT_KIND_F32; + mult_param_types[1].kind = WASMTIME_COMPONENT_KIND_F32; + wasmtime_component_type_t f32_result_type; + f32_result_type.kind = WASMTIME_COMPONENT_KIND_F32; + + wasmtime_error_t* error = wasmtime_component_linker_define_func( + linker, "host", 4, "multiply", 8, mult_param_types, 2, + &f32_result_type, 1, mult, (void*)multiply_data, NULL); + if (error) + exit_with_error("failed to define function multiply", error, NULL); + + // `apply` uses an enum, which needs additional data, use a wasmtime_component_type_vec_t + wasmtime_component_type_vec_t apply_param_types; + wasmtime_component_type_vec_new_uninitialized(&apply_param_types, 3); + apply_param_types.data[0].kind = WASMTIME_COMPONENT_KIND_F32; + apply_param_types.data[1].kind = WASMTIME_COMPONENT_KIND_F32; + apply_param_types.data[2].kind = WASMTIME_COMPONENT_KIND_ENUM; + wasmtime_component_string_vec_new_uninitialized(&apply_param_types.data[2].payload.enumeration, 2); + wasm_name_new_from_string(&apply_param_types.data[2].payload.enumeration.data[0], "add"); + wasm_name_new_from_string(&apply_param_types.data[2].payload.enumeration.data[1], "multiply"); + error = wasmtime_component_linker_define_func( + linker, "host", 4, "apply", 5, apply_param_types.data, apply_param_types.size, + &f32_result_type, 1, apply, (void*)apply_data, NULL); + if (error) + exit_with_error("failed to define function apply", error, NULL); + // deleting the vector also drops the full types hierarchy + wasmtime_component_type_vec_delete(&apply_param_types); + + error = wasmtime_component_linker_build(linker); + if (error) + exit_with_error("failed to build linker", error, NULL); + + // Load binary. + printf("Loading binary...\n"); + FILE *file = fopen("target/wasm32-unknown-unknown/debug/guest.wasm", "rb"); + if (!file) { + printf("> Error opening module!\n"); + return 1; + } + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); + wasm_byte_vec_t binary; + wasm_byte_vec_new_uninitialized(&binary, file_size); + if (fread(binary.data, file_size, 1, file) != 1) { + printf("> Error reading module!\n"); + return 1; + } + fclose(file); + + // Compile. + // Note that the binary should be a component (not a plain module), typically built by running + // `cargo component build -p example-component-wasm --target wasm32-unknown-unknown` + // this will therefore fail if only the README instructions are followed + printf("Compiling component...\n"); + wasmtime_component_t* component; + error = wasmtime_component_from_binary(engine, (uint8_t *)binary.data, binary.size, &component); + if (error) + exit_with_error("failed to build component", error, NULL); + wasm_byte_vec_delete(&binary); + + wasmtime_store_t *store = wasmtime_store_new(engine, (void*)context_data, NULL); + wasmtime_context_t *context = wasmtime_store_context(store); + + // Instantiate. + printf("Instantiating component...\n"); + wasmtime_component_instance_t *instance; + error = wasmtime_component_linker_instantiate(linker, context, component, &instance); + if (error) + exit_with_error("failed to instantiate component", error, NULL); + + // Lookup functions. + wasmtime_component_func_t* convert1; + const char* func_name1 = "convert-celsius-to-fahrenheit"; + bool ok = wasmtime_component_instance_get_func(instance, context, func_name1, strlen(func_name1), &convert1); + if (!ok) + exit_with_error("function convert-celsius-to-fahrenheit not found", NULL, NULL); + wasmtime_component_func_t* convert2; + const char* func_name2 = "convert"; + ok = wasmtime_component_instance_get_func(instance, context, func_name2, strlen(func_name2), &convert2); + if (!ok) + exit_with_error("function convert not found", NULL, NULL); + + // Call. + printf("Calling convert-celsius-to-fahrenheit...\n"); + wasmtime_component_val_t param_val, result_val; + param_val.kind = WASMTIME_COMPONENT_KIND_F32; + param_val.payload.f32 = 23.4f; + // will be written, but must be "droppable", i.e. without owned data + result_val.kind = WASMTIME_COMPONENT_KIND_BOOL; + wasm_trap_t* trap = NULL; + error = wasmtime_component_func_call(convert1, context, ¶m_val, 1, &result_val, 1, &trap); + if (error != NULL || trap != NULL) + exit_with_error("failed to call function convert-celsius-to-fahrenheit", error, trap); + + assert(result_val.kind == WASMTIME_COMPONENT_KIND_F32); + printf("23.4°C = %f°F\n", result_val.payload.f32); + + printf("Calling convert...\n"); + wasmtime_component_val_t* t = wasmtime_component_val_new(); + t->kind = WASMTIME_COMPONENT_KIND_VARIANT; + t->payload.variant.discriminant = 1; + t->payload.variant.val = wasmtime_component_val_new(); + t->payload.variant.val->kind = WASMTIME_COMPONENT_KIND_F32; + t->payload.variant.val->payload.f32 = 66.2f; + wasmtime_component_val_t* result = wasmtime_component_val_new(); + + error = wasmtime_component_func_call(convert2, context, t, 1, result, 1, &trap); + if (error != NULL || trap != NULL) + exit_with_error("failed to call function", error, trap); + wasmtime_component_val_delete(t); + + assert(result->kind == WASMTIME_COMPONENT_KIND_VARIANT); + assert(result->payload.variant.discriminant == 0); + assert(result->payload.variant.val != NULL); + assert(result->payload.variant.val->kind == WASMTIME_COMPONENT_KIND_F32); + printf("66.2°F = %f°C\n", result->payload.variant.val->payload.f32); + wasmtime_component_val_delete(result); + + wasmtime_store_delete(store); + + // Shut down. + printf("Shutting down...\n"); + wasmtime_component_delete(component); + wasmtime_component_linker_delete(linker); + wasm_engine_delete(engine); + + // All done. + printf("Done.\n"); + return 0; +} + +static void exit_with_error(const char *message, wasmtime_error_t *error, + wasm_trap_t *trap) { + fprintf(stderr, "error: %s\n", message); + wasm_byte_vec_t error_message; + if (error != NULL) { + wasmtime_error_message(error, &error_message); + } else { + wasm_trap_message(trap, &error_message); + } + fprintf(stderr, "%.*s\n", (int)error_message.size, error_message.data); + wasm_byte_vec_delete(&error_message); + exit(1); +} diff --git a/examples/component/main.rs b/examples/component/main.rs index c26e70b57607..784fc134bc32 100644 --- a/examples/component/main.rs +++ b/examples/component/main.rs @@ -16,6 +16,12 @@ impl host::Host for HostComponent { fn multiply(&mut self, a: f32, b: f32) -> f32 { a * b } + fn apply(&mut self, a: f32, b: f32, op: host::BinaryOperation) -> f32 { + match op { + host::BinaryOperation::Add => a + b, + host::BinaryOperation::Multiply => a * b, + } + } } struct MyState { @@ -30,10 +36,16 @@ struct MyState { /// /// In this example we convert the code here to simplify the testing process and build system. fn convert_to_component(path: impl AsRef) -> Result> { - let bytes = &fs::read(&path).context("failed to read input file")?; - wit_component::ComponentEncoder::default() - .module(&bytes)? - .encode() + let bytes = fs::read(&path).context("failed to read input file")?; + // allow direct use of a component : look at the layer field of the preamble, see + // https://github.com/WebAssembly/component-model/blob/main/design/mvp/Binary.md#component-definitions + if bytes.len() > 6 && bytes[6] == 1 { + Ok(bytes) + } else { + wit_component::ComponentEncoder::default() + .module(&bytes)? + .encode() + } } fn main() -> Result<()> { @@ -45,18 +57,21 @@ fn main() -> Result<()> { // model. let component = convert_to_component("target/wasm32-unknown-unknown/debug/guest.wasm")?; - // Create our component and call our generated host function. + // Create our component and call our generated host functions. let component = Component::from_binary(&engine, &component)?; + let mut linker = Linker::new(&engine); + host::add_to_linker(&mut linker, |state: &mut MyState| &mut state.host)?; + let mut store = Store::new( &engine, MyState { host: HostComponent {}, }, ); - let mut linker = Linker::new(&engine); - host::add_to_linker(&mut linker, |state: &mut MyState| &mut state.host)?; let convert = Convert::instantiate(&mut store, &component, &linker)?; let result = convert.call_convert_celsius_to_fahrenheit(&mut store, 23.4)?; println!("Converted to: {result:?}"); + let result = convert.call_convert(&mut store, Temperature::Fahrenheit(66.2))?; + println!("Converted to: {result:?}"); Ok(()) } diff --git a/examples/component/wasm/guest.rs b/examples/component/wasm/guest.rs index 8af47944470f..c91f258428ec 100644 --- a/examples/component/wasm/guest.rs +++ b/examples/component/wasm/guest.rs @@ -11,6 +11,17 @@ export!(GuestComponent); impl Guest for GuestComponent { fn convert_celsius_to_fahrenheit(x: f32) -> f32 { - host::multiply(x, 1.8) + 32.0 + host::apply( + host::apply(x, 1.8, host::BinaryOperation::Multiply), + 32.0, + host::BinaryOperation::Add, + ) + } + + fn convert(t: Temperature) -> Temperature { + match t { + Temperature::Celsius(t) => Temperature::Fahrenheit(host::multiply(t, 1.8) + 32.0), + Temperature::Fahrenheit(t) => Temperature::Celsius(host::multiply(t - 32.0, 5.0 / 9.0)), + } } }