Merge pull request #977 from peterhuene/wasi-c-api

Implement a WASI instantiation C API.
This commit is contained in:
Peter Huene
2020-02-25 16:25:09 -08:00
committed by GitHub
39 changed files with 1949 additions and 601 deletions

View File

@@ -168,7 +168,7 @@ fn from_wasmtime_abiparam(param: &ir::AbiParam) -> Option<ValType> {
/// A descriptor for a function in a WebAssembly module.
///
/// WebAssembly functions can have 0 or more parameters and results.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct FuncType {
params: Box<[ValType]>,
results: Box<[ValType]>,

View File

@@ -18,3 +18,5 @@ doctest = false
[dependencies]
wasmtime = { path = "../api" }
wasi-common = { path = "../wasi-common" }
wasmtime-wasi = { path = "../wasi" }

View File

@@ -0,0 +1,70 @@
// WASI C API
#ifndef WASI_H
#define WASI_H
#include "wasm.h"
#ifndef WASI_API_EXTERN
#ifdef _WIN32
#define WASI_API_EXTERN __declspec(dllimport)
#else
#define WASI_API_EXTERN
#endif
#endif
#ifdef __cplusplus
extern "C" {
#endif
#define own
#define WASI_DECLARE_OWN(name) \
typedef struct wasi_##name##_t wasi_##name##_t; \
WASI_API_EXTERN void wasi_##name##_delete(own wasi_##name##_t*);
// WASI config
WASI_DECLARE_OWN(config)
WASI_API_EXTERN own wasi_config_t* wasi_config_new();
WASI_API_EXTERN void wasi_config_set_argv(wasi_config_t* config, int argc, const char* argv[]);
WASI_API_EXTERN void wasi_config_inherit_argv(wasi_config_t* config);
WASI_API_EXTERN void wasi_config_set_env(wasi_config_t* config, int envc, const char* names[], const char* values[]);
WASI_API_EXTERN void wasi_config_inherit_env(wasi_config_t* config);
WASI_API_EXTERN bool wasi_config_set_stdin_file(wasi_config_t* config, const char* path);
WASI_API_EXTERN void wasi_config_inherit_stdin(wasi_config_t* config);
WASI_API_EXTERN bool wasi_config_set_stdout_file(wasi_config_t* config, const char* path);
WASI_API_EXTERN void wasi_config_inherit_stdout(wasi_config_t* config);
WASI_API_EXTERN bool wasi_config_set_stderr_file(wasi_config_t* config, const char* path);
WASI_API_EXTERN void wasi_config_inherit_stderr(wasi_config_t* config);
WASI_API_EXTERN bool wasi_config_preopen_dir(wasi_config_t* config, const char* path, const char* guest_path);
// WASI instance
WASI_DECLARE_OWN(instance)
WASI_API_EXTERN own wasi_instance_t* wasi_instance_new(
wasm_store_t* store,
own wasi_config_t* config,
own wasm_trap_t** trap
);
WASI_API_EXTERN const wasm_extern_t* wasi_instance_bind_import(
const wasi_instance_t* instance,
const wasm_importtype_t* import
);
#undef own
#ifdef __cplusplus
} // extern "C"
#endif
#endif // #ifdef WASI_H

View File

@@ -15,6 +15,12 @@ use wasmtime::{
Table, TableType, Trap, Val, ValType,
};
mod ext;
mod wasi;
pub use crate::ext::*;
pub use crate::wasi::*;
macro_rules! declare_vec {
($name:ident, $elem_ty:path) => {
#[repr(C)]
@@ -1025,7 +1031,7 @@ pub unsafe extern "C" fn wasm_trap_new(
if message[message.len() - 1] != 0 {
panic!("wasm_trap_new message stringz expected");
}
let message = String::from_utf8_lossy(message);
let message = String::from_utf8_lossy(&message[..message.len() - 1]);
let trap = Box::new(wasm_trap_t {
trap: HostRef::new(Trap::new(message)),
});
@@ -1777,7 +1783,3 @@ pub unsafe extern "C" fn wasm_valtype_vec_copy(
let slice = slice::from_raw_parts((*src).data, (*src).size);
(*out).set_from_slice(slice);
}
mod ext;
pub use crate::ext::*;

239
crates/c-api/src/wasi.rs Normal file
View File

@@ -0,0 +1,239 @@
//! The WASI embedding API definitions for Wasmtime.
use crate::{wasm_extern_t, wasm_importtype_t, wasm_store_t, wasm_trap_t, ExternHost, ExternType};
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::slice;
use wasi_common::{preopen_dir, WasiCtxBuilder};
use wasmtime::{HostRef, Trap};
use wasmtime_wasi::Wasi;
unsafe fn cstr_to_path<'a>(path: *const c_char) -> Option<&'a Path> {
CStr::from_ptr(path).to_str().map(Path::new).ok()
}
unsafe fn open_file(path: *const c_char) -> Option<File> {
File::open(cstr_to_path(path)?).ok()
}
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,
}
impl wasi_config_t {}
#[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(),
}))
}
#[no_mangle]
pub unsafe extern "C" fn wasi_config_delete(config: *mut wasi_config_t) {
drop(Box::from_raw(config));
}
#[no_mangle]
pub unsafe extern "C" fn wasi_config_set_argv(
config: *mut wasi_config_t,
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())),
);
}
#[no_mangle]
pub unsafe extern "C" fn wasi_config_inherit_argv(config: *mut wasi_config_t) {
(*config).builder.inherit_args();
}
#[no_mangle]
pub unsafe extern "C" fn wasi_config_set_env(
config: *mut wasi_config_t,
envc: c_int,
names: *const *const c_char,
values: *const *const c_char,
) {
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())),
);
}
#[no_mangle]
pub unsafe extern "C" fn wasi_config_inherit_env(config: *mut wasi_config_t) {
(*config).builder.inherit_env();
}
#[no_mangle]
pub unsafe extern "C" fn wasi_config_set_stdin_file(
config: *mut wasi_config_t,
path: *const c_char,
) -> bool {
let file = match open_file(path) {
Some(f) => f,
None => return false,
};
(*config).builder.stdin(file);
true
}
#[no_mangle]
pub unsafe extern "C" fn wasi_config_inherit_stdin(config: *mut wasi_config_t) {
(*config).builder.inherit_stdin();
}
#[no_mangle]
pub unsafe extern "C" fn wasi_config_set_stdout_file(
config: *mut wasi_config_t,
path: *const c_char,
) -> bool {
let file = match create_file(path) {
Some(f) => f,
None => return false,
};
(*config).builder.stdout(file);
true
}
#[no_mangle]
pub unsafe extern "C" fn wasi_config_inherit_stdout(config: *mut wasi_config_t) {
(*config).builder.inherit_stdout();
}
#[no_mangle]
pub unsafe extern "C" fn wasi_config_set_stderr_file(
config: *mut wasi_config_t,
path: *const c_char,
) -> bool {
let file = match create_file(path) {
Some(f) => f,
None => return false,
};
(*config).builder.stderr(file);
true
}
#[no_mangle]
pub unsafe extern "C" fn wasi_config_inherit_stderr(config: *mut wasi_config_t) {
(*config).builder.inherit_stderr();
}
#[no_mangle]
pub unsafe extern "C" fn wasi_config_preopen_dir(
config: *mut wasi_config_t,
path: *const c_char,
guest_path: *const c_char,
) -> bool {
let guest_path = match cstr_to_path(guest_path) {
Some(p) => p,
None => return false,
};
let dir = match cstr_to_path(path) {
Some(p) => match preopen_dir(p) {
Ok(d) => d,
Err(_) => return false,
},
None => return false,
};
(*config).builder.preopened_dir(dir, guest_path);
true
}
#[repr(C)]
pub struct wasi_instance_t {
wasi: Wasi,
export_cache: HashMap<String, Box<wasm_extern_t>>,
}
#[no_mangle]
pub unsafe extern "C" fn wasi_instance_new(
store: *mut wasm_store_t,
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);
match config.builder.build() {
Ok(ctx) => Box::into_raw(Box::new(wasi_instance_t {
wasi: Wasi::new(store, ctx),
export_cache: HashMap::new(),
})),
Err(e) => {
(*trap) = Box::into_raw(Box::new(wasm_trap_t {
trap: HostRef::new(Trap::new(e.to_string())),
}));
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn wasi_instance_delete(instance: *mut wasi_instance_t) {
drop(Box::from_raw(instance));
}
#[no_mangle]
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 name = (*import).ty.name();
match (*instance).wasi.get_export(name) {
Some(export) => {
if export.ty() != func_type {
return std::ptr::null_mut();
}
&**(*instance)
.export_cache
.entry(name.to_string())
.or_insert_with(|| {
Box::new(wasm_extern_t {
which: ExternHost::Func(HostRef::new(export.clone())),
})
}) as *const wasm_extern_t
}
None => std::ptr::null_mut(),
}
}

View File

@@ -142,13 +142,12 @@ namespace Tutorial
{
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()))
{
instance.run();
}
using var engine = new Engine();
using var store = engine.CreateStore();
using var module = store.CreateModule("hello.wasm");
using dynamic instance = module.Instantiate(new Host());
instance.run();
}
}
}
@@ -166,10 +165,8 @@ Alternatively, the `run` function could be invoked without using the runtime bin
```c#
...
using (var instance = module.Instantiate(new Host()))
{
instance.Externs.Functions[0].Invoke();
}
using var instance = module.Instantiate(new Host());
instance.Externs.Functions[0].Invoke();
...
```

View File

@@ -21,13 +21,12 @@ namespace HelloExample
{
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()))
{
instance.run(20);
}
using var engine = new Engine();
using var store = engine.CreateStore();
using var module = store.CreateModule("global.wasm");
using dynamic instance = module.Instantiate(new Host());
instance.run(20);
}
}
}

View File

@@ -18,13 +18,12 @@ namespace HelloExample
{
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()))
{
instance.run();
}
using var engine = new Engine();
using var store = engine.CreateStore();
using var module = store.CreateModule("hello.wasm");
using dynamic instance = module.Instantiate(new Host());
instance.run();
}
}
}

View File

@@ -19,13 +19,12 @@ namespace HelloExample
{
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()))
{
instance.run();
}
using var engine = new Engine();
using var store = engine.CreateStore();
using var module = store.CreateModule("memory.wasm");
using dynamic instance = module.Instantiate(new Host());
instance.run();
}
}
}

View File

