Merge pull request #1411 from peterhuene/reimplement-dotnet-api
Reimplement the .NET API in terms of a linker
This commit is contained in:
@@ -52,6 +52,7 @@ WASI_DECLARE_OWN(instance)
|
||||
|
||||
WASI_API_EXTERN own wasi_instance_t* wasi_instance_new(
|
||||
wasm_store_t* store,
|
||||
const char* name,
|
||||
own wasi_config_t* config,
|
||||
own wasm_trap_t** trap
|
||||
);
|
||||
|
||||
@@ -50,8 +50,6 @@ WASMTIME_CONFIG_PROP(profiler, wasmtime_profiling_strategy_t)
|
||||
|
||||
// Converts from the text format of WebAssembly to to the binary format.
|
||||
//
|
||||
// * `engine` - a previously created engine which will drive allocations and
|
||||
// such
|
||||
// * `wat` - this it the input buffer with the WebAssembly Text Format inside of
|
||||
// it. This will be parsed and converted to the binary format.
|
||||
// * `ret` - if the conversion is successful, this byte vector is filled in with
|
||||
@@ -62,7 +60,6 @@ WASMTIME_CONFIG_PROP(profiler, wasmtime_profiling_strategy_t)
|
||||
//
|
||||
// Returns `true` if conversion succeeded, or `false` if it failed.
|
||||
WASM_API_EXTERN bool wasmtime_wat2wasm(
|
||||
wasm_engine_t *engine,
|
||||
const wasm_byte_vec_t *wat,
|
||||
own wasm_byte_vec_t *ret,
|
||||
own wasm_byte_vec_t *error_message
|
||||
@@ -77,6 +74,8 @@ WASMTIME_DECLARE_OWN(linker)
|
||||
|
||||
WASM_API_EXTERN own wasmtime_linker_t* wasmtime_linker_new(wasm_store_t* store);
|
||||
|
||||
WASM_API_EXTERN void wasmtime_linker_allow_shadowing(wasmtime_linker_t* linker, bool allow_shadowing);
|
||||
|
||||
WASM_API_EXTERN bool wasmtime_linker_define(
|
||||
wasmtime_linker_t *linker,
|
||||
const wasm_name_t *module,
|
||||
@@ -101,6 +100,23 @@ WASM_API_EXTERN wasm_instance_t* wasmtime_linker_instantiate(
|
||||
own wasm_trap_t **trap
|
||||
);
|
||||
|
||||
typedef struct wasmtime_caller_t wasmtime_caller_t;
|
||||
|
||||
typedef own wasm_trap_t* (*wasmtime_func_callback_t)(const wasmtime_caller_t* caller, const wasm_val_t args[], wasm_val_t results[]);
|
||||
typedef own wasm_trap_t* (*wasmtime_func_callback_with_env_t)(const wasmtime_caller_t* caller, void* env, const wasm_val_t args[], wasm_val_t results[]);
|
||||
|
||||
WASM_API_EXTERN own wasm_func_t* wasmtime_func_new(wasm_store_t*, const wasm_functype_t*, wasmtime_func_callback_t callback);
|
||||
|
||||
WASM_API_EXTERN own wasm_func_t* wasmtime_func_new_with_env(
|
||||
wasm_store_t* store,
|
||||
const wasm_functype_t* type,
|
||||
wasmtime_func_callback_with_env_t callback,
|
||||
void* env,
|
||||
void (*finalizer)(void*)
|
||||
);
|
||||
|
||||
WASM_API_EXTERN own wasm_extern_t* wasmtime_caller_export_get(const wasmtime_caller_t* caller, const wasm_name_t* name);
|
||||
|
||||
#undef own
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -109,7 +109,6 @@ pub unsafe extern "C" fn wasmtime_config_profiler_set(
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_wat2wasm(
|
||||
_engine: *mut wasm_engine_t,
|
||||
wat: *const wasm_byte_vec_t,
|
||||
ret: *mut wasm_byte_vec_t,
|
||||
error: *mut wasm_byte_vec_t,
|
||||
@@ -149,6 +148,14 @@ pub unsafe extern "C" fn wasmtime_linker_new(store: *mut wasm_store_t) -> *mut w
|
||||
}))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_linker_allow_shadowing(
|
||||
linker: *mut wasmtime_linker_t,
|
||||
allow_shadowing: bool,
|
||||
) {
|
||||
(*linker).linker.allow_shadowing(allow_shadowing);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_linker_delete(linker: *mut wasmtime_linker_t) {
|
||||
drop(Box::from_raw(linker));
|
||||
@@ -185,7 +192,7 @@ pub unsafe extern "C" fn wasmtime_linker_define_wasi(
|
||||
instance: *const wasi_instance_t,
|
||||
) -> bool {
|
||||
let linker = &mut (*linker).linker;
|
||||
(*instance).wasi.add_to_linker(linker).is_ok()
|
||||
(*instance).add_to_linker(linker).is_ok()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -213,3 +220,67 @@ pub unsafe extern "C" fn wasmtime_linker_instantiate(
|
||||
let linker = &(*linker).linker;
|
||||
handle_instantiate(linker.instantiate(&(*module).module.borrow()), trap)
|
||||
}
|
||||
|
||||
pub type wasmtime_func_callback_t = unsafe extern "C" fn(
|
||||
caller: *const wasmtime_caller_t,
|
||||
args: *const wasm_val_t,
|
||||
results: *mut wasm_val_t,
|
||||
) -> *mut wasm_trap_t;
|
||||
|
||||
pub type wasmtime_func_callback_with_env_t = unsafe extern "C" fn(
|
||||
caller: *const wasmtime_caller_t,
|
||||
env: *mut std::ffi::c_void,
|
||||
args: *const wasm_val_t,
|
||||
results: *mut wasm_val_t,
|
||||
) -> *mut wasm_trap_t;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct wasmtime_caller_t<'a> {
|
||||
pub inner: wasmtime::Caller<'a>,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_func_new(
|
||||
store: *mut wasm_store_t,
|
||||
ty: *const wasm_functype_t,
|
||||
callback: wasmtime_func_callback_t,
|
||||
) -> *mut wasm_func_t {
|
||||
crate::create_function(store, ty, crate::Callback::Wasmtime(callback))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_func_new_with_env(
|
||||
store: *mut wasm_store_t,
|
||||
ty: *const wasm_functype_t,
|
||||
callback: wasmtime_func_callback_with_env_t,
|
||||
env: *mut std::ffi::c_void,
|
||||
finalizer: Option<unsafe extern "C" fn(arg1: *mut std::ffi::c_void)>,
|
||||
) -> *mut wasm_func_t {
|
||||
crate::create_function_with_env(
|
||||
store,
|
||||
ty,
|
||||
crate::CallbackWithEnv::Wasmtime(callback),
|
||||
env,
|
||||
finalizer,
|
||||
)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_caller_export_get(
|
||||
caller: *const wasmtime_caller_t,
|
||||
name: *const wasm_name_t,
|
||||
) -> *mut wasm_extern_t {
|
||||
let name = match str::from_utf8((*name).as_slice()) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return std::ptr::null_mut(),
|
||||
};
|
||||
match (*caller).inner.get_export(name).map(|e| match e {
|
||||
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(export) => Box::into_raw(Box::new(wasm_extern_t { which: export })),
|
||||
None => std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,16 +368,14 @@ impl wasm_func_t {
|
||||
}
|
||||
}
|
||||
|
||||
pub type wasm_func_callback_t = std::option::Option<
|
||||
unsafe extern "C" fn(args: *const wasm_val_t, results: *mut wasm_val_t) -> *mut wasm_trap_t,
|
||||
>;
|
||||
pub type wasm_func_callback_with_env_t = std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
env: *mut std::ffi::c_void,
|
||||
args: *const wasm_val_t,
|
||||
results: *mut wasm_val_t,
|
||||
) -> *mut wasm_trap_t,
|
||||
>;
|
||||
pub type wasm_func_callback_t =
|
||||
unsafe extern "C" fn(args: *const wasm_val_t, results: *mut wasm_val_t) -> *mut wasm_trap_t;
|
||||
|
||||
pub type wasm_func_callback_with_env_t = unsafe extern "C" fn(
|
||||
env: *mut std::ffi::c_void,
|
||||
args: *const wasm_val_t,
|
||||
results: *mut wasm_val_t,
|
||||
) -> *mut wasm_trap_t;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[repr(transparent)]
|
||||
@@ -622,22 +620,36 @@ impl wasm_val_t {
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasm_func_new(
|
||||
enum Callback {
|
||||
Wasm(wasm_func_callback_t),
|
||||
Wasmtime(crate::ext::wasmtime_func_callback_t),
|
||||
}
|
||||
|
||||
enum CallbackWithEnv {
|
||||
Wasm(wasm_func_callback_with_env_t),
|
||||
Wasmtime(crate::ext::wasmtime_func_callback_with_env_t),
|
||||
}
|
||||
|
||||
unsafe fn create_function(
|
||||
store: *mut wasm_store_t,
|
||||
ty: *const wasm_functype_t,
|
||||
callback: wasm_func_callback_t,
|
||||
callback: Callback,
|
||||
) -> *mut wasm_func_t {
|
||||
let store = &(*store).store.borrow();
|
||||
let ty = (*ty).functype.clone();
|
||||
let func = Func::new(store, ty, move |_, params, results| {
|
||||
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 func = callback.expect("wasm_func_callback_t fn");
|
||||
let out = func(params.as_ptr(), out_results.as_mut_ptr());
|
||||
let out = match callback {
|
||||
Callback::Wasm(callback) => callback(params.as_ptr(), out_results.as_mut_ptr()),
|
||||
Callback::Wasmtime(callback) => {
|
||||
let caller = crate::ext::wasmtime_caller_t { inner: caller };
|
||||
callback(&caller, params.as_ptr(), out_results.as_mut_ptr())
|
||||
}
|
||||
};
|
||||
if !out.is_null() {
|
||||
let trap: Box<wasm_trap_t> = Box::from_raw(out);
|
||||
return Err(trap.trap.borrow().clone());
|
||||
@@ -655,6 +667,74 @@ pub unsafe extern "C" fn wasm_func_new(
|
||||
Box::into_raw(func)
|
||||
}
|
||||
|
||||
unsafe fn create_function_with_env(
|
||||
store: *mut wasm_store_t,
|
||||
ty: *const wasm_functype_t,
|
||||
callback: CallbackWithEnv,
|
||||
env: *mut std::ffi::c_void,
|
||||
finalizer: Option<unsafe extern "C" fn(arg1: *mut std::ffi::c_void)>,
|
||||
) -> *mut wasm_func_t {
|
||||
let store = &(*store).store.borrow();
|
||||
let ty = (*ty).functype.clone();
|
||||
|
||||
// Create a small object which will run the finalizer when it's dropped, and
|
||||
// then we move this `run_finalizer` object into the closure below (via the
|
||||
// `drop(&run_finalizer)` statement so it's all dropped when the closure is
|
||||
// dropped.
|
||||
struct RunOnDrop<F: FnMut()>(F);
|
||||
impl<F: FnMut()> Drop for RunOnDrop<F> {
|
||||
fn drop(&mut self) {
|
||||
(self.0)();
|
||||
}
|
||||
}
|
||||
let run_finalizer = RunOnDrop(move || {
|
||||
if let Some(finalizer) = finalizer {
|
||||
finalizer(env);
|
||||
}
|
||||
});
|
||||
let func = Func::new(store, ty, move |caller, params, results| {
|
||||
drop(&run_finalizer);
|
||||
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 = match callback {
|
||||
CallbackWithEnv::Wasm(callback) => {
|
||||
callback(env, params.as_ptr(), out_results.as_mut_ptr())
|
||||
}
|
||||
CallbackWithEnv::Wasmtime(callback) => {
|
||||
let caller = crate::ext::wasmtime_caller_t { inner: caller };
|
||||
callback(&caller, env, params.as_ptr(), out_results.as_mut_ptr())
|
||||
}
|
||||
};
|
||||
if !out.is_null() {
|
||||
let trap: Box<wasm_trap_t> = Box::from_raw(out);
|
||||
return Err(trap.trap.borrow().clone());
|
||||
}
|
||||
for i in 0..results.len() {
|
||||
results[i] = out_results[i].val();
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let func = Box::new(wasm_func_t {
|
||||
ext: wasm_extern_t {
|
||||
which: ExternHost::Func(HostRef::new(func)),
|
||||
},
|
||||
});
|
||||
Box::into_raw(func)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasm_func_new(
|
||||
store: *mut wasm_store_t,
|
||||
ty: *const wasm_functype_t,
|
||||
callback: wasm_func_callback_t,
|
||||
) -> *mut wasm_func_t {
|
||||
create_function(store, ty, Callback::Wasm(callback))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasm_func_delete(f: *mut wasm_func_t) {
|
||||
let _ = Box::from_raw(f);
|
||||
@@ -896,49 +976,7 @@ pub unsafe extern "C" fn wasm_func_new_with_env(
|
||||
env: *mut std::ffi::c_void,
|
||||
finalizer: Option<unsafe extern "C" fn(arg1: *mut std::ffi::c_void)>,
|
||||
) -> *mut wasm_func_t {
|
||||
let store = &(*store).store.borrow();
|
||||
let ty = (*ty).functype.clone();
|
||||
|
||||
// Create a small object which will run the finalizer when it's dropped, and
|
||||
// then we move this `run_finalizer` object into the closure below (via the
|
||||
// `drop(&run_finalizer)` statement so it's all dropped when the closure is
|
||||
// dropped.
|
||||
struct RunOnDrop<F: FnMut()>(F);
|
||||
impl<F: FnMut()> Drop for RunOnDrop<F> {
|
||||
fn drop(&mut self) {
|
||||
(self.0)();
|
||||
}
|
||||
}
|
||||
let run_finalizer = RunOnDrop(move || {
|
||||
if let Some(finalizer) = finalizer {
|
||||
finalizer(env);
|
||||
}
|
||||
});
|
||||
let func = Func::new(store, ty, move |_, params, results| {
|
||||
drop(&run_finalizer);
|
||||
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 func = callback.expect("wasm_func_callback_with_env_t fn");
|
||||
let out = func(env, params.as_ptr(), out_results.as_mut_ptr());
|
||||
if !out.is_null() {
|
||||
let trap: Box<wasm_trap_t> = Box::from_raw(out);
|
||||
return Err(trap.trap.borrow().clone());
|
||||
}
|
||||
for i in 0..results.len() {
|
||||
results[i] = out_results[i].val();
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let func = Box::new(wasm_func_t {
|
||||
ext: wasm_extern_t {
|
||||
which: ExternHost::Func(HostRef::new(func)),
|
||||
},
|
||||
});
|
||||
Box::into_raw(func)
|
||||
create_function_with_env(store, ty, CallbackWithEnv::Wasm(callback), env, finalizer)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
//! The WASI embedding API definitions for Wasmtime.
|
||||
use crate::{wasm_extern_t, wasm_importtype_t, wasm_store_t, wasm_trap_t, ExternHost, ExternType};
|
||||
use anyhow::Result;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CStr;
|
||||
use std::fs::File;
|
||||
use std::os::raw::{c_char, c_int};
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::slice;
|
||||
use wasi_common::{preopen_dir, WasiCtxBuilder};
|
||||
use wasmtime::{HostRef, Trap};
|
||||
use wasmtime_wasi::Wasi;
|
||||
use wasi_common::{
|
||||
old::snapshot_0::WasiCtxBuilder as WasiSnapshot0CtxBuilder, preopen_dir,
|
||||
WasiCtxBuilder as WasiPreview1CtxBuilder,
|
||||
};
|
||||
use wasmtime::{HostRef, Linker, Store, Trap};
|
||||
use wasmtime_wasi::{old::snapshot_0::Wasi as WasiSnapshot0, Wasi as WasiPreview1};
|
||||
|
||||
unsafe fn cstr_to_path<'a>(path: *const c_char) -> Option<&'a Path> {
|
||||
CStr::from_ptr(path).to_str().map(Path::new).ok()
|
||||
@@ -22,18 +26,32 @@ unsafe fn create_file(path: *const c_char) -> Option<File> {
|
||||
File::create(cstr_to_path(path)?).ok()
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct wasi_config_t {
|
||||
builder: WasiCtxBuilder,
|
||||
pub enum WasiModule {
|
||||
Snapshot0(WasiSnapshot0),
|
||||
Preview1(WasiPreview1),
|
||||
}
|
||||
|
||||
impl wasi_config_t {}
|
||||
impl WasiModule {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub struct wasi_config_t {
|
||||
args: Vec<Vec<u8>>,
|
||||
env: Vec<(Vec<u8>, Vec<u8>)>,
|
||||
stdin: Option<File>,
|
||||
stdout: Option<File>,
|
||||
stderr: Option<File>,
|
||||
preopens: Vec<(File, PathBuf)>,
|
||||
inherit_args: bool,
|
||||
inherit_env: bool,
|
||||
inherit_stdin: bool,
|
||||
inherit_stdout: bool,
|
||||
inherit_stderr: bool,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_new() -> *mut wasi_config_t {
|
||||
Box::into_raw(Box::new(wasi_config_t {
|
||||
builder: WasiCtxBuilder::new(),
|
||||
}))
|
||||
Box::into_raw(Box::new(wasi_config_t::default()))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -47,16 +65,17 @@ pub unsafe extern "C" fn wasi_config_set_argv(
|
||||
argc: c_int,
|
||||
argv: *const *const c_char,
|
||||
) {
|
||||
(*config).builder.args(
|
||||
slice::from_raw_parts(argv, argc as usize)
|
||||
.iter()
|
||||
.map(|a| slice::from_raw_parts(*a as *const u8, CStr::from_ptr(*a).to_bytes().len())),
|
||||
);
|
||||
(*config).args = slice::from_raw_parts(argv, argc as usize)
|
||||
.iter()
|
||||
.map(|p| CStr::from_ptr(*p).to_bytes().to_owned())
|
||||
.collect();
|
||||
(*config).inherit_args = false;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_inherit_argv(config: *mut wasi_config_t) {
|
||||
(*config).builder.inherit_args();
|
||||
(*config).args.clear();
|
||||
(*config).inherit_args = true;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -69,17 +88,22 @@ pub unsafe extern "C" fn wasi_config_set_env(
|
||||
let names = slice::from_raw_parts(names, envc as usize);
|
||||
let values = slice::from_raw_parts(values, envc as usize);
|
||||
|
||||
(*config).builder.envs(
|
||||
names
|
||||
.iter()
|
||||
.map(|p| CStr::from_ptr(*p).to_bytes())
|
||||
.zip(values.iter().map(|p| CStr::from_ptr(*p).to_bytes())),
|
||||
);
|
||||
(*config).env = names
|
||||
.iter()
|
||||
.map(|p| CStr::from_ptr(*p).to_bytes().to_owned())
|
||||
.zip(
|
||||
values
|
||||
.iter()
|
||||
.map(|p| CStr::from_ptr(*p).to_bytes().to_owned()),
|
||||
)
|
||||
.collect();
|
||||
(*config).inherit_env = false;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_inherit_env(config: *mut wasi_config_t) {
|
||||
(*config).builder.inherit_env();
|
||||
(*config).env.clear();
|
||||
(*config).inherit_env = true;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -92,14 +116,16 @@ pub unsafe extern "C" fn wasi_config_set_stdin_file(
|
||||
None => return false,
|
||||
};
|
||||
|
||||
(*config).builder.stdin(file);
|
||||
(*config).stdin = Some(file);
|
||||
(*config).inherit_stdin = false;
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_inherit_stdin(config: *mut wasi_config_t) {
|
||||
(*config).builder.inherit_stdin();
|
||||
(*config).stdin = None;
|
||||
(*config).inherit_stdin = true;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -112,14 +138,16 @@ pub unsafe extern "C" fn wasi_config_set_stdout_file(
|
||||
None => return false,
|
||||
};
|
||||
|
||||
(*config).builder.stdout(file);
|
||||
(*config).stdout = Some(file);
|
||||
(*config).inherit_stdout = false;
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_inherit_stdout(config: *mut wasi_config_t) {
|
||||
(*config).builder.inherit_stdout();
|
||||
(*config).stdout = None;
|
||||
(*config).inherit_stdout = true;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -132,14 +160,16 @@ pub unsafe extern "C" fn wasi_config_set_stderr_file(
|
||||
None => return false,
|
||||
};
|
||||
|
||||
(*config).builder.stderr(file);
|
||||
(*config).stderr = Some(file);
|
||||
(*config).inherit_stderr = false;
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_inherit_stderr(config: *mut wasi_config_t) {
|
||||
(*config).builder.inherit_stderr();
|
||||
(*config).stderr = None;
|
||||
(*config).inherit_stderr = true;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -161,29 +191,110 @@ pub unsafe extern "C" fn wasi_config_preopen_dir(
|
||||
None => return false,
|
||||
};
|
||||
|
||||
(*config).builder.preopened_dir(dir, guest_path);
|
||||
(*config).preopens.push((dir, guest_path.to_owned()));
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
enum WasiInstance {
|
||||
Preview1(WasiPreview1),
|
||||
Snapshot0(WasiSnapshot0),
|
||||
}
|
||||
|
||||
macro_rules! config_to_builder {
|
||||
($builder:ident, $config:ident) => {{
|
||||
let mut builder = $builder::new();
|
||||
|
||||
if $config.inherit_args {
|
||||
builder.inherit_args();
|
||||
} else if !$config.args.is_empty() {
|
||||
builder.args($config.args);
|
||||
}
|
||||
|
||||
if $config.inherit_env {
|
||||
builder.inherit_env();
|
||||
} else if !$config.env.is_empty() {
|
||||
builder.envs($config.env);
|
||||
}
|
||||
|
||||
if $config.inherit_stdin {
|
||||
builder.inherit_stdin();
|
||||
} else if let Some(file) = $config.stdin {
|
||||
builder.stdin(file);
|
||||
}
|
||||
|
||||
if $config.inherit_stdout {
|
||||
builder.inherit_stdout();
|
||||
} else if let Some(file) = $config.stdout {
|
||||
builder.stdout(file);
|
||||
}
|
||||
|
||||
if $config.inherit_stderr {
|
||||
builder.inherit_stderr();
|
||||
} else if let Some(file) = $config.stderr {
|
||||
builder.stderr(file);
|
||||
}
|
||||
|
||||
for preopen in $config.preopens {
|
||||
builder.preopened_dir(preopen.0, preopen.1);
|
||||
}
|
||||
|
||||
builder
|
||||
}};
|
||||
}
|
||||
|
||||
fn create_snapshot0_instance(store: &Store, config: wasi_config_t) -> Result<WasiInstance, String> {
|
||||
Ok(WasiInstance::Snapshot0(WasiSnapshot0::new(
|
||||
store,
|
||||
config_to_builder!(WasiSnapshot0CtxBuilder, config)
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?,
|
||||
)))
|
||||
}
|
||||
|
||||
fn create_preview1_instance(store: &Store, config: wasi_config_t) -> Result<WasiInstance, String> {
|
||||
Ok(WasiInstance::Preview1(WasiPreview1::new(
|
||||
store,
|
||||
config_to_builder!(WasiPreview1CtxBuilder, config)
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?,
|
||||
)))
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct wasi_instance_t {
|
||||
pub wasi: Wasi,
|
||||
wasi: WasiInstance,
|
||||
export_cache: HashMap<String, Box<wasm_extern_t>>,
|
||||
}
|
||||
|
||||
impl wasi_instance_t {
|
||||
pub fn add_to_linker(&self, linker: &mut Linker) -> Result<()> {
|
||||
match &self.wasi {
|
||||
WasiInstance::Snapshot0(w) => w.add_to_linker(linker),
|
||||
WasiInstance::Preview1(w) => w.add_to_linker(linker),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_instance_new(
|
||||
store: *mut wasm_store_t,
|
||||
name: *const c_char,
|
||||
config: *mut wasi_config_t,
|
||||
trap: *mut *mut wasm_trap_t,
|
||||
) -> *mut wasi_instance_t {
|
||||
let store = &(*store).store.borrow();
|
||||
let mut config = Box::from_raw(config);
|
||||
let config = Box::from_raw(config);
|
||||
|
||||
match config.builder.build() {
|
||||
Ok(ctx) => Box::into_raw(Box::new(wasi_instance_t {
|
||||
wasi: Wasi::new(store, ctx),
|
||||
let result = match CStr::from_ptr(name).to_str().unwrap_or("") {
|
||||
"wasi_snapshot_preview1" => create_preview1_instance(store, *config),
|
||||
"wasi_unstable" => create_snapshot0_instance(store, *config),
|
||||
_ => Err("unsupported WASI version".into()),
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(wasi) => Box::into_raw(Box::new(wasi_instance_t {
|
||||
wasi,
|
||||
export_cache: HashMap::new(),
|
||||
})),
|
||||
Err(e) => {
|
||||
@@ -206,20 +317,32 @@ pub unsafe extern "C" fn wasi_instance_bind_import(
|
||||
instance: *mut wasi_instance_t,
|
||||
import: *const wasm_importtype_t,
|
||||
) -> *const wasm_extern_t {
|
||||
// TODO: support previous versions?
|
||||
if (*import).ty.module() != "wasi_snapshot_preview1" {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
// The import should be a function (WASI only exports functions)
|
||||
let func_type = match (*import).ty.ty() {
|
||||
ExternType::Func(f) => f,
|
||||
_ => return std::ptr::null_mut(),
|
||||
};
|
||||
|
||||
let module = (*import).ty.module();
|
||||
let name = (*import).ty.name();
|
||||
|
||||
match (*instance).wasi.get_export(name) {
|
||||
let import = match &(*instance).wasi {
|
||||
WasiInstance::Preview1(wasi) => {
|
||||
if module != "wasi_snapshot_preview1" {
|
||||
return std::ptr::null();
|
||||
}
|
||||
wasi.get_export(name)
|
||||
}
|
||||
WasiInstance::Snapshot0(wasi) => {
|
||||
if module != "wasi_unstable" {
|
||||
return std::ptr::null();
|
||||
}
|
||||
|
||||
wasi.get_export(name)
|
||||
}
|
||||
};
|
||||
|
||||
match import {
|
||||
Some(export) => {
|
||||
if export.ty() != func_type {
|
||||
return std::ptr::null_mut();
|
||||
|
||||
@@ -109,7 +109,7 @@ dotnet new console
|
||||
To use Wasmtime for .NET from the project, we need to add a reference to the [Wasmtime NuGet package](https://www.nuget.org/packages/Wasmtime):
|
||||
|
||||
```text
|
||||
dotnet add package --version 0.0.1-alpha1 wasmtime
|
||||
dotnet add package --version 0.14.0-preview1 wasmtime
|
||||
```
|
||||
|
||||
_Note that the `--version` option is required because the package is currently prerelease._
|
||||
@@ -126,38 +126,32 @@ using Wasmtime;
|
||||
|
||||
namespace Tutorial
|
||||
{
|
||||
class Host : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("print", Module="env")]
|
||||
public void Print(int address, int length)
|
||||
{
|
||||
var message = Instance.Externs.Memories[0].ReadString(address, length);
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
}
|
||||
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
using var engine = new Engine();
|
||||
using var store = engine.CreateStore();
|
||||
using var module = store.CreateModule("hello.wasm");
|
||||
using dynamic instance = module.Instantiate(new Host());
|
||||
using var host = new Host();
|
||||
|
||||
host.DefineFunction(
|
||||
"env",
|
||||
"print",
|
||||
(Caller caller, int address, int length) => {
|
||||
Console.WriteLine(caller.GetMemory("mem").ReadString(address, length));
|
||||
}
|
||||
);
|
||||
|
||||
using var module = host.LoadModule("hello.wasm");
|
||||
|
||||
using dynamic instance = host.Instantiate(module);
|
||||
instance.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `Host` class is responsible for implementing the imported [functions](https://webassembly.github.io/spec/core/syntax/modules.html#functions), [globals](https://webassembly.github.io/spec/core/syntax/modules.html#globals), [memories](https://webassembly.github.io/spec/core/syntax/modules.html#memories), and [tables](https://webassembly.github.io/spec/core/syntax/modules.html#syntax-table) for the WebAssembly module. For Wasmtime for .NET, this is done via the [`Import`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.ImportAttribute.html) attribute applied to functions and fields of type [`Global<T>`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.Global-1.html), [`MutableGlobal<T>`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.MutableGlobal-1.html), and [`Memory`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.Memory.html) (support for WebAssembly tables is not yet implemented). The [`Instance`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.IHost.html#Wasmtime_IHost_Instance) property of the host is set during instantiation of the WebAssembly module.
|
||||
The `Host` class is responsible for implementing an environment that WebAssembly modules can execute in.
|
||||
|
||||
Here the host is implementing an import of `print` in the `env` module, which is the default import module name for WebAssembly modules compiled using the Rust toolchain.
|
||||
|
||||
The [`Engine`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.Engine.html) is used to create a [`Store`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.Store.html) that will store all Wasmtime runtime objects, such as WebAssembly modules and their instantiations.
|
||||
Here we are creating a host that is implementing a function named `print` in the `env` module, which is the default import module name for WebAssembly modules compiled using the Rust toolchain.
|
||||
|
||||
A WebAssembly module _instantiation_ is the stateful representation of a module that can be executed. Here, the code is casting the [`Instance`](https://peterhuene.github.io/wasmtime.net/api/Wasmtime.Instance.html) to [`dynamic`](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/types/using-type-dynamic) which allows us to easily invoke the `run` function that was exported by the WebAssembly module.
|
||||
|
||||
@@ -165,7 +159,7 @@ Alternatively, the `run` function could be invoked without using the runtime bin
|
||||
|
||||
```c#
|
||||
...
|
||||
using var instance = module.Instantiate(new Host());
|
||||
using var instance = host.Instantiate(module);
|
||||
instance.Externs.Functions[0].Invoke();
|
||||
...
|
||||
```
|
||||
|
||||
@@ -3,29 +3,25 @@ using Wasmtime;
|
||||
|
||||
namespace HelloExample
|
||||
{
|
||||
class Host : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("print_global")]
|
||||
public void PrintGlobal()
|
||||
{
|
||||
Console.WriteLine($"The value of the global is: {Global.Value}.");
|
||||
}
|
||||
|
||||
[Import("global")]
|
||||
public readonly MutableGlobal<int> Global = new MutableGlobal<int>(1);
|
||||
}
|
||||
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
using var engine = new Engine();
|
||||
using var store = engine.CreateStore();
|
||||
using var module = store.CreateModule("global.wasm");
|
||||
using dynamic instance = module.Instantiate(new Host());
|
||||
using var host = new Host();
|
||||
|
||||
var global = host.DefineMutableGlobal("", "global", 1);
|
||||
|
||||
host.DefineFunction(
|
||||
"",
|
||||
"print_global",
|
||||
() => {
|
||||
Console.WriteLine($"The value of the global is: {global.Value}.");
|
||||
}
|
||||
);
|
||||
|
||||
using var module = host.LoadModule("global.wasm");
|
||||
|
||||
using dynamic instance = host.Instantiate(module);
|
||||
instance.run(20);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,26 +3,21 @@ using Wasmtime;
|
||||
|
||||
namespace HelloExample
|
||||
{
|
||||
class Host : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("hello")]
|
||||
public void SayHello()
|
||||
{
|
||||
Console.WriteLine("Hello from C#, WebAssembly!");
|
||||
}
|
||||
}
|
||||
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
using var engine = new Engine();
|
||||
using var store = engine.CreateStore();
|
||||
using var module = store.CreateModule("hello.wasm");
|
||||
using dynamic instance = module.Instantiate(new Host());
|
||||
using var host = new Host();
|
||||
|
||||
host.DefineFunction(
|
||||
"",
|
||||
"hello",
|
||||
() => Console.WriteLine("Hello from C#, WebAssembly!")
|
||||
);
|
||||
|
||||
using var module = host.LoadModule("hello.wasm");
|
||||
|
||||
using dynamic instance = host.Instantiate(module);
|
||||
instance.run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,27 +3,24 @@ using Wasmtime;
|
||||
|
||||
namespace HelloExample
|
||||
{
|
||||
class Host : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("log")]
|
||||
public void Log(int address, int length)
|
||||
{
|
||||
var message = Instance.Externs.Memories[0].ReadString(address, length);
|
||||
Console.WriteLine($"Message from WebAssembly: {message}");
|
||||
}
|
||||
}
|
||||
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
using var engine = new Engine();
|
||||
using var store = engine.CreateStore();
|
||||
using var module = store.CreateModule("memory.wasm");
|
||||
using dynamic instance = module.Instantiate(new Host());
|
||||
using var host = new Host();
|
||||
|
||||
host.DefineFunction(
|
||||
"",
|
||||
"log",
|
||||
(Caller caller, int address, int length) => {
|
||||
var message = caller.GetMemory("mem").ReadString(address, length);
|
||||
Console.WriteLine($"Message from WebAssembly: {message}");
|
||||
}
|
||||
);
|
||||
|
||||
using var module = host.LoadModule("memory.wasm");
|
||||
|
||||
using dynamic instance = host.Instantiate(module);
|
||||
instance.run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using Wasmtime.Imports;
|
||||
|
||||
namespace Wasmtime.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an abstract host binding.
|
||||
/// </summary>
|
||||
internal abstract class Binding
|
||||
{
|
||||
public abstract SafeHandle Bind(Store store, IHost host);
|
||||
|
||||
public static WasmtimeException CreateBindingException(Import import, MemberInfo member, string message)
|
||||
{
|
||||
return new WasmtimeException($"Unable to bind '{member.DeclaringType.Name}.{member.Name}' to WebAssembly import '{import}': {message}.");
|
||||
}
|
||||
|
||||
public static List<Binding> GetImportBindings(Module module, Wasi wasi = null, IHost host = null)
|
||||
{
|
||||
if (module is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(module));
|
||||
}
|
||||
|
||||
var bindings = new List<Binding>();
|
||||
var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;
|
||||
var type = host?.GetType();
|
||||
var methods = type?.GetMethods(flags).Where(m => !m.IsSpecialName && Attribute.IsDefined(m, typeof(ImportAttribute)));
|
||||
var fields = type?.GetFields(flags).Where(m => !m.IsSpecialName && Attribute.IsDefined(m, typeof(ImportAttribute)));
|
||||
|
||||
foreach (var import in module.Imports.All)
|
||||
{
|
||||
var wasiBinding = wasi?.Bind(import);
|
||||
if (!(wasiBinding is null))
|
||||
{
|
||||
bindings.Add(wasiBinding);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (import)
|
||||
{
|
||||
case FunctionImport func:
|
||||
bindings.Add(BindFunction(func, methods));
|
||||
break;
|
||||
|
||||
case GlobalImport global:
|
||||
bindings.Add(BindGlobal(global, fields));
|
||||
break;
|
||||
|
||||
case MemoryImport memory:
|
||||
bindings.Add(BindMemory(memory, fields));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException("Unsupported import binding type.");
|
||||
}
|
||||
}
|
||||
|
||||
return bindings;
|
||||
}
|
||||
|
||||
private static FunctionBinding BindFunction(FunctionImport import, IEnumerable<MethodInfo> methods)
|
||||
{
|
||||
var method = methods?.Where(m =>
|
||||
{
|
||||
var attribute = (ImportAttribute)m.GetCustomAttribute(typeof(ImportAttribute));
|
||||
if (attribute is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return attribute.Name == import.Name &&
|
||||
((string.IsNullOrEmpty(attribute.Module) &&
|
||||
string.IsNullOrEmpty(import.ModuleName)) ||
|
||||
attribute.Module == import.ModuleName);
|
||||
}
|
||||
).FirstOrDefault();
|
||||
|
||||
if (method is null)
|
||||
{
|
||||
throw new WasmtimeException($"Failed to bind function import '{import}': the host does not contain a method with a matching 'Import' attribute.");
|
||||
}
|
||||
|
||||
return new FunctionBinding(import, method);
|
||||
}
|
||||
|
||||
private static GlobalBinding BindGlobal(GlobalImport import, IEnumerable<FieldInfo> fields)
|
||||
{
|
||||
var field = fields?.Where(f =>
|
||||
{
|
||||
var attribute = (ImportAttribute)f.GetCustomAttribute(typeof(ImportAttribute));
|
||||
return attribute.Name == import.Name &&
|
||||
((string.IsNullOrEmpty(attribute.Module) &&
|
||||
string.IsNullOrEmpty(import.ModuleName)) ||
|
||||
attribute.Module == import.ModuleName);
|
||||
}
|
||||
).FirstOrDefault();
|
||||
|
||||
if (field is null)
|
||||
{
|
||||
throw new WasmtimeException($"Failed to bind global import '{import}': the host does not contain a global field with a matching 'Import' attribute.");
|
||||
}
|
||||
|
||||
return new GlobalBinding(import, field);
|
||||
}
|
||||
|
||||
private static MemoryBinding BindMemory(MemoryImport import, IEnumerable<FieldInfo> fields)
|
||||
{
|
||||
var field = fields?.Where(f =>
|
||||
{
|
||||
var attribute = (ImportAttribute)f.GetCustomAttribute(typeof(ImportAttribute));
|
||||
return attribute.Name == import.Name &&
|
||||
((string.IsNullOrEmpty(attribute.Module) &&
|
||||
string.IsNullOrEmpty(import.ModuleName)) ||
|
||||
attribute.Module == import.ModuleName);
|
||||
}
|
||||
).FirstOrDefault();
|
||||
|
||||
if (field is null)
|
||||
{
|
||||
throw new WasmtimeException($"Failed to bind memory import '{import}': the host does not contain a memory field with a matching 'Import' attribute.");
|
||||
}
|
||||
|
||||
return new MemoryBinding(import, field);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,339 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Wasmtime.Imports;
|
||||
|
||||
namespace Wasmtime.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a host function binding.
|
||||
/// </summary>
|
||||
internal class FunctionBinding : Binding
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new function binding.
|
||||
/// </summary>
|
||||
/// <param name="import">The function import of the binding.</param>
|
||||
/// <param name="method">The method the import is bound to.</param>
|
||||
public FunctionBinding(FunctionImport import, MethodInfo method)
|
||||
{
|
||||
if (import is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(import));
|
||||
}
|
||||
|
||||
if (method is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(method));
|
||||
}
|
||||
|
||||
Import = import;
|
||||
Method = method;
|
||||
|
||||
Validate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The function import of the binding.
|
||||
/// </summary>
|
||||
public FunctionImport Import { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The method the import is bound to.
|
||||
/// </summary>
|
||||
public MethodInfo Method { get; private set; }
|
||||
|
||||
public override SafeHandle Bind(Store store, IHost host)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var parameters = Interop.ToValueTypeVec(Import.Parameters);
|
||||
var results = Interop.ToValueTypeVec(Import.Results);
|
||||
using var funcType = Interop.wasm_functype_new(ref parameters, ref results);
|
||||
var callback = CreateCallback(store, host);
|
||||
var func = Interop.wasm_func_new(store.Handle, funcType, callback);
|
||||
// Store the callback with the safe handle to keep the delegate GC reachable
|
||||
func.Callback = callback;
|
||||
return func;
|
||||
}
|
||||
}
|
||||
|
||||
private void Validate()
|
||||
{
|
||||
if (Method.IsStatic)
|
||||
{
|
||||
throw CreateBindingException(Import, Method, "method cannot be static");
|
||||
}
|
||||
|
||||
if (Method.IsGenericMethod)
|
||||
{
|
||||
throw CreateBindingException(Import, Method, "method cannot be generic");
|
||||
}
|
||||
|
||||
if (Method.IsConstructor)
|
||||
{
|
||||
throw CreateBindingException(Import, Method, "method cannot be a constructor");
|
||||
}
|
||||
|
||||
ValidateParameters();
|
||||
|
||||
ValidateReturnType();
|
||||
}
|
||||
|
||||
private void ValidateParameters()
|
||||
{
|
||||
var parameters = Method.GetParameters();
|
||||
if (parameters.Length != Import.Parameters.Count)
|
||||
{
|
||||
throw CreateBindingException(
|
||||
Import,
|
||||
Method,
|
||||
$"parameter mismatch: import requires {Import.Parameters.Count} but the method has {parameters.Length}");
|
||||
}
|
||||
|
||||
for (int i = 0; i < parameters.Length; ++i)
|
||||
{
|
||||
var parameter = parameters[i];
|
||||
if (parameter.ParameterType.IsByRef)
|
||||
{
|
||||
if (parameter.IsOut)
|
||||
{
|
||||
throw CreateBindingException(Import, Method, $"parameter '{parameter.Name}' cannot be an 'out' parameter");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw CreateBindingException(Import, Method, $"parameter '{parameter.Name}' cannot be a 'ref' parameter");
|
||||
}
|
||||
}
|
||||
|
||||
var expected = Import.Parameters[i];
|
||||
if (!Interop.TryGetValueKind(parameter.ParameterType, out var kind) || !Interop.IsMatchingKind(kind, expected))
|
||||
{
|
||||
throw CreateBindingException(Import, Method, $"method parameter '{parameter.Name}' is expected to be of type '{Interop.ToString(expected)}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateReturnType()
|
||||
{
|
||||
int resultsCount = Import.Results.Count();
|
||||
if (resultsCount == 0)
|
||||
{
|
||||
if (Method.ReturnType != typeof(void))
|
||||
{
|
||||
throw CreateBindingException(Import, Method, "method must return void");
|
||||
}
|
||||
}
|
||||
else if (resultsCount == 1)
|
||||
{
|
||||
var expected = Import.Results[0];
|
||||
if (!Interop.TryGetValueKind(Method.ReturnType, out var kind) || !Interop.IsMatchingKind(kind, expected))
|
||||
{
|
||||
throw CreateBindingException(Import, Method, $"return type is expected to be '{Interop.ToString(expected)}'");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!IsTupleOfSize(Method.ReturnType, resultsCount))
|
||||
{
|
||||
throw CreateBindingException(Import, Method, $"return type is expected to be a tuple of size {resultsCount}");
|
||||
}
|
||||
|
||||
var typeArguments =
|
||||
Method.ReturnType.GetGenericArguments().SelectMany(type =>
|
||||
{
|
||||
if (type.IsConstructedGenericType)
|
||||
{
|
||||
return type.GenericTypeArguments;
|
||||
}
|
||||
return Enumerable.Repeat(type, 1);
|
||||
});
|
||||
|
||||
int i = 0;
|
||||
foreach (var typeArgument in typeArguments)
|
||||
{
|
||||
var expected = Import.Results[i];
|
||||
if (!Interop.TryGetValueKind(typeArgument, out var kind) || !Interop.IsMatchingKind(kind, expected))
|
||||
{
|
||||
throw CreateBindingException(Import, Method, $"return tuple item #{i} is expected to be of type '{Interop.ToString(expected)}'");
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsTupleOfSize(Type type, int size)
|
||||
{
|
||||
if (!type.IsConstructedGenericType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var definition = type.GetGenericTypeDefinition();
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
return definition == typeof(ValueTuple);
|
||||
}
|
||||
|
||||
if (size == 1)
|
||||
{
|
||||
return definition == typeof(ValueTuple<>);
|
||||
}
|
||||
|
||||
if (size == 2)
|
||||
{
|
||||
return definition == typeof(ValueTuple<,>);
|
||||
}
|
||||
|
||||
if (size == 3)
|
||||
{
|
||||
return definition == typeof(ValueTuple<,,>);
|
||||
}
|
||||
|
||||
if (size == 4)
|
||||
{
|
||||
return definition == typeof(ValueTuple<,,,>);
|
||||
}
|
||||
|
||||
if (size == 5)
|
||||
{
|
||||
return definition == typeof(ValueTuple<,,,,>);
|
||||
}
|
||||
|
||||
if (size == 6)
|
||||
{
|
||||
return definition == typeof(ValueTuple<,,,,,>);
|
||||
}
|
||||
|
||||
if (size == 7)
|
||||
{
|
||||
return definition == typeof(ValueTuple<,,,,,,>);
|
||||
}
|
||||
|
||||
if (definition != typeof(ValueTuple<,,,,,,,>))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return IsTupleOfSize(type.GetGenericArguments().Last(), size - 7);
|
||||
}
|
||||
|
||||
private unsafe Interop.WasmFuncCallback CreateCallback(Store store, IHost host)
|
||||
{
|
||||
var args = new object[Import.Parameters.Count];
|
||||
bool hasReturn = Method.ReturnType != typeof(void);
|
||||
var storeHandle = store.Handle;
|
||||
|
||||
Interop.WasmFuncCallback callback = (arguments, results) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
SetArgs(arguments, args);
|
||||
|
||||
var result = Method.Invoke(host, BindingFlags.DoNotWrapExceptions, null, args, null);
|
||||
|
||||
if (hasReturn)
|
||||
{
|
||||
SetResults(result, results);
|
||||
}
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(ex.Message + "\0" /* exception messages need a null */);
|
||||
|
||||
fixed (byte* ptr = bytes)
|
||||
{
|
||||
Interop.wasm_byte_vec_t message = new Interop.wasm_byte_vec_t();
|
||||
message.size = (UIntPtr)bytes.Length;
|
||||
message.data = ptr;
|
||||
|
||||
return Interop.wasm_trap_new(storeHandle, ref message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return callback;
|
||||
}
|
||||
|
||||
private static unsafe void SetArgs(Interop.wasm_val_t* arguments, object[] args)
|
||||
{
|
||||
for (int i = 0; i < args.Length; ++i)
|
||||
{
|
||||
var arg = arguments[i];
|
||||
|
||||
switch (arg.kind)
|
||||
{
|
||||
case Interop.wasm_valkind_t.WASM_I32:
|
||||
args[i] = arg.of.i32;
|
||||
break;
|
||||
|
||||
case Interop.wasm_valkind_t.WASM_I64:
|
||||
args[i] = arg.of.i64;
|
||||
break;
|
||||
|
||||
case Interop.wasm_valkind_t.WASM_F32:
|
||||
args[i] = arg.of.f32;
|
||||
break;
|
||||
|
||||
case Interop.wasm_valkind_t.WASM_F64:
|
||||
args[i] = arg.of.f64;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException("Unsupported value type.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe void SetResults(object value, Interop.wasm_val_t* results)
|
||||
{
|
||||
var tuple = value as ITuple;
|
||||
if (tuple is null)
|
||||
{
|
||||
SetResult(value, &results[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < tuple.Length; ++i)
|
||||
{
|
||||
SetResults(tuple[i], &results[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe void SetResult(object value, Interop.wasm_val_t* result)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case int i:
|
||||
result->kind = Interop.wasm_valkind_t.WASM_I32;
|
||||
result->of.i32 = i;
|
||||
break;
|
||||
|
||||
case long l:
|
||||
result->kind = Interop.wasm_valkind_t.WASM_I64;
|
||||
result->of.i64 = l;
|
||||
break;
|
||||
|
||||
case float f:
|
||||
result->kind = Interop.wasm_valkind_t.WASM_F32;
|
||||
result->of.f32 = f;
|
||||
break;
|
||||
|
||||
case double d:
|
||||
result->kind = Interop.wasm_valkind_t.WASM_F64;
|
||||
result->of.f64 = d;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException("Unsupported return value type.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using Wasmtime.Imports;
|
||||
|
||||
namespace Wasmtime.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a host global binding.
|
||||
/// </summary>
|
||||
internal class GlobalBinding : Binding
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new global binding.
|
||||
/// </summary>
|
||||
/// <param name="import">The global import of the binding.</param>
|
||||
/// <param name="field">The field the import is bound to.</param>
|
||||
public GlobalBinding(GlobalImport import, FieldInfo field)
|
||||
{
|
||||
if (import is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(import));
|
||||
}
|
||||
|
||||
if (field is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(field));
|
||||
}
|
||||
|
||||
Import = import;
|
||||
Field = field;
|
||||
|
||||
Validate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The global import of the binding.
|
||||
/// </summary>
|
||||
public GlobalImport Import { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The field the import is bound to.
|
||||
/// </summary>
|
||||
public FieldInfo Field { get; private set; }
|
||||
|
||||
public override SafeHandle Bind(Store store, IHost host)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
dynamic global = Field.GetValue(host);
|
||||
if (!(global.Handle is null))
|
||||
{
|
||||
throw new InvalidOperationException("Cannot bind more than once.");
|
||||
}
|
||||
|
||||
var v = Interop.ToValue((object)global.InitialValue, Import.Kind);
|
||||
|
||||
var valueType = Interop.wasm_valtype_new(v.kind);
|
||||
var valueTypeHandle = valueType.DangerousGetHandle();
|
||||
valueType.SetHandleAsInvalid();
|
||||
|
||||
using var globalType = Interop.wasm_globaltype_new(
|
||||
valueTypeHandle,
|
||||
Import.IsMutable ? Interop.wasm_mutability_t.WASM_VAR : Interop.wasm_mutability_t.WASM_CONST
|
||||
);
|
||||
|
||||
var handle = Interop.wasm_global_new(store.Handle, globalType, &v);
|
||||
global.Handle = handle;
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
private void Validate()
|
||||
{
|
||||
if (Field.IsStatic)
|
||||
{
|
||||
throw CreateBindingException(Import, Field, "field cannot be static");
|
||||
}
|
||||
|
||||
if (!Field.IsInitOnly)
|
||||
{
|
||||
throw CreateBindingException(Import, Field, "field must be readonly");
|
||||
}
|
||||
|
||||
if (!Field.FieldType.IsGenericType)
|
||||
{
|
||||
throw CreateBindingException(Import, Field, "field is expected to be of type 'Global<T>'");
|
||||
}
|
||||
|
||||
var definition = Field.FieldType.GetGenericTypeDefinition();
|
||||
if (definition == typeof(Global<>))
|
||||
{
|
||||
if (Import.IsMutable)
|
||||
{
|
||||
throw CreateBindingException(Import, Field, "the import is mutable (use the 'MutableGlobal' type)");
|
||||
}
|
||||
}
|
||||
else if (definition == typeof(MutableGlobal<>))
|
||||
{
|
||||
if (!Import.IsMutable)
|
||||
{
|
||||
throw CreateBindingException(Import, Field, "the import is constant (use the 'Global' type)");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw CreateBindingException(Import, Field, "field is expected to be of type 'Global<T>' or 'MutableGlobal<T>'");
|
||||
}
|
||||
|
||||
var arg = Field.FieldType.GetGenericArguments()[0];
|
||||
|
||||
if (Interop.TryGetValueKind(arg, out var kind))
|
||||
{
|
||||
if (!Interop.IsMatchingKind(kind, Import.Kind))
|
||||
{
|
||||
throw CreateBindingException(Import, Field, $"global type argument is expected to be of type '{Interop.ToString(Import.Kind)}'");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw CreateBindingException(Import, Field, $"'{arg}' is not a valid global type");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using Wasmtime.Imports;
|
||||
|
||||
namespace Wasmtime.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a host memory binding.
|
||||
/// </summary>
|
||||
internal class MemoryBinding : Binding
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new memory binding.
|
||||
/// </summary>
|
||||
/// <param name="import">The memory import of the binding.</param>
|
||||
/// <param name="field">The field the import is bound to.</param>
|
||||
public MemoryBinding(MemoryImport import, FieldInfo field)
|
||||
{
|
||||
if (import is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(import));
|
||||
}
|
||||
|
||||
if (field is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(field));
|
||||
}
|
||||
|
||||
Import = import;
|
||||
Field = field;
|
||||
|
||||
Validate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The memory import of the binding.
|
||||
/// </summary>
|
||||
public MemoryImport Import { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The field the import is bound to.
|
||||
/// </summary>
|
||||
public FieldInfo Field { get; private set; }
|
||||
|
||||
public override SafeHandle Bind(Store store, IHost host)
|
||||
{
|
||||
Memory memory = (Memory)Field.GetValue(host);
|
||||
if (!(memory.Handle is null))
|
||||
{
|
||||
throw new InvalidOperationException("Cannot bind more than once.");
|
||||
}
|
||||
|
||||
uint min = memory.Minimum;
|
||||
uint max = memory.Maximum;
|
||||
|
||||
if (min != Import.Minimum)
|
||||
{
|
||||
throw CreateBindingException(Import, Field, $"Memory does not have the expected minimum of {Import.Minimum} page(s)");
|
||||
}
|
||||
if (max != Import.Maximum)
|
||||
{
|
||||
throw CreateBindingException(Import, Field, $"Memory does not have the expected maximum of {Import.Maximum} page(s)");
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
Interop.wasm_limits_t limits = new Interop.wasm_limits_t();
|
||||
limits.min = min;
|
||||
limits.max = max;
|
||||
|
||||
using var memoryType = Interop.wasm_memorytype_new(&limits);
|
||||
var handle = Interop.wasm_memory_new(store.Handle, memoryType);
|
||||
memory.Handle = handle;
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
private void Validate()
|
||||
{
|
||||
if (Field.IsStatic)
|
||||
{
|
||||
throw CreateBindingException(Import, Field, "field cannot be static");
|
||||
}
|
||||
|
||||
if (!Field.IsInitOnly)
|
||||
{
|
||||
throw CreateBindingException(Import, Field, "field must be readonly");
|
||||
}
|
||||
|
||||
if (Field.FieldType != typeof(Memory))
|
||||
{
|
||||
throw CreateBindingException(Import, Field, "field is expected to be of type 'Memory'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Wasmtime.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a binding to a WASI export.
|
||||
/// </summary>
|
||||
internal class WasiBinding : Binding
|
||||
{
|
||||
public WasiBinding(IntPtr handle)
|
||||
{
|
||||
_handle = handle;
|
||||
}
|
||||
|
||||
public override SafeHandle Bind(Store store, IHost host)
|
||||
{
|
||||
return new Interop.WasiExportHandle(_handle);
|
||||
}
|
||||
|
||||
private IntPtr _handle;
|
||||
}
|
||||
}
|
||||
82
crates/misc/dotnet/src/Caller.cs
Normal file
82
crates/misc/dotnet/src/Caller.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Wasmtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an exported memory of a host function caller.
|
||||
/// </summary>
|
||||
public class CallerMemory : MemoryBase, IDisposable
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_extern.IsInvalid)
|
||||
{
|
||||
_extern.Dispose();
|
||||
_extern.SetHandleAsInvalid();
|
||||
}
|
||||
}
|
||||
|
||||
internal CallerMemory(Interop.ExternHandle ext, IntPtr memory)
|
||||
{
|
||||
_extern = ext;
|
||||
_memory = memory;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IntPtr MemoryHandle => _memory;
|
||||
|
||||
private Interop.ExternHandle _extern;
|
||||
|
||||
private IntPtr _memory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents information of the caller of a host function.
|
||||
/// </summary>
|
||||
public class Caller
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an exported memory of the caller by the given name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the exported memory.</param>
|
||||
/// <returns>Returns the exported memory if found or null if a memory of the requested name is not exported.</returns>
|
||||
public CallerMemory GetMemory(string name)
|
||||
{
|
||||
if (Handle == IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(name);
|
||||
|
||||
fixed (byte* ptr = bytes)
|
||||
{
|
||||
Interop.wasm_byte_vec_t nameVec = new Interop.wasm_byte_vec_t();
|
||||
nameVec.size = (UIntPtr)bytes.Length;
|
||||
nameVec.data = ptr;
|
||||
|
||||
var export = Interop.wasmtime_caller_export_get(Handle, ref nameVec);
|
||||
if (export.IsInvalid)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var memory = Interop.wasm_extern_as_memory(export.DangerousGetHandle());
|
||||
if (memory == IntPtr.Zero)
|
||||
{
|
||||
export.Dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CallerMemory(export, memory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal IntPtr Handle { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Wasmtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the Wasmtime engine.
|
||||
/// </summary>
|
||||
public class Engine : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="Engine" />.
|
||||
/// </summary>
|
||||
public Engine()
|
||||
{
|
||||
Handle = Interop.wasm_engine_new();
|
||||
|
||||
if (Handle.IsInvalid)
|
||||
{
|
||||
throw new WasmtimeException("Failed to create Wasmtime engine.");
|
||||
}
|
||||
}
|
||||
|
||||
internal Engine(Interop.WasmConfigHandle config)
|
||||
{
|
||||
Handle = Interop.wasm_engine_new_with_config(config);
|
||||
config.SetHandleAsInvalid();
|
||||
|
||||
if (Handle.IsInvalid)
|
||||
{
|
||||
throw new WasmtimeException("Failed to create Wasmtime engine.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Wasmtime <see cref="Store" />.
|
||||
/// </summary>
|
||||
/// <returns>Returns the new <see cref="Store" />.</returns>
|
||||
public Store CreateStore()
|
||||
{
|
||||
return new Store(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the WebAssembly text format to the binary format
|
||||
/// </summary>
|
||||
/// <returns>Returns the binary-encoded wasm module.</returns>
|
||||
public byte[] WatToWasm(string wat)
|
||||
{
|
||||
var watBytes = Encoding.UTF8.GetBytes(wat);
|
||||
unsafe
|
||||
{
|
||||
fixed (byte *ptr = watBytes)
|
||||
{
|
||||
Interop.wasm_byte_vec_t watByteVec;
|
||||
watByteVec.size = (UIntPtr)watBytes.Length;
|
||||
watByteVec.data = ptr;
|
||||
if (!Interop.wasmtime_wat2wasm(Handle, ref watByteVec, out var bytes, out var error)) {
|
||||
var errorSpan = new ReadOnlySpan<byte>(error.data, checked((int)error.size));
|
||||
var message = Encoding.UTF8.GetString(errorSpan);
|
||||
Interop.wasm_byte_vec_delete(ref error);
|
||||
throw new WasmtimeException("failed to parse input wat: " + message);
|
||||
}
|
||||
var byteSpan = new ReadOnlySpan<byte>(bytes.data, checked((int)bytes.size));
|
||||
var ret = byteSpan.ToArray();
|
||||
Interop.wasm_byte_vec_delete(ref bytes);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!Handle.IsInvalid)
|
||||
{
|
||||
Handle.Dispose();
|
||||
Handle.SetHandleAsInvalid();
|
||||
}
|
||||
}
|
||||
|
||||
internal Interop.EngineHandle Handle { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace Wasmtime.Externs
|
||||
/// <summary>
|
||||
/// Represents an external (instantiated) WebAssembly memory.
|
||||
/// </summary>
|
||||
public class ExternMemory
|
||||
public class ExternMemory : MemoryBase
|
||||
{
|
||||
internal ExternMemory(MemoryExport export, IntPtr memory)
|
||||
{
|
||||
@@ -31,230 +31,7 @@ namespace Wasmtime.Externs
|
||||
/// </summary>
|
||||
public uint Maximum => _export.Maximum;
|
||||
|
||||
/// <summary>
|
||||
/// The span of the memory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The span may become invalid if the memory grows.
|
||||
///
|
||||
/// This may happen if the memory is explicitly requested to grow or
|
||||
/// grows as a result of WebAssembly execution.
|
||||
///
|
||||
/// Therefore, the returned Span should not be stored.
|
||||
/// </remarks>
|
||||
public unsafe Span<byte> Span
|
||||
{
|
||||
get
|
||||
{
|
||||
var data = Interop.wasm_memory_data(_memory);
|
||||
var size = Convert.ToInt32(Interop.wasm_memory_data_size(_memory).ToUInt32());
|
||||
return new Span<byte>(data, size);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a UTF-8 string from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <param name="length">The length of bytes to read.</param>
|
||||
/// <returns>Returns the string read from memory.</returns>
|
||||
public string ReadString(int address, int length)
|
||||
{
|
||||
return Encoding.UTF8.GetString(Span.Slice(address, length));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a null-terminated UTF-8 string from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the string read from memory.</returns>
|
||||
public string ReadNullTerminatedString(int address)
|
||||
{
|
||||
var slice = Span.Slice(address);
|
||||
var terminator = slice.IndexOf((byte)0);
|
||||
if (terminator == -1)
|
||||
{
|
||||
throw new InvalidOperationException("string is not null terminated");
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(slice.Slice(0, terminator));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a UTF-8 string at the given address.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The string to write.</param>
|
||||
/// <return>Returns the number of bytes written.</return>
|
||||
public int WriteString(int address, string value)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(value, Span.Slice(address));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a byte from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the byte read from memory.</returns>
|
||||
public byte ReadByte(int address)
|
||||
{
|
||||
return Span[address];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a byte to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The byte to write.</param>
|
||||
public void WriteByte(int address, byte value)
|
||||
{
|
||||
Span[address] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a short from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the short read from memory.</returns>
|
||||
public short ReadInt16(int address)
|
||||
{
|
||||
return BinaryPrimitives.ReadInt16LittleEndian(Span.Slice(address, 2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a short to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The short to write.</param>
|
||||
public void WriteInt16(int address, short value)
|
||||
{
|
||||
BinaryPrimitives.WriteInt16LittleEndian(Span.Slice(address, 2), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an int from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the int read from memory.</returns>
|
||||
public int ReadInt32(int address)
|
||||
{
|
||||
return BinaryPrimitives.ReadInt32LittleEndian(Span.Slice(address, 4));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an int to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The int to write.</param>
|
||||
public void WriteInt32(int address, int value)
|
||||
{
|
||||
BinaryPrimitives.WriteInt32LittleEndian(Span.Slice(address, 4), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a long from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the long read from memory.</returns>
|
||||
public long ReadInt64(int address)
|
||||
{
|
||||
return BinaryPrimitives.ReadInt64LittleEndian(Span.Slice(address, 8));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a long to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The long to write.</param>
|
||||
public void WriteInt64(int address, long value)
|
||||
{
|
||||
BinaryPrimitives.WriteInt64LittleEndian(Span.Slice(address, 8), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an IntPtr from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the IntPtr read from memory.</returns>
|
||||
public IntPtr ReadIntPtr(int address)
|
||||
{
|
||||
if (IntPtr.Size == 4)
|
||||
{
|
||||
return (IntPtr)ReadInt32(address);
|
||||
}
|
||||
return (IntPtr)ReadInt64(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an IntPtr to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The IntPtr to write.</param>
|
||||
public void WriteIntPtr(int address, IntPtr value)
|
||||
{
|
||||
if (IntPtr.Size == 4)
|
||||
{
|
||||
WriteInt32(address, value.ToInt32());
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteInt64(address, value.ToInt64());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a long from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the long read from memory.</returns>
|
||||
public float ReadSingle(int address)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var i = ReadInt32(address);
|
||||
return *((float*)&i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a single to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The single to write.</param>
|
||||
public void WriteSingle(int address, float value)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
WriteInt32(address, *(int*)&value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a double from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the double read from memory.</returns>
|
||||
public double ReadDouble(int address)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var i = ReadInt64(address);
|
||||
return *((double*)&i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a double to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The double to write.</param>
|
||||
public void WriteDouble(int address, double value)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
WriteInt64(address, *(long*)&value);
|
||||
}
|
||||
}
|
||||
protected override IntPtr MemoryHandle => _memory;
|
||||
|
||||
private MemoryExport _export;
|
||||
private IntPtr _memory;
|
||||
|
||||
355
crates/misc/dotnet/src/Function.cs
Normal file
355
crates/misc/dotnet/src/Function.cs
Normal file
@@ -0,0 +1,355 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Wasmtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a host function.
|
||||
/// </summary>
|
||||
public class Function : IDisposable
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!Handle.IsInvalid)
|
||||
{
|
||||
Handle.Dispose();
|
||||
Handle.SetHandleAsInvalid();
|
||||
}
|
||||
}
|
||||
|
||||
internal Function(Interop.StoreHandle store, Delegate func, bool hasReturn)
|
||||
{
|
||||
if (func is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(func));
|
||||
}
|
||||
|
||||
var type = func.GetType();
|
||||
Span<Type> parameterTypes = null;
|
||||
Type returnType = null;
|
||||
|
||||
if (hasReturn)
|
||||
{
|
||||
parameterTypes = type.GenericTypeArguments[0..^1];
|
||||
returnType = type.GenericTypeArguments[^1];
|
||||
}
|
||||
else
|
||||
{
|
||||
parameterTypes = type.GenericTypeArguments;
|
||||
returnType = null;
|
||||
}
|
||||
|
||||
bool hasCaller = parameterTypes.Length > 0 && parameterTypes[0] == typeof(Caller);
|
||||
|
||||
if (hasCaller)
|
||||
{
|
||||
parameterTypes = parameterTypes[1..];
|
||||
}
|
||||
|
||||
ValidateParameterTypes(parameterTypes);
|
||||
|
||||
ValidateReturnType(returnType);
|
||||
|
||||
var parameters = CreateValueTypeVec(parameterTypes);
|
||||
var results = CreateReturnValueTypeVec(returnType);
|
||||
using var funcType = Interop.wasm_functype_new(ref parameters, ref results);
|
||||
|
||||
if (hasCaller)
|
||||
{
|
||||
Callback = CreateCallbackWithCaller(store, func, parameterTypes.Length, hasReturn);
|
||||
Handle = Interop.wasmtime_func_new(store, funcType, (Interop.WasmtimeFuncCallback)Callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
Callback = CreateCallback(store, func, parameterTypes.Length, hasReturn);
|
||||
Handle = Interop.wasm_func_new(store, funcType, (Interop.WasmFuncCallback)Callback);
|
||||
}
|
||||
|
||||
if (Handle.IsInvalid)
|
||||
{
|
||||
throw new WasmtimeException("Failed to create Wasmtime function.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateParameterTypes(Span<Type> parameters)
|
||||
{
|
||||
foreach (var type in parameters)
|
||||
{
|
||||
if (type == typeof(Caller))
|
||||
{
|
||||
throw new WasmtimeException($"A Caller parameter must be the first parameter of the function.");
|
||||
}
|
||||
|
||||
if (!Interop.TryGetValueKind(type, out var kind))
|
||||
{
|
||||
throw new WasmtimeException($"Unable to create a function with parameter of type '{type.ToString()}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateReturnType(Type returnType)
|
||||
{
|
||||
if (returnType is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsTuple(returnType))
|
||||
{
|
||||
var types = returnType
|
||||
.GetGenericArguments()
|
||||
.SelectMany(type =>
|
||||
{
|
||||
if (type.IsConstructedGenericType)
|
||||
{
|
||||
return type.GenericTypeArguments;
|
||||
}
|
||||
return Enumerable.Repeat(type, 1);
|
||||
});
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
ValidateReturnType(type);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Interop.TryGetValueKind(returnType, out var kind))
|
||||
{
|
||||
throw new WasmtimeException($"Unable to create a function with a return type of type '{returnType.ToString()}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsTuple(Type type)
|
||||
{
|
||||
if (!type.IsConstructedGenericType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var definition = type.GetGenericTypeDefinition();
|
||||
|
||||
return definition == typeof(ValueTuple) ||
|
||||
definition == typeof(ValueTuple<>) ||
|
||||
definition == typeof(ValueTuple<,>) ||
|
||||
definition == typeof(ValueTuple<,,>) ||
|
||||
definition == typeof(ValueTuple<,,,>) ||
|
||||
definition == typeof(ValueTuple<,,,,>) ||
|
||||
definition == typeof(ValueTuple<,,,,,>) ||
|
||||
definition == typeof(ValueTuple<,,,,,,>) ||
|
||||
definition == typeof(ValueTuple<,,,,,,,>);
|
||||
}
|
||||
|
||||
private static unsafe Interop.WasmFuncCallback CreateCallback(Interop.StoreHandle store, Delegate func, int parameterCount, bool hasReturn)
|
||||
{
|
||||
// NOTE: this capture is not thread-safe.
|
||||
var args = new object[parameterCount];
|
||||
var method = func.Method;
|
||||
var target = func.Target;
|
||||
|
||||
return (arguments, results) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
SetArgs(arguments, args);
|
||||
|
||||
var result = method.Invoke(target, BindingFlags.DoNotWrapExceptions, null, args, null);
|
||||
|
||||
if (hasReturn)
|
||||
{
|
||||
SetResults(result, results);
|
||||
}
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(ex.Message + "\0" /* exception messages need a null */);
|
||||
|
||||
fixed (byte* ptr = bytes)
|
||||
{
|
||||
Interop.wasm_byte_vec_t message = new Interop.wasm_byte_vec_t();
|
||||
message.size = (UIntPtr)bytes.Length;
|
||||
message.data = ptr;
|
||||
|
||||
return Interop.wasm_trap_new(store, ref message);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static unsafe Interop.WasmtimeFuncCallback CreateCallbackWithCaller(Interop.StoreHandle store, Delegate func, int parameterCount, bool hasReturn)
|
||||
{
|
||||
// NOTE: this capture is not thread-safe.
|
||||
var args = new object[parameterCount + 1];
|
||||
var caller = new Caller();
|
||||
var method = func.Method;
|
||||
var target = func.Target;
|
||||
|
||||
args[0] = caller;
|
||||
|
||||
return (callerHandle, arguments, results) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
caller.Handle = callerHandle;
|
||||
|
||||
SetArgs(arguments, args, 1);
|
||||
|
||||
var result = method.Invoke(target, BindingFlags.DoNotWrapExceptions, null, args, null);
|
||||
|
||||
caller.Handle = IntPtr.Zero;
|
||||
|
||||
if (hasReturn)
|
||||
{
|
||||
SetResults(result, results);
|
||||
}
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(ex.Message + "\0" /* exception messages need a null */);
|
||||
|
||||
fixed (byte* ptr = bytes)
|
||||
{
|
||||
Interop.wasm_byte_vec_t message = new Interop.wasm_byte_vec_t();
|
||||
message.size = (UIntPtr)bytes.Length;
|
||||
message.data = ptr;
|
||||
|
||||
return Interop.wasm_trap_new(store, ref message);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static unsafe void SetArgs(Interop.wasm_val_t* arguments, object[] args, int offset = 0)
|
||||
{
|
||||
for (int i = 0; i < args.Length - offset; ++i)
|
||||
{
|
||||
var arg = arguments[i];
|
||||
|
||||
switch (arg.kind)
|
||||
{
|
||||
case Interop.wasm_valkind_t.WASM_I32:
|
||||
args[i + offset] = arg.of.i32;
|
||||
break;
|
||||
|
||||
case Interop.wasm_valkind_t.WASM_I64:
|
||||
args[i + offset] = arg.of.i64;
|
||||
break;
|
||||
|
||||
case Interop.wasm_valkind_t.WASM_F32:
|
||||
args[i + offset] = arg.of.f32;
|
||||
break;
|
||||
|
||||
case Interop.wasm_valkind_t.WASM_F64:
|
||||
args[i + offset] = arg.of.f64;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException("Unsupported value type.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe void SetResults(object value, Interop.wasm_val_t* results)
|
||||
{
|
||||
var tuple = value as ITuple;
|
||||
if (tuple is null)
|
||||
{
|
||||
SetResult(value, &results[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < tuple.Length; ++i)
|
||||
{
|
||||
SetResult(tuple[i], &results[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe void SetResult(object value, Interop.wasm_val_t* result)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case int i:
|
||||
result->kind = Interop.wasm_valkind_t.WASM_I32;
|
||||
result->of.i32 = i;
|
||||
break;
|
||||
|
||||
case long l:
|
||||
result->kind = Interop.wasm_valkind_t.WASM_I64;
|
||||
result->of.i64 = l;
|
||||
break;
|
||||
|
||||
case float f:
|
||||
result->kind = Interop.wasm_valkind_t.WASM_F32;
|
||||
result->of.f32 = f;
|
||||
break;
|
||||
|
||||
case double d:
|
||||
result->kind = Interop.wasm_valkind_t.WASM_F64;
|
||||
result->of.f64 = d;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException("Unsupported return value type.");
|
||||
}
|
||||
}
|
||||
|
||||
private static Interop.wasm_valtype_vec_t CreateValueTypeVec(Span<Type> types)
|
||||
{
|
||||
Interop.wasm_valtype_vec_t vec;
|
||||
Interop.wasm_valtype_vec_new_uninitialized(out vec, (UIntPtr)types.Length);
|
||||
|
||||
int i = 0;
|
||||
foreach (var type in types)
|
||||
{
|
||||
var valType = Interop.wasm_valtype_new((Interop.wasm_valkind_t)Interop.ToValueKind(type));
|
||||
unsafe
|
||||
{
|
||||
vec.data[i++] = valType.DangerousGetHandle();
|
||||
}
|
||||
valType.SetHandleAsInvalid();
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
private static Interop.wasm_valtype_vec_t CreateReturnValueTypeVec(Type returnType)
|
||||
{
|
||||
if (returnType is null)
|
||||
{
|
||||
Interop.wasm_valtype_vec_t vec;
|
||||
Interop.wasm_valtype_vec_new_empty(out vec);
|
||||
return vec;
|
||||
}
|
||||
|
||||
if (IsTuple(returnType))
|
||||
{
|
||||
return CreateValueTypeVec(
|
||||
returnType
|
||||
.GetGenericArguments()
|
||||
.SelectMany(type =>
|
||||
{
|
||||
if (type.IsConstructedGenericType)
|
||||
{
|
||||
return type.GenericTypeArguments;
|
||||
}
|
||||
return Enumerable.Repeat(type, 1);
|
||||
})
|
||||
.ToArray()
|
||||
);
|
||||
}
|
||||
|
||||
return CreateValueTypeVec(new Type[] { returnType });
|
||||
}
|
||||
|
||||
internal Interop.FunctionHandle Handle { get; private set; }
|
||||
internal Delegate Callback { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -5,18 +5,8 @@ namespace Wasmtime
|
||||
/// <summary>
|
||||
/// Represents a constant WebAssembly global value.
|
||||
/// </summary>
|
||||
public class Global<T>
|
||||
public class Global<T> : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Global<T>" /> with the given initial value.
|
||||
/// </summary>
|
||||
/// <param name="initialValue">The initial value of the global.</param>
|
||||
public Global(T initialValue)
|
||||
{
|
||||
InitialValue = initialValue;
|
||||
Kind = Interop.ToValueKind(typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value of the global.
|
||||
/// </summary>
|
||||
@@ -35,16 +25,58 @@ namespace Wasmtime
|
||||
|
||||
Interop.wasm_global_get(Handle.DangerousGetHandle(), v);
|
||||
|
||||
// TODO: figure out a way that doesn't box the value
|
||||
return (T)Interop.ToObject(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal ValueKind Kind { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the value kind of the global.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public ValueKind Kind { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!Handle.IsInvalid)
|
||||
{
|
||||
Handle.Dispose();
|
||||
Handle.SetHandleAsInvalid();
|
||||
}
|
||||
}
|
||||
|
||||
internal Global(Interop.StoreHandle store, T initialValue)
|
||||
{
|
||||
if (!Interop.TryGetValueKind(typeof(T), out var kind))
|
||||
{
|
||||
throw new WasmtimeException($"Global variables cannot be of type '{typeof(T).ToString()}'.");
|
||||
}
|
||||
|
||||
Kind = kind;
|
||||
|
||||
var value = Interop.ToValue((object)initialValue, Kind);
|
||||
|
||||
var valueType = Interop.wasm_valtype_new(value.kind);
|
||||
var valueTypeHandle = valueType.DangerousGetHandle();
|
||||
valueType.SetHandleAsInvalid();
|
||||
|
||||
using var globalType = Interop.wasm_globaltype_new(
|
||||
valueTypeHandle,
|
||||
Interop.wasm_mutability_t.WASM_CONST
|
||||
);
|
||||
|
||||
unsafe
|
||||
{
|
||||
Handle = Interop.wasm_global_new(store, globalType, &value);
|
||||
|
||||
if (Handle.IsInvalid)
|
||||
{
|
||||
throw new WasmtimeException("Failed to create Wasmtime global.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal Interop.GlobalHandle Handle { get; set; }
|
||||
|
||||
internal T InitialValue { get; private set; }
|
||||
}
|
||||
}
|
||||
|
||||
806
crates/misc/dotnet/src/Host.cs
Normal file
806
crates/misc/dotnet/src/Host.cs
Normal file
@@ -0,0 +1,806 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Wasmtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a WebAssembly host environment.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A host is used to configure the environment for WebAssembly modules to execute in.
|
||||
/// </remarks>
|
||||
public class Host : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new host.
|
||||
/// </summary>
|
||||
public Host()
|
||||
{
|
||||
Initialize(Interop.wasm_engine_new());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a WASI implementation in the host.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the WASI module to define.</param>
|
||||
/// <param name="config">The <see cref="WasiConfiguration"/> to configure the WASI implementation with.</param>
|
||||
public void DefineWasi(string name, WasiConfiguration config = null)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentException("Name cannot be null or empty.", nameof(name));
|
||||
}
|
||||
|
||||
if (config is null)
|
||||
{
|
||||
config = new WasiConfiguration();
|
||||
}
|
||||
|
||||
using var wasi = config.CreateWasi(Store, name);
|
||||
|
||||
if (!Interop.wasmtime_linker_define_wasi(Linker, wasi))
|
||||
{
|
||||
throw new WasmtimeException($"Failed to define WASI module '{name}'.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction(string moduleName, string name, Action func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T>(string moduleName, string name, Action<T> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2>(string moduleName, string name, Action<T1, T2> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3>(string moduleName, string name, Action<T1, T2, T3> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4>(string moduleName, string name, Action<T1, T2, T3, T4> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5>(string moduleName, string name, Action<T1, T2, T3, T4, T5> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7, T8> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<TResult>(string moduleName, string name, Func<TResult> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T, TResult>(string moduleName, string name, Func<T, TResult> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, TResult>(string moduleName, string name, Func<T1, T2, TResult> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, TResult>(string moduleName, string name, Func<T1, T2, T3, TResult> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, TResult> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, TResult> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, TResult> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, TResult> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, TResult> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, TResult> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TResult> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, TResult> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, TResult> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, TResult> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, TResult> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, TResult> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a host function.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host function.</param>
|
||||
/// <param name="name">The name of the host function.</param>
|
||||
/// <param name="func">The callback for when the host function is invoked.</param>
|
||||
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
|
||||
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult> func)
|
||||
{
|
||||
return DefineFunction(moduleName, name, func, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a new host global variable.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host variable.</param>
|
||||
/// <param name="name">The name of the host variable.</param>
|
||||
/// <param name="initialValue">The initial value of the host variable.</param>
|
||||
/// <typeparam name="T">The type of the host variable.</typeparam>
|
||||
/// <returns>Returns a new <see cref="Global"/> representing the defined global variable.</returns>
|
||||
public Global<T> DefineGlobal<T>(string moduleName, string name, T initialValue)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
if (moduleName is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(moduleName));
|
||||
}
|
||||
|
||||
if (name is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
var global = new Global<T>(Store, initialValue);
|
||||
|
||||
if (!Define(moduleName, name, Interop.wasm_global_as_extern(global.Handle)))
|
||||
{
|
||||
global.Dispose();
|
||||
throw new WasmtimeException($"Failed to define global '{name}' in module '{moduleName}'.");
|
||||
}
|
||||
|
||||
return global;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a new host mutable global variable.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host variable.</param>
|
||||
/// <param name="name">The name of the host variable.</param>
|
||||
/// <param name="initialValue">The initial value of the host variable.</param>
|
||||
/// <typeparam name="T">The type of the host variable.</typeparam>
|
||||
/// <returns>Returns a new <see cref="MutableGlobal"/> representing the defined mutable global variable.</returns>
|
||||
public MutableGlobal<T> DefineMutableGlobal<T>(string moduleName, string name, T initialValue)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
if (moduleName is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(moduleName));
|
||||
}
|
||||
|
||||
if (name is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
var global = new MutableGlobal<T>(Store, initialValue);
|
||||
|
||||
if (!Define(moduleName, name, Interop.wasm_global_as_extern(global.Handle)))
|
||||
{
|
||||
global.Dispose();
|
||||
throw new WasmtimeException($"Failed to define global '{name}' in module '{moduleName}'.");
|
||||
}
|
||||
|
||||
return global;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a new host memory.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">The module name of the host memory.</param>
|
||||
/// <param name="name">The name of the host memory.</param>
|
||||
/// <param name="minimum">The minimum number of pages for the host memory.</param>
|
||||
/// <param name="maximum">The maximum number of pages for the host memory.</param>
|
||||
/// <returns>Returns a new <see cref="Memory"/> representing the defined memory.</returns>
|
||||
public Memory DefineMemory(string moduleName, string name, uint minimum = 1, uint maximum = uint.MaxValue)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
if (moduleName is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(moduleName));
|
||||
}
|
||||
|
||||
if (name is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
var memory = new Memory(Store, minimum, maximum);
|
||||
|
||||
if (!Define(moduleName, name, Interop.wasm_memory_as_extern(memory.Handle)))
|
||||
{
|
||||
memory.Dispose();
|
||||
throw new WasmtimeException($"Failed to define memory '{name}' in module '{moduleName}'.");
|
||||
}
|
||||
|
||||
return memory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a <see cref="Module"/> given the module name and bytes.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the module.</param>
|
||||
/// <param name="bytes">The bytes of the module.</param>
|
||||
/// <returns>Returns a new <see cref="Module"/>.</returns>
|
||||
public Module LoadModule(string name, byte[] bytes)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (bytes is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bytes));
|
||||
}
|
||||
|
||||
return new Module(Store, name, bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a <see cref="Module"/> given the path to the WebAssembly file.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the WebAssembly file.</param>
|
||||
/// <returns>Returns a new <see cref="Module"/>.</returns>
|
||||
public Module LoadModule(string path)
|
||||
{
|
||||
return LoadModule(Path.GetFileNameWithoutExtension(path), File.ReadAllBytes(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a <see cref="Module"/> based on a WebAssembly text format representation.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the module.</param>
|
||||
/// <param name="text">The WebAssembly text format representation of the module.</param>
|
||||
/// <returns>Returns a new <see cref="Module"/>.</returns>
|
||||
public Module LoadModuleText(string name, string text)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (text is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
var textBytes = Encoding.UTF8.GetBytes(text);
|
||||
unsafe
|
||||
{
|
||||
fixed (byte *ptr = textBytes)
|
||||
{
|
||||
Interop.wasm_byte_vec_t textVec;
|
||||
textVec.size = (UIntPtr)textBytes.Length;
|
||||
textVec.data = ptr;
|
||||
|
||||
if (!Interop.wasmtime_wat2wasm(ref textVec, out var bytes, out var error))
|
||||
{
|
||||
var errorSpan = new ReadOnlySpan<byte>(error.data, checked((int)error.size));
|
||||
var message = Encoding.UTF8.GetString(errorSpan);
|
||||
Interop.wasm_byte_vec_delete(ref error);
|
||||
throw new WasmtimeException($"Failed to parse module text: {message}");
|
||||
}
|
||||
|
||||
var byteSpan = new ReadOnlySpan<byte>(bytes.data, checked((int)bytes.size));
|
||||
var moduleBytes = byteSpan.ToArray();
|
||||
Interop.wasm_byte_vec_delete(ref bytes);
|
||||
return LoadModule(name, moduleBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a <see cref="Module"/> based on the path to a WebAssembly text format file.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the WebAssembly text format file.</param>
|
||||
/// <returns>Returns a new <see cref="Module"/>.</returns>
|
||||
public Module LoadModuleText(string path)
|
||||
{
|
||||
return LoadModuleText(Path.GetFileNameWithoutExtension(path), File.ReadAllText(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a WebAssembly module.
|
||||
/// </summary>
|
||||
/// <param name="module">The module to instantiate.</param>
|
||||
/// <returns>Returns a new <see cref="Instance" />.</returns>
|
||||
public Instance Instantiate(Module module)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
if (module is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(module));
|
||||
}
|
||||
|
||||
return new Instance(Linker, module);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all existing definitions in the host.
|
||||
/// </summary>
|
||||
public void ClearDefinitions()
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
var linker = Interop.wasmtime_linker_new(Store);
|
||||
if (linker.IsInvalid)
|
||||
{
|
||||
throw new WasmtimeException("Failed to create Wasmtime linker.");
|
||||
}
|
||||
|
||||
Interop.wasmtime_linker_allow_shadowing(linker, allowShadowing: true);
|
||||
|
||||
Linker.Dispose();
|
||||
Linker = linker;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!Linker.IsInvalid)
|
||||
{
|
||||
Linker.Dispose();
|
||||
Linker.SetHandleAsInvalid();
|
||||
}
|
||||
|
||||
if (!Store.IsInvalid)
|
||||
{
|
||||
Store.Dispose();
|
||||
Store.SetHandleAsInvalid();
|
||||
}
|
||||
|
||||
if (!Engine.IsInvalid)
|
||||
{
|
||||
Engine.Dispose();
|
||||
Engine.SetHandleAsInvalid();
|
||||
}
|
||||
}
|
||||
|
||||
internal Host(Interop.WasmConfigHandle config)
|
||||
{
|
||||
var engine = Interop.wasm_engine_new_with_config(config);
|
||||
config.SetHandleAsInvalid();
|
||||
|
||||
Initialize(engine);
|
||||
}
|
||||
|
||||
private void Initialize(Interop.EngineHandle engine)
|
||||
{
|
||||
if (engine.IsInvalid)
|
||||
{
|
||||
throw new WasmtimeException("Failed to create Wasmtime engine.");
|
||||
}
|
||||
|
||||
var store = Interop.wasm_store_new(engine);
|
||||
|
||||
if (store.IsInvalid)
|
||||
{
|
||||
throw new WasmtimeException("Failed to create Wasmtime store.");
|
||||
}
|
||||
|
||||
var linker = Interop.wasmtime_linker_new(store);
|
||||
if (linker.IsInvalid)
|
||||
{
|
||||
throw new WasmtimeException("Failed to create Wasmtime linker.");
|
||||
}
|
||||
|
||||
Interop.wasmtime_linker_allow_shadowing(linker, allowShadowing: true);
|
||||
|
||||
Engine = engine;
|
||||
Store = store;
|
||||
Linker = linker;
|
||||
}
|
||||
|
||||
private void CheckDisposed()
|
||||
{
|
||||
if (Engine.IsInvalid)
|
||||
{
|
||||
throw new ObjectDisposedException(typeof(Host).FullName);
|
||||
}
|
||||
}
|
||||
|
||||
private Function DefineFunction(string moduleName, string name, Delegate func, bool hasReturn)
|
||||
{
|
||||
if (moduleName is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(moduleName));
|
||||
}
|
||||
|
||||
if (name is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (func is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(func));
|
||||
}
|
||||
|
||||
var function = new Function(Store, func, hasReturn);
|
||||
|
||||
if (!Define(moduleName, name, Interop.wasm_func_as_extern(function.Handle)))
|
||||
{
|
||||
function.Dispose();
|
||||
throw new WasmtimeException($"Failed to define function '{name}' in module '{moduleName}'.");
|
||||
}
|
||||
|
||||
_callbacks.Add(function.Callback);
|
||||
return function;
|
||||
}
|
||||
|
||||
private bool Define(string moduleName, string name, IntPtr ext)
|
||||
{
|
||||
var moduleNameBytes = Encoding.UTF8.GetBytes(moduleName);
|
||||
var nameBytes = Encoding.UTF8.GetBytes(name);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* moduleNamePtr = moduleNameBytes)
|
||||
fixed (byte* namePtr = nameBytes)
|
||||
{
|
||||
Interop.wasm_byte_vec_t moduleNameVec = new Interop.wasm_byte_vec_t();
|
||||
moduleNameVec.size = (UIntPtr)moduleNameBytes.Length;
|
||||
moduleNameVec.data = moduleNamePtr;
|
||||
|
||||
Interop.wasm_byte_vec_t nameVec = new Interop.wasm_byte_vec_t();
|
||||
nameVec.size = (UIntPtr)nameBytes.Length;
|
||||
nameVec.data = namePtr;
|
||||
|
||||
return Interop.wasmtime_linker_define(Linker, ref moduleNameVec, ref nameVec, ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal Interop.EngineHandle Engine { get; private set; }
|
||||
internal Interop.StoreHandle Store { get; private set; }
|
||||
internal Interop.LinkerHandle Linker { get; private set; }
|
||||
|
||||
private List<Delegate> _callbacks = new List<Delegate>();
|
||||
}
|
||||
}
|
||||
@@ -43,23 +43,16 @@ namespace Wasmtime
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a builder of <see cref="Engine"/> instances.
|
||||
/// Represents a builder of <see cref="Host"/> instances.
|
||||
/// </summary>
|
||||
public class EngineBuilder
|
||||
public class HostBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="EngineBuilder" />.
|
||||
/// </summary>
|
||||
public EngineBuilder()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether or not to enable debug information.
|
||||
/// </summary>
|
||||
/// <param name="enable">True to enable debug information or false to disable.</param>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public EngineBuilder WithDebugInfo(bool enable)
|
||||
public HostBuilder WithDebugInfo(bool enable)
|
||||
{
|
||||
_enableDebugInfo = enable;
|
||||
return this;
|
||||
@@ -70,7 +63,7 @@ namespace Wasmtime
|
||||
/// </summary>
|
||||
/// <param name="enable">True to enable WebAssembly threads support or false to disable.</param>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public EngineBuilder WithWasmThreads(bool enable)
|
||||
public HostBuilder WithWasmThreads(bool enable)
|
||||
{
|
||||
_enableWasmThreads = enable;
|
||||
return this;
|
||||
@@ -81,7 +74,7 @@ namespace Wasmtime
|
||||
/// </summary>
|
||||
/// <param name="enable">True to enable WebAssembly reference types support or false to disable.</param>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public EngineBuilder WithReferenceTypes(bool enable)
|
||||
public HostBuilder WithReferenceTypes(bool enable)
|
||||
{
|
||||
_enableReferenceTypes = enable;
|
||||
return this;
|
||||
@@ -92,7 +85,7 @@ namespace Wasmtime
|
||||
/// </summary>
|
||||
/// <param name="enable">True to enable WebAssembly SIMD support or false to disable.</param>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public EngineBuilder WithSIMD(bool enable)
|
||||
public HostBuilder WithSIMD(bool enable)
|
||||
{
|
||||
_enableSIMD = enable;
|
||||
return this;
|
||||
@@ -103,7 +96,7 @@ namespace Wasmtime
|
||||
/// </summary>
|
||||
/// <param name="enable">True to enable WebAssembly multi-value support or false to disable.</param>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public EngineBuilder WithMultiValue(bool enable)
|
||||
public HostBuilder WithMultiValue(bool enable)
|
||||
{
|
||||
_enableMultiValue = enable;
|
||||
return this;
|
||||
@@ -114,7 +107,7 @@ namespace Wasmtime
|
||||
/// </summary>
|
||||
/// <param name="enable">True to enable WebAssembly bulk memory support or false to disable.</param>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public EngineBuilder WithBulkMemory(bool enable)
|
||||
public HostBuilder WithBulkMemory(bool enable)
|
||||
{
|
||||
_enableBulkMemory = enable;
|
||||
return this;
|
||||
@@ -125,7 +118,7 @@ namespace Wasmtime
|
||||
/// </summary>
|
||||
/// <param name="strategy">The compiler strategy to use.</param>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public EngineBuilder WithCompilerStrategy(CompilerStrategy strategy)
|
||||
public HostBuilder WithCompilerStrategy(CompilerStrategy strategy)
|
||||
{
|
||||
switch (strategy)
|
||||
{
|
||||
@@ -152,7 +145,7 @@ namespace Wasmtime
|
||||
/// </summary>
|
||||
/// <param name="enable">True to enable the Cranelift debug verifier or false to disable.</param>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public EngineBuilder WithCraneliftDebugVerifier(bool enable)
|
||||
public HostBuilder WithCraneliftDebugVerifier(bool enable)
|
||||
{
|
||||
_enableCraneliftDebugVerifier = enable;
|
||||
return this;
|
||||
@@ -163,7 +156,7 @@ namespace Wasmtime
|
||||
/// </summary>
|
||||
/// <param name="level">The optimization level to use.</param>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public EngineBuilder WithOptimizationLevel(OptimizationLevel level)
|
||||
public HostBuilder WithOptimizationLevel(OptimizationLevel level)
|
||||
{
|
||||
switch (level)
|
||||
{
|
||||
@@ -186,10 +179,10 @@ namespace Wasmtime
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the <see cref="Engine" /> instance.
|
||||
/// Builds the <see cref="Host" /> instance.
|
||||
/// </summary>
|
||||
/// <returns>Returns the new <see cref="Engine" /> instance.</returns>
|
||||
public Engine Build()
|
||||
/// <returns>Returns the new <see cref="Host" /> instance.</returns>
|
||||
public Host Build()
|
||||
{
|
||||
var config = Interop.wasm_config_new();
|
||||
|
||||
@@ -238,7 +231,7 @@ namespace Wasmtime
|
||||
Interop.wasmtime_config_cranelift_opt_level_set(config, _optLevel.Value);
|
||||
}
|
||||
|
||||
return new Engine(config);
|
||||
return new Host(config);
|
||||
}
|
||||
|
||||
private bool? _enableDebugInfo;
|
||||
@@ -1,18 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Wasmtime.Bindings;
|
||||
|
||||
namespace Wasmtime
|
||||
{
|
||||
/// <summary>
|
||||
/// The interface implemented by Wasmtime hosts.
|
||||
/// </summary>
|
||||
public interface IHost
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Wasmtime.Instance" /> that the host is bound to.
|
||||
/// </summary>
|
||||
/// <remarks>A host can only bind to one module instance at a time.</remarks>
|
||||
Instance Instance { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Wasmtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to mark .NET methods and fields as imports to a WebAssembly module.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
|
||||
public class ImportAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="ImportAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the import.</param>
|
||||
public ImportAttribute(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name of the import.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The module name of the import.
|
||||
/// </summary>
|
||||
/// <remarks>A null or empty module name implies that the import is not scoped to a module.</remarks>
|
||||
public string Module { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Dynamic;
|
||||
using Wasmtime.Externs;
|
||||
using Wasmtime.Bindings;
|
||||
|
||||
namespace Wasmtime
|
||||
{
|
||||
@@ -13,49 +12,6 @@ namespace Wasmtime
|
||||
/// </summary>
|
||||
public class Instance : DynamicObject, IDisposable
|
||||
{
|
||||
internal Instance(Module module, Wasi wasi = null, IHost host = null)
|
||||
{
|
||||
Host = host;
|
||||
Module = module;
|
||||
|
||||
// Save the bindings to root the objects.
|
||||
// Otherwise the GC may collect the callback delegates from FunctionHandles for example.
|
||||
_bindings = Binding.GetImportBindings(module, wasi, host)
|
||||
.Select(b => b.Bind(module.Store, host))
|
||||
.ToArray();
|
||||
|
||||
unsafe
|
||||
{
|
||||
Handle = Interop.wasm_instance_new(
|
||||
Module.Store.Handle,
|
||||
Module.Handle,
|
||||
_bindings.Select(h => ToExtern(h)).ToArray(),
|
||||
out var trap);
|
||||
|
||||
if (trap != IntPtr.Zero)
|
||||
{
|
||||
throw TrapException.FromOwnedTrap(trap);
|
||||
}
|
||||
}
|
||||
|
||||
if (Handle.IsInvalid)
|
||||
{
|
||||
throw new WasmtimeException($"Failed to instantiate module '{module.Name}'.");
|
||||
}
|
||||
|
||||
Interop.wasm_instance_exports(Handle, out _externs);
|
||||
|
||||
Externs = new Wasmtime.Externs.Externs(Module.Exports, _externs);
|
||||
|
||||
_functions = Externs.Functions.ToDictionary(f => f.Name);
|
||||
_globals = Externs.Globals.ToDictionary(g => g.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The host associated with this instance.
|
||||
/// </summary>
|
||||
public IHost Host { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The WebAssembly module associated with the instantiation.
|
||||
/// </summary>
|
||||
@@ -75,15 +31,6 @@ namespace Wasmtime
|
||||
Handle.SetHandleAsInvalid();
|
||||
}
|
||||
|
||||
if (!(_bindings is null))
|
||||
{
|
||||
foreach (var binding in _bindings)
|
||||
{
|
||||
binding.Dispose();
|
||||
}
|
||||
_bindings = null;
|
||||
}
|
||||
|
||||
if (!(_externs.data is null))
|
||||
{
|
||||
Interop.wasm_extern_vec_delete(ref _externs);
|
||||
@@ -127,29 +74,35 @@ namespace Wasmtime
|
||||
return true;
|
||||
}
|
||||
|
||||
private static unsafe IntPtr ToExtern(SafeHandle handle)
|
||||
internal Instance(Interop.LinkerHandle linker, Module module)
|
||||
{
|
||||
switch (handle)
|
||||
Module = module;
|
||||
|
||||
unsafe
|
||||
{
|
||||
case Interop.FunctionHandle f:
|
||||
return Interop.wasm_func_as_extern(f);
|
||||
Handle = Interop.wasmtime_linker_instantiate(linker, module.Handle, out var trap);
|
||||
|
||||
case Interop.GlobalHandle g:
|
||||
return Interop.wasm_global_as_extern(g);
|
||||
|
||||
case Interop.MemoryHandle m:
|
||||
return Interop.wasm_memory_as_extern(m);
|
||||
|
||||
case Interop.WasiExportHandle w:
|
||||
return w.DangerousGetHandle();
|
||||
|
||||
default:
|
||||
throw new NotSupportedException("Unexpected handle type.");
|
||||
if (trap != IntPtr.Zero)
|
||||
{
|
||||
throw TrapException.FromOwnedTrap(trap);
|
||||
}
|
||||
}
|
||||
|
||||
if (Handle.IsInvalid)
|
||||
{
|
||||
throw new WasmtimeException("Failed to create Wasmtime instance.");
|
||||
}
|
||||
|
||||
Interop.wasm_instance_exports(Handle, out _externs);
|
||||
|
||||
Externs = new Wasmtime.Externs.Externs(Module.Exports, _externs);
|
||||
|
||||
_functions = Externs.Functions.ToDictionary(f => f.Name);
|
||||
_globals = Externs.Globals.ToDictionary(g => g.Name);
|
||||
}
|
||||
|
||||
|
||||
internal Interop.InstanceHandle Handle { get; private set; }
|
||||
private SafeHandle[] _bindings;
|
||||
private Interop.wasm_extern_vec_t _externs;
|
||||
private Dictionary<string, ExternFunction> _functions;
|
||||
private Dictionary<string, ExternGlobal> _globals;
|
||||
|
||||
@@ -64,8 +64,6 @@ namespace Wasmtime
|
||||
{
|
||||
}
|
||||
|
||||
public WasmFuncCallback Callback { get; set; } = null;
|
||||
|
||||
public override bool IsInvalid => handle == IntPtr.Zero;
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
@@ -240,6 +238,36 @@ namespace Wasmtime
|
||||
}
|
||||
}
|
||||
|
||||
internal class LinkerHandle : SafeHandle
|
||||
{
|
||||
public LinkerHandle() : base(IntPtr.Zero, true)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsInvalid => handle == IntPtr.Zero;
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
Interop.wasmtime_linker_delete(handle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ExternHandle : SafeHandle
|
||||
{
|
||||
public ExternHandle() : base(IntPtr.Zero, true)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsInvalid => handle == IntPtr.Zero;
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
Interop.wasm_extern_delete(handle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal unsafe struct wasm_byte_vec_t
|
||||
{
|
||||
@@ -474,6 +502,8 @@ namespace Wasmtime
|
||||
|
||||
internal unsafe delegate IntPtr WasmFuncCallback(wasm_val_t* parameters, wasm_val_t* results);
|
||||
|
||||
internal unsafe delegate IntPtr WasmtimeFuncCallback(IntPtr caller, wasm_val_t* parameters, wasm_val_t* results);
|
||||
|
||||
internal enum wasm_externkind_t : byte
|
||||
{
|
||||
WASM_EXTERN_FUNC,
|
||||
@@ -706,6 +736,11 @@ namespace Wasmtime
|
||||
[DllImport(LibraryName)]
|
||||
public static extern IntPtr wasm_extern_as_memory(IntPtr ext);
|
||||
|
||||
// Externs
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern void wasm_extern_delete(IntPtr ext);
|
||||
|
||||
// Extern type imports
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
@@ -936,6 +971,7 @@ namespace Wasmtime
|
||||
[DllImport(LibraryName)]
|
||||
public static extern WasiInstanceHandle wasi_instance_new(
|
||||
StoreHandle store,
|
||||
[MarshalAs(UnmanagedType.LPUTF8Str)] string name,
|
||||
WasiConfigHandle config,
|
||||
out IntPtr trap
|
||||
);
|
||||
@@ -975,13 +1011,44 @@ namespace Wasmtime
|
||||
[DllImport(LibraryName)]
|
||||
public static extern void wasmtime_config_cranelift_opt_level_set(WasmConfigHandle config, wasmtime_opt_level_t level);
|
||||
|
||||
// Utility functions
|
||||
|
||||
[DllImport(LibraryName, CharSet=CharSet.Ansi)]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool wasmtime_wat2wasm(
|
||||
EngineHandle engine,
|
||||
ref wasm_byte_vec_t wat,
|
||||
out wasm_byte_vec_t vec,
|
||||
out wasm_byte_vec_t error_message
|
||||
);
|
||||
public static extern bool wasmtime_wat2wasm(ref wasm_byte_vec_t text, out wasm_byte_vec_t bytes, out wasm_byte_vec_t error);
|
||||
|
||||
// Linking functions
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern LinkerHandle wasmtime_linker_new(StoreHandle store);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern void wasmtime_linker_allow_shadowing(LinkerHandle linker, [MarshalAs(UnmanagedType.I1)] bool allowShadowing);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern void wasmtime_linker_delete(IntPtr linker);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool wasmtime_linker_define(LinkerHandle linker, ref wasm_byte_vec_t module, ref wasm_byte_vec_t name, IntPtr externType);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool wasmtime_linker_define_wasi(LinkerHandle linker, WasiInstanceHandle wasi);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool wasmtime_linker_define_instance(LinkerHandle linker, ref wasm_byte_vec_t name, InstanceHandle instance);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern InstanceHandle wasmtime_linker_instantiate(LinkerHandle linker, ModuleHandle module, out IntPtr trap);
|
||||
|
||||
// Caller functions
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern FunctionHandle wasmtime_func_new(StoreHandle store, FuncTypeHandle type, WasmtimeFuncCallback callback);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern ExternHandle wasmtime_caller_export_get(IntPtr caller, ref wasm_byte_vec_t name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,34 +7,13 @@ namespace Wasmtime
|
||||
/// <summary>
|
||||
/// Represents a WebAssembly memory.
|
||||
/// </summary>
|
||||
public class Memory
|
||||
public class Memory : MemoryBase, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The size, in bytes, of a WebAssembly memory page.
|
||||
/// </summary>
|
||||
public const int PageSize = 65536;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new memory with the given minimum and maximum page counts.
|
||||
/// </summary>
|
||||
/// <param name="minimum"></param>
|
||||
/// <param name="maximum"></param>
|
||||
public Memory(uint minimum = 1, uint maximum = uint.MaxValue)
|
||||
{
|
||||
if (minimum == 0)
|
||||
{
|
||||
throw new ArgumentException("The minimum cannot be zero..", nameof(minimum));
|
||||
}
|
||||
|
||||
if (maximum < minimum)
|
||||
{
|
||||
throw new ArgumentException("The maximum cannot be less than the minimum.", nameof(maximum));
|
||||
}
|
||||
|
||||
Minimum = minimum;
|
||||
Maximum = maximum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The minimum memory size (in WebAssembly page units).
|
||||
/// </summary>
|
||||
@@ -45,226 +24,49 @@ namespace Wasmtime
|
||||
/// </summary>
|
||||
public uint Maximum { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The span of the memory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The span may become invalid if the memory grows.
|
||||
///
|
||||
/// This may happen if the memory is explicitly requested to grow or
|
||||
/// grows as a result of WebAssembly execution.
|
||||
///
|
||||
/// Therefore, the returned Span should not be stored.
|
||||
/// </remarks>
|
||||
public unsafe Span<byte> Span
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
get
|
||||
if (!Handle.IsInvalid)
|
||||
{
|
||||
var data = Interop.wasm_memory_data(_handle.DangerousGetHandle());
|
||||
var size = Convert.ToInt32(Interop.wasm_memory_data_size(_handle.DangerousGetHandle()).ToUInt32());
|
||||
return new Span<byte>(data, size);
|
||||
Handle.Dispose();
|
||||
Handle.SetHandleAsInvalid();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a UTF-8 string from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <param name="length">The length of bytes to read.</param>
|
||||
/// <returns>Returns the string read from memory.</returns>
|
||||
public string ReadString(int address, int length)
|
||||
internal Memory(Interop.StoreHandle store, uint minimum = 1, uint maximum = uint.MaxValue)
|
||||
{
|
||||
return Encoding.UTF8.GetString(Span.Slice(address, length));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a UTF-8 string at the given address.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The string to write.</param>
|
||||
/// <return>Returns the number of bytes written.</return>
|
||||
public int WriteString(int address, string value)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(value, Span.Slice(address));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a byte from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the byte read from memory.</returns>
|
||||
public byte ReadByte(int address)
|
||||
{
|
||||
return Span[address];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a byte to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The byte to write.</param>
|
||||
public void WriteByte(int address, byte value)
|
||||
{
|
||||
Span[address] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a short from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the short read from memory.</returns>
|
||||
public short ReadInt16(int address)
|
||||
{
|
||||
return BinaryPrimitives.ReadInt16LittleEndian(Span.Slice(address, 2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a short to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The short to write.</param>
|
||||
public void WriteInt16(int address, short value)
|
||||
{
|
||||
BinaryPrimitives.WriteInt16LittleEndian(Span.Slice(address, 2), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an int from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the int read from memory.</returns>
|
||||
public int ReadInt32(int address)
|
||||
{
|
||||
return BinaryPrimitives.ReadInt32LittleEndian(Span.Slice(address, 4));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an int to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The int to write.</param>
|
||||
public void WriteInt32(int address, int value)
|
||||
{
|
||||
BinaryPrimitives.WriteInt32LittleEndian(Span.Slice(address, 4), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a long from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the long read from memory.</returns>
|
||||
public long ReadInt64(int address)
|
||||
{
|
||||
return BinaryPrimitives.ReadInt64LittleEndian(Span.Slice(address, 8));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a long to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The long to write.</param>
|
||||
public void WriteInt64(int address, long value)
|
||||
{
|
||||
BinaryPrimitives.WriteInt64LittleEndian(Span.Slice(address, 8), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an IntPtr from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the IntPtr read from memory.</returns>
|
||||
public IntPtr ReadIntPtr(int address)
|
||||
{
|
||||
if (IntPtr.Size == 4)
|
||||
if (minimum == 0)
|
||||
{
|
||||
return (IntPtr)ReadInt32(address);
|
||||
throw new ArgumentException("The minimum cannot be zero.", nameof(minimum));
|
||||
}
|
||||
return (IntPtr)ReadInt64(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an IntPtr to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The IntPtr to write.</param>
|
||||
public void WriteIntPtr(int address, IntPtr value)
|
||||
{
|
||||
if (IntPtr.Size == 4)
|
||||
if (maximum < minimum)
|
||||
{
|
||||
WriteInt32(address, value.ToInt32());
|
||||
throw new ArgumentException("The maximum cannot be less than the minimum.", nameof(maximum));
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteInt64(address, value.ToInt64());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a long from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the long read from memory.</returns>
|
||||
public float ReadSingle(int address)
|
||||
{
|
||||
Minimum = minimum;
|
||||
Maximum = maximum;
|
||||
|
||||
unsafe
|
||||
{
|
||||
var i = ReadInt32(address);
|
||||
return *((float*)&i);
|
||||
Interop.wasm_limits_t limits = new Interop.wasm_limits_t();
|
||||
limits.min = minimum;
|
||||
limits.max = maximum;
|
||||
|
||||
using var memoryType = Interop.wasm_memorytype_new(&limits);
|
||||
Handle = Interop.wasm_memory_new(store, memoryType);
|
||||
|
||||
if (Handle.IsInvalid)
|
||||
{
|
||||
throw new WasmtimeException("Failed to create Wasmtime memory.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a single to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The single to write.</param>
|
||||
public void WriteSingle(int address, float value)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
WriteInt32(address, *(int*)&value);
|
||||
}
|
||||
}
|
||||
protected override IntPtr MemoryHandle => Handle.DangerousGetHandle();
|
||||
|
||||
/// <summary>
|
||||
/// Reads a double from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the double read from memory.</returns>
|
||||
public double ReadDouble(int address)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var i = ReadInt64(address);
|
||||
return *((double*)&i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a double to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The double to write.</param>
|
||||
public void WriteDouble(int address, double value)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
WriteInt64(address, *(long*)&value);
|
||||
}
|
||||
}
|
||||
|
||||
internal Interop.MemoryHandle Handle
|
||||
{
|
||||
get
|
||||
{
|
||||
return _handle;
|
||||
}
|
||||
set
|
||||
{
|
||||
_handle = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Interop.MemoryHandle _handle;
|
||||
internal Interop.MemoryHandle Handle { get; private set; }
|
||||
}
|
||||
}
|
||||
|
||||
236
crates/misc/dotnet/src/MemoryBase.cs
Normal file
236
crates/misc/dotnet/src/MemoryBase.cs
Normal file
@@ -0,0 +1,236 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Buffers.Binary;
|
||||
|
||||
namespace Wasmtime
|
||||
{
|
||||
public abstract class MemoryBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The span of the memory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The span may become invalid if the memory grows.
|
||||
///
|
||||
/// This may happen if the memory is explicitly requested to grow or
|
||||
/// grows as a result of WebAssembly execution.
|
||||
///
|
||||
/// Therefore, the returned Span should not be stored.
|
||||
/// </remarks>
|
||||
public unsafe Span<byte> Span
|
||||
{
|
||||
get
|
||||
{
|
||||
var data = Interop.wasm_memory_data(MemoryHandle);
|
||||
var size = Convert.ToInt32(Interop.wasm_memory_data_size(MemoryHandle).ToUInt32());
|
||||
return new Span<byte>(data, size);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a UTF-8 string from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <param name="length">The length of bytes to read.</param>
|
||||
/// <returns>Returns the string read from memory.</returns>
|
||||
public string ReadString(int address, int length)
|
||||
{
|
||||
return Encoding.UTF8.GetString(Span.Slice(address, length));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a null-terminated UTF-8 string from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the string read from memory.</returns>
|
||||
public string ReadNullTerminatedString(int address)
|
||||
{
|
||||
var slice = Span.Slice(address);
|
||||
var terminator = slice.IndexOf((byte)0);
|
||||
if (terminator == -1)
|
||||
{
|
||||
throw new InvalidOperationException("string is not null terminated");
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(slice.Slice(0, terminator));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a UTF-8 string at the given address.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The string to write.</param>
|
||||
/// <return>Returns the number of bytes written.</return>
|
||||
public int WriteString(int address, string value)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(value, Span.Slice(address));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a byte from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the byte read from memory.</returns>
|
||||
public byte ReadByte(int address)
|
||||
{
|
||||
return Span[address];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a byte to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The byte to write.</param>
|
||||
public void WriteByte(int address, byte value)
|
||||
{
|
||||
Span[address] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a short from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the short read from memory.</returns>
|
||||
public short ReadInt16(int address)
|
||||
{
|
||||
return BinaryPrimitives.ReadInt16LittleEndian(Span.Slice(address, 2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a short to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The short to write.</param>
|
||||
public void WriteInt16(int address, short value)
|
||||
{
|
||||
BinaryPrimitives.WriteInt16LittleEndian(Span.Slice(address, 2), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an int from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the int read from memory.</returns>
|
||||
public int ReadInt32(int address)
|
||||
{
|
||||
return BinaryPrimitives.ReadInt32LittleEndian(Span.Slice(address, 4));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an int to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The int to write.</param>
|
||||
public void WriteInt32(int address, int value)
|
||||
{
|
||||
BinaryPrimitives.WriteInt32LittleEndian(Span.Slice(address, 4), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a long from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the long read from memory.</returns>
|
||||
public long ReadInt64(int address)
|
||||
{
|
||||
return BinaryPrimitives.ReadInt64LittleEndian(Span.Slice(address, 8));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a long to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The long to write.</param>
|
||||
public void WriteInt64(int address, long value)
|
||||
{
|
||||
BinaryPrimitives.WriteInt64LittleEndian(Span.Slice(address, 8), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an IntPtr from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the IntPtr read from memory.</returns>
|
||||
public IntPtr ReadIntPtr(int address)
|
||||
{
|
||||
if (IntPtr.Size == 4)
|
||||
{
|
||||
return (IntPtr)ReadInt32(address);
|
||||
}
|
||||
return (IntPtr)ReadInt64(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an IntPtr to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The IntPtr to write.</param>
|
||||
public void WriteIntPtr(int address, IntPtr value)
|
||||
{
|
||||
if (IntPtr.Size == 4)
|
||||
{
|
||||
WriteInt32(address, value.ToInt32());
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteInt64(address, value.ToInt64());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a long from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the long read from memory.</returns>
|
||||
public float ReadSingle(int address)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var i = ReadInt32(address);
|
||||
return *((float*)&i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a single to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The single to write.</param>
|
||||
public void WriteSingle(int address, float value)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
WriteInt32(address, *(int*)&value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a double from memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to read from.</param>
|
||||
/// <returns>Returns the double read from memory.</returns>
|
||||
public double ReadDouble(int address)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var i = ReadInt64(address);
|
||||
return *((double*)&i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a double to memory.
|
||||
/// </summary>
|
||||
/// <param name="address">The zero-based address to write to.</param>
|
||||
/// <param name="value">The double to write.</param>
|
||||
public void WriteDouble(int address, double value)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
WriteInt64(address, *(long*)&value);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract IntPtr MemoryHandle { get; }
|
||||
}
|
||||
}
|
||||
@@ -8,75 +8,6 @@ namespace Wasmtime
|
||||
/// </summary>
|
||||
public class Module : IDisposable
|
||||
{
|
||||
internal Module(Store store, string name, byte[] bytes)
|
||||
{
|
||||
if (store.Handle.IsInvalid)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(store));
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte *ptr = bytes)
|
||||
{
|
||||
Interop.wasm_byte_vec_t vec;
|
||||
vec.size = (UIntPtr)bytes.Length;
|
||||
vec.data = ptr;
|
||||
|
||||
Handle = Interop.wasm_module_new(store.Handle, ref vec);
|
||||
}
|
||||
|
||||
if (Handle.IsInvalid)
|
||||
{
|
||||
throw new WasmtimeException($"WebAssembly module '{name}' is not valid.");
|
||||
}
|
||||
}
|
||||
|
||||
Store = store;
|
||||
Name = name;
|
||||
Imports = new Wasmtime.Imports.Imports(this);
|
||||
Exports = new Wasmtime.Exports.Exports(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a WebAssembly module for the given host.
|
||||
/// </summary>
|
||||
/// <param name="host">The host to use for the WebAssembly module's instance.</param>
|
||||
/// <returns>Returns a new <see cref="Instance" />.</returns>
|
||||
public Instance Instantiate(IHost host = null)
|
||||
{
|
||||
return Instantiate(null, host);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a WebAssembly module for the given host.
|
||||
/// </summary>
|
||||
/// <param name="wasi">The WASI instance to use for WASI imports.</param>
|
||||
/// <param name="host">The host to use for the WebAssembly module's instance.</param>
|
||||
/// <returns>Returns a new <see cref="Instance" />.</returns>
|
||||
public Instance Instantiate(Wasi wasi, IHost host = null)
|
||||
{
|
||||
if (!(host?.Instance is null))
|
||||
{
|
||||
throw new InvalidOperationException("The host has already been associated with an instantiated module.");
|
||||
}
|
||||
|
||||
var instance = new Instance(this, wasi, host);
|
||||
|
||||
if (!(host is null))
|
||||
{
|
||||
host.Instance = instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Store"/> associated with the module.
|
||||
/// </summary>
|
||||
public Store Store { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the module.
|
||||
/// </summary>
|
||||
@@ -108,6 +39,30 @@ namespace Wasmtime
|
||||
}
|
||||
}
|
||||
|
||||
internal Module(Interop.StoreHandle store, string name, byte[] bytes)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (byte *ptr = bytes)
|
||||
{
|
||||
Interop.wasm_byte_vec_t vec;
|
||||
vec.size = (UIntPtr)bytes.Length;
|
||||
vec.data = ptr;
|
||||
|
||||
Handle = Interop.wasm_module_new(store, ref vec);
|
||||
}
|
||||
|
||||
if (Handle.IsInvalid)
|
||||
{
|
||||
throw new WasmtimeException($"WebAssembly module '{name}' is not valid.");
|
||||
}
|
||||
}
|
||||
|
||||
Name = name;
|
||||
Imports = new Wasmtime.Imports.Imports(this);
|
||||
Exports = new Wasmtime.Exports.Exports(this);
|
||||
}
|
||||
|
||||
internal Interop.ModuleHandle Handle { get; private set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,18 +5,8 @@ namespace Wasmtime
|
||||
/// <summary>
|
||||
/// Represents a mutable WebAssembly global value.
|
||||
/// </summary>
|
||||
public class MutableGlobal<T>
|
||||
public class MutableGlobal<T> : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="MutableGlobal<T>" /> with the given initial value.
|
||||
/// </summary>
|
||||
/// <param name="initialValue">The initial value of the global.</param>
|
||||
public MutableGlobal(T initialValue)
|
||||
{
|
||||
InitialValue = initialValue;
|
||||
Kind = Interop.ToValueKind(typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value of the global.
|
||||
/// </summary>
|
||||
@@ -32,10 +22,7 @@ namespace Wasmtime
|
||||
unsafe
|
||||
{
|
||||
var v = stackalloc Interop.wasm_val_t[1];
|
||||
|
||||
Interop.wasm_global_get(Handle.DangerousGetHandle(), v);
|
||||
|
||||
// TODO: figure out a way that doesn't box the value
|
||||
return (T)Interop.ToObject(v);
|
||||
}
|
||||
}
|
||||
@@ -46,7 +33,6 @@ namespace Wasmtime
|
||||
throw new InvalidOperationException("The global cannot be used before it is instantiated.");
|
||||
}
|
||||
|
||||
// TODO: figure out a way that doesn't box the value
|
||||
var v = Interop.ToValue(value, Kind);
|
||||
|
||||
unsafe
|
||||
@@ -56,10 +42,53 @@ namespace Wasmtime
|
||||
}
|
||||
}
|
||||
|
||||
internal ValueKind Kind { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the value kind of the global.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public ValueKind Kind { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!Handle.IsInvalid)
|
||||
{
|
||||
Handle.Dispose();
|
||||
Handle.SetHandleAsInvalid();
|
||||
}
|
||||
}
|
||||
|
||||
internal MutableGlobal(Interop.StoreHandle store, T initialValue)
|
||||
{
|
||||
if (!Interop.TryGetValueKind(typeof(T), out var kind))
|
||||
{
|
||||
throw new WasmtimeException($"Mutable global variables cannot be of type '{typeof(T).ToString()}'.");
|
||||
}
|
||||
|
||||
Kind = kind;
|
||||
|
||||
var value = Interop.ToValue((object)initialValue, Kind);
|
||||
|
||||
var valueType = Interop.wasm_valtype_new(value.kind);
|
||||
var valueTypeHandle = valueType.DangerousGetHandle();
|
||||
valueType.SetHandleAsInvalid();
|
||||
|
||||
using var globalType = Interop.wasm_globaltype_new(
|
||||
valueTypeHandle,
|
||||
Interop.wasm_mutability_t.WASM_VAR
|
||||
);
|
||||
|
||||
unsafe
|
||||
{
|
||||
Handle = Interop.wasm_global_new(store, globalType, &value);
|
||||
|
||||
if (Handle.IsInvalid)
|
||||
{
|
||||
throw new WasmtimeException("Failed to create mutable Wasmtime global.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal Interop.GlobalHandle Handle { get; set; }
|
||||
|
||||
internal T InitialValue { get; private set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Wasmtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the Wasmtime store.
|
||||
/// </summary>
|
||||
public sealed class Store : IDisposable
|
||||
{
|
||||
internal Store(Engine engine)
|
||||
{
|
||||
Handle = Interop.wasm_store_new(engine.Handle);
|
||||
|
||||
if (Handle.IsInvalid)
|
||||
{
|
||||
throw new WasmtimeException("Failed to create Wasmtime store.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="Module"/> given the module name and bytes.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the module.</param>
|
||||
/// <param name="bytes">The bytes of the module.</param>
|
||||
/// <returns>Retuw <see cref="Module"/>.</returns>
|
||||
public Module CreateModule(string name, byte[] bytes)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (bytes is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bytes));
|
||||
}
|
||||
|
||||
return new Module(this, name, bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="Module"/> given the module name and path to the WebAssembly file.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the module.</param>
|
||||
/// <param name="path">The path to the WebAssembly file.</param>
|
||||
/// <returns>Returns a new <see cref="Module"/>.</returns>
|
||||
public Module CreateModule(string name, string path)
|
||||
{
|
||||
return CreateModule(name, File.ReadAllBytes(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="Module"/> given the path to the WebAssembly file.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the WebAssembly file.</param>
|
||||
/// <returns>Returns a new <see cref="Module"/>.</returns>
|
||||
public Module CreateModule(string path)
|
||||
{
|
||||
return CreateModule(Path.GetFileNameWithoutExtension(path), path);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!Handle.IsInvalid)
|
||||
{
|
||||
Handle.Dispose();
|
||||
Handle.SetHandleAsInvalid();
|
||||
}
|
||||
}
|
||||
|
||||
internal Interop.StoreHandle Handle { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System;
|
||||
using Wasmtime.Bindings;
|
||||
using Wasmtime.Imports;
|
||||
|
||||
namespace Wasmtime
|
||||
{
|
||||
public class Wasi
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a default <see cref="Wasi"/> instance.
|
||||
/// </summary>
|
||||
public Wasi(Store store) :
|
||||
this(
|
||||
(store ?? throw new ArgumentNullException(nameof(store))).Handle,
|
||||
Interop.wasi_config_new()
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
internal Wasi(Interop.StoreHandle store, Interop.WasiConfigHandle config)
|
||||
{
|
||||
IntPtr trap;
|
||||
Handle = Interop.wasi_instance_new(store, config, out trap);
|
||||
config.SetHandleAsInvalid();
|
||||
|
||||
if (trap != IntPtr.Zero)
|
||||
{
|
||||
throw TrapException.FromOwnedTrap(trap);
|
||||
}
|
||||
}
|
||||
|
||||
internal WasiBinding Bind(Import import)
|
||||
{
|
||||
var export = Interop.wasi_instance_bind_import(Handle, import.Handle);
|
||||
if (export == IntPtr.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new WasiBinding(export);
|
||||
}
|
||||
|
||||
internal Interop.WasiInstanceHandle Handle { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -5,23 +5,16 @@ using System.Linq;
|
||||
namespace Wasmtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a builder of <see cref="Wasi"/> instances.
|
||||
/// Represents a WASI configuration.
|
||||
/// </summary>
|
||||
public class WasiBuilder
|
||||
public class WasiConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="WasiBuilder" />.
|
||||
/// </summary>
|
||||
public WasiBuilder()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a command line argument to the builder.
|
||||
/// Adds a command line argument to the configuration.
|
||||
/// </summary>
|
||||
/// <param name="arg">The command line argument to add.</param>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public WasiBuilder WithArg(string arg)
|
||||
/// <returns>Returns the current configuration.</returns>
|
||||
public WasiConfiguration WithArg(string arg)
|
||||
{
|
||||
if (arg is null)
|
||||
{
|
||||
@@ -39,11 +32,11 @@ namespace Wasmtime
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple command line arguments to the builder.
|
||||
/// Adds multiple command line arguments to the configuration.
|
||||
/// </summary>
|
||||
/// <param name="args">The command line arguments to add.</param>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public WasiBuilder WithArgs(IEnumerable<string> args)
|
||||
/// <returns>Returns the current configuration.</returns>
|
||||
public WasiConfiguration WithArgs(IEnumerable<string> args)
|
||||
{
|
||||
if (args is null)
|
||||
{
|
||||
@@ -64,21 +57,21 @@ namespace Wasmtime
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple command line arguments to the builder.
|
||||
/// Adds multiple command line arguments to the configuration.
|
||||
/// </summary>
|
||||
/// <param name="args">The command line arguments to add.</param>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public WasiBuilder WithArgs(params string[] args)
|
||||
/// <returns>Returns the current configuration.</returns>
|
||||
public WasiConfiguration WithArgs(params string[] args)
|
||||
{
|
||||
return WithArgs((IEnumerable<string>)args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the builder to inherit command line arguments.
|
||||
/// Sets the configuration to inherit command line arguments.
|
||||
/// </summary>
|
||||
/// <remarks>Any explicitly specified command line arguments will be removed.</remarks>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public WasiBuilder WithInheritedArgs()
|
||||
/// <returns>Returns the current configuration.</returns>
|
||||
public WasiConfiguration WithInheritedArgs()
|
||||
{
|
||||
_inheritArgs = true;
|
||||
_args.Clear();
|
||||
@@ -87,12 +80,12 @@ namespace Wasmtime
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an environment variable to the builder.
|
||||
/// Adds an environment variable to the configuration.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the environment variable.</param>
|
||||
/// <param name="value">The value of the environment variable.</param>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public WasiBuilder WithEnvironmentVariable(string name, string value)
|
||||
/// <returns>Returns the current configuration.</returns>
|
||||
public WasiConfiguration WithEnvironmentVariable(string name, string value)
|
||||
{
|
||||
if (name is null)
|
||||
{
|
||||
@@ -114,11 +107,11 @@ namespace Wasmtime
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple environment variables to the builder.
|
||||
/// Adds multiple environment variables to the configuration.
|
||||
/// </summary>
|
||||
/// <param name="vars">The name-value tuples of the environment variables to add.</param>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public WasiBuilder WithEnvironmentVariables(IEnumerable<(string,string)> vars)
|
||||
/// <returns>Returns the current configuration.</returns>
|
||||
public WasiConfiguration WithEnvironmentVariables(IEnumerable<(string,string)> vars)
|
||||
{
|
||||
if (vars is null)
|
||||
{
|
||||
@@ -136,11 +129,11 @@ namespace Wasmtime
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the builder to inherit environment variables.
|
||||
/// Sets the configuration to inherit environment variables.
|
||||
/// </summary>
|
||||
/// <remarks>Any explicitly specified environment variables will be removed.</remarks>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public WasiBuilder WithInheritedEnvironment()
|
||||
/// <returns>Returns the current configuration.</returns>
|
||||
public WasiConfiguration WithInheritedEnvironment()
|
||||
{
|
||||
_inheritEnv = true;
|
||||
_vars.Clear();
|
||||
@@ -148,11 +141,11 @@ namespace Wasmtime
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the builder to use the given file path as stdin.
|
||||
/// Sets the configuration to use the given file path as stdin.
|
||||
/// </summary>
|
||||
/// <param name="path">The file to use as stdin.</param>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public WasiBuilder WithStandardInput(string path)
|
||||
/// <returns>Returns the current configuration.</returns>
|
||||
public WasiConfiguration WithStandardInput(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
@@ -165,11 +158,11 @@ namespace Wasmtime
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the builder to inherit stdin.
|
||||
/// Sets the configuration to inherit stdin.
|
||||
/// </summary>
|
||||
/// <remarks>Any explicitly specified stdin file will be removed.</remarks>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public WasiBuilder WithInheritedStandardInput()
|
||||
/// <returns>Returns the current configuration.</returns>
|
||||
public WasiConfiguration WithInheritedStandardInput()
|
||||
{
|
||||
_inheritStandardInput = true;
|
||||
_standardInputPath = null;
|
||||
@@ -177,11 +170,11 @@ namespace Wasmtime
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the builder to use the given file path as stdout.
|
||||
/// Sets the configuration to use the given file path as stdout.
|
||||
/// </summary>
|
||||
/// <param name="path">The file to use as stdout.</param>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public WasiBuilder WithStandardOutput(string path)
|
||||
/// <returns>Returns the current configuration.</returns>
|
||||
public WasiConfiguration WithStandardOutput(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
@@ -194,11 +187,11 @@ namespace Wasmtime
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the builder to inherit stdout.
|
||||
/// Sets the configuration to inherit stdout.
|
||||
/// </summary>
|
||||
/// <remarks>Any explicitly specified stdout file will be removed.</remarks>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public WasiBuilder WithInheritedStandardOutput()
|
||||
/// <returns>Returns the current configuration.</returns>
|
||||
public WasiConfiguration WithInheritedStandardOutput()
|
||||
{
|
||||
_inheritStandardOutput = true;
|
||||
_standardOutputPath = null;
|
||||
@@ -206,11 +199,11 @@ namespace Wasmtime
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the builder to use the given file path as stderr.
|
||||
/// Sets the configuration to use the given file path as stderr.
|
||||
/// </summary>
|
||||
/// <param name="path">The file to use as stderr.</param>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public WasiBuilder WithStandardError(string path)
|
||||
/// <returns>Returns the current configuration.</returns>
|
||||
public WasiConfiguration WithStandardError(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
@@ -223,11 +216,11 @@ namespace Wasmtime
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the builder to inherit stderr.
|
||||
/// Sets the configuration to inherit stderr.
|
||||
/// </summary>
|
||||
/// <remarks>Any explicitly specified stderr file will be removed.</remarks>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public WasiBuilder WithInheritedStandardError()
|
||||
/// <returns>Returns the current configuration.</returns>
|
||||
public WasiConfiguration WithInheritedStandardError()
|
||||
{
|
||||
_inheritStandardError = true;
|
||||
_standardErrorPath = null;
|
||||
@@ -235,12 +228,12 @@ namespace Wasmtime
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a preopen directory to the builder.
|
||||
/// Adds a preopen directory to the configuration.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the directory to add.</param>
|
||||
/// <param name="guestPath">The path the guest will use to open the directory.</param>
|
||||
/// <returns>Returns the current builder.</returns>
|
||||
public WasiBuilder WithPreopenedDirectory(string path, string guestPath)
|
||||
/// <returns>Returns the current configuration.</returns>
|
||||
public WasiConfiguration WithPreopenedDirectory(string path, string guestPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
@@ -255,12 +248,7 @@ namespace Wasmtime
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the <see cref="Wasi" /> instance.
|
||||
/// </summary>
|
||||
/// <param name="store">The <see cref="Store" /> to use.</param>
|
||||
/// <returns>Returns the new <see cref="Wasi" /> instance.</returns>
|
||||
public Wasi Build(Store store)
|
||||
internal Interop.WasiInstanceHandle CreateWasi(Interop.StoreHandle store, string name)
|
||||
{
|
||||
var config = Interop.wasi_config_new();
|
||||
|
||||
@@ -270,8 +258,22 @@ namespace Wasmtime
|
||||
SetStandardOut(config);
|
||||
SetStandardError(config);
|
||||
SetPreopenDirectories(config);
|
||||
|
||||
return new Wasi(store.Handle, config);
|
||||
|
||||
IntPtr trap;
|
||||
var wasi = Interop.wasi_instance_new(store, name, config, out trap);
|
||||
config.SetHandleAsInvalid();
|
||||
|
||||
if (trap != IntPtr.Zero)
|
||||
{
|
||||
throw TrapException.FromOwnedTrap(trap);
|
||||
}
|
||||
|
||||
if (wasi.IsInvalid)
|
||||
{
|
||||
throw new WasmtimeException($"Failed to create instance for WASI module '{name}'.");
|
||||
}
|
||||
|
||||
return wasi;
|
||||
}
|
||||
|
||||
private unsafe void SetConfigArgs(Interop.WasiConfigHandle config)
|
||||
@@ -8,14 +8,12 @@ namespace Wasmtime.Tests
|
||||
{
|
||||
public ModuleFixture()
|
||||
{
|
||||
Engine = new EngineBuilder()
|
||||
Host = new HostBuilder()
|
||||
.WithMultiValue(true)
|
||||
.WithReferenceTypes(true)
|
||||
.Build();
|
||||
Store = Engine.CreateStore();
|
||||
var wat = Path.Combine("Modules", ModuleFileName);
|
||||
var wasm = Engine.WatToWasm(File.ReadAllText(wat));
|
||||
Module = Store.CreateModule(wat, wasm);
|
||||
|
||||
Module = Host.LoadModuleText(Path.Combine("Modules", ModuleFileName));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -26,21 +24,14 @@ namespace Wasmtime.Tests
|
||||
Module = null;
|
||||
}
|
||||
|
||||
if (!(Store is null))
|
||||
if (!(Host is null))
|
||||
{
|
||||
Store.Dispose();
|
||||
Store = null;
|
||||
}
|
||||
|
||||
if (!(Engine is null))
|
||||
{
|
||||
Engine.Dispose();
|
||||
Engine = null;
|
||||
Host.Dispose();
|
||||
Host = null;
|
||||
}
|
||||
}
|
||||
|
||||
public Engine Engine { get; set; }
|
||||
public Store Store { get; set; }
|
||||
public Host Host { get; set; }
|
||||
public Module Module { get; set; }
|
||||
|
||||
protected abstract string ModuleFileName { get; }
|
||||
|
||||
@@ -14,20 +14,16 @@ namespace Wasmtime.Tests
|
||||
{
|
||||
const string THROW_MESSAGE = "Test error message for wasmtime dotnet unit tests.";
|
||||
|
||||
class MyHost : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("add", Module = "env")]
|
||||
public int Add(int x, int y) => x + y;
|
||||
|
||||
[Import("do_throw", Module = "env")]
|
||||
public void Throw() => throw new Exception(THROW_MESSAGE);
|
||||
}
|
||||
|
||||
public FunctionThunkingTests(FunctionThunkingFixture fixture)
|
||||
{
|
||||
Fixture = fixture;
|
||||
|
||||
Fixture.Host.DefineFunction("env", "add", (int x, int y) => x + y);
|
||||
Fixture.Host.DefineFunction("env", "swap", (int x, int y) => (y, x));
|
||||
Fixture.Host.DefineFunction("env", "do_throw", () => throw new Exception(THROW_MESSAGE));
|
||||
Fixture.Host.DefineFunction("env", "check_string", (Caller caller, int address, int length) => {
|
||||
caller.GetMemory("mem").ReadString(address, length).Should().Be("Hello World");
|
||||
});
|
||||
}
|
||||
|
||||
private FunctionThunkingFixture Fixture { get; }
|
||||
@@ -35,30 +31,37 @@ namespace Wasmtime.Tests
|
||||
[Fact]
|
||||
public void ItBindsImportMethodsAndCallsThemCorrectly()
|
||||
{
|
||||
var host = new MyHost();
|
||||
using var instance = Fixture.Module.Instantiate(host);
|
||||
using dynamic instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
|
||||
var add_func = instance.Externs.Functions.Where(f => f.Name == "add_wrapper").Single();
|
||||
int invoke_add(int x, int y) => (int)add_func.Invoke(new object[] { x, y });
|
||||
int x = instance.add(40, 2);
|
||||
x.Should().Be(42);
|
||||
x = instance.add(22, 5);
|
||||
x.Should().Be(27);
|
||||
|
||||
invoke_add(40, 2).Should().Be(42);
|
||||
invoke_add(22, 5).Should().Be(27);
|
||||
object[] results = instance.swap(10, 100);
|
||||
results.Should().Equal(new object[] { 100, 10 });
|
||||
|
||||
//Collect garbage to make sure delegate function pointers pasted to wasmtime are rooted.
|
||||
instance.check_string();
|
||||
|
||||
// Collect garbage to make sure delegate function pointers pasted to wasmtime are rooted.
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
|
||||
invoke_add(1970, 50).Should().Be(2020);
|
||||
x = instance.add(1970, 50);
|
||||
x.Should().Be(2020);
|
||||
|
||||
results = instance.swap(2020, 1970);
|
||||
results.Should().Equal(new object[] { 1970, 2020 });
|
||||
|
||||
instance.check_string();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItPropagatesExceptionsToCallersViaTraps()
|
||||
{
|
||||
var host = new MyHost();
|
||||
using var instance = Fixture.Module.Instantiate(host);
|
||||
using dynamic instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
|
||||
var throw_func = instance.Externs.Functions.Where(f => f.Name == "do_throw_wrapper").Single();
|
||||
Action action = () => throw_func.Invoke();
|
||||
Action action = () => instance.do_throw();
|
||||
|
||||
action
|
||||
.Should()
|
||||
|
||||
@@ -15,11 +15,6 @@ namespace Wasmtime.Tests
|
||||
|
||||
public class GlobalExportsTests : IClassFixture<GlobalExportsFixture>
|
||||
{
|
||||
public class Host : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
}
|
||||
|
||||
public GlobalExportsTests(GlobalExportsFixture fixture)
|
||||
{
|
||||
Fixture = fixture;
|
||||
@@ -46,7 +41,7 @@ namespace Wasmtime.Tests
|
||||
[Fact]
|
||||
public void ItCreatesExternsForTheGlobals()
|
||||
{
|
||||
using var instance = Fixture.Module.Instantiate(new Host());
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
|
||||
dynamic dyn = instance;
|
||||
var globals = instance.Externs.Globals;
|
||||
|
||||
@@ -11,106 +11,11 @@ namespace Wasmtime.Tests
|
||||
|
||||
public class GlobalImportBindingTests : IClassFixture<GlobalImportBindingFixture>
|
||||
{
|
||||
class NoImportsHost : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
}
|
||||
|
||||
class GlobalIsStaticHost : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("global_i32_mut")]
|
||||
public static int x = 0;
|
||||
}
|
||||
|
||||
class GlobalIsNotReadOnlyHost : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("global_i32_mut")]
|
||||
public int x = 0;
|
||||
}
|
||||
|
||||
class NotAGlobalHost : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("global_i32_mut")]
|
||||
public readonly int x = 0;
|
||||
}
|
||||
|
||||
class NotAValidGlobalTypeHost : IHost
|
||||
{
|
||||
public struct NotAValue
|
||||
{
|
||||
}
|
||||
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("global_i32_mut")]
|
||||
public readonly MutableGlobal<NotAValue> x = new MutableGlobal<NotAValue>(new NotAValue());
|
||||
}
|
||||
|
||||
class TypeMismatchHost : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("global_i32_mut")]
|
||||
public readonly MutableGlobal<long> x = new MutableGlobal<long>(0);
|
||||
}
|
||||
|
||||
class NotMutHost : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("global_i32_mut")]
|
||||
public readonly Global<int> Int32Mut = new Global<int>(0);
|
||||
}
|
||||
|
||||
class MutHost : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("global_i32_mut")]
|
||||
public readonly MutableGlobal<int> Int32Mut = new MutableGlobal<int>(0);
|
||||
|
||||
[Import("global_i32")]
|
||||
public readonly MutableGlobal<int> Int32 = new MutableGlobal<int>(0);
|
||||
}
|
||||
|
||||
class ValidHost : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("global_i32_mut")]
|
||||
public readonly MutableGlobal<int> Int32Mut = new MutableGlobal<int>(0);
|
||||
|
||||
[Import("global_i32")]
|
||||
public readonly Global<int> Int32 = new Global<int>(1);
|
||||
|
||||
[Import("global_i64_mut")]
|
||||
public readonly MutableGlobal<long> Int64Mut = new MutableGlobal<long>(2);
|
||||
|
||||
[Import("global_i64")]
|
||||
public readonly Global<long> Int64 = new Global<long>(3);
|
||||
|
||||
[Import("global_f32_mut")]
|
||||
public readonly MutableGlobal<float> Float32Mut = new MutableGlobal<float>(4);
|
||||
|
||||
[Import("global_f32")]
|
||||
public readonly Global<float> Float32 = new Global<float>(5);
|
||||
|
||||
[Import("global_f64_mut")]
|
||||
public readonly MutableGlobal<double> Float64Mut = new MutableGlobal<double>(6);
|
||||
|
||||
[Import("global_f64")]
|
||||
public readonly Global<double> Float64 = new Global<double>(7);
|
||||
}
|
||||
|
||||
public GlobalImportBindingTests(GlobalImportBindingFixture fixture)
|
||||
{
|
||||
Fixture = fixture;
|
||||
|
||||
Fixture.Host.ClearDefinitions();
|
||||
}
|
||||
|
||||
private GlobalImportBindingFixture Fixture { get; set; }
|
||||
@@ -118,140 +23,119 @@ namespace Wasmtime.Tests
|
||||
[Fact]
|
||||
public void ItFailsToInstantiateWithMissingImport()
|
||||
{
|
||||
Action action = () => { using var instance = Fixture.Module.Instantiate(new NoImportsHost()); };
|
||||
Action action = () => { using var instance = Fixture.Host.Instantiate(Fixture.Module); };
|
||||
|
||||
action
|
||||
.Should()
|
||||
.Throw<WasmtimeException>()
|
||||
.WithMessage("Failed to bind global import 'global_i32_mut': the host does not contain a global field with a matching 'Import' attribute.");
|
||||
.WithMessage("unknown import: `::global_i32_mut` has not been defined");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItFailsToInstantiateWithStaticField()
|
||||
public void ItFailsToDefineAGlobalWithInvalidType()
|
||||
{
|
||||
Action action = () => { using var instance = Fixture.Module.Instantiate(new GlobalIsStaticHost()); };
|
||||
Action action = () => { Fixture.Host.DefineGlobal("", "global_i32_mut", "invalid"); };
|
||||
|
||||
action
|
||||
.Should()
|
||||
.Throw<WasmtimeException>()
|
||||
.WithMessage("Unable to bind 'GlobalIsStaticHost.x' to WebAssembly import 'global_i32_mut': field cannot be static.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItFailsToInstantiateWithNonReadOnlyField()
|
||||
{
|
||||
Action action = () => { using var instance = Fixture.Module.Instantiate(new GlobalIsNotReadOnlyHost()); };
|
||||
|
||||
action
|
||||
.Should()
|
||||
.Throw<WasmtimeException>()
|
||||
.WithMessage("Unable to bind 'GlobalIsNotReadOnlyHost.x' to WebAssembly import 'global_i32_mut': field must be readonly.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItFailsToInstantiateWithInvalidType()
|
||||
{
|
||||
Action action = () => { using var instance = Fixture.Module.Instantiate(new NotAGlobalHost()); };
|
||||
|
||||
action
|
||||
.Should()
|
||||
.Throw<WasmtimeException>()
|
||||
.WithMessage("Unable to bind 'NotAGlobalHost.x' to WebAssembly import 'global_i32_mut': field is expected to be of type 'Global<T>'.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItFailsToInstantiateWithInvalidGlobalType()
|
||||
{
|
||||
Action action = () => { using var instance = Fixture.Module.Instantiate(new NotAValidGlobalTypeHost()); };
|
||||
|
||||
action
|
||||
.Should()
|
||||
.Throw<NotSupportedException>()
|
||||
.WithMessage("Type 'Wasmtime.Tests.GlobalImportBindingTests+NotAValidGlobalTypeHost+NotAValue' is not a supported WebAssembly value type.");
|
||||
.WithMessage("Global variables cannot be of type 'System.String'.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItFailsToInstantiateWithGlobalTypeMismatch()
|
||||
{
|
||||
Action action = () => { using var instance = Fixture.Module.Instantiate(new TypeMismatchHost()); };
|
||||
Fixture.Host.DefineGlobal("", "global_i32_mut", 0L);
|
||||
Action action = () => { using var instance = Fixture.Host.Instantiate(Fixture.Module); };
|
||||
|
||||
action
|
||||
.Should()
|
||||
.Throw<WasmtimeException>()
|
||||
.WithMessage("Unable to bind 'TypeMismatchHost.x' to WebAssembly import 'global_i32_mut': global type argument is expected to be of type 'int'.");
|
||||
.WithMessage("incompatible import type for `::global_i32_mut` specified*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItFailsToInstantiateWhenGlobalIsNotMut()
|
||||
{
|
||||
Action action = () => { using var instance = Fixture.Module.Instantiate(new NotMutHost()); };
|
||||
Fixture.Host.DefineGlobal("", "global_i32_mut", 1);
|
||||
Action action = () => { using var instance = Fixture.Host.Instantiate(Fixture.Module); };
|
||||
|
||||
action
|
||||
.Should()
|
||||
.Throw<WasmtimeException>()
|
||||
.WithMessage("Unable to bind 'NotMutHost.Int32Mut' to WebAssembly import 'global_i32_mut': the import is mutable (use the 'MutableGlobal' type).");
|
||||
.WithMessage("incompatible import type for `::global_i32_mut` specified*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItFailsToInstantiateWhenGlobalIsMut()
|
||||
{
|
||||
Action action = () => { using var instance = Fixture.Module.Instantiate(new MutHost()); };
|
||||
Fixture.Host.DefineMutableGlobal("", "global_i32_mut", 0);
|
||||
Fixture.Host.DefineMutableGlobal("", "global_i32", 0);
|
||||
Action action = () => { using var instance = Fixture.Host.Instantiate(Fixture.Module); };
|
||||
|
||||
action
|
||||
.Should()
|
||||
.Throw<WasmtimeException>()
|
||||
.WithMessage("Unable to bind 'MutHost.Int32' to WebAssembly import 'global_i32': the import is constant (use the 'Global' type).");
|
||||
.WithMessage("incompatible import type for `::global_i32` specified*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItBindsTheGlobalsCorrectly()
|
||||
{
|
||||
var host = new ValidHost();
|
||||
using dynamic instance = Fixture.Module.Instantiate(host);
|
||||
var global_i32_mut = Fixture.Host.DefineMutableGlobal("", "global_i32_mut", 0);
|
||||
var global_i32 = Fixture.Host.DefineGlobal("", "global_i32", 1);
|
||||
var global_i64_mut = Fixture.Host.DefineMutableGlobal("", "global_i64_mut", 2L);
|
||||
var global_i64 = Fixture.Host.DefineGlobal("", "global_i64", 3L);
|
||||
var global_f32_mut = Fixture.Host.DefineMutableGlobal("", "global_f32_mut", 4f);
|
||||
var global_f32 = Fixture.Host.DefineGlobal("", "global_f32", 5f);
|
||||
var global_f64_mut = Fixture.Host.DefineMutableGlobal("", "global_f64_mut", 6.0);
|
||||
var global_f64 = Fixture.Host.DefineGlobal("", "global_f64", 7.0);
|
||||
|
||||
host.Int32Mut.Value.Should().Be(0);
|
||||
using dynamic instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
|
||||
global_i32_mut.Value.Should().Be(0);
|
||||
((int)instance.get_global_i32_mut()).Should().Be(0);
|
||||
host.Int32.Value.Should().Be(1);
|
||||
global_i32.Value.Should().Be(1);
|
||||
((int)instance.get_global_i32()).Should().Be(1);
|
||||
host.Int64Mut.Value.Should().Be(2);
|
||||
global_i64_mut.Value.Should().Be(2);
|
||||
((long)instance.get_global_i64_mut()).Should().Be(2);
|
||||
host.Int64.Value.Should().Be(3);
|
||||
global_i64.Value.Should().Be(3);
|
||||
((long)instance.get_global_i64()).Should().Be(3);
|
||||
host.Float32Mut.Value.Should().Be(4);
|
||||
global_f32_mut.Value.Should().Be(4);
|
||||
((float)instance.get_global_f32_mut()).Should().Be(4);
|
||||
host.Float32.Value.Should().Be(5);
|
||||
global_f32.Value.Should().Be(5);
|
||||
((float)instance.get_global_f32()).Should().Be(5);
|
||||
host.Float64Mut.Value.Should().Be(6);
|
||||
global_f64_mut.Value.Should().Be(6);
|
||||
((double)instance.get_global_f64_mut()).Should().Be(6);
|
||||
host.Float64.Value.Should().Be(7);
|
||||
global_f64.Value.Should().Be(7);
|
||||
((double)instance.get_global_f64()).Should().Be(7);
|
||||
|
||||
host.Int32Mut.Value = 10;
|
||||
host.Int32Mut.Value.Should().Be(10);
|
||||
global_i32_mut.Value = 10;
|
||||
global_i32_mut.Value.Should().Be(10);
|
||||
((int)instance.get_global_i32_mut()).Should().Be(10);
|
||||
instance.set_global_i32_mut(11);
|
||||
host.Int32Mut.Value.Should().Be(11);
|
||||
global_i32_mut.Value.Should().Be(11);
|
||||
((int)instance.get_global_i32_mut()).Should().Be(11);
|
||||
|
||||
host.Int64Mut.Value = 12;
|
||||
host.Int64Mut.Value.Should().Be(12);
|
||||
global_i64_mut.Value = 12;
|
||||
global_i64_mut.Value.Should().Be(12);
|
||||
((long)instance.get_global_i64_mut()).Should().Be(12);
|
||||
instance.set_global_i64_mut(13);
|
||||
host.Int64Mut.Value.Should().Be(13);
|
||||
global_i64_mut.Value.Should().Be(13);
|
||||
((long)instance.get_global_i64_mut()).Should().Be(13);
|
||||
|
||||
host.Float32Mut.Value = 14;
|
||||
host.Float32Mut.Value.Should().Be(14);
|
||||
global_f32_mut.Value = 14;
|
||||
global_f32_mut.Value.Should().Be(14);
|
||||
((float)instance.get_global_f32_mut()).Should().Be(14);
|
||||
instance.set_global_f32_mut(15);
|
||||
host.Float32Mut.Value.Should().Be(15);
|
||||
global_f32_mut.Value.Should().Be(15);
|
||||
((float)instance.get_global_f32_mut()).Should().Be(15);
|
||||
|
||||
host.Float64Mut.Value = 16;
|
||||
host.Float64Mut.Value.Should().Be(16);
|
||||
global_f64_mut.Value = 16;
|
||||
global_f64_mut.Value.Should().Be(16);
|
||||
((double)instance.get_global_f64_mut()).Should().Be(16);
|
||||
instance.set_global_f64_mut(17);
|
||||
host.Float64Mut.Value.Should().Be(17);
|
||||
global_f64_mut.Value.Should().Be(17);
|
||||
((double)instance.get_global_f64_mut()).Should().Be(17);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,6 @@ namespace Wasmtime.Tests
|
||||
|
||||
public class MemoryExportsTests : IClassFixture<MemoryExportsFixture>
|
||||
{
|
||||
public class Host : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
}
|
||||
|
||||
public MemoryExportsTests(MemoryExportsFixture fixture)
|
||||
{
|
||||
Fixture = fixture;
|
||||
@@ -44,8 +39,7 @@ namespace Wasmtime.Tests
|
||||
[Fact]
|
||||
public void ItCreatesExternsForTheMemories()
|
||||
{
|
||||
var host = new Host();
|
||||
using var instance = Fixture.Module.Instantiate(host);
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
|
||||
instance.Externs.Memories.Count.Should().Be(1);
|
||||
|
||||
|
||||
@@ -11,62 +11,11 @@ namespace Wasmtime.Tests
|
||||
|
||||
public class MemoryImportBindingTests : IClassFixture<MemoryImportBindingFixture>
|
||||
{
|
||||
class MissingImportsHost : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
}
|
||||
|
||||
class MemoryIsStaticHost : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("mem")]
|
||||
public static Memory x = new Memory(minimum: 1);
|
||||
}
|
||||
|
||||
class MemoryIsNotReadOnlyHost : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("mem")]
|
||||
public Memory x = new Memory(minimum: 1);
|
||||
}
|
||||
|
||||
class NotAMemoryHost : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("mem")]
|
||||
public readonly int x = 0;
|
||||
}
|
||||
|
||||
class InvalidMinimumHost : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("mem")]
|
||||
public readonly Memory Mem = new Memory(minimum: 2);
|
||||
}
|
||||
|
||||
class InvalidMaximumHost : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("mem")]
|
||||
public readonly Memory Mem = new Memory(maximum: 2);
|
||||
}
|
||||
|
||||
class ValidHost : IHost
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
[Import("mem")]
|
||||
public readonly Memory Mem = new Memory(minimum: 1);
|
||||
}
|
||||
|
||||
public MemoryImportBindingTests(MemoryImportBindingFixture fixture)
|
||||
{
|
||||
Fixture = fixture;
|
||||
|
||||
Fixture.Host.ClearDefinitions();
|
||||
}
|
||||
|
||||
private MemoryImportBindingFixture Fixture { get; set; }
|
||||
@@ -74,112 +23,58 @@ namespace Wasmtime.Tests
|
||||
[Fact]
|
||||
public void ItFailsToInstantiateWithMissingImport()
|
||||
{
|
||||
Action action = () => { using var instance = Fixture.Module.Instantiate(new MissingImportsHost()); };
|
||||
Action action = () => { using var instance = Fixture.Host.Instantiate(Fixture.Module); };
|
||||
|
||||
action
|
||||
.Should()
|
||||
.Throw<WasmtimeException>()
|
||||
.WithMessage("Failed to bind memory import 'mem': the host does not contain a memory field with a matching 'Import' attribute.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItFailsToInstantiateWithStaticField()
|
||||
{
|
||||
Action action = () => { using var instance = Fixture.Module.Instantiate(new MemoryIsStaticHost()); };
|
||||
|
||||
action
|
||||
.Should()
|
||||
.Throw<WasmtimeException>()
|
||||
.WithMessage("Unable to bind 'MemoryIsStaticHost.x' to WebAssembly import 'mem': field cannot be static.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItFailsToInstantiateWithNonReadOnlyField()
|
||||
{
|
||||
Action action = () => { using var instance = Fixture.Module.Instantiate(new MemoryIsNotReadOnlyHost()); };
|
||||
|
||||
action
|
||||
.Should()
|
||||
.Throw<WasmtimeException>()
|
||||
.WithMessage("Unable to bind 'MemoryIsNotReadOnlyHost.x' to WebAssembly import 'mem': field must be readonly.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItFailsToInstantiateWithInvalidType()
|
||||
{
|
||||
Action action = () => { using var instance = Fixture.Module.Instantiate(new NotAMemoryHost()); };
|
||||
|
||||
action
|
||||
.Should()
|
||||
.Throw<WasmtimeException>()
|
||||
.WithMessage("Unable to bind 'NotAMemoryHost.x' to WebAssembly import 'mem': field is expected to be of type 'Memory'.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItFailsToInstantiateWhenMemoryHasInvalidMinimum()
|
||||
{
|
||||
Action action = () => { using var instance = Fixture.Module.Instantiate(new InvalidMinimumHost()); };
|
||||
|
||||
action
|
||||
.Should()
|
||||
.Throw<WasmtimeException>()
|
||||
.WithMessage("Unable to bind 'InvalidMinimumHost.Mem' to WebAssembly import 'mem': Memory does not have the expected minimum of 1 page(s).");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItFailsToInstantiateWhenMemoryHasInvalidMaximum()
|
||||
{
|
||||
Action action = () => { using var instance = Fixture.Module.Instantiate(new InvalidMaximumHost()); };
|
||||
|
||||
action
|
||||
.Should()
|
||||
.Throw<WasmtimeException>()
|
||||
.WithMessage("Unable to bind 'InvalidMaximumHost.Mem' to WebAssembly import 'mem': Memory does not have the expected maximum of 4294967295 page(s).");
|
||||
.WithMessage("unknown import: `::mem` has not been defined");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItBindsTheGlobalsCorrectly()
|
||||
{
|
||||
var host = new ValidHost();
|
||||
using dynamic instance = Fixture.Module.Instantiate(host);
|
||||
var mem = Fixture.Host.DefineMemory("", "mem");
|
||||
|
||||
host.Mem.ReadString(0, 11).Should().Be("Hello World");
|
||||
int written = host.Mem.WriteString(0, "WebAssembly Rocks!");
|
||||
host.Mem.ReadString(0, written).Should().Be("WebAssembly Rocks!");
|
||||
using dynamic instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
|
||||
host.Mem.ReadByte(20).Should().Be(1);
|
||||
host.Mem.WriteByte(20, 11);
|
||||
host.Mem.ReadByte(20).Should().Be(11);
|
||||
mem.ReadString(0, 11).Should().Be("Hello World");
|
||||
int written = mem.WriteString(0, "WebAssembly Rocks!");
|
||||
mem.ReadString(0, written).Should().Be("WebAssembly Rocks!");
|
||||
|
||||
mem.ReadByte(20).Should().Be(1);
|
||||
mem.WriteByte(20, 11);
|
||||
mem.ReadByte(20).Should().Be(11);
|
||||
((byte)instance.ReadByte()).Should().Be(11);
|
||||
|
||||
host.Mem.ReadInt16(21).Should().Be(2);
|
||||
host.Mem.WriteInt16(21, 12);
|
||||
host.Mem.ReadInt16(21).Should().Be(12);
|
||||
mem.ReadInt16(21).Should().Be(2);
|
||||
mem.WriteInt16(21, 12);
|
||||
mem.ReadInt16(21).Should().Be(12);
|
||||
((short)instance.ReadInt16()).Should().Be(12);
|
||||
|
||||
host.Mem.ReadInt32(23).Should().Be(3);
|
||||
host.Mem.WriteInt32(23, 13);
|
||||
host.Mem.ReadInt32(23).Should().Be(13);
|
||||
mem.ReadInt32(23).Should().Be(3);
|
||||
mem.WriteInt32(23, 13);
|
||||
mem.ReadInt32(23).Should().Be(13);
|
||||
((int)instance.ReadInt32()).Should().Be(13);
|
||||
|
||||
host.Mem.ReadInt64(27).Should().Be(4);
|
||||
host.Mem.WriteInt64(27, 14);
|
||||
host.Mem.ReadInt64(27).Should().Be(14);
|
||||
mem.ReadInt64(27).Should().Be(4);
|
||||
mem.WriteInt64(27, 14);
|
||||
mem.ReadInt64(27).Should().Be(14);
|
||||
((long)instance.ReadInt64()).Should().Be(14);
|
||||
|
||||
host.Mem.ReadSingle(35).Should().Be(5);
|
||||
host.Mem.WriteSingle(35, 15);
|
||||
host.Mem.ReadSingle(35).Should().Be(15);
|
||||
mem.ReadSingle(35).Should().Be(5);
|
||||
mem.WriteSingle(35, 15);
|
||||
mem.ReadSingle(35).Should().Be(15);
|
||||
((float)instance.ReadFloat32()).Should().Be(15);
|
||||
|
||||
host.Mem.ReadDouble(39).Should().Be(6);
|
||||
host.Mem.WriteDouble(39, 16);
|
||||
host.Mem.ReadDouble(39).Should().Be(16);
|
||||
mem.ReadDouble(39).Should().Be(6);
|
||||
mem.WriteDouble(39, 16);
|
||||
mem.ReadDouble(39).Should().Be(16);
|
||||
((double)instance.ReadFloat64()).Should().Be(16);
|
||||
|
||||
host.Mem.ReadIntPtr(48).Should().Be((IntPtr)7);
|
||||
host.Mem.WriteIntPtr(48, (IntPtr)17);
|
||||
host.Mem.ReadIntPtr(48).Should().Be((IntPtr)17);
|
||||
mem.ReadIntPtr(48).Should().Be((IntPtr)7);
|
||||
mem.WriteIntPtr(48, (IntPtr)17);
|
||||
mem.ReadIntPtr(48).Should().Be((IntPtr)17);
|
||||
((IntPtr)instance.ReadIntPtr()).Should().Be((IntPtr)17);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
(module
|
||||
(type $FUNCSIG$iii (func (param i32 i32) (result i32)))
|
||||
(type $FUNCSIG$v (func))
|
||||
(import "env" "add" (func $add (param i32 i32) (result i32)))
|
||||
(import "env" "do_throw" (func $do_throw))
|
||||
(table 0 anyfunc)
|
||||
(memory $0 1)
|
||||
(export "memory" (memory $0))
|
||||
(export "add_wrapper" (func $add_wrapper))
|
||||
(export "do_throw_wrapper" (func $do_throw_wrapper))
|
||||
(func $add_wrapper (; 2 ;) (param $0 i32) (param $1 i32) (result i32)
|
||||
(call $add
|
||||
(get_local $0)
|
||||
(get_local $1)
|
||||
)
|
||||
(import "env" "add" (func $env.add (param i32 i32) (result i32)))
|
||||
(import "env" "swap" (func $env.swap (param i32 i32) (result i32 i32)))
|
||||
(import "env" "do_throw" (func $env.do_throw))
|
||||
(import "env" "check_string" (func $env.check_string (param i32 i32)))
|
||||
(memory (export "mem") 1)
|
||||
(export "add" (func $add))
|
||||
(export "swap" (func $swap))
|
||||
(export "do_throw" (func $do_throw))
|
||||
(export "check_string" (func $check_string))
|
||||
(func $add (param i32 i32) (result i32)
|
||||
(call $env.add (local.get 0) (local.get 1))
|
||||
)
|
||||
(func $do_throw_wrapper (; 3 ;)
|
||||
(call $do_throw)
|
||||
(func $swap (param i32 i32) (result i32 i32)
|
||||
(call $env.swap (local.get 0) (local.get 1))
|
||||
)
|
||||
(func $do_throw
|
||||
(call $env.do_throw)
|
||||
)
|
||||
(func $check_string
|
||||
(call $env.check_string (i32.const 0) (i32.const 11))
|
||||
)
|
||||
(data (i32.const 0) "Hello World")
|
||||
)
|
||||
|
||||
66
crates/misc/dotnet/tests/Modules/WasiSnapshot0.wat
Normal file
66
crates/misc/dotnet/tests/Modules/WasiSnapshot0.wat
Normal file
@@ -0,0 +1,66 @@
|
||||
(module
|
||||
(type $t0 (func (param i32 i32) (result i32)))
|
||||
(type $t1 (func (param i32 i32 i32 i32) (result i32)))
|
||||
(type $t2 (func (param i32) (result i32)))
|
||||
(type $t3 (func (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32)))
|
||||
(import "wasi_unstable" "environ_sizes_get" (func $wasi_unstable.environ_sizes_get (type $t0)))
|
||||
(import "wasi_unstable" "environ_get" (func $wasi_unstable.environ_get (type $t0)))
|
||||
(import "wasi_unstable" "args_sizes_get" (func $wasi_unstable.args_sizes_get (type $t0)))
|
||||
(import "wasi_unstable" "args_get" (func $wasi_unstable.args_get (type $t0)))
|
||||
(import "wasi_unstable" "fd_write" (func $wasi_unstable.fd_write (type $t1)))
|
||||
(import "wasi_unstable" "fd_read" (func $wasi_unstable.fd_read (type $t1)))
|
||||
(import "wasi_unstable" "fd_close" (func $wasi_unstable.fd_close (type $t2)))
|
||||
(import "wasi_unstable" "path_open" (func $wasi_unstable.path_open (type $t3)))
|
||||
(memory $memory 1)
|
||||
(export "memory" (memory 0))
|
||||
(func $call_environ_sizes_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
|
||||
local.get $p0
|
||||
local.get $p1
|
||||
call $wasi_unstable.environ_sizes_get)
|
||||
(func $call_environ_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
|
||||
local.get $p0
|
||||
local.get $p1
|
||||
call $wasi_unstable.environ_get)
|
||||
(func $call_args_sizes_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
|
||||
local.get $p0
|
||||
local.get $p1
|
||||
call $wasi_unstable.args_sizes_get)
|
||||
(func $call_args_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
|
||||
local.get $p0
|
||||
local.get $p1
|
||||
call $wasi_unstable.args_get)
|
||||
(func $call_fd_write (type $t1) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (result i32)
|
||||
local.get $p0
|
||||
local.get $p1
|
||||
local.get $p2
|
||||
local.get $p3
|
||||
call $wasi_unstable.fd_write)
|
||||
(func $call_fd_read (type $t1) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (result i32)
|
||||
local.get $p0
|
||||
local.get $p1
|
||||
local.get $p2
|
||||
local.get $p3
|
||||
call $wasi_unstable.fd_read)
|
||||
(func $call_fd_close (type $t2) (param $p0 i32) (result i32)
|
||||
local.get $p0
|
||||
call $wasi_unstable.fd_close)
|
||||
(func $call_path_open (type $t3) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (param $p4 i32) (param $p5 i64) (param $p6 i64) (param $p7 i32) (param $p8 i32) (result i32)
|
||||
local.get $p0
|
||||
local.get $p1
|
||||
local.get $p2
|
||||
local.get $p3
|
||||
local.get $p4
|
||||
local.get $p5
|
||||
local.get $p6
|
||||
local.get $p7
|
||||
local.get $p8
|
||||
call $wasi_unstable.path_open)
|
||||
(export "call_environ_sizes_get" (func $call_environ_sizes_get))
|
||||
(export "call_environ_get" (func $call_environ_get))
|
||||
(export "call_args_sizes_get" (func $call_args_sizes_get))
|
||||
(export "call_args_get" (func $call_args_get))
|
||||
(export "call_fd_write" (func $call_fd_write))
|
||||
(export "call_fd_read" (func $call_fd_read))
|
||||
(export "call_fd_close" (func $call_fd_close))
|
||||
(export "call_path_open" (func $call_path_open))
|
||||
)
|
||||
257
crates/misc/dotnet/tests/WasiSnapshot0Tests.cs
Normal file
257
crates/misc/dotnet/tests/WasiSnapshot0Tests.cs
Normal file
@@ -0,0 +1,257 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace Wasmtime.Tests
|
||||
{
|
||||
public class WasiSnapshot0Fixture : ModuleFixture
|
||||
{
|
||||
protected override string ModuleFileName => "WasiSnapshot0.wat";
|
||||
}
|
||||
|
||||
public class WasiSnapshot0Tests : IClassFixture<WasiSnapshot0Fixture>
|
||||
{
|
||||
public WasiSnapshot0Tests(WasiSnapshot0Fixture fixture)
|
||||
{
|
||||
Fixture = fixture;
|
||||
|
||||
Fixture.Host.ClearDefinitions();
|
||||
}
|
||||
|
||||
private WasiSnapshot0Fixture Fixture { get; set; }
|
||||
|
||||
[Fact]
|
||||
public void ItHasNoEnvironmentByDefault()
|
||||
{
|
||||
Fixture.Host.DefineWasi("wasi_unstable");
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
dynamic inst = instance;
|
||||
|
||||
var memory = instance.Externs.Memories[0];
|
||||
|
||||
Assert.Equal(0, inst.call_environ_sizes_get(0, 4));
|
||||
Assert.Equal(0, memory.ReadInt32(0));
|
||||
Assert.Equal(0, memory.ReadInt32(4));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItHasSpecifiedEnvironment()
|
||||
{
|
||||
var env = new Dictionary<string, string>() {
|
||||
{"FOO", "BAR"},
|
||||
{"WASM", "IS"},
|
||||
{"VERY", "COOL"},
|
||||
};
|
||||
|
||||
var config = new WasiConfiguration()
|
||||
.WithEnvironmentVariables(env.Select(kvp => (kvp.Key, kvp.Value)));
|
||||
|
||||
Fixture.Host.DefineWasi("wasi_unstable", config);
|
||||
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
dynamic inst = instance;
|
||||
|
||||
var memory = instance.Externs.Memories[0];
|
||||
|
||||
Assert.Equal(0, inst.call_environ_sizes_get(0, 4));
|
||||
Assert.Equal(env.Count, memory.ReadInt32(0));
|
||||
Assert.Equal(env.Sum(kvp => kvp.Key.Length + kvp.Value.Length + 2), memory.ReadInt32(4));
|
||||
Assert.Equal(0, inst.call_environ_get(0, 4 * env.Count));
|
||||
|
||||
for (int i = 0; i < env.Count; ++i)
|
||||
{
|
||||
var kvp = memory.ReadNullTerminatedString(memory.ReadInt32(i * 4)).Split("=");
|
||||
Assert.Equal(env[kvp[0]], kvp[1]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItInheritsEnvironment()
|
||||
{
|
||||
var config = new WasiConfiguration()
|
||||
.WithInheritedEnvironment();
|
||||
|
||||
Fixture.Host.DefineWasi("wasi_unstable", config);
|
||||
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
dynamic inst = instance;
|
||||
|
||||
var memory = instance.Externs.Memories[0];
|
||||
|
||||
Assert.Equal(0, inst.call_environ_sizes_get(0, 4));
|
||||
Assert.Equal(Environment.GetEnvironmentVariables().Keys.Count, memory.ReadInt32(0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItHasNoArgumentsByDefault()
|
||||
{
|
||||
Fixture.Host.DefineWasi("wasi_unstable");
|
||||
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
dynamic inst = instance;
|
||||
|
||||
var memory = instance.Externs.Memories[0];
|
||||
|
||||
Assert.Equal(0, inst.call_args_sizes_get(0, 4));
|
||||
Assert.Equal(0, memory.ReadInt32(0));
|
||||
Assert.Equal(0, memory.ReadInt32(4));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItHasSpecifiedArguments()
|
||||
{
|
||||
var args = new List<string>() {
|
||||
"WASM",
|
||||
"IS",
|
||||
"VERY",
|
||||
"COOL"
|
||||
};
|
||||
|
||||
var config = new WasiConfiguration()
|
||||
.WithArgs(args);
|
||||
|
||||
Fixture.Host.DefineWasi("wasi_unstable", config);
|
||||
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
dynamic inst = instance;
|
||||
|
||||
var memory = instance.Externs.Memories[0];
|
||||
|
||||
Assert.Equal(0, inst.call_args_sizes_get(0, 4));
|
||||
Assert.Equal(args.Count, memory.ReadInt32(0));
|
||||
Assert.Equal(args.Sum(a => a.Length + 1), memory.ReadInt32(4));
|
||||
Assert.Equal(0, inst.call_args_get(0, 4 * args.Count));
|
||||
|
||||
for (int i = 0; i < args.Count; ++i)
|
||||
{
|
||||
var arg = memory.ReadNullTerminatedString(memory.ReadInt32(i * 4));
|
||||
Assert.Equal(args[i], arg);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItInheritsArguments()
|
||||
{
|
||||
var config = new WasiConfiguration()
|
||||
.WithInheritedArgs();
|
||||
|
||||
Fixture.Host.DefineWasi("wasi_unstable", config);
|
||||
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
dynamic inst = instance;
|
||||
|
||||
var memory = instance.Externs.Memories[0];
|
||||
|
||||
Assert.Equal(0, inst.call_args_sizes_get(0, 4));
|
||||
Assert.Equal(Environment.GetCommandLineArgs().Length, memory.ReadInt32(0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItSetsStdIn()
|
||||
{
|
||||
const string MESSAGE = "WASM IS VERY COOL";
|
||||
|
||||
using var file = new TempFile();
|
||||
File.WriteAllText(file.Path, MESSAGE);
|
||||
|
||||
var config = new WasiConfiguration()
|
||||
.WithStandardInput(file.Path);
|
||||
|
||||
Fixture.Host.DefineWasi("wasi_unstable", config);
|
||||
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
dynamic inst = instance;
|
||||
|
||||
var memory = instance.Externs.Memories[0];
|
||||
memory.WriteInt32(0, 8);
|
||||
memory.WriteInt32(4, MESSAGE.Length);
|
||||
|
||||
Assert.Equal(0, inst.call_fd_read(0, 0, 1, 32));
|
||||
Assert.Equal(MESSAGE.Length, memory.ReadInt32(32));
|
||||
Assert.Equal(MESSAGE, memory.ReadString(8, MESSAGE.Length));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1)]
|
||||
[InlineData(2)]
|
||||
public void ItSetsStdOutAndStdErr(int fd)
|
||||
{
|
||||
const string MESSAGE = "WASM IS VERY COOL";
|
||||
|
||||
using var file = new TempFile();
|
||||
|
||||
var config = new WasiConfiguration();
|
||||
if (fd == 1)
|
||||
{
|
||||
config.WithStandardOutput(file.Path);
|
||||
}
|
||||
else if (fd == 2)
|
||||
{
|
||||
config.WithStandardError(file.Path);
|
||||
}
|
||||
|
||||
Fixture.Host.DefineWasi("wasi_unstable", config);
|
||||
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
dynamic inst = instance;
|
||||
|
||||
var memory = instance.Externs.Memories[0];
|
||||
memory.WriteInt32(0, 8);
|
||||
memory.WriteInt32(4, MESSAGE.Length);
|
||||
memory.WriteString(8, MESSAGE);
|
||||
|
||||
Assert.Equal(0, inst.call_fd_write(fd, 0, 1, 32));
|
||||
Assert.Equal(MESSAGE.Length, memory.ReadInt32(32));
|
||||
Assert.Equal(0, inst.call_fd_close(fd));
|
||||
Assert.Equal(MESSAGE, File.ReadAllText(file.Path));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItSetsPreopenDirectories()
|
||||
{
|
||||
const string MESSAGE = "WASM IS VERY COOL";
|
||||
|
||||
using var file = new TempFile();
|
||||
|
||||
var config = new WasiConfiguration()
|
||||
.WithPreopenedDirectory(Path.GetDirectoryName(file.Path), "/foo");
|
||||
|
||||
Fixture.Host.DefineWasi("wasi_unstable", config);
|
||||
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
dynamic inst = instance;
|
||||
|
||||
var memory = instance.Externs.Memories[0];
|
||||
var fileName = Path.GetFileName(file.Path);
|
||||
memory.WriteString(0, fileName);
|
||||
|
||||
Assert.Equal(0, inst.call_path_open(
|
||||
3,
|
||||
0,
|
||||
0,
|
||||
fileName.Length,
|
||||
0,
|
||||
0x40 /* RIGHTS_FD_WRITE */,
|
||||
0,
|
||||
0,
|
||||
64
|
||||
)
|
||||
);
|
||||
|
||||
var fileFd = (int) memory.ReadInt32(64);
|
||||
Assert.True(fileFd > 3);
|
||||
|
||||
memory.WriteInt32(0, 8);
|
||||
memory.WriteInt32(4, MESSAGE.Length);
|
||||
memory.WriteString(8, MESSAGE);
|
||||
|
||||
Assert.Equal(0, inst.call_fd_write(fileFd, 0, 1, 64));
|
||||
Assert.Equal(MESSAGE.Length, memory.ReadInt32(64));
|
||||
Assert.Equal(0, inst.call_fd_close(fileFd));
|
||||
Assert.Equal(MESSAGE, File.ReadAllText(file.Path));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,9 @@ namespace Wasmtime.Tests
|
||||
[Fact]
|
||||
public void ItHasNoEnvironmentByDefault()
|
||||
{
|
||||
using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store));
|
||||
Fixture.Host.DefineWasi("wasi_snapshot_preview1");
|
||||
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
dynamic inst = instance;
|
||||
|
||||
var memory = instance.Externs.Memories[0];
|
||||
@@ -43,11 +45,12 @@ namespace Wasmtime.Tests
|
||||
{"VERY", "COOL"},
|
||||
};
|
||||
|
||||
var wasi = new WasiBuilder()
|
||||
.WithEnvironmentVariables(env.Select(kvp => (kvp.Key, kvp.Value)))
|
||||
.Build(Fixture.Module.Store);
|
||||
var config = new WasiConfiguration()
|
||||
.WithEnvironmentVariables(env.Select(kvp => (kvp.Key, kvp.Value)));
|
||||
|
||||
using var instance = Fixture.Module.Instantiate(wasi);
|
||||
Fixture.Host.DefineWasi("wasi_snapshot_preview1", config);
|
||||
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
dynamic inst = instance;
|
||||
|
||||
var memory = instance.Externs.Memories[0];
|
||||
@@ -67,11 +70,12 @@ namespace Wasmtime.Tests
|
||||
[Fact]
|
||||
public void ItInheritsEnvironment()
|
||||
{
|
||||
var wasi = new WasiBuilder()
|
||||
.WithInheritedEnvironment()
|
||||
.Build(Fixture.Module.Store);
|
||||
var config = new WasiConfiguration()
|
||||
.WithInheritedEnvironment();
|
||||
|
||||
using var instance = Fixture.Module.Instantiate(wasi);
|
||||
Fixture.Host.DefineWasi("wasi_snapshot_preview1", config);
|
||||
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
dynamic inst = instance;
|
||||
|
||||
var memory = instance.Externs.Memories[0];
|
||||
@@ -83,7 +87,9 @@ namespace Wasmtime.Tests
|
||||
[Fact]
|
||||
public void ItHasNoArgumentsByDefault()
|
||||
{
|
||||
using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store));
|
||||
Fixture.Host.DefineWasi("wasi_snapshot_preview1");
|
||||
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
dynamic inst = instance;
|
||||
|
||||
var memory = instance.Externs.Memories[0];
|
||||
@@ -103,11 +109,12 @@ namespace Wasmtime.Tests
|
||||
"COOL"
|
||||
};
|
||||
|
||||
var wasi = new WasiBuilder()
|
||||
.WithArgs(args)
|
||||
.Build(Fixture.Module.Store);
|
||||
var config = new WasiConfiguration()
|
||||
.WithArgs(args);
|
||||
|
||||
using var instance = Fixture.Module.Instantiate(wasi);
|
||||
Fixture.Host.DefineWasi("wasi_snapshot_preview1", config);
|
||||
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
dynamic inst = instance;
|
||||
|
||||
var memory = instance.Externs.Memories[0];
|
||||
@@ -127,11 +134,12 @@ namespace Wasmtime.Tests
|
||||
[Fact]
|
||||
public void ItInheritsArguments()
|
||||
{
|
||||
var wasi = new WasiBuilder()
|
||||
.WithInheritedArgs()
|
||||
.Build(Fixture.Module.Store);
|
||||
var config = new WasiConfiguration()
|
||||
.WithInheritedArgs();
|
||||
|
||||
using var instance = Fixture.Module.Instantiate(wasi);
|
||||
Fixture.Host.DefineWasi("wasi_snapshot_preview1", config);
|
||||
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
dynamic inst = instance;
|
||||
|
||||
var memory = instance.Externs.Memories[0];
|
||||
@@ -148,11 +156,12 @@ namespace Wasmtime.Tests
|
||||
using var file = new TempFile();
|
||||
File.WriteAllText(file.Path, MESSAGE);
|
||||
|
||||
var wasi = new WasiBuilder()
|
||||
.WithStandardInput(file.Path)
|
||||
.Build(Fixture.Module.Store);
|
||||
var config = new WasiConfiguration()
|
||||
.WithStandardInput(file.Path);
|
||||
|
||||
using var instance = Fixture.Module.Instantiate(wasi);
|
||||
Fixture.Host.DefineWasi("wasi_snapshot_preview1", config);
|
||||
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
dynamic inst = instance;
|
||||
|
||||
var memory = instance.Externs.Memories[0];
|
||||
@@ -173,19 +182,19 @@ namespace Wasmtime.Tests
|
||||
|
||||
using var file = new TempFile();
|
||||
|
||||
var builder = new WasiBuilder();
|
||||
var config = new WasiConfiguration();
|
||||
if (fd == 1)
|
||||
{
|
||||
builder.WithStandardOutput(file.Path);
|
||||
config.WithStandardOutput(file.Path);
|
||||
}
|
||||
else if (fd == 2)
|
||||
{
|
||||
builder.WithStandardError(file.Path);
|
||||
config.WithStandardError(file.Path);
|
||||
}
|
||||
|
||||
var wasi = builder.Build(Fixture.Module.Store);
|
||||
Fixture.Host.DefineWasi("wasi_snapshot_preview1", config);
|
||||
|
||||
using var instance = Fixture.Module.Instantiate(wasi);
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
dynamic inst = instance;
|
||||
|
||||
var memory = instance.Externs.Memories[0];
|
||||
@@ -206,11 +215,12 @@ namespace Wasmtime.Tests
|
||||
|
||||
using var file = new TempFile();
|
||||
|
||||
var wasi = new WasiBuilder()
|
||||
.WithPreopenedDirectory(Path.GetDirectoryName(file.Path), "/foo")
|
||||
.Build(Fixture.Module.Store);
|
||||
var config = new WasiConfiguration()
|
||||
.WithPreopenedDirectory(Path.GetDirectoryName(file.Path), "/foo");
|
||||
|
||||
using var instance = Fixture.Module.Instantiate(wasi);
|
||||
Fixture.Host.DefineWasi("wasi_snapshot_preview1", config);
|
||||
|
||||
using var instance = Fixture.Host.Instantiate(Fixture.Module);
|
||||
dynamic inst = instance;
|
||||
|
||||
var memory = instance.Externs.Memories[0];
|
||||
|
||||
@@ -101,46 +101,46 @@ impl PendingCString {
|
||||
|
||||
/// A builder allowing customizable construction of `WasiCtx` instances.
|
||||
pub struct WasiCtxBuilder {
|
||||
stdin: PendingEntry,
|
||||
stdout: PendingEntry,
|
||||
stderr: PendingEntry,
|
||||
preopens: Vec<(PathBuf, File)>,
|
||||
args: Vec<PendingCString>,
|
||||
env: HashMap<PendingCString, PendingCString>,
|
||||
stdin: Option<PendingEntry>,
|
||||
stdout: Option<PendingEntry>,
|
||||
stderr: Option<PendingEntry>,
|
||||
preopens: Option<Vec<(PathBuf, File)>>,
|
||||
args: Option<Vec<PendingCString>>,
|
||||
env: Option<HashMap<PendingCString, PendingCString>>,
|
||||
}
|
||||
|
||||
impl WasiCtxBuilder {
|
||||
/// Builder for a new `WasiCtx`.
|
||||
pub fn new() -> Self {
|
||||
let stdin = PendingEntry::Thunk(Entry::null);
|
||||
let stdout = PendingEntry::Thunk(Entry::null);
|
||||
let stderr = PendingEntry::Thunk(Entry::null);
|
||||
Self {
|
||||
stdin,
|
||||
stdout,
|
||||
stderr,
|
||||
preopens: Vec::new(),
|
||||
args: vec![],
|
||||
env: HashMap::new(),
|
||||
stdin: Some(PendingEntry::Thunk(Entry::null)),
|
||||
stdout: Some(PendingEntry::Thunk(Entry::null)),
|
||||
stderr: Some(PendingEntry::Thunk(Entry::null)),
|
||||
preopens: Some(Vec::new()),
|
||||
args: Some(Vec::new()),
|
||||
env: Some(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add arguments to the command-line arguments list.
|
||||
///
|
||||
/// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail.
|
||||
pub fn args<S: AsRef<[u8]>>(mut self, args: impl IntoIterator<Item = S>) -> Self {
|
||||
self.args = args
|
||||
.into_iter()
|
||||
.map(|arg| arg.as_ref().to_vec().into())
|
||||
.collect();
|
||||
pub fn args<S: AsRef<[u8]>>(&mut self, args: impl IntoIterator<Item = S>) -> &mut Self {
|
||||
self.args
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.extend(args.into_iter().map(|a| a.as_ref().to_vec().into()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an argument to the command-line arguments list.
|
||||
///
|
||||
/// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail.
|
||||
pub fn arg<S: AsRef<[u8]>>(mut self, arg: S) -> Self {
|
||||
self.args.push(arg.as_ref().to_vec().into());
|
||||
pub fn arg<S: AsRef<[u8]>>(&mut self, arg: S) -> &mut Self {
|
||||
self.args
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.push(arg.as_ref().to_vec().into());
|
||||
self
|
||||
}
|
||||
|
||||
@@ -148,16 +148,36 @@ impl WasiCtxBuilder {
|
||||
///
|
||||
/// If any arguments from the host process contain invalid UTF-8, `WasiCtxBuilder::build()` will
|
||||
/// fail.
|
||||
pub fn inherit_args(mut self) -> Self {
|
||||
self.args = env::args_os().map(PendingCString::OsString).collect();
|
||||
pub fn inherit_args(&mut self) -> &mut Self {
|
||||
let args = self.args.as_mut().unwrap();
|
||||
args.clear();
|
||||
args.extend(env::args_os().map(PendingCString::OsString));
|
||||
self
|
||||
}
|
||||
|
||||
/// Inherit stdin from the host process.
|
||||
pub fn inherit_stdin(&mut self) -> &mut Self {
|
||||
self.stdin = Some(PendingEntry::Thunk(Entry::duplicate_stdin));
|
||||
self
|
||||
}
|
||||
|
||||
/// Inherit stdout from the host process.
|
||||
pub fn inherit_stdout(&mut self) -> &mut Self {
|
||||
self.stdout = Some(PendingEntry::Thunk(Entry::duplicate_stdout));
|
||||
self
|
||||
}
|
||||
|
||||
/// Inherit stdout from the host process.
|
||||
pub fn inherit_stderr(&mut self) -> &mut Self {
|
||||
self.stderr = Some(PendingEntry::Thunk(Entry::duplicate_stderr));
|
||||
self
|
||||
}
|
||||
|
||||
/// Inherit the stdin, stdout, and stderr streams from the host process.
|
||||
pub fn inherit_stdio(mut self) -> Self {
|
||||
self.stdin = PendingEntry::Thunk(Entry::duplicate_stdin);
|
||||
self.stdout = PendingEntry::Thunk(Entry::duplicate_stdout);
|
||||
self.stderr = PendingEntry::Thunk(Entry::duplicate_stderr);
|
||||
pub fn inherit_stdio(&mut self) -> &mut Self {
|
||||
self.stdin = Some(PendingEntry::Thunk(Entry::duplicate_stdin));
|
||||
self.stdout = Some(PendingEntry::Thunk(Entry::duplicate_stdout));
|
||||
self.stderr = Some(PendingEntry::Thunk(Entry::duplicate_stderr));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -165,10 +185,10 @@ impl WasiCtxBuilder {
|
||||
///
|
||||
/// If any environment variables from the host process contain invalid Unicode (UTF-16 for
|
||||
/// Windows, UTF-8 for other platforms), `WasiCtxBuilder::build()` will fail.
|
||||
pub fn inherit_env(mut self) -> Self {
|
||||
self.env = std::env::vars_os()
|
||||
.map(|(k, v)| (k.into(), v.into()))
|
||||
.collect();
|
||||
pub fn inherit_env(&mut self) -> &mut Self {
|
||||
let env = self.env.as_mut().unwrap();
|
||||
env.clear();
|
||||
env.extend(std::env::vars_os().map(|(k, v)| (k.into(), v.into())));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -176,8 +196,10 @@ impl WasiCtxBuilder {
|
||||
///
|
||||
/// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else
|
||||
/// `WasiCtxBuilder::build()` will fail.
|
||||
pub fn env<S: AsRef<[u8]>>(mut self, k: S, v: S) -> Self {
|
||||
pub fn env<S: AsRef<[u8]>>(&mut self, k: S, v: S) -> &mut Self {
|
||||
self.env
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.insert(k.as_ref().to_vec().into(), v.as_ref().to_vec().into());
|
||||
self
|
||||
}
|
||||
@@ -187,40 +209,40 @@ impl WasiCtxBuilder {
|
||||
/// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else
|
||||
/// `WasiCtxBuilder::build()` will fail.
|
||||
pub fn envs<S: AsRef<[u8]>, T: Borrow<(S, S)>>(
|
||||
mut self,
|
||||
&mut self,
|
||||
envs: impl IntoIterator<Item = T>,
|
||||
) -> Self {
|
||||
self.env = envs
|
||||
.into_iter()
|
||||
.map(|t| {
|
||||
let (k, v) = t.borrow();
|
||||
(k.as_ref().to_vec().into(), v.as_ref().to_vec().into())
|
||||
})
|
||||
.collect();
|
||||
) -> &mut Self {
|
||||
self.env.as_mut().unwrap().extend(envs.into_iter().map(|t| {
|
||||
let (k, v) = t.borrow();
|
||||
(k.as_ref().to_vec().into(), v.as_ref().to_vec().into())
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
/// Provide a File to use as stdin
|
||||
pub fn stdin(mut self, file: File) -> Self {
|
||||
self.stdin = PendingEntry::File(file);
|
||||
pub fn stdin(&mut self, file: File) -> &mut Self {
|
||||
self.stdin = Some(PendingEntry::File(file));
|
||||
self
|
||||
}
|
||||
|
||||
/// Provide a File to use as stdout
|
||||
pub fn stdout(mut self, file: File) -> Self {
|
||||
self.stdout = PendingEntry::File(file);
|
||||
pub fn stdout(&mut self, file: File) -> &mut Self {
|
||||
self.stdout = Some(PendingEntry::File(file));
|
||||
self
|
||||
}
|
||||
|
||||
/// Provide a File to use as stderr
|
||||
pub fn stderr(mut self, file: File) -> Self {
|
||||
self.stderr = PendingEntry::File(file);
|
||||
pub fn stderr(&mut self, file: File) -> &mut Self {
|
||||
self.stderr = Some(PendingEntry::File(file));
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a preopened directory.
|
||||
pub fn preopened_dir<P: AsRef<Path>>(mut self, dir: File, guest_path: P) -> Self {
|
||||
self.preopens.push((guest_path.as_ref().to_owned(), dir));
|
||||
pub fn preopened_dir<P: AsRef<Path>>(&mut self, dir: File, guest_path: P) -> &mut Self {
|
||||
self.preopens
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.push((guest_path.as_ref().to_owned(), dir));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -228,17 +250,21 @@ impl WasiCtxBuilder {
|
||||
///
|
||||
/// If any of the arguments or environment variables in this builder cannot be converted into
|
||||
/// `CString`s, either due to NUL bytes or Unicode conversions, this returns an error.
|
||||
pub fn build(self) -> WasiCtxBuilderResult<WasiCtx> {
|
||||
pub fn build(&mut self) -> WasiCtxBuilderResult<WasiCtx> {
|
||||
// Process arguments and environment variables into `CString`s, failing quickly if they
|
||||
// contain any NUL bytes, or if conversion from `OsString` fails.
|
||||
let args = self
|
||||
.args
|
||||
.take()
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|arg| arg.into_utf8_cstring())
|
||||
.collect::<WasiCtxBuilderResult<Vec<CString>>>()?;
|
||||
|
||||
let env = self
|
||||
.env
|
||||
.take()
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|(k, v)| {
|
||||
k.into_string().and_then(|mut pair| {
|
||||
@@ -257,12 +283,12 @@ impl WasiCtxBuilder {
|
||||
let mut fd_pool = FdPool::new();
|
||||
let mut entries: HashMap<wasi::__wasi_fd_t, Entry> = HashMap::new();
|
||||
// Populate the non-preopen fds.
|
||||
for pending in vec![self.stdin, self.stdout, self.stderr] {
|
||||
for pending in &mut [&mut self.stdin, &mut self.stdout, &mut self.stderr] {
|
||||
let fd = fd_pool
|
||||
.allocate()
|
||||
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?;
|
||||
log::debug!("WasiCtx inserting ({:?}, {:?})", fd, pending);
|
||||
match pending {
|
||||
match pending.take().unwrap() {
|
||||
PendingEntry::Thunk(f) => {
|
||||
entries.insert(fd, f()?);
|
||||
}
|
||||
@@ -272,7 +298,7 @@ impl WasiCtxBuilder {
|
||||
}
|
||||
}
|
||||
// Then add the preopen fds.
|
||||
for (guest_path, dir) in self.preopens {
|
||||
for (guest_path, dir) in self.preopens.take().unwrap() {
|
||||
// We do the increment at the beginning of the loop body, so that we don't overflow
|
||||
// unnecessarily if we have exactly the maximum number of file descriptors.
|
||||
let preopen_fd = fd_pool
|
||||
|
||||
@@ -33,14 +33,19 @@ pub fn define_struct(args: TokenStream) -> TokenStream {
|
||||
let mut get_exports = Vec::new();
|
||||
let mut ctor_externs = Vec::new();
|
||||
let mut ctor_fields = Vec::new();
|
||||
let mut linker_add = Vec::new();
|
||||
|
||||
for module in doc.modules() {
|
||||
let module_name = module.name.as_str();
|
||||
for func in module.funcs() {
|
||||
let name = func.name.as_str();
|
||||
let name_ident = Ident::new(func.name.as_str(), Span::call_site());
|
||||
let name_ident = Ident::new(name, Span::call_site());
|
||||
fields.push(quote! { pub #name_ident: wasmtime::Func });
|
||||
get_exports.push(quote! { #name => Some(&self.#name_ident) });
|
||||
ctor_fields.push(name_ident.clone());
|
||||
linker_add.push(quote! {
|
||||
linker.define(#module_name, #name, self.#name_ident.clone())?;
|
||||
});
|
||||
|
||||
let mut shim_arg_decls = Vec::new();
|
||||
let mut params = Vec::new();
|
||||
@@ -249,6 +254,12 @@ pub fn define_struct(args: TokenStream) -> TokenStream {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds all wasi items to the specified `Linker`.
|
||||
pub fn add_to_linker(&self, linker: &mut wasmtime::Linker) -> anyhow::Result<()> {
|
||||
#(#linker_add)*
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,10 +281,10 @@ pub fn define_struct_for_wiggle(args: TokenStream) -> TokenStream {
|
||||
|
||||
for module in doc.modules() {
|
||||
let module_name = module.name.as_str();
|
||||
let module_id = Ident::new(module.name.as_str(), Span::call_site());
|
||||
let module_id = Ident::new(module_name, Span::call_site());
|
||||
for func in module.funcs() {
|
||||
let name = func.name.as_str();
|
||||
let name_ident = Ident::new(func.name.as_str(), Span::call_site());
|
||||
let name_ident = Ident::new(name, Span::call_site());
|
||||
fields.push(quote! { pub #name_ident: wasmtime::Func });
|
||||
get_exports.push(quote! { #name => Some(&self.#name_ident) });
|
||||
ctor_fields.push(name_ident.clone());
|
||||
|
||||
Reference in New Issue
Block a user