diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c47869bf247e..23921e5103a9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -231,6 +231,8 @@ jobs: # Ensure all our examples build and execute - run: cargo run -p run-examples + env: + RUST_BACKTRACE: 1 # Build and test all features except for lightbeam - run: | diff --git a/.gitignore b/.gitignore index f890733b7db7..18a3a0200cb3 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ rusty-tags.* tags target .z3-trace +foo diff --git a/Cargo.lock b/Cargo.lock index 91cc09f5d63e..855f8bfc4f8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1742,6 +1742,7 @@ dependencies = [ name = "run-examples" version = "0.18.0" dependencies = [ + "anyhow", "cc", ] diff --git a/crates/c-api/include/doc-wasm.h b/crates/c-api/include/doc-wasm.h index 462b7c529be2..096c92d2e1c2 100644 --- a/crates/c-api/include/doc-wasm.h +++ b/crates/c-api/include/doc-wasm.h @@ -1147,19 +1147,22 @@ /** * \struct wasm_ref_t - * \brief Unimplemented and used in Wasmtime right now. + * \brief A reference type: either a funcref or an externref. * * \typedef wasm_ref_t * \brief Convenience alias for #wasm_ref_t * * \fn void wasm_ref_delete(own wasm_ref_t *v); - * \brief Deletes a reference. + * \brief Delete a reference. * * \fn own wasm_ref_t *wasm_ref_copy(const wasm_ref_t *) - * \brief Unimplemented in Wasmtime, aborts the process if called. + * \brief Copy a reference. * * \fn bool wasm_ref_same(const wasm_ref_t *, const wasm_ref_t *) - * \brief Unimplemented in Wasmtime, aborts the process if called. + * \brief Are the given references pointing to the same externref? + * + * > Note: Wasmtime does not support checking funcrefs for equality, and this + * > function will always return false for funcrefs. * * \fn void* wasm_ref_get_host_info(const wasm_ref_t *); * \brief Unimplemented in Wasmtime, always returns `NULL`. @@ -1614,6 +1617,9 @@ * If a trap happens during execution or some other error then a non-`NULL` trap * is returned. In this situation the `results` are is unmodified. * + * Does not take ownership of `wasm_val_t` arguments. Gives ownership of + * `wasm_val_t` results. + * * > Note: to avoid the UB associated with passing the wrong number of results * > or parameters by accident, or to distinguish between traps and other * > errors, it's recommended to use #wasmtime_func_call. @@ -1758,10 +1764,9 @@ * Returns an error if the #wasm_ref_t does not match the element type of the * table provided or if it comes from a different store than the one provided. * + * Does not take ownship of the `init` value. + * * > Note: for funcref tables you can use #wasmtime_funcref_table_new as well. - * > - * > Additionally the #wasm_ref_t does not have much support in Wasmtime, so you - * > may not be able to create an appropriate initial value. * * \fn wasm_tabletype_t *wasm_table_type(const wasm_table_t *); * \brief Returns the type of this table. @@ -1774,12 +1779,10 @@ * Attempts to get a value at an index in this table. This function returns * `NULL` if the index is out of bounds. * + * Gives ownership of the resulting `wasm_ref_t*`. + * * > Note: for funcref tables you can use #wasmtime_funcref_table_get to learn * > about out-of-bounds errors. - * > - * > Additionally the #wasm_ref_t does not have much - * > support in Wasmtime, so you may not be able to do much with the returned - * > value. * * \fn void wasm_table_set(wasm_table_t *, wasm_table_size_t index, wasm_ref_t *); * \brief Sets an element in this table. @@ -1791,11 +1794,10 @@ * * The #wasm_ref_t comes from a different store than the table provided. * * The #wasm_ref_t does not have an appropriate type to store in this table. * + * Does not take ownership of the given `wasm_ref_t*`. + * * > Note: for funcref tables you can use #wasmtime_funcref_table_set to learn * > about errors. - * > - * > Additionally the #wasm_ref_t does not have much support in Wasmtime, so you - * > may not be able to create an appropriate initial value. * * \fn wasm_table_size_t wasm_table_size(const wasm_table_t *); * \brief Gets the current size, in elements, of this table. @@ -1813,10 +1815,9 @@ * * The #wasm_ref_t comes from a different store than the table provided. * * The #wasm_ref_t does not have an appropriate type to store in this table. * + * Does not take ownership of the givein `init` value. + * * > Note: for funcref tables you can use #wasmtime_funcref_table_grow as well. - * > - * > Additionally the #wasm_ref_t does not have much support in Wasmtime, so you - * > may not be able to create an appropriate initial value. */ /** diff --git a/crates/c-api/include/wasmtime.h b/crates/c-api/include/wasmtime.h index 47820938a91f..eb6253b72804 100644 --- a/crates/c-api/include/wasmtime.h +++ b/crates/c-api/include/wasmtime.h @@ -651,6 +651,9 @@ WASM_API_EXTERN const wasm_name_t *wasmtime_frame_module_name(const wasm_frame_t * * The `trap` pointer cannot be `NULL`. The `args` and `results` pointers may be * `NULL` if the corresponding length is zero. + * + * Does not take ownership of `wasm_val_t` arguments. Gives ownership of + * `wasm_val_t` results. */ WASM_API_EXTERN own wasmtime_error_t *wasmtime_func_call( wasm_func_t *func, @@ -833,6 +836,63 @@ WASM_API_EXTERN wasmtime_error_t *wasmtime_funcref_table_grow( wasm_table_size_t *prev_size ); +/** + * \brief Create a new `externref` value. + * + * Creates a new `externref` value wrapping the provided data, and writes it to + * `valp`. + * + * This function does not take an associated finalizer to clean up the data when + * the reference is reclaimed. If you need a finalizer to clean up the data, + * then use #wasmtime_externref_new_with_finalizer. + */ +WASM_API_EXTERN void wasmtime_externref_new(void *data, wasm_val_t *valp); + +/** + * \brief A finalizer for an `externref`'s wrapped data. + * + * A finalizer callback to clean up an `externref`'s wrapped data after the + * `externref` has been reclaimed. This is an opportunity to run destructors, + * free dynamically allocated memory, close file handles, etc. + */ +typedef void (*wasmtime_externref_finalizer_t)(void*); + +/** + * \brief Create a new `externref` value with a finalizer. + * + * Creates a new `externref` value wrapping the provided data, and writes it to + * `valp`. + * + * When the reference is reclaimed, the wrapped data is cleaned up with the + * provided finalizer. If you do not need to clean up the wrapped data, then use + * #wasmtime_externref_new. + */ +WASM_API_EXTERN void wasmtime_externref_new_with_finalizer( + void *data, + wasmtime_externref_finalizer_t finalizer, + wasm_val_t *valp +); + +/** + * \brief Get an `externref`'s wrapped data + * + * If the given value is a reference to a non-null `externref`, writes the + * wrapped data that was passed into #wasmtime_externref_new or + * #wasmtime_externref_new_with_finalizer when creating the given `externref` to + * `datap`, and returns `true`. + * + * If the value is a reference to a null `externref`, writes `NULL` to `datap` + * and returns `true`. + * + * If the given value is not an `externref`, returns `false` and leaves `datap` + * unmodified. + * + * Does not take ownership of `val`. + * + * Both `val` and `datap` must not be `NULL`. + */ +WASM_API_EXTERN bool wasmtime_externref_data(wasm_val_t* val, void** datap); + #undef own #ifdef __cplusplus diff --git a/crates/c-api/src/func.rs b/crates/c-api/src/func.rs index 54b8d989b901..fe494dbbcb18 100644 --- a/crates/c-api/src/func.rs +++ b/crates/c-api/src/func.rs @@ -2,6 +2,7 @@ use crate::{wasm_extern_t, wasm_functype_t, wasm_store_t, wasm_val_t}; use crate::{wasm_name_t, wasm_trap_t, wasmtime_error_t}; use anyhow::anyhow; use std::ffi::c_void; +use std::mem::MaybeUninit; use std::panic::{self, AssertUnwindSafe}; use std::ptr; use std::str; @@ -89,6 +90,7 @@ fn create_function( let func = Func::new(store, ty, move |caller, params, results| { let params = params .iter() + .cloned() .map(|p| wasm_val_t::from_val(p)) .collect::>(); let mut out_results = vec![wasm_val_t::default(); results.len()]; @@ -163,7 +165,7 @@ pub extern "C" fn wasmtime_func_new_with_env( pub unsafe extern "C" fn wasm_func_call( wasm_func: &wasm_func_t, args: *const wasm_val_t, - results: *mut wasm_val_t, + results: *mut MaybeUninit, ) -> *mut wasm_trap_t { let func = wasm_func.func(); let mut trap = ptr::null_mut(); @@ -186,7 +188,7 @@ pub unsafe extern "C" fn wasmtime_func_call( func: &wasm_func_t, args: *const wasm_val_t, num_args: usize, - results: *mut wasm_val_t, + results: *mut MaybeUninit, num_results: usize, trap_ptr: &mut *mut wasm_trap_t, ) -> Option> { @@ -201,7 +203,7 @@ pub unsafe extern "C" fn wasmtime_func_call( fn _wasmtime_func_call( func: &wasm_func_t, args: &[wasm_val_t], - results: &mut [wasm_val_t], + results: &mut [MaybeUninit], trap_ptr: &mut *mut wasm_trap_t, ) -> Option> { let func = func.func(); @@ -217,8 +219,8 @@ fn _wasmtime_func_call( let result = panic::catch_unwind(AssertUnwindSafe(|| func.call(¶ms))); match result { Ok(Ok(out)) => { - for (slot, val) in results.iter_mut().zip(out.iter()) { - *slot = wasm_val_t::from_val(val); + for (slot, val) in results.iter_mut().zip(out.into_vec().into_iter()) { + crate::initialize(slot, wasm_val_t::from_val(val)); } None } diff --git a/crates/c-api/src/global.rs b/crates/c-api/src/global.rs index 012e1ee19eff..8a370ee433bf 100644 --- a/crates/c-api/src/global.rs +++ b/crates/c-api/src/global.rs @@ -1,5 +1,6 @@ use crate::{handle_result, wasmtime_error_t}; use crate::{wasm_extern_t, wasm_globaltype_t, wasm_store_t, wasm_val_t}; +use std::mem::MaybeUninit; use std::ptr; use wasmtime::{Extern, Global}; @@ -72,8 +73,8 @@ pub extern "C" fn wasm_global_type(g: &wasm_global_t) -> Box } #[no_mangle] -pub extern "C" fn wasm_global_get(g: &wasm_global_t, out: &mut wasm_val_t) { - out.set(g.global().get()); +pub extern "C" fn wasm_global_get(g: &wasm_global_t, out: &mut MaybeUninit) { + crate::initialize(out, wasm_val_t::from_val(g.global().get())); } #[no_mangle] diff --git a/crates/c-api/src/lib.rs b/crates/c-api/src/lib.rs index 9a62dad2be5d..9f716fb6eb51 100644 --- a/crates/c-api/src/lib.rs +++ b/crates/c-api/src/lib.rs @@ -63,15 +63,13 @@ pub struct wasm_shared_module_t { _unused: [u8; 0], } -struct HostInfoState { - info: *mut std::ffi::c_void, - finalizer: Option, -} - -impl Drop for HostInfoState { - fn drop(&mut self) { - if let Some(f) = &self.finalizer { - f(self.info); - } +/// Initialize a `MaybeUninit` +/// +/// TODO: Replace calls to this function with +/// https://doc.rust-lang.org/nightly/std/mem/union.MaybeUninit.html#method.write +/// once it is stable. +pub(crate) fn initialize(dst: &mut std::mem::MaybeUninit, val: T) { + unsafe { + std::ptr::write(dst.as_mut_ptr(), val); } } diff --git a/crates/c-api/src/ref.rs b/crates/c-api/src/ref.rs index 5ddc4378364b..890a6e96e819 100644 --- a/crates/c-api/src/ref.rs +++ b/crates/c-api/src/ref.rs @@ -1,45 +1,144 @@ +use crate::wasm_val_t; +use std::any::Any; +use std::mem::MaybeUninit; use std::os::raw::c_void; -use wasmtime::ExternRef; +use std::ptr; +use wasmtime::{ExternRef, Func, Val}; -#[repr(C)] +/// `*mut wasm_ref_t` is a reference type (`externref` or `funcref`), as seen by +/// the C API. Because we do not have a uniform representation for `funcref`s +/// and `externref`s, a `*mut wasm_ref_t` is morally a +/// `Option>>`. +/// +/// A null `*mut wasm_ref_t` is either a null `funcref` or a null `externref` +/// depending on context (e.g. the table's element type that it is going into or +/// coming out of). +/// +/// Note: this is not `#[repr(C)]` because it is an opaque type in the header, +/// and only ever referenced as `*mut wasm_ref_t`. This also lets us use a +/// regular, non-`repr(C)` `enum` to define `WasmRefInner`. #[derive(Clone)] pub struct wasm_ref_t { - pub(crate) r: Option, + pub(crate) r: WasmRefInner, +} + +#[derive(Clone)] +pub(crate) enum WasmRefInner { + ExternRef(ExternRef), + FuncRef(Func), } wasmtime_c_api_macros::declare_own!(wasm_ref_t); +pub(crate) fn ref_to_val(r: &wasm_ref_t) -> Val { + match &r.r { + WasmRefInner::ExternRef(x) => Val::ExternRef(Some(x.clone())), + WasmRefInner::FuncRef(f) => Val::FuncRef(Some(f.clone())), + } +} + +pub(crate) fn val_into_ref(val: Val) -> Option> { + match val { + Val::ExternRef(Some(x)) => Some(Box::new(wasm_ref_t { + r: WasmRefInner::ExternRef(x), + })), + Val::FuncRef(Some(f)) => Some(Box::new(wasm_ref_t { + r: WasmRefInner::FuncRef(f), + })), + _ => None, + } +} + #[no_mangle] -pub extern "C" fn wasm_ref_copy(r: &wasm_ref_t) -> Box { - Box::new(r.clone()) +pub extern "C" fn wasm_ref_copy(r: Option<&wasm_ref_t>) -> Option> { + r.map(|r| Box::new(r.clone())) } #[no_mangle] -pub extern "C" fn wasm_ref_same(a: &wasm_ref_t, b: &wasm_ref_t) -> bool { - match (a.r.as_ref(), b.r.as_ref()) { - (Some(a), Some(b)) => a.ptr_eq(b), +pub extern "C" fn wasm_ref_same(a: Option<&wasm_ref_t>, b: Option<&wasm_ref_t>) -> bool { + match (a.map(|a| &a.r), b.map(|b| &b.r)) { + (Some(WasmRefInner::ExternRef(a)), Some(WasmRefInner::ExternRef(b))) => a.ptr_eq(b), (None, None) => true, + // Note: we don't support equality for `Func`, so we always return + // `false` for `funcref`s. _ => false, } } #[no_mangle] -pub extern "C" fn wasm_ref_get_host_info(_ref: &wasm_ref_t) -> *mut c_void { +pub extern "C" fn wasm_ref_get_host_info(_ref: Option<&wasm_ref_t>) -> *mut c_void { std::ptr::null_mut() } #[no_mangle] -pub extern "C" fn wasm_ref_set_host_info(_ref: &wasm_ref_t, _info: *mut c_void) { +pub extern "C" fn wasm_ref_set_host_info(_ref: Option<&wasm_ref_t>, _info: *mut c_void) { eprintln!("`wasm_ref_set_host_info` is not implemented"); std::process::abort(); } #[no_mangle] pub extern "C" fn wasm_ref_set_host_info_with_finalizer( - _ref: &wasm_ref_t, + _ref: Option<&wasm_ref_t>, _info: *mut c_void, _finalizer: Option, ) { eprintln!("`wasm_ref_set_host_info_with_finalizer` is not implemented"); std::process::abort(); } + +type wasmtime_externref_finalizer_t = extern "C" fn(*mut c_void); + +struct CExternRef { + data: *mut c_void, + finalizer: Option, +} + +impl Drop for CExternRef { + fn drop(&mut self) { + if let Some(f) = self.finalizer { + f(self.data); + } + } +} + +#[no_mangle] +pub extern "C" fn wasmtime_externref_new(data: *mut c_void, valp: &mut MaybeUninit) { + wasmtime_externref_new_with_finalizer(data, None, valp) +} + +#[no_mangle] +pub extern "C" fn wasmtime_externref_new_with_finalizer( + data: *mut c_void, + finalizer: Option, + valp: &mut MaybeUninit, +) { + crate::initialize( + valp, + wasm_val_t::from_val(Val::ExternRef(Some(ExternRef::new(CExternRef { + data, + finalizer, + })))), + ); +} + +#[no_mangle] +pub extern "C" fn wasmtime_externref_data( + val: &wasm_val_t, + datap: &mut MaybeUninit<*mut c_void>, +) -> bool { + match val.val() { + Val::ExternRef(None) => { + crate::initialize(datap, ptr::null_mut()); + true + } + Val::ExternRef(Some(x)) => { + let data = match x.data().downcast_ref::() { + Some(r) => r.data, + None => x.data() as *const dyn Any as *mut c_void, + }; + crate::initialize(datap, data); + true + } + _ => false, + } +} diff --git a/crates/c-api/src/table.rs b/crates/c-api/src/table.rs index c9833702a907..c88620da8587 100644 --- a/crates/c-api/src/table.rs +++ b/crates/c-api/src/table.rs @@ -1,7 +1,8 @@ +use crate::r#ref::{ref_to_val, val_into_ref}; use crate::{handle_result, wasm_func_t, wasm_ref_t, wasmtime_error_t}; use crate::{wasm_extern_t, wasm_store_t, wasm_tabletype_t}; use std::ptr; -use wasmtime::{Extern, Table, Val}; +use wasmtime::{Extern, Table, TableType, Val, ValType}; #[derive(Clone)] #[repr(transparent)] @@ -29,16 +30,24 @@ impl wasm_table_t { } } +fn ref_to_val_for_table(r: Option<&wasm_ref_t>, table_ty: &TableType) -> Val { + r.map_or_else( + || match table_ty.element() { + ValType::FuncRef => Val::FuncRef(None), + ValType::ExternRef => Val::ExternRef(None), + ty => panic!("unsupported table element type: {:?}", ty), + }, + |r| ref_to_val(r), + ) +} + #[no_mangle] pub extern "C" fn wasm_table_new( store: &wasm_store_t, tt: &wasm_tabletype_t, - init: Option>, + init: Option<&wasm_ref_t>, ) -> Option> { - let init: Val = match init { - Some(init) => init.r.into(), - None => Val::FuncRef(None), - }; + let init = ref_to_val_for_table(init, &tt.ty().ty); let table = Table::new(&store.store, tt.ty().ty.clone(), init).ok()?; Some(Box::new(wasm_table_t { ext: wasm_extern_t { @@ -77,11 +86,12 @@ pub extern "C" fn wasm_table_type(t: &wasm_table_t) -> Box { } #[no_mangle] -pub extern "C" fn wasm_table_get(t: &wasm_table_t, index: wasm_table_size_t) -> *mut wasm_ref_t { - match t.table().get(index) { - Some(val) => into_funcref(val), - None => into_funcref(Val::FuncRef(None)), - } +pub extern "C" fn wasm_table_get( + t: &wasm_table_t, + index: wasm_table_size_t, +) -> Option> { + let val = t.table().get(index)?; + Some(val_into_ref(val).unwrap()) } #[no_mangle] @@ -108,9 +118,9 @@ pub extern "C" fn wasmtime_funcref_table_get( pub unsafe extern "C" fn wasm_table_set( t: &wasm_table_t, index: wasm_table_size_t, - r: *mut wasm_ref_t, + r: Option<&wasm_ref_t>, ) -> bool { - let val = from_funcref(r); + let val = ref_to_val_for_table(r, &t.table().ty()); t.table().set(index, val).is_ok() } @@ -127,26 +137,6 @@ pub extern "C" fn wasmtime_funcref_table_set( handle_result(t.table().set(index, val), |()| {}) } -fn into_funcref(val: Val) -> *mut wasm_ref_t { - if let Val::FuncRef(None) = val { - return ptr::null_mut(); - } - let externref = match val.externref() { - Some(externref) => externref, - None => return ptr::null_mut(), - }; - let r = Box::new(wasm_ref_t { r: externref }); - Box::into_raw(r) -} - -unsafe fn from_funcref(r: *mut wasm_ref_t) -> Val { - if !r.is_null() { - Box::from_raw(r).r.into() - } else { - Val::FuncRef(None) - } -} - #[no_mangle] pub extern "C" fn wasm_table_size(t: &wasm_table_t) -> wasm_table_size_t { t.table().size() @@ -156,9 +146,9 @@ pub extern "C" fn wasm_table_size(t: &wasm_table_t) -> wasm_table_size_t { pub unsafe extern "C" fn wasm_table_grow( t: &wasm_table_t, delta: wasm_table_size_t, - init: *mut wasm_ref_t, + init: Option<&wasm_ref_t>, ) -> bool { - let init = from_funcref(init); + let init = ref_to_val_for_table(init, &t.table().ty()); t.table().grow(delta, init).is_ok() } diff --git a/crates/c-api/src/val.rs b/crates/c-api/src/val.rs index 4cd1cb2abd20..25754d4ed006 100644 --- a/crates/c-api/src/val.rs +++ b/crates/c-api/src/val.rs @@ -1,8 +1,10 @@ +use crate::r#ref::{ref_to_val, WasmRefInner}; use crate::{from_valtype, into_valtype, wasm_ref_t, wasm_valkind_t, WASM_I32}; +use std::mem::MaybeUninit; +use std::ptr; use wasmtime::{Val, ValType}; #[repr(C)] -#[derive(Copy, Clone)] pub struct wasm_val_t { pub kind: wasm_valkind_t, pub of: wasm_val_union, @@ -20,6 +22,34 @@ pub union wasm_val_union { pub ref_: *mut wasm_ref_t, } +impl Drop for wasm_val_t { + fn drop(&mut self) { + match into_valtype(self.kind) { + ValType::ExternRef => unsafe { + drop(Box::from_raw(self.of.ref_)); + }, + _ => {} + } + } +} + +impl Clone for wasm_val_t { + fn clone(&self) -> Self { + match into_valtype(self.kind) { + ValType::ExternRef => wasm_val_t { + kind: self.kind, + of: wasm_val_union { + ref_: unsafe { Box::into_raw(Box::new((*self.of.ref_).clone())) }, + }, + }, + _ => wasm_val_t { + kind: self.kind, + of: self.of, + }, + } + } +} + impl Default for wasm_val_t { fn default() -> Self { wasm_val_t { @@ -30,46 +60,52 @@ impl Default for wasm_val_t { } impl wasm_val_t { - pub fn from_val(val: &Val) -> wasm_val_t { + pub fn from_val(val: Val) -> wasm_val_t { match val { Val::I32(i) => wasm_val_t { kind: from_valtype(&ValType::I32), - of: wasm_val_union { i32: *i }, + of: wasm_val_union { i32: i }, }, Val::I64(i) => wasm_val_t { kind: from_valtype(&ValType::I64), - of: wasm_val_union { i64: *i }, + of: wasm_val_union { i64: i }, }, Val::F32(f) => wasm_val_t { kind: from_valtype(&ValType::F32), - of: wasm_val_union { u32: *f }, + of: wasm_val_union { u32: f }, }, Val::F64(f) => wasm_val_t { kind: from_valtype(&ValType::F64), - of: wasm_val_union { u64: *f }, + of: wasm_val_union { u64: f }, + }, + Val::ExternRef(None) => wasm_val_t { + kind: from_valtype(&ValType::ExternRef), + of: wasm_val_union { + ref_: ptr::null_mut(), + }, + }, + Val::ExternRef(Some(r)) => wasm_val_t { + kind: from_valtype(&ValType::ExternRef), + of: wasm_val_union { + ref_: Box::into_raw(Box::new(wasm_ref_t { + r: WasmRefInner::ExternRef(r), + })), + }, + }, + Val::FuncRef(None) => wasm_val_t { + kind: from_valtype(&ValType::FuncRef), + of: wasm_val_union { + ref_: ptr::null_mut(), + }, + }, + Val::FuncRef(Some(f)) => wasm_val_t { + kind: from_valtype(&ValType::FuncRef), + of: wasm_val_union { + ref_: Box::into_raw(Box::new(wasm_ref_t { + r: WasmRefInner::FuncRef(f), + })), + }, }, - _ => unimplemented!("wasm_val_t::from_val {:?}", val), - } - } - - pub fn set(&mut self, val: Val) { - match val { - Val::I32(i) => { - self.kind = from_valtype(&ValType::I32); - self.of = wasm_val_union { i32: i }; - } - Val::I64(i) => { - self.kind = from_valtype(&ValType::I64); - self.of = wasm_val_union { i64: i }; - } - Val::F32(f) => { - self.kind = from_valtype(&ValType::F32); - self.of = wasm_val_union { u32: f }; - } - Val::F64(f) => { - self.kind = from_valtype(&ValType::F64); - self.of = wasm_val_union { u64: f }; - } _ => unimplemented!("wasm_val_t::from_val {:?}", val), } } @@ -80,20 +116,29 @@ impl wasm_val_t { ValType::I64 => Val::from(unsafe { self.of.i64 }), ValType::F32 => Val::from(unsafe { self.of.f32 }), ValType::F64 => Val::from(unsafe { self.of.f64 }), + ValType::ExternRef | ValType::FuncRef => ref_to_val(unsafe { &*self.of.ref_ }), _ => unimplemented!("wasm_val_t::val {:?}", self.kind), } } } #[no_mangle] -pub unsafe extern "C" fn wasm_val_copy(out: *mut wasm_val_t, source: &wasm_val_t) { - *out = match into_valtype(source.kind) { - ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 => *source, - _ => unimplemented!("wasm_val_copy arg"), - }; +pub unsafe extern "C" fn wasm_val_copy(out: &mut MaybeUninit, source: &wasm_val_t) { + crate::initialize( + out, + match into_valtype(source.kind) { + ValType::I32 + | ValType::I64 + | ValType::F32 + | ValType::F64 + | ValType::ExternRef + | ValType::FuncRef => source.clone(), + _ => unimplemented!("wasm_val_copy arg"), + }, + ); } #[no_mangle] -pub extern "C" fn wasm_val_delete(_val: &mut wasm_val_t) { - // currently we only support integers/floats which need no deletion +pub unsafe extern "C" fn wasm_val_delete(val: *mut wasm_val_t) { + ptr::drop_in_place(val); } diff --git a/crates/c-api/tests/wasm-c-examples.rs b/crates/c-api/tests/wasm-c-examples.rs deleted file mode 100644 index feedce7caead..000000000000 --- a/crates/c-api/tests/wasm-c-examples.rs +++ /dev/null @@ -1,148 +0,0 @@ -use std::env; -use std::fs; -use std::path::Path; -use std::process::Command; -use std::sync::Once; - -fn run_c_example(name: &'static str, expected_out: &str) { - // Windows requires different `cc` flags and I'm not sure what they - // are. Also we need a way to shepherd the current host target to the `cc` - // invocation but `cargo` only defines the `TARGET` environment variable for - // build scripts, not tests. Therefore, we just make these tests specific to - // bog standard x64 linux. This should run in CI, at least! - if cfg!(not(all( - target_arch = "x86_64", - target_os = "linux", - target_env = "gnu" - ))) { - eprintln!("This test is only enabled for the `x86_64-unknown-linux-gnu` target"); - return; - } - - let pkg_dir = Path::new(env!("CARGO_MANIFEST_DIR")); - - // Make sure we've built `libwasmtime.a` with the `wat` feature enabled - // so that we have the `wasmtime_wat2wasm` function. - static BUILD_LIBWASMTIME: Once = Once::new(); - BUILD_LIBWASMTIME.call_once(|| { - let status = Command::new("cargo") - .args(&["build", "-p", "wasmtime-c-api", "--features", "wat"]) - .current_dir(pkg_dir) - .status() - .expect("should run `cargo build` OK"); - assert!(status.success()); - }); - - let examples_dir = pkg_dir - // Pop `c-api`. - .join("..") - // Pop `crates`. - .join("..") - .join("examples"); - let include_dir = pkg_dir.join("include"); - let wasm_c_api_include_dir = pkg_dir.join("wasm-c-api").join("include"); - let out_dir = pkg_dir.join("..").join("..").join("target").join("debug"); - let c_examples_dir = out_dir.join("c-examples"); - fs::create_dir_all(&c_examples_dir).unwrap(); - let libwasmtime = out_dir.join("libwasmtime.a"); - assert!(libwasmtime.exists()); - - let status = Command::new(env::var("CC").unwrap_or("gcc".into())) - .arg(examples_dir.join(name).with_extension("c")) - .arg(libwasmtime) - .arg(format!("-I{}", include_dir.display())) - .arg(format!("-I{}", wasm_c_api_include_dir.display())) - .arg("-lpthread") - .arg("-ldl") - .arg("-lm") - .arg("-lrt") - .current_dir(&examples_dir) - .arg("-o") - .arg(c_examples_dir.join(name)) - .status() - .expect("should spawn CC ok"); - assert!(status.success()); - assert!(c_examples_dir.join(name).exists()); - - let output = Command::new(c_examples_dir.join(name)) - .current_dir(pkg_dir.join("..").join("..")) - .output() - .expect("should spawn C example OK"); - - assert!( - output.status.success(), - "failed to execute the C example '{}': {}", - name, - String::from_utf8_lossy(&output.stderr), - ); - - let actual_stdout = - String::from_utf8(output.stdout).expect("C example's output should be utf-8"); - assert_eq!( - actual_stdout, expected_out, - "unexpected stdout from example", - ); -} - -#[test] -fn test_run_hello_example() { - run_c_example( - "hello", - "Initializing...\n\ - Compiling module...\n\ - Creating callback...\n\ - Instantiating module...\n\ - Extracting export...\n\ - Calling export...\n\ - Calling back...\n\ - > Hello World!\n\ - All finished!\n", - ); -} - -#[test] -fn test_run_memory_example() { - run_c_example( - "memory", - "Initializing...\n\ - Compiling module...\n\ - Instantiating module...\n\ - Extracting exports...\n\ - Checking memory...\n\ - Mutating memory...\n\ - Growing memory...\n\ - Creating stand-alone memory...\n\ - Shutting down...\n\ - Done.\n", - ); -} - -#[test] -fn test_run_linking_example() { - run_c_example("linking", "Hello, world!\n"); -} - -#[test] -fn test_run_multi_example() { - run_c_example( - "multi", - "Initializing...\n\ - Compiling module...\n\ - Creating callback...\n\ - Instantiating module...\n\ - Extracting export...\n\ - Calling export...\n\ - Calling back...\n\ - > 1 2\n\ - \n\ - Printing result...\n\ - > 2 1\n\ - Shutting down...\n\ - Done.\n", - ); -} - -#[test] -fn test_run_gcd_example() { - run_c_example("gcd", "gcd(6, 27) = 3\n"); -} diff --git a/crates/misc/run-examples/Cargo.toml b/crates/misc/run-examples/Cargo.toml index 6dc0ead7e901..616e4ffc1db7 100644 --- a/crates/misc/run-examples/Cargo.toml +++ b/crates/misc/run-examples/Cargo.toml @@ -6,4 +6,5 @@ edition = "2018" publish = false [dependencies] +anyhow = "1.0.31" cc = "1.0" diff --git a/crates/misc/run-examples/src/main.rs b/crates/misc/run-examples/src/main.rs index a0cc098ab729..94e5b56dc958 100644 --- a/crates/misc/run-examples/src/main.rs +++ b/crates/misc/run-examples/src/main.rs @@ -1,26 +1,25 @@ +use anyhow::Context; use std::collections::BTreeSet; use std::process::Command; -fn main() { +fn main() -> anyhow::Result<()> { let example_to_run = std::env::args().nth(1); - let examples = std::fs::read_dir("examples").unwrap(); - let examples = examples - .filter_map(|e| { - let e = e.unwrap(); - let path = e.path(); - let dir = e.metadata().unwrap().is_dir(); - if let Some("wat") = path.extension().and_then(|s| s.to_str()) { - return None; - } + let mut examples = BTreeSet::new(); + for e in std::fs::read_dir("examples")? { + let e = e?; + let path = e.path(); + let dir = e.metadata()?.is_dir(); + if let Some("wat") = path.extension().and_then(|s| s.to_str()) { + continue; + } - Some((path.file_stem().unwrap().to_str().unwrap().to_owned(), dir)) - }) - .collect::>(); + examples.insert((path.file_stem().unwrap().to_str().unwrap().to_owned(), dir)); + } println!("======== Building libwasmtime.a ==========="); run(Command::new("cargo") .args(&["build"]) - .current_dir("crates/c-api")); + .current_dir("crates/c-api"))?; for (example, is_dir) in examples { if example == "README" { @@ -43,13 +42,13 @@ fn main() { .arg("-p") .arg(format!("example-{}-wasm", example)) .arg("--target") - .arg(target)); + .arg(target))?; } println!("======== Rust example `{}` ============", example); run(Command::new("cargo") .arg("run") .arg("--example") - .arg(&example)); + .arg(&example))?; println!("======== C/C++ example `{}` ============", example); for extension in ["c", "cc"].iter() { @@ -85,26 +84,34 @@ fn main() { .arg("ntdll.lib") .arg("shell32.lib") .arg("ole32.lib"); - "./main.exe" + if is_dir { + "main.exe".to_string() + } else { + format!("./{}.exe", example) + } } else { cmd.arg("target/debug/libwasmtime.a").arg("-o").arg("foo"); - "./foo" + "./foo".to_string() }; if cfg!(target_os = "linux") { cmd.arg("-lpthread").arg("-ldl").arg("-lm"); } - run(&mut cmd); + run(&mut cmd)?; - run(&mut Command::new(exe)); + run(&mut Command::new(exe))?; } } + + Ok(()) } -fn run(cmd: &mut Command) { - let s = cmd.status().unwrap(); - if !s.success() { - eprintln!("failed to run {:?}", cmd); - eprintln!("status: {}", s); - std::process::exit(1); - } +fn run(cmd: &mut Command) -> anyhow::Result<()> { + (|| -> anyhow::Result<()> { + let s = cmd.status()?; + if !s.success() { + anyhow::bail!("Exited with failure status: {}", s); + } + Ok(()) + })() + .with_context(|| format!("failed to run `{:?}`", cmd)) } diff --git a/examples/externref.c b/examples/externref.c new file mode 100644 index 000000000000..92785a202277 --- /dev/null +++ b/examples/externref.c @@ -0,0 +1,181 @@ +/* +Example of using `externref` values. + +You can compile and run this example on Linux with: + + cargo build --release -p wasmtime + cc examples/externref.c \ + -I crates/c-api/include \ + -I crates/c-api/wasm-c-api/include \ + target/release/libwasmtime.a \ + -lpthread -ldl -lm \ + -o externref + ./externref + +Note that on Windows and macOS the command will be similar, but you'll need +to tweak the `-lpthread` and such annotations as well as the name of the +`libwasmtime.a` file on Windows. +*/ + +#include +#include +#include +#include +#include +#include + +static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap); + +int main() { + int ret = 0; + bool ok = true; + // Create a new configuration with Wasm reference types enabled. + printf("Initializing...\n"); + wasm_config_t *config = wasm_config_new(); + assert(config != NULL); + wasmtime_config_wasm_reference_types_set(config, true); + + // Create an *engine*, which is a compilation context, with our configured + // options. + wasm_engine_t *engine = wasm_engine_new_with_config(config); + assert(engine != NULL); + + // With an engine we can create a *store* which is a long-lived group of wasm + // modules. + wasm_store_t *store = wasm_store_new(engine); + assert(store != NULL); + + // Read our input file, which in this case is a wasm text file. + FILE* file = fopen("examples/externref.wat", "r"); + assert(file != NULL); + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); + wasm_byte_vec_t wat; + wasm_byte_vec_new_uninitialized(&wat, file_size); + assert(fread(wat.data, file_size, 1, file) == 1); + fclose(file); + + // Parse the wat into the binary wasm format + wasm_byte_vec_t wasm; + wasmtime_error_t *error = wasmtime_wat2wasm(&wat, &wasm); + if (error != NULL) + exit_with_error("failed to parse wat", error, NULL); + wasm_byte_vec_delete(&wat); + + // Now that we've got our binary webassembly we can compile our module. + printf("Compiling module...\n"); + wasm_module_t *module = NULL; + error = wasmtime_module_new(store, &wasm, &module); + wasm_byte_vec_delete(&wasm); + if (error != NULL) + exit_with_error("failed to compile module", error, NULL); + + // Instantiate the module. + printf("Instantiating module...\n"); + wasm_trap_t *trap = NULL; + wasm_instance_t *instance = NULL; + error = wasmtime_instance_new(store, module, NULL, 0, &instance, &trap); + if (instance == NULL) + exit_with_error("failed to instantiate", error, trap); + + printf("Creating new `externref`...\n"); + + // Create a new `externref` value. + wasm_val_t externref; + wasmtime_externref_new("Hello, World!", &externref); + assert(externref.kind == WASM_ANYREF); + + // The `externref`'s wrapped data should be the string "Hello, World!". + void* data = NULL; + ok = wasmtime_externref_data(&externref, &data); + assert(ok); + assert(strcmp((char*)data, "Hello, World!") == 0); + + printf("Touching `externref` table...\n"); + + // Lookup the `table` export. + wasm_extern_vec_t externs; + wasm_instance_exports(instance, &externs); + assert(externs.size == 3); + wasm_table_t *table = wasm_extern_as_table(externs.data[0]); + assert(table != NULL); + + // Set `table[3]` to our `externref`. + wasm_val_t elem; + wasm_val_copy(&elem, &externref); + assert(elem.kind == WASM_ANYREF); + ok = wasm_table_set(table, 3, elem.of.ref); + assert(ok); + + // `table[3]` should now be our `externref`. + wasm_ref_delete(elem.of.ref); + elem.of.ref = wasm_table_get(table, 3); + assert(elem.of.ref != NULL); + assert(wasm_ref_same(elem.of.ref, externref.of.ref)); + + printf("Touching `externref` global...\n"); + + // Lookup the `global` export. + wasm_global_t *global = wasm_extern_as_global(externs.data[1]); + assert(global != NULL); + + // Set the global to our `externref`. + wasm_global_set(global, &externref); + + // Get the global, and it should return our `externref` again. + wasm_val_t global_val; + wasm_global_get(global, &global_val); + assert(global_val.kind == WASM_ANYREF); + assert(wasm_ref_same(global_val.of.ref, externref.of.ref)); + + printf("Calling `externref` func...\n"); + + // Lookup the `func` export. + wasm_func_t *func = wasm_extern_as_func(externs.data[2]); + assert(func != NULL); + + // And call it! + wasm_val_t args[1]; + wasm_val_copy(&args[0], &externref); + wasm_val_t results[1]; + error = wasmtime_func_call(func, args, 1, results, 1, &trap); + if (error != NULL || trap != NULL) + exit_with_error("failed to call function", error, trap); + + // `func` returns the same reference we gave it, so `results[0]` should be our + // `externref`. + assert(results[0].kind == WASM_ANYREF); + assert(wasm_ref_same(results[0].of.ref, externref.of.ref)); + + // Clean up after ourselves at this point + printf("All finished!\n"); + ret = 0; + + wasm_val_delete(&results[0]); + wasm_val_delete(&args[0]); + wasm_val_delete(&global_val); + wasm_val_delete(&elem); + wasm_extern_vec_delete(&externs); + wasm_val_delete(&externref); + wasm_instance_delete(instance); + wasm_module_delete(module); + wasm_store_delete(store); + wasm_engine_delete(engine); + return ret; +} + +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); + wasmtime_error_delete(error); + } else { + wasm_trap_message(trap, &error_message); + wasm_trap_delete(trap); + } + fprintf(stderr, "%.*s\n", (int) error_message.size, error_message.data); + wasm_byte_vec_delete(&error_message); + exit(1); +} diff --git a/examples/externref.rs b/examples/externref.rs new file mode 100644 index 000000000000..b301dba241a8 --- /dev/null +++ b/examples/externref.rs @@ -0,0 +1,51 @@ +//! Small example of how to use `externref`s. + +// You can execute this example with `cargo run --example externref` + +use anyhow::Result; +use wasmtime::*; + +fn main() -> Result<()> { + println!("Initializing..."); + let mut config = Config::new(); + config.wasm_reference_types(true); + let engine = Engine::new(&config); + let store = Store::new(&engine); + + println!("Compiling module..."); + let module = Module::from_file(&engine, "examples/externref.wat")?; + + println!("Instantiating module..."); + let imports = []; + let instance = Instance::new(&store, &module, &imports)?; + + println!("Creating new `externref`..."); + let externref = ExternRef::new("Hello, World!"); + assert!(externref.data().is::<&'static str>()); + assert_eq!( + *externref.data().downcast_ref::<&'static str>().unwrap(), + "Hello, World!" + ); + + println!("Touching `externref` table..."); + let table = instance.get_table("table").unwrap(); + table.set(3, Some(externref.clone()).into())?; + let elem = table.get(3).unwrap().unwrap_externref().unwrap(); + assert!(elem.ptr_eq(&externref)); + + println!("Touching `externref` global..."); + let global = instance.get_global("global").unwrap(); + global.set(Some(externref.clone()).into())?; + let global_val = global.get().unwrap_externref().unwrap(); + assert!(global_val.ptr_eq(&externref)); + + println!("Calling `externref` func..."); + let func = instance.get_func("func").unwrap(); + let func = func.get1::, Option>()?; + let ret = func(Some(externref.clone()))?; + assert!(ret.is_some()); + assert!(ret.unwrap().ptr_eq(&externref)); + + println!("Done."); + Ok(()) +} diff --git a/examples/externref.wat b/examples/externref.wat new file mode 100644 index 000000000000..57dc8a72bb1b --- /dev/null +++ b/examples/externref.wat @@ -0,0 +1,9 @@ +(module + (table $table (export "table") 10 externref) + + (global $global (export "global") (mut externref) (ref.null extern)) + + (func (export "func") (param externref) (result externref) + local.get 0 + ) +)