@@ -10,35 +10,37 @@ namespace Wasmtime.Bindings
/// <summary>
/// Represents an abstract host binding.
/// </summary>
public abstract class Binding
internal abstract class Binding
{
internal abstract SafeHandle Bind(Store store, IHost host);
public abstract SafeHandle Bind(Store store, IHost host);
internal static void ThrowBindingException(Import import, MemberInfo member, string message)
public static WasmtimeException CreateBindingException(Import import, MemberInfo member, string message)
{
throw new WasmtimeException($"Unable to bind '{member.DeclaringType.Name}.{member.Name}' to WebAssembly import '{import}': {message}.");
return new WasmtimeException($"Unable to bind '{member.DeclaringType.Name}.{member.Name}' to WebAssembly import '{import}': {message}.");
}
internal static List<Binding> GetImportBindings(IHost host, Module module)
public static List<Binding> GetImportBindings(Module module, Wasi wasi = null, IHost host = null)
{
if (host is null)
{
throw new ArgumentNullException(nameof(host));
}
if (module is null)
{
throw new ArgumentNullException(nameof(module));
}
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)));
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:
@@ -63,7 +65,7 @@ namespace Wasmtime.Bindings
private static FunctionBinding BindFunction(FunctionImport import, IEnumerable<MethodInfo> methods)
{
var method = methods.Where(m =>
var method = methods?.Where(m =>
{
var attribute = (ImportAttribute)m.GetCustomAttribute(typeof(ImportAttribute));
if (attribute is null)
@@ -88,7 +90,7 @@ namespace Wasmtime.Bindings
private static GlobalBinding BindGlobal(GlobalImport import, IEnumerable<FieldInfo> fields)
{
var field = fields.Where(f =>
var field = fields?.Where(f =>
{
var attribute = (ImportAttribute)f.GetCustomAttribute(typeof(ImportAttribute));
return attribute.Name == import.Name &&
@@ -108,7 +110,7 @@ namespace Wasmtime.Bindings
private static MemoryBinding BindMemory(MemoryImport import, IEnumerable<FieldInfo> fields)
{
var field = fields.Where(f =>
var field = fields?.Where(f =>
{
var attribute = (ImportAttribute)f.GetCustomAttribute(typeof(ImportAttribute));
return attribute.Name == import.Name &&

View File

@@ -11,7 +11,7 @@ namespace Wasmtime.Bindings
/// <summary>
/// Represents a host function binding.
/// </summary>
public class FunctionBinding : Binding
internal class FunctionBinding : Binding
{
/// <summary>
/// Constructs a new function binding.
@@ -46,23 +46,18 @@ namespace Wasmtime.Bindings
/// </summary>
public MethodInfo Method { get; private set; }
internal override SafeHandle Bind(Store store, IHost host)
public override SafeHandle Bind(Store store, IHost host)
{
unsafe
{
if (_callback != null)
{
throw new InvalidOperationException("Cannot bind more than once.");
}
_callback = CreateCallback(store, host);
var parameters = Interop.ToValueTypeVec(Import.Parameters);
var results = Interop.ToValueTypeVec(Import.Results);
using (var funcType = Interop.wasm_functype_new(ref parameters, ref results))
{
return Interop.wasm_func_new(store.Handle, funcType, _callback);
}
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;
}
}
@@ -70,17 +65,17 @@ namespace Wasmtime.Bindings
{
if (Method.IsStatic)
{
ThrowBindingException(Import, Method, "method cannot be static");
throw CreateBindingException(Import, Method, "method cannot be static");
}
if (Method.IsGenericMethod)
{
ThrowBindingException(Import, Method, "method cannot be generic");
throw CreateBindingException(Import, Method, "method cannot be generic");
}
if (Method.IsConstructor)
{
ThrowBindingException(Import, Method, "method cannot be a constructor");
throw CreateBindingException(Import, Method, "method cannot be a constructor");
}
ValidateParameters();
@@ -93,7 +88,7 @@ namespace Wasmtime.Bindings
var parameters = Method.GetParameters();
if (parameters.Length != Import.Parameters.Count)
{
ThrowBindingException(
throw CreateBindingException(
Import,
Method,
$"parameter mismatch: import requires {Import.Parameters.Count} but the method has {parameters.Length}");
@@ -106,18 +101,18 @@ namespace Wasmtime.Bindings
{
if (parameter.IsOut)
{
ThrowBindingException(Import, Method, $"parameter '{parameter.Name}' cannot be an 'out' parameter");
throw CreateBindingException(Import, Method, $"parameter '{parameter.Name}' cannot be an 'out' parameter");
}
else
{
ThrowBindingException(Import, Method, $"parameter '{parameter.Name}' cannot be a 'ref' parameter");
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))
{
ThrowBindingException(Import, Method, $"method parameter '{parameter.Name}' is expected to be of type '{Interop.ToString(expected)}'");
throw CreateBindingException(Import, Method, $"method parameter '{parameter.Name}' is expected to be of type '{Interop.ToString(expected)}'");
}
}
}
@@ -129,7 +124,7 @@ namespace Wasmtime.Bindings
{
if (Method.ReturnType != typeof(void))
{
ThrowBindingException(Import, Method, "method must return void");
throw CreateBindingException(Import, Method, "method must return void");
}
}
else if (resultsCount == 1)
@@ -137,14 +132,14 @@ namespace Wasmtime.Bindings
var expected = Import.Results[0];
if (!Interop.TryGetValueKind(Method.ReturnType, out var kind) || !Interop.IsMatchingKind(kind, expected))
{
ThrowBindingException(Import, Method, $"return type is expected to be '{Interop.ToString(expected)}'");
throw CreateBindingException(Import, Method, $"return type is expected to be '{Interop.ToString(expected)}'");
}
}
else
{
if (!IsTupleOfSize(Method.ReturnType, resultsCount))
{
ThrowBindingException(Import, Method, $"return type is expected to be a tuple of size {resultsCount}");
throw CreateBindingException(Import, Method, $"return type is expected to be a tuple of size {resultsCount}");
}
var typeArguments =
@@ -163,7 +158,7 @@ namespace Wasmtime.Bindings
var expected = Import.Results[i];
if (!Interop.TryGetValueKind(typeArgument, out var kind) || !Interop.IsMatchingKind(kind, expected))
{
ThrowBindingException(Import, Method, $"return tuple item #{i} is expected to be of type '{Interop.ToString(expected)}'");
throw CreateBindingException(Import, Method, $"return tuple item #{i} is expected to be of type '{Interop.ToString(expected)}'");
}
++i;
@@ -340,7 +335,5 @@ namespace Wasmtime.Bindings
throw new NotSupportedException("Unsupported return value type.");
}
}
private Interop.WasmFuncCallback _callback;
}
}

View File

@@ -8,7 +8,7 @@ namespace Wasmtime.Bindings
/// <summary>
/// Represents a host global binding.
/// </summary>
public class GlobalBinding : Binding
internal class GlobalBinding : Binding
{
/// <summary>
/// Constructs a new global binding.
@@ -43,12 +43,12 @@ namespace Wasmtime.Bindings
/// </summary>
public FieldInfo Field { get; private set; }
internal override SafeHandle Bind(Store store, IHost host)
public override SafeHandle Bind(Store store, IHost host)
{
unsafe
{
dynamic global = Field.GetValue(host);
if (global.Handle != null)
if (!(global.Handle is null))
{
throw new InvalidOperationException("Cannot bind more than once.");
}
@@ -59,14 +59,14 @@ namespace Wasmtime.Bindings
var valueTypeHandle = valueType.DangerousGetHandle();
valueType.SetHandleAsInvalid();
using (var globalType = Interop.wasm_globaltype_new(
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;
}
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;
}
}
@@ -74,17 +74,17 @@ namespace Wasmtime.Bindings
{
if (Field.IsStatic)
{
ThrowBindingException(Import, Field, "field cannot be static");
throw CreateBindingException(Import, Field, "field cannot be static");
}
if (!Field.IsInitOnly)
{
ThrowBindingException(Import, Field, "field must be readonly");
throw CreateBindingException(Import, Field, "field must be readonly");
}
if (!Field.FieldType.IsGenericType)
{
ThrowBindingException(Import, Field, "field is expected to be of type 'Global<T>'");
throw CreateBindingException(Import, Field, "field is expected to be of type 'Global<T>'");
}
var definition = Field.FieldType.GetGenericTypeDefinition();
@@ -92,19 +92,19 @@ namespace Wasmtime.Bindings
{
if (Import.IsMutable)
{
ThrowBindingException(Import, Field, "the import is mutable (use the 'MutableGlobal' type)");
throw CreateBindingException(Import, Field, "the import is mutable (use the 'MutableGlobal' type)");
}
}
else if (definition == typeof(MutableGlobal<>))
{
if (!Import.IsMutable)
{
ThrowBindingException(Import, Field, "the import is constant (use the 'Global' type)");
throw CreateBindingException(Import, Field, "the import is constant (use the 'Global' type)");
}
}
else
{
ThrowBindingException(Import, Field, "field is expected to be of type 'Global<T>' or 'MutableGlobal<T>'");
throw CreateBindingException(Import, Field, "field is expected to be of type 'Global<T>' or 'MutableGlobal<T>'");
}
var arg = Field.FieldType.GetGenericArguments()[0];
@@ -113,12 +113,12 @@ namespace Wasmtime.Bindings
{
if (!Interop.IsMatchingKind(kind, Import.Kind))
{
ThrowBindingException(Import, Field, $"global type argument is expected to be of type '{Interop.ToString(Import.Kind)}'");
throw CreateBindingException(Import, Field, $"global type argument is expected to be of type '{Interop.ToString(Import.Kind)}'");
}
}
else
{
ThrowBindingException(Import, Field, $"'{arg}' is not a valid global type");
throw CreateBindingException(Import, Field, $"'{arg}' is not a valid global type");
}
}
}

View File

@@ -8,7 +8,7 @@ namespace Wasmtime.Bindings
/// <summary>
/// Represents a host memory binding.
/// </summary>
public class MemoryBinding : Binding
internal class MemoryBinding : Binding
{
/// <summary>
/// Constructs a new memory binding.
@@ -43,10 +43,10 @@ namespace Wasmtime.Bindings
/// </summary>
public FieldInfo Field { get; private set; }
internal override SafeHandle Bind(Store store, IHost host)
public override SafeHandle Bind(Store store, IHost host)
{
Memory memory = (Memory)Field.GetValue(host);
if (memory.Handle != null)
if (!(memory.Handle is null))
{
throw new InvalidOperationException("Cannot bind more than once.");
}
@@ -56,11 +56,11 @@ namespace Wasmtime.Bindings
if (min != Import.Minimum)
{
ThrowBindingException(Import, Field, $"Memory does not have the expected minimum of {Import.Minimum} page(s)");
throw CreateBindingException(Import, Field, $"Memory does not have the expected minimum of {Import.Minimum} page(s)");
}
if (max != Import.Maximum)
{
ThrowBindingException(Import, Field, $"Memory does not have the expected maximum of {Import.Maximum} page(s)");
throw CreateBindingException(Import, Field, $"Memory does not have the expected maximum of {Import.Maximum} page(s)");
}
unsafe
@@ -68,12 +68,11 @@ namespace Wasmtime.Bindings
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;
}
using var memoryType = Interop.wasm_memorytype_new(&limits);
var handle = Interop.wasm_memory_new(store.Handle, memoryType);
memory.Handle = handle;
return handle;
}
}
@@ -81,17 +80,17 @@ namespace Wasmtime.Bindings
{
if (Field.IsStatic)
{
ThrowBindingException(Import, Field, "field cannot be static");
throw CreateBindingException(Import, Field, "field cannot be static");
}
if (!Field.IsInitOnly)
{
ThrowBindingException(Import, Field, "field must be readonly");
throw CreateBindingException(Import, Field, "field must be readonly");
}
if (Field.FieldType != typeof(Memory))
{
ThrowBindingException(Import, Field, "field is expected to be of type 'Memory'");
throw CreateBindingException(Import, Field, "field is expected to be of type 'Memory'");
}
}
}

View File

@@ -0,0 +1,23 @@
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;
}
}

View File

@@ -63,6 +63,23 @@ namespace Wasmtime.Externs
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>

View File

@@ -14,12 +14,5 @@ namespace Wasmtime
/// </summary>
/// <remarks>A host can only bind to one module instance at a time.</remarks>
Instance Instance { get; set; }
/// <summary>
/// Gets the import bindings of the host given a WebAssembly module.
/// </summary>
/// <param name="module">The WebAssembly module to get the import bindings for.</param>
/// <returns>Returns the list of import bindings for the host.</returns>
List<Binding> GetImportBindings(Module module) => Binding.GetImportBindings(this, module);
}
}

View File

@@ -12,10 +12,12 @@ namespace Wasmtime.Imports
{
unsafe
{
var moduleName = Interop.wasm_importtype_module(importType);
Handle = importType;
var moduleName = Interop.wasm_importtype_module(Handle);
ModuleName = Marshal.PtrToStringUTF8((IntPtr)moduleName->data, (int)moduleName->size);
var name = Interop.wasm_importtype_name(importType);
var name = Interop.wasm_importtype_name(Handle);
Name = Marshal.PtrToStringUTF8((IntPtr)name->data, (int)name->size);
}
}
@@ -30,6 +32,8 @@ namespace Wasmtime.Imports
/// </summary>
public string Name { get; private set; }
internal IntPtr Handle { get; private set; }
/// <inheritdoc/>
public override string ToString()
{

View File

@@ -6,69 +6,72 @@ namespace Wasmtime.Imports
/// <summary>
/// Represents imported functions, globals, tables, and memories to a WebAssembly module.
/// </summary>
public class Imports
public class Imports : IDisposable
{
internal Imports(Module module)
{
Interop.wasm_importtype_vec_t imports;
Interop.wasm_module_imports(module.Handle, out imports);
try
var all = new List<Import>((int)imports.size);
var functions = new List<FunctionImport>();
var globals = new List<GlobalImport>();
var tables = new List<TableImport>();
var memories = new List<MemoryImport>();
for (int i = 0; i < (int)imports.size; ++i)
{
var all = new List<Import>((int)imports.size);
var functions = new List<FunctionImport>();
var globals = new List<GlobalImport>();
var tables = new List<TableImport>();
var memories = new List<MemoryImport>();
for (int i = 0; i < (int)imports.size; ++i)
unsafe
{
unsafe
var importType = imports.data[i];
var externType = Interop.wasm_importtype_type(importType);
switch (Interop.wasm_externtype_kind(externType))
{
var importType = imports.data[i];
var externType = Interop.wasm_importtype_type(importType);
case Interop.wasm_externkind_t.WASM_EXTERN_FUNC:
var function = new FunctionImport(importType, externType);
functions.Add(function);
all.Add(function);
break;
switch (Interop.wasm_externtype_kind(externType))
{
case Interop.wasm_externkind_t.WASM_EXTERN_FUNC:
var function = new FunctionImport(importType, externType);
functions.Add(function);
all.Add(function);
break;
case Interop.wasm_externkind_t.WASM_EXTERN_GLOBAL:
var global = new GlobalImport(importType, externType);
globals.Add(global);
all.Add(global);
break;
case Interop.wasm_externkind_t.WASM_EXTERN_GLOBAL:
var global = new GlobalImport(importType, externType);
globals.Add(global);
all.Add(global);
break;
case Interop.wasm_externkind_t.WASM_EXTERN_TABLE:
var table = new TableImport(importType, externType);
tables.Add(table);
all.Add(table);
break;
case Interop.wasm_externkind_t.WASM_EXTERN_TABLE:
var table = new TableImport(importType, externType);
tables.Add(table);
all.Add(table);
break;
case Interop.wasm_externkind_t.WASM_EXTERN_MEMORY:
var memory = new MemoryImport(importType, externType);
memories.Add(memory);
all.Add(memory);
break;
case Interop.wasm_externkind_t.WASM_EXTERN_MEMORY:
var memory = new MemoryImport(importType, externType);
memories.Add(memory);
all.Add(memory);
break;
default:
throw new NotSupportedException("Unsupported import extern type.");
}
default:
throw new NotSupportedException("Unsupported import extern type.");
}
}
Functions = functions;
Globals = globals;
Tables = tables;
Memories = memories;
All = all;
}
finally
Functions = functions;
Globals = globals;
Tables = tables;
Memories = memories;
All = all;
}
/// <inheritdoc/>
public unsafe void Dispose()
{
if (!(_imports.data is null))
{
Interop.wasm_importtype_vec_delete(ref imports);
Interop.wasm_importtype_vec_delete(ref _imports);
_imports.data = null;
}
}
@@ -93,5 +96,7 @@ namespace Wasmtime.Imports
public IReadOnlyList<MemoryImport> Memories { get; private set; }
internal IReadOnlyList<Import> All { get; private set; }
private Interop.wasm_importtype_vec_t _imports;
}
}

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Dynamic;
using Wasmtime.Externs;
using Wasmtime.Bindings;
namespace Wasmtime
{
@@ -12,22 +13,23 @@ namespace Wasmtime
/// </summary>
public class Instance : DynamicObject, IDisposable
{
internal Instance(Module module, IHost host)
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 delegates from ExternFunction for example.
_bindings = host.GetImportBindings(module);
var handles = _bindings.Select(b => b.Bind(module.Store, host)).ToList();
// 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,
handles.Select(h => ToExtern(h)).ToArray(),
_bindings.Select(h => ToExtern(h)).ToArray(),
out var trap);
if (trap != IntPtr.Zero)
@@ -41,12 +43,6 @@ namespace Wasmtime
throw new WasmtimeException($"Failed to instantiate module '{module.Name}'.");
}
// Dispose of all function handles (not needed at runtime)
foreach (var h in handles.Where(h => h is Interop.FunctionHandle))
{
h.Dispose();
}
Interop.wasm_instance_exports(Handle, out _externs);
Externs = new Wasmtime.Externs.Externs(Module.Exports, _externs);
@@ -71,17 +67,27 @@ namespace Wasmtime
public Wasmtime.Externs.Externs Externs { get; private set; }
/// <inheritdoc/>
public void Dispose()
public unsafe void Dispose()
{
if (!Handle.IsInvalid)
{
Handle.Dispose();
Handle.SetHandleAsInvalid();
}
if (_externs.size != UIntPtr.Zero)
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);
_externs.size = UIntPtr.Zero;
_externs.data = null;
}
}
@@ -134,15 +140,18 @@ namespace Wasmtime
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.");
}
}
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;
private List<Bindings.Binding> _bindings;
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace Wasmtime
{
@@ -10,6 +11,8 @@ namespace Wasmtime
/// <remarks>See https://github.com/WebAssembly/wasm-c-api/blob/master/include/wasm.h for the C API reference.</remarks>
internal static class Interop
{
const string LibraryName = "wasmtime";
internal class EngineHandle : SafeHandle
{
public EngineHandle() : base(IntPtr.Zero, true)
@@ -61,6 +64,8 @@ namespace Wasmtime
{
}
public WasmFuncCallback Callback { get; set; } = null;
public override bool IsInvalid => handle == IntPtr.Zero;
protected override bool ReleaseHandle()
@@ -175,6 +180,51 @@ namespace Wasmtime
}
}
internal class WasiConfigHandle : SafeHandle
{
public WasiConfigHandle() : base(IntPtr.Zero, true)
{
}
public override bool IsInvalid => handle == IntPtr.Zero;
protected override bool ReleaseHandle()
{
Interop.wasi_config_delete(handle);
return true;
}
}
internal class WasiInstanceHandle : SafeHandle
{
public WasiInstanceHandle() : base(IntPtr.Zero, true)
{
}
public override bool IsInvalid => handle == IntPtr.Zero;
protected override bool ReleaseHandle()
{
Interop.wasi_instance_delete(handle);
return true;
}
}
internal class WasiExportHandle : SafeHandle
{
public WasiExportHandle(IntPtr handle) : base(IntPtr.Zero, false /* not owned */)
{
SetHandle(handle);
}
public override bool IsInvalid => handle == IntPtr.Zero;
protected override bool ReleaseHandle()
{
return true;
}
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct wasm_byte_vec_t
{
@@ -440,323 +490,415 @@ namespace Wasmtime
return vec;
}
internal static unsafe (byte*[], GCHandle[]) ToUTF8PtrArray(IList<string> strings)
{
// Unfortunately .NET cannot currently marshal string[] as UTF-8
// See: https://github.com/dotnet/runtime/issues/7315
// Therefore, we need to marshal the strings manually
var handles = new GCHandle[strings.Count];
var ptrs = new byte*[strings.Count];
for (int i = 0; i < strings.Count; ++i)
{
handles[i] = GCHandle.Alloc(
Encoding.UTF8.GetBytes(strings[i] + '\0'),
GCHandleType.Pinned
);
ptrs[i] = (byte*)handles[i].AddrOfPinnedObject();
}
return (ptrs, handles);
}
// Engine imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern EngineHandle wasm_engine_new();
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_engine_delete(IntPtr engine);
// Store imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern StoreHandle wasm_store_new(EngineHandle engine);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_store_delete(IntPtr engine);
// Byte vec imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_byte_vec_new_empty(out wasm_byte_vec_t vec);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_byte_vec_new_uninitialized(out wasm_byte_vec_t vec, UIntPtr length);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_byte_vec_new(out wasm_byte_vec_t vec, UIntPtr length, byte[] data);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_byte_vec_copy(out wasm_byte_vec_t vec, ref wasm_byte_vec_t src);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_byte_vec_delete(ref wasm_byte_vec_t vec);
// Value type vec imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_valtype_vec_new_empty(out wasm_valtype_vec_t vec);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_valtype_vec_new_uninitialized(out wasm_valtype_vec_t vec, UIntPtr length);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_valtype_vec_new(out wasm_valtype_vec_t vec, UIntPtr length, IntPtr[] data);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_valtype_vec_copy(out wasm_valtype_vec_t vec, ref wasm_valtype_vec_t src);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_valtype_vec_delete(ref wasm_valtype_vec_t vec);
// Extern vec imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_extern_vec_new_empty(out wasm_extern_vec_t vec);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_extern_vec_new_uninitialized(out wasm_extern_vec_t vec, UIntPtr length);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_extern_vec_new(out wasm_extern_vec_t vec, UIntPtr length, IntPtr[] data);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_extern_vec_copy(out wasm_extern_vec_t vec, ref wasm_extern_vec_t src);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_extern_vec_delete(ref wasm_extern_vec_t vec);
// Import type vec imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_importtype_vec_new_empty(out wasm_importtype_vec_t vec);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_importtype_vec_new_uninitialized(out wasm_importtype_vec_t vec, UIntPtr length);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_importtype_vec_new(out wasm_importtype_vec_t vec, UIntPtr length, IntPtr[] data);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_importtype_vec_copy(out wasm_importtype_vec_t vec, ref wasm_importtype_vec_t src);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_importtype_vec_delete(ref wasm_importtype_vec_t vec);
// Export type vec imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_exporttype_vec_new_empty(out wasm_exporttype_vec_t vec);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_exporttype_vec_new_uninitialized(out wasm_exporttype_vec_t vec, UIntPtr length);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_exporttype_vec_new(out wasm_exporttype_vec_t vec, UIntPtr length, IntPtr[] data);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_exporttype_vec_copy(out wasm_exporttype_vec_t vec, ref wasm_exporttype_vec_t src);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_exporttype_vec_delete(ref wasm_exporttype_vec_t vec);
// Import type imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern unsafe wasm_byte_vec_t* wasm_importtype_module(IntPtr importType);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern unsafe wasm_byte_vec_t* wasm_importtype_name(IntPtr importType);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern unsafe IntPtr wasm_importtype_type(IntPtr importType);
// Export type imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern unsafe wasm_byte_vec_t* wasm_exporttype_name(IntPtr exportType);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern unsafe IntPtr wasm_exporttype_type(IntPtr exportType);
// Module imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern ModuleHandle wasm_module_new(StoreHandle store, ref wasm_byte_vec_t bytes);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_module_imports(ModuleHandle module, out wasm_importtype_vec_t imports);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_module_exports(ModuleHandle module, out wasm_exporttype_vec_t exports);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_module_delete(IntPtr module);
// Value type imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern ValueTypeHandle wasm_valtype_new(wasm_valkind_t kind);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_valtype_delete(IntPtr valueType);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern ValueKind wasm_valtype_kind(IntPtr valueType);
// Extern imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern wasm_externkind_t wasm_extern_kind(IntPtr ext);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_extern_type(IntPtr ext);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_extern_as_func(IntPtr ext);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_extern_as_global(IntPtr ext);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_extern_as_table(IntPtr ext);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_extern_as_memory(IntPtr ext);
// Extern type imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern wasm_externkind_t wasm_externtype_kind(IntPtr externType);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_externtype_as_functype_const(IntPtr externType);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_externtype_as_globaltype_const(IntPtr externType);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_externtype_as_tabletype_const(IntPtr externType);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_externtype_as_memorytype_const(IntPtr externType);
// Function imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern FunctionHandle wasm_func_new(StoreHandle store, FuncTypeHandle type, WasmFuncCallback callback);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_func_delete(IntPtr function);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static unsafe extern IntPtr wasm_func_call(IntPtr function, wasm_val_t* args, wasm_val_t* results);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_func_as_extern(FunctionHandle function);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_global_as_extern(GlobalHandle global);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_memory_as_extern(MemoryHandle memory);
// Function type imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern unsafe wasm_valtype_vec_t* wasm_functype_params(IntPtr funcType);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern unsafe wasm_valtype_vec_t* wasm_functype_results(IntPtr funcType);
// Instance imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern unsafe InstanceHandle wasm_instance_new(StoreHandle store, ModuleHandle module, IntPtr[] imports, out IntPtr trap);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_instance_delete(IntPtr ext);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_instance_exports(InstanceHandle instance, out wasm_extern_vec_t exports);
// Function type imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern FuncTypeHandle wasm_functype_new(ref wasm_valtype_vec_t parameters, ref wasm_valtype_vec_t results);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_functype_delete(IntPtr functype);
// Global type imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern GlobalTypeHandle wasm_globaltype_new(IntPtr valueType, wasm_mutability_t mutability);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_globaltype_delete(IntPtr globalType);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_globaltype_content(IntPtr globalType);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern wasm_mutability_t wasm_globaltype_mutability(IntPtr globalType);
// Memory type imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern unsafe MemoryTypeHandle wasm_memorytype_new(wasm_limits_t* limits);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_memorytype_delete(IntPtr memoryType);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern unsafe wasm_limits_t* wasm_memorytype_limits(MemoryTypeHandle memoryType);
// Trap imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_trap_new(StoreHandle store, ref wasm_byte_vec_t message);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_trap_delete(IntPtr trap);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_trap_message(IntPtr trap, out wasm_byte_vec_t message);
// Table type imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_tabletype_element(IntPtr tableType);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static unsafe extern wasm_limits_t* wasm_tabletype_limits(IntPtr tableType);
// Memory type imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static unsafe extern wasm_limits_t* wasm_memorytype_limits(IntPtr memoryType);
// Global imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static unsafe extern GlobalHandle wasm_global_new(StoreHandle handle, GlobalTypeHandle globalType, wasm_val_t* initialValue);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_global_delete(IntPtr global);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_global_type(IntPtr global);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static unsafe extern void wasm_global_get(IntPtr global, wasm_val_t* value);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static unsafe extern void wasm_global_set(IntPtr global, wasm_val_t* value);
// Memory imports
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern MemoryHandle wasm_memory_new(StoreHandle handle, MemoryTypeHandle memoryType);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern void wasm_memory_delete(IntPtr memory);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern IntPtr wasm_memory_type(MemoryHandle memory);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static unsafe extern byte* wasm_memory_data(IntPtr memory);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern UIntPtr wasm_memory_data_size(IntPtr memory);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern uint wasm_memory_size(MemoryHandle memory);
[DllImport("wasmtime")]
[DllImport(LibraryName)]
public static extern bool wasm_memory_grow(MemoryHandle memory, uint delta);
// WASI config
[DllImport(LibraryName)]
public static extern WasiConfigHandle wasi_config_new();
[DllImport(LibraryName)]
public static extern void wasi_config_delete(IntPtr config);
[DllImport(LibraryName)]
public unsafe static extern void wasi_config_set_argv(WasiConfigHandle config, int argc, byte*[] argv);
[DllImport(LibraryName)]
public static extern void wasi_config_inherit_argv(WasiConfigHandle config);
[DllImport(LibraryName)]
public static extern unsafe void wasi_config_set_env(
WasiConfigHandle config,
int envc,
byte*[] names,
byte*[] values
);
[DllImport(LibraryName)]
public static extern void wasi_config_inherit_env(WasiConfigHandle config);
[DllImport(LibraryName)]
public static extern bool wasi_config_set_stdin_file(
WasiConfigHandle config,
[MarshalAs(UnmanagedType.LPUTF8Str)] string path
);
[DllImport(LibraryName)]
public static extern void wasi_config_inherit_stdin(WasiConfigHandle config);
[DllImport(LibraryName)]
public static extern bool wasi_config_set_stdout_file(
WasiConfigHandle config,
[MarshalAs(UnmanagedType.LPUTF8Str)] string path
);
[DllImport(LibraryName)]
public static extern void wasi_config_inherit_stdout(WasiConfigHandle config);
[DllImport(LibraryName)]
public static extern bool wasi_config_set_stderr_file(
WasiConfigHandle config,
[MarshalAs(UnmanagedType.LPUTF8Str)] string path
);
[DllImport(LibraryName)]
public static extern void wasi_config_inherit_stderr(WasiConfigHandle config);
[DllImport(LibraryName)]
public static extern bool wasi_config_preopen_dir(
WasiConfigHandle config,
[MarshalAs(UnmanagedType.LPUTF8Str)] string path,
[MarshalAs(UnmanagedType.LPUTF8Str)] string guestPath
);
// WASI instance
[DllImport(LibraryName)]
public static extern WasiInstanceHandle wasi_instance_new(
StoreHandle store,
WasiConfigHandle config,
out IntPtr trap
);
[DllImport(LibraryName)]
public static extern void wasi_instance_delete(IntPtr instance);
[DllImport(LibraryName)]
public static extern IntPtr wasi_instance_bind_import(WasiInstanceHandle instance, IntPtr importType);
}
}

View File

@@ -49,20 +49,33 @@ namespace Wasmtime
/// </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)
public Instance Instantiate(IHost host = null)
{
if (host is null)
{
throw new ArgumentNullException(nameof(host));
}
return Instantiate(null, host);
}
if (host.Instance != null)
/// <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.");
}
host.Instance = new Instance(this, host);
return host.Instance;
var instance = new Instance(this, wasi, host);
if (!(host is null))
{
host.Instance = instance;
return instance;
}
return instance;
}
/// <summary>
@@ -94,6 +107,11 @@ namespace Wasmtime
Handle.Dispose();
Handle.SetHandleAsInvalid();
}
if (!(Imports is null))
{
Imports.Dispose();
Imports = null;
}
}
internal Interop.ModuleHandle Handle { get; private set; }

