Refactor and fill out wasmtime's C API (#1415)
* Refactor and improve safety of C API This commit is intended to be a relatively large refactoring of the C API which is targeted at improving the safety of our C API definitions. Not all of the APIs have been updated yet but this is intended to be the start. The goal here is to make as many functions safe as we can, expressing inputs/outputs as native Rust types rather than raw pointers wherever possible. For example instead of `*const wasm_foo_t` we'd take `&wasm_foo_t`. Instead of returning `*mut wasm_foo_t` we'd return `Box<wasm_foo_t>`. No ABI/API changes are intended from this commit, it's supposed to only change how we define all these functions internally. This commit also additionally implements a few more API bindings for exposed vector types by unifying everything into one macro. Finally, this commit moves many internal caches in the C API to the `OnceCell` type which provides a safe interface for one-time initialization. * Split apart monolithic C API `lib.rs` This commit splits the monolithic `src/lib.rs` in the C API crate into lots of smaller files. The goal here is to make this a bit more readable and digestable. Each module now contains only API bindings for a particular type, roughly organized around the grouping in the wasm.h header file already. A few more extensions were added, such as filling out `*_as_*` conversions with both const and non-const versions. Additionally many APIs were made safer in the same style as the previous commit, generally preferring Rust types rather than raw pointer types. Overall no functional change is intended here, it should be mostly just code movement and minor refactorings! * Make a few wasi C bindings safer Use safe Rust types where we can and touch up a few APIs here and there. * Implement `wasm_*type_as_externtype*` APIs This commit restructures `wasm_externtype_t` to be similar to `wasm_extern_t` so type conversion between the `*_extern_*` variants to the concrete variants are all simple casts. (checked in the case of general to concrete, of course). * Consistently imlpement host info functions in the API This commit adds a small macro crate which is then used to consistently define the various host-info-related functions in the C API. The goal here is to try to mirror what the `wasm.h` header provides to provide a full implementation of the header.
This commit is contained in:
243
crates/c-api/src/func.rs
Normal file
243
crates/c-api/src/func.rs
Normal file
@@ -0,0 +1,243 @@
|
||||
use crate::{wasm_extern_t, wasm_functype_t, wasm_store_t, wasm_val_t};
|
||||
use crate::{wasm_name_t, wasm_trap_t, ExternHost};
|
||||
use std::ffi::c_void;
|
||||
use std::panic::{self, AssertUnwindSafe};
|
||||
use std::ptr;
|
||||
use std::str;
|
||||
use wasmtime::{Caller, Extern, Func, HostRef, Trap};
|
||||
|
||||
#[derive(Clone)]
|
||||
#[repr(transparent)]
|
||||
pub struct wasm_func_t {
|
||||
ext: wasm_extern_t,
|
||||
}
|
||||
|
||||
wasmtime_c_api_macros::declare_ref!(wasm_func_t);
|
||||
|
||||
#[repr(C)]
|
||||
pub struct wasmtime_caller_t<'a> {
|
||||
caller: Caller<'a>,
|
||||
}
|
||||
|
||||
pub type wasm_func_callback_t =
|
||||
extern "C" fn(args: *const wasm_val_t, results: *mut wasm_val_t) -> Option<Box<wasm_trap_t>>;
|
||||
|
||||
pub type wasm_func_callback_with_env_t = extern "C" fn(
|
||||
env: *mut std::ffi::c_void,
|
||||
args: *const wasm_val_t,
|
||||
results: *mut wasm_val_t,
|
||||
) -> Option<Box<wasm_trap_t>>;
|
||||
|
||||
pub type wasmtime_func_callback_t = extern "C" fn(
|
||||
caller: *const wasmtime_caller_t,
|
||||
args: *const wasm_val_t,
|
||||
results: *mut wasm_val_t,
|
||||
) -> Option<Box<wasm_trap_t>>;
|
||||
|
||||
pub type wasmtime_func_callback_with_env_t = extern "C" fn(
|
||||
caller: *const wasmtime_caller_t,
|
||||
env: *mut std::ffi::c_void,
|
||||
args: *const wasm_val_t,
|
||||
results: *mut wasm_val_t,
|
||||
) -> Option<Box<wasm_trap_t>>;
|
||||
|
||||
struct Finalizer {
|
||||
env: *mut c_void,
|
||||
finalizer: Option<extern "C" fn(*mut c_void)>,
|
||||
}
|
||||
|
||||
impl Drop for Finalizer {
|
||||
fn drop(&mut self) {
|
||||
if let Some(f) = self.finalizer {
|
||||
f(self.env);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl wasm_func_t {
|
||||
pub(crate) fn try_from(e: &wasm_extern_t) -> Option<&wasm_func_t> {
|
||||
match &e.which {
|
||||
ExternHost::Func(_) => Some(unsafe { &*(e as *const _ as *const _) }),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn func(&self) -> &HostRef<Func> {
|
||||
match &self.ext.which {
|
||||
ExternHost::Func(f) => f,
|
||||
_ => unsafe { std::hint::unreachable_unchecked() },
|
||||
}
|
||||
}
|
||||
|
||||
fn anyref(&self) -> wasmtime::AnyRef {
|
||||
self.func().anyref()
|
||||
}
|
||||
}
|
||||
|
||||
fn create_function(
|
||||
store: &wasm_store_t,
|
||||
ty: &wasm_functype_t,
|
||||
func: impl Fn(Caller<'_>, *const wasm_val_t, *mut wasm_val_t) -> Option<Box<wasm_trap_t>> + 'static,
|
||||
) -> Box<wasm_func_t> {
|
||||
let store = &store.store.borrow();
|
||||
let ty = ty.ty().ty.clone();
|
||||
let func = Func::new(store, ty, move |caller, params, results| {
|
||||
let params = params
|
||||
.iter()
|
||||
.map(|p| wasm_val_t::from_val(p))
|
||||
.collect::<Vec<_>>();
|
||||
let mut out_results = vec![wasm_val_t::default(); results.len()];
|
||||
let out = func(caller, params.as_ptr(), out_results.as_mut_ptr());
|
||||
if let Some(trap) = out {
|
||||
return Err(trap.trap.borrow().clone());
|
||||
}
|
||||
for i in 0..results.len() {
|
||||
results[i] = out_results[i].val();
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
Box::new(wasm_func_t {
|
||||
ext: wasm_extern_t {
|
||||
which: ExternHost::Func(HostRef::new(func)),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_func_new(
|
||||
store: &wasm_store_t,
|
||||
ty: &wasm_functype_t,
|
||||
callback: wasm_func_callback_t,
|
||||
) -> Box<wasm_func_t> {
|
||||
create_function(store, ty, move |_caller, params, results| {
|
||||
callback(params, results)
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_func_new(
|
||||
store: &wasm_store_t,
|
||||
ty: &wasm_functype_t,
|
||||
callback: wasmtime_func_callback_t,
|
||||
) -> Box<wasm_func_t> {
|
||||
create_function(store, ty, move |caller, params, results| {
|
||||
callback(&wasmtime_caller_t { caller }, params, results)
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_func_new_with_env(
|
||||
store: &wasm_store_t,
|
||||
ty: &wasm_functype_t,
|
||||
callback: wasm_func_callback_with_env_t,
|
||||
env: *mut c_void,
|
||||
finalizer: Option<extern "C" fn(arg1: *mut std::ffi::c_void)>,
|
||||
) -> Box<wasm_func_t> {
|
||||
let finalizer = Finalizer { env, finalizer };
|
||||
create_function(store, ty, move |_caller, params, results| {
|
||||
callback(finalizer.env, params, results)
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasmtime_func_new_with_env(
|
||||
store: &wasm_store_t,
|
||||
ty: &wasm_functype_t,
|
||||
callback: wasmtime_func_callback_with_env_t,
|
||||
env: *mut c_void,
|
||||
finalizer: Option<extern "C" fn(*mut c_void)>,
|
||||
) -> Box<wasm_func_t> {
|
||||
let finalizer = Finalizer { env, finalizer };
|
||||
create_function(store, ty, move |caller, params, results| {
|
||||
callback(
|
||||
&wasmtime_caller_t { caller },
|
||||
finalizer.env,
|
||||
params,
|
||||
results,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasm_func_call(
|
||||
func: &wasm_func_t,
|
||||
args: *const wasm_val_t,
|
||||
results: *mut wasm_val_t,
|
||||
) -> *mut wasm_trap_t {
|
||||
let func = func.func().borrow();
|
||||
let mut params = Vec::with_capacity(func.param_arity());
|
||||
for i in 0..func.param_arity() {
|
||||
let val = &(*args.add(i));
|
||||
params.push(val.val());
|
||||
}
|
||||
|
||||
// We're calling arbitrary code here most of the time, and we in general
|
||||
// want to try to insulate callers against bugs in wasmtime/wasi/etc if we
|
||||
// can. As a result we catch panics here and transform them to traps to
|
||||
// allow the caller to have any insulation possible against Rust panics.
|
||||
let result = panic::catch_unwind(AssertUnwindSafe(|| func.call(¶ms)));
|
||||
match result {
|
||||
Ok(Ok(out)) => {
|
||||
for i in 0..func.result_arity() {
|
||||
let val = &mut (*results.add(i));
|
||||
*val = wasm_val_t::from_val(&out[i]);
|
||||
}
|
||||
ptr::null_mut()
|
||||
}
|
||||
Ok(Err(trap)) => {
|
||||
let trap = Box::new(wasm_trap_t {
|
||||
trap: HostRef::new(trap),
|
||||
});
|
||||
Box::into_raw(trap)
|
||||
}
|
||||
Err(panic) => {
|
||||
let trap = if let Some(msg) = panic.downcast_ref::<String>() {
|
||||
Trap::new(msg)
|
||||
} else if let Some(msg) = panic.downcast_ref::<&'static str>() {
|
||||
Trap::new(*msg)
|
||||
} else {
|
||||
Trap::new("rust panic happened")
|
||||
};
|
||||
let trap = Box::new(wasm_trap_t {
|
||||
trap: HostRef::new(trap),
|
||||
});
|
||||
Box::into_raw(trap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_func_type(f: &wasm_func_t) -> Box<wasm_functype_t> {
|
||||
Box::new(wasm_functype_t::new(f.func().borrow().ty().clone()))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_func_param_arity(f: &wasm_func_t) -> usize {
|
||||
f.func().borrow().param_arity()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_func_result_arity(f: &wasm_func_t) -> usize {
|
||||
f.func().borrow().result_arity()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_func_as_extern(f: &mut wasm_func_t) -> &mut wasm_extern_t {
|
||||
&mut (*f).ext
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_caller_export_get(
|
||||
caller: &wasmtime_caller_t,
|
||||
name: &wasm_name_t,
|
||||
) -> Option<Box<wasm_extern_t>> {
|
||||
let name = str::from_utf8(name.as_slice()).ok()?;
|
||||
let export = caller.caller.get_export(name)?;
|
||||
let which = match export {
|
||||
Extern::Func(f) => ExternHost::Func(HostRef::new(f.clone())),
|
||||
Extern::Global(g) => ExternHost::Global(HostRef::new(g.clone())),
|
||||
Extern::Memory(m) => ExternHost::Memory(HostRef::new(m.clone())),
|
||||
Extern::Table(t) => ExternHost::Table(HostRef::new(t.clone())),
|
||||
};
|
||||
Some(Box::new(wasm_extern_t { which }))
|
||||
}
|
||||
Reference in New Issue
Block a user