diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml index 84b7d45ede1e..6917d6bdcdee 100644 --- a/crates/c-api/Cargo.toml +++ b/crates/c-api/Cargo.toml @@ -49,6 +49,7 @@ logging = ['dep:env_logger'] disable-logging = ["log/max_level_off", "tracing/max_level_off"] coredump = ["wasmtime/coredump"] addr2line = ["wasmtime/addr2line"] +component-model = ["wasmtime/component-model", "cranelift"] demangle = ["wasmtime/demangle"] threads = ["wasmtime/threads"] gc = ["wasmtime/gc"] diff --git a/crates/c-api/artifact/Cargo.toml b/crates/c-api/artifact/Cargo.toml index 1eb6659ec01b..afc0222deb82 100644 --- a/crates/c-api/artifact/Cargo.toml +++ b/crates/c-api/artifact/Cargo.toml @@ -38,6 +38,7 @@ default = [ 'gc-null', 'cranelift', 'winch', + 'component-model', # ... if you add a line above this be sure to change the other locations # marked WASMTIME_FEATURE_LIST ] @@ -52,6 +53,7 @@ coredump = ["wasmtime-c-api/coredump"] addr2line = ["wasmtime-c-api/addr2line"] demangle = ["wasmtime-c-api/demangle"] wat = ["wasmtime-c-api/wat"] +component-model = ["wasmtime-c-api/component-model"] threads = ["wasmtime-c-api/threads"] gc = ["wasmtime-c-api/gc"] gc-drc = ["wasmtime-c-api/gc-drc"] diff --git a/crates/c-api/build.rs b/crates/c-api/build.rs index b500fd1bf4d1..d1b24f1c7ee7 100644 --- a/crates/c-api/build.rs +++ b/crates/c-api/build.rs @@ -19,6 +19,7 @@ const FEATURES: &[&str] = &[ "GC_NULL", "CRANELIFT", "WINCH", + "COMPONENT_MODEL", ]; // ... if you add a line above this be sure to change the other locations // marked WASMTIME_FEATURE_LIST diff --git a/crates/c-api/cmake/features.cmake b/crates/c-api/cmake/features.cmake index 385c139659e1..cb8079446fc8 100644 --- a/crates/c-api/cmake/features.cmake +++ b/crates/c-api/cmake/features.cmake @@ -43,5 +43,6 @@ feature(gc-null ON) feature(async ON) feature(cranelift ON) feature(winch ON) +feature(component-model ON) # ... if you add a line above this be sure to change the other locations # marked WASMTIME_FEATURE_LIST diff --git a/crates/c-api/include/wasmtime/component.h b/crates/c-api/include/wasmtime/component.h new file mode 100644 index 000000000000..3d8922503fc3 --- /dev/null +++ b/crates/c-api/include/wasmtime/component.h @@ -0,0 +1,635 @@ +/** + * The component model + * + * Wasmtime APIs for interacting with WebAssembly Component Model. + * + */ + +#ifndef WASMTIME_COMPONENT_H +#define WASMTIME_COMPONENT_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief Whether or not to enable support for the component model in + * Wasmtime. + * + * For more information see the Rust documentation at + * https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.wasm_component_model + */ +WASMTIME_CONFIG_PROP(void, component_model, bool) + +/** + * \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. + * + * Note that resources are currently not supported. + */ +typedef uint8_t wasmtime_component_kind_t; + +/// Value of #wasmtime_component_kind_t indicating a boolean +#define WASMTIME_COMPONENT_KIND_BOOL 0 +/// Value of #wasmtime_component_kind_t indicating a signed 8-bit integer +#define WASMTIME_COMPONENT_KIND_S8 1 +/// Value of #wasmtime_component_kind_t indicating an unsigned 8-bit integer +#define WASMTIME_COMPONENT_KIND_U8 2 +/// Value of #wasmtime_component_kind_t indicating a signed 16-bit integer +#define WASMTIME_COMPONENT_KIND_S16 3 +/// Value of #wasmtime_component_kind_t indicating an unsigned 16-bit integer +#define WASMTIME_COMPONENT_KIND_U16 4 +/// Value of #wasmtime_component_kind_t indicating a signed 32-bit integer +#define WASMTIME_COMPONENT_KIND_S32 5 +/// Value of #wasmtime_component_kind_t indicating an unsigned 32-bit integer +#define WASMTIME_COMPONENT_KIND_U32 6 +/// Value of #wasmtime_component_kind_t indicating a signed 64-bit integer +#define WASMTIME_COMPONENT_KIND_S64 7 +/// Value of #wasmtime_component_kind_t indicating an unsigned 64-bit integer +#define WASMTIME_COMPONENT_KIND_U64 8 +/// Value of #wasmtime_component_kind_t indicating a 32-bit floating point +/// number +#define WASMTIME_COMPONENT_KIND_F32 9 +/// Value of #wasmtime_component_kind_t indicating a 64-bit floating point +/// number +#define WASMTIME_COMPONENT_KIND_F64 10 +/// Value of #wasmtime_component_kind_t indicating a unicode character +#define WASMTIME_COMPONENT_KIND_CHAR 11 +/// Value of #wasmtime_component_kind_t indicating a unicode string +#define WASMTIME_COMPONENT_KIND_STRING 12 +/// Value of #wasmtime_component_kind_t indicating a list +#define WASMTIME_COMPONENT_KIND_LIST 13 +/// Value of #wasmtime_component_kind_t indicating a record +#define WASMTIME_COMPONENT_KIND_RECORD 14 +/// Value of #wasmtime_component_kind_t indicating a tuple +#define WASMTIME_COMPONENT_KIND_TUPLE 15 +/// Value of #wasmtime_component_kind_t indicating a variant +#define WASMTIME_COMPONENT_KIND_VARIANT 16 +/// Value of #wasmtime_component_kind_t indicating an enum +#define WASMTIME_COMPONENT_KIND_ENUM 17 +/// Value of #wasmtime_component_kind_t indicating an option +#define WASMTIME_COMPONENT_KIND_OPTION 18 +/// Value of #wasmtime_component_kind_t indicating a result +#define WASMTIME_COMPONENT_KIND_RESULT 19 +/// 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; +typedef struct wasmtime_component_type_field_t wasmtime_component_type_field_t; + +#define WASMTIME_COMPONENT_DECLARE_VEC(name, element) \ + typedef struct wasmtime_component_##name##_t { \ + size_t size; \ + element *data; \ + } wasmtime_component_##name##_t; \ + \ + WASM_API_EXTERN void wasmtime_component_##name##_new_empty( \ + wasmtime_component_##name##_t *out); \ + WASM_API_EXTERN void wasmtime_component_##name##_new_uninitialized( \ + wasmtime_component_##name##_t *out, size_t); \ + WASM_API_EXTERN void wasmtime_component_##name##_copy( \ + wasmtime_component_##name##_t *out, \ + const wasmtime_component_##name##_t *); \ + WASM_API_EXTERN void wasmtime_component_##name##_delete( \ + wasmtime_component_##name##_t *); + +// in C, an array type needs a complete element type, we need to defer xxx_new +#define WASMTIME_COMPONENT_DECLARE_VEC_NEW(name, element) \ + WASM_API_EXTERN void wasmtime_component_##name##_new( \ + wasmtime_component_##name##_t *out, size_t, element const[]); + +/// A vector of values. +WASMTIME_COMPONENT_DECLARE_VEC(val_vec, wasmtime_component_val_t); + +/// A tuple of named fields. +WASMTIME_COMPONENT_DECLARE_VEC(val_record, + wasmtime_component_val_record_field_t); + +/// A variable sized bitset. +WASMTIME_COMPONENT_DECLARE_VEC(val_flags, uint32_t); +WASMTIME_COMPONENT_DECLARE_VEC_NEW(val_flags, uint32_t); + +/// A vector of types +WASMTIME_COMPONENT_DECLARE_VEC(type_vec, wasmtime_component_type_t); + +/// A vector of field types +WASMTIME_COMPONENT_DECLARE_VEC(type_field_vec, wasmtime_component_type_field_t); + +/// A vector of strings +WASMTIME_COMPONENT_DECLARE_VEC(string_vec, wasm_name_t); +WASMTIME_COMPONENT_DECLARE_VEC_NEW(string_vec, wasm_name_t); + +#undef WASMTIME_COMPONENT_DECLARE_VEC + +/// Representation of a variant value +typedef struct wasmtime_component_val_variant_t { + /// Discriminant indicating the index of the variant case of this value + uint32_t discriminant; + /// #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; + +/** + * \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 { + /// Value of the result, either of the ok type or of the error type, depending + /// on #error + wasmtime_component_val_t *val; + /// Discriminant indicating if this result is an ok value or an error + bool error; +} wasmtime_component_val_result_t; + +/// Representation of an enum value +typedef struct wasmtime_component_val_enum_t { + /// 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 dynamic data to one of this field means that the payload now owns it + */ +typedef union wasmtime_component_val_payload_t { + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_BOOLEAN + bool boolean; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_S8 + int8_t s8; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_U8 + uint8_t u8; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_S16 + int16_t s16; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_U16 + uint16_t u16; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_S32 + int32_t s32; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_U32 + uint32_t u32; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_S64 + int64_t s64; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_U64 + uint64_t u64; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_F32 + float f32; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_F64 + double f64; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_CHARACTER + uint8_t character; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_STRING + wasm_name_t string; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_LIST + wasmtime_component_val_vec_t list; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_RECORD + wasmtime_component_val_record_t record; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_TUPLE + wasmtime_component_val_vec_t tuple; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_VARIANT + wasmtime_component_val_variant_t variant; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_ENUMERATION + wasmtime_component_val_enum_t enumeration; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_OPTION + wasmtime_component_val_t *option; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_RESULT + wasmtime_component_val_result_t result; + /// Field used if #wasmtime_component_val_t::kind is + /// #WASMTIME_COMPONENT_KIND_FLAGS + wasmtime_component_val_flags_t flags; +} wasmtime_component_val_payload_t; + +/** + * \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 { + /// Discriminant indicating which field of #payload is valid + wasmtime_component_kind_t kind; + /// 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); + +/// Representation of record field. +typedef struct wasmtime_component_val_record_field_t { + /// Name of the field + wasm_name_t name; + /// 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); + +/** + * \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); + +/** + * \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); + +/// Representation of a field in a record type or a case in a variant type +typedef struct wasmtime_component_type_field_t { + /// Name of the record field or variant case + wasm_name_t name; + /// 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); + +/// Representation of a result type +typedef struct wasmtime_component_type_result_t { + /// Type of the ok value (if there is one) + wasmtime_component_type_t *ok_ty; + /// Type of the error value (if there is one) + wasmtime_component_type_t *err_ty; +} wasmtime_component_type_result_t; + +/// Container for different kind of component model type data +typedef union wasmtime_component_type_payload_t { + /// Field used if #wasmtime_component_type_t::kind is + /// #WASMTIME_COMPONENT_KIND_LIST + wasmtime_component_type_t *list; + /// Field used if #wasmtime_component_type_t::kind is + /// #WASMTIME_COMPONENT_KIND_RECORD + wasmtime_component_type_field_vec_t record; + /// Field used if #wasmtime_component_type_t::kind is + /// #WASMTIME_COMPONENT_KIND_TUPLE + wasmtime_component_type_vec_t tuple; + /// Field used if #wasmtime_component_type_t::kind is + /// #WASMTIME_COMPONENT_KIND_VARIANT + wasmtime_component_type_field_vec_t variant; + /// Field used if #wasmtime_component_type_t::kind is + /// #WASMTIME_COMPONENT_KIND_ENUM + wasmtime_component_string_vec_t enumeration; + /// Field used if #wasmtime_component_type_t::kind is + /// #WASMTIME_COMPONENT_KIND_OPTION + wasmtime_component_type_t *option; + /// Field used if #wasmtime_component_type_t::kind is + /// #WASMTIME_COMPONENT_KIND_RESULT + wasmtime_component_type_result_t result; + /// 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 { + /// Discriminant indicating what kind of type it is, and which field of + /// #payload is valid, if any + wasmtime_component_kind_t kind; + /// Container for the type data, if any + wasmtime_component_type_payload_t payload; +} wasmtime_component_type_t; + +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); + +/// 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); + +/// 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, + wasmtime_component_type_t *params_types_buf, size_t params_types_len, + 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); + +/// 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, + wasmtime_component_val_t *results, size_t results_len, + wasm_trap_t **trap_out); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WASMTIME_COMPONENT_H diff --git a/crates/c-api/include/wasmtime/conf.h.in b/crates/c-api/include/wasmtime/conf.h.in index 3dff987acc4f..2288407cca41 100644 --- a/crates/c-api/include/wasmtime/conf.h.in +++ b/crates/c-api/include/wasmtime/conf.h.in @@ -25,6 +25,7 @@ #cmakedefine WASMTIME_FEATURE_ASYNC #cmakedefine WASMTIME_FEATURE_CRANELIFT #cmakedefine WASMTIME_FEATURE_WINCH +#cmakedefine WASMTIME_FEATURE_COMPONENT_MODEL // ... if you add a line above this be sure to change the other locations // marked WASMTIME_FEATURE_LIST diff --git a/crates/c-api/src/async.rs b/crates/c-api/src/async.rs index c6066cf5402d..4d96d5c03931 100644 --- a/crates/c-api/src/async.rs +++ b/crates/c-api/src/async.rs @@ -7,9 +7,7 @@ use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; use std::{ptr, str}; -use wasmtime::{ - AsContextMut, Func, Instance, Result, RootScope, StackCreator, StackMemory, Trap, Val, -}; +use wasmtime::{AsContextMut, Func, Instance, Result, RootScope, StackCreator, StackMemory, Val}; use crate::{ bad_utf8, handle_result, to_str, translate_args, wasm_config_t, wasm_functype_t, wasm_trap_t, @@ -211,10 +209,8 @@ fn handle_call_error( trap_ret: &mut *mut wasm_trap_t, err_ret: &mut *mut wasmtime_error_t, ) { - if err.is::() { - *trap_ret = Box::into_raw(Box::new(wasm_trap_t::new(err))); - } else { - *err_ret = Box::into_raw(Box::new(wasmtime_error_t::from(err))); + if let Some(err) = crate::handle_call_error(err, trap_ret) { + *err_ret = Box::into_raw(err); } } diff --git a/crates/c-api/src/component.rs b/crates/c-api/src/component.rs new file mode 100644 index 000000000000..eb723e2d4517 --- /dev/null +++ b/crates/c-api/src/component.rs @@ -0,0 +1,1632 @@ +use anyhow::{anyhow, bail, ensure, Context, Result}; +use wasmtime::component::{Component, Func, Instance, Linker, LinkerInstance, Type, Val}; +use wasmtime::{AsContext, AsContextMut}; + +use crate::{ + bad_utf8, declare_vecs, handle_call_error, handle_result, to_str, wasm_byte_vec_t, + wasm_config_t, wasm_engine_t, wasm_name_t, wasm_trap_t, wasmtime_error_t, + WasmtimeStoreContextMut, WasmtimeStoreData, +}; +use core::ffi::c_void; +use std::collections::HashMap; +use std::ops::Deref; +use std::{mem, mem::MaybeUninit, ptr, slice, str}; + +#[no_mangle] +pub extern "C" fn wasmtime_config_component_model_set(c: &mut wasm_config_t, enable: bool) { + c.config.wasm_component_model(enable); +} + +pub type wasmtime_component_string_t = wasm_byte_vec_t; + +#[repr(C)] +#[derive(Clone)] +pub struct wasmtime_component_val_record_field_t { + pub name: wasm_name_t, + pub val: wasmtime_component_val_t, +} + +impl Default for wasmtime_component_val_record_field_t { + fn default() -> Self { + Self { + name: Vec::new().into(), + val: Default::default(), + } + } +} + +declare_vecs! { + ( + name: wasmtime_component_val_vec_t, + ty: wasmtime_component_val_t, + new: wasmtime_component_val_vec_new, + empty: wasmtime_component_val_vec_new_empty, + uninit: wasmtime_component_val_vec_new_uninitialized, + copy: wasmtime_component_val_vec_copy, + delete: wasmtime_component_val_vec_delete, + ) + ( + name: wasmtime_component_val_record_t, + ty: wasmtime_component_val_record_field_t, + new: wasmtime_component_val_record_new, + empty: wasmtime_component_val_record_new_empty, + uninit: wasmtime_component_val_record_new_uninitialized, + copy: wasmtime_component_val_record_copy, + delete: wasmtime_component_val_record_delete, + ) + ( + name: wasmtime_component_val_flags_t, + ty: u32, + new: wasmtime_component_val_flags_new, + empty: wasmtime_component_val_flags_new_empty, + uninit: wasmtime_component_val_flags_new_uninitialized, + copy: wasmtime_component_val_flags_copy, + delete: wasmtime_component_val_flags_delete, + ) +} + +#[repr(C)] +#[derive(Clone)] +pub struct wasmtime_component_val_variant_t { + pub discriminant: u32, + pub val: Option>, +} + +#[repr(C)] +#[derive(Clone)] +pub struct wasmtime_component_val_result_t { + pub value: Option>, + pub error: bool, +} + +#[repr(C)] +#[derive(Clone)] +pub struct wasmtime_component_val_enum_t { + pub discriminant: u32, +} + +#[no_mangle] +pub extern "C" fn wasmtime_component_val_flags_set( + flags: &mut wasmtime_component_val_flags_t, + index: u32, + enabled: bool, +) { + let mut f = flags.take(); + let (idx, bit) = ((index / u32::BITS) as usize, index % u32::BITS); + if idx >= f.len() { + f.resize(idx + 1, Default::default()); + } + if enabled { + f[idx] |= 1 << (bit); + } else { + f[idx] &= !(1 << (bit)); + } + flags.set_buffer(f); +} + +#[no_mangle] +pub extern "C" fn wasmtime_component_val_flags_test( + flags: &wasmtime_component_val_flags_t, + index: u32, +) -> bool { + let flags = flags.as_slice(); + let (idx, bit) = ((index / u32::BITS) as usize, index % u32::BITS); + flags.get(idx).map(|v| v & (1 << bit) != 0).unwrap_or(false) +} + +#[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(char), + String(wasmtime_component_string_t), + List(wasmtime_component_val_vec_t), + Record(wasmtime_component_val_record_t), + Tuple(wasmtime_component_val_vec_t), + Variant(wasmtime_component_val_variant_t), + Enum(wasmtime_component_val_enum_t), + Option(Option>), + Result(wasmtime_component_val_result_t), + Flags(wasmtime_component_val_flags_t), +} + +macro_rules! ensure_type { + ($ty:ident, $variant:pat) => { + ensure!( + matches!($ty, $variant), + "attempted to create a {} for a {}", + $ty.desc(), + stringify!($variant) + ); + }; +} + +// a c_api value and its associated Type (from the component model runtime) +struct TypedCVal<'a>(&'a wasmtime_component_val_t, &'a Type); + +impl TryFrom> for Val { + type Error = anyhow::Error; + fn try_from(value: TypedCVal) -> Result { + let (value, ty) = (value.0, value.1); + Ok(match value { + &wasmtime_component_val_t::Bool(b) => { + ensure_type!(ty, Type::Bool); + Val::Bool(b) + } + &wasmtime_component_val_t::S8(v) => { + ensure_type!(ty, Type::S8); + Val::S8(v) + } + &wasmtime_component_val_t::U8(v) => { + ensure_type!(ty, Type::U8); + Val::U8(v) + } + &wasmtime_component_val_t::S16(v) => { + ensure_type!(ty, Type::S16); + Val::S16(v) + } + &wasmtime_component_val_t::U16(v) => { + ensure_type!(ty, Type::U16); + Val::U16(v) + } + &wasmtime_component_val_t::S32(v) => { + ensure_type!(ty, Type::S32); + Val::S32(v) + } + &wasmtime_component_val_t::U32(v) => { + ensure_type!(ty, Type::U32); + Val::U32(v) + } + &wasmtime_component_val_t::S64(v) => { + ensure_type!(ty, Type::S64); + Val::S64(v) + } + &wasmtime_component_val_t::U64(v) => { + ensure_type!(ty, Type::U64); + Val::U64(v) + } + &wasmtime_component_val_t::F32(v) => { + ensure_type!(ty, Type::Float32); + Val::Float32(v) + } + &wasmtime_component_val_t::F64(v) => { + ensure_type!(ty, Type::Float64); + Val::Float64(v) + } + &wasmtime_component_val_t::Char(v) => { + ensure_type!(ty, Type::Char); + Val::Char(v) + } + wasmtime_component_val_t::String(v) => { + ensure_type!(ty, Type::String); + Val::String(String::from_utf8(v.as_slice().to_vec())?) + } + wasmtime_component_val_t::List(v) => { + if let Type::List(ty) = ty { + Val::List( + v.as_slice() + .iter() + .map(|v| TypedCVal(v, &ty.ty()).try_into()) + .collect::>>()?, + ) + } else { + bail!("attempted to create a list for a {}", ty.desc()); + } + } + wasmtime_component_val_t::Record(v) => { + if let Type::Record(ty) = ty { + let mut field_vals: HashMap<&[u8], &wasmtime_component_val_t> = + HashMap::from_iter( + v.as_slice().iter().map(|f| (f.name.as_slice(), &f.val)), + ); + let field_tys = ty.fields(); + Val::Record( + field_tys + .map(|tyf| { + if let Some(v) = field_vals.remove(tyf.name.as_bytes()) { + Ok((tyf.name.to_string(), TypedCVal(v, &tyf.ty).try_into()?)) + } else { + bail!("record missing field: {}", tyf.name); + } + }) + .collect::>>()?, + ) + } else { + bail!("attempted to create a record for a {}", ty.desc()); + } + } + wasmtime_component_val_t::Tuple(v) => { + if let Type::Tuple(ty) = ty { + Val::Tuple( + ty.types() + .zip(v.as_slice().iter()) + .map(|(ty, v)| TypedCVal(v, &ty).try_into()) + .collect::>>()?, + ) + } else { + bail!("attempted to create a tuple for a {}", ty.desc()); + } + } + wasmtime_component_val_t::Variant(v) => { + if let Type::Variant(ty) = ty { + let case = ty + .cases() + .nth(v.discriminant as usize) + .with_context(|| format!("missing variant {}", v.discriminant))?; + ensure!( + case.ty.is_some() == v.val.is_some(), + "variant type mismatch: {}", + case.ty.map(|ty| ty.desc()).unwrap_or("none") + ); + if let (Some(t), Some(v)) = (case.ty, &v.val) { + let v = TypedCVal(v.as_ref(), &t).try_into()?; + Val::Variant(case.name.to_string(), Some(Box::new(v))) + } else { + Val::Variant(case.name.to_string(), None) + } + } else { + bail!("attempted to create a variant for a {}", ty.desc()); + } + } + wasmtime_component_val_t::Enum(v) => { + if let Type::Enum(ty) = ty { + let name = ty + .names() + .nth(v.discriminant as usize) + .with_context(|| format!("missing enumeration {}", v.discriminant))?; + Val::Enum(name.to_string()) + } else { + bail!("attempted to create an enum for a {}", ty.desc()); + } + } + wasmtime_component_val_t::Option(v) => { + if let Type::Option(ty) = ty { + Val::Option(match v { + Some(v) => Some(Box::new(TypedCVal(v.as_ref(), &ty.ty()).try_into()?)), + None => None, + }) + } else { + bail!("attempted to create an option for a {}", ty.desc()); + } + } + wasmtime_component_val_t::Result(v) => { + if let Type::Result(ty) = ty { + if v.error { + match &v.value { + Some(v) => { + let ty = ty.err().context("expected err type")?; + Val::Result(Err(Some(Box::new( + TypedCVal(v.as_ref(), &ty).try_into()?, + )))) + } + None => { + ensure!(ty.err().is_none(), "expected no err type"); + Val::Result(Err(None)) + } + } + } else { + match &v.value { + Some(v) => { + let ty = ty.ok().context("expected ok type")?; + Val::Result(Ok(Some(Box::new( + TypedCVal(v.as_ref(), &ty).try_into()?, + )))) + } + None => { + ensure!(ty.ok().is_none(), "expected no ok type"); + Val::Result(Ok(None)) + } + } + } + } else { + bail!("attempted to create a result for a {}", ty.desc()); + } + } + wasmtime_component_val_t::Flags(flags) => { + if let Type::Flags(ty) = ty { + let mut set = Vec::new(); + for (idx, name) in ty.names().enumerate() { + if wasmtime_component_val_flags_test(&flags, idx as u32) { + set.push(name.to_string()); + } + } + Val::Flags(set) + } else { + bail!("attempted to create a flags for a {}", ty.desc()); + } + } + }) + } +} + +// a Val and its associated wasmtime_component_type_t (from the c_api) +struct CTypedVal<'a>(&'a Val, &'a CType); + +impl TryFrom> for wasmtime_component_val_t { + type Error = anyhow::Error; + fn try_from(value: CTypedVal) -> Result { + let (value, ty) = (value.0, value.1); + Ok(match value { + Val::Bool(v) => { + ensure_type!(ty, CType::Bool); + wasmtime_component_val_t::Bool(*v) + } + Val::S8(v) => { + ensure_type!(ty, CType::S8); + wasmtime_component_val_t::S8(*v) + } + Val::U8(v) => { + ensure_type!(ty, CType::U8); + wasmtime_component_val_t::U8(*v) + } + Val::S16(v) => { + ensure_type!(ty, CType::S16); + wasmtime_component_val_t::S16(*v) + } + Val::U16(v) => { + ensure_type!(ty, CType::U16); + wasmtime_component_val_t::U16(*v) + } + Val::S32(v) => { + ensure_type!(ty, CType::S32); + wasmtime_component_val_t::S32(*v) + } + Val::U32(v) => { + ensure_type!(ty, CType::U32); + wasmtime_component_val_t::U32(*v) + } + Val::S64(v) => { + ensure_type!(ty, CType::S64); + wasmtime_component_val_t::S64(*v) + } + Val::U64(v) => { + ensure_type!(ty, CType::U64); + wasmtime_component_val_t::U64(*v) + } + Val::Float32(v) => { + ensure_type!(ty, CType::F32); + wasmtime_component_val_t::F32(*v) + } + Val::Float64(v) => { + ensure_type!(ty, CType::F64); + wasmtime_component_val_t::F64(*v) + } + Val::Char(v) => { + ensure_type!(ty, CType::Char); + wasmtime_component_val_t::Char(*v) + } + Val::String(v) => { + ensure_type!(ty, CType::String); + wasmtime_component_val_t::String(v.clone().into_bytes().into()) + } + Val::List(vec) => { + if let CType::List(ty) = ty { + wasmtime_component_val_t::List( + vec.iter() + .map(|v| CTypedVal(v, ty.deref()).try_into()) + .collect::>>()? + .into(), + ) + } else { + bail!("attempted to create a List for a {}", ty.desc()); + } + } + Val::Record(vec) => { + if let CType::Record(ty) = ty { + let mut field_vals: HashMap<&str, &Val> = + HashMap::from_iter(vec.iter().map(|f| (f.0.as_str(), &f.1))); + + wasmtime_component_val_t::Record( + ty.iter() + .map(|(field_name, field_type)| { + match field_vals.remove(field_name.as_str()) { + Some(v) => Ok(wasmtime_component_val_record_field_t { + name: field_name.clone().into_bytes().into(), + val: CTypedVal(v, field_type).try_into()?, + }), + None => bail!("missing field {} in record", field_name), + } + }) + .collect::>>()? + .into(), + ) + } else { + bail!("attempted to create a Record for a {}", ty.desc()); + } + } + Val::Tuple(vec) => { + if let CType::Tuple(ty) = ty { + wasmtime_component_val_t::Tuple( + vec.iter() + .zip(ty.iter()) + .map(|(v, ty)| CTypedVal(v, ty).try_into()) + .collect::>>()? + .into(), + ) + } else { + bail!("attempted to create a Tuple for a {}", ty.desc()); + } + } + Val::Variant(case, val) => { + if let CType::Variant(ty) = ty { + let index = ty + .iter() + .position(|c| &c.0 == case) + .context(format!("case {case} not found in type"))?; + ensure!( + val.is_some() == ty[index].1.is_some(), + "mismatched variant case {} : value is {}, but type is {}", + case, + if val.is_some() { "some" } else { "none" }, + ty[index].1.as_ref().map(|ty| ty.desc()).unwrap_or("none") + ); + wasmtime_component_val_t::Variant(wasmtime_component_val_variant_t { + discriminant: index as u32, + val: match val { + Some(val) => Some(Box::new( + CTypedVal(val.deref(), ty[index].1.as_ref().unwrap()).try_into()?, + )), + None => None, + }, + }) + } else { + bail!("attempted to create a Variant for a {}", ty.desc()); + } + } + Val::Enum(v) => { + if let CType::Enum(ty) = ty { + let index = ty + .iter() + .position(|s| s == v) + .context(format!("enum value {v} not found in type"))?; + wasmtime_component_val_t::Enum(wasmtime_component_val_enum_t { + discriminant: index as u32, + }) + } else { + bail!("attempted to create a Enum for a {}", ty.desc()); + } + } + Val::Option(val) => { + if let CType::Option(ty) = ty { + wasmtime_component_val_t::Option(match val { + Some(val) => Some(Box::new(CTypedVal(val.deref(), ty.deref()).try_into()?)), + None => None, + }) + } else { + bail!("attempted to create a Option for a {}", ty.desc()); + } + } + Val::Result(val) => { + if let CType::Result(ok_type, err_type) = ty { + wasmtime_component_val_t::Result(match val { + Ok(Some(ok)) => { + let ok_type = ok_type + .as_ref() + .context("some ok result found instead of none")?; + wasmtime_component_val_result_t { + value: Some(Box::new( + CTypedVal(ok.deref(), ok_type.deref()).try_into()?, + )), + error: false, + } + } + Ok(None) => { + ensure!( + ok_type.is_none(), + "none ok result found instead of {}", + ok_type.as_ref().unwrap().desc() + ); + wasmtime_component_val_result_t { + value: None, + error: false, + } + } + Err(Some(err)) => { + let err_type = err_type + .as_ref() + .context("some err result found instead of none")?; + wasmtime_component_val_result_t { + value: Some(Box::new( + CTypedVal(err.deref(), err_type.deref()).try_into()?, + )), + error: true, + } + } + Err(None) => { + ensure!( + err_type.is_none(), + "none err result found instead of {}", + err_type.as_ref().unwrap().desc() + ); + wasmtime_component_val_result_t { + value: None, + error: true, + } + } + }) + } else { + bail!("attempted to create a Result for a {}", ty.desc()); + } + } + Val::Flags(vec) => { + if let CType::Flags(ty) = ty { + let mapping: HashMap<_, _> = ty.iter().zip(0u32..).collect(); + let mut flags: wasmtime_component_val_flags_t = Vec::new().into(); + for name in vec.iter() { + let idx = mapping.get(name).context("expected valid name")?; + wasmtime_component_val_flags_set(&mut flags, *idx, true); + } + wasmtime_component_val_t::Flags(flags) + } else { + bail!("attempted to create a Flags for a {}", ty.desc()); + } + } + Val::Resource(_) => bail!("resources not supported"), + }) + } +} + +// a wasmtime_component_val_t and its associated wasmtime_component_type_t +struct CTypedCVal<'a>(&'a wasmtime_component_val_t, &'a CType); + +impl TryFrom> for Val { + type Error = anyhow::Error; + fn try_from(value: CTypedCVal) -> Result { + let (value, ty) = (value.0, value.1); + Ok(match value { + &wasmtime_component_val_t::Bool(b) => { + ensure_type!(ty, CType::Bool); + Val::Bool(b) + } + &wasmtime_component_val_t::S8(v) => { + ensure_type!(ty, CType::S8); + Val::S8(v) + } + &wasmtime_component_val_t::U8(v) => { + ensure_type!(ty, CType::U8); + Val::U8(v) + } + &wasmtime_component_val_t::S16(v) => { + ensure_type!(ty, CType::S16); + Val::S16(v) + } + &wasmtime_component_val_t::U16(v) => { + ensure_type!(ty, CType::U16); + Val::U16(v) + } + &wasmtime_component_val_t::S32(v) => { + ensure_type!(ty, CType::S32); + Val::S32(v) + } + &wasmtime_component_val_t::U32(v) => { + ensure_type!(ty, CType::U32); + Val::U32(v) + } + &wasmtime_component_val_t::S64(v) => { + ensure_type!(ty, CType::S64); + Val::S64(v) + } + &wasmtime_component_val_t::U64(v) => { + ensure_type!(ty, CType::U64); + Val::U64(v) + } + &wasmtime_component_val_t::F32(v) => { + ensure_type!(ty, CType::F32); + Val::Float32(v) + } + &wasmtime_component_val_t::F64(v) => { + ensure_type!(ty, CType::F64); + Val::Float64(v) + } + &wasmtime_component_val_t::Char(v) => { + ensure_type!(ty, CType::Char); + Val::Char(v) + } + wasmtime_component_val_t::String(v) => { + ensure_type!(ty, CType::String); + Val::String(String::from_utf8(v.as_slice().to_vec())?) + } + wasmtime_component_val_t::List(v) => { + if let CType::List(ty) = ty { + Val::List( + v.as_slice() + .iter() + .map(|v| CTypedCVal(v, ty.deref()).try_into()) + .collect::>>()?, + ) + } else { + bail!("attempted to create a list for a {}", ty.desc()); + } + } + wasmtime_component_val_t::Record(v) => { + if let CType::Record(ty) = ty { + let mut field_vals: HashMap<&[u8], &wasmtime_component_val_t> = + HashMap::from_iter( + v.as_slice().iter().map(|f| (f.name.as_slice(), &f.val)), + ); + Val::Record( + ty.iter() + .map(|tyf| { + if let Some(v) = field_vals.remove(tyf.0.as_bytes()) { + Ok((tyf.0.clone(), CTypedCVal(v, &tyf.1).try_into()?)) + } else { + bail!("record missing field: {}", tyf.0); + } + }) + .collect::>>()?, + ) + } else { + bail!("attempted to create a record for a {}", ty.desc()); + } + } + wasmtime_component_val_t::Tuple(v) => { + if let CType::Tuple(ty) = ty { + Val::Tuple( + ty.iter() + .zip(v.as_slice().iter()) + .map(|(ty, v)| CTypedCVal(v, ty).try_into()) + .collect::>>()?, + ) + } else { + bail!("attempted to create a tuple for a {}", ty.desc()); + } + } + wasmtime_component_val_t::Variant(v) => { + if let CType::Variant(ty) = ty { + let index = v.discriminant as usize; + ensure!(index < ty.len(), "variant index outside range"); + let case = &ty[index]; + let case_name = case.0.clone(); + ensure!( + case.1.is_some() == v.val.is_some(), + "variant type mismatch for case {}: {} instead of {}", + case_name, + if v.val.is_some() { "some" } else { "none" }, + case.1.as_ref().map(|ty| ty.desc()).unwrap_or("none") + ); + if let (Some(t), Some(v)) = (&case.1, &v.val) { + let v = CTypedCVal(v.as_ref(), t.deref()).try_into()?; + Val::Variant(case_name, Some(Box::new(v))) + } else { + Val::Variant(case_name, None) + } + } else { + bail!("attempted to create a variant for a {}", ty.desc()); + } + } + wasmtime_component_val_t::Enum(v) => { + if let CType::Enum(ty) = ty { + let index = v.discriminant as usize; + ensure!(index < ty.as_slice().len(), "variant index outside range"); + Val::Enum(ty[index].clone()) + } else { + bail!("attempted to create an enum for a {}", ty.desc()); + } + } + wasmtime_component_val_t::Option(v) => { + if let CType::Option(ty) = ty { + Val::Option(match v { + Some(v) => Some(Box::new(CTypedCVal(v.as_ref(), ty.deref()).try_into()?)), + None => None, + }) + } else { + bail!("attempted to create an option for a {}", ty.desc()); + } + } + wasmtime_component_val_t::Result(v) => { + if let CType::Result(ok_ty, err_ty) = ty { + if v.error { + match &v.value { + Some(v) => { + let ty = err_ty.as_deref().context("expected err type")?; + Val::Result(Err(Some(Box::new( + CTypedCVal(v.as_ref(), ty).try_into()?, + )))) + } + None => { + ensure!(err_ty.is_none(), "expected no err type"); + Val::Result(Err(None)) + } + } + } else { + match &v.value { + Some(v) => { + let ty = ok_ty.as_deref().context("expected ok type")?; + Val::Result(Ok(Some(Box::new( + CTypedCVal(v.as_ref(), ty).try_into()?, + )))) + } + None => { + ensure!(ok_ty.is_none(), "expected no ok type"); + Val::Result(Ok(None)) + } + } + } + } else { + bail!("attempted to create a result for a {}", ty.desc()); + } + } + wasmtime_component_val_t::Flags(flags) => { + if let CType::Flags(ty) = ty { + let mut set = Vec::new(); + for (idx, name) in ty.iter().enumerate() { + if wasmtime_component_val_flags_test(&flags, idx as u32) { + set.push(name.clone()); + } + } + Val::Flags(set) + } else { + bail!("attempted to create a flags for a {}", ty.desc()); + } + } + }) + } +} + +impl TryFrom<(&Val, &Type)> for wasmtime_component_val_t { + type Error = anyhow::Error; + + fn try_from((value, ty): (&Val, &Type)) -> Result { + Ok(match value { + Val::Bool(v) => wasmtime_component_val_t::Bool(*v), + Val::S8(v) => wasmtime_component_val_t::S8(*v), + Val::U8(v) => wasmtime_component_val_t::U8(*v), + Val::S16(v) => wasmtime_component_val_t::S16(*v), + Val::U16(v) => wasmtime_component_val_t::U16(*v), + Val::S32(v) => wasmtime_component_val_t::S32(*v), + Val::U32(v) => wasmtime_component_val_t::U32(*v), + Val::S64(v) => wasmtime_component_val_t::S64(*v), + Val::U64(v) => wasmtime_component_val_t::U64(*v), + Val::Float32(v) => wasmtime_component_val_t::F32(*v), + Val::Float64(v) => wasmtime_component_val_t::F64(*v), + Val::Char(v) => wasmtime_component_val_t::Char(*v), + Val::String(v) => wasmtime_component_val_t::String(v.clone().into_bytes().into()), + Val::List(v) => { + if let Type::List(ty) = ty { + let v = v + .iter() + .map(|v| (v, &ty.ty()).try_into()) + .collect::>>()?; + wasmtime_component_val_t::List(v.into()) + } else { + bail!("attempted to create a {} from a list", ty.desc()); + } + } + Val::Record(v) => { + if let Type::Record(ty) = ty { + let fields_types: HashMap = + HashMap::from_iter(ty.fields().map(|f| (f.name.to_string(), f.ty))); + let v = v + .iter() + .map(|(name, v)| { + if let Some(ty) = fields_types.get(name.as_str()) { + Ok(wasmtime_component_val_record_field_t { + name: name.clone().into_bytes().into(), + val: (v, ty).try_into()?, + }) + } else { + bail!("field {} not found in record type", name); + } + }) + .collect::>>()?; + wasmtime_component_val_t::Record(v.into()) + } else { + bail!("attempted to create a {} from a record", ty.desc()); + } + } + Val::Tuple(v) => { + if let Type::Tuple(ty) = ty { + let elem_types = ty.types().collect::>(); + if v.len() != elem_types.len() { + bail!( + "attempted to create a size {} tuple from a size {} tuple", + elem_types.len(), + v.len() + ); + } + let v = v + .iter() + .zip(elem_types.iter()) + .map(|v| v.try_into()) + .collect::>>()?; + wasmtime_component_val_t::Tuple(v.into()) + } else { + bail!("attempted to create a {} from a tuple", ty.desc()); + } + } + Val::Variant(discriminant, v) => { + if let Type::Variant(ty) = ty { + let (index, case) = ty + .cases() + .enumerate() + .find(|(_, v)| v.name == discriminant) + .map(|(idx, case)| (idx as u32, case)) + .context("expected valid discriminant")?; + let val = match v { + Some(v) => { + if let Some(ty) = &case.ty { + Some(Box::new((v.as_ref(), ty).try_into()?)) + } else { + bail!("attempted to create a None Variant from a Some variant"); + } + } + None => None, + }; + wasmtime_component_val_t::Variant(wasmtime_component_val_variant_t { + discriminant: index, + val, + }) + } else { + bail!("attempted to create a {} from a variant", ty.desc()); + } + } + Val::Enum(discriminant) => { + if let Type::Enum(ty) = ty { + let index = ty + .names() + .zip(0u32..) + .find(|(n, _)| *n == discriminant) + .map(|(_, idx)| idx) + .context("expected valid discriminant")?; + wasmtime_component_val_t::Enum(wasmtime_component_val_enum_t { + discriminant: index, + }) + } else { + bail!("attempted to create a {} from an enum", ty.desc()); + } + } + Val::Option(v) => { + if let Type::Option(ty) = ty { + wasmtime_component_val_t::Option(match v { + Some(v) => Some(Box::new((v.as_ref(), &ty.ty()).try_into()?)), + None => None, + }) + } else { + bail!("attempted to create a {} from an option", ty.desc()); + } + } + Val::Result(v) => { + if let Type::Result(ty) = ty { + let (error, value) = match v { + Err(v) => { + let value = match v { + Some(v) => { + if let Some(ty) = ty.err() { + Some(Box::new((v.as_ref(), &ty).try_into()?)) + } else { + bail!( + "attempted to create a None result from a Some result" + ); + } + } + None => None, + }; + (true, value) + } + Ok(v) => { + let value = match v { + Some(v) => { + if let Some(ty) = ty.ok() { + Some(Box::new((v.as_ref(), &ty).try_into()?)) + } else { + bail!( + "attempted to create a None result from a Some result" + ); + } + } + None => None, + }; + (false, value) + } + }; + wasmtime_component_val_t::Result(wasmtime_component_val_result_t { + value, + error, + }) + } else { + bail!("attempted to create a {} from a result", ty.desc()); + } + } + Val::Flags(v) => { + if let Type::Flags(ty) = ty { + let mapping: HashMap<_, _> = ty.names().zip(0u32..).collect(); + let mut flags: wasmtime_component_val_flags_t = Vec::new().into(); + for name in v { + let idx = mapping.get(name.as_str()).context("expected valid name")?; + wasmtime_component_val_flags_set(&mut flags, *idx, true); + } + wasmtime_component_val_t::Flags(flags) + } else { + bail!("attempted to create a {} from a flags", ty.desc()); + } + } + Val::Resource(_) => bail!("resource types are unimplemented"), + }) + } +} + +impl Default for wasmtime_component_val_t { + fn default() -> Self { + Self::Bool(false) + } +} + +#[no_mangle] +pub extern "C" fn wasmtime_component_val_new() -> Box { + Box::new(wasmtime_component_val_t::default()) +} + +#[no_mangle] +pub unsafe extern "C" fn wasmtime_component_val_delete(_: Box) {} + +pub type wasmtime_component_kind_t = u8; +pub const WASMTIME_COMPONENT_KIND_BOOL: wasmtime_component_kind_t = 0; +pub const WASMTIME_COMPONENT_KIND_S8: wasmtime_component_kind_t = 1; +pub const WASMTIME_COMPONENT_KIND_U8: wasmtime_component_kind_t = 2; +pub const WASMTIME_COMPONENT_KIND_S16: wasmtime_component_kind_t = 3; +pub const WASMTIME_COMPONENT_KIND_U16: wasmtime_component_kind_t = 4; +pub const WASMTIME_COMPONENT_KIND_S32: wasmtime_component_kind_t = 5; +pub const WASMTIME_COMPONENT_KIND_U32: wasmtime_component_kind_t = 6; +pub const WASMTIME_COMPONENT_KIND_S64: wasmtime_component_kind_t = 7; +pub const WASMTIME_COMPONENT_KIND_U64: wasmtime_component_kind_t = 8; +pub const WASMTIME_COMPONENT_KIND_F32: wasmtime_component_kind_t = 9; +pub const WASMTIME_COMPONENT_KIND_F64: wasmtime_component_kind_t = 10; +pub const WASMTIME_COMPONENT_KIND_CHAR: wasmtime_component_kind_t = 11; +pub const WASMTIME_COMPONENT_KIND_STRING: wasmtime_component_kind_t = 12; +pub const WASMTIME_COMPONENT_KIND_LIST: wasmtime_component_kind_t = 13; +pub const WASMTIME_COMPONENT_KIND_RECORD: wasmtime_component_kind_t = 14; +pub const WASMTIME_COMPONENT_KIND_TUPLE: wasmtime_component_kind_t = 15; +pub const WASMTIME_COMPONENT_KIND_VARIANT: wasmtime_component_kind_t = 16; +pub const WASMTIME_COMPONENT_KIND_ENUM: wasmtime_component_kind_t = 17; +pub const WASMTIME_COMPONENT_KIND_OPTION: wasmtime_component_kind_t = 18; +pub const WASMTIME_COMPONENT_KIND_RESULT: wasmtime_component_kind_t = 19; +pub const WASMTIME_COMPONENT_KIND_FLAGS: wasmtime_component_kind_t = 20; + +#[repr(C)] +#[derive(Clone)] +pub struct wasmtime_component_type_field_t { + pub name: wasm_name_t, + pub ty: Option, +} + +impl Default for wasmtime_component_type_field_t { + fn default() -> Self { + Self { + name: Vec::new().into(), + ty: Default::default(), + } + } +} + +#[repr(C)] +#[derive(Clone)] +pub struct wasmtime_component_type_result_t { + pub ok_ty: Option>, + pub err_ty: Option>, +} + +declare_vecs! { + ( + name: wasmtime_component_type_vec_t, + ty: wasmtime_component_type_t, + new: wasmtime_component_type_vec_new, + empty: wasmtime_component_type_vec_new_empty, + uninit: wasmtime_component_type_vec_new_uninitialized, + copy: wasmtime_component_type_vec_copy, + delete: wasmtime_component_type_vec_delete, + ) + ( + name: wasmtime_component_type_field_vec_t, + ty: wasmtime_component_type_field_t, + new: wasmtime_component_type_field_vec_new, + empty: wasmtime_component_type_field_vec_new_empty, + uninit: wasmtime_component_type_field_vec_new_uninitialized, + copy: wasmtime_component_type_field_vec_copy, + delete: wasmtime_component_type_field_vec_delete, + ) + ( + name: wasmtime_component_string_vec_t, + ty: wasmtime_component_string_t, + new: wasmtime_component_string_vec_new, + empty: wasmtime_component_string_vec_new_empty, + uninit: wasmtime_component_string_vec_new_uninitialized, + copy: wasmtime_component_string_vec_copy, + delete: wasmtime_component_string_vec_delete, + ) +} + +#[repr(C, u8)] +#[derive(Clone)] +pub enum wasmtime_component_type_t { + Bool, + S8, + U8, + S16, + U16, + S32, + U32, + S64, + U64, + F32, + F64, + Char, + String, + List(Box), + Record(wasmtime_component_type_field_vec_t), + Tuple(wasmtime_component_type_vec_t), + Variant(wasmtime_component_type_field_vec_t), + Enum(wasmtime_component_string_vec_t), + Option(Box), + Result(wasmtime_component_type_result_t), + Flags(wasmtime_component_string_vec_t), +} + +impl Default for wasmtime_component_type_t { + fn default() -> Self { + Self::Bool + } +} + +#[no_mangle] +pub extern "C" fn wasmtime_component_type_new() -> Box { + Box::new(wasmtime_component_type_t::Bool) +} + +#[no_mangle] +pub unsafe extern "C" fn wasmtime_component_type_delete(_: Box) {} + +#[derive(Clone)] +pub enum CType { + Bool, + S8, + U8, + S16, + U16, + S32, + U32, + S64, + U64, + F32, + F64, + Char, + String, + List(Box), + Record(Vec<(String, CType)>), + Tuple(Vec), + Variant(Vec<(String, Option>)>), + Enum(Vec), + Option(Box), + Result(Option>, Option>), + Flags(Vec), +} + +impl TryFrom<&wasmtime_component_type_t> for CType { + type Error = anyhow::Error; + + fn try_from(ty: &wasmtime_component_type_t) -> Result { + Ok(match ty { + wasmtime_component_type_t::Bool => CType::Bool, + wasmtime_component_type_t::S8 => CType::S8, + wasmtime_component_type_t::U8 => CType::U8, + wasmtime_component_type_t::S16 => CType::S16, + wasmtime_component_type_t::U16 => CType::U16, + wasmtime_component_type_t::S32 => CType::S32, + wasmtime_component_type_t::U32 => CType::U32, + wasmtime_component_type_t::S64 => CType::S64, + wasmtime_component_type_t::U64 => CType::U64, + wasmtime_component_type_t::F32 => CType::F32, + wasmtime_component_type_t::F64 => CType::F64, + wasmtime_component_type_t::Char => CType::Char, + wasmtime_component_type_t::String => CType::String, + wasmtime_component_type_t::List(ty) => CType::List(Box::new(ty.as_ref().try_into()?)), + wasmtime_component_type_t::Record(fields) => CType::Record( + fields + .as_slice() + .iter() + .map(|field| { + let field_name = String::from_utf8(field.name.as_slice().to_vec())?; + let field_type = match &field.ty { + Some(ty) => ty.try_into()?, + None => bail!("missing type of field {} in record", field_name), + }; + Ok((field_name, field_type)) + }) + .collect::>>()?, + ), + wasmtime_component_type_t::Tuple(types) => CType::Tuple( + types + .as_slice() + .iter() + .map(|ty| ty.try_into()) + .collect::>>()?, + ), + wasmtime_component_type_t::Variant(cases) => CType::Variant( + cases + .as_slice() + .iter() + .map(|case| { + let case_name = String::from_utf8(case.name.as_slice().to_vec())?; + let case_type = match &case.ty { + Some(ty) => Some(Box::new(ty.try_into()?)), + None => None, + }; + Ok((case_name, case_type)) + }) + .collect::>>()?, + ), + wasmtime_component_type_t::Enum(enums) => CType::Enum( + enums + .as_slice() + .iter() + .map(|s| Ok(String::from_utf8(s.as_slice().to_vec())?)) + .collect::>>()?, + ), + wasmtime_component_type_t::Option(ty) => { + CType::Option(Box::new(ty.as_ref().try_into()?)) + } + wasmtime_component_type_t::Result(wasmtime_component_type_result_t { + ok_ty, + err_ty, + }) => CType::Result( + match ok_ty { + Some(ty) => Some(Box::new(ty.as_ref().try_into()?)), + None => None, + }, + match err_ty { + Some(ty) => Some(Box::new(ty.as_ref().try_into()?)), + None => None, + }, + ), + wasmtime_component_type_t::Flags(flags) => CType::Flags( + flags + .as_slice() + .iter() + .map(|s| Ok(String::from_utf8(s.as_slice().to_vec())?)) + .collect::>>()?, + ), + }) + } +} + +impl CType { + /// Return a string description of this type + fn desc(&self) -> &'static str { + match self { + CType::Bool => "bool", + CType::S8 => "s8", + CType::U8 => "u8", + CType::S16 => "s16", + CType::U16 => "u16", + CType::S32 => "s32", + CType::U32 => "u32", + CType::S64 => "s64", + CType::U64 => "u64", + CType::F32 => "f32", + CType::F64 => "f64", + CType::Char => "char", + CType::String => "string", + CType::List(_) => "list", + CType::Record(_) => "record", + CType::Tuple(_) => "tuple", + CType::Variant(_) => "variant", + CType::Enum(_) => "enum", + CType::Option(_) => "option", + CType::Result(_, _) => "result", + CType::Flags(_) => "flags", + } + } + + fn default_cval(&self) -> wasmtime_component_val_t { + match self { + CType::Bool => wasmtime_component_val_t::Bool(false), + CType::S8 => wasmtime_component_val_t::S8(0), + CType::U8 => wasmtime_component_val_t::U8(0), + CType::S16 => wasmtime_component_val_t::S16(0), + CType::U16 => wasmtime_component_val_t::U16(0), + CType::S32 => wasmtime_component_val_t::S32(0), + CType::U32 => wasmtime_component_val_t::U32(0), + CType::S64 => wasmtime_component_val_t::S64(0), + CType::U64 => wasmtime_component_val_t::U64(0), + CType::F32 => wasmtime_component_val_t::F32(0.0), + CType::F64 => wasmtime_component_val_t::F64(0.0), + CType::Char => wasmtime_component_val_t::Char('\0'), + CType::String => { + wasmtime_component_val_t::String(wasmtime_component_string_t::default()) + } + CType::List(_) => { + wasmtime_component_val_t::List(wasmtime_component_val_vec_t::default()) + } + CType::Record(fields) => wasmtime_component_val_t::Record( + fields + .iter() + .map(|(name, ty)| wasmtime_component_val_record_field_t { + name: name.clone().into_bytes().into(), + val: ty.default_cval(), + }) + .collect::>() + .into(), + ), + CType::Tuple(tuple) => wasmtime_component_val_t::Tuple( + tuple + .iter() + .map(|ty| ty.default_cval()) + .collect::>() + .into(), + ), + CType::Variant(cases) => { + wasmtime_component_val_t::Variant(wasmtime_component_val_variant_t { + discriminant: 0, + val: match &cases[0].1 { + Some(ty) => Some(Box::new(ty.default_cval())), + None => None, + }, + }) + } + CType::Enum(_) => { + wasmtime_component_val_t::Enum(wasmtime_component_val_enum_t { discriminant: 0 }) + } + CType::Option(_) => wasmtime_component_val_t::Option(None), + CType::Result(_, _) => { + wasmtime_component_val_t::Result(wasmtime_component_val_result_t { + value: None, + error: false, + }) + } + CType::Flags(_) => { + wasmtime_component_val_t::Flags(wasmtime_component_val_flags_t::default()) + } + } + } +} + +#[repr(transparent)] +pub struct wasmtime_component_t { + component: Component, +} + +#[no_mangle] +pub unsafe extern "C" fn wasmtime_component_from_binary( + engine: &wasm_engine_t, + bytes: *const u8, + len: usize, + out: &mut *mut wasmtime_component_t, +) -> Option> { + let bytes = crate::slice_from_raw_parts(bytes, len); + handle_result(Component::from_binary(&engine.engine, bytes), |component| { + *out = Box::into_raw(Box::new(wasmtime_component_t { component })); + }) +} + +#[no_mangle] +pub unsafe extern "C" fn wasmtime_component_delete(_: Box) {} + +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>; + +struct HostFuncDefinition { + path: Vec, + name: String, + params_types: Vec, + outputs_types: Vec, + callback: wasmtime_component_func_callback_t, + data: *mut c_void, + finalizer: Option, +} + +#[repr(C)] +pub struct wasmtime_component_linker_t { + linker: Linker, + is_built: bool, + functions: Vec, +} + +#[no_mangle] +pub extern "C" fn wasmtime_component_linker_new( + engine: &wasm_engine_t, +) -> Box { + Box::new(wasmtime_component_linker_t { + linker: Linker::new(&engine.engine), + is_built: false, + functions: Vec::new(), + }) +} + +#[no_mangle] +pub unsafe extern "C" fn wasmtime_component_linker_delete(_: Box) {} + +fn to_ctype_vec(buf: *mut wasmtime_component_type_t, len: usize) -> Result> { + if len == 0 { + return Ok(Vec::new()); + } + let v = unsafe { crate::slice_from_raw_parts(buf, len) }; + v.iter().map(|t| t.try_into()).collect::>>() +} + +#[no_mangle] +pub unsafe extern "C" fn wasmtime_component_linker_define_func( + linker: &mut wasmtime_component_linker_t, + path_buf: *const u8, + path_len: usize, + name_buf: *const u8, + name_len: usize, + params_types_buf: *mut wasmtime_component_type_t, + params_types_len: usize, + outputs_types_buf: *mut wasmtime_component_type_t, + outputs_types_len: usize, + callback: wasmtime_component_func_callback_t, + data: *mut c_void, + finalizer: Option, +) -> Option> { + let path = to_str!(path_buf, path_len) + .split('.') + .filter(|s| s.len() > 0) + .map(|s| s.to_string()) + .collect::>(); + let name = to_str!(name_buf, name_len).to_string(); + let params_types = match to_ctype_vec(params_types_buf, params_types_len) { + Err(err) => return Some(Box::new(wasmtime_error_t::from(err))), + Ok(p) => p, + }; + let outputs_types = match to_ctype_vec(outputs_types_buf, outputs_types_len) { + Err(err) => return Some(Box::new(wasmtime_error_t::from(err))), + Ok(p) => p, + }; + + linker.functions.push(HostFuncDefinition { + path, + name, + params_types, + outputs_types, + callback, + data, + finalizer, + }); + None +} + +fn build_closure( + function: &HostFuncDefinition, +) -> impl Fn(WasmtimeStoreContextMut<'_>, &[Val], &mut [Val]) -> Result<()> { + let func = function.callback; + let params_types = function.params_types.clone(); + let outputs_types = function.outputs_types.clone(); + let foreign = crate::ForeignData { + data: function.data, + finalizer: function.finalizer, + }; + move |context, parameters, outputs| { + let _ = &foreign; + let _ = ¶ms_types; + let _ = &outputs_types; + let mut params = Vec::new(); + for param in parameters.iter().zip(params_types.iter()) { + params.push(CTypedVal(param.0, param.1).try_into()?); + } + let mut outs = Vec::new(); + for output_type in outputs_types.iter() { + outs.push(output_type.default_cval()); + } + let res = func( + foreign.data, + context, + params.as_ptr(), + params.len(), + outs.as_mut_ptr(), + outs.len(), + ); + match res { + None => { + for (i, (output, output_type)) in outs.iter().zip(outputs_types.iter()).enumerate() + { + outputs[i] = CTypedCVal(output, output_type).try_into()?; + } + Ok(()) + } + Some(trap) => Err(trap.error), + } + } +} + +#[no_mangle] +pub extern "C" fn wasmtime_component_linker_build( + linker: &mut wasmtime_component_linker_t, +) -> Option> { + if linker.is_built { + return Some(Box::new(wasmtime_error_t::from(anyhow!( + "cannot build an already built linker" + )))); + } + + struct InstanceTree { + children: HashMap, + functions: Vec, + } + + impl InstanceTree { + fn insert(&mut self, depth: usize, function: HostFuncDefinition) { + if function.path.len() == depth { + self.functions.push(function); + } else { + let child = self + .children + .entry(function.path[depth].to_string()) + .or_insert_with(|| InstanceTree { + children: HashMap::new(), + functions: Vec::new(), + }); + child.insert(depth + 1, function); + } + } + fn build(&self, mut instance: LinkerInstance) -> Result<()> { + for function in self.functions.iter() { + instance.func_new(&function.name, build_closure(function))?; + } + for (name, child) in self.children.iter() { + let child_instance = instance.instance(&name)?; + child.build(child_instance)?; + } + Ok(()) + } + } + + let mut root = InstanceTree { + children: HashMap::new(), + functions: Vec::new(), + }; + for function in linker.functions.drain(..) { + root.insert(0, function); + } + match root.build(linker.linker.root()) { + Ok(()) => { + linker.is_built = true; + None + } + Err(err) => Some(Box::new(wasmtime_error_t::from(anyhow!(err)))), + } +} + +#[no_mangle] +pub extern "C" fn wasmtime_component_linker_instantiate( + linker: &wasmtime_component_linker_t, + store: WasmtimeStoreContextMut<'_>, + component: &wasmtime_component_t, + out: &mut *mut wasmtime_component_instance_t, +) -> Option> { + if !linker.is_built && !linker.functions.is_empty() { + return Some(Box::new(wasmtime_error_t::from(anyhow!( + "cannot instantiate with a linker not built" + )))); + } + match linker.linker.instantiate(store, &component.component) { + Ok(instance) => { + *out = Box::into_raw(Box::new(wasmtime_component_instance_t { instance })); + None + } + Err(e) => Some(Box::new(wasmtime_error_t::from(e))), + } +} + +#[repr(transparent)] +pub struct wasmtime_component_instance_t { + instance: Instance, +} + +#[no_mangle] +pub unsafe extern "C" fn wasmtime_component_instance_get_func( + instance: &wasmtime_component_instance_t, + context: WasmtimeStoreContextMut<'_>, + name: *const u8, + len: usize, + item: &mut *mut wasmtime_component_func_t, +) -> bool { + let name = crate::slice_from_raw_parts(name, len); + let name = match std::str::from_utf8(name) { + Ok(name) => name, + Err(_) => return false, + }; + let func = instance.instance.get_func(context, name); + if let Some(func) = func { + *item = Box::into_raw(Box::new(wasmtime_component_func_t { func })); + } + func.is_some() +} + +#[repr(transparent)] +pub struct wasmtime_component_func_t { + func: Func, +} + +fn call_func( + func: &wasmtime_component_func_t, + mut context: WasmtimeStoreContextMut<'_>, + raw_params: &[wasmtime_component_val_t], + raw_results: &mut [wasmtime_component_val_t], +) -> Result<()> { + let params_types = func.func.params(context.as_context()); + if params_types.len() != raw_params.len() { + bail!( + "called with {} parameters instead of the expected {}", + raw_params.len(), + params_types.len() + ); + } + let results_types = func.func.results(context.as_context()); + if results_types.len() != raw_results.len() { + bail!( + "returns {} results instead of the expected {}", + raw_results.len(), + results_types.len() + ); + } + let params = func + .func + .params(context.as_context()) + .iter() + .zip(raw_params.iter()) + .map(|(ty, v)| TypedCVal(v, &ty.1).try_into()) + .collect::>>()?; + let mut results = vec![Val::Bool(false); raw_results.len()]; + func.func + .call(context.as_context_mut(), ¶ms, &mut results)?; + func.func.post_return(context)?; + for (i, (ty, r)) in results_types.iter().zip(results.iter()).enumerate() { + raw_results[i] = (r, ty).try_into()?; + } + Ok(()) +} + +#[no_mangle] +pub unsafe extern "C" fn wasmtime_component_func_call( + func: &wasmtime_component_func_t, + context: WasmtimeStoreContextMut<'_>, + params: *const wasmtime_component_val_t, + params_len: usize, + results: *mut wasmtime_component_val_t, + results_len: usize, + out_trap: &mut *mut wasm_trap_t, +) -> Option> { + let raw_params = crate::slice_from_raw_parts(params, params_len); + let mut raw_results = crate::slice_from_raw_parts_mut(results, results_len); + match call_func(func, context, &raw_params, &mut raw_results) { + Ok(_) => None, + Err(e) => handle_call_error(e, out_trap), + } +} + +#[cfg(test)] +mod tests { + use crate::{ + wasmtime_component_val_flags_set, wasmtime_component_val_flags_t, + wasmtime_component_val_flags_test, + }; + + #[test] + fn bit_fiddling() { + let mut flags: wasmtime_component_val_flags_t = Vec::new().into(); + wasmtime_component_val_flags_set(&mut flags, 1, true); + assert!(wasmtime_component_val_flags_test(&flags, 1)); + assert!(!wasmtime_component_val_flags_test(&flags, 0)); + wasmtime_component_val_flags_set(&mut flags, 260, true); + assert!(wasmtime_component_val_flags_test(&flags, 260)); + assert!(!wasmtime_component_val_flags_test(&flags, 261)); + assert!(!wasmtime_component_val_flags_test(&flags, 259)); + assert!(wasmtime_component_val_flags_test(&flags, 1)); + assert!(!wasmtime_component_val_flags_test(&flags, 0)); + } +} diff --git a/crates/c-api/src/lib.rs b/crates/c-api/src/lib.rs index 9537407f2e6f..5b78fe15ad34 100644 --- a/crates/c-api/src/lib.rs +++ b/crates/c-api/src/lib.rs @@ -70,6 +70,11 @@ mod wat2wasm; #[cfg(feature = "wat")] pub use crate::wat2wasm::*; +#[cfg(feature = "component-model")] +mod component; +#[cfg(feature = "component-model")] +pub use crate::component::*; + /// Initialize a `MaybeUninit` /// /// TODO: Replace calls to this function with @@ -121,6 +126,20 @@ unsafe fn slice_from_raw_parts_mut<'a, T>(ptr: *mut T, len: usize) -> &'a mut [T } } +#[cfg(any(feature = "async", feature = "component-model"))] +pub(crate) fn handle_call_error( + err: wasmtime::Error, + trap_ret: &mut *mut wasm_trap_t, +) -> Option> { + use wasmtime::Trap; + if err.is::() { + *trap_ret = Box::into_raw(Box::new(wasm_trap_t::new(err))); + None + } else { + Some(Box::new(wasmtime_error_t::from(err))) + } +} + pub(crate) fn abort(name: &str) -> ! { eprintln!("`{name}` is not implemented"); std::process::abort(); diff --git a/crates/c-api/src/vec.rs b/crates/c-api/src/vec.rs index 7a5aae734ffa..b2765423699d 100644 --- a/crates/c-api/src/vec.rs +++ b/crates/c-api/src/vec.rs @@ -103,6 +103,15 @@ macro_rules! declare_vecs { } } + impl$(<$lt>)? Default for $name $(<$lt>)? { + fn default() -> Self { + Self { + size: 0, + data: ptr::null_mut() + } + } + } + #[no_mangle] pub extern "C" fn $empty(out: &mut $name) { out.size = 0; @@ -249,3 +258,5 @@ declare_vecs! { delete: wasm_extern_vec_delete, ) } + +pub(crate) use declare_vecs; diff --git a/crates/wasmtime/src/runtime/component/types.rs b/crates/wasmtime/src/runtime/component/types.rs index 0d63bd664625..20964dfb1651 100644 --- a/crates/wasmtime/src/runtime/component/types.rs +++ b/crates/wasmtime/src/runtime/component/types.rs @@ -663,7 +663,8 @@ impl Type { } } - fn desc(&self) -> &'static str { + /// Return a string description of this type + pub fn desc(&self) -> &'static str { match self { Type::Bool => "bool", Type::S8 => "s8", diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 84a02e46fb56..1f4860a17dc3 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) @@ -65,6 +66,9 @@ create_rust_wasm(fib-debug wasm32-unknown-unknown) create_rust_wasm(tokio wasm32-wasip1) create_rust_wasm(wasi wasm32-wasip1) create_rust_wasm(component wasm32-unknown-unknown) +# the C test for component needs a component binary, not a plain module +# this is done using the rust example to do the conversion +execute_process(COMMAND cargo run --example component copy WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/..) create_rust_test(epochs) create_rust_test(externref) create_rust_test(fib-debug) 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..7813e252a7ed --- /dev/null +++ b/examples/component/main.c @@ -0,0 +1,218 @@ +#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"); + // 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`, here created with a secondary usage of rust test + FILE *file = + fopen("target/wasm32-unknown-unknown/debug/guest-component.wasm", "rb"); + if (!file) { + printf("> Error opening component!\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 component!\n"); + return 1; + } + fclose(file); + + // Compile. + 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..5bc346fa58f6 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,33 +36,52 @@ 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<()> { - // Create an engine with the component model enabled (disabled by default). - let engine = Engine::new(Config::new().wasm_component_model(true))?; - // NOTE: The wasm32-unknown-unknown target is used here for simplicity, real world use cases // should probably use the wasm32-wasip1 target, and enable wasi preview2 within the component // model. - let component = convert_to_component("target/wasm32-unknown-unknown/debug/guest.wasm")?; + let module_path = "target/wasm32-unknown-unknown/debug/guest.wasm"; + let component = convert_to_component(module_path)?; + + if std::env::args().len() > 1 { + // if called with an argument, just write the component binary + // this is useful for the c-api test, which doesn't expose wit_component::ComponentEncoder + let component_path = module_path.replace("guest.wasm", "guest-component.wasm"); + println!("{} bytes written to {}", component.len(), component_path); + fs::write(&component_path, &component)?; + return Ok(()); + } + + // Create an engine with the component model enabled (disabled by default). + let engine = Engine::new(Config::new().wasm_component_model(true))?; - // 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)), + } } }