View File

@@ -29,9 +29,11 @@ namespace Wasmtime
Interop.wasm_trap_message(trap, out var bytes);
var byteSpan = new ReadOnlySpan<byte>(bytes.data, checked((int)bytes.size));
int indexOfNull = byteSpan.IndexOf((byte)0);
int indexOfNull = byteSpan.LastIndexOf((byte)0);
if (indexOfNull != -1)
{
byteSpan = byteSpan.Slice(0, indexOfNull);
}
var message = Encoding.UTF8.GetString(byteSpan);
Interop.wasm_byte_vec_delete(ref bytes);

View File

@@ -0,0 +1,44 @@
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; }
}
}

View File

@@ -0,0 +1,409 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Wasmtime
{
/// <summary>
/// Represents a build of WASI instances.
/// </summary>
public class WasiBuilder
{
/// <summary>
/// Constructs a new <see cref="WasiBuilder" />.
/// </summary>
public WasiBuilder()
{
}
/// <summary>
/// Adds a command line argument to the builder.
/// </summary>
/// <param name="arg">The command line argument to add.</param>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithArg(string arg)
{
if (arg is null)
{
throw new ArgumentNullException(nameof(arg));
}
if (_inheritArgs)
{
_args.Clear();
_inheritArgs = false;
}
_args.Add(arg);
return this;
}
/// <summary>
/// Adds multiple command line arguments to the builder.
/// </summary>
/// <param name="args">The command line arguments to add.</param>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithArgs(IEnumerable<string> args)
{
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}
if (_inheritArgs)
{
_args.Clear();
_inheritArgs = false;
}
foreach (var arg in args)
{
_args.Add(arg);
}
return this;
}
/// <summary>
/// Adds multiple command line arguments to the builder.
/// </summary>
/// <param name="args">The command line arguments to add.</param>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithArgs(params string[] args)
{
return WithArgs((IEnumerable<string>)args);
}
/// <summary>
/// Sets the builder 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()
{
_inheritArgs = true;
_args.Clear();
_args.AddRange(Environment.GetCommandLineArgs());
return this;
}
/// <summary>
/// Adds an environment variable to the builder.
/// </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)
{
if (name is null)
{
throw new ArgumentNullException(nameof(name));
}
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("Environment variable name cannot be empty.", nameof(name));
}
_inheritEnv = false;
_vars.Add((name, value));
return this;
}
/// <summary>
/// Adds multiple environment variables to the builder.
/// </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)
{
if (vars is null)
{
throw new ArgumentNullException(nameof(vars));
}
_inheritEnv = false;
foreach (var v in vars)
{
_vars.Add(v);
}
return this;
}
/// <summary>
/// Sets the builder to inherit environment variables.
/// </summary>
/// <remarks>Any explicitly specified environment variables will be removed.</remarks>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithInheritedEnvironment()
{
_inheritEnv = true;
_vars.Clear();
return this;
}
/// <summary>
/// Sets the builder 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)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException("The path cannot be null or empty.", nameof(path));
}
_inheritStandardInput = false;
_standardInputPath = path;
return this;
}
/// <summary>
/// Sets the builder to inherit stdin.
/// </summary>
/// <remarks>Any explicitly specified stdin file will be removed.</remarks>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithInheritedStandardInput()
{
_inheritStandardInput = true;
_standardInputPath = null;
return this;
}
/// <summary>
/// Sets the builder 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)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException("The path cannot be null or empty.", nameof(path));
}
_inheritStandardOutput = false;
_standardOutputPath = path;
return this;
}
/// <summary>
/// Sets the builder to inherit stdout.
/// </summary>
/// <remarks>Any explicitly specified stdout file will be removed.</remarks>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithInheritedStandardOutput()
{
_inheritStandardOutput = true;
_standardOutputPath = null;
return this;
}
/// <summary>
/// Sets the builder 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)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException("The path cannot be null or empty.", nameof(path));
}
_inheritStandardError = false;
_standardErrorPath = path;
return this;
}
/// <summary>
/// Sets the builder to inherit stderr.
/// </summary>
/// <remarks>Any explicitly specified stderr file will be removed.</remarks>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithInheritedStandardError()
{
_inheritStandardError = true;
_standardErrorPath = null;
return this;
}
/// <summary>
/// Adds a preopen directory to the builder.
/// </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)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException("The path cannot be null or empty.", nameof(path));
}
if (string.IsNullOrEmpty(guestPath))
{
throw new ArgumentException("The guest path cannot be null or empty.", nameof(guestPath));
}
_preopenDirs.Add((path, guestPath));
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)
{
var config = Interop.wasi_config_new();
SetConfigArgs(config);
SetEnvironmentVariables(config);
SetStandardIn(config);
SetStandardOut(config);
SetStandardError(config);
SetPreopenDirectories(config);
return new Wasi(store.Handle, config);
}
private unsafe void SetConfigArgs(Interop.WasiConfigHandle config)
{
// Don't call wasi_config_inherit_argv as the command line to the .NET program may not be
// the same as the process' command line (e.g. `dotnet foo.dll foo bar baz` => "foo.dll foo bar baz").
if (_args.Count == 0)
{
return;
}
var (args, handles) = Interop.ToUTF8PtrArray(_args);
try
{
Interop.wasi_config_set_argv(config, _args.Count, args);
}
finally
{
foreach (var handle in handles)
{
handle.Free();
}
}
}
private unsafe void SetEnvironmentVariables(Interop.WasiConfigHandle config)
{
if (_inheritEnv)
{
Interop.wasi_config_inherit_env(config);
return;
}
if (_vars.Count == 0)
{
return;
}
var (names, nameHandles) = Interop.ToUTF8PtrArray(_vars.Select(var => var.Name).ToArray());
var (values, valueHandles) = Interop.ToUTF8PtrArray(_vars.Select(var => var.Value).ToArray());
try
{
Interop.wasi_config_set_env(config, _vars.Count, names, values);
}
finally
{
foreach (var handle in nameHandles)
{
handle.Free();
}
foreach (var handle in valueHandles)
{
handle.Free();
}
}
}
private void SetStandardIn(Interop.WasiConfigHandle config)
{
if (_inheritStandardInput)
{
Interop.wasi_config_inherit_stdin(config);
return;
}
if (!string.IsNullOrEmpty(_standardInputPath))
{
if (!Interop.wasi_config_set_stdin_file(config, _standardInputPath))
{
throw new InvalidOperationException($"Failed to set stdin to file '{_standardInputPath}'.");
}
}
}
private void SetStandardOut(Interop.WasiConfigHandle config)
{
if (_inheritStandardOutput)
{
Interop.wasi_config_inherit_stdout(config);
return;
}
if (!string.IsNullOrEmpty(_standardOutputPath))
{
if (!Interop.wasi_config_set_stdout_file(config, _standardOutputPath))
{
throw new InvalidOperationException($"Failed to set stdout to file '{_standardOutputPath}'.");
}
}
}
private void SetStandardError(Interop.WasiConfigHandle config)
{
if (_inheritStandardError)
{
Interop.wasi_config_inherit_stderr(config);
return;
}
if (!string.IsNullOrEmpty(_standardErrorPath))
{
if (!Interop.wasi_config_set_stderr_file(config, _standardErrorPath))
{
throw new InvalidOperationException($"Failed to set stderr to file '{_standardErrorPath}'.");
}
}
}
private void SetPreopenDirectories(Interop.WasiConfigHandle config)
{
foreach (var dir in _preopenDirs)
{
if (!Interop.wasi_config_preopen_dir(config, dir.Path, dir.GuestPath))
{
throw new InvalidOperationException($"Failed to preopen directory '{dir.Path}'.");
}
}
}
private readonly List<string> _args = new List<string>();
private readonly List<(string Name, string Value)> _vars = new List<(string, string)>();
private string _standardInputPath;
private string _standardOutputPath;
private string _standardErrorPath;
private readonly List<(string Path, string GuestPath)> _preopenDirs = new List<(string, string)>();
private bool _inheritArgs = false;
private bool _inheritEnv = false;
private bool _inheritStandardInput = false;
private bool _inheritStandardOutput = false;
private bool _inheritStandardError = false;
}
}

