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

1
.gitignore vendored
View File

@@ -10,3 +10,4 @@ rusty-tags.*
*~ *~
\#*\# \#*\#
docs/book docs/book
.vscode/

2
Cargo.lock generated
View File

@@ -1990,7 +1990,9 @@ dependencies = [
name = "wasmtime-c-api" name = "wasmtime-c-api"
version = "0.9.0" version = "0.9.0"
dependencies = [ dependencies = [
"wasi-common",
"wasmtime", "wasmtime",
"wasmtime-wasi",
] ]
[[package]] [[package]]

View File

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

View File

@@ -18,3 +18,5 @@ doctest = false
[dependencies] [dependencies]
wasmtime = { path = "../api" } 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, Table, TableType, Trap, Val, ValType,
}; };
mod ext;
mod wasi;
pub use crate::ext::*;
pub use crate::wasi::*;
macro_rules! declare_vec { macro_rules! declare_vec {
($name:ident, $elem_ty:path) => { ($name:ident, $elem_ty:path) => {
#[repr(C)] #[repr(C)]
@@ -1025,7 +1031,7 @@ pub unsafe extern "C" fn wasm_trap_new(
if message[message.len() - 1] != 0 { if message[message.len() - 1] != 0 {
panic!("wasm_trap_new message stringz expected"); 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 { let trap = Box::new(wasm_trap_t {
trap: HostRef::new(Trap::new(message)), 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); let slice = slice::from_raw_parts((*src).data, (*src).size);
(*out).set_from_slice(slice); (*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) static void Main(string[] args)
{ {
using (var engine = new Engine()) using var engine = new Engine();
using (var store = engine.CreateStore()) using var store = engine.CreateStore();
using (var module = store.CreateModule("hello.wasm")) using var module = store.CreateModule("hello.wasm");
using (dynamic instance = module.Instantiate(new Host())) using dynamic instance = module.Instantiate(new Host());
{
instance.run(); instance.run();
}
} }
} }
} }
@@ -166,10 +165,8 @@ Alternatively, the `run` function could be invoked without using the runtime bin
```c# ```c#
... ...
using (var instance = module.Instantiate(new Host())) using var instance = module.Instantiate(new Host());
{ instance.Externs.Functions[0].Invoke();
instance.Externs.Functions[0].Invoke();
}
... ...
``` ```

View File

@@ -21,13 +21,12 @@ namespace HelloExample
{ {
static void Main(string[] args) static void Main(string[] args)
{ {
using (var engine = new Engine()) using var engine = new Engine();
using (var store = engine.CreateStore()) using var store = engine.CreateStore();
using (var module = store.CreateModule("global.wasm")) using var module = store.CreateModule("global.wasm");
using (dynamic instance = module.Instantiate(new Host())) using dynamic instance = module.Instantiate(new Host());
{
instance.run(20); instance.run(20);
}
} }
} }
} }

View File

@@ -18,13 +18,12 @@ namespace HelloExample
{ {
static void Main(string[] args) static void Main(string[] args)
{ {
using (var engine = new Engine()) using var engine = new Engine();
using (var store = engine.CreateStore()) using var store = engine.CreateStore();
using (var module = store.CreateModule("hello.wasm")) using var module = store.CreateModule("hello.wasm");
using (dynamic instance = module.Instantiate(new Host())) using dynamic instance = module.Instantiate(new Host());
{
instance.run(); instance.run();
}
} }
} }
} }

View File

@@ -19,13 +19,12 @@ namespace HelloExample
{ {
static void Main(string[] args) static void Main(string[] args)
{ {
using (var engine = new Engine()) using var engine = new Engine();
using (var store = engine.CreateStore()) using var store = engine.CreateStore();
using (var module = store.CreateModule("memory.wasm")) using var module = store.CreateModule("memory.wasm");
using (dynamic instance = module.Instantiate(new Host())) using dynamic instance = module.Instantiate(new Host());
{
instance.run(); instance.run();
}
} }
} }
} }

View File

@@ -10,35 +10,37 @@ namespace Wasmtime.Bindings
/// <summary> /// <summary>
/// Represents an abstract host binding. /// Represents an abstract host binding.
/// </summary> /// </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) if (module is null)
{ {
throw new ArgumentNullException(nameof(module)); 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 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) foreach (var import in module.Imports.All)
{ {
var wasiBinding = wasi?.Bind(import);
if (!(wasiBinding is null))
{
bindings.Add(wasiBinding);
continue;
}
switch (import) switch (import)
{ {
case FunctionImport func: case FunctionImport func:
@@ -63,7 +65,7 @@ namespace Wasmtime.Bindings
private static FunctionBinding BindFunction(FunctionImport import, IEnumerable<MethodInfo> methods) 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)); var attribute = (ImportAttribute)m.GetCustomAttribute(typeof(ImportAttribute));
if (attribute is null) if (attribute is null)
@@ -88,7 +90,7 @@ namespace Wasmtime.Bindings
private static GlobalBinding BindGlobal(GlobalImport import, IEnumerable<FieldInfo> fields) 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)); var attribute = (ImportAttribute)f.GetCustomAttribute(typeof(ImportAttribute));
return attribute.Name == import.Name && return attribute.Name == import.Name &&
@@ -108,7 +110,7 @@ namespace Wasmtime.Bindings
private static MemoryBinding BindMemory(MemoryImport import, IEnumerable<FieldInfo> fields) 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)); var attribute = (ImportAttribute)f.GetCustomAttribute(typeof(ImportAttribute));
return attribute.Name == import.Name && return attribute.Name == import.Name &&

View File

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

View File

