Merge pull request #1996 from fitzgen/ref-types-in-c-api

Support reference types in the C API
This commit is contained in:
Nick Fitzgerald
2020-07-13 10:58:42 -07:00
committed by GitHub
17 changed files with 592 additions and 291 deletions

View File

@@ -231,6 +231,8 @@ jobs:
# Ensure all our examples build and execute # Ensure all our examples build and execute
- run: cargo run -p run-examples - run: cargo run -p run-examples
env:
RUST_BACKTRACE: 1
# Build and test all features except for lightbeam # Build and test all features except for lightbeam
- run: | - run: |

1
.gitignore vendored
View File

@@ -15,3 +15,4 @@ rusty-tags.*
tags tags
target target
.z3-trace .z3-trace
foo

1
Cargo.lock generated
View File

@@ -1743,6 +1743,7 @@ dependencies = [
name = "run-examples" name = "run-examples"
version = "0.18.0" version = "0.18.0"
dependencies = [ dependencies = [
"anyhow",
"cc", "cc",
] ]

View File

@@ -1147,19 +1147,22 @@
/** /**
* \struct wasm_ref_t * \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 * \typedef wasm_ref_t
* \brief Convenience alias for #wasm_ref_t * \brief Convenience alias for #wasm_ref_t
* *
* \fn void wasm_ref_delete(own wasm_ref_t *v); * \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 *) * \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 *) * \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 *); * \fn void* wasm_ref_get_host_info(const wasm_ref_t *);
* \brief Unimplemented in Wasmtime, always returns `NULL`. * \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 * 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. * 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 * > Note: to avoid the UB associated with passing the wrong number of results
* > or parameters by accident, or to distinguish between traps and other * > or parameters by accident, or to distinguish between traps and other
* > errors, it's recommended to use #wasmtime_func_call. * > 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 * 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. * 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. * > 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 *); * \fn wasm_tabletype_t *wasm_table_type(const wasm_table_t *);
* \brief Returns the type of this table. * \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 * Attempts to get a value at an index in this table. This function returns
* `NULL` if the index is out of bounds. * `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 * > Note: for funcref tables you can use #wasmtime_funcref_table_get to learn
* > about out-of-bounds errors. * > 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 *); * \fn void wasm_table_set(wasm_table_t *, wasm_table_size_t index, wasm_ref_t *);
* \brief Sets an element in this table. * \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 comes from a different store than the table provided.
* * The #wasm_ref_t does not have an appropriate type to store in this table. * * 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 * > Note: for funcref tables you can use #wasmtime_funcref_table_set to learn
* > about errors. * > 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 *); * \fn wasm_table_size_t wasm_table_size(const wasm_table_t *);
* \brief Gets the current size, in elements, of this table. * \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 comes from a different store than the table provided.
* * The #wasm_ref_t does not have an appropriate type to store in this table. * * 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. * > 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.
*/ */
/** /**

View File

@@ -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 * The `trap` pointer cannot be `NULL`. The `args` and `results` pointers may be
* `NULL` if the corresponding length is zero. * `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_API_EXTERN own wasmtime_error_t *wasmtime_func_call(
wasm_func_t *func, wasm_func_t *func,
@@ -833,6 +836,63 @@ WASM_API_EXTERN wasmtime_error_t *wasmtime_funcref_table_grow(
wasm_table_size_t *prev_size 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 #undef own
#ifdef __cplusplus #ifdef __cplusplus

View File

@@ -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 crate::{wasm_name_t, wasm_trap_t, wasmtime_error_t};
use anyhow::anyhow; use anyhow::anyhow;
use std::ffi::c_void; use std::ffi::c_void;
use std::mem::MaybeUninit;
use std::panic::{self, AssertUnwindSafe}; use std::panic::{self, AssertUnwindSafe};
use std::ptr; use std::ptr;
use std::str; use std::str;
@@ -89,6 +90,7 @@ fn create_function(
let func = Func::new(store, ty, move |caller, params, results| { let func = Func::new(store, ty, move |caller, params, results| {
let params = params let params = params
.iter() .iter()
.cloned()
.map(|p| wasm_val_t::from_val(p)) .map(|p| wasm_val_t::from_val(p))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut out_results = vec![wasm_val_t::default(); results.len()]; 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( pub unsafe extern "C" fn wasm_func_call(
wasm_func: &wasm_func_t, wasm_func: &wasm_func_t,
args: *const wasm_val_t, args: *const wasm_val_t,
results: *mut wasm_val_t, results: *mut MaybeUninit<wasm_val_t>,
) -> *mut wasm_trap_t { ) -> *mut wasm_trap_t {
let func = wasm_func.func(); let func = wasm_func.func();
let mut trap = ptr::null_mut(); let mut trap = ptr::null_mut();
@@ -186,7 +188,7 @@ pub unsafe extern "C" fn wasmtime_func_call(
func: &wasm_func_t, func: &wasm_func_t,
args: *const wasm_val_t, args: *const wasm_val_t,
num_args: usize, num_args: usize,
results: *mut wasm_val_t, results: *mut MaybeUninit<wasm_val_t>,
num_results: usize, num_results: usize,
trap_ptr: &mut *mut wasm_trap_t, trap_ptr: &mut *mut wasm_trap_t,
) -> Option<Box<wasmtime_error_t>> { ) -> Option<Box<wasmtime_error_t>> {
@@ -201,7 +203,7 @@ pub unsafe extern "C" fn wasmtime_func_call(
fn _wasmtime_func_call( fn _wasmtime_func_call(
func: &wasm_func_t, func: &wasm_func_t,
args: &[wasm_val_t], args: &[wasm_val_t],
results: &mut [wasm_val_t], results: &mut [MaybeUninit<wasm_val_t>],
trap_ptr: &mut *mut wasm_trap_t, trap_ptr: &mut *mut wasm_trap_t,
) -> Option<Box<wasmtime_error_t>> { ) -> Option<Box<wasmtime_error_t>> {
let func = func.func(); let func = func.func();
@@ -217,8 +219,8 @@ fn _wasmtime_func_call(
let result = panic::catch_unwind(AssertUnwindSafe(|| func.call(&params))); let result = panic::catch_unwind(AssertUnwindSafe(|| func.call(&params)));
match result { match result {
Ok(Ok(out)) => { Ok(Ok(out)) => {
for (slot, val) in results.iter_mut().zip(out.iter()) { for (slot, val) in results.iter_mut().zip(out.into_vec().into_iter()) {
*slot = wasm_val_t::from_val(val); crate::initialize(slot, wasm_val_t::from_val(val));
} }
None None
} }

View File

@@ -1,5 +1,6 @@
use crate::{handle_result, wasmtime_error_t}; use crate::{handle_result, wasmtime_error_t};
use crate::{wasm_extern_t, wasm_globaltype_t, wasm_store_t, wasm_val_t}; use crate::{wasm_extern_t, wasm_globaltype_t, wasm_store_t, wasm_val_t};
use std::mem::MaybeUninit;
use std::ptr; use std::ptr;
use wasmtime::{Extern, Global}; use wasmtime::{Extern, Global};
@@ -72,8 +73,8 @@ pub extern "C" fn wasm_global_type(g: &wasm_global_t) -> Box<wasm_globaltype_t>
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn wasm_global_get(g: &wasm_global_t, out: &mut wasm_val_t) { pub extern "C" fn wasm_global_get(g: &wasm_global_t, out: &mut MaybeUninit<wasm_val_t>) {
out.set(g.global().get()); crate::initialize(out, wasm_val_t::from_val(g.global().get()));
} }
#[no_mangle] #[no_mangle]

View File

@@ -63,15 +63,13 @@ pub struct wasm_shared_module_t {
_unused: [u8; 0], _unused: [u8; 0],
} }
struct HostInfoState { /// Initialize a `MaybeUninit<T>`
info: *mut std::ffi::c_void, ///
finalizer: Option<extern "C" fn(arg1: *mut std::ffi::c_void)>, /// TODO: Replace calls to this function with
} /// https://doc.rust-lang.org/nightly/std/mem/union.MaybeUninit.html#method.write
/// once it is stable.
impl Drop for HostInfoState { pub(crate) fn initialize<T>(dst: &mut std::mem::MaybeUninit<T>, val: T) {
fn drop(&mut self) { unsafe {
if let Some(f) = &self.finalizer { std::ptr::write(dst.as_mut_ptr(), val);
f(self.info);
}
} }
} }

View File

@@ -1,45 +1,144 @@
use crate::wasm_val_t;
use std::any::Any;
use std::mem::MaybeUninit;
use std::os::raw::c_void; 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<Box<Either<ExternRef, Func>>>`.
///
/// 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)] #[derive(Clone)]
pub struct wasm_ref_t { pub struct wasm_ref_t {
pub(crate) r: Option<ExternRef>, pub(crate) r: WasmRefInner,
}
#[derive(Clone)]
pub(crate) enum WasmRefInner {
ExternRef(ExternRef),
FuncRef(Func),
} }
wasmtime_c_api_macros::declare_own!(wasm_ref_t); wasmtime_c_api_macros::declare_own!(wasm_ref_t);
#[no_mangle] pub(crate) fn ref_to_val(r: &wasm_ref_t) -> Val {
pub extern "C" fn wasm_ref_copy(r: &wasm_ref_t) -> Box<wasm_ref_t> { match &r.r {
Box::new(r.clone()) 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<Box<wasm_ref_t>> {
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] #[no_mangle]
pub extern "C" fn wasm_ref_same(a: &wasm_ref_t, b: &wasm_ref_t) -> bool { pub extern "C" fn wasm_ref_copy(r: Option<&wasm_ref_t>) -> Option<Box<wasm_ref_t>> {
match (a.r.as_ref(), b.r.as_ref()) { r.map(|r| Box::new(r.clone()))
(Some(a), Some(b)) => a.ptr_eq(b), }
#[no_mangle]
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, (None, None) => true,
// Note: we don't support equality for `Func`, so we always return
// `false` for `funcref`s.
_ => false, _ => false,
} }
} }
#[no_mangle] #[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() std::ptr::null_mut()
} }
#[no_mangle] #[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"); eprintln!("`wasm_ref_set_host_info` is not implemented");
std::process::abort(); std::process::abort();
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn wasm_ref_set_host_info_with_finalizer( pub extern "C" fn wasm_ref_set_host_info_with_finalizer(
_ref: &wasm_ref_t, _ref: Option<&wasm_ref_t>,
_info: *mut c_void, _info: *mut c_void,
_finalizer: Option<extern "C" fn(*mut c_void)>, _finalizer: Option<extern "C" fn(*mut c_void)>,
) { ) {
eprintln!("`wasm_ref_set_host_info_with_finalizer` is not implemented"); eprintln!("`wasm_ref_set_host_info_with_finalizer` is not implemented");
std::process::abort(); std::process::abort();
} }
type wasmtime_externref_finalizer_t = extern "C" fn(*mut c_void);
struct CExternRef {
data: *mut c_void,
finalizer: Option<wasmtime_externref_finalizer_t>,
}
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<wasm_val_t>) {
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<wasmtime_externref_finalizer_t>,
valp: &mut MaybeUninit<wasm_val_t>,
) {
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::<CExternRef>() {
Some(r) => r.data,
None => x.data() as *const dyn Any as *mut c_void,
};
crate::initialize(datap, data);
true
}
_ => false,
}
}

View File

@@ -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::{handle_result, wasm_func_t, wasm_ref_t, wasmtime_error_t};
use crate::{wasm_extern_t, wasm_store_t, wasm_tabletype_t}; use crate::{wasm_extern_t, wasm_store_t, wasm_tabletype_t};
use std::ptr; use std::ptr;
use wasmtime::{Extern, Table, Val}; use wasmtime::{Extern, Table, TableType, Val, ValType};
#[derive(Clone)] #[derive(Clone)]
#[repr(transparent)] #[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] #[no_mangle]
pub extern "C" fn wasm_table_new( pub extern "C" fn wasm_table_new(
store: &wasm_store_t, store: &wasm_store_t,
tt: &wasm_tabletype_t, tt: &wasm_tabletype_t,
init: Option<Box<wasm_ref_t>>, init: Option<&wasm_ref_t>,
) -> Option<Box<wasm_table_t>> { ) -> Option<Box<wasm_table_t>> {
let init: Val = match init { let init = ref_to_val_for_table(init, &tt.ty().ty);
Some(init) => init.r.into(),
None => Val::FuncRef(None),
};
let table = Table::new(&store.store, tt.ty().ty.clone(), init).ok()?; let table = Table::new(&store.store, tt.ty().ty.clone(), init).ok()?;
Some(Box::new(wasm_table_t { Some(Box::new(wasm_table_t {
ext: wasm_extern_t { ext: wasm_extern_t {
@@ -77,11 +86,12 @@ pub extern "C" fn wasm_table_type(t: &wasm_table_t) -> Box<wasm_tabletype_t> {
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn wasm_table_get(t: &wasm_table_t, index: wasm_table_size_t) -> *mut wasm_ref_t { pub extern "C" fn wasm_table_get(
match t.table().get(index) { t: &wasm_table_t,
Some(val) => into_funcref(val), index: wasm_table_size_t,
None => into_funcref(Val::FuncRef(None)), ) -> Option<Box<wasm_ref_t>> {
} let val = t.table().get(index)?;
Some(val_into_ref(val).unwrap())
} }
#[no_mangle] #[no_mangle]
@@ -108,9 +118,9 @@ pub extern "C" fn wasmtime_funcref_table_get(
pub unsafe extern "C" fn wasm_table_set( pub unsafe extern "C" fn wasm_table_set(
t: &wasm_table_t, t: &wasm_table_t,
index: wasm_table_size_t, index: wasm_table_size_t,
r: *mut wasm_ref_t, r: Option<&wasm_ref_t>,
) -> bool { ) -> bool {
let val = from_funcref(r); let val = ref_to_val_for_table(r, &t.table().ty());
t.table().set(index, val).is_ok() 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), |()| {}) 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] #[no_mangle]
pub extern "C" fn wasm_table_size(t: &wasm_table_t) -> wasm_table_size_t { pub extern "C" fn wasm_table_size(t: &wasm_table_t) -> wasm_table_size_t {
t.table().size() 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( pub unsafe extern "C" fn wasm_table_grow(
t: &wasm_table_t, t: &wasm_table_t,
delta: wasm_table_size_t, delta: wasm_table_size_t,
init: *mut wasm_ref_t, init: Option<&wasm_ref_t>,
) -> bool { ) -> bool {
let init = from_funcref(init); let init = ref_to_val_for_table(init, &t.table().ty());
t.table().grow(delta, init).is_ok() t.table().grow(delta, init).is_ok()
} }

View File

@@ -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 crate::{from_valtype, into_valtype, wasm_ref_t, wasm_valkind_t, WASM_I32};
use std::mem::MaybeUninit;
use std::ptr;
use wasmtime::{Val, ValType}; use wasmtime::{Val, ValType};
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone)]
pub struct wasm_val_t { pub struct wasm_val_t {
pub kind: wasm_valkind_t, pub kind: wasm_valkind_t,
pub of: wasm_val_union, pub of: wasm_val_union,
@@ -20,6 +22,34 @@ pub union wasm_val_union {
pub ref_: *mut wasm_ref_t, 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 { impl Default for wasm_val_t {
fn default() -> Self { fn default() -> Self {
wasm_val_t { wasm_val_t {
@@ -30,46 +60,52 @@ impl Default for wasm_val_t {
} }
impl 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 { match val {
Val::I32(i) => wasm_val_t { Val::I32(i) => wasm_val_t {
kind: from_valtype(&ValType::I32), kind: from_valtype(&ValType::I32),
of: wasm_val_union { i32: *i }, of: wasm_val_union { i32: i },
}, },
Val::I64(i) => wasm_val_t { Val::I64(i) => wasm_val_t {
kind: from_valtype(&ValType::I64), kind: from_valtype(&ValType::I64),
of: wasm_val_union { i64: *i }, of: wasm_val_union { i64: i },
}, },
Val::F32(f) => wasm_val_t { Val::F32(f) => wasm_val_t {
kind: from_valtype(&ValType::F32), kind: from_valtype(&ValType::F32),
of: wasm_val_union { u32: *f }, of: wasm_val_union { u32: f },
}, },
Val::F64(f) => wasm_val_t { Val::F64(f) => wasm_val_t {
kind: from_valtype(&ValType::F64), 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), _ => unimplemented!("wasm_val_t::from_val {:?}", val),
} }
} }
@@ -80,20 +116,29 @@ impl wasm_val_t {
ValType::I64 => Val::from(unsafe { self.of.i64 }), ValType::I64 => Val::from(unsafe { self.of.i64 }),
ValType::F32 => Val::from(unsafe { self.of.f32 }), ValType::F32 => Val::from(unsafe { self.of.f32 }),
ValType::F64 => Val::from(unsafe { self.of.f64 }), 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), _ => unimplemented!("wasm_val_t::val {:?}", self.kind),
} }
} }
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn wasm_val_copy(out: *mut wasm_val_t, source: &wasm_val_t) { pub unsafe extern "C" fn wasm_val_copy(out: &mut MaybeUninit<wasm_val_t>, source: &wasm_val_t) {
*out = match into_valtype(source.kind) { crate::initialize(
ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 => *source, 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"), _ => unimplemented!("wasm_val_copy arg"),
}; },
);
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn wasm_val_delete(_val: &mut wasm_val_t) { pub unsafe extern "C" fn wasm_val_delete(val: *mut wasm_val_t) {
// currently we only support integers/floats which need no deletion ptr::drop_in_place(val);
} }

View File

@@ -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");
}

View File

@@ -6,4 +6,5 @@ edition = "2018"
publish = false publish = false
[dependencies] [dependencies]
anyhow = "1.0.31"
cc = "1.0" cc = "1.0"

View File

@@ -1,26 +1,25 @@
use anyhow::Context;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::process::Command; use std::process::Command;
fn main() { fn main() -> anyhow::Result<()> {
let example_to_run = std::env::args().nth(1); let example_to_run = std::env::args().nth(1);
let examples = std::fs::read_dir("examples").unwrap(); let mut examples = BTreeSet::new();
let examples = examples for e in std::fs::read_dir("examples")? {
.filter_map(|e| { let e = e?;
let e = e.unwrap();
let path = e.path(); let path = e.path();
let dir = e.metadata().unwrap().is_dir(); let dir = e.metadata()?.is_dir();
if let Some("wat") = path.extension().and_then(|s| s.to_str()) { if let Some("wat") = path.extension().and_then(|s| s.to_str()) {
return None; continue;
} }
Some((path.file_stem().unwrap().to_str().unwrap().to_owned(), dir)) examples.insert((path.file_stem().unwrap().to_str().unwrap().to_owned(), dir));
}) }
.collect::<BTreeSet<_>>();
println!("======== Building libwasmtime.a ==========="); println!("======== Building libwasmtime.a ===========");
run(Command::new("cargo") run(Command::new("cargo")
.args(&["build"]) .args(&["build"])
.current_dir("crates/c-api")); .current_dir("crates/c-api"))?;
for (example, is_dir) in examples { for (example, is_dir) in examples {
if example == "README" { if example == "README" {
@@ -43,13 +42,13 @@ fn main() {
.arg("-p") .arg("-p")
.arg(format!("example-{}-wasm", example)) .arg(format!("example-{}-wasm", example))
.arg("--target") .arg("--target")
.arg(target)); .arg(target))?;
} }
println!("======== Rust example `{}` ============", example); println!("======== Rust example `{}` ============", example);
run(Command::new("cargo") run(Command::new("cargo")
.arg("run") .arg("run")
.arg("--example") .arg("--example")
.arg(&example)); .arg(&example))?;
println!("======== C/C++ example `{}` ============", example); println!("======== C/C++ example `{}` ============", example);
for extension in ["c", "cc"].iter() { for extension in ["c", "cc"].iter() {
@@ -85,26 +84,34 @@ fn main() {
.arg("ntdll.lib") .arg("ntdll.lib")
.arg("shell32.lib") .arg("shell32.lib")
.arg("ole32.lib"); .arg("ole32.lib");
"./main.exe" if is_dir {
"main.exe".to_string()
} else {
format!("./{}.exe", example)
}
} else { } else {
cmd.arg("target/debug/libwasmtime.a").arg("-o").arg("foo"); cmd.arg("target/debug/libwasmtime.a").arg("-o").arg("foo");
"./foo" "./foo".to_string()
}; };
if cfg!(target_os = "linux") { if cfg!(target_os = "linux") {
cmd.arg("-lpthread").arg("-ldl").arg("-lm"); cmd.arg("-lpthread").arg("-ldl").arg("-lm");
} }
run(&mut cmd); run(&mut cmd)?;
run(&mut Command::new(exe)); run(&mut Command::new(exe))?;
}
} }
} }
fn run(cmd: &mut Command) { Ok(())
let s = cmd.status().unwrap(); }
fn run(cmd: &mut Command) -> anyhow::Result<()> {
(|| -> anyhow::Result<()> {
let s = cmd.status()?;
if !s.success() { if !s.success() {
eprintln!("failed to run {:?}", cmd); anyhow::bail!("Exited with failure status: {}", s);
eprintln!("status: {}", s);
std::process::exit(1);
} }
Ok(())
})()
.with_context(|| format!("failed to run `{:?}`", cmd))
} }

181
examples/externref.c Normal file
View File

@@ -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 <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wasm.h>
#include <wasmtime.h>
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);
}

51
examples/externref.rs Normal file
View File

@@ -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<ExternRef>, Option<ExternRef>>()?;
let ret = func(Some(externref.clone()))?;
assert!(ret.is_some());
assert!(ret.unwrap().ptr_eq(&externref));
println!("Done.");
Ok(())
}

9
examples/externref.wat Normal file
View File

@@ -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
)
)