View File

@@ -15,19 +15,19 @@ namespace Wasmtime.Tests
public void Dispose()
{
if (Module != null)
if (!(Module is null))
{
Module.Dispose();
Module = null;
}
if (Store != null)
if (!(Store is null))
{
Store.Dispose();
Store = null;
}
if (Engine != null)
if (!(Engine is null))
{
Engine.Dispose();
Engine = null;

View File

@@ -12,7 +12,7 @@ namespace Wasmtime.Tests
public class FunctionThunkingTests : IClassFixture<FunctionThunkingFixture>
{
const string THROW_MESSAGE = "Test error messages for wasmtime dotnet bidnings unit tests.";
const string THROW_MESSAGE = "Test error message for wasmtime dotnet unit tests.";
class MyHost : IHost
{
@@ -36,36 +36,34 @@ namespace Wasmtime.Tests
public void ItBindsImportMethodsAndCallsThemCorrectly()
{
var host = new MyHost();
using (var instance = Fixture.Module.Instantiate(host))
{
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 });
using var instance = Fixture.Module.Instantiate(host);
invoke_add(40, 2).Should().Be(42);
invoke_add(22, 5).Should().Be(27);
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 });
//Collect garbage to make sure delegate function pointers pasted to wasmtime are rooted.
GC.Collect();
GC.WaitForPendingFinalizers();
invoke_add(40, 2).Should().Be(42);
invoke_add(22, 5).Should().Be(27);
invoke_add(1970, 50).Should().Be(2020);
}
//Collect garbage to make sure delegate function pointers pasted to wasmtime are rooted.
GC.Collect();
GC.WaitForPendingFinalizers();
invoke_add(1970, 50).Should().Be(2020);
}
[Fact]
public void ItPropegatesExceptionsToCallersViaTraps()
public void ItPropagatesExceptionsToCallersViaTraps()
{
var host = new MyHost();
using (var instance = Fixture.Module.Instantiate(host))
{
var throw_func = instance.Externs.Functions.Where(f => f.Name == "do_throw_wrapper").Single();
Action action = () => throw_func.Invoke();
using var instance = Fixture.Module.Instantiate(host);
action
.Should()
.Throw<TrapException>()
.WithMessage(THROW_MESSAGE);
}
var throw_func = instance.Externs.Functions.Where(f => f.Name == "do_throw_wrapper").Single();
Action action = () => throw_func.Invoke();
action
.Should()
.Throw<TrapException>()
.WithMessage(THROW_MESSAGE);
}
}
}

