diff --git a/.gitignore b/.gitignore index 0ad741626274..daa279dbafcb 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ rusty-tags.* *~ \#*\# docs/book +.vscode/ diff --git a/Cargo.lock b/Cargo.lock index c6979deb506e..380ef5810723 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1990,7 +1990,9 @@ dependencies = [ name = "wasmtime-c-api" version = "0.9.0" dependencies = [ + "wasi-common", "wasmtime", + "wasmtime-wasi", ] [[package]] diff --git a/crates/api/src/types.rs b/crates/api/src/types.rs index 968f62e32f77..8e344d5d0a2a 100644 --- a/crates/api/src/types.rs +++ b/crates/api/src/types.rs @@ -168,7 +168,7 @@ fn from_wasmtime_abiparam(param: &ir::AbiParam) -> Option { /// A descriptor for a function in a WebAssembly module. /// /// WebAssembly functions can have 0 or more parameters and results. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct FuncType { params: Box<[ValType]>, results: Box<[ValType]>, diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml index 9c1a820cb286..3b279681afde 100644 --- a/crates/c-api/Cargo.toml +++ b/crates/c-api/Cargo.toml @@ -18,3 +18,5 @@ doctest = false [dependencies] wasmtime = { path = "../api" } +wasi-common = { path = "../wasi-common" } +wasmtime-wasi = { path = "../wasi" } diff --git a/crates/c-api/include/wasi.h b/crates/c-api/include/wasi.h new file mode 100644 index 000000000000..e1a7ffc5dcda --- /dev/null +++ b/crates/c-api/include/wasi.h @@ -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 \ No newline at end of file diff --git a/crates/c-api/src/lib.rs b/crates/c-api/src/lib.rs index 82c1f819d7fa..fdf07c435dcc 100644 --- a/crates/c-api/src/lib.rs +++ b/crates/c-api/src/lib.rs @@ -15,6 +15,12 @@ use wasmtime::{ Table, TableType, Trap, Val, ValType, }; +mod ext; +mod wasi; + +pub use crate::ext::*; +pub use crate::wasi::*; + macro_rules! declare_vec { ($name:ident, $elem_ty:path) => { #[repr(C)] @@ -1025,7 +1031,7 @@ pub unsafe extern "C" fn wasm_trap_new( if message[message.len() - 1] != 0 { panic!("wasm_trap_new message stringz expected"); } - let message = String::from_utf8_lossy(message); + let message = String::from_utf8_lossy(&message[..message.len() - 1]); let trap = Box::new(wasm_trap_t { trap: HostRef::new(Trap::new(message)), }); @@ -1777,7 +1783,3 @@ pub unsafe extern "C" fn wasm_valtype_vec_copy( let slice = slice::from_raw_parts((*src).data, (*src).size); (*out).set_from_slice(slice); } - -mod ext; - -pub use crate::ext::*; diff --git a/crates/c-api/src/wasi.rs b/crates/c-api/src/wasi.rs new file mode 100644 index 000000000000..5aeeb75e546f --- /dev/null +++ b/crates/c-api/src/wasi.rs @@ -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::open(cstr_to_path(path)?).ok() +} + +unsafe fn create_file(path: *const c_char) -> Option { + 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>, +} + +#[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(), + } +} diff --git a/crates/misc/dotnet/docs/articles/intro.md b/crates/misc/dotnet/docs/articles/intro.md index 6b1d2b95523b..510c9243252f 100644 --- a/crates/misc/dotnet/docs/articles/intro.md +++ b/crates/misc/dotnet/docs/articles/intro.md @@ -142,13 +142,12 @@ namespace Tutorial { static void Main(string[] args) { - using (var engine = new Engine()) - using (var store = engine.CreateStore()) - using (var module = store.CreateModule("hello.wasm")) - using (dynamic instance = module.Instantiate(new Host())) - { - instance.run(); - } + using var engine = new Engine(); + using var store = engine.CreateStore(); + using var module = store.CreateModule("hello.wasm"); + using dynamic instance = module.Instantiate(new Host()); + + instance.run(); } } } @@ -166,10 +165,8 @@ Alternatively, the `run` function could be invoked without using the runtime bin ```c# ... -using (var instance = module.Instantiate(new Host())) -{ - instance.Externs.Functions[0].Invoke(); -} +using var instance = module.Instantiate(new Host()); +instance.Externs.Functions[0].Invoke(); ... ``` diff --git a/crates/misc/dotnet/examples/global/Program.cs b/crates/misc/dotnet/examples/global/Program.cs index 4dcf32b1313b..b8e499485e3a 100644 --- a/crates/misc/dotnet/examples/global/Program.cs +++ b/crates/misc/dotnet/examples/global/Program.cs @@ -21,13 +21,12 @@ class Program { static void Main(string[] args) { - using (var engine = new Engine()) - using (var store = engine.CreateStore()) - using (var module = store.CreateModule("global.wasm")) - using (dynamic instance = module.Instantiate(new Host())) - { - instance.run(20); - } + using var engine = new Engine(); + using var store = engine.CreateStore(); + using var module = store.CreateModule("global.wasm"); + using dynamic instance = module.Instantiate(new Host()); + + instance.run(20); } } } diff --git a/crates/misc/dotnet/examples/hello/Program.cs b/crates/misc/dotnet/examples/hello/Program.cs index c4d853386ba8..ad07f26e2919 100644 --- a/crates/misc/dotnet/examples/hello/Program.cs +++ b/crates/misc/dotnet/examples/hello/Program.cs @@ -18,13 +18,12 @@ class Program { static void Main(string[] args) { - using (var engine = new Engine()) - using (var store = engine.CreateStore()) - using (var module = store.CreateModule("hello.wasm")) - using (dynamic instance = module.Instantiate(new Host())) - { - instance.run(); - } + using var engine = new Engine(); + using var store = engine.CreateStore(); + using var module = store.CreateModule("hello.wasm"); + using dynamic instance = module.Instantiate(new Host()); + + instance.run(); } } } diff --git a/crates/misc/dotnet/examples/memory/Program.cs b/crates/misc/dotnet/examples/memory/Program.cs index f42b806fec2a..349899ca188c 100644 --- a/crates/misc/dotnet/examples/memory/Program.cs +++ b/crates/misc/dotnet/examples/memory/Program.cs @@ -19,13 +19,12 @@ class Program { static void Main(string[] args) { - using (var engine = new Engine()) - using (var store = engine.CreateStore()) - using (var module = store.CreateModule("memory.wasm")) - using (dynamic instance = module.Instantiate(new Host())) - { - instance.run(); - } + using var engine = new Engine(); + using var store = engine.CreateStore(); + using var module = store.CreateModule("memory.wasm"); + using dynamic instance = module.Instantiate(new Host()); + + instance.run(); } } } diff --git a/crates/misc/dotnet/src/Bindings/Binding.cs b/crates/misc/dotnet/src/Bindings/Binding.cs index f8d1a972a347..3ca964ec0c35 100644 --- a/crates/misc/dotnet/src/Bindings/Binding.cs +++ b/crates/misc/dotnet/src/Bindings/Binding.cs @@ -10,35 +10,37 @@ namespace Wasmtime.Bindings /// /// Represents an abstract host binding. /// - 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 GetImportBindings(IHost host, Module module) + public static List GetImportBindings(Module module, Wasi wasi = null, IHost host = null) { - if (host is null) - { - throw new ArgumentNullException(nameof(host)); - } - if (module is null) { throw new ArgumentNullException(nameof(module)); } + var bindings = new List(); 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 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(); foreach (var import in module.Imports.All) { + var wasiBinding = wasi?.Bind(import); + if (!(wasiBinding is null)) + { + bindings.Add(wasiBinding); + continue; + } + switch (import) { case FunctionImport func: @@ -63,7 +65,7 @@ internal static List GetImportBindings(IHost host, Module module) private static FunctionBinding BindFunction(FunctionImport import, IEnumerable methods) { - var method = methods.Where(m => + var method = methods?.Where(m => { var attribute = (ImportAttribute)m.GetCustomAttribute(typeof(ImportAttribute)); if (attribute is null) @@ -88,7 +90,7 @@ private static FunctionBinding BindFunction(FunctionImport import, IEnumerable fields) { - var field = fields.Where(f => + var field = fields?.Where(f => { var attribute = (ImportAttribute)f.GetCustomAttribute(typeof(ImportAttribute)); return attribute.Name == import.Name && @@ -108,7 +110,7 @@ private static GlobalBinding BindGlobal(GlobalImport import, IEnumerable fields) { - var field = fields.Where(f => + var field = fields?.Where(f => { var attribute = (ImportAttribute)f.GetCustomAttribute(typeof(ImportAttribute)); return attribute.Name == import.Name && diff --git a/crates/misc/dotnet/src/Bindings/FunctionBinding.cs b/crates/misc/dotnet/src/Bindings/FunctionBinding.cs index 632fa733da01..5d5d78175039 100644 --- a/crates/misc/dotnet/src/Bindings/FunctionBinding.cs +++ b/crates/misc/dotnet/src/Bindings/FunctionBinding.cs @@ -11,7 +11,7 @@ namespace Wasmtime.Bindings /// /// Represents a host function binding. /// - public class FunctionBinding : Binding + internal class FunctionBinding : Binding { /// /// Constructs a new function binding. @@ -46,23 +46,18 @@ public FunctionBinding(FunctionImport import, MethodInfo method) /// public MethodInfo Method { get; private set; } - internal override SafeHandle Bind(Store store, IHost host) + public override SafeHandle Bind(Store store, IHost host) { unsafe { - if (_callback != null) - { - throw new InvalidOperationException("Cannot bind more than once."); - } - - _callback = CreateCallback(store, host); - var parameters = Interop.ToValueTypeVec(Import.Parameters); var results = Interop.ToValueTypeVec(Import.Results); - using (var funcType = Interop.wasm_functype_new(ref parameters, ref results)) - { - return Interop.wasm_func_new(store.Handle, funcType, _callback); - } + using var funcType = Interop.wasm_functype_new(ref parameters, ref results); + var callback = CreateCallback(store, host); + var func = Interop.wasm_func_new(store.Handle, funcType, callback); + // Store the callback with the safe handle to keep the delegate GC reachable + func.Callback = callback; + return func; } } @@ -70,17 +65,17 @@ private void Validate() { if (Method.IsStatic) { - ThrowBindingException(Import, Method, "method cannot be static"); + throw CreateBindingException(Import, Method, "method cannot be static"); } if (Method.IsGenericMethod) { - ThrowBindingException(Import, Method, "method cannot be generic"); + throw CreateBindingException(Import, Method, "method cannot be generic"); } if (Method.IsConstructor) { - ThrowBindingException(Import, Method, "method cannot be a constructor"); + throw CreateBindingException(Import, Method, "method cannot be a constructor"); } ValidateParameters(); @@ -93,7 +88,7 @@ private void ValidateParameters() var parameters = Method.GetParameters(); if (parameters.Length != Import.Parameters.Count) { - ThrowBindingException( + throw CreateBindingException( Import, Method, $"parameter mismatch: import requires {Import.Parameters.Count} but the method has {parameters.Length}"); @@ -106,18 +101,18 @@ private void ValidateParameters() { if (parameter.IsOut) { - ThrowBindingException(Import, Method, $"parameter '{parameter.Name}' cannot be an 'out' parameter"); + throw CreateBindingException(Import, Method, $"parameter '{parameter.Name}' cannot be an 'out' parameter"); } else { - ThrowBindingException(Import, Method, $"parameter '{parameter.Name}' cannot be a 'ref' parameter"); + throw CreateBindingException(Import, Method, $"parameter '{parameter.Name}' cannot be a 'ref' parameter"); } } var expected = Import.Parameters[i]; if (!Interop.TryGetValueKind(parameter.ParameterType, out var kind) || !Interop.IsMatchingKind(kind, expected)) { - ThrowBindingException(Import, Method, $"method parameter '{parameter.Name}' is expected to be of type '{Interop.ToString(expected)}'"); + throw CreateBindingException(Import, Method, $"method parameter '{parameter.Name}' is expected to be of type '{Interop.ToString(expected)}'"); } } } @@ -129,7 +124,7 @@ private void ValidateReturnType() { if (Method.ReturnType != typeof(void)) { - ThrowBindingException(Import, Method, "method must return void"); + throw CreateBindingException(Import, Method, "method must return void"); } } else if (resultsCount == 1) @@ -137,14 +132,14 @@ private void ValidateReturnType() var expected = Import.Results[0]; if (!Interop.TryGetValueKind(Method.ReturnType, out var kind) || !Interop.IsMatchingKind(kind, expected)) { - ThrowBindingException(Import, Method, $"return type is expected to be '{Interop.ToString(expected)}'"); + throw CreateBindingException(Import, Method, $"return type is expected to be '{Interop.ToString(expected)}'"); } } else { if (!IsTupleOfSize(Method.ReturnType, resultsCount)) { - ThrowBindingException(Import, Method, $"return type is expected to be a tuple of size {resultsCount}"); + throw CreateBindingException(Import, Method, $"return type is expected to be a tuple of size {resultsCount}"); } var typeArguments = @@ -163,7 +158,7 @@ private void ValidateReturnType() var expected = Import.Results[i]; if (!Interop.TryGetValueKind(typeArgument, out var kind) || !Interop.IsMatchingKind(kind, expected)) { - ThrowBindingException(Import, Method, $"return tuple item #{i} is expected to be of type '{Interop.ToString(expected)}'"); + throw CreateBindingException(Import, Method, $"return tuple item #{i} is expected to be of type '{Interop.ToString(expected)}'"); } ++i; @@ -340,7 +335,5 @@ private static unsafe void SetResult(object value, Interop.wasm_val_t* result) throw new NotSupportedException("Unsupported return value type."); } } - - private Interop.WasmFuncCallback _callback; } } diff --git a/crates/misc/dotnet/src/Bindings/GlobalBinding.cs b/crates/misc/dotnet/src/Bindings/GlobalBinding.cs index 33ebeb71c006..da75e8148be4 100644 --- a/crates/misc/dotnet/src/Bindings/GlobalBinding.cs +++ b/crates/misc/dotnet/src/Bindings/GlobalBinding.cs @@ -8,7 +8,7 @@ namespace Wasmtime.Bindings /// /// Represents a host global binding. /// - public class GlobalBinding : Binding + internal class GlobalBinding : Binding { /// /// Constructs a new global binding. @@ -43,12 +43,12 @@ public GlobalBinding(GlobalImport import, FieldInfo field) /// public FieldInfo Field { get; private set; } - internal override SafeHandle Bind(Store store, IHost host) + public override SafeHandle Bind(Store store, IHost host) { unsafe { dynamic global = Field.GetValue(host); - if (global.Handle != null) + if (!(global.Handle is null)) { throw new InvalidOperationException("Cannot bind more than once."); } @@ -59,14 +59,14 @@ internal override SafeHandle Bind(Store store, IHost host) var valueTypeHandle = valueType.DangerousGetHandle(); valueType.SetHandleAsInvalid(); - using (var globalType = Interop.wasm_globaltype_new( + using var globalType = Interop.wasm_globaltype_new( valueTypeHandle, - Import.IsMutable ? Interop.wasm_mutability_t.WASM_VAR : Interop.wasm_mutability_t.WASM_CONST)) - { - var handle = Interop.wasm_global_new(store.Handle, globalType, &v); - global.Handle = handle; - return handle; - } + Import.IsMutable ? Interop.wasm_mutability_t.WASM_VAR : Interop.wasm_mutability_t.WASM_CONST + ); + + var handle = Interop.wasm_global_new(store.Handle, globalType, &v); + global.Handle = handle; + return handle; } } @@ -74,17 +74,17 @@ private void Validate() { if (Field.IsStatic) { - ThrowBindingException(Import, Field, "field cannot be static"); + throw CreateBindingException(Import, Field, "field cannot be static"); } if (!Field.IsInitOnly) { - ThrowBindingException(Import, Field, "field must be readonly"); + throw CreateBindingException(Import, Field, "field must be readonly"); } if (!Field.FieldType.IsGenericType) { - ThrowBindingException(Import, Field, "field is expected to be of type 'Global'"); + throw CreateBindingException(Import, Field, "field is expected to be of type 'Global'"); } var definition = Field.FieldType.GetGenericTypeDefinition(); @@ -92,19 +92,19 @@ private void Validate() { if (Import.IsMutable) { - ThrowBindingException(Import, Field, "the import is mutable (use the 'MutableGlobal' type)"); + throw CreateBindingException(Import, Field, "the import is mutable (use the 'MutableGlobal' type)"); } } else if (definition == typeof(MutableGlobal<>)) { if (!Import.IsMutable) { - ThrowBindingException(Import, Field, "the import is constant (use the 'Global' type)"); + throw CreateBindingException(Import, Field, "the import is constant (use the 'Global' type)"); } } else { - ThrowBindingException(Import, Field, "field is expected to be of type 'Global' or 'MutableGlobal'"); + throw CreateBindingException(Import, Field, "field is expected to be of type 'Global' or 'MutableGlobal'"); } var arg = Field.FieldType.GetGenericArguments()[0]; @@ -113,12 +113,12 @@ private void Validate() { if (!Interop.IsMatchingKind(kind, Import.Kind)) { - ThrowBindingException(Import, Field, $"global type argument is expected to be of type '{Interop.ToString(Import.Kind)}'"); + throw CreateBindingException(Import, Field, $"global type argument is expected to be of type '{Interop.ToString(Import.Kind)}'"); } } else { - ThrowBindingException(Import, Field, $"'{arg}' is not a valid global type"); + throw CreateBindingException(Import, Field, $"'{arg}' is not a valid global type"); } } } diff --git a/crates/misc/dotnet/src/Bindings/MemoryBinding.cs b/crates/misc/dotnet/src/Bindings/MemoryBinding.cs index 790f3c5ddbcc..30e9f18cdec4 100644 --- a/crates/misc/dotnet/src/Bindings/MemoryBinding.cs +++ b/crates/misc/dotnet/src/Bindings/MemoryBinding.cs @@ -8,7 +8,7 @@ namespace Wasmtime.Bindings /// /// Represents a host memory binding. /// - public class MemoryBinding : Binding + internal class MemoryBinding : Binding { /// /// Constructs a new memory binding. @@ -43,10 +43,10 @@ public MemoryBinding(MemoryImport import, FieldInfo field) /// public FieldInfo Field { get; private set; } - internal override SafeHandle Bind(Store store, IHost host) + public override SafeHandle Bind(Store store, IHost host) { Memory memory = (Memory)Field.GetValue(host); - if (memory.Handle != null) + if (!(memory.Handle is null)) { throw new InvalidOperationException("Cannot bind more than once."); } @@ -56,11 +56,11 @@ internal override SafeHandle Bind(Store store, IHost host) if (min != Import.Minimum) { - ThrowBindingException(Import, Field, $"Memory does not have the expected minimum of {Import.Minimum} page(s)"); + throw CreateBindingException(Import, Field, $"Memory does not have the expected minimum of {Import.Minimum} page(s)"); } if (max != Import.Maximum) { - ThrowBindingException(Import, Field, $"Memory does not have the expected maximum of {Import.Maximum} page(s)"); + throw CreateBindingException(Import, Field, $"Memory does not have the expected maximum of {Import.Maximum} page(s)"); } unsafe @@ -68,12 +68,11 @@ internal override SafeHandle Bind(Store store, IHost host) Interop.wasm_limits_t limits = new Interop.wasm_limits_t(); limits.min = min; limits.max = max; - using (var memoryType = Interop.wasm_memorytype_new(&limits)) - { - var handle = Interop.wasm_memory_new(store.Handle, memoryType); - memory.Handle = handle; - return handle; - } + + using var memoryType = Interop.wasm_memorytype_new(&limits); + var handle = Interop.wasm_memory_new(store.Handle, memoryType); + memory.Handle = handle; + return handle; } } @@ -81,17 +80,17 @@ private void Validate() { if (Field.IsStatic) { - ThrowBindingException(Import, Field, "field cannot be static"); + throw CreateBindingException(Import, Field, "field cannot be static"); } if (!Field.IsInitOnly) { - ThrowBindingException(Import, Field, "field must be readonly"); + throw CreateBindingException(Import, Field, "field must be readonly"); } if (Field.FieldType != typeof(Memory)) { - ThrowBindingException(Import, Field, "field is expected to be of type 'Memory'"); + throw CreateBindingException(Import, Field, "field is expected to be of type 'Memory'"); } } } diff --git a/crates/misc/dotnet/src/Bindings/WasiBinding.cs b/crates/misc/dotnet/src/Bindings/WasiBinding.cs new file mode 100644 index 000000000000..4541f808c7e0 --- /dev/null +++ b/crates/misc/dotnet/src/Bindings/WasiBinding.cs @@ -0,0 +1,23 @@ +using System; +using System.Runtime.InteropServices; + +namespace Wasmtime.Bindings +{ + /// + /// Represents a binding to a WASI export. + /// + 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; + } +} diff --git a/crates/misc/dotnet/src/Externs/ExternMemory.cs b/crates/misc/dotnet/src/Externs/ExternMemory.cs index a9011aacb7b7..e86be4f34169 100644 --- a/crates/misc/dotnet/src/Externs/ExternMemory.cs +++ b/crates/misc/dotnet/src/Externs/ExternMemory.cs @@ -63,6 +63,23 @@ public string ReadString(int address, int length) return Encoding.UTF8.GetString(Span.Slice(address, length)); } + /// + /// Reads a null-terminated UTF-8 string from memory. + /// + /// The zero-based address to read from. + /// Returns the string read from memory. + 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)); + } + /// /// Writes a UTF-8 string at the given address. /// diff --git a/crates/misc/dotnet/src/IHost.cs b/crates/misc/dotnet/src/IHost.cs index f8a3f4175f62..1734c2d9f18d 100644 --- a/crates/misc/dotnet/src/IHost.cs +++ b/crates/misc/dotnet/src/IHost.cs @@ -14,12 +14,5 @@ public interface IHost /// /// A host can only bind to one module instance at a time. Instance Instance { get; set; } - - /// - /// Gets the import bindings of the host given a WebAssembly module. - /// - /// The WebAssembly module to get the import bindings for. - /// Returns the list of import bindings for the host. - List GetImportBindings(Module module) => Binding.GetImportBindings(this, module); } } diff --git a/crates/misc/dotnet/src/Imports/Import.cs b/crates/misc/dotnet/src/Imports/Import.cs index f2e811502735..09cf354fd04f 100644 --- a/crates/misc/dotnet/src/Imports/Import.cs +++ b/crates/misc/dotnet/src/Imports/Import.cs @@ -12,10 +12,12 @@ internal Import(IntPtr importType) { unsafe { - var moduleName = Interop.wasm_importtype_module(importType); + Handle = importType; + + var moduleName = Interop.wasm_importtype_module(Handle); ModuleName = Marshal.PtrToStringUTF8((IntPtr)moduleName->data, (int)moduleName->size); - var name = Interop.wasm_importtype_name(importType); + var name = Interop.wasm_importtype_name(Handle); Name = Marshal.PtrToStringUTF8((IntPtr)name->data, (int)name->size); } } @@ -30,6 +32,8 @@ internal Import(IntPtr importType) /// public string Name { get; private set; } + internal IntPtr Handle { get; private set; } + /// public override string ToString() { diff --git a/crates/misc/dotnet/src/Imports/Imports.cs b/crates/misc/dotnet/src/Imports/Imports.cs index 153fee336361..bda7796e587a 100644 --- a/crates/misc/dotnet/src/Imports/Imports.cs +++ b/crates/misc/dotnet/src/Imports/Imports.cs @@ -6,69 +6,72 @@ namespace Wasmtime.Imports /// /// Represents imported functions, globals, tables, and memories to a WebAssembly module. /// - public class Imports + public class Imports : IDisposable { internal Imports(Module module) { Interop.wasm_importtype_vec_t imports; Interop.wasm_module_imports(module.Handle, out imports); - try - { - var all = new List((int)imports.size); - var functions = new List(); - var globals = new List(); - var tables = new List(); - var memories = new List(); + var all = new List((int)imports.size); + var functions = new List(); + var globals = new List(); + var tables = new List(); + var memories = new List(); - for (int i = 0; i < (int)imports.size; ++i) + for (int i = 0; i < (int)imports.size; ++i) + { + unsafe { - unsafe + var importType = imports.data[i]; + var externType = Interop.wasm_importtype_type(importType); + + switch (Interop.wasm_externtype_kind(externType)) { - var importType = imports.data[i]; - var externType = Interop.wasm_importtype_type(importType); - - switch (Interop.wasm_externtype_kind(externType)) - { - case Interop.wasm_externkind_t.WASM_EXTERN_FUNC: - var function = new FunctionImport(importType, externType); - functions.Add(function); - all.Add(function); - break; - - case Interop.wasm_externkind_t.WASM_EXTERN_GLOBAL: - var global = new GlobalImport(importType, externType); - globals.Add(global); - all.Add(global); - break; - - case Interop.wasm_externkind_t.WASM_EXTERN_TABLE: - var table = new TableImport(importType, externType); - tables.Add(table); - all.Add(table); - break; - - case Interop.wasm_externkind_t.WASM_EXTERN_MEMORY: - var memory = new MemoryImport(importType, externType); - memories.Add(memory); - all.Add(memory); - break; - - default: - throw new NotSupportedException("Unsupported import extern type."); - } + case Interop.wasm_externkind_t.WASM_EXTERN_FUNC: + var function = new FunctionImport(importType, externType); + functions.Add(function); + all.Add(function); + break; + + case Interop.wasm_externkind_t.WASM_EXTERN_GLOBAL: + var global = new GlobalImport(importType, externType); + globals.Add(global); + all.Add(global); + break; + + case Interop.wasm_externkind_t.WASM_EXTERN_TABLE: + var table = new TableImport(importType, externType); + tables.Add(table); + all.Add(table); + break; + + case Interop.wasm_externkind_t.WASM_EXTERN_MEMORY: + var memory = new MemoryImport(importType, externType); + memories.Add(memory); + all.Add(memory); + break; + + 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; + } + + /// + 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 @@ internal Imports(Module module) public IReadOnlyList Memories { get; private set; } internal IReadOnlyList All { get; private set; } + + private Interop.wasm_importtype_vec_t _imports; } } diff --git a/crates/misc/dotnet/src/Instance.cs b/crates/misc/dotnet/src/Instance.cs index 86f0b3f825e4..f274101d0877 100644 --- a/crates/misc/dotnet/src/Instance.cs +++ b/crates/misc/dotnet/src/Instance.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; using System.Dynamic; using Wasmtime.Externs; +using Wasmtime.Bindings; namespace Wasmtime { @@ -12,22 +13,23 @@ namespace Wasmtime /// public class Instance : DynamicObject, IDisposable { - internal Instance(Module module, IHost host) + internal Instance(Module module, Wasi wasi = null, IHost host = null) { Host = host; Module = module; - //Save the bindings to root the objects. - //Otherwise the GC may collect the delegates from ExternFunction for example. - _bindings = host.GetImportBindings(module); - var handles = _bindings.Select(b => b.Bind(module.Store, host)).ToList(); + // Save the bindings to root the objects. + // Otherwise the GC may collect the callback delegates from FunctionHandles for example. + _bindings = Binding.GetImportBindings(module, wasi, host) + .Select(b => b.Bind(module.Store, host)) + .ToArray(); unsafe { Handle = Interop.wasm_instance_new( Module.Store.Handle, Module.Handle, - handles.Select(h => ToExtern(h)).ToArray(), + _bindings.Select(h => ToExtern(h)).ToArray(), out var trap); if (trap != IntPtr.Zero) @@ -41,12 +43,6 @@ internal Instance(Module module, IHost host) throw new WasmtimeException($"Failed to instantiate module '{module.Name}'."); } - // Dispose of all function handles (not needed at runtime) - foreach (var h in handles.Where(h => h is Interop.FunctionHandle)) - { - h.Dispose(); - } - Interop.wasm_instance_exports(Handle, out _externs); Externs = new Wasmtime.Externs.Externs(Module.Exports, _externs); @@ -71,17 +67,27 @@ internal Instance(Module module, IHost host) public Wasmtime.Externs.Externs Externs { get; private set; } /// - public void Dispose() + public unsafe void Dispose() { if (!Handle.IsInvalid) { Handle.Dispose(); Handle.SetHandleAsInvalid(); } - if (_externs.size != UIntPtr.Zero) + + if (!(_bindings is null)) + { + foreach (var binding in _bindings) + { + binding.Dispose(); + } + _bindings = null; + } + + if (!(_externs.data is null)) { Interop.wasm_extern_vec_delete(ref _externs); - _externs.size = UIntPtr.Zero; + _externs.data = null; } } @@ -134,15 +140,18 @@ private static unsafe IntPtr ToExtern(SafeHandle handle) case Interop.MemoryHandle m: return Interop.wasm_memory_as_extern(m); + case Interop.WasiExportHandle w: + return w.DangerousGetHandle(); + default: throw new NotSupportedException("Unexpected handle type."); } } internal Interop.InstanceHandle Handle { get; private set; } + private SafeHandle[] _bindings; private Interop.wasm_extern_vec_t _externs; private Dictionary _functions; private Dictionary _globals; - private List _bindings; } } diff --git a/crates/misc/dotnet/src/Interop.cs b/crates/misc/dotnet/src/Interop.cs index a7d979617529..727acb19d3a4 100644 --- a/crates/misc/dotnet/src/Interop.cs +++ b/crates/misc/dotnet/src/Interop.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; +using System.Text; namespace Wasmtime { @@ -10,6 +11,8 @@ namespace Wasmtime /// See https://github.com/WebAssembly/wasm-c-api/blob/master/include/wasm.h for the C API reference. internal static class Interop { + const string LibraryName = "wasmtime"; + internal class EngineHandle : SafeHandle { public EngineHandle() : base(IntPtr.Zero, true) @@ -61,6 +64,8 @@ public FunctionHandle() : base(IntPtr.Zero, true) { } + public WasmFuncCallback Callback { get; set; } = null; + public override bool IsInvalid => handle == IntPtr.Zero; protected override bool ReleaseHandle() @@ -175,6 +180,51 @@ protected override bool ReleaseHandle() } } + internal class WasiConfigHandle : SafeHandle + { + public WasiConfigHandle() : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + Interop.wasi_config_delete(handle); + return true; + } + } + + internal class WasiInstanceHandle : SafeHandle + { + public WasiInstanceHandle() : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + Interop.wasi_instance_delete(handle); + return true; + } + } + + internal class WasiExportHandle : SafeHandle + { + public WasiExportHandle(IntPtr handle) : base(IntPtr.Zero, false /* not owned */) + { + SetHandle(handle); + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + return true; + } + } + [StructLayout(LayoutKind.Sequential)] internal unsafe struct wasm_byte_vec_t { @@ -440,323 +490,415 @@ internal static Interop.wasm_valtype_vec_t ToValueTypeVec(IReadOnlyList strings) + { + // Unfortunately .NET cannot currently marshal string[] as UTF-8 + // See: https://github.com/dotnet/runtime/issues/7315 + // Therefore, we need to marshal the strings manually + var handles = new GCHandle[strings.Count]; + var ptrs = new byte*[strings.Count]; + for (int i = 0; i < strings.Count; ++i) + { + handles[i] = GCHandle.Alloc( + Encoding.UTF8.GetBytes(strings[i] + '\0'), + GCHandleType.Pinned + ); + ptrs[i] = (byte*)handles[i].AddrOfPinnedObject(); + } + + return (ptrs, handles); + } + // Engine imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern EngineHandle wasm_engine_new(); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_engine_delete(IntPtr engine); // Store imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern StoreHandle wasm_store_new(EngineHandle engine); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_store_delete(IntPtr engine); // Byte vec imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_byte_vec_new_empty(out wasm_byte_vec_t vec); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_byte_vec_new_uninitialized(out wasm_byte_vec_t vec, UIntPtr length); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_byte_vec_new(out wasm_byte_vec_t vec, UIntPtr length, byte[] data); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_byte_vec_copy(out wasm_byte_vec_t vec, ref wasm_byte_vec_t src); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_byte_vec_delete(ref wasm_byte_vec_t vec); // Value type vec imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_valtype_vec_new_empty(out wasm_valtype_vec_t vec); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_valtype_vec_new_uninitialized(out wasm_valtype_vec_t vec, UIntPtr length); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_valtype_vec_new(out wasm_valtype_vec_t vec, UIntPtr length, IntPtr[] data); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_valtype_vec_copy(out wasm_valtype_vec_t vec, ref wasm_valtype_vec_t src); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_valtype_vec_delete(ref wasm_valtype_vec_t vec); // Extern vec imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_extern_vec_new_empty(out wasm_extern_vec_t vec); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_extern_vec_new_uninitialized(out wasm_extern_vec_t vec, UIntPtr length); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_extern_vec_new(out wasm_extern_vec_t vec, UIntPtr length, IntPtr[] data); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_extern_vec_copy(out wasm_extern_vec_t vec, ref wasm_extern_vec_t src); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_extern_vec_delete(ref wasm_extern_vec_t vec); // Import type vec imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_importtype_vec_new_empty(out wasm_importtype_vec_t vec); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_importtype_vec_new_uninitialized(out wasm_importtype_vec_t vec, UIntPtr length); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_importtype_vec_new(out wasm_importtype_vec_t vec, UIntPtr length, IntPtr[] data); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_importtype_vec_copy(out wasm_importtype_vec_t vec, ref wasm_importtype_vec_t src); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_importtype_vec_delete(ref wasm_importtype_vec_t vec); // Export type vec imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_exporttype_vec_new_empty(out wasm_exporttype_vec_t vec); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_exporttype_vec_new_uninitialized(out wasm_exporttype_vec_t vec, UIntPtr length); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_exporttype_vec_new(out wasm_exporttype_vec_t vec, UIntPtr length, IntPtr[] data); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_exporttype_vec_copy(out wasm_exporttype_vec_t vec, ref wasm_exporttype_vec_t src); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_exporttype_vec_delete(ref wasm_exporttype_vec_t vec); // Import type imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern unsafe wasm_byte_vec_t* wasm_importtype_module(IntPtr importType); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern unsafe wasm_byte_vec_t* wasm_importtype_name(IntPtr importType); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern unsafe IntPtr wasm_importtype_type(IntPtr importType); // Export type imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern unsafe wasm_byte_vec_t* wasm_exporttype_name(IntPtr exportType); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern unsafe IntPtr wasm_exporttype_type(IntPtr exportType); // Module imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern ModuleHandle wasm_module_new(StoreHandle store, ref wasm_byte_vec_t bytes); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_module_imports(ModuleHandle module, out wasm_importtype_vec_t imports); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_module_exports(ModuleHandle module, out wasm_exporttype_vec_t exports); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_module_delete(IntPtr module); // Value type imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern ValueTypeHandle wasm_valtype_new(wasm_valkind_t kind); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_valtype_delete(IntPtr valueType); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern ValueKind wasm_valtype_kind(IntPtr valueType); // Extern imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern wasm_externkind_t wasm_extern_kind(IntPtr ext); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_extern_type(IntPtr ext); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_extern_as_func(IntPtr ext); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_extern_as_global(IntPtr ext); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_extern_as_table(IntPtr ext); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_extern_as_memory(IntPtr ext); // Extern type imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern wasm_externkind_t wasm_externtype_kind(IntPtr externType); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_externtype_as_functype_const(IntPtr externType); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_externtype_as_globaltype_const(IntPtr externType); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_externtype_as_tabletype_const(IntPtr externType); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_externtype_as_memorytype_const(IntPtr externType); // Function imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern FunctionHandle wasm_func_new(StoreHandle store, FuncTypeHandle type, WasmFuncCallback callback); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_func_delete(IntPtr function); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static unsafe extern IntPtr wasm_func_call(IntPtr function, wasm_val_t* args, wasm_val_t* results); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_func_as_extern(FunctionHandle function); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_global_as_extern(GlobalHandle global); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_memory_as_extern(MemoryHandle memory); // Function type imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern unsafe wasm_valtype_vec_t* wasm_functype_params(IntPtr funcType); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern unsafe wasm_valtype_vec_t* wasm_functype_results(IntPtr funcType); // Instance imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern unsafe InstanceHandle wasm_instance_new(StoreHandle store, ModuleHandle module, IntPtr[] imports, out IntPtr trap); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_instance_delete(IntPtr ext); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_instance_exports(InstanceHandle instance, out wasm_extern_vec_t exports); // Function type imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern FuncTypeHandle wasm_functype_new(ref wasm_valtype_vec_t parameters, ref wasm_valtype_vec_t results); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_functype_delete(IntPtr functype); // Global type imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern GlobalTypeHandle wasm_globaltype_new(IntPtr valueType, wasm_mutability_t mutability); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_globaltype_delete(IntPtr globalType); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_globaltype_content(IntPtr globalType); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern wasm_mutability_t wasm_globaltype_mutability(IntPtr globalType); // Memory type imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern unsafe MemoryTypeHandle wasm_memorytype_new(wasm_limits_t* limits); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_memorytype_delete(IntPtr memoryType); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern unsafe wasm_limits_t* wasm_memorytype_limits(MemoryTypeHandle memoryType); // Trap imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_trap_new(StoreHandle store, ref wasm_byte_vec_t message); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_trap_delete(IntPtr trap); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_trap_message(IntPtr trap, out wasm_byte_vec_t message); // Table type imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_tabletype_element(IntPtr tableType); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static unsafe extern wasm_limits_t* wasm_tabletype_limits(IntPtr tableType); // Memory type imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static unsafe extern wasm_limits_t* wasm_memorytype_limits(IntPtr memoryType); // Global imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static unsafe extern GlobalHandle wasm_global_new(StoreHandle handle, GlobalTypeHandle globalType, wasm_val_t* initialValue); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_global_delete(IntPtr global); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_global_type(IntPtr global); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static unsafe extern void wasm_global_get(IntPtr global, wasm_val_t* value); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static unsafe extern void wasm_global_set(IntPtr global, wasm_val_t* value); // Memory imports - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern MemoryHandle wasm_memory_new(StoreHandle handle, MemoryTypeHandle memoryType); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern void wasm_memory_delete(IntPtr memory); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern IntPtr wasm_memory_type(MemoryHandle memory); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static unsafe extern byte* wasm_memory_data(IntPtr memory); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern UIntPtr wasm_memory_data_size(IntPtr memory); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern uint wasm_memory_size(MemoryHandle memory); - [DllImport("wasmtime")] + [DllImport(LibraryName)] public static extern bool wasm_memory_grow(MemoryHandle memory, uint delta); + + // WASI config + + [DllImport(LibraryName)] + public static extern WasiConfigHandle wasi_config_new(); + + [DllImport(LibraryName)] + public static extern void wasi_config_delete(IntPtr config); + + [DllImport(LibraryName)] + public unsafe static extern void wasi_config_set_argv(WasiConfigHandle config, int argc, byte*[] argv); + + [DllImport(LibraryName)] + public static extern void wasi_config_inherit_argv(WasiConfigHandle config); + + [DllImport(LibraryName)] + public static extern unsafe void wasi_config_set_env( + WasiConfigHandle config, + int envc, + byte*[] names, + byte*[] values + ); + + [DllImport(LibraryName)] + public static extern void wasi_config_inherit_env(WasiConfigHandle config); + + [DllImport(LibraryName)] + public static extern bool wasi_config_set_stdin_file( + WasiConfigHandle config, + [MarshalAs(UnmanagedType.LPUTF8Str)] string path + ); + + [DllImport(LibraryName)] + public static extern void wasi_config_inherit_stdin(WasiConfigHandle config); + + [DllImport(LibraryName)] + public static extern bool wasi_config_set_stdout_file( + WasiConfigHandle config, + [MarshalAs(UnmanagedType.LPUTF8Str)] string path + ); + + [DllImport(LibraryName)] + public static extern void wasi_config_inherit_stdout(WasiConfigHandle config); + + [DllImport(LibraryName)] + public static extern bool wasi_config_set_stderr_file( + WasiConfigHandle config, + [MarshalAs(UnmanagedType.LPUTF8Str)] string path + ); + + [DllImport(LibraryName)] + public static extern void wasi_config_inherit_stderr(WasiConfigHandle config); + + [DllImport(LibraryName)] + public static extern bool wasi_config_preopen_dir( + WasiConfigHandle config, + [MarshalAs(UnmanagedType.LPUTF8Str)] string path, + [MarshalAs(UnmanagedType.LPUTF8Str)] string guestPath + ); + + // WASI instance + [DllImport(LibraryName)] + public static extern WasiInstanceHandle wasi_instance_new( + StoreHandle store, + WasiConfigHandle config, + out IntPtr trap + ); + + [DllImport(LibraryName)] + public static extern void wasi_instance_delete(IntPtr instance); + + [DllImport(LibraryName)] + public static extern IntPtr wasi_instance_bind_import(WasiInstanceHandle instance, IntPtr importType); } } diff --git a/crates/misc/dotnet/src/Module.cs b/crates/misc/dotnet/src/Module.cs index c1f0dda6c389..203e5f7a6af0 100644 --- a/crates/misc/dotnet/src/Module.cs +++ b/crates/misc/dotnet/src/Module.cs @@ -49,20 +49,33 @@ internal Module(Store store, string name, byte[] bytes) /// /// The host to use for the WebAssembly module's instance. /// Returns a new . - public Instance Instantiate(IHost host) + public Instance Instantiate(IHost host = null) { - if (host is null) + return Instantiate(null, host); + } + + /// + /// Instantiates a WebAssembly module for the given host. + /// + /// The WASI instance to use for WASI imports. + /// The host to use for the WebAssembly module's instance. + /// Returns a new . + public Instance Instantiate(Wasi wasi, IHost host = null) + { + if (!(host?.Instance is null)) { - throw new ArgumentNullException(nameof(host)); + throw new InvalidOperationException("The host has already been associated with an instantiated module."); } - if (host.Instance != null) + var instance = new Instance(this, wasi, host); + + if (!(host is null)) { - throw new InvalidOperationException("The host has already been associated with an instantiated module."); + host.Instance = instance; + return instance; } - host.Instance = new Instance(this, host); - return host.Instance; + return instance; } /// @@ -94,6 +107,11 @@ public void Dispose() Handle.Dispose(); Handle.SetHandleAsInvalid(); } + if (!(Imports is null)) + { + Imports.Dispose(); + Imports = null; + } } internal Interop.ModuleHandle Handle { get; private set; } diff --git a/crates/misc/dotnet/src/TrapException.cs b/crates/misc/dotnet/src/TrapException.cs index 2ed7df8e760c..7446ab6a3d87 100644 --- a/crates/misc/dotnet/src/TrapException.cs +++ b/crates/misc/dotnet/src/TrapException.cs @@ -29,9 +29,11 @@ internal static TrapException FromOwnedTrap(IntPtr trap) Interop.wasm_trap_message(trap, out var bytes); var byteSpan = new ReadOnlySpan(bytes.data, checked((int)bytes.size)); - int indexOfNull = byteSpan.IndexOf((byte)0); + int indexOfNull = byteSpan.LastIndexOf((byte)0); if (indexOfNull != -1) + { byteSpan = byteSpan.Slice(0, indexOfNull); + } var message = Encoding.UTF8.GetString(byteSpan); Interop.wasm_byte_vec_delete(ref bytes); diff --git a/crates/misc/dotnet/src/Wasi.cs b/crates/misc/dotnet/src/Wasi.cs new file mode 100644 index 000000000000..e16feaf58b48 --- /dev/null +++ b/crates/misc/dotnet/src/Wasi.cs @@ -0,0 +1,44 @@ +using System; +using Wasmtime.Bindings; +using Wasmtime.Imports; + +namespace Wasmtime +{ + public class Wasi + { + /// + /// Creates a default instance. + /// + 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; } + } +} diff --git a/crates/misc/dotnet/src/WasiBuilder.cs b/crates/misc/dotnet/src/WasiBuilder.cs new file mode 100644 index 000000000000..cee85c258921 --- /dev/null +++ b/crates/misc/dotnet/src/WasiBuilder.cs @@ -0,0 +1,409 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Wasmtime +{ + /// + /// Represents a build of WASI instances. + /// + public class WasiBuilder + { + /// + /// Constructs a new . + /// + public WasiBuilder() + { + } + + /// + /// Adds a command line argument to the builder. + /// + /// The command line argument to add. + /// Returns the current builder. + 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; + } + + /// + /// Adds multiple command line arguments to the builder. + /// + /// The command line arguments to add. + /// Returns the current builder. + public WasiBuilder WithArgs(IEnumerable 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; + } + + /// + /// Adds multiple command line arguments to the builder. + /// + /// The command line arguments to add. + /// Returns the current builder. + public WasiBuilder WithArgs(params string[] args) + { + return WithArgs((IEnumerable)args); + } + + /// + /// Sets the builder to inherit command line arguments. + /// + /// Any explicitly specified command line arguments will be removed. + /// Returns the current builder. + public WasiBuilder WithInheritedArgs() + { + _inheritArgs = true; + _args.Clear(); + _args.AddRange(Environment.GetCommandLineArgs()); + return this; + } + + /// + /// Adds an environment variable to the builder. + /// + /// The name of the environment variable. + /// The value of the environment variable. + /// Returns the current builder. + 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; + } + + /// + /// Adds multiple environment variables to the builder. + /// + /// The name-value tuples of the environment variables to add. + /// Returns the current builder. + 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; + } + + /// + /// Sets the builder to inherit environment variables. + /// + /// Any explicitly specified environment variables will be removed. + /// Returns the current builder. + public WasiBuilder WithInheritedEnvironment() + { + _inheritEnv = true; + _vars.Clear(); + return this; + } + + /// + /// Sets the builder to use the given file path as stdin. + /// + /// The file to use as stdin. + /// Returns the current builder. + 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; + } + + /// + /// Sets the builder to inherit stdin. + /// + /// Any explicitly specified stdin file will be removed. + /// Returns the current builder. + public WasiBuilder WithInheritedStandardInput() + { + _inheritStandardInput = true; + _standardInputPath = null; + return this; + } + + /// + /// Sets the builder to use the given file path as stdout. + /// + /// The file to use as stdout. + /// Returns the current builder. + 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; + } + + /// + /// Sets the builder to inherit stdout. + /// + /// Any explicitly specified stdout file will be removed. + /// Returns the current builder. + public WasiBuilder WithInheritedStandardOutput() + { + _inheritStandardOutput = true; + _standardOutputPath = null; + return this; + } + + /// + /// Sets the builder to use the given file path as stderr. + /// + /// The file to use as stderr. + /// Returns the current builder. + 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; + } + + /// + /// Sets the builder to inherit stderr. + /// + /// Any explicitly specified stderr file will be removed. + /// Returns the current builder. + public WasiBuilder WithInheritedStandardError() + { + _inheritStandardError = true; + _standardErrorPath = null; + return this; + } + + /// + /// Adds a preopen directory to the builder. + /// + /// The path to the directory to add. + /// The path the guest will use to open the directory. + /// Returns the current builder. + 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; + } + + /// + /// Builds the instance. + /// + /// The to use. + /// Returns the new instance. + 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 _args = new List(); + 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; + } +} diff --git a/crates/misc/dotnet/tests/Fixtures/ModuleFixture.cs b/crates/misc/dotnet/tests/Fixtures/ModuleFixture.cs index 91bd52bbadb2..8c140700c1ad 100644 --- a/crates/misc/dotnet/tests/Fixtures/ModuleFixture.cs +++ b/crates/misc/dotnet/tests/Fixtures/ModuleFixture.cs @@ -15,19 +15,19 @@ public ModuleFixture() public void Dispose() { - if (Module != null) + if (!(Module is null)) { Module.Dispose(); Module = null; } - if (Store != null) + if (!(Store is null)) { Store.Dispose(); Store = null; } - if (Engine != null) + if (!(Engine is null)) { Engine.Dispose(); Engine = null; diff --git a/crates/misc/dotnet/tests/FunctionThunkingTests.cs b/crates/misc/dotnet/tests/FunctionThunkingTests.cs index dc7af0bd3b14..97eba2a29924 100644 --- a/crates/misc/dotnet/tests/FunctionThunkingTests.cs +++ b/crates/misc/dotnet/tests/FunctionThunkingTests.cs @@ -12,7 +12,7 @@ public class FunctionThunkingFixture : ModuleFixture public class FunctionThunkingTests : IClassFixture { - const string THROW_MESSAGE = "Test error messages for wasmtime dotnet bidnings unit tests."; + const string THROW_MESSAGE = "Test error message for wasmtime dotnet unit tests."; class MyHost : IHost { @@ -36,36 +36,34 @@ public FunctionThunkingTests(FunctionThunkingFixture fixture) public void ItBindsImportMethodsAndCallsThemCorrectly() { var host = new MyHost(); - using (var instance = Fixture.Module.Instantiate(host)) - { - var add_func = instance.Externs.Functions.Where(f => f.Name == "add_wrapper").Single(); - int invoke_add(int x, int y) => (int)add_func.Invoke(new object[] { x, y }); + using var instance = Fixture.Module.Instantiate(host); - invoke_add(40, 2).Should().Be(42); - invoke_add(22, 5).Should().Be(27); + var add_func = instance.Externs.Functions.Where(f => f.Name == "add_wrapper").Single(); + int invoke_add(int x, int y) => (int)add_func.Invoke(new object[] { x, y }); - //Collect garbage to make sure delegate function pointers pasted to wasmtime are rooted. - GC.Collect(); - GC.WaitForPendingFinalizers(); + invoke_add(40, 2).Should().Be(42); + invoke_add(22, 5).Should().Be(27); - invoke_add(1970, 50).Should().Be(2020); - } + //Collect garbage to make sure delegate function pointers pasted to wasmtime are rooted. + GC.Collect(); + GC.WaitForPendingFinalizers(); + + invoke_add(1970, 50).Should().Be(2020); } [Fact] - public void ItPropegatesExceptionsToCallersViaTraps() + public void ItPropagatesExceptionsToCallersViaTraps() { var host = new MyHost(); - using (var instance = Fixture.Module.Instantiate(host)) - { - var throw_func = instance.Externs.Functions.Where(f => f.Name == "do_throw_wrapper").Single(); - Action action = () => throw_func.Invoke(); + using var instance = Fixture.Module.Instantiate(host); + + var throw_func = instance.Externs.Functions.Where(f => f.Name == "do_throw_wrapper").Single(); + Action action = () => throw_func.Invoke(); - action - .Should() - .Throw() - .WithMessage(THROW_MESSAGE); - } + action + .Should() + .Throw() + .WithMessage(THROW_MESSAGE); } } } diff --git a/crates/misc/dotnet/tests/GlobalExportsTests.cs b/crates/misc/dotnet/tests/GlobalExportsTests.cs index 63b6250679ce..ca143f7286c4 100644 --- a/crates/misc/dotnet/tests/GlobalExportsTests.cs +++ b/crates/misc/dotnet/tests/GlobalExportsTests.cs @@ -46,124 +46,123 @@ public void ItHasTheExpectedNumberOfExportedGlobals() [Fact] public void ItCreatesExternsForTheGlobals() { - using (var instance = Fixture.Module.Instantiate(new Host())) - { - dynamic dyn = instance; - var globals = instance.Externs.Globals; - globals.Count.Should().Be(8); - - var i32 = globals[0]; - i32.Name.Should().Be("global_i32"); - i32.Kind.Should().Be(ValueKind.Int32); - i32.IsMutable.Should().Be(false); - i32.Value.Should().Be(0); - - var i32Mut = globals[1]; - i32Mut.Name.Should().Be("global_i32_mut"); - i32Mut.Kind.Should().Be(ValueKind.Int32); - i32Mut.IsMutable.Should().Be(true); - i32Mut.Value.Should().Be(1); - i32Mut.Value = 11; - i32Mut.Value.Should().Be(11); - dyn.global_i32_mut = 12; - ((int)dyn.global_i32_mut).Should().Be(12); - i32Mut.Value.Should().Be(12); - - var i64 = globals[2]; - i64.Name.Should().Be("global_i64"); - i64.Kind.Should().Be(ValueKind.Int64); - i64.IsMutable.Should().Be(false); - i64.Value.Should().Be(2); - - var i64Mut = globals[3]; - i64Mut.Name.Should().Be("global_i64_mut"); - i64Mut.Kind.Should().Be(ValueKind.Int64); - i64Mut.IsMutable.Should().Be(true); - i64Mut.Value.Should().Be(3); - i64Mut.Value = 13; - i64Mut.Value.Should().Be(13); - dyn.global_i64_mut = 14; - ((long)dyn.global_i64_mut).Should().Be(14); - i64Mut.Value.Should().Be(14); - - var f32 = globals[4]; - f32.Name.Should().Be("global_f32"); - f32.Kind.Should().Be(ValueKind.Float32); - f32.IsMutable.Should().Be(false); - f32.Value.Should().Be(4); - - var f32Mut = globals[5]; - f32Mut.Name.Should().Be("global_f32_mut"); - f32Mut.Kind.Should().Be(ValueKind.Float32); - f32Mut.IsMutable.Should().Be(true); - f32Mut.Value.Should().Be(5); - f32Mut.Value = 15; - f32Mut.Value.Should().Be(15); - dyn.global_f32_mut = 16; - ((float)dyn.global_f32_mut).Should().Be(16); - f32Mut.Value.Should().Be(16); - - var f64 = globals[6]; - f64.Name.Should().Be("global_f64"); - f64.Kind.Should().Be(ValueKind.Float64); - f64.IsMutable.Should().Be(false); - f64.Value.Should().Be(6); - - var f64Mut = globals[7]; - f64Mut.Name.Should().Be("global_f64_mut"); - f64Mut.Kind.Should().Be(ValueKind.Float64); - f64Mut.IsMutable.Should().Be(true); - f64Mut.Value.Should().Be(7); - f64Mut.Value = 17; - f64Mut.Value.Should().Be(17); - dyn.global_f64_mut = 17; - ((double)dyn.global_f64_mut).Should().Be(17); - f64Mut.Value.Should().Be(17); - - Action action = () => i32.Value = 0; - action - .Should() - .Throw() - .WithMessage("The value of global 'global_i32' cannot be modified."); - action = () => dyn.global_i32 = 0; - action - .Should() - .Throw() - .WithMessage("The value of global 'global_i32' cannot be modified."); - - action = () => i64.Value = 0; - action - .Should() - .Throw() - .WithMessage("The value of global 'global_i64' cannot be modified."); - action = () => dyn.global_i64 = 0; - action - .Should() - .Throw() - .WithMessage("The value of global 'global_i64' cannot be modified."); - - action = () => f32.Value = 0; - action - .Should() - .Throw() - .WithMessage("The value of global 'global_f32' cannot be modified."); - action = () => dyn.global_f32 = 0; - action - .Should() - .Throw() - .WithMessage("The value of global 'global_f32' cannot be modified."); - - action = () => f64.Value = 0; - action - .Should() - .Throw() - .WithMessage("The value of global 'global_f64' cannot be modified."); - action = () => dyn.global_f64 = 0; - action - .Should() - .Throw() - .WithMessage("The value of global 'global_f64' cannot be modified."); - } + 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]; + i32.Name.Should().Be("global_i32"); + i32.Kind.Should().Be(ValueKind.Int32); + i32.IsMutable.Should().Be(false); + i32.Value.Should().Be(0); + + var i32Mut = globals[1]; + i32Mut.Name.Should().Be("global_i32_mut"); + i32Mut.Kind.Should().Be(ValueKind.Int32); + i32Mut.IsMutable.Should().Be(true); + i32Mut.Value.Should().Be(1); + i32Mut.Value = 11; + i32Mut.Value.Should().Be(11); + dyn.global_i32_mut = 12; + ((int)dyn.global_i32_mut).Should().Be(12); + i32Mut.Value.Should().Be(12); + + var i64 = globals[2]; + i64.Name.Should().Be("global_i64"); + i64.Kind.Should().Be(ValueKind.Int64); + i64.IsMutable.Should().Be(false); + i64.Value.Should().Be(2); + + var i64Mut = globals[3]; + i64Mut.Name.Should().Be("global_i64_mut"); + i64Mut.Kind.Should().Be(ValueKind.Int64); + i64Mut.IsMutable.Should().Be(true); + i64Mut.Value.Should().Be(3); + i64Mut.Value = 13; + i64Mut.Value.Should().Be(13); + dyn.global_i64_mut = 14; + ((long)dyn.global_i64_mut).Should().Be(14); + i64Mut.Value.Should().Be(14); + + var f32 = globals[4]; + f32.Name.Should().Be("global_f32"); + f32.Kind.Should().Be(ValueKind.Float32); + f32.IsMutable.Should().Be(false); + f32.Value.Should().Be(4); + + var f32Mut = globals[5]; + f32Mut.Name.Should().Be("global_f32_mut"); + f32Mut.Kind.Should().Be(ValueKind.Float32); + f32Mut.IsMutable.Should().Be(true); + f32Mut.Value.Should().Be(5); + f32Mut.Value = 15; + f32Mut.Value.Should().Be(15); + dyn.global_f32_mut = 16; + ((float)dyn.global_f32_mut).Should().Be(16); + f32Mut.Value.Should().Be(16); + + var f64 = globals[6]; + f64.Name.Should().Be("global_f64"); + f64.Kind.Should().Be(ValueKind.Float64); + f64.IsMutable.Should().Be(false); + f64.Value.Should().Be(6); + + var f64Mut = globals[7]; + f64Mut.Name.Should().Be("global_f64_mut"); + f64Mut.Kind.Should().Be(ValueKind.Float64); + f64Mut.IsMutable.Should().Be(true); + f64Mut.Value.Should().Be(7); + f64Mut.Value = 17; + f64Mut.Value.Should().Be(17); + dyn.global_f64_mut = 17; + ((double)dyn.global_f64_mut).Should().Be(17); + f64Mut.Value.Should().Be(17); + + Action action = () => i32.Value = 0; + action + .Should() + .Throw() + .WithMessage("The value of global 'global_i32' cannot be modified."); + action = () => dyn.global_i32 = 0; + action + .Should() + .Throw() + .WithMessage("The value of global 'global_i32' cannot be modified."); + + action = () => i64.Value = 0; + action + .Should() + .Throw() + .WithMessage("The value of global 'global_i64' cannot be modified."); + action = () => dyn.global_i64 = 0; + action + .Should() + .Throw() + .WithMessage("The value of global 'global_i64' cannot be modified."); + + action = () => f32.Value = 0; + action + .Should() + .Throw() + .WithMessage("The value of global 'global_f32' cannot be modified."); + action = () => dyn.global_f32 = 0; + action + .Should() + .Throw() + .WithMessage("The value of global 'global_f32' cannot be modified."); + + action = () => f64.Value = 0; + action + .Should() + .Throw() + .WithMessage("The value of global 'global_f64' cannot be modified."); + action = () => dyn.global_f64 = 0; + action + .Should() + .Throw() + .WithMessage("The value of global 'global_f64' cannot be modified."); } public static IEnumerable GetGlobalExports() diff --git a/crates/misc/dotnet/tests/GlobalImportBindingTests.cs b/crates/misc/dotnet/tests/GlobalImportBindingTests.cs index f6b92d6e76f1..7969a90641fe 100644 --- a/crates/misc/dotnet/tests/GlobalImportBindingTests.cs +++ b/crates/misc/dotnet/tests/GlobalImportBindingTests.cs @@ -118,7 +118,7 @@ public GlobalImportBindingTests(GlobalImportBindingFixture fixture) [Fact] public void ItFailsToInstantiateWithMissingImport() { - Action action = () => { using (var instance = Fixture.Module.Instantiate(new NoImportsHost())) { } }; + Action action = () => { using var instance = Fixture.Module.Instantiate(new NoImportsHost()); }; action .Should() @@ -129,7 +129,7 @@ public void ItFailsToInstantiateWithMissingImport() [Fact] public void ItFailsToInstantiateWithStaticField() { - Action action = () => { using (var instance = Fixture.Module.Instantiate(new GlobalIsStaticHost())) { } }; + Action action = () => { using var instance = Fixture.Module.Instantiate(new GlobalIsStaticHost()); }; action .Should() @@ -140,7 +140,7 @@ public void ItFailsToInstantiateWithStaticField() [Fact] public void ItFailsToInstantiateWithNonReadOnlyField() { - Action action = () => { using (var instance = Fixture.Module.Instantiate(new GlobalIsNotReadOnlyHost())) { } }; + Action action = () => { using var instance = Fixture.Module.Instantiate(new GlobalIsNotReadOnlyHost()); }; action .Should() @@ -151,7 +151,7 @@ public void ItFailsToInstantiateWithNonReadOnlyField() [Fact] public void ItFailsToInstantiateWithInvalidType() { - Action action = () => { using (var instance = Fixture.Module.Instantiate(new NotAGlobalHost())) { } }; + Action action = () => { using var instance = Fixture.Module.Instantiate(new NotAGlobalHost()); }; action .Should() @@ -162,7 +162,7 @@ public void ItFailsToInstantiateWithInvalidType() [Fact] public void ItFailsToInstantiateWithInvalidGlobalType() { - Action action = () => { using (var instance = Fixture.Module.Instantiate(new NotAValidGlobalTypeHost())) { } }; + Action action = () => { using var instance = Fixture.Module.Instantiate(new NotAValidGlobalTypeHost()); }; action .Should() @@ -173,7 +173,7 @@ public void ItFailsToInstantiateWithInvalidGlobalType() [Fact] public void ItFailsToInstantiateWithGlobalTypeMismatch() { - Action action = () => { using (var instance = Fixture.Module.Instantiate(new TypeMismatchHost())) { } }; + Action action = () => { using var instance = Fixture.Module.Instantiate(new TypeMismatchHost()); }; action .Should() @@ -184,7 +184,7 @@ public void ItFailsToInstantiateWithGlobalTypeMismatch() [Fact] public void ItFailsToInstantiateWhenGlobalIsNotMut() { - Action action = () => { using (var instance = Fixture.Module.Instantiate(new NotMutHost())) { } }; + Action action = () => { using var instance = Fixture.Module.Instantiate(new NotMutHost()); }; action .Should() @@ -195,7 +195,7 @@ public void ItFailsToInstantiateWhenGlobalIsNotMut() [Fact] public void ItFailsToInstantiateWhenGlobalIsMut() { - Action action = () => { using (var instance = Fixture.Module.Instantiate(new MutHost())) { } }; + Action action = () => { using var instance = Fixture.Module.Instantiate(new MutHost()); }; action .Should() @@ -207,53 +207,52 @@ public void ItFailsToInstantiateWhenGlobalIsMut() public void ItBindsTheGlobalsCorrectly() { var host = new ValidHost(); - using (dynamic instance = Fixture.Module.Instantiate(host)) - { - host.Int32Mut.Value.Should().Be(0); - ((int)instance.get_global_i32_mut()).Should().Be(0); - host.Int32.Value.Should().Be(1); - ((int)instance.get_global_i32()).Should().Be(1); - host.Int64Mut.Value.Should().Be(2); - ((long)instance.get_global_i64_mut()).Should().Be(2); - host.Int64.Value.Should().Be(3); - ((long)instance.get_global_i64()).Should().Be(3); - host.Float32Mut.Value.Should().Be(4); - ((float)instance.get_global_f32_mut()).Should().Be(4); - host.Float32.Value.Should().Be(5); - ((float)instance.get_global_f32()).Should().Be(5); - host.Float64Mut.Value.Should().Be(6); - ((double)instance.get_global_f64_mut()).Should().Be(6); - host.Float64.Value.Should().Be(7); - ((double)instance.get_global_f64()).Should().Be(7); - - host.Int32Mut.Value = 10; - host.Int32Mut.Value.Should().Be(10); - ((int)instance.get_global_i32_mut()).Should().Be(10); - instance.set_global_i32_mut(11); - host.Int32Mut.Value.Should().Be(11); - ((int)instance.get_global_i32_mut()).Should().Be(11); - - host.Int64Mut.Value = 12; - host.Int64Mut.Value.Should().Be(12); - ((long)instance.get_global_i64_mut()).Should().Be(12); - instance.set_global_i64_mut(13); - host.Int64Mut.Value.Should().Be(13); - ((long)instance.get_global_i64_mut()).Should().Be(13); - - host.Float32Mut.Value = 14; - host.Float32Mut.Value.Should().Be(14); - ((float)instance.get_global_f32_mut()).Should().Be(14); - instance.set_global_f32_mut(15); - host.Float32Mut.Value.Should().Be(15); - ((float)instance.get_global_f32_mut()).Should().Be(15); - - host.Float64Mut.Value = 16; - host.Float64Mut.Value.Should().Be(16); - ((double)instance.get_global_f64_mut()).Should().Be(16); - instance.set_global_f64_mut(17); - host.Float64Mut.Value.Should().Be(17); - ((double)instance.get_global_f64_mut()).Should().Be(17); - } + 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(10); + ((int)instance.get_global_i32_mut()).Should().Be(10); + instance.set_global_i32_mut(11); + host.Int32Mut.Value.Should().Be(11); + ((int)instance.get_global_i32_mut()).Should().Be(11); + + host.Int64Mut.Value = 12; + host.Int64Mut.Value.Should().Be(12); + ((long)instance.get_global_i64_mut()).Should().Be(12); + instance.set_global_i64_mut(13); + host.Int64Mut.Value.Should().Be(13); + ((long)instance.get_global_i64_mut()).Should().Be(13); + + host.Float32Mut.Value = 14; + host.Float32Mut.Value.Should().Be(14); + ((float)instance.get_global_f32_mut()).Should().Be(14); + instance.set_global_f32_mut(15); + host.Float32Mut.Value.Should().Be(15); + ((float)instance.get_global_f32_mut()).Should().Be(15); + + host.Float64Mut.Value = 16; + host.Float64Mut.Value.Should().Be(16); + ((double)instance.get_global_f64_mut()).Should().Be(16); + instance.set_global_f64_mut(17); + host.Float64Mut.Value.Should().Be(17); + ((double)instance.get_global_f64_mut()).Should().Be(17); } } } diff --git a/crates/misc/dotnet/tests/MemoryExportsTests.cs b/crates/misc/dotnet/tests/MemoryExportsTests.cs index 1a91ea18a32c..08242e75c7dc 100644 --- a/crates/misc/dotnet/tests/MemoryExportsTests.cs +++ b/crates/misc/dotnet/tests/MemoryExportsTests.cs @@ -45,43 +45,42 @@ public void ItHasTheExpectedNumberOfExportedTables() public void ItCreatesExternsForTheMemories() { var host = new Host(); - using (var instance = Fixture.Module.Instantiate(host)) - { - instance.Externs.Memories.Count.Should().Be(1); - - var memory = instance.Externs.Memories[0]; - memory.ReadString(0, 11).Should().Be("Hello World"); - int written = memory.WriteString(0, "WebAssembly Rocks!"); - memory.ReadString(0, written).Should().Be("WebAssembly Rocks!"); - - memory.ReadByte(20).Should().Be(1); - memory.WriteByte(20, 11); - memory.ReadByte(20).Should().Be(11); - - memory.ReadInt16(21).Should().Be(2); - memory.WriteInt16(21, 12); - memory.ReadInt16(21).Should().Be(12); - - memory.ReadInt32(23).Should().Be(3); - memory.WriteInt32(23, 13); - memory.ReadInt32(23).Should().Be(13); - - memory.ReadInt64(27).Should().Be(4); - memory.WriteInt64(27, 14); - memory.ReadInt64(27).Should().Be(14); - - memory.ReadSingle(35).Should().Be(5); - memory.WriteSingle(35, 15); - memory.ReadSingle(35).Should().Be(15); - - memory.ReadDouble(39).Should().Be(6); - memory.WriteDouble(39, 16); - memory.ReadDouble(39).Should().Be(16); - - memory.ReadIntPtr(48).Should().Be((IntPtr)7); - memory.WriteIntPtr(48, (IntPtr)17); - memory.ReadIntPtr(48).Should().Be((IntPtr)17); - } + using var instance = Fixture.Module.Instantiate(host); + + instance.Externs.Memories.Count.Should().Be(1); + + var memory = instance.Externs.Memories[0]; + memory.ReadString(0, 11).Should().Be("Hello World"); + int written = memory.WriteString(0, "WebAssembly Rocks!"); + memory.ReadString(0, written).Should().Be("WebAssembly Rocks!"); + + memory.ReadByte(20).Should().Be(1); + memory.WriteByte(20, 11); + memory.ReadByte(20).Should().Be(11); + + memory.ReadInt16(21).Should().Be(2); + memory.WriteInt16(21, 12); + memory.ReadInt16(21).Should().Be(12); + + memory.ReadInt32(23).Should().Be(3); + memory.WriteInt32(23, 13); + memory.ReadInt32(23).Should().Be(13); + + memory.ReadInt64(27).Should().Be(4); + memory.WriteInt64(27, 14); + memory.ReadInt64(27).Should().Be(14); + + memory.ReadSingle(35).Should().Be(5); + memory.WriteSingle(35, 15); + memory.ReadSingle(35).Should().Be(15); + + memory.ReadDouble(39).Should().Be(6); + memory.WriteDouble(39, 16); + memory.ReadDouble(39).Should().Be(16); + + memory.ReadIntPtr(48).Should().Be((IntPtr)7); + memory.WriteIntPtr(48, (IntPtr)17); + memory.ReadIntPtr(48).Should().Be((IntPtr)17); } public static IEnumerable GetMemoryExports() diff --git a/crates/misc/dotnet/tests/MemoryImportBindingTests.cs b/crates/misc/dotnet/tests/MemoryImportBindingTests.cs index cbe9efaae925..cab85e243410 100644 --- a/crates/misc/dotnet/tests/MemoryImportBindingTests.cs +++ b/crates/misc/dotnet/tests/MemoryImportBindingTests.cs @@ -74,7 +74,7 @@ public MemoryImportBindingTests(MemoryImportBindingFixture fixture) [Fact] public void ItFailsToInstantiateWithMissingImport() { - Action action = () => { using (var instance = Fixture.Module.Instantiate(new MissingImportsHost())) { } }; + Action action = () => { using var instance = Fixture.Module.Instantiate(new MissingImportsHost()); }; action .Should() @@ -85,7 +85,7 @@ public void ItFailsToInstantiateWithMissingImport() [Fact] public void ItFailsToInstantiateWithStaticField() { - Action action = () => { using (var instance = Fixture.Module.Instantiate(new MemoryIsStaticHost())) { } }; + Action action = () => { using var instance = Fixture.Module.Instantiate(new MemoryIsStaticHost()); }; action .Should() @@ -96,7 +96,7 @@ public void ItFailsToInstantiateWithStaticField() [Fact] public void ItFailsToInstantiateWithNonReadOnlyField() { - Action action = () => { using (var instance = Fixture.Module.Instantiate(new MemoryIsNotReadOnlyHost())) { } }; + Action action = () => { using var instance = Fixture.Module.Instantiate(new MemoryIsNotReadOnlyHost()); }; action .Should() @@ -107,7 +107,7 @@ public void ItFailsToInstantiateWithNonReadOnlyField() [Fact] public void ItFailsToInstantiateWithInvalidType() { - Action action = () => { using (var instance = Fixture.Module.Instantiate(new NotAMemoryHost())) { } }; + Action action = () => { using var instance = Fixture.Module.Instantiate(new NotAMemoryHost()); }; action .Should() @@ -118,7 +118,7 @@ public void ItFailsToInstantiateWithInvalidType() [Fact] public void ItFailsToInstantiateWhenMemoryHasInvalidMinimum() { - Action action = () => { using (var instance = Fixture.Module.Instantiate(new InvalidMinimumHost())) { } }; + Action action = () => { using var instance = Fixture.Module.Instantiate(new InvalidMinimumHost()); }; action .Should() @@ -129,7 +129,7 @@ public void ItFailsToInstantiateWhenMemoryHasInvalidMinimum() [Fact] public void ItFailsToInstantiateWhenMemoryHasInvalidMaximum() { - Action action = () => { using (var instance = Fixture.Module.Instantiate(new InvalidMaximumHost())) { } }; + Action action = () => { using var instance = Fixture.Module.Instantiate(new InvalidMaximumHost()); }; action .Should() @@ -141,47 +141,46 @@ public void ItFailsToInstantiateWhenMemoryHasInvalidMaximum() public void ItBindsTheGlobalsCorrectly() { var host = new ValidHost(); - using (dynamic instance = Fixture.Module.Instantiate(host)) - { - host.Mem.ReadString(0, 11).Should().Be("Hello World"); - int written = host.Mem.WriteString(0, "WebAssembly Rocks!"); - host.Mem.ReadString(0, written).Should().Be("WebAssembly Rocks!"); - - host.Mem.ReadByte(20).Should().Be(1); - host.Mem.WriteByte(20, 11); - host.Mem.ReadByte(20).Should().Be(11); - ((byte)instance.ReadByte()).Should().Be(11); - - host.Mem.ReadInt16(21).Should().Be(2); - host.Mem.WriteInt16(21, 12); - host.Mem.ReadInt16(21).Should().Be(12); - ((short)instance.ReadInt16()).Should().Be(12); - - host.Mem.ReadInt32(23).Should().Be(3); - host.Mem.WriteInt32(23, 13); - host.Mem.ReadInt32(23).Should().Be(13); - ((int)instance.ReadInt32()).Should().Be(13); - - host.Mem.ReadInt64(27).Should().Be(4); - host.Mem.WriteInt64(27, 14); - host.Mem.ReadInt64(27).Should().Be(14); - ((long)instance.ReadInt64()).Should().Be(14); - - host.Mem.ReadSingle(35).Should().Be(5); - host.Mem.WriteSingle(35, 15); - host.Mem.ReadSingle(35).Should().Be(15); - ((float)instance.ReadFloat32()).Should().Be(15); - - host.Mem.ReadDouble(39).Should().Be(6); - host.Mem.WriteDouble(39, 16); - host.Mem.ReadDouble(39).Should().Be(16); - ((double)instance.ReadFloat64()).Should().Be(16); - - host.Mem.ReadIntPtr(48).Should().Be((IntPtr)7); - host.Mem.WriteIntPtr(48, (IntPtr)17); - host.Mem.ReadIntPtr(48).Should().Be((IntPtr)17); - ((IntPtr)instance.ReadIntPtr()).Should().Be((IntPtr)17); - } + 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.WriteByte(20, 11); + host.Mem.ReadByte(20).Should().Be(11); + ((byte)instance.ReadByte()).Should().Be(11); + + host.Mem.ReadInt16(21).Should().Be(2); + host.Mem.WriteInt16(21, 12); + host.Mem.ReadInt16(21).Should().Be(12); + ((short)instance.ReadInt16()).Should().Be(12); + + host.Mem.ReadInt32(23).Should().Be(3); + host.Mem.WriteInt32(23, 13); + host.Mem.ReadInt32(23).Should().Be(13); + ((int)instance.ReadInt32()).Should().Be(13); + + host.Mem.ReadInt64(27).Should().Be(4); + host.Mem.WriteInt64(27, 14); + host.Mem.ReadInt64(27).Should().Be(14); + ((long)instance.ReadInt64()).Should().Be(14); + + host.Mem.ReadSingle(35).Should().Be(5); + host.Mem.WriteSingle(35, 15); + host.Mem.ReadSingle(35).Should().Be(15); + ((float)instance.ReadFloat32()).Should().Be(15); + + host.Mem.ReadDouble(39).Should().Be(6); + host.Mem.WriteDouble(39, 16); + host.Mem.ReadDouble(39).Should().Be(16); + ((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); } } } diff --git a/crates/misc/dotnet/tests/Modules/Wasi.wasm b/crates/misc/dotnet/tests/Modules/Wasi.wasm new file mode 100644 index 000000000000..62644ebfed22 Binary files /dev/null and b/crates/misc/dotnet/tests/Modules/Wasi.wasm differ diff --git a/crates/misc/dotnet/tests/Modules/Wasi.wat b/crates/misc/dotnet/tests/Modules/Wasi.wat new file mode 100644 index 000000000000..da028cb4dd25 --- /dev/null +++ b/crates/misc/dotnet/tests/Modules/Wasi.wat @@ -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)) +) diff --git a/crates/misc/dotnet/tests/TempFile.cs b/crates/misc/dotnet/tests/TempFile.cs new file mode 100644 index 000000000000..6456b37b7266 --- /dev/null +++ b/crates/misc/dotnet/tests/TempFile.cs @@ -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; } + } +} \ No newline at end of file diff --git a/crates/misc/dotnet/tests/WasiTests.cs b/crates/misc/dotnet/tests/WasiTests.cs new file mode 100644 index 000000000000..06d332634da1 --- /dev/null +++ b/crates/misc/dotnet/tests/WasiTests.cs @@ -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 + { + 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() { + {"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() { + "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)); + } + } +} diff --git a/crates/test-programs/tests/wasm_tests/runtime.rs b/crates/test-programs/tests/wasm_tests/runtime.rs index d10f239f512a..42d4c7221bf0 100644 --- a/crates/test-programs/tests/wasm_tests/runtime.rs +++ b/crates/test-programs/tests/wasm_tests/runtime.rs @@ -19,12 +19,12 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any // Create our wasi context with pretty standard arguments/inheritance/etc. // Additionally register andy preopened directories if we have them. - let mut builder = wasi_common::WasiCtxBuilder::new() - .arg(bin_name) - .arg(".") - .inherit_stdio(); + let mut builder = wasi_common::WasiCtxBuilder::new(); + + builder.arg(bin_name).arg(".").inherit_stdio(); + for (dir, file) in get_preopens(workspace)? { - builder = builder.preopened_dir(file, dir); + builder.preopened_dir(file, dir); } // The nonstandard thing we do with `WasiCtxBuilder` is to ensure that @@ -32,7 +32,7 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any // where `stdin` is never ready to be read. In some CI systems, however, // stdin is closed which causes tests to fail. let (reader, _writer) = os_pipe::pipe()?; - builder = builder.stdin(reader_to_file(reader)); + builder.stdin(reader_to_file(reader)); let snapshot1 = wasmtime_wasi::Wasi::new(&store, builder.build()?); let module = Module::new(&store, &data).context("failed to create wasm module")?; let imports = module diff --git a/crates/wasi-common/src/ctx.rs b/crates/wasi-common/src/ctx.rs index e2299d212efc..e9e64a386610 100644 --- a/crates/wasi-common/src/ctx.rs +++ b/crates/wasi-common/src/ctx.rs @@ -60,38 +60,38 @@ impl PendingCString { /// A builder allowing customizable construction of `WasiCtx` instances. pub struct WasiCtxBuilder { - fds: HashMap, - preopens: Vec<(PathBuf, File)>, - args: Vec, - env: HashMap, + fds: Option>, + preopens: Option>, + args: Option>, + env: Option>, } impl WasiCtxBuilder { /// Builder for a new `WasiCtx`. pub fn new() -> Self { - let mut builder = Self { - fds: HashMap::new(), - preopens: Vec::new(), - args: vec![], - env: HashMap::new(), - }; - - builder.fds.insert(0, PendingFdEntry::Thunk(FdEntry::null)); - builder.fds.insert(1, PendingFdEntry::Thunk(FdEntry::null)); - builder.fds.insert(2, PendingFdEntry::Thunk(FdEntry::null)); - - builder + let mut fds = HashMap::new(); + + fds.insert(0, PendingFdEntry::Thunk(FdEntry::null)); + fds.insert(1, PendingFdEntry::Thunk(FdEntry::null)); + fds.insert(2, PendingFdEntry::Thunk(FdEntry::null)); + + Self { + fds: Some(fds), + preopens: Some(Vec::new()), + args: Some(Vec::new()), + env: Some(HashMap::new()), + } } /// Add arguments to the command-line arguments list. /// /// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail /// with `Error::EILSEQ`. - pub fn args>(mut self, args: impl IntoIterator) -> Self { - self.args = args - .into_iter() - .map(|arg| arg.as_ref().to_vec().into()) - .collect(); + pub fn args>(&mut self, args: impl IntoIterator) -> &mut Self { + self.args + .as_mut() + .unwrap() + .extend(args.into_iter().map(|a| a.as_ref().to_vec().into())); self } @@ -99,8 +99,11 @@ impl WasiCtxBuilder { /// /// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail /// with `Error::EILSEQ`. - pub fn arg>(mut self, arg: S) -> Self { - self.args.push(arg.as_ref().to_vec().into()); + pub fn arg>(&mut self, arg: S) -> &mut Self { + self.args + .as_mut() + .unwrap() + .push(arg.as_ref().to_vec().into()); self } @@ -108,31 +111,58 @@ impl WasiCtxBuilder { /// /// If any arguments from the host process contain invalid UTF-8, `WasiCtxBuilder::build()` will /// fail with `Error::EILSEQ`. - pub fn inherit_args(mut self) -> Self { - self.args = env::args_os().map(PendingCString::OsString).collect(); + pub fn inherit_args(&mut self) -> &mut Self { + let args = self.args.as_mut().unwrap(); + args.clear(); + args.extend(env::args_os().map(PendingCString::OsString)); self } - /// Inherit the stdin, stdout, and stderr streams from the host process. - pub fn inherit_stdio(mut self) -> Self { + /// Inherit stdin from the host process. + pub fn inherit_stdin(&mut self) -> &mut Self { self.fds + .as_mut() + .unwrap() .insert(0, PendingFdEntry::Thunk(FdEntry::duplicate_stdin)); + self + } + + /// Inherit stdout from the host process. + pub fn inherit_stdout(&mut self) -> &mut Self { self.fds + .as_mut() + .unwrap() .insert(1, PendingFdEntry::Thunk(FdEntry::duplicate_stdout)); + self + } + + /// Inherit stdout from the host process. + pub fn inherit_stderr(&mut self) -> &mut Self { self.fds + .as_mut() + .unwrap() .insert(2, PendingFdEntry::Thunk(FdEntry::duplicate_stderr)); self } + /// Inherit the stdin, stdout, and stderr streams from the host process. + pub fn inherit_stdio(&mut self) -> &mut Self { + let fds = self.fds.as_mut().unwrap(); + fds.insert(0, PendingFdEntry::Thunk(FdEntry::duplicate_stdin)); + fds.insert(1, PendingFdEntry::Thunk(FdEntry::duplicate_stdout)); + fds.insert(2, PendingFdEntry::Thunk(FdEntry::duplicate_stderr)); + self + } + /// Inherit the environment variables from the host process. /// /// If any environment variables from the host process contain invalid Unicode (UTF-16 for /// Windows, UTF-8 for other platforms), `WasiCtxBuilder::build()` will fail with /// `Error::EILSEQ`. - pub fn inherit_env(mut self) -> Self { - self.env = std::env::vars_os() - .map(|(k, v)| (k.into(), v.into())) - .collect(); + pub fn inherit_env(&mut self) -> &mut Self { + let env = self.env.as_mut().unwrap(); + env.clear(); + env.extend(std::env::vars_os().map(|(k, v)| (k.into(), v.into()))); self } @@ -140,8 +170,10 @@ impl WasiCtxBuilder { /// /// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else /// `WasiCtxBuilder::build()` will fail with `Error::EILSEQ`. - pub fn env>(mut self, k: S, v: S) -> Self { + pub fn env>(&mut self, k: S, v: S) -> &mut Self { self.env + .as_mut() + .unwrap() .insert(k.as_ref().to_vec().into(), v.as_ref().to_vec().into()); self } @@ -151,40 +183,49 @@ impl WasiCtxBuilder { /// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else /// `WasiCtxBuilder::build()` will fail with `Error::EILSEQ`. pub fn envs, T: Borrow<(S, S)>>( - mut self, + &mut self, envs: impl IntoIterator, - ) -> Self { - self.env = envs - .into_iter() - .map(|t| { - let (k, v) = t.borrow(); - (k.as_ref().to_vec().into(), v.as_ref().to_vec().into()) - }) - .collect(); + ) -> &mut Self { + self.env.as_mut().unwrap().extend(envs.into_iter().map(|t| { + let (k, v) = t.borrow(); + (k.as_ref().to_vec().into(), v.as_ref().to_vec().into()) + })); self } /// Provide a File to use as stdin - pub fn stdin(mut self, file: File) -> Self { - self.fds.insert(0, PendingFdEntry::File(file)); + pub fn stdin(&mut self, file: File) -> &mut Self { + self.fds + .as_mut() + .unwrap() + .insert(0, PendingFdEntry::File(file)); self } /// Provide a File to use as stdout - pub fn stdout(mut self, file: File) -> Self { - self.fds.insert(1, PendingFdEntry::File(file)); + pub fn stdout(&mut self, file: File) -> &mut Self { + self.fds + .as_mut() + .unwrap() + .insert(1, PendingFdEntry::File(file)); self } /// Provide a File to use as stderr - pub fn stderr(mut self, file: File) -> Self { - self.fds.insert(2, PendingFdEntry::File(file)); + pub fn stderr(&mut self, file: File) -> &mut Self { + self.fds + .as_mut() + .unwrap() + .insert(2, PendingFdEntry::File(file)); self } /// Add a preopened directory. - pub fn preopened_dir>(mut self, dir: File, guest_path: P) -> Self { - self.preopens.push((guest_path.as_ref().to_owned(), dir)); + pub fn preopened_dir>(&mut self, dir: File, guest_path: P) -> &mut Self { + self.preopens + .as_mut() + .unwrap() + .push((guest_path.as_ref().to_owned(), dir)); self } @@ -192,17 +233,21 @@ impl WasiCtxBuilder { /// /// If any of the arguments or environment variables in this builder cannot be converted into /// `CString`s, either due to NUL bytes or Unicode conversions, this returns `Error::EILSEQ`. - pub fn build(self) -> Result { + pub fn build(&mut self) -> Result { // Process arguments and environment variables into `CString`s, failing quickly if they // contain any NUL bytes, or if conversion from `OsString` fails. let args = self .args + .take() + .ok_or(Error::EINVAL)? .into_iter() .map(|arg| arg.into_utf8_cstring()) .collect::>>()?; let env = self .env + .take() + .ok_or(Error::EINVAL)? .into_iter() .map(|(k, v)| { k.into_string().and_then(|mut pair| { @@ -219,7 +264,7 @@ impl WasiCtxBuilder { let mut fds: HashMap = HashMap::new(); // Populate the non-preopen fds. - for (fd, pending) in self.fds { + for (fd, pending) in self.fds.take().ok_or(Error::EINVAL)? { log::debug!("WasiCtx inserting ({:?}, {:?})", fd, pending); match pending { PendingFdEntry::Thunk(f) => { @@ -234,7 +279,7 @@ impl WasiCtxBuilder { // so we start from there. This variable is initially 2, though, because the loop // immediately does the increment and check for overflow. let mut preopen_fd: wasi::__wasi_fd_t = 2; - for (guest_path, dir) in self.preopens { + for (guest_path, dir) in self.preopens.take().ok_or(Error::EINVAL)? { // We do the increment at the beginning of the loop body, so that we don't overflow // unnecessarily if we have exactly the maximum number of file descriptors. preopen_fd = preopen_fd.checked_add(1).ok_or(Error::ENFILE)?; diff --git a/src/commands/run.rs b/src/commands/run.rs index 3e1cb2c5caa6..d5fa7ee0a886 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -303,22 +303,25 @@ impl ModuleRegistry { argv: &[String], vars: &[(String, String)], ) -> Result { - let mut cx1 = wasi_common::WasiCtxBuilder::new() - .inherit_stdio() - .args(argv) - .envs(vars); + let mut cx1 = wasi_common::WasiCtxBuilder::new(); + + cx1.inherit_stdio().args(argv).envs(vars); + 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 mut cx2 = wasi_common::old::snapshot_0::WasiCtxBuilder::new() .inherit_stdio() .args(argv) .envs(vars); + for (name, file) in preopen_dirs { cx2 = cx2.preopened_dir(file.try_clone()?, name); } + let cx2 = cx2.build()?; Ok(ModuleRegistry {