@@ -8,7 +8,7 @@ namespace Wasmtime.Bindings
/// <summary> /// <summary>
/// Represents a host global binding. /// Represents a host global binding.
/// </summary> /// </summary>
public class GlobalBinding : Binding internal class GlobalBinding : Binding
{ {
/// <summary> /// <summary>
/// Constructs a new global binding. /// Constructs a new global binding.
@@ -43,12 +43,12 @@ namespace Wasmtime.Bindings
/// </summary> /// </summary>
public FieldInfo Field { get; private set; } public FieldInfo Field { get; private set; }
internal override SafeHandle Bind(Store store, IHost host) public override SafeHandle Bind(Store store, IHost host)
{ {
unsafe unsafe
{ {
dynamic global = Field.GetValue(host); dynamic global = Field.GetValue(host);
if (global.Handle != null) if (!(global.Handle is null))
{ {
throw new InvalidOperationException("Cannot bind more than once."); throw new InvalidOperationException("Cannot bind more than once.");
} }
@@ -59,14 +59,14 @@ namespace Wasmtime.Bindings
var valueTypeHandle = valueType.DangerousGetHandle(); var valueTypeHandle = valueType.DangerousGetHandle();
valueType.SetHandleAsInvalid(); valueType.SetHandleAsInvalid();
using (var globalType = Interop.wasm_globaltype_new( using var globalType = Interop.wasm_globaltype_new(
valueTypeHandle, valueTypeHandle,
Import.IsMutable ? Interop.wasm_mutability_t.WASM_VAR : Interop.wasm_mutability_t.WASM_CONST)) 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; var handle = Interop.wasm_global_new(store.Handle, globalType, &v);
return handle; global.Handle = handle;
} return handle;
} }
} }
@@ -74,17 +74,17 @@ namespace Wasmtime.Bindings
{ {
if (Field.IsStatic) if (Field.IsStatic)
{ {
ThrowBindingException(Import, Field, "field cannot be static"); throw CreateBindingException(Import, Field, "field cannot be static");
} }
if (!Field.IsInitOnly) if (!Field.IsInitOnly)
{ {
ThrowBindingException(Import, Field, "field must be readonly"); throw CreateBindingException(Import, Field, "field must be readonly");
} }
if (!Field.FieldType.IsGenericType) 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(); var definition = Field.FieldType.GetGenericTypeDefinition();
@@ -92,19 +92,19 @@ namespace Wasmtime.Bindings
{ {
if (Import.IsMutable) 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<>)) else if (definition == typeof(MutableGlobal<>))
{ {
if (!Import.IsMutable) 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 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]; var arg = Field.FieldType.GetGenericArguments()[0];
@@ -113,12 +113,12 @@ namespace Wasmtime.Bindings
{ {
if (!Interop.IsMatchingKind(kind, Import.Kind)) 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 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> /// <summary>
/// Represents a host memory binding. /// Represents a host memory binding.
/// </summary> /// </summary>
public class MemoryBinding : Binding internal class MemoryBinding : Binding
{ {
/// <summary> /// <summary>
/// Constructs a new memory binding. /// Constructs a new memory binding.
@@ -43,10 +43,10 @@ namespace Wasmtime.Bindings
/// </summary> /// </summary>
public FieldInfo Field { get; private set; } 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); Memory memory = (Memory)Field.GetValue(host);
if (memory.Handle != null) if (!(memory.Handle is null))
{ {
throw new InvalidOperationException("Cannot bind more than once."); throw new InvalidOperationException("Cannot bind more than once.");
} }
@@ -56,11 +56,11 @@ namespace Wasmtime.Bindings
if (min != Import.Minimum) 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) 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 unsafe
@@ -68,12 +68,11 @@ namespace Wasmtime.Bindings
Interop.wasm_limits_t limits = new Interop.wasm_limits_t(); Interop.wasm_limits_t limits = new Interop.wasm_limits_t();
limits.min = min; limits.min = min;
limits.max = max; limits.max = max;
using (var memoryType = Interop.wasm_memorytype_new(&limits))
{ using var memoryType = Interop.wasm_memorytype_new(&limits);
var handle = Interop.wasm_memory_new(store.Handle, memoryType); var handle = Interop.wasm_memory_new(store.Handle, memoryType);
memory.Handle = handle; memory.Handle = handle;
return handle; return handle;
}
} }
} }
@@ -81,17 +80,17 @@ namespace Wasmtime.Bindings
{ {
if (Field.IsStatic) if (Field.IsStatic)
{ {
ThrowBindingException(Import, Field, "field cannot be static"); throw CreateBindingException(Import, Field, "field cannot be static");
} }
if (!Field.IsInitOnly) if (!Field.IsInitOnly)
{ {
ThrowBindingException(Import, Field, "field must be readonly"); throw CreateBindingException(Import, Field, "field must be readonly");
} }
if (Field.FieldType != typeof(Memory)) 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)); 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> /// <summary>
/// Writes a UTF-8 string at the given address. /// Writes a UTF-8 string at the given address.
/// </summary> /// </summary>

View File

@@ -14,12 +14,5 @@ namespace Wasmtime
/// </summary> /// </summary>
/// <remarks>A host can only bind to one module instance at a time.</remarks> /// <remarks>A host can only bind to one module instance at a time.</remarks>
Instance Instance { get; set; } 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 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); 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); Name = Marshal.PtrToStringUTF8((IntPtr)name->data, (int)name->size);
} }
} }
@@ -30,6 +32,8 @@ namespace Wasmtime.Imports
/// </summary> /// </summary>
public string Name { get; private set; } public string Name { get; private set; }
internal IntPtr Handle { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() public override string ToString()
{ {

View File

@@ -6,69 +6,72 @@ namespace Wasmtime.Imports
/// <summary> /// <summary>
/// Represents imported functions, globals, tables, and memories to a WebAssembly module. /// Represents imported functions, globals, tables, and memories to a WebAssembly module.
/// </summary> /// </summary>
public class Imports public class Imports : IDisposable
{ {
internal Imports(Module module) internal Imports(Module module)
{ {
Interop.wasm_importtype_vec_t imports; Interop.wasm_importtype_vec_t imports;
Interop.wasm_module_imports(module.Handle, out 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); unsafe
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 var importType = imports.data[i];
var externType = Interop.wasm_importtype_type(importType);
switch (Interop.wasm_externtype_kind(externType))
{ {
var importType = imports.data[i]; case Interop.wasm_externkind_t.WASM_EXTERN_FUNC:
var externType = Interop.wasm_importtype_type(importType); 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_GLOBAL:
{ var global = new GlobalImport(importType, externType);
case Interop.wasm_externkind_t.WASM_EXTERN_FUNC: globals.Add(global);
var function = new FunctionImport(importType, externType); all.Add(global);
functions.Add(function); break;
all.Add(function);
break;
case Interop.wasm_externkind_t.WASM_EXTERN_GLOBAL: case Interop.wasm_externkind_t.WASM_EXTERN_TABLE:
var global = new GlobalImport(importType, externType); var table = new TableImport(importType, externType);
globals.Add(global); tables.Add(table);
all.Add(global); all.Add(table);
break; break;
case Interop.wasm_externkind_t.WASM_EXTERN_TABLE: case Interop.wasm_externkind_t.WASM_EXTERN_MEMORY:
var table = new TableImport(importType, externType); var memory = new MemoryImport(importType, externType);
tables.Add(table); memories.Add(memory);
all.Add(table); all.Add(memory);
break; break;
case Interop.wasm_externkind_t.WASM_EXTERN_MEMORY: default:
var memory = new MemoryImport(importType, externType); throw new NotSupportedException("Unsupported import extern type.");
memories.Add(memory);
all.Add(memory);
break;
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; } public IReadOnlyList<MemoryImport> Memories { get; private set; }
internal IReadOnlyList<Import> All { 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.Runtime.InteropServices;
using System.Dynamic; using System.Dynamic;
using Wasmtime.Externs; using Wasmtime.Externs;
using Wasmtime.Bindings;
namespace Wasmtime namespace Wasmtime
{ {
@@ -12,22 +13,23 @@ namespace Wasmtime
/// </summary> /// </summary>
public class Instance : DynamicObject, IDisposable public class Instance : DynamicObject, IDisposable
{ {
internal Instance(Module module, IHost host) internal Instance(Module module, Wasi wasi = null, IHost host = null)
{ {
Host = host; Host = host;
Module = module; Module = module;
//Save the bindings to root the objects. // Save the bindings to root the objects.
//Otherwise the GC may collect the delegates from ExternFunction for example. // Otherwise the GC may collect the callback delegates from FunctionHandles for example.
_bindings = host.GetImportBindings(module); _bindings = Binding.GetImportBindings(module, wasi, host)
var handles = _bindings.Select(b => b.Bind(module.Store, host)).ToList(); .Select(b => b.Bind(module.Store, host))
.ToArray();
unsafe unsafe
{ {
Handle = Interop.wasm_instance_new( Handle = Interop.wasm_instance_new(
Module.Store.Handle, Module.Store.Handle,
Module.Handle, Module.Handle,
handles.Select(h => ToExtern(h)).ToArray(), _bindings.Select(h => ToExtern(h)).ToArray(),
out var trap); out var trap);
if (trap != IntPtr.Zero) if (trap != IntPtr.Zero)
@@ -41,12 +43,6 @@ namespace Wasmtime
throw new WasmtimeException($"Failed to instantiate module '{module.Name}'."); 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); Interop.wasm_instance_exports(Handle, out _externs);
Externs = new Wasmtime.Externs.Externs(Module.Exports, _externs); Externs = new Wasmtime.Externs.Externs(Module.Exports, _externs);
@@ -71,17 +67,27 @@ namespace Wasmtime
public Wasmtime.Externs.Externs Externs { get; private set; } public Wasmtime.Externs.Externs Externs { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public unsafe void Dispose()
{ {
if (!Handle.IsInvalid) if (!Handle.IsInvalid)
{ {
Handle.Dispose(); Handle.Dispose();
Handle.SetHandleAsInvalid(); 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); Interop.wasm_extern_vec_delete(ref _externs);
_externs.size = UIntPtr.Zero; _externs.data = null;
} }
} }
@@ -134,15 +140,18 @@ namespace Wasmtime
case Interop.MemoryHandle m: case Interop.MemoryHandle m:
return Interop.wasm_memory_as_extern(m); return Interop.wasm_memory_as_extern(m);
case Interop.WasiExportHandle w:
return w.DangerousGetHandle();
default: default:
throw new NotSupportedException("Unexpected handle type."); throw new NotSupportedException("Unexpected handle type.");
} }
} }
internal Interop.InstanceHandle Handle { get; private set; } internal Interop.InstanceHandle Handle { get; private set; }
private SafeHandle[] _bindings;
private Interop.wasm_extern_vec_t _externs; private Interop.wasm_extern_vec_t _externs;
private Dictionary<string, ExternFunction> _functions; private Dictionary<string, ExternFunction> _functions;
private Dictionary<string, ExternGlobal> _globals; private Dictionary<string, ExternGlobal> _globals;
private List<Bindings.Binding> _bindings;
} }
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
namespace Wasmtime 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> /// <remarks>See https://github.com/WebAssembly/wasm-c-api/blob/master/include/wasm.h for the C API reference.</remarks>
internal static class Interop internal static class Interop
{ {
const string LibraryName = "wasmtime";
internal class EngineHandle : SafeHandle internal class EngineHandle : SafeHandle
{ {
public EngineHandle() : base(IntPtr.Zero, true) 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; public override bool IsInvalid => handle == IntPtr.Zero;
protected override bool ReleaseHandle() 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)] [StructLayout(LayoutKind.Sequential)]
internal unsafe struct wasm_byte_vec_t internal unsafe struct wasm_byte_vec_t
{ {
@@ -440,323 +490,415 @@ namespace Wasmtime
return vec; 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 // Engine imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern EngineHandle wasm_engine_new(); public static extern EngineHandle wasm_engine_new();
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern void wasm_engine_delete(IntPtr engine); public static extern void wasm_engine_delete(IntPtr engine);
// Store imports // Store imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern StoreHandle wasm_store_new(EngineHandle engine); public static extern StoreHandle wasm_store_new(EngineHandle engine);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern void wasm_store_delete(IntPtr engine); public static extern void wasm_store_delete(IntPtr engine);
// Byte vec imports // Byte vec imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern void wasm_byte_vec_new_empty(out wasm_byte_vec_t vec); 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); 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); 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); 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); public static extern void wasm_byte_vec_delete(ref wasm_byte_vec_t vec);
// Value type vec imports // Value type vec imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern void wasm_valtype_vec_new_empty(out wasm_valtype_vec_t vec); 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); 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); 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); 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); public static extern void wasm_valtype_vec_delete(ref wasm_valtype_vec_t vec);
// Extern vec imports // Extern vec imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern void wasm_extern_vec_new_empty(out wasm_extern_vec_t vec); 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); 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); 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); 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); public static extern void wasm_extern_vec_delete(ref wasm_extern_vec_t vec);
// Import type vec imports // Import type vec imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern void wasm_importtype_vec_new_empty(out wasm_importtype_vec_t vec); 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); 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); 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); 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); public static extern void wasm_importtype_vec_delete(ref wasm_importtype_vec_t vec);
// Export type vec imports // Export type vec imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern void wasm_exporttype_vec_new_empty(out wasm_exporttype_vec_t vec); 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); 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); 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); 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); public static extern void wasm_exporttype_vec_delete(ref wasm_exporttype_vec_t vec);
// Import type imports // Import type imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern unsafe wasm_byte_vec_t* wasm_importtype_module(IntPtr importType); 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); 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); public static extern unsafe IntPtr wasm_importtype_type(IntPtr importType);
// Export type imports // Export type imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern unsafe wasm_byte_vec_t* wasm_exporttype_name(IntPtr exportType); 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); public static extern unsafe IntPtr wasm_exporttype_type(IntPtr exportType);
// Module imports // Module imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern ModuleHandle wasm_module_new(StoreHandle store, ref wasm_byte_vec_t bytes); 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); 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); 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); public static extern void wasm_module_delete(IntPtr module);
// Value type imports // Value type imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern ValueTypeHandle wasm_valtype_new(wasm_valkind_t kind); public static extern ValueTypeHandle wasm_valtype_new(wasm_valkind_t kind);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern void wasm_valtype_delete(IntPtr valueType); public static extern void wasm_valtype_delete(IntPtr valueType);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern ValueKind wasm_valtype_kind(IntPtr valueType); public static extern ValueKind wasm_valtype_kind(IntPtr valueType);
// Extern imports // Extern imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern wasm_externkind_t wasm_extern_kind(IntPtr ext); public static extern wasm_externkind_t wasm_extern_kind(IntPtr ext);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern IntPtr wasm_extern_type(IntPtr ext); public static extern IntPtr wasm_extern_type(IntPtr ext);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern IntPtr wasm_extern_as_func(IntPtr ext); public static extern IntPtr wasm_extern_as_func(IntPtr ext);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern IntPtr wasm_extern_as_global(IntPtr ext); public static extern IntPtr wasm_extern_as_global(IntPtr ext);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern IntPtr wasm_extern_as_table(IntPtr ext); public static extern IntPtr wasm_extern_as_table(IntPtr ext);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern IntPtr wasm_extern_as_memory(IntPtr ext); public static extern IntPtr wasm_extern_as_memory(IntPtr ext);
// Extern type imports // Extern type imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern wasm_externkind_t wasm_externtype_kind(IntPtr externType); 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); 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); 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); 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); public static extern IntPtr wasm_externtype_as_memorytype_const(IntPtr externType);
// Function imports // Function imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern FunctionHandle wasm_func_new(StoreHandle store, FuncTypeHandle type, WasmFuncCallback callback); 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); 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); 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); public static extern IntPtr wasm_func_as_extern(FunctionHandle function);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern IntPtr wasm_global_as_extern(GlobalHandle global); public static extern IntPtr wasm_global_as_extern(GlobalHandle global);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern IntPtr wasm_memory_as_extern(MemoryHandle memory); public static extern IntPtr wasm_memory_as_extern(MemoryHandle memory);
// Function type imports // Function type imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern unsafe wasm_valtype_vec_t* wasm_functype_params(IntPtr funcType); 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); public static extern unsafe wasm_valtype_vec_t* wasm_functype_results(IntPtr funcType);
// Instance imports // Instance imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern unsafe InstanceHandle wasm_instance_new(StoreHandle store, ModuleHandle module, IntPtr[] imports, out IntPtr trap); 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); 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); public static extern void wasm_instance_exports(InstanceHandle instance, out wasm_extern_vec_t exports);
// Function type imports // 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); 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); public static extern void wasm_functype_delete(IntPtr functype);
// Global type imports // Global type imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern GlobalTypeHandle wasm_globaltype_new(IntPtr valueType, wasm_mutability_t mutability); 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); public static extern IntPtr wasm_globaltype_delete(IntPtr globalType);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern IntPtr wasm_globaltype_content(IntPtr globalType); public static extern IntPtr wasm_globaltype_content(IntPtr globalType);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern wasm_mutability_t wasm_globaltype_mutability(IntPtr globalType); public static extern wasm_mutability_t wasm_globaltype_mutability(IntPtr globalType);
// Memory type imports // Memory type imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern unsafe MemoryTypeHandle wasm_memorytype_new(wasm_limits_t* limits); 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); 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); public static extern unsafe wasm_limits_t* wasm_memorytype_limits(MemoryTypeHandle memoryType);
// Trap imports // Trap imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern IntPtr wasm_trap_new(StoreHandle store, ref wasm_byte_vec_t message); 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); 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); public static extern void wasm_trap_message(IntPtr trap, out wasm_byte_vec_t message);
// Table type imports // Table type imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern IntPtr wasm_tabletype_element(IntPtr tableType); 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); public static unsafe extern wasm_limits_t* wasm_tabletype_limits(IntPtr tableType);
// Memory type imports // Memory type imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static unsafe extern wasm_limits_t* wasm_memorytype_limits(IntPtr memoryType); public static unsafe extern wasm_limits_t* wasm_memorytype_limits(IntPtr memoryType);
// Global imports // Global imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static unsafe extern GlobalHandle wasm_global_new(StoreHandle handle, GlobalTypeHandle globalType, wasm_val_t* initialValue); 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); public static extern void wasm_global_delete(IntPtr global);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern IntPtr wasm_global_type(IntPtr global); 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); 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); public static unsafe extern void wasm_global_set(IntPtr global, wasm_val_t* value);
// Memory imports // Memory imports
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern MemoryHandle wasm_memory_new(StoreHandle handle, MemoryTypeHandle memoryType); public static extern MemoryHandle wasm_memory_new(StoreHandle handle, MemoryTypeHandle memoryType);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern void wasm_memory_delete(IntPtr memory); public static extern void wasm_memory_delete(IntPtr memory);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern IntPtr wasm_memory_type(MemoryHandle memory); public static extern IntPtr wasm_memory_type(MemoryHandle memory);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static unsafe extern byte* wasm_memory_data(IntPtr memory); public static unsafe extern byte* wasm_memory_data(IntPtr memory);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern UIntPtr wasm_memory_data_size(IntPtr memory); public static extern UIntPtr wasm_memory_data_size(IntPtr memory);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern uint wasm_memory_size(MemoryHandle memory); public static extern uint wasm_memory_size(MemoryHandle memory);
[DllImport("wasmtime")] [DllImport(LibraryName)]
public static extern bool wasm_memory_grow(MemoryHandle memory, uint delta); 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> /// </summary>
/// <param name="host">The host to use for the WebAssembly module's instance.</param> /// <param name="host">The host to use for the WebAssembly module's instance.</param>
/// <returns>Returns a new <see cref="Instance" />.</returns> /// <returns>Returns a new <see cref="Instance" />.</returns>
public Instance Instantiate(IHost host) public Instance Instantiate(IHost host = null)
{ {
if (host is null) return Instantiate(null, host);
{ }
throw new ArgumentNullException(nameof(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."); throw new InvalidOperationException("The host has already been associated with an instantiated module.");
} }
host.Instance = new Instance(this, host); var instance = new Instance(this, wasi, host);
return host.Instance;
if (!(host is null))
{
host.Instance = instance;
return instance;
}
return instance;
} }
/// <summary> /// <summary>
@@ -94,6 +107,11 @@ namespace Wasmtime
Handle.Dispose(); Handle.Dispose();
Handle.SetHandleAsInvalid(); Handle.SetHandleAsInvalid();
} }
if (!(Imports is null))
{
Imports.Dispose();
Imports = null;
}
} }
internal Interop.ModuleHandle Handle { get; private set; } internal Interop.ModuleHandle Handle { get; private set; }

View File

@@ -29,9 +29,11 @@ namespace Wasmtime
Interop.wasm_trap_message(trap, out var bytes); Interop.wasm_trap_message(trap, out var bytes);
var byteSpan = new ReadOnlySpan<byte>(bytes.data, checked((int)bytes.size)); 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) if (indexOfNull != -1)
{
byteSpan = byteSpan.Slice(0, indexOfNull); byteSpan = byteSpan.Slice(0, indexOfNull);
}
var message = Encoding.UTF8.GetString(byteSpan); var message = Encoding.UTF8.GetString(byteSpan);
Interop.wasm_byte_vec_delete(ref bytes); 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() public void Dispose()
{ {
if (Module != null) if (!(Module is null))
{ {
Module.Dispose(); Module.Dispose();
Module = null; Module = null;
} }
if (Store != null) if (!(Store is null))
{ {
Store.Dispose(); Store.Dispose();
Store = null; Store = null;
} }
if (Engine != null) if (!(Engine is null))
{ {
Engine.Dispose(); Engine.Dispose();
Engine = null; Engine = null;

View File

@@ -12,7 +12,7 @@ namespace Wasmtime.Tests
public class FunctionThunkingTests : IClassFixture<FunctionThunkingFixture> 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 class MyHost : IHost
{ {
@@ -36,36 +36,34 @@ namespace Wasmtime.Tests
public void ItBindsImportMethodsAndCallsThemCorrectly() public void ItBindsImportMethodsAndCallsThemCorrectly()
{ {
var host = new MyHost(); var host = new MyHost();
using (var instance = Fixture.Module.Instantiate(host)) 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 });
invoke_add(40, 2).Should().Be(42); var add_func = instance.Externs.Functions.Where(f => f.Name == "add_wrapper").Single();
invoke_add(22, 5).Should().Be(27); 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. invoke_add(40, 2).Should().Be(42);
GC.Collect(); invoke_add(22, 5).Should().Be(27);
GC.WaitForPendingFinalizers();
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] [Fact]
public void ItPropegatesExceptionsToCallersViaTraps() public void ItPropagatesExceptionsToCallersViaTraps()
{ {
var host = new MyHost(); var host = new MyHost();
using (var instance = Fixture.Module.Instantiate(host)) 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();
action var throw_func = instance.Externs.Functions.Where(f => f.Name == "do_throw_wrapper").Single();
.Should() Action action = () => throw_func.Invoke();
.Throw<TrapException>()
.WithMessage(THROW_MESSAGE); action
} .Should()
.Throw<TrapException>()
.WithMessage(THROW_MESSAGE);
} }
} }
} }

View File

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

View File

@@ -118,7 +118,7 @@ namespace Wasmtime.Tests
[Fact] [Fact]
public void ItFailsToInstantiateWithMissingImport() public void ItFailsToInstantiateWithMissingImport()
{ {
Action action = () => { using (var instance = Fixture.Module.Instantiate(new NoImportsHost())) { } }; Action action = () => { using var instance = Fixture.Module.Instantiate(new NoImportsHost()); };
action action
.Should() .Should()
@@ -129,7 +129,7 @@ namespace Wasmtime.Tests
[Fact] [Fact]
public void ItFailsToInstantiateWithStaticField() public void ItFailsToInstantiateWithStaticField()
{ {
Action action = () => { using (var instance = Fixture.Module.Instantiate(new GlobalIsStaticHost())) { } }; Action action = () => { using var instance = Fixture.Module.Instantiate(new GlobalIsStaticHost()); };
action action
.Should() .Should()
@@ -140,7 +140,7 @@ namespace Wasmtime.Tests
[Fact] [Fact]
public void ItFailsToInstantiateWithNonReadOnlyField() public void ItFailsToInstantiateWithNonReadOnlyField()
{ {
Action action = () => { using (var instance = Fixture.Module.Instantiate(new GlobalIsNotReadOnlyHost())) { } }; Action action = () => { using var instance = Fixture.Module.Instantiate(new GlobalIsNotReadOnlyHost()); };
action action
.Should() .Should()
@@ -151,7 +151,7 @@ namespace Wasmtime.Tests
[Fact] [Fact]
public void ItFailsToInstantiateWithInvalidType() public void ItFailsToInstantiateWithInvalidType()
{ {
Action action = () => { using (var instance = Fixture.Module.Instantiate(new NotAGlobalHost())) { } }; Action action = () => { using var instance = Fixture.Module.Instantiate(new NotAGlobalHost()); };
action action
.Should() .Should()
@@ -162,7 +162,7 @@ namespace Wasmtime.Tests
[Fact] [Fact]
public void ItFailsToInstantiateWithInvalidGlobalType() public void ItFailsToInstantiateWithInvalidGlobalType()
{ {
Action action = () => { using (var instance = Fixture.Module.Instantiate(new NotAValidGlobalTypeHost())) { } }; Action action = () => { using var instance = Fixture.Module.Instantiate(new NotAValidGlobalTypeHost()); };
action action
.Should() .Should()
@@ -173,7 +173,7 @@ namespace Wasmtime.Tests
[Fact] [Fact]
public void ItFailsToInstantiateWithGlobalTypeMismatch() public void ItFailsToInstantiateWithGlobalTypeMismatch()
{ {
Action action = () => { using (var instance = Fixture.Module.Instantiate(new TypeMismatchHost())) { } }; Action action = () => { using var instance = Fixture.Module.Instantiate(new TypeMismatchHost()); };
action action
.Should() .Should()
@@ -184,7 +184,7 @@ namespace Wasmtime.Tests
[Fact] [Fact]
public void ItFailsToInstantiateWhenGlobalIsNotMut() public void ItFailsToInstantiateWhenGlobalIsNotMut()
{ {
Action action = () => { using (var instance = Fixture.Module.Instantiate(new NotMutHost())) { } }; Action action = () => { using var instance = Fixture.Module.Instantiate(new NotMutHost()); };
action action
.Should() .Should()
@@ -195,7 +195,7 @@ namespace Wasmtime.Tests
[Fact] [Fact]
public void ItFailsToInstantiateWhenGlobalIsMut() public void ItFailsToInstantiateWhenGlobalIsMut()
{ {
Action action = () => { using (var instance = Fixture.Module.Instantiate(new MutHost())) { } }; Action action = () => { using var instance = Fixture.Module.Instantiate(new MutHost()); };
action action
.Should() .Should()
@@ -207,53 +207,52 @@ namespace Wasmtime.Tests
public void ItBindsTheGlobalsCorrectly() public void ItBindsTheGlobalsCorrectly()
{ {
var host = new ValidHost(); var host = new ValidHost();
using (dynamic instance = Fixture.Module.Instantiate(host)) 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);
host.Int32Mut.Value = 10; host.Int32Mut.Value.Should().Be(0);
host.Int32Mut.Value.Should().Be(10); ((int)instance.get_global_i32_mut()).Should().Be(0);
((int)instance.get_global_i32_mut()).Should().Be(10); host.Int32.Value.Should().Be(1);
instance.set_global_i32_mut(11); ((int)instance.get_global_i32()).Should().Be(1);
host.Int32Mut.Value.Should().Be(11); host.Int64Mut.Value.Should().Be(2);
((int)instance.get_global_i32_mut()).Should().Be(11); ((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.Int32Mut.Value = 10;
host.Int64Mut.Value.Should().Be(12); host.Int32Mut.Value.Should().Be(10);
((long)instance.get_global_i64_mut()).Should().Be(12); ((int)instance.get_global_i32_mut()).Should().Be(10);
instance.set_global_i64_mut(13); instance.set_global_i32_mut(11);
host.Int64Mut.Value.Should().Be(13); host.Int32Mut.Value.Should().Be(11);
((long)instance.get_global_i64_mut()).Should().Be(13); ((int)instance.get_global_i32_mut()).Should().Be(11);
host.Float32Mut.Value = 14; host.Int64Mut.Value = 12;
host.Float32Mut.Value.Should().Be(14); host.Int64Mut.Value.Should().Be(12);
((float)instance.get_global_f32_mut()).Should().Be(14); ((long)instance.get_global_i64_mut()).Should().Be(12);
instance.set_global_f32_mut(15); instance.set_global_i64_mut(13);
host.Float32Mut.Value.Should().Be(15); host.Int64Mut.Value.Should().Be(13);
((float)instance.get_global_f32_mut()).Should().Be(15); ((long)instance.get_global_i64_mut()).Should().Be(13);
host.Float64Mut.Value = 16; host.Float32Mut.Value = 14;
host.Float64Mut.Value.Should().Be(16); host.Float32Mut.Value.Should().Be(14);
((double)instance.get_global_f64_mut()).Should().Be(16); ((float)instance.get_global_f32_mut()).Should().Be(14);
instance.set_global_f64_mut(17); instance.set_global_f32_mut(15);
host.Float64Mut.Value.Should().Be(17); host.Float32Mut.Value.Should().Be(15);
((double)instance.get_global_f64_mut()).Should().Be(17); ((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() public void ItCreatesExternsForTheMemories()
{ {
var host = new Host(); var host = new Host();
using (var instance = Fixture.Module.Instantiate(host)) using var instance = Fixture.Module.Instantiate(host);
{
instance.Externs.Memories.Count.Should().Be(1);
var memory = instance.Externs.Memories[0]; instance.Externs.Memories.Count.Should().Be(1);
memory.ReadString(0, 11).Should().Be("Hello World");
int written = memory.WriteString(0, "WebAssembly Rocks!");
memory.ReadString(0, written).Should().Be("WebAssembly Rocks!");
memory.ReadByte(20).Should().Be(1); var memory = instance.Externs.Memories[0];
memory.WriteByte(20, 11); memory.ReadString(0, 11).Should().Be("Hello World");
memory.ReadByte(20).Should().Be(11); int written = memory.WriteString(0, "WebAssembly Rocks!");
memory.ReadString(0, written).Should().Be("WebAssembly Rocks!");
memory.ReadInt16(21).Should().Be(2); memory.ReadByte(20).Should().Be(1);
memory.WriteInt16(21, 12); memory.WriteByte(20, 11);
memory.ReadInt16(21).Should().Be(12); memory.ReadByte(20).Should().Be(11);
memory.ReadInt32(23).Should().Be(3); memory.ReadInt16(21).Should().Be(2);
memory.WriteInt32(23, 13); memory.WriteInt16(21, 12);
memory.ReadInt32(23).Should().Be(13); memory.ReadInt16(21).Should().Be(12);
memory.ReadInt64(27).Should().Be(4); memory.ReadInt32(23).Should().Be(3);
memory.WriteInt64(27, 14); memory.WriteInt32(23, 13);
memory.ReadInt64(27).Should().Be(14); memory.ReadInt32(23).Should().Be(13);
memory.ReadSingle(35).Should().Be(5); memory.ReadInt64(27).Should().Be(4);
memory.WriteSingle(35, 15); memory.WriteInt64(27, 14);
memory.ReadSingle(35).Should().Be(15); memory.ReadInt64(27).Should().Be(14);
memory.ReadDouble(39).Should().Be(6); memory.ReadSingle(35).Should().Be(5);
memory.WriteDouble(39, 16); memory.WriteSingle(35, 15);
memory.ReadDouble(39).Should().Be(16); memory.ReadSingle(35).Should().Be(15);
memory.ReadIntPtr(48).Should().Be((IntPtr)7); memory.ReadDouble(39).Should().Be(6);
memory.WriteIntPtr(48, (IntPtr)17); memory.WriteDouble(39, 16);
memory.ReadIntPtr(48).Should().Be((IntPtr)17); 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() public static IEnumerable<object[]> GetMemoryExports()

View File

@@ -74,7 +74,7 @@ namespace Wasmtime.Tests
[Fact] [Fact]
public void ItFailsToInstantiateWithMissingImport() public void ItFailsToInstantiateWithMissingImport()
{ {
Action action = () => { using (var instance = Fixture.Module.Instantiate(new MissingImportsHost())) { } }; Action action = () => { using var instance = Fixture.Module.Instantiate(new MissingImportsHost()); };
action action
.Should() .Should()
@@ -85,7 +85,7 @@ namespace Wasmtime.Tests
[Fact] [Fact]
public void ItFailsToInstantiateWithStaticField() public void ItFailsToInstantiateWithStaticField()
{ {
Action action = () => { using (var instance = Fixture.Module.Instantiate(new MemoryIsStaticHost())) { } }; Action action = () => { using var instance = Fixture.Module.Instantiate(new MemoryIsStaticHost()); };
action action
.Should() .Should()
@@ -96,7 +96,7 @@ namespace Wasmtime.Tests
[Fact] [Fact]
public void ItFailsToInstantiateWithNonReadOnlyField() public void ItFailsToInstantiateWithNonReadOnlyField()
{ {
Action action = () => { using (var instance = Fixture.Module.Instantiate(new MemoryIsNotReadOnlyHost())) { } }; Action action = () => { using var instance = Fixture.Module.Instantiate(new MemoryIsNotReadOnlyHost()); };
action action
.Should() .Should()
@@ -107,7 +107,7 @@ namespace Wasmtime.Tests
[Fact] [Fact]
public void ItFailsToInstantiateWithInvalidType() public void ItFailsToInstantiateWithInvalidType()
{ {
Action action = () => { using (var instance = Fixture.Module.Instantiate(new NotAMemoryHost())) { } }; Action action = () => { using var instance = Fixture.Module.Instantiate(new NotAMemoryHost()); };
action action
.Should() .Should()
@@ -118,7 +118,7 @@ namespace Wasmtime.Tests
[Fact] [Fact]
public void ItFailsToInstantiateWhenMemoryHasInvalidMinimum() public void ItFailsToInstantiateWhenMemoryHasInvalidMinimum()
{ {
Action action = () => { using (var instance = Fixture.Module.Instantiate(new InvalidMinimumHost())) { } }; Action action = () => { using var instance = Fixture.Module.Instantiate(new InvalidMinimumHost()); };
action action
.Should() .Should()
@@ -129,7 +129,7 @@ namespace Wasmtime.Tests
[Fact] [Fact]
public void ItFailsToInstantiateWhenMemoryHasInvalidMaximum() public void ItFailsToInstantiateWhenMemoryHasInvalidMaximum()
{ {
Action action = () => { using (var instance = Fixture.Module.Instantiate(new InvalidMaximumHost())) { } }; Action action = () => { using var instance = Fixture.Module.Instantiate(new InvalidMaximumHost()); };
action action
.Should() .Should()
@@ -141,47 +141,46 @@ namespace Wasmtime.Tests
public void ItBindsTheGlobalsCorrectly() public void ItBindsTheGlobalsCorrectly()
{ {
var host = new ValidHost(); var host = new ValidHost();
using (dynamic instance = Fixture.Module.Instantiate(host)) 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!");
host.Mem.ReadByte(20).Should().Be(1); host.Mem.ReadString(0, 11).Should().Be("Hello World");
host.Mem.WriteByte(20, 11); int written = host.Mem.WriteString(0, "WebAssembly Rocks!");
host.Mem.ReadByte(20).Should().Be(11); host.Mem.ReadString(0, written).Should().Be("WebAssembly Rocks!");
((byte)instance.ReadByte()).Should().Be(11);
host.Mem.ReadInt16(21).Should().Be(2); host.Mem.ReadByte(20).Should().Be(1);
host.Mem.WriteInt16(21, 12); host.Mem.WriteByte(20, 11);
host.Mem.ReadInt16(21).Should().Be(12); host.Mem.ReadByte(20).Should().Be(11);
((short)instance.ReadInt16()).Should().Be(12); ((byte)instance.ReadByte()).Should().Be(11);
host.Mem.ReadInt32(23).Should().Be(3); host.Mem.ReadInt16(21).Should().Be(2);
host.Mem.WriteInt32(23, 13); host.Mem.WriteInt16(21, 12);
host.Mem.ReadInt32(23).Should().Be(13); host.Mem.ReadInt16(21).Should().Be(12);
((int)instance.ReadInt32()).Should().Be(13); ((short)instance.ReadInt16()).Should().Be(12);
host.Mem.ReadInt64(27).Should().Be(4); host.Mem.ReadInt32(23).Should().Be(3);
host.Mem.WriteInt64(27, 14); host.Mem.WriteInt32(23, 13);
host.Mem.ReadInt64(27).Should().Be(14); host.Mem.ReadInt32(23).Should().Be(13);
((long)instance.ReadInt64()).Should().Be(14); ((int)instance.ReadInt32()).Should().Be(13);
host.Mem.ReadSingle(35).Should().Be(5); host.Mem.ReadInt64(27).Should().Be(4);
host.Mem.WriteSingle(35, 15); host.Mem.WriteInt64(27, 14);
host.Mem.ReadSingle(35).Should().Be(15); host.Mem.ReadInt64(27).Should().Be(14);
((float)instance.ReadFloat32()).Should().Be(15); ((long)instance.ReadInt64()).Should().Be(14);
host.Mem.ReadDouble(39).Should().Be(6); host.Mem.ReadSingle(35).Should().Be(5);
host.Mem.WriteDouble(39, 16); host.Mem.WriteSingle(35, 15);
host.Mem.ReadDouble(39).Should().Be(16); host.Mem.ReadSingle(35).Should().Be(15);
((double)instance.ReadFloat64()).Should().Be(16); ((float)instance.ReadFloat32()).Should().Be(15);
host.Mem.ReadIntPtr(48).Should().Be((IntPtr)7); host.Mem.ReadDouble(39).Should().Be(6);
host.Mem.WriteIntPtr(48, (IntPtr)17); host.Mem.WriteDouble(39, 16);
host.Mem.ReadIntPtr(48).Should().Be((IntPtr)17); host.Mem.ReadDouble(39).Should().Be(16);
((IntPtr)instance.ReadIntPtr()).Should().Be((IntPtr)17); ((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. // Create our wasi context with pretty standard arguments/inheritance/etc.
// Additionally register andy preopened directories if we have them. // Additionally register andy preopened directories if we have them.
let mut builder = wasi_common::WasiCtxBuilder::new() let mut builder = wasi_common::WasiCtxBuilder::new();
.arg(bin_name)
.arg(".") builder.arg(bin_name).arg(".").inherit_stdio();
.inherit_stdio();
for (dir, file) in get_preopens(workspace)? { 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 // 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, // where `stdin` is never ready to be read. In some CI systems, however,
// stdin is closed which causes tests to fail. // stdin is closed which causes tests to fail.
let (reader, _writer) = os_pipe::pipe()?; 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 snapshot1 = wasmtime_wasi::Wasi::new(&store, builder.build()?);
let module = Module::new(&store, &data).context("failed to create wasm module")?; let module = Module::new(&store, &data).context("failed to create wasm module")?;
let imports = module let imports = module

View File

@@ -60,38 +60,38 @@ impl PendingCString {
/// A builder allowing customizable construction of `WasiCtx` instances. /// A builder allowing customizable construction of `WasiCtx` instances.
pub struct WasiCtxBuilder { pub struct WasiCtxBuilder {
fds: HashMap<wasi::__wasi_fd_t, PendingFdEntry>, fds: Option<HashMap<wasi::__wasi_fd_t, PendingFdEntry>>,
preopens: Vec<(PathBuf, File)>, preopens: Option<Vec<(PathBuf, File)>>,
args: Vec<PendingCString>, args: Option<Vec<PendingCString>>,
env: HashMap<PendingCString, PendingCString>, env: Option<HashMap<PendingCString, PendingCString>>,
} }
impl WasiCtxBuilder { impl WasiCtxBuilder {
/// Builder for a new `WasiCtx`. /// Builder for a new `WasiCtx`.
pub fn new() -> Self { pub fn new() -> Self {
let mut builder = Self { let mut fds = HashMap::new();
fds: HashMap::new(),
preopens: Vec::new(),
args: vec![],
env: HashMap::new(),
};
builder.fds.insert(0, PendingFdEntry::Thunk(FdEntry::null)); fds.insert(0, PendingFdEntry::Thunk(FdEntry::null));
builder.fds.insert(1, PendingFdEntry::Thunk(FdEntry::null)); fds.insert(1, PendingFdEntry::Thunk(FdEntry::null));
builder.fds.insert(2, 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. /// Add arguments to the command-line arguments list.
/// ///
/// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail /// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail
/// with `Error::EILSEQ`. /// with `Error::EILSEQ`.
pub fn args<S: AsRef<[u8]>>(mut self, args: impl IntoIterator<Item = S>) -> Self { pub fn args<S: AsRef<[u8]>>(&mut self, args: impl IntoIterator<Item = S>) -> &mut Self {
self.args = args self.args
.into_iter() .as_mut()
.map(|arg| arg.as_ref().to_vec().into()) .unwrap()
.collect(); .extend(args.into_iter().map(|a| a.as_ref().to_vec().into()));
self self
} }
@@ -99,8 +99,11 @@ impl WasiCtxBuilder {
/// ///
/// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail /// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail
/// with `Error::EILSEQ`. /// with `Error::EILSEQ`.
pub fn arg<S: AsRef<[u8]>>(mut self, arg: S) -> Self { pub fn arg<S: AsRef<[u8]>>(&mut self, arg: S) -> &mut Self {
self.args.push(arg.as_ref().to_vec().into()); self.args
.as_mut()
.unwrap()
.push(arg.as_ref().to_vec().into());
self self
} }
@@ -108,19 +111,46 @@ impl WasiCtxBuilder {
/// ///
/// If any arguments from the host process contain invalid UTF-8, `WasiCtxBuilder::build()` will /// If any arguments from the host process contain invalid UTF-8, `WasiCtxBuilder::build()` will
/// fail with `Error::EILSEQ`. /// fail with `Error::EILSEQ`.
pub fn inherit_args(mut self) -> Self { pub fn inherit_args(&mut self) -> &mut Self {
self.args = env::args_os().map(PendingCString::OsString).collect(); 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 self
} }
/// Inherit the stdin, stdout, and stderr streams from the host process. /// Inherit the stdin, stdout, and stderr streams from the host process.
pub fn inherit_stdio(mut self) -> Self { pub fn inherit_stdio(&mut self) -> &mut Self {
self.fds let fds = self.fds.as_mut().unwrap();
.insert(0, PendingFdEntry::Thunk(FdEntry::duplicate_stdin)); fds.insert(0, PendingFdEntry::Thunk(FdEntry::duplicate_stdin));
self.fds fds.insert(1, PendingFdEntry::Thunk(FdEntry::duplicate_stdout));
.insert(1, PendingFdEntry::Thunk(FdEntry::duplicate_stdout)); fds.insert(2, PendingFdEntry::Thunk(FdEntry::duplicate_stderr));
self.fds
.insert(2, PendingFdEntry::Thunk(FdEntry::duplicate_stderr));
self self
} }
@@ -129,10 +159,10 @@ impl WasiCtxBuilder {
/// If any environment variables from the host process contain invalid Unicode (UTF-16 for /// 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 /// Windows, UTF-8 for other platforms), `WasiCtxBuilder::build()` will fail with
/// `Error::EILSEQ`. /// `Error::EILSEQ`.
pub fn inherit_env(mut self) -> Self { pub fn inherit_env(&mut self) -> &mut Self {
self.env = std::env::vars_os() let env = self.env.as_mut().unwrap();
.map(|(k, v)| (k.into(), v.into())) env.clear();
.collect(); env.extend(std::env::vars_os().map(|(k, v)| (k.into(), v.into())));
self self
} }
@@ -140,8 +170,10 @@ impl WasiCtxBuilder {
/// ///
/// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else /// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else
/// `WasiCtxBuilder::build()` will fail with `Error::EILSEQ`. /// `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 self.env
.as_mut()
.unwrap()
.insert(k.as_ref().to_vec().into(), v.as_ref().to_vec().into()); .insert(k.as_ref().to_vec().into(), v.as_ref().to_vec().into());
self self
} }
@@ -151,40 +183,49 @@ impl WasiCtxBuilder {
/// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else /// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else
/// `WasiCtxBuilder::build()` will fail with `Error::EILSEQ`. /// `WasiCtxBuilder::build()` will fail with `Error::EILSEQ`.
pub fn envs<S: AsRef<[u8]>, T: Borrow<(S, S)>>( pub fn envs<S: AsRef<[u8]>, T: Borrow<(S, S)>>(
mut self, &mut self,
envs: impl IntoIterator<Item = T>, envs: impl IntoIterator<Item = T>,
) -> Self { ) -> &mut Self {
self.env = envs self.env.as_mut().unwrap().extend(envs.into_iter().map(|t| {
.into_iter() let (k, v) = t.borrow();
.map(|t| { (k.as_ref().to_vec().into(), v.as_ref().to_vec().into())
let (k, v) = t.borrow(); }));
(k.as_ref().to_vec().into(), v.as_ref().to_vec().into())
})
.collect();
self self
} }
/// Provide a File to use as stdin /// Provide a File to use as stdin
pub fn stdin(mut self, file: File) -> Self { pub fn stdin(&mut self, file: File) -> &mut Self {
self.fds.insert(0, PendingFdEntry::File(file)); self.fds
.as_mut()
.unwrap()
.insert(0, PendingFdEntry::File(file));
self self
} }
/// Provide a File to use as stdout /// Provide a File to use as stdout
pub fn stdout(mut self, file: File) -> Self { pub fn stdout(&mut self, file: File) -> &mut Self {
self.fds.insert(1, PendingFdEntry::File(file)); self.fds
.as_mut()
.unwrap()
.insert(1, PendingFdEntry::File(file));
self self
} }
/// Provide a File to use as stderr /// Provide a File to use as stderr
pub fn stderr(mut self, file: File) -> Self { pub fn stderr(&mut self, file: File) -> &mut Self {
self.fds.insert(2, PendingFdEntry::File(file)); self.fds
.as_mut()
.unwrap()
.insert(2, PendingFdEntry::File(file));
self self
} }
/// Add a preopened directory. /// Add a preopened directory.
pub fn preopened_dir<P: AsRef<Path>>(mut self, dir: File, guest_path: P) -> Self { pub fn preopened_dir<P: AsRef<Path>>(&mut self, dir: File, guest_path: P) -> &mut Self {
self.preopens.push((guest_path.as_ref().to_owned(), dir)); self.preopens
.as_mut()
.unwrap()
.push((guest_path.as_ref().to_owned(), dir));
self self
} }
@@ -192,17 +233,21 @@ impl WasiCtxBuilder {
/// ///
/// If any of the arguments or environment variables in this builder cannot be converted into /// 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`. /// `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 // Process arguments and environment variables into `CString`s, failing quickly if they
// contain any NUL bytes, or if conversion from `OsString` fails. // contain any NUL bytes, or if conversion from `OsString` fails.
let args = self let args = self
.args .args
.take()
.ok_or(Error::EINVAL)?
.into_iter() .into_iter()
.map(|arg| arg.into_utf8_cstring()) .map(|arg| arg.into_utf8_cstring())
.collect::<Result<Vec<CString>>>()?; .collect::<Result<Vec<CString>>>()?;
let env = self let env = self
.env .env
.take()
.ok_or(Error::EINVAL)?
.into_iter() .into_iter()
.map(|(k, v)| { .map(|(k, v)| {
k.into_string().and_then(|mut pair| { k.into_string().and_then(|mut pair| {
@@ -219,7 +264,7 @@ impl WasiCtxBuilder {
let mut fds: HashMap<wasi::__wasi_fd_t, FdEntry> = HashMap::new(); let mut fds: HashMap<wasi::__wasi_fd_t, FdEntry> = HashMap::new();
// Populate the non-preopen fds. // 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); log::debug!("WasiCtx inserting ({:?}, {:?})", fd, pending);
match pending { match pending {
PendingFdEntry::Thunk(f) => { PendingFdEntry::Thunk(f) => {
@@ -234,7 +279,7 @@ impl WasiCtxBuilder {
// so we start from there. This variable is initially 2, though, because the loop // so we start from there. This variable is initially 2, though, because the loop
// immediately does the increment and check for overflow. // immediately does the increment and check for overflow.
let mut preopen_fd: wasi::__wasi_fd_t = 2; 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 // 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. // unnecessarily if we have exactly the maximum number of file descriptors.
preopen_fd = preopen_fd.checked_add(1).ok_or(Error::ENFILE)?; preopen_fd = preopen_fd.checked_add(1).ok_or(Error::ENFILE)?;

View File

@@ -303,22 +303,25 @@ impl ModuleRegistry {
argv: &[String], argv: &[String],
vars: &[(String, String)], vars: &[(String, String)],
) -> Result<ModuleRegistry> { ) -> Result<ModuleRegistry> {
let mut cx1 = wasi_common::WasiCtxBuilder::new() let mut cx1 = wasi_common::WasiCtxBuilder::new();
.inherit_stdio()
.args(argv) cx1.inherit_stdio().args(argv).envs(vars);
.envs(vars);
for (name, file) in preopen_dirs { for (name, file) in preopen_dirs {
cx1 = cx1.preopened_dir(file.try_clone()?, name); cx1.preopened_dir(file.try_clone()?, name);
} }
let cx1 = cx1.build()?; let cx1 = cx1.build()?;
let mut cx2 = wasi_common::old::snapshot_0::WasiCtxBuilder::new() let mut cx2 = wasi_common::old::snapshot_0::WasiCtxBuilder::new()
.inherit_stdio() .inherit_stdio()
.args(argv) .args(argv)
.envs(vars); .envs(vars);
for (name, file) in preopen_dirs { for (name, file) in preopen_dirs {
cx2 = cx2.preopened_dir(file.try_clone()?, name); cx2 = cx2.preopened_dir(file.try_clone()?, name);
} }
let cx2 = cx2.build()?; let cx2 = cx2.build()?;
Ok(ModuleRegistry { Ok(ModuleRegistry {