View File

@@ -46,124 +46,123 @@ namespace Wasmtime.Tests
[Fact]
public void ItCreatesExternsForTheGlobals()
{
using (var instance = Fixture.Module.Instantiate(new Host()))
{
dynamic dyn = instance;
var globals = instance.Externs.Globals;
globals.Count.Should().Be(8);
using var instance = Fixture.Module.Instantiate(new Host());
var i32 = globals[0];
i32.Name.Should().Be("global_i32");
i32.Kind.Should().Be(ValueKind.Int32);
i32.IsMutable.Should().Be(false);
i32.Value.Should().Be(0);
dynamic dyn = instance;
var globals = instance.Externs.Globals;
globals.Count.Should().Be(8);
var i32Mut = globals[1];
i32Mut.Name.Should().Be("global_i32_mut");
i32Mut.Kind.Should().Be(ValueKind.Int32);
i32Mut.IsMutable.Should().Be(true);
i32Mut.Value.Should().Be(1);
i32Mut.Value = 11;
i32Mut.Value.Should().Be(11);
dyn.global_i32_mut = 12;
((int)dyn.global_i32_mut).Should().Be(12);
i32Mut.Value.Should().Be(12);
var i32 = globals[0];
i32.Name.Should().Be("global_i32");
i32.Kind.Should().Be(ValueKind.Int32);
i32.IsMutable.Should().Be(false);
i32.Value.Should().Be(0);
var i64 = globals[2];
i64.Name.Should().Be("global_i64");
i64.Kind.Should().Be(ValueKind.Int64);
i64.IsMutable.Should().Be(false);
i64.Value.Should().Be(2);
var i32Mut = globals[1];
i32Mut.Name.Should().Be("global_i32_mut");
i32Mut.Kind.Should().Be(ValueKind.Int32);
i32Mut.IsMutable.Should().Be(true);
i32Mut.Value.Should().Be(1);
i32Mut.Value = 11;
i32Mut.Value.Should().Be(11);
dyn.global_i32_mut = 12;
((int)dyn.global_i32_mut).Should().Be(12);
i32Mut.Value.Should().Be(12);
var i64Mut = globals[3];
i64Mut.Name.Should().Be("global_i64_mut");
i64Mut.Kind.Should().Be(ValueKind.Int64);
i64Mut.IsMutable.Should().Be(true);
i64Mut.Value.Should().Be(3);
i64Mut.Value = 13;
i64Mut.Value.Should().Be(13);
dyn.global_i64_mut = 14;
((long)dyn.global_i64_mut).Should().Be(14);
i64Mut.Value.Should().Be(14);
var i64 = globals[2];
i64.Name.Should().Be("global_i64");
i64.Kind.Should().Be(ValueKind.Int64);
i64.IsMutable.Should().Be(false);
i64.Value.Should().Be(2);
var f32 = globals[4];
f32.Name.Should().Be("global_f32");
f32.Kind.Should().Be(ValueKind.Float32);
f32.IsMutable.Should().Be(false);
f32.Value.Should().Be(4);
var i64Mut = globals[3];
i64Mut.Name.Should().Be("global_i64_mut");
i64Mut.Kind.Should().Be(ValueKind.Int64);
i64Mut.IsMutable.Should().Be(true);
i64Mut.Value.Should().Be(3);
i64Mut.Value = 13;
i64Mut.Value.Should().Be(13);
dyn.global_i64_mut = 14;
((long)dyn.global_i64_mut).Should().Be(14);
i64Mut.Value.Should().Be(14);
var f32Mut = globals[5];
f32Mut.Name.Should().Be("global_f32_mut");
f32Mut.Kind.Should().Be(ValueKind.Float32);
f32Mut.IsMutable.Should().Be(true);
f32Mut.Value.Should().Be(5);
f32Mut.Value = 15;
f32Mut.Value.Should().Be(15);
dyn.global_f32_mut = 16;
((float)dyn.global_f32_mut).Should().Be(16);
f32Mut.Value.Should().Be(16);
var f32 = globals[4];
f32.Name.Should().Be("global_f32");
f32.Kind.Should().Be(ValueKind.Float32);
f32.IsMutable.Should().Be(false);
f32.Value.Should().Be(4);
var f64 = globals[6];
f64.Name.Should().Be("global_f64");
f64.Kind.Should().Be(ValueKind.Float64);
f64.IsMutable.Should().Be(false);
f64.Value.Should().Be(6);
var f32Mut = globals[5];
f32Mut.Name.Should().Be("global_f32_mut");
f32Mut.Kind.Should().Be(ValueKind.Float32);
f32Mut.IsMutable.Should().Be(true);
f32Mut.Value.Should().Be(5);
f32Mut.Value = 15;
f32Mut.Value.Should().Be(15);
dyn.global_f32_mut = 16;
((float)dyn.global_f32_mut).Should().Be(16);
f32Mut.Value.Should().Be(16);
var f64Mut = globals[7];
f64Mut.Name.Should().Be("global_f64_mut");
f64Mut.Kind.Should().Be(ValueKind.Float64);
f64Mut.IsMutable.Should().Be(true);
f64Mut.Value.Should().Be(7);
f64Mut.Value = 17;
f64Mut.Value.Should().Be(17);
dyn.global_f64_mut = 17;
((double)dyn.global_f64_mut).Should().Be(17);
f64Mut.Value.Should().Be(17);
var f64 = globals[6];
f64.Name.Should().Be("global_f64");
f64.Kind.Should().Be(ValueKind.Float64);
f64.IsMutable.Should().Be(false);
f64.Value.Should().Be(6);
Action action = () => i32.Value = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_i32' cannot be modified.");
action = () => dyn.global_i32 = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_i32' cannot be modified.");
var f64Mut = globals[7];
f64Mut.Name.Should().Be("global_f64_mut");
f64Mut.Kind.Should().Be(ValueKind.Float64);
f64Mut.IsMutable.Should().Be(true);
f64Mut.Value.Should().Be(7);
f64Mut.Value = 17;
f64Mut.Value.Should().Be(17);
dyn.global_f64_mut = 17;
((double)dyn.global_f64_mut).Should().Be(17);
f64Mut.Value.Should().Be(17);
action = () => i64.Value = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_i64' cannot be modified.");
action = () => dyn.global_i64 = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_i64' cannot be modified.");
Action action = () => i32.Value = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_i32' cannot be modified.");
action = () => dyn.global_i32 = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_i32' cannot be modified.");
action = () => f32.Value = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_f32' cannot be modified.");
action = () => dyn.global_f32 = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_f32' cannot be modified.");
action = () => i64.Value = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_i64' cannot be modified.");
action = () => dyn.global_i64 = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_i64' cannot be modified.");
action = () => f64.Value = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_f64' cannot be modified.");
action = () => dyn.global_f64 = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_f64' cannot be modified.");
}
action = () => f32.Value = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_f32' cannot be modified.");
action = () => dyn.global_f32 = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_f32' cannot be modified.");
action = () => f64.Value = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_f64' cannot be modified.");
action = () => dyn.global_f64 = 0;
action
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The value of global 'global_f64' cannot be modified.");
}
public static IEnumerable<object[]> GetGlobalExports()

