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..99caaaed18ab --- /dev/null +++ b/crates/c-api/include/wasmtime/component.h @@ -0,0 +1,199 @@ +/** + * The component model + * + * TODO: Write some more documentation here like in the Rust API. + * + */ + +#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) + +// The tag part of wasmtime_component_val_t that specifies what variant is +// populated in wasmtime_component_val_payload_t. +typedef uint8_t wasmtime_component_kind_t; + +#define WASMTIME_COMPONENT_KIND_BOOL 0 +#define WASMTIME_COMPONENT_KIND_S8 1 +#define WASMTIME_COMPONENT_KIND_U8 2 +#define WASMTIME_COMPONENT_KIND_S16 3 +#define WASMTIME_COMPONENT_KIND_U16 4 +#define WASMTIME_COMPONENT_KIND_S32 5 +#define WASMTIME_COMPONENT_KIND_U32 6 +#define WASMTIME_COMPONENT_KIND_S64 7 +#define WASMTIME_COMPONENT_KIND_U64 8 +#define WASMTIME_COMPONENT_KIND_F32 9 +#define WASMTIME_COMPONENT_KIND_F64 10 +#define WASMTIME_COMPONENT_KIND_CHAR 11 +#define WASMTIME_COMPONENT_KIND_STRING 12 +#define WASMTIME_COMPONENT_KIND_LIST 13 +#define WASMTIME_COMPONENT_KIND_RECORD 14 +#define WASMTIME_COMPONENT_KIND_TUPLE 15 +#define WASMTIME_COMPONENT_KIND_VARIANT 16 +#define WASMTIME_COMPONENT_KIND_ENUM 17 +#define WASMTIME_COMPONENT_KIND_OPTION 18 +#define WASMTIME_COMPONENT_KIND_RESULT 19 +#define WASMTIME_COMPONENT_KIND_FLAGS 20 + +typedef struct wasmtime_component_val_t wasmtime_component_val_t; +typedef struct wasmtime_component_val_record_field_t wasmtime_component_val_record_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[]); + +/// \brief A vector of values. +WASMTIME_COMPONENT_DECLARE_VEC(val_vec, wasmtime_component_val_t); + +/// \brief A tuple of named fields. +WASMTIME_COMPONENT_DECLARE_VEC(val_record, wasmtime_component_val_record_field_t); + +/// \brief A variable sized bitset. +WASMTIME_COMPONENT_DECLARE_VEC(val_flags, uint32_t); +WASMTIME_COMPONENT_DECLARE_VEC_NEW(val_flags, uint32_t); + +#undef WASMTIME_COMPONENT_DECLARE_VEC + +// A variant contains the discriminant index and an optional value that is held. +typedef struct wasmtime_component_val_variant_t { + uint32_t discriminant; + 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. +typedef struct wasmtime_component_val_result_t { + wasmtime_component_val_t *val; + bool error; +} wasmtime_component_val_result_t; + +// Which value within an enumeration is selected. +typedef struct wasmtime_component_val_enum_t { + uint32_t discriminant; +} wasmtime_component_val_enum_t; + +typedef union wasmtime_component_val_payload_t { + bool boolean; + int8_t s8; + uint8_t u8; + int16_t s16; + uint16_t u16; + int32_t s32; + uint32_t u32; + int64_t s64; + uint64_t u64; + float f32; + double f64; + uint8_t character; + wasm_name_t string; + wasmtime_component_val_vec_t list; + wasmtime_component_val_record_t record; + wasmtime_component_val_vec_t tuple; + wasmtime_component_val_variant_t variant; + wasmtime_component_val_enum_t enumeration; + wasmtime_component_val_t *option; + wasmtime_component_val_result_t result; + wasmtime_component_val_flags_t flags; +} wasmtime_component_val_payload_t; + +// The tagged union for a value within the component model. +typedef struct wasmtime_component_val_t { + wasmtime_component_kind_t kind; + 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. +typedef struct wasmtime_component_val_record_field_t { + wasm_name_t name; + 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. +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`. +bool wasmtime_component_val_flags_test(const wasmtime_component_val_flags_t* flags, uint32_t index); + +wasmtime_component_val_t* wasmtime_component_val_new(); + +void wasmtime_component_val_delete(wasmtime_component_val_t* val); + +typedef struct wasmtime_component_t wasmtime_component_t; + +wasmtime_error_t * +wasmtime_component_from_binary(const wasm_engine_t *engine, const uint8_t *buf, size_t len, + wasmtime_component_t **component_out); + +void wasmtime_component_delete(wasmtime_component_t *component); + +typedef struct wasmtime_component_linker_t wasmtime_component_linker_t; + +wasmtime_component_linker_t *wasmtime_component_linker_new(const wasm_engine_t *engine); + +void wasmtime_component_linker_delete(wasmtime_component_linker_t *linker); + +typedef struct wasmtime_component_instance_t wasmtime_component_instance_t; + +// declaration from store.h +typedef struct wasmtime_context wasmtime_context_t; + +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); + +typedef struct wasmtime_component_func_t wasmtime_component_func_t; + +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); + +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..85c2f1e9d174 --- /dev/null +++ b/crates/c-api/src/component.rs @@ -0,0 +1,729 @@ +use anyhow::{bail, ensure, Context, Result}; +use wasmtime::component::{Component, Func, Instance, Linker, Type, Val}; +use wasmtime::{AsContext, AsContextMut}; + +use crate::{ + declare_vecs, handle_call_error, handle_result, wasm_byte_vec_t, wasm_config_t, wasm_engine_t, + wasm_name_t, wasm_trap_t, wasmtime_error_t, WasmtimeStoreContextMut, WasmtimeStoreData, +}; +use std::collections::HashMap; +use std::{mem, mem::MaybeUninit, ptr, slice}; + +#[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()); + } + } + }) + } +} + +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(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) {} + +#[repr(C)] +pub struct wasmtime_component_linker_t { + linker: Linker, +} + +#[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), + }) +} + +#[no_mangle] +pub unsafe extern "C" fn wasmtime_component_linker_delete(_: Box) {} + +#[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> { + 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..c5db0ddc8579 100644 --- a/crates/c-api/src/lib.rs +++ b/crates/c-api/src/lib.rs @@ -15,6 +15,7 @@ #![expect(non_camel_case_types, reason = "matching C style, not Rust")] pub use wasmtime; +use wasmtime::Trap; mod config; mod engine; @@ -70,6 +71,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 +127,18 @@ unsafe fn slice_from_raw_parts_mut<'a, T>(ptr: *mut T, len: usize) -> &'a mut [T } } +pub(crate) fn handle_call_error( + err: wasmtime::Error, + trap_ret: &mut *mut wasm_trap_t, +) -> Option> { + 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",