View File

@@ -118,7 +118,7 @@ namespace Wasmtime.Tests
[Fact]
public void ItFailsToInstantiateWithMissingImport()
{
Action action = () => { using (var instance = Fixture.Module.Instantiate(new NoImportsHost())) { } };
Action action = () => { using var instance = Fixture.Module.Instantiate(new NoImportsHost()); };
action
.Should()
@@ -129,7 +129,7 @@ namespace Wasmtime.Tests
[Fact]
public void ItFailsToInstantiateWithStaticField()
{
Action action = () => { using (var instance = Fixture.Module.Instantiate(new GlobalIsStaticHost())) { } };
Action action = () => { using var instance = Fixture.Module.Instantiate(new GlobalIsStaticHost()); };
action
.Should()
@@ -140,7 +140,7 @@ namespace Wasmtime.Tests
[Fact]
public void ItFailsToInstantiateWithNonReadOnlyField()
{
Action action = () => { using (var instance = Fixture.Module.Instantiate(new GlobalIsNotReadOnlyHost())) { } };
Action action = () => { using var instance = Fixture.Module.Instantiate(new GlobalIsNotReadOnlyHost()); };
action
.Should()
@@ -151,7 +151,7 @@ namespace Wasmtime.Tests
[Fact]
public void ItFailsToInstantiateWithInvalidType()
{
Action action = () => { using (var instance = Fixture.Module.Instantiate(new NotAGlobalHost())) { } };
Action action = () => { using var instance = Fixture.Module.Instantiate(new NotAGlobalHost()); };
action
.Should()
@@ -162,7 +162,7 @@ namespace Wasmtime.Tests
[Fact]
public void ItFailsToInstantiateWithInvalidGlobalType()
{
Action action = () => { using (var instance = Fixture.Module.Instantiate(new NotAValidGlobalTypeHost())) { } };
Action action = () => { using var instance = Fixture.Module.Instantiate(new NotAValidGlobalTypeHost()); };
action
.Should()
@@ -173,7 +173,7 @@ namespace Wasmtime.Tests
[Fact]
public void ItFailsToInstantiateWithGlobalTypeMismatch()
{
Action action = () => { using (var instance = Fixture.Module.Instantiate(new TypeMismatchHost())) { } };
Action action = () => { using var instance = Fixture.Module.Instantiate(new TypeMismatchHost()); };
action
.Should()
@@ -184,7 +184,7 @@ namespace Wasmtime.Tests
[Fact]
public void ItFailsToInstantiateWhenGlobalIsNotMut()
{
Action action = () => { using (var instance = Fixture.Module.Instantiate(new NotMutHost())) { } };
Action action = () => { using var instance = Fixture.Module.Instantiate(new NotMutHost()); };
action
.Should()
@@ -195,7 +195,7 @@ namespace Wasmtime.Tests
[Fact]
public void ItFailsToInstantiateWhenGlobalIsMut()
{
Action action = () => { using (var instance = Fixture.Module.Instantiate(new MutHost())) { } };
Action action = () => { using var instance = Fixture.Module.Instantiate(new MutHost()); };
action
.Should()
@@ -207,53 +207,52 @@ namespace Wasmtime.Tests
public void ItBindsTheGlobalsCorrectly()
{
var host = new ValidHost();
using (dynamic instance = Fixture.Module.Instantiate(host))
{
host.Int32Mut.Value.Should().Be(0);
((int)instance.get_global_i32_mut()).Should().Be(0);
host.Int32.Value.Should().Be(1);
((int)instance.get_global_i32()).Should().Be(1);
host.Int64Mut.Value.Should().Be(2);
((long)instance.get_global_i64_mut()).Should().Be(2);
host.Int64.Value.Should().Be(3);
((long)instance.get_global_i64()).Should().Be(3);
host.Float32Mut.Value.Should().Be(4);
((float)instance.get_global_f32_mut()).Should().Be(4);
host.Float32.Value.Should().Be(5);
((float)instance.get_global_f32()).Should().Be(5);
host.Float64Mut.Value.Should().Be(6);
((double)instance.get_global_f64_mut()).Should().Be(6);
host.Float64.Value.Should().Be(7);
((double)instance.get_global_f64()).Should().Be(7);
using dynamic instance = Fixture.Module.Instantiate(host);
host.Int32Mut.Value = 10;
host.Int32Mut.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);
((int)instance.get_global_i32_mut()).Should().Be(11);
host.Int32Mut.Value.Should().Be(0);
((int)instance.get_global_i32_mut()).Should().Be(0);
host.Int32.Value.Should().Be(1);
((int)instance.get_global_i32()).Should().Be(1);
host.Int64Mut.Value.Should().Be(2);
((long)instance.get_global_i64_mut()).Should().Be(2);
host.Int64.Value.Should().Be(3);
((long)instance.get_global_i64()).Should().Be(3);
host.Float32Mut.Value.Should().Be(4);
((float)instance.get_global_f32_mut()).Should().Be(4);
host.Float32.Value.Should().Be(5);
((float)instance.get_global_f32()).Should().Be(5);
host.Float64Mut.Value.Should().Be(6);
((double)instance.get_global_f64_mut()).Should().Be(6);
host.Float64.Value.Should().Be(7);
((double)instance.get_global_f64()).Should().Be(7);
host.Int64Mut.Value = 12;
host.Int64Mut.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);
((long)instance.get_global_i64_mut()).Should().Be(13);
host.Int32Mut.Value = 10;
host.Int32Mut.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);
((int)instance.get_global_i32_mut()).Should().Be(11);
host.Float32Mut.Value = 14;
host.Float32Mut.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);
((float)instance.get_global_f32_mut()).Should().Be(15);
host.Int64Mut.Value = 12;
host.Int64Mut.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);
((long)instance.get_global_i64_mut()).Should().Be(13);
host.Float64Mut.Value = 16;
host.Float64Mut.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);
((double)instance.get_global_f64_mut()).Should().Be(17);
}
host.Float32Mut.Value = 14;
host.Float32Mut.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);
((float)instance.get_global_f32_mut()).Should().Be(15);
host.Float64Mut.Value = 16;
host.Float64Mut.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);
((double)instance.get_global_f64_mut()).Should().Be(17);
}
}
}

View File

@@ -45,43 +45,42 @@ namespace Wasmtime.Tests
public void ItCreatesExternsForTheMemories()
{
var host = new Host();
using (var instance = Fixture.Module.Instantiate(host))
{
instance.Externs.Memories.Count.Should().Be(1);
using var instance = Fixture.Module.Instantiate(host);
var memory = instance.Externs.Memories[0];
memory.ReadString(0, 11).Should().Be("Hello World");
int written = memory.WriteString(0, "WebAssembly Rocks!");
memory.ReadString(0, written).Should().Be("WebAssembly Rocks!");
instance.Externs.Memories.Count.Should().Be(1);
memory.ReadByte(20).Should().Be(1);
memory.WriteByte(20, 11);
memory.ReadByte(20).Should().Be(11);
var memory = instance.Externs.Memories[0];
memory.ReadString(0, 11).Should().Be("Hello World");
int written = memory.WriteString(0, "WebAssembly Rocks!");
memory.ReadString(0, written).Should().Be("WebAssembly Rocks!");
memory.ReadInt16(21).Should().Be(2);
memory.WriteInt16(21, 12);
memory.ReadInt16(21).Should().Be(12);
memory.ReadByte(20).Should().Be(1);
memory.WriteByte(20, 11);
memory.ReadByte(20).Should().Be(11);
memory.ReadInt32(23).Should().Be(3);
memory.WriteInt32(23, 13);
memory.ReadInt32(23).Should().Be(13);
memory.ReadInt16(21).Should().Be(2);
memory.WriteInt16(21, 12);
memory.ReadInt16(21).Should().Be(12);
memory.ReadInt64(27).Should().Be(4);
memory.WriteInt64(27, 14);
memory.ReadInt64(27).Should().Be(14);
memory.ReadInt32(23).Should().Be(3);
memory.WriteInt32(23, 13);
memory.ReadInt32(23).Should().Be(13);
memory.ReadSingle(35).Should().Be(5);
memory.WriteSingle(35, 15);
memory.ReadSingle(35).Should().Be(15);
memory.ReadInt64(27).Should().Be(4);
memory.WriteInt64(27, 14);
memory.ReadInt64(27).Should().Be(14);
memory.ReadDouble(39).Should().Be(6);
memory.WriteDouble(39, 16);
memory.ReadDouble(39).Should().Be(16);
memory.ReadSingle(35).Should().Be(5);
memory.WriteSingle(35, 15);
memory.ReadSingle(35).Should().Be(15);
memory.ReadIntPtr(48).Should().Be((IntPtr)7);
memory.WriteIntPtr(48, (IntPtr)17);
memory.ReadIntPtr(48).Should().Be((IntPtr)17);
}
memory.ReadDouble(39).Should().Be(6);
memory.WriteDouble(39, 16);
memory.ReadDouble(39).Should().Be(16);
memory.ReadIntPtr(48).Should().Be((IntPtr)7);
memory.WriteIntPtr(48, (IntPtr)17);
memory.ReadIntPtr(48).Should().Be((IntPtr)17);
}
public static IEnumerable<object[]> GetMemoryExports()

View File

@@ -74,7 +74,7 @@ namespace Wasmtime.Tests
[Fact]
public void ItFailsToInstantiateWithMissingImport()
{
Action action = () => { using (var instance = Fixture.Module.Instantiate(new MissingImportsHost())) { } };
Action action = () => { using var instance = Fixture.Module.Instantiate(new MissingImportsHost()); };
action
.Should()
@@ -85,7 +85,7 @@ namespace Wasmtime.Tests
[Fact]
public void ItFailsToInstantiateWithStaticField()
{
Action action = () => { using (var instance = Fixture.Module.Instantiate(new MemoryIsStaticHost())) { } };
Action action = () => { using var instance = Fixture.Module.Instantiate(new MemoryIsStaticHost()); };
action
.Should()
@@ -96,7 +96,7 @@ namespace Wasmtime.Tests
[Fact]
public void ItFailsToInstantiateWithNonReadOnlyField()
{
Action action = () => { using (var instance = Fixture.Module.Instantiate(new MemoryIsNotReadOnlyHost())) { } };
Action action = () => { using var instance = Fixture.Module.Instantiate(new MemoryIsNotReadOnlyHost()); };
action
.Should()
@@ -107,7 +107,7 @@ namespace Wasmtime.Tests
[Fact]
public void ItFailsToInstantiateWithInvalidType()
{
Action action = () => { using (var instance = Fixture.Module.Instantiate(new NotAMemoryHost())) { } };
Action action = () => { using var instance = Fixture.Module.Instantiate(new NotAMemoryHost()); };
action
.Should()
@@ -118,7 +118,7 @@ namespace Wasmtime.Tests
[Fact]
public void ItFailsToInstantiateWhenMemoryHasInvalidMinimum()
{
Action action = () => { using (var instance = Fixture.Module.Instantiate(new InvalidMinimumHost())) { } };
Action action = () => { using var instance = Fixture.Module.Instantiate(new InvalidMinimumHost()); };
action
.Should()
@@ -129,7 +129,7 @@ namespace Wasmtime.Tests
[Fact]
public void ItFailsToInstantiateWhenMemoryHasInvalidMaximum()
{
Action action = () => { using (var instance = Fixture.Module.Instantiate(new InvalidMaximumHost())) { } };
Action action = () => { using var instance = Fixture.Module.Instantiate(new InvalidMaximumHost()); };
action
.Should()
@@ -141,47 +141,46 @@ namespace Wasmtime.Tests
public void ItBindsTheGlobalsCorrectly()
{
var host = new ValidHost();
using (dynamic instance = Fixture.Module.Instantiate(host))
{
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.Module.Instantiate(host);
host.Mem.ReadByte(20).Should().Be(1);
host.Mem.WriteByte(20, 11);
host.Mem.ReadByte(20).Should().Be(11);
((byte)instance.ReadByte()).Should().Be(11);
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!");
host.Mem.ReadInt16(21).Should().Be(2);
host.Mem.WriteInt16(21, 12);
host.Mem.ReadInt16(21).Should().Be(12);
((short)instance.ReadInt16()).Should().Be(12);
host.Mem.ReadByte(20).Should().Be(1);
host.Mem.WriteByte(20, 11);
host.Mem.ReadByte(20).Should().Be(11);
((byte)instance.ReadByte()).Should().Be(11);
host.Mem.ReadInt32(23).Should().Be(3);
host.Mem.WriteInt32(23, 13);
host.Mem.ReadInt32(23).Should().Be(13);
((int)instance.ReadInt32()).Should().Be(13);
host.Mem.ReadInt16(21).Should().Be(2);
host.Mem.WriteInt16(21, 12);
host.Mem.ReadInt16(21).Should().Be(12);
((short)instance.ReadInt16()).Should().Be(12);
host.Mem.ReadInt64(27).Should().Be(4);
host.Mem.WriteInt64(27, 14);
host.Mem.ReadInt64(27).Should().Be(14);
((long)instance.ReadInt64()).Should().Be(14);
host.Mem.ReadInt32(23).Should().Be(3);
host.Mem.WriteInt32(23, 13);
host.Mem.ReadInt32(23).Should().Be(13);
((int)instance.ReadInt32()).Should().Be(13);
host.Mem.ReadSingle(35).Should().Be(5);
host.Mem.WriteSingle(35, 15);
host.Mem.ReadSingle(35).Should().Be(15);
((float)instance.ReadFloat32()).Should().Be(15);
host.Mem.ReadInt64(27).Should().Be(4);
host.Mem.WriteInt64(27, 14);
host.Mem.ReadInt64(27).Should().Be(14);
((long)instance.ReadInt64()).Should().Be(14);
host.Mem.ReadDouble(39).Should().Be(6);
host.Mem.WriteDouble(39, 16);
host.Mem.ReadDouble(39).Should().Be(16);
((double)instance.ReadFloat64()).Should().Be(16);
host.Mem.ReadSingle(35).Should().Be(5);
host.Mem.WriteSingle(35, 15);
host.Mem.ReadSingle(35).Should().Be(15);
((float)instance.ReadFloat32()).Should().Be(15);
host.Mem.ReadIntPtr(48).Should().Be((IntPtr)7);
host.Mem.WriteIntPtr(48, (IntPtr)17);
host.Mem.ReadIntPtr(48).Should().Be((IntPtr)17);
((IntPtr)instance.ReadIntPtr()).Should().Be((IntPtr)17);
}
host.Mem.ReadDouble(39).Should().Be(6);
host.Mem.WriteDouble(39, 16);
host.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);
((IntPtr)instance.ReadIntPtr()).Should().Be((IntPtr)17);
}
}
}

Binary file not shown.

View 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_snapshot_preview1" "environ_sizes_get" (func $wasi_snapshot_preview1.environ_sizes_get (type $t0)))
(import "wasi_snapshot_preview1" "environ_get" (func $wasi_snapshot_preview1.environ_get (type $t0)))
(import "wasi_snapshot_preview1" "args_sizes_get" (func $wasi_snapshot_preview1.args_sizes_get (type $t0)))
(import "wasi_snapshot_preview1" "args_get" (func $wasi_snapshot_preview1.args_get (type $t0)))
(import "wasi_snapshot_preview1" "fd_write" (func $wasi_snapshot_preview1.fd_write (type $t1)))
(import "wasi_snapshot_preview1" "fd_read" (func $wasi_snapshot_preview1.fd_read (type $t1)))
(import "wasi_snapshot_preview1" "fd_close" (func $wasi_snapshot_preview1.fd_close (type $t2)))
(import "wasi_snapshot_preview1" "path_open" (func $wasi_snapshot_preview1.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_snapshot_preview1.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_snapshot_preview1.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_snapshot_preview1.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_snapshot_preview1.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_snapshot_preview1.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_snapshot_preview1.fd_read)
(func $call_fd_close (type $t2) (param $p0 i32) (result i32)
local.get $p0
call $wasi_snapshot_preview1.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_snapshot_preview1.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))
)

View File

@@ -0,0 +1,24 @@
using System;
using System.IO;
namespace Wasmtime.Tests
{
internal class TempFile : IDisposable
{
public TempFile()
{
Path = System.IO.Path.GetTempFileName();
}
public void Dispose()
{
if (Path != null)
{
File.Delete(Path);
Path = null;
}
}
public string Path { get; private set; }
}
}

View File

@@ -0,0 +1,246 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using FluentAssertions;
using Xunit;
namespace Wasmtime.Tests
{
public class WasiFixture : ModuleFixture
{
protected override string ModuleFileName => "Wasi.wasm";
}
public class WasiTests : IClassFixture<WasiFixture>
{
public WasiTests(WasiFixture fixture)
{
Fixture = fixture;
}
private WasiFixture Fixture { get; set; }
[Fact]
public void ItHasNoEnvironmentByDefault()
{
using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store));
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 wasi = new WasiBuilder()
.WithEnvironmentVariables(env.Select(kvp => (kvp.Key, kvp.Value)))
.Build(Fixture.Module.Store);
using var instance = Fixture.Module.Instantiate(wasi);
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 wasi = new WasiBuilder()
.WithInheritedEnvironment()
.Build(Fixture.Module.Store);
using var instance = Fixture.Module.Instantiate(wasi);
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()
{
using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store));
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 wasi = new WasiBuilder()
.WithArgs(args)
.Build(Fixture.Module.Store);
using var instance = Fixture.Module.Instantiate(wasi);
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 wasi = new WasiBuilder()
.WithInheritedArgs()
.Build(Fixture.Module.Store);
using var instance = Fixture.Module.Instantiate(wasi);
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 wasi = new WasiBuilder()
.WithStandardInput(file.Path)
.Build(Fixture.Module.Store);
using var instance = Fixture.Module.Instantiate(wasi);
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 builder = new WasiBuilder();
if (fd == 1)
{
builder.WithStandardOutput(file.Path);
}
else if (fd == 2)
{
builder.WithStandardError(file.Path);
}
var wasi = builder.Build(Fixture.Module.Store);
using var instance = Fixture.Module.Instantiate(wasi);
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 wasi = new WasiBuilder()
.WithPreopenedDirectory(Path.GetDirectoryName(file.Path), "/foo")
.Build(Fixture.Module.Store);
using var instance = Fixture.Module.Instantiate(wasi);
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));
}
}
}

View File

@@ -19,12 +19,12 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any
// Create our wasi context with pretty standard arguments/inheritance/etc.
// Additionally register andy preopened directories if we have them.
let mut builder = wasi_common::WasiCtxBuilder::new()
.arg(bin_name)
.arg(".")
.inherit_stdio();
let mut builder = wasi_common::WasiCtxBuilder::new();
builder.arg(bin_name).arg(".").inherit_stdio();
for (dir, file) in get_preopens(workspace)? {
builder = builder.preopened_dir(file, dir);
builder.preopened_dir(file, dir);
}
// The nonstandard thing we do with `WasiCtxBuilder` is to ensure that
@@ -32,7 +32,7 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any
// where `stdin` is never ready to be read. In some CI systems, however,
// stdin is closed which causes tests to fail.
let (reader, _writer) = os_pipe::pipe()?;
builder = builder.stdin(reader_to_file(reader));
builder.stdin(reader_to_file(reader));
let snapshot1 = wasmtime_wasi::Wasi::new(&store, builder.build()?);
let module = Module::new(&store, &data).context("failed to create wasm module")?;
let imports = module

View File

@@ -60,38 +60,38 @@ impl PendingCString {
/// A builder allowing customizable construction of `WasiCtx` instances.
pub struct WasiCtxBuilder {
fds: HashMap<wasi::__wasi_fd_t, PendingFdEntry>,
preopens: Vec<(PathBuf, File)>,
args: Vec<PendingCString>,
env: HashMap<PendingCString, PendingCString>,
fds: Option<HashMap<wasi::__wasi_fd_t, PendingFdEntry>>,
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 mut builder = Self {
fds: HashMap::new(),
preopens: Vec::new(),
args: vec![],
env: HashMap::new(),
};
let mut fds = HashMap::new();
builder.fds.insert(0, PendingFdEntry::Thunk(FdEntry::null));
builder.fds.insert(1, PendingFdEntry::Thunk(FdEntry::null));
builder.fds.insert(2, PendingFdEntry::Thunk(FdEntry::null));
fds.insert(0, PendingFdEntry::Thunk(FdEntry::null));
fds.insert(1, PendingFdEntry::Thunk(FdEntry::null));
fds.insert(2, PendingFdEntry::Thunk(FdEntry::null));
builder
Self {
fds: Some(fds),
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
/// with `Error::EILSEQ`.
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
}
@@ -99,8 +99,11 @@ impl WasiCtxBuilder {
///
/// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail
/// with `Error::EILSEQ`.
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
}
@@ -108,19 +111,46 @@ impl WasiCtxBuilder {
///
/// If any arguments from the host process contain invalid UTF-8, `WasiCtxBuilder::build()` will
/// fail with `Error::EILSEQ`.
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.fds
.as_mut()
.unwrap()
.insert(0, PendingFdEntry::Thunk(FdEntry::duplicate_stdin));
self
}
/// Inherit stdout from the host process.
pub fn inherit_stdout(&mut self) -> &mut Self {
self.fds
.as_mut()
.unwrap()
.insert(1, PendingFdEntry::Thunk(FdEntry::duplicate_stdout));
self
}
/// Inherit stdout from the host process.
pub fn inherit_stderr(&mut self) -> &mut Self {
self.fds
.as_mut()
.unwrap()
.insert(2, PendingFdEntry::Thunk(FdEntry::duplicate_stderr));
self
}
/// Inherit the stdin, stdout, and stderr streams from the host process.
pub fn inherit_stdio(mut self) -> Self {
self.fds
.insert(0, PendingFdEntry::Thunk(FdEntry::duplicate_stdin));
self.fds
.insert(1, PendingFdEntry::Thunk(FdEntry::duplicate_stdout));
self.fds
.insert(2, PendingFdEntry::Thunk(FdEntry::duplicate_stderr));
pub fn inherit_stdio(&mut self) -> &mut Self {
let fds = self.fds.as_mut().unwrap();
fds.insert(0, PendingFdEntry::Thunk(FdEntry::duplicate_stdin));
fds.insert(1, PendingFdEntry::Thunk(FdEntry::duplicate_stdout));
fds.insert(2, PendingFdEntry::Thunk(FdEntry::duplicate_stderr));
self
}
@@ -129,10 +159,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 with
/// `Error::EILSEQ`.
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
}
@@ -140,8 +170,10 @@ impl WasiCtxBuilder {
///
/// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else
/// `WasiCtxBuilder::build()` will fail with `Error::EILSEQ`.
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
}
@@ -151,40 +183,49 @@ impl WasiCtxBuilder {
/// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else
/// `WasiCtxBuilder::build()` will fail with `Error::EILSEQ`.
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.fds.insert(0, PendingFdEntry::File(file));
pub fn stdin(&mut self, file: File) -> &mut Self {
self.fds
.as_mut()
.unwrap()
.insert(0, PendingFdEntry::File(file));
self
}
/// Provide a File to use as stdout
pub fn stdout(mut self, file: File) -> Self {
self.fds.insert(1, PendingFdEntry::File(file));
pub fn stdout(&mut self, file: File) -> &mut Self {
self.fds
.as_mut()
.unwrap()
.insert(1, PendingFdEntry::File(file));
self
}
/// Provide a File to use as stderr
pub fn stderr(mut self, file: File) -> Self {
self.fds.insert(2, PendingFdEntry::File(file));
pub fn stderr(&mut self, file: File) -> &mut Self {
self.fds
.as_mut()
.unwrap()
.insert(2, PendingFdEntry::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
}
@@ -192,17 +233,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 `Error::EILSEQ`.
pub fn build(self) -> Result<WasiCtx> {
pub fn build(&mut self) -> Result<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()
.ok_or(Error::EINVAL)?
.into_iter()
.map(|arg| arg.into_utf8_cstring())
.collect::<Result<Vec<CString>>>()?;
let env = self
.env
.take()
.ok_or(Error::EINVAL)?
.into_iter()
.map(|(k, v)| {
k.into_string().and_then(|mut pair| {
@@ -219,7 +264,7 @@ impl WasiCtxBuilder {
let mut fds: HashMap<wasi::__wasi_fd_t, FdEntry> = HashMap::new();
// Populate the non-preopen fds.
for (fd, pending) in self.fds {
for (fd, pending) in self.fds.take().ok_or(Error::EINVAL)? {
log::debug!("WasiCtx inserting ({:?}, {:?})", fd, pending);
match pending {
PendingFdEntry::Thunk(f) => {
@@ -234,7 +279,7 @@ impl WasiCtxBuilder {
// so we start from there. This variable is initially 2, though, because the loop
// immediately does the increment and check for overflow.
let mut preopen_fd: wasi::__wasi_fd_t = 2;
for (guest_path, dir) in self.preopens {
for (guest_path, dir) in self.preopens.take().ok_or(Error::EINVAL)? {
// 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.
preopen_fd = preopen_fd.checked_add(1).ok_or(Error::ENFILE)?;