Reimplement the C# API.

This commit reimplements the C# API in terms of a Wasmtime linker.

It removes the custom binding implementation that was based on reflection in
favor of the linker's implementation.

This should make the C# API a little closer to the Rust API.

The `Engine` and `Store` types have been hidden behind the `Host` type which is
responsible for hosting WebAssembly module instances.

Documentation and tests have been updated.
This commit is contained in:
Peter Huene
2020-03-24 18:20:22 -07:00
parent 0d5d63fdb1
commit cf1d9ee857
38 changed files with 2149 additions and 2213 deletions

View File

@@ -1,131 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using Wasmtime.Imports;
namespace Wasmtime.Bindings
{
/// <summary>
/// Represents an abstract host binding.
/// </summary>
internal abstract class Binding
{
public abstract SafeHandle Bind(Store store, IHost host);
public static WasmtimeException CreateBindingException(Import import, MemberInfo member, string message)
{
return new WasmtimeException($"Unable to bind '{member.DeclaringType.Name}.{member.Name}' to WebAssembly import '{import}': {message}.");
}
public static List<Binding> GetImportBindings(Module module, Wasi wasi = null, IHost host = null)
{
if (module is null)
{
throw new ArgumentNullException(nameof(module));
}
var bindings = new List<Binding>();
var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;
var type = host?.GetType();
var methods = type?.GetMethods(flags).Where(m => !m.IsSpecialName && Attribute.IsDefined(m, typeof(ImportAttribute)));
var fields = type?.GetFields(flags).Where(m => !m.IsSpecialName && Attribute.IsDefined(m, typeof(ImportAttribute)));
foreach (var import in module.Imports.All)
{
var wasiBinding = wasi?.Bind(import);
if (!(wasiBinding is null))
{
bindings.Add(wasiBinding);
continue;
}
switch (import)
{
case FunctionImport func:
bindings.Add(BindFunction(func, methods));
break;
case GlobalImport global:
bindings.Add(BindGlobal(global, fields));
break;
case MemoryImport memory:
bindings.Add(BindMemory(memory, fields));
break;
default:
throw new NotSupportedException("Unsupported import binding type.");
}
}
return bindings;
}
private static FunctionBinding BindFunction(FunctionImport import, IEnumerable<MethodInfo> methods)
{
var method = methods?.Where(m =>
{
var attribute = (ImportAttribute)m.GetCustomAttribute(typeof(ImportAttribute));
if (attribute is null)
{
return false;
}
return attribute.Name == import.Name &&
((string.IsNullOrEmpty(attribute.Module) &&
string.IsNullOrEmpty(import.ModuleName)) ||
attribute.Module == import.ModuleName);
}
).FirstOrDefault();
if (method is null)
{
throw new WasmtimeException($"Failed to bind function import '{import}': the host does not contain a method with a matching 'Import' attribute.");
}
return new FunctionBinding(import, method);
}
private static GlobalBinding BindGlobal(GlobalImport import, IEnumerable<FieldInfo> fields)
{
var field = fields?.Where(f =>
{
var attribute = (ImportAttribute)f.GetCustomAttribute(typeof(ImportAttribute));
return attribute.Name == import.Name &&
((string.IsNullOrEmpty(attribute.Module) &&
string.IsNullOrEmpty(import.ModuleName)) ||
attribute.Module == import.ModuleName);
}
).FirstOrDefault();
if (field is null)
{
throw new WasmtimeException($"Failed to bind global import '{import}': the host does not contain a global field with a matching 'Import' attribute.");
}
return new GlobalBinding(import, field);
}
private static MemoryBinding BindMemory(MemoryImport import, IEnumerable<FieldInfo> fields)
{
var field = fields?.Where(f =>
{
var attribute = (ImportAttribute)f.GetCustomAttribute(typeof(ImportAttribute));
return attribute.Name == import.Name &&
((string.IsNullOrEmpty(attribute.Module) &&
string.IsNullOrEmpty(import.ModuleName)) ||
attribute.Module == import.ModuleName);
}
).FirstOrDefault();
if (field is null)
{
throw new WasmtimeException($"Failed to bind memory import '{import}': the host does not contain a memory field with a matching 'Import' attribute.");
}
return new MemoryBinding(import, field);
}
}
}

View File

@@ -1,339 +0,0 @@
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Wasmtime.Imports;
namespace Wasmtime.Bindings
{
/// <summary>
/// Represents a host function binding.
/// </summary>
internal class FunctionBinding : Binding
{
/// <summary>
/// Constructs a new function binding.
/// </summary>
/// <param name="import">The function import of the binding.</param>
/// <param name="method">The method the import is bound to.</param>
public FunctionBinding(FunctionImport import, MethodInfo method)
{
if (import is null)
{
throw new ArgumentNullException(nameof(import));
}
if (method is null)
{
throw new ArgumentNullException(nameof(method));
}
Import = import;
Method = method;
Validate();
}
/// <summary>
/// The function import of the binding.
/// </summary>
public FunctionImport Import { get; private set; }
/// <summary>
/// The method the import is bound to.
/// </summary>
public MethodInfo Method { get; private set; }
public override SafeHandle Bind(Store store, IHost host)
{
unsafe
{
var parameters = Interop.ToValueTypeVec(Import.Parameters);
var results = Interop.ToValueTypeVec(Import.Results);
using var funcType = Interop.wasm_functype_new(ref parameters, ref results);
var callback = CreateCallback(store, host);
var func = Interop.wasm_func_new(store.Handle, funcType, callback);
// Store the callback with the safe handle to keep the delegate GC reachable
func.Callback = callback;
return func;
}
}
private void Validate()
{
if (Method.IsStatic)
{
throw CreateBindingException(Import, Method, "method cannot be static");
}
if (Method.IsGenericMethod)
{
throw CreateBindingException(Import, Method, "method cannot be generic");
}
if (Method.IsConstructor)
{
throw CreateBindingException(Import, Method, "method cannot be a constructor");
}
ValidateParameters();
ValidateReturnType();
}
private void ValidateParameters()
{
var parameters = Method.GetParameters();
if (parameters.Length != Import.Parameters.Count)
{
throw CreateBindingException(
Import,
Method,
$"parameter mismatch: import requires {Import.Parameters.Count} but the method has {parameters.Length}");
}
for (int i = 0; i < parameters.Length; ++i)
{
var parameter = parameters[i];
if (parameter.ParameterType.IsByRef)
{
if (parameter.IsOut)
{
throw CreateBindingException(Import, Method, $"parameter '{parameter.Name}' cannot be an 'out' parameter");
}
else
{
throw CreateBindingException(Import, Method, $"parameter '{parameter.Name}' cannot be a 'ref' parameter");
}
}
var expected = Import.Parameters[i];
if (!Interop.TryGetValueKind(parameter.ParameterType, out var kind) || !Interop.IsMatchingKind(kind, expected))
{
throw CreateBindingException(Import, Method, $"method parameter '{parameter.Name}' is expected to be of type '{Interop.ToString(expected)}'");
}
}
}
private void ValidateReturnType()
{
int resultsCount = Import.Results.Count();
if (resultsCount == 0)
{
if (Method.ReturnType != typeof(void))
{
throw CreateBindingException(Import, Method, "method must return void");
}
}
else if (resultsCount == 1)
{
var expected = Import.Results[0];
if (!Interop.TryGetValueKind(Method.ReturnType, out var kind) || !Interop.IsMatchingKind(kind, expected))
{
throw CreateBindingException(Import, Method, $"return type is expected to be '{Interop.ToString(expected)}'");
}
}
else
{
if (!IsTupleOfSize(Method.ReturnType, resultsCount))
{
throw CreateBindingException(Import, Method, $"return type is expected to be a tuple of size {resultsCount}");
}
var typeArguments =
Method.ReturnType.GetGenericArguments().SelectMany(type =>
{
if (type.IsConstructedGenericType)
{
return type.GenericTypeArguments;
}
return Enumerable.Repeat(type, 1);
});
int i = 0;
foreach (var typeArgument in typeArguments)
{
var expected = Import.Results[i];
if (!Interop.TryGetValueKind(typeArgument, out var kind) || !Interop.IsMatchingKind(kind, expected))
{
throw CreateBindingException(Import, Method, $"return tuple item #{i} is expected to be of type '{Interop.ToString(expected)}'");
}
++i;
}
}
}
private static bool IsTupleOfSize(Type type, int size)
{
if (!type.IsConstructedGenericType)
{
return false;
}
var definition = type.GetGenericTypeDefinition();
if (size == 0)
{
return definition == typeof(ValueTuple);
}
if (size == 1)
{
return definition == typeof(ValueTuple<>);
}
if (size == 2)
{
return definition == typeof(ValueTuple<,>);
}
if (size == 3)
{
return definition == typeof(ValueTuple<,,>);
}
if (size == 4)
{
return definition == typeof(ValueTuple<,,,>);
}
if (size == 5)
{
return definition == typeof(ValueTuple<,,,,>);
}
if (size == 6)
{
return definition == typeof(ValueTuple<,,,,,>);
}
if (size == 7)
{
return definition == typeof(ValueTuple<,,,,,,>);
}
if (definition != typeof(ValueTuple<,,,,,,,>))
{
return false;
}
return IsTupleOfSize(type.GetGenericArguments().Last(), size - 7);
}
private unsafe Interop.WasmFuncCallback CreateCallback(Store store, IHost host)
{
var args = new object[Import.Parameters.Count];
bool hasReturn = Method.ReturnType != typeof(void);
var storeHandle = store.Handle;
Interop.WasmFuncCallback callback = (arguments, results) =>
{
try
{
SetArgs(arguments, args);
var result = Method.Invoke(host, BindingFlags.DoNotWrapExceptions, null, args, null);
if (hasReturn)
{
SetResults(result, results);
}
return IntPtr.Zero;
}
catch (Exception ex)
{
var bytes = Encoding.UTF8.GetBytes(ex.Message + "\0" /* exception messages need a null */);
fixed (byte* ptr = bytes)
{
Interop.wasm_byte_vec_t message = new Interop.wasm_byte_vec_t();
message.size = (UIntPtr)bytes.Length;
message.data = ptr;
return Interop.wasm_trap_new(storeHandle, ref message);
}
}
};
return callback;
}
private static unsafe void SetArgs(Interop.wasm_val_t* arguments, object[] args)
{
for (int i = 0; i < args.Length; ++i)
{
var arg = arguments[i];
switch (arg.kind)
{
case Interop.wasm_valkind_t.WASM_I32:
args[i] = arg.of.i32;
break;
case Interop.wasm_valkind_t.WASM_I64:
args[i] = arg.of.i64;
break;
case Interop.wasm_valkind_t.WASM_F32:
args[i] = arg.of.f32;
break;
case Interop.wasm_valkind_t.WASM_F64:
args[i] = arg.of.f64;
break;
default:
throw new NotSupportedException("Unsupported value type.");
}
}
}
private static unsafe void SetResults(object value, Interop.wasm_val_t* results)
{
var tuple = value as ITuple;
if (tuple is null)
{
SetResult(value, &results[0]);
}
else
{
for (int i = 0; i < tuple.Length; ++i)
{
SetResults(tuple[i], &results[i]);
}
}
}
private static unsafe void SetResult(object value, Interop.wasm_val_t* result)
{
switch (value)
{
case int i:
result->kind = Interop.wasm_valkind_t.WASM_I32;
result->of.i32 = i;
break;
case long l:
result->kind = Interop.wasm_valkind_t.WASM_I64;
result->of.i64 = l;
break;
case float f:
result->kind = Interop.wasm_valkind_t.WASM_F32;
result->of.f32 = f;
break;
case double d:
result->kind = Interop.wasm_valkind_t.WASM_F64;
result->of.f64 = d;
break;
default:
throw new NotSupportedException("Unsupported return value type.");
}
}
}
}

View File

@@ -1,125 +0,0 @@
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using Wasmtime.Imports;
namespace Wasmtime.Bindings
{
/// <summary>
/// Represents a host global binding.
/// </summary>
internal class GlobalBinding : Binding
{
/// <summary>
/// Constructs a new global binding.
/// </summary>
/// <param name="import">The global import of the binding.</param>
/// <param name="field">The field the import is bound to.</param>
public GlobalBinding(GlobalImport import, FieldInfo field)
{
if (import is null)
{
throw new ArgumentNullException(nameof(import));
}
if (field is null)
{
throw new ArgumentNullException(nameof(field));
}
Import = import;
Field = field;
Validate();
}
/// <summary>
/// The global import of the binding.
/// </summary>
public GlobalImport Import { get; private set; }
/// <summary>
/// The field the import is bound to.
/// </summary>
public FieldInfo Field { get; private set; }
public override SafeHandle Bind(Store store, IHost host)
{
unsafe
{
dynamic global = Field.GetValue(host);
if (!(global.Handle is null))
{
throw new InvalidOperationException("Cannot bind more than once.");
}
var v = Interop.ToValue((object)global.InitialValue, Import.Kind);
var valueType = Interop.wasm_valtype_new(v.kind);
var valueTypeHandle = valueType.DangerousGetHandle();
valueType.SetHandleAsInvalid();
using var globalType = Interop.wasm_globaltype_new(
valueTypeHandle,
Import.IsMutable ? Interop.wasm_mutability_t.WASM_VAR : Interop.wasm_mutability_t.WASM_CONST
);
var handle = Interop.wasm_global_new(store.Handle, globalType, &v);
global.Handle = handle;
return handle;
}
}
private void Validate()
{
if (Field.IsStatic)
{
throw CreateBindingException(Import, Field, "field cannot be static");
}
if (!Field.IsInitOnly)
{
throw CreateBindingException(Import, Field, "field must be readonly");
}
if (!Field.FieldType.IsGenericType)
{
throw CreateBindingException(Import, Field, "field is expected to be of type 'Global<T>'");
}
var definition = Field.FieldType.GetGenericTypeDefinition();
if (definition == typeof(Global<>))
{
if (Import.IsMutable)
{
throw CreateBindingException(Import, Field, "the import is mutable (use the 'MutableGlobal' type)");
}
}
else if (definition == typeof(MutableGlobal<>))
{
if (!Import.IsMutable)
{
throw CreateBindingException(Import, Field, "the import is constant (use the 'Global' type)");
}
}
else
{
throw CreateBindingException(Import, Field, "field is expected to be of type 'Global<T>' or 'MutableGlobal<T>'");
}
var arg = Field.FieldType.GetGenericArguments()[0];
if (Interop.TryGetValueKind(arg, out var kind))
{
if (!Interop.IsMatchingKind(kind, Import.Kind))
{
throw CreateBindingException(Import, Field, $"global type argument is expected to be of type '{Interop.ToString(Import.Kind)}'");
}
}
else
{
throw CreateBindingException(Import, Field, $"'{arg}' is not a valid global type");
}
}
}
}

View File

@@ -1,97 +0,0 @@
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using Wasmtime.Imports;
namespace Wasmtime.Bindings
{
/// <summary>
/// Represents a host memory binding.
/// </summary>
internal class MemoryBinding : Binding
{
/// <summary>
/// Constructs a new memory binding.
/// </summary>
/// <param name="import">The memory import of the binding.</param>
/// <param name="field">The field the import is bound to.</param>
public MemoryBinding(MemoryImport import, FieldInfo field)
{
if (import is null)
{
throw new ArgumentNullException(nameof(import));
}
if (field is null)
{
throw new ArgumentNullException(nameof(field));
}
Import = import;
Field = field;
Validate();
}
/// <summary>
/// The memory import of the binding.
/// </summary>
public MemoryImport Import { get; private set; }
/// <summary>
/// The field the import is bound to.
/// </summary>
public FieldInfo Field { get; private set; }
public override SafeHandle Bind(Store store, IHost host)
{
Memory memory = (Memory)Field.GetValue(host);
if (!(memory.Handle is null))
{
throw new InvalidOperationException("Cannot bind more than once.");
}
uint min = memory.Minimum;
uint max = memory.Maximum;
if (min != Import.Minimum)
{
throw CreateBindingException(Import, Field, $"Memory does not have the expected minimum of {Import.Minimum} page(s)");
}
if (max != Import.Maximum)
{
throw CreateBindingException(Import, Field, $"Memory does not have the expected maximum of {Import.Maximum} page(s)");
}
unsafe
{
Interop.wasm_limits_t limits = new Interop.wasm_limits_t();
limits.min = min;
limits.max = max;
using var memoryType = Interop.wasm_memorytype_new(&limits);
var handle = Interop.wasm_memory_new(store.Handle, memoryType);
memory.Handle = handle;
return handle;
}
}
private void Validate()
{
if (Field.IsStatic)
{
throw CreateBindingException(Import, Field, "field cannot be static");
}
if (!Field.IsInitOnly)
{
throw CreateBindingException(Import, Field, "field must be readonly");
}
if (Field.FieldType != typeof(Memory))
{
throw CreateBindingException(Import, Field, "field is expected to be of type 'Memory'");
}
}
}
}

View File

@@ -1,23 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace Wasmtime.Bindings
{
/// <summary>
/// Represents a binding to a WASI export.
/// </summary>
internal class WasiBinding : Binding
{
public WasiBinding(IntPtr handle)
{
_handle = handle;
}
public override SafeHandle Bind(Store store, IHost host)
{
return new Interop.WasiExportHandle(_handle);
}
private IntPtr _handle;
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Text;
namespace Wasmtime
{
/// <summary>
/// Represents an exported memory of a host function caller.
/// </summary>
public class CallerMemory : MemoryBase, IDisposable
{
/// <inheritdoc/>
public void Dispose()
{
if (!_extern.IsInvalid)
{
_extern.Dispose();
_extern.SetHandleAsInvalid();
}
}
internal CallerMemory(Interop.ExternHandle ext, IntPtr memory)
{
_extern = ext;
_memory = memory;
}
/// <inheritdoc/>
protected override IntPtr MemoryHandle => _memory;
private Interop.ExternHandle _extern;
private IntPtr _memory;
}
/// <summary>
/// Represents information of the caller of a host function.
/// </summary>
public class Caller
{
/// <summary>
/// Gets an exported memory of the caller by the given name.
/// </summary>
/// <param name="name">The name of the exported memory.</param>
/// <returns>Returns the exported memory if found or null if a memory of the requested name is not exported.</returns>
public CallerMemory GetMemory(string name)
{
if (Handle == IntPtr.Zero)
{
throw new InvalidOperationException();
}
unsafe
{
var bytes = Encoding.UTF8.GetBytes(name);
fixed (byte* ptr = bytes)
{
Interop.wasm_byte_vec_t nameVec = new Interop.wasm_byte_vec_t();
nameVec.size = (UIntPtr)bytes.Length;
nameVec.data = ptr;
var export = Interop.wasmtime_caller_export_get(Handle, ref nameVec);
if (export.IsInvalid)
{
return null;
}
var memory = Interop.wasm_extern_as_memory(export.DangerousGetHandle());
if (memory == IntPtr.Zero)
{
export.Dispose();
return null;
}
return new CallerMemory(export, memory);
}
}
}
internal IntPtr Handle { get; set; }
}
}

View File

@@ -1,85 +0,0 @@
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace Wasmtime
{
/// <summary>
/// Represents the Wasmtime engine.
/// </summary>
public class Engine : IDisposable
{
/// <summary>
/// Constructs a new <see cref="Engine" />.
/// </summary>
public Engine()
{
Handle = Interop.wasm_engine_new();
if (Handle.IsInvalid)
{
throw new WasmtimeException("Failed to create Wasmtime engine.");
}
}
internal Engine(Interop.WasmConfigHandle config)
{
Handle = Interop.wasm_engine_new_with_config(config);
config.SetHandleAsInvalid();
if (Handle.IsInvalid)
{
throw new WasmtimeException("Failed to create Wasmtime engine.");
}
}
/// <summary>
/// Creates a new Wasmtime <see cref="Store" />.
/// </summary>
/// <returns>Returns the new <see cref="Store" />.</returns>
public Store CreateStore()
{
return new Store(this);
}
/// <summary>
/// Converts the WebAssembly text format to the binary format
/// </summary>
/// <returns>Returns the binary-encoded wasm module.</returns>
public byte[] WatToWasm(string wat)
{
var watBytes = Encoding.UTF8.GetBytes(wat);
unsafe
{
fixed (byte *ptr = watBytes)
{
Interop.wasm_byte_vec_t watByteVec;
watByteVec.size = (UIntPtr)watBytes.Length;
watByteVec.data = ptr;
if (!Interop.wasmtime_wat2wasm(Handle, ref watByteVec, out var bytes, out var error)) {
var errorSpan = new ReadOnlySpan<byte>(error.data, checked((int)error.size));
var message = Encoding.UTF8.GetString(errorSpan);
Interop.wasm_byte_vec_delete(ref error);
throw new WasmtimeException("failed to parse input wat: " + message);
}
var byteSpan = new ReadOnlySpan<byte>(bytes.data, checked((int)bytes.size));
var ret = byteSpan.ToArray();
Interop.wasm_byte_vec_delete(ref bytes);
return ret;
}
}
}
/// <inheritdoc/>
public void Dispose()
{
if (!Handle.IsInvalid)
{
Handle.Dispose();
Handle.SetHandleAsInvalid();
}
}
internal Interop.EngineHandle Handle { get; private set; }
}
}

View File

@@ -8,7 +8,7 @@ namespace Wasmtime.Externs
/// <summary>
/// Represents an external (instantiated) WebAssembly memory.
/// </summary>
public class ExternMemory
public class ExternMemory : MemoryBase
{
internal ExternMemory(MemoryExport export, IntPtr memory)
{
@@ -31,230 +31,7 @@ namespace Wasmtime.Externs
/// </summary>
public uint Maximum => _export.Maximum;
/// <summary>
/// The span of the memory.
/// </summary>
/// <remarks>
/// The span may become invalid if the memory grows.
///
/// This may happen if the memory is explicitly requested to grow or
/// grows as a result of WebAssembly execution.
///
/// Therefore, the returned Span should not be stored.
/// </remarks>
public unsafe Span<byte> Span
{
get
{
var data = Interop.wasm_memory_data(_memory);
var size = Convert.ToInt32(Interop.wasm_memory_data_size(_memory).ToUInt32());
return new Span<byte>(data, size);
}
}
/// <summary>
/// Reads a UTF-8 string from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <param name="length">The length of bytes to read.</param>
/// <returns>Returns the string read from memory.</returns>
public string ReadString(int address, int length)
{
return Encoding.UTF8.GetString(Span.Slice(address, length));
}
/// <summary>
/// Reads a null-terminated UTF-8 string from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the string read from memory.</returns>
public string ReadNullTerminatedString(int address)
{
var slice = Span.Slice(address);
var terminator = slice.IndexOf((byte)0);
if (terminator == -1)
{
throw new InvalidOperationException("string is not null terminated");
}
return Encoding.UTF8.GetString(slice.Slice(0, terminator));
}
/// <summary>
/// Writes a UTF-8 string at the given address.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The string to write.</param>
/// <return>Returns the number of bytes written.</return>
public int WriteString(int address, string value)
{
return Encoding.UTF8.GetBytes(value, Span.Slice(address));
}
/// <summary>
/// Reads a byte from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the byte read from memory.</returns>
public byte ReadByte(int address)
{
return Span[address];
}
/// <summary>
/// Writes a byte to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The byte to write.</param>
public void WriteByte(int address, byte value)
{
Span[address] = value;
}
/// <summary>
/// Reads a short from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the short read from memory.</returns>
public short ReadInt16(int address)
{
return BinaryPrimitives.ReadInt16LittleEndian(Span.Slice(address, 2));
}
/// <summary>
/// Writes a short to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The short to write.</param>
public void WriteInt16(int address, short value)
{
BinaryPrimitives.WriteInt16LittleEndian(Span.Slice(address, 2), value);
}
/// <summary>
/// Reads an int from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the int read from memory.</returns>
public int ReadInt32(int address)
{
return BinaryPrimitives.ReadInt32LittleEndian(Span.Slice(address, 4));
}
/// <summary>
/// Writes an int to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The int to write.</param>
public void WriteInt32(int address, int value)
{
BinaryPrimitives.WriteInt32LittleEndian(Span.Slice(address, 4), value);
}
/// <summary>
/// Reads a long from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the long read from memory.</returns>
public long ReadInt64(int address)
{
return BinaryPrimitives.ReadInt64LittleEndian(Span.Slice(address, 8));
}
/// <summary>
/// Writes a long to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The long to write.</param>
public void WriteInt64(int address, long value)
{
BinaryPrimitives.WriteInt64LittleEndian(Span.Slice(address, 8), value);
}
/// <summary>
/// Reads an IntPtr from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the IntPtr read from memory.</returns>
public IntPtr ReadIntPtr(int address)
{
if (IntPtr.Size == 4)
{
return (IntPtr)ReadInt32(address);
}
return (IntPtr)ReadInt64(address);
}
/// <summary>
/// Writes an IntPtr to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The IntPtr to write.</param>
public void WriteIntPtr(int address, IntPtr value)
{
if (IntPtr.Size == 4)
{
WriteInt32(address, value.ToInt32());
}
else
{
WriteInt64(address, value.ToInt64());
}
}
/// <summary>
/// Reads a long from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the long read from memory.</returns>
public float ReadSingle(int address)
{
unsafe
{
var i = ReadInt32(address);
return *((float*)&i);
}
}
/// <summary>
/// Writes a single to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The single to write.</param>
public void WriteSingle(int address, float value)
{
unsafe
{
WriteInt32(address, *(int*)&value);
}
}
/// <summary>
/// Reads a double from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the double read from memory.</returns>
public double ReadDouble(int address)
{
unsafe
{
var i = ReadInt64(address);
return *((double*)&i);
}
}
/// <summary>
/// Writes a double to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The double to write.</param>
public void WriteDouble(int address, double value)
{
unsafe
{
WriteInt64(address, *(long*)&value);
}
}
protected override IntPtr MemoryHandle => _memory;
private MemoryExport _export;
private IntPtr _memory;

View File

@@ -0,0 +1,355 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
namespace Wasmtime
{
/// <summary>
/// Represents a host function.
/// </summary>
public class Function : IDisposable
{
/// <inheritdoc/>
public void Dispose()
{
if (!Handle.IsInvalid)
{
Handle.Dispose();
Handle.SetHandleAsInvalid();
}
}
internal Function(Interop.StoreHandle store, Delegate func, bool hasReturn)
{
if (func is null)
{
throw new ArgumentNullException(nameof(func));
}
var type = func.GetType();
Span<Type> parameterTypes = null;
Type returnType = null;
if (hasReturn)
{
parameterTypes = type.GenericTypeArguments[0..^1];
returnType = type.GenericTypeArguments[^1];
}
else
{
parameterTypes = type.GenericTypeArguments;
returnType = null;
}
bool hasCaller = parameterTypes.Length > 0 && parameterTypes[0] == typeof(Caller);
if (hasCaller)
{
parameterTypes = parameterTypes[1..];
}
ValidateParameterTypes(parameterTypes);
ValidateReturnType(returnType);
var parameters = CreateValueTypeVec(parameterTypes);
var results = CreateReturnValueTypeVec(returnType);
using var funcType = Interop.wasm_functype_new(ref parameters, ref results);
if (hasCaller)
{
Callback = CreateCallbackWithCaller(store, func, parameterTypes.Length, hasReturn);
Handle = Interop.wasmtime_func_new(store, funcType, (Interop.WasmtimeFuncCallback)Callback);
}
else
{
Callback = CreateCallback(store, func, parameterTypes.Length, hasReturn);
Handle = Interop.wasm_func_new(store, funcType, (Interop.WasmFuncCallback)Callback);
}
if (Handle.IsInvalid)
{
throw new WasmtimeException("Failed to create Wasmtime function.");
}
}
private static void ValidateParameterTypes(Span<Type> parameters)
{
foreach (var type in parameters)
{
if (type == typeof(Caller))
{
throw new WasmtimeException($"A Caller parameter must be the first parameter of the function.");
}
if (!Interop.TryGetValueKind(type, out var kind))
{
throw new WasmtimeException($"Unable to create a function with parameter of type '{type.ToString()}'.");
}
}
}
private static void ValidateReturnType(Type returnType)
{
if (returnType is null)
{
return;
}
if (IsTuple(returnType))
{
var types = returnType
.GetGenericArguments()
.SelectMany(type =>
{
if (type.IsConstructedGenericType)
{
return type.GenericTypeArguments;
}
return Enumerable.Repeat(type, 1);
});
foreach (var type in types)
{
ValidateReturnType(type);
}
return;
}
if (!Interop.TryGetValueKind(returnType, out var kind))
{
throw new WasmtimeException($"Unable to create a function with a return type of type '{returnType.ToString()}'.");
}
}
private static bool IsTuple(Type type)
{
if (!type.IsConstructedGenericType)
{
return false;
}
var definition = type.GetGenericTypeDefinition();
return definition == typeof(ValueTuple) ||
definition == typeof(ValueTuple<>) ||
definition == typeof(ValueTuple<,>) ||
definition == typeof(ValueTuple<,,>) ||
definition == typeof(ValueTuple<,,,>) ||
definition == typeof(ValueTuple<,,,,>) ||
definition == typeof(ValueTuple<,,,,,>) ||
definition == typeof(ValueTuple<,,,,,,>) ||
definition == typeof(ValueTuple<,,,,,,,>);
}
private static unsafe Interop.WasmFuncCallback CreateCallback(Interop.StoreHandle store, Delegate func, int parameterCount, bool hasReturn)
{
// NOTE: this capture is not thread-safe.
var args = new object[parameterCount];
var method = func.Method;
var target = func.Target;
return (arguments, results) =>
{
try
{
SetArgs(arguments, args);
var result = method.Invoke(target, BindingFlags.DoNotWrapExceptions, null, args, null);
if (hasReturn)
{
SetResults(result, results);
}
return IntPtr.Zero;
}
catch (Exception ex)
{
var bytes = Encoding.UTF8.GetBytes(ex.Message + "\0" /* exception messages need a null */);
fixed (byte* ptr = bytes)
{
Interop.wasm_byte_vec_t message = new Interop.wasm_byte_vec_t();
message.size = (UIntPtr)bytes.Length;
message.data = ptr;
return Interop.wasm_trap_new(store, ref message);
}
}
};
}
private static unsafe Interop.WasmtimeFuncCallback CreateCallbackWithCaller(Interop.StoreHandle store, Delegate func, int parameterCount, bool hasReturn)
{
// NOTE: this capture is not thread-safe.
var args = new object[parameterCount + 1];
var caller = new Caller();
var method = func.Method;
var target = func.Target;
args[0] = caller;
return (callerHandle, arguments, results) =>
{
try
{
caller.Handle = callerHandle;
SetArgs(arguments, args, 1);
var result = method.Invoke(target, BindingFlags.DoNotWrapExceptions, null, args, null);
caller.Handle = IntPtr.Zero;
if (hasReturn)
{
SetResults(result, results);
}
return IntPtr.Zero;
}
catch (Exception ex)
{
var bytes = Encoding.UTF8.GetBytes(ex.Message + "\0" /* exception messages need a null */);
fixed (byte* ptr = bytes)
{
Interop.wasm_byte_vec_t message = new Interop.wasm_byte_vec_t();
message.size = (UIntPtr)bytes.Length;
message.data = ptr;
return Interop.wasm_trap_new(store, ref message);
}
}
};
}
private static unsafe void SetArgs(Interop.wasm_val_t* arguments, object[] args, int offset = 0)
{
for (int i = 0; i < args.Length - offset; ++i)
{
var arg = arguments[i];
switch (arg.kind)
{
case Interop.wasm_valkind_t.WASM_I32:
args[i + offset] = arg.of.i32;
break;
case Interop.wasm_valkind_t.WASM_I64:
args[i + offset] = arg.of.i64;
break;
case Interop.wasm_valkind_t.WASM_F32:
args[i + offset] = arg.of.f32;
break;
case Interop.wasm_valkind_t.WASM_F64:
args[i + offset] = arg.of.f64;
break;
default:
throw new NotSupportedException("Unsupported value type.");
}
}
}
private static unsafe void SetResults(object value, Interop.wasm_val_t* results)
{
var tuple = value as ITuple;
if (tuple is null)
{
SetResult(value, &results[0]);
}
else
{
for (int i = 0; i < tuple.Length; ++i)
{
SetResults(tuple[i], &results[i]);
}
}
}
private static unsafe void SetResult(object value, Interop.wasm_val_t* result)
{
switch (value)
{
case int i:
result->kind = Interop.wasm_valkind_t.WASM_I32;
result->of.i32 = i;
break;
case long l:
result->kind = Interop.wasm_valkind_t.WASM_I64;
result->of.i64 = l;
break;
case float f:
result->kind = Interop.wasm_valkind_t.WASM_F32;
result->of.f32 = f;
break;
case double d:
result->kind = Interop.wasm_valkind_t.WASM_F64;
result->of.f64 = d;
break;
default:
throw new NotSupportedException("Unsupported return value type.");
}
}
private static Interop.wasm_valtype_vec_t CreateValueTypeVec(Span<Type> types)
{
Interop.wasm_valtype_vec_t vec;
Interop.wasm_valtype_vec_new_uninitialized(out vec, (UIntPtr)types.Length);
int i = 0;
foreach (var type in types)
{
var valType = Interop.wasm_valtype_new((Interop.wasm_valkind_t)Interop.ToValueKind(type));
unsafe
{
vec.data[i++] = valType.DangerousGetHandle();
}
valType.SetHandleAsInvalid();
}
return vec;
}
private static Interop.wasm_valtype_vec_t CreateReturnValueTypeVec(Type returnType)
{
if (returnType is null)
{
Interop.wasm_valtype_vec_t vec;
Interop.wasm_valtype_vec_new_empty(out vec);
return vec;
}
if (IsTuple(returnType))
{
return CreateValueTypeVec(
returnType
.GetGenericArguments()
.SelectMany(type =>
{
if (type.IsConstructedGenericType)
{
return type.GenericTypeArguments;
}
return Enumerable.Repeat(type, 1);
})
.ToArray()
);
}
return CreateValueTypeVec(new Type[] { returnType });
}
internal Interop.FunctionHandle Handle { get; private set; }
internal Delegate Callback { get; private set; }
}
}

View File

@@ -5,18 +5,8 @@ namespace Wasmtime
/// <summary>
/// Represents a constant WebAssembly global value.
/// </summary>
public class Global<T>
public class Global<T> : IDisposable
{
/// <summary>
/// Creates a new <see cref="Global&lt;T&gt;" /> with the given initial value.
/// </summary>
/// <param name="initialValue">The initial value of the global.</param>
public Global(T initialValue)
{
InitialValue = initialValue;
Kind = Interop.ToValueKind(typeof(T));
}
/// <summary>
/// The value of the global.
/// </summary>
@@ -35,16 +25,58 @@ namespace Wasmtime
Interop.wasm_global_get(Handle.DangerousGetHandle(), v);
// TODO: figure out a way that doesn't box the value
return (T)Interop.ToObject(v);
}
}
}
internal ValueKind Kind { get; private set; }
/// <summary>
/// Gets the value kind of the global.
/// </summary>
/// <value></value>
public ValueKind Kind { get; private set; }
/// <inheritdoc/>
public void Dispose()
{
if (!Handle.IsInvalid)
{
Handle.Dispose();
Handle.SetHandleAsInvalid();
}
}
internal Global(Interop.StoreHandle store, T initialValue)
{
if (!Interop.TryGetValueKind(typeof(T), out var kind))
{
throw new WasmtimeException($"Global variables cannot be of type '{typeof(T).ToString()}'.");
}
Kind = kind;
var value = Interop.ToValue((object)initialValue, Kind);
var valueType = Interop.wasm_valtype_new(value.kind);
var valueTypeHandle = valueType.DangerousGetHandle();
valueType.SetHandleAsInvalid();
using var globalType = Interop.wasm_globaltype_new(
valueTypeHandle,
Interop.wasm_mutability_t.WASM_CONST
);
unsafe
{
Handle = Interop.wasm_global_new(store, globalType, &value);
if (Handle.IsInvalid)
{
throw new WasmtimeException("Failed to create Wasmtime global.");
}
}
}
internal Interop.GlobalHandle Handle { get; set; }
internal T InitialValue { get; private set; }
}
}

View File

@@ -0,0 +1,802 @@
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Wasmtime
{
/// <summary>
/// Represents a WebAssembly host environment.
/// </summary>
/// <remarks>
/// A host is used to configure the environment for WebAssembly modules to execute in.
/// </remarks>
public class Host : IDisposable
{
/// <summary>
/// Constructs a new host.
/// </summary>
public Host()
{
Initialize(Interop.wasm_engine_new());
}
/// <summary>
/// Defines a WASI implementation in the host.
/// </summary>
/// <param name="name">The name of the WASI module to define.</param>
/// <param name="config">The <see cref="WasiConfiguration"/> to configure the WASI implementation with.</param>
public void DefineWasi(string name, WasiConfiguration config = null)
{
CheckDisposed();
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("Name cannot be null or empty.", nameof(name));
}
if (config is null)
{
config = new WasiConfiguration();
}
using var wasi = config.CreateWasi(Store, name);
if (!Interop.wasmtime_linker_define_wasi(Linker, wasi))
{
throw new WasmtimeException($"Failed to define WASI module '{name}'.");
}
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction(string moduleName, string name, Action func)
{
return DefineFunction(moduleName, name, func, false);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T>(string moduleName, string name, Action<T> func)
{
return DefineFunction(moduleName, name, func, false);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2>(string moduleName, string name, Action<T1, T2> func)
{
return DefineFunction(moduleName, name, func, false);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3>(string moduleName, string name, Action<T1, T2, T3> func)
{
return DefineFunction(moduleName, name, func, false);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4>(string moduleName, string name, Action<T1, T2, T3, T4> func)
{
return DefineFunction(moduleName, name, func, false);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5>(string moduleName, string name, Action<T1, T2, T3, T4, T5> func)
{
return DefineFunction(moduleName, name, func, false);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6> func)
{
return DefineFunction(moduleName, name, func, false);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7> func)
{
return DefineFunction(moduleName, name, func, false);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7, T8> func)
{
return DefineFunction(moduleName, name, func, false);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9> func)
{
return DefineFunction(moduleName, name, func, false);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> func)
{
return DefineFunction(moduleName, name, func, false);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> func)
{
return DefineFunction(moduleName, name, func, false);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12> func)
{
return DefineFunction(moduleName, name, func, false);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13> func)
{
return DefineFunction(moduleName, name, func, false);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14> func)
{
return DefineFunction(moduleName, name, func, false);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15> func)
{
return DefineFunction(moduleName, name, func, false);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>(string moduleName, string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16> func)
{
return DefineFunction(moduleName, name, func, false);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<TResult>(string moduleName, string name, Func<TResult> func)
{
return DefineFunction(moduleName, name, func, true);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T, TResult>(string moduleName, string name, Func<T, TResult> func)
{
return DefineFunction(moduleName, name, func, true);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, TResult>(string moduleName, string name, Func<T1, T2, TResult> func)
{
return DefineFunction(moduleName, name, func, true);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, TResult>(string moduleName, string name, Func<T1, T2, T3, TResult> func)
{
return DefineFunction(moduleName, name, func, true);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, TResult> func)
{
return DefineFunction(moduleName, name, func, true);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, TResult> func)
{
return DefineFunction(moduleName, name, func, true);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, TResult> func)
{
return DefineFunction(moduleName, name, func, true);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, TResult> func)
{
return DefineFunction(moduleName, name, func, true);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, TResult> func)
{
return DefineFunction(moduleName, name, func, true);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, TResult> func)
{
return DefineFunction(moduleName, name, func, true);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TResult> func)
{
return DefineFunction(moduleName, name, func, true);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, TResult> func)
{
return DefineFunction(moduleName, name, func, true);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, TResult> func)
{
return DefineFunction(moduleName, name, func, true);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, TResult> func)
{
return DefineFunction(moduleName, name, func, true);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, TResult> func)
{
return DefineFunction(moduleName, name, func, true);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, TResult> func)
{
return DefineFunction(moduleName, name, func, true);
}
/// <summary>
/// Defines a host function.
/// </summary>
/// <param name="moduleName">The module name of the host function.</param>
/// <param name="name">The name of the host function.</param>
/// <param name="func">The callback for when the host function is invoked.</param>
/// <returns>Returns a <see cref="Function"/> representing the host function.</returns>
public Function DefineFunction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult>(string moduleName, string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult> func)
{
return DefineFunction(moduleName, name, func, true);
}
/// <summary>
/// Defines a new host global variable.
/// </summary>
/// <param name="moduleName">The module name of the host variable.</param>
/// <param name="name">The name of the host variable.</param>
/// <param name="initialValue">The initial value of the host variable.</param>
/// <typeparam name="T">The type of the host variable.</typeparam>
/// <returns>Returns a new <see cref="Global"/> representing the defined global variable.</returns>
public Global<T> DefineGlobal<T>(string moduleName, string name, T initialValue)
{
CheckDisposed();
if (moduleName is null)
{
throw new ArgumentNullException(nameof(moduleName));
}
if (name is null)
{
throw new ArgumentNullException(nameof(name));
}
var global = new Global<T>(Store, initialValue);
if (!Define(moduleName, name, Interop.wasm_global_as_extern(global.Handle)))
{
global.Dispose();
throw new WasmtimeException($"Failed to define global '{name}' in module '{moduleName}'.");
}
return global;
}
/// <summary>
/// Defines a new host mutable global variable.
/// </summary>
/// <param name="moduleName">The module name of the host variable.</param>
/// <param name="name">The name of the host variable.</param>
/// <param name="initialValue">The initial value of the host variable.</param>
/// <typeparam name="T">The type of the host variable.</typeparam>
/// <returns>Returns a new <see cref="MutableGlobal"/> representing the defined mutable global variable.</returns>
public MutableGlobal<T> DefineMutableGlobal<T>(string moduleName, string name, T initialValue)
{
CheckDisposed();
if (moduleName is null)
{
throw new ArgumentNullException(nameof(moduleName));
}
if (name is null)
{
throw new ArgumentNullException(nameof(name));
}
var global = new MutableGlobal<T>(Store, initialValue);
if (!Define(moduleName, name, Interop.wasm_global_as_extern(global.Handle)))
{
global.Dispose();
throw new WasmtimeException($"Failed to define global '{name}' in module '{moduleName}'.");
}
return global;
}
/// <summary>
/// Defines a new host memory.
/// </summary>
/// <param name="moduleName">The module name of the host memory.</param>
/// <param name="name">The name of the host memory.</param>
/// <param name="minimum">The minimum number of pages for the host memory.</param>
/// <param name="maximum">The maximum number of pages for the host memory.</param>
/// <returns>Returns a new <see cref="Memory"/> representing the defined memory.</returns>
public Memory DefineMemory(string moduleName, string name, uint minimum = 1, uint maximum = uint.MaxValue)
{
CheckDisposed();
if (moduleName is null)
{
throw new ArgumentNullException(nameof(moduleName));
}
if (name is null)
{
throw new ArgumentNullException(nameof(name));
}
var memory = new Memory(Store, minimum, maximum);
if (!Define(moduleName, name, Interop.wasm_memory_as_extern(memory.Handle)))
{
memory.Dispose();
throw new WasmtimeException($"Failed to define memory '{name}' in module '{moduleName}'.");
}
return memory;
}
/// <summary>
/// Loads a <see cref="Module"/> given the module name and bytes.
/// </summary>
/// <param name="name">The name of the module.</param>
/// <param name="bytes">The bytes of the module.</param>
/// <returns>Returns a new <see cref="Module"/>.</returns>
public Module LoadModule(string name, byte[] bytes)
{
CheckDisposed();
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException(nameof(name));
}
if (bytes is null)
{
throw new ArgumentNullException(nameof(bytes));
}
return new Module(Store, name, bytes);
}
/// <summary>
/// Loads a <see cref="Module"/> given the path to the WebAssembly file.
/// </summary>
/// <param name="path">The path to the WebAssembly file.</param>
/// <returns>Returns a new <see cref="Module"/>.</returns>
public Module LoadModule(string path)
{
return LoadModule(Path.GetFileNameWithoutExtension(path), File.ReadAllBytes(path));
}
/// <summary>
/// Loads a <see cref="Module"/> based on a WebAssembly text format representation.
/// </summary>
/// <param name="name">The name of the module.</param>
/// <param name="text">The WebAssembly text format representation of the module.</param>
/// <returns>Returns a new <see cref="Module"/>.</returns>
public Module LoadModuleText(string name, string text)
{
CheckDisposed();
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException(nameof(name));
}
if (text is null)
{
throw new ArgumentNullException(nameof(text));
}
var textBytes = Encoding.UTF8.GetBytes(text);
unsafe
{
fixed (byte *ptr = textBytes)
{
Interop.wasm_byte_vec_t textVec;
textVec.size = (UIntPtr)textBytes.Length;
textVec.data = ptr;
if (!Interop.wasmtime_wat2wasm(ref textVec, out var bytes, out var error))
{
var errorSpan = new ReadOnlySpan<byte>(error.data, checked((int)error.size));
var message = Encoding.UTF8.GetString(errorSpan);
Interop.wasm_byte_vec_delete(ref error);
throw new WasmtimeException($"Failed to parse module text: {message}");
}
var byteSpan = new ReadOnlySpan<byte>(bytes.data, checked((int)bytes.size));
var moduleBytes = byteSpan.ToArray();
Interop.wasm_byte_vec_delete(ref bytes);
return LoadModule(name, moduleBytes);
}
}
}
/// <summary>
/// Loads a <see cref="Module"/> based on the path to a WebAssembly text format file.
/// </summary>
/// <param name="path">The path to the WebAssembly text format file.</param>
/// <returns>Returns a new <see cref="Module"/>.</returns>
public Module LoadModuleText(string path)
{
return LoadModuleText(Path.GetFileNameWithoutExtension(path), File.ReadAllText(path));
}
/// <summary>
/// Instantiates a WebAssembly module.
/// </summary>
/// <param name="module">The module to instantiate.</param>
/// <returns>Returns a new <see cref="Instance" />.</returns>
public Instance Instantiate(Module module)
{
CheckDisposed();
if (module is null)
{
throw new ArgumentNullException(nameof(module));
}
return new Instance(Linker, module);
}
/// <summary>
/// Clears all existing definitions in the host.
/// </summary>
public void ClearDefinitions()
{
CheckDisposed();
var linker = Interop.wasmtime_linker_new(Store, allowShadowing: true);
if (linker.IsInvalid)
{
throw new WasmtimeException("Failed to create Wasmtime linker.");
}
Linker.Dispose();
Linker = linker;
}
/// <inheritdoc/>
public void Dispose()
{
if (!Linker.IsInvalid)
{
Linker.Dispose();
Linker.SetHandleAsInvalid();
}
if (!Store.IsInvalid)
{
Store.Dispose();
Store.SetHandleAsInvalid();
}
if (!Engine.IsInvalid)
{
Engine.Dispose();
Engine.SetHandleAsInvalid();
}
}
internal Host(Interop.WasmConfigHandle config)
{
var engine = Interop.wasm_engine_new_with_config(config);
config.SetHandleAsInvalid();
Initialize(engine);
}
private void Initialize(Interop.EngineHandle engine)
{
if (engine.IsInvalid)
{
throw new WasmtimeException("Failed to create Wasmtime engine.");
}
var store = Interop.wasm_store_new(engine);
if (store.IsInvalid)
{
throw new WasmtimeException("Failed to create Wasmtime store.");
}
var linker = Interop.wasmtime_linker_new(store, allowShadowing: true);
if (linker.IsInvalid)
{
throw new WasmtimeException("Failed to create Wasmtime linker.");
}
Engine = engine;
Store = store;
Linker = linker;
}
private void CheckDisposed()
{
if (Engine.IsInvalid)
{
throw new ObjectDisposedException(typeof(Host).FullName);
}
}
private Function DefineFunction(string moduleName, string name, Delegate func, bool hasReturn)
{
if (moduleName is null)
{
throw new ArgumentNullException(nameof(moduleName));
}
if (name is null)
{
throw new ArgumentNullException(nameof(name));
}
if (func is null)
{
throw new ArgumentNullException(nameof(func));
}
var function = new Function(Store, func, hasReturn);
if (!Define(moduleName, name, Interop.wasm_func_as_extern(function.Handle)))
{
function.Dispose();
throw new WasmtimeException($"Failed to define function '{name}' in module '{moduleName}'.");
}
_callbacks.Add(function.Callback);
return function;
}
private bool Define(string moduleName, string name, IntPtr ext)
{
var moduleNameBytes = Encoding.UTF8.GetBytes(moduleName);
var nameBytes = Encoding.UTF8.GetBytes(name);
unsafe
{
fixed (byte* moduleNamePtr = moduleNameBytes)
fixed (byte* namePtr = nameBytes)
{
Interop.wasm_byte_vec_t moduleNameVec = new Interop.wasm_byte_vec_t();
moduleNameVec.size = (UIntPtr)moduleNameBytes.Length;
moduleNameVec.data = moduleNamePtr;
Interop.wasm_byte_vec_t nameVec = new Interop.wasm_byte_vec_t();
nameVec.size = (UIntPtr)nameBytes.Length;
nameVec.data = namePtr;
return Interop.wasmtime_linker_define(Linker, ref moduleNameVec, ref nameVec, ext);
}
}
}
internal Interop.EngineHandle Engine { get; private set; }
internal Interop.StoreHandle Store { get; private set; }
internal Interop.LinkerHandle Linker { get; private set; }
private List<Delegate> _callbacks = new List<Delegate>();
}
}

View File

@@ -43,23 +43,16 @@ namespace Wasmtime
}
/// <summary>
/// Represents a builder of <see cref="Engine"/> instances.
/// Represents a builder of <see cref="Host"/> instances.
/// </summary>
public class EngineBuilder
public class HostBuilder
{
/// <summary>
/// Constructs a new <see cref="EngineBuilder" />.
/// </summary>
public EngineBuilder()
{
}
/// <summary>
/// Sets whether or not to enable debug information.
/// </summary>
/// <param name="enable">True to enable debug information or false to disable.</param>
/// <returns>Returns the current builder.</returns>
public EngineBuilder WithDebugInfo(bool enable)
public HostBuilder WithDebugInfo(bool enable)
{
_enableDebugInfo = enable;
return this;
@@ -70,7 +63,7 @@ namespace Wasmtime
/// </summary>
/// <param name="enable">True to enable WebAssembly threads support or false to disable.</param>
/// <returns>Returns the current builder.</returns>
public EngineBuilder WithWasmThreads(bool enable)
public HostBuilder WithWasmThreads(bool enable)
{
_enableWasmThreads = enable;
return this;
@@ -81,7 +74,7 @@ namespace Wasmtime
/// </summary>
/// <param name="enable">True to enable WebAssembly reference types support or false to disable.</param>
/// <returns>Returns the current builder.</returns>
public EngineBuilder WithReferenceTypes(bool enable)
public HostBuilder WithReferenceTypes(bool enable)
{
_enableReferenceTypes = enable;
return this;
@@ -92,7 +85,7 @@ namespace Wasmtime
/// </summary>
/// <param name="enable">True to enable WebAssembly SIMD support or false to disable.</param>
/// <returns>Returns the current builder.</returns>
public EngineBuilder WithSIMD(bool enable)
public HostBuilder WithSIMD(bool enable)
{
_enableSIMD = enable;
return this;
@@ -103,7 +96,7 @@ namespace Wasmtime
/// </summary>
/// <param name="enable">True to enable WebAssembly multi-value support or false to disable.</param>
/// <returns>Returns the current builder.</returns>
public EngineBuilder WithMultiValue(bool enable)
public HostBuilder WithMultiValue(bool enable)
{
_enableMultiValue = enable;
return this;
@@ -114,7 +107,7 @@ namespace Wasmtime
/// </summary>
/// <param name="enable">True to enable WebAssembly bulk memory support or false to disable.</param>
/// <returns>Returns the current builder.</returns>
public EngineBuilder WithBulkMemory(bool enable)
public HostBuilder WithBulkMemory(bool enable)
{
_enableBulkMemory = enable;
return this;
@@ -125,7 +118,7 @@ namespace Wasmtime
/// </summary>
/// <param name="strategy">The compiler strategy to use.</param>
/// <returns>Returns the current builder.</returns>
public EngineBuilder WithCompilerStrategy(CompilerStrategy strategy)
public HostBuilder WithCompilerStrategy(CompilerStrategy strategy)
{
switch (strategy)
{
@@ -152,7 +145,7 @@ namespace Wasmtime
/// </summary>
/// <param name="enable">True to enable the Cranelift debug verifier or false to disable.</param>
/// <returns>Returns the current builder.</returns>
public EngineBuilder WithCraneliftDebugVerifier(bool enable)
public HostBuilder WithCraneliftDebugVerifier(bool enable)
{
_enableCraneliftDebugVerifier = enable;
return this;
@@ -163,7 +156,7 @@ namespace Wasmtime
/// </summary>
/// <param name="level">The optimization level to use.</param>
/// <returns>Returns the current builder.</returns>
public EngineBuilder WithOptimizationLevel(OptimizationLevel level)
public HostBuilder WithOptimizationLevel(OptimizationLevel level)
{
switch (level)
{
@@ -186,10 +179,10 @@ namespace Wasmtime
}
/// <summary>
/// Builds the <see cref="Engine" /> instance.
/// Builds the <see cref="Host" /> instance.
/// </summary>
/// <returns>Returns the new <see cref="Engine" /> instance.</returns>
public Engine Build()
/// <returns>Returns the new <see cref="Host" /> instance.</returns>
public Host Build()
{
var config = Interop.wasm_config_new();
@@ -238,7 +231,7 @@ namespace Wasmtime
Interop.wasmtime_config_cranelift_opt_level_set(config, _optLevel.Value);
}
return new Engine(config);
return new Host(config);
}
private bool? _enableDebugInfo;

View File

@@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using Wasmtime.Bindings;
namespace Wasmtime
{
/// <summary>
/// The interface implemented by Wasmtime hosts.
/// </summary>
public interface IHost
{
/// <summary>
/// The <see cref="Wasmtime.Instance" /> that the host is bound to.
/// </summary>
/// <remarks>A host can only bind to one module instance at a time.</remarks>
Instance Instance { get; set; }
}
}

View File

@@ -1,31 +0,0 @@
using System;
namespace Wasmtime
{
/// <summary>
/// Used to mark .NET methods and fields as imports to a WebAssembly module.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
public class ImportAttribute : Attribute
{
/// <summary>
/// Constructs a new <see cref="ImportAttribute"/>.
/// </summary>
/// <param name="name">The name of the import.</param>
public ImportAttribute(string name)
{
Name = name;
}
/// <summary>
/// The name of the import.
/// </summary>
public string Name { get; set; }
/// <summary>
/// The module name of the import.
/// </summary>
/// <remarks>A null or empty module name implies that the import is not scoped to a module.</remarks>
public string Module { get; set; }
}
}

View File

@@ -4,7 +4,6 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Dynamic;
using Wasmtime.Externs;
using Wasmtime.Bindings;
namespace Wasmtime
{
@@ -13,49 +12,6 @@ namespace Wasmtime
/// </summary>
public class Instance : DynamicObject, IDisposable
{
internal Instance(Module module, Wasi wasi = null, IHost host = null)
{
Host = host;
Module = module;
// Save the bindings to root the objects.
// Otherwise the GC may collect the callback delegates from FunctionHandles for example.
_bindings = Binding.GetImportBindings(module, wasi, host)
.Select(b => b.Bind(module.Store, host))
.ToArray();
unsafe
{
Handle = Interop.wasm_instance_new(
Module.Store.Handle,
Module.Handle,
_bindings.Select(h => ToExtern(h)).ToArray(),
out var trap);
if (trap != IntPtr.Zero)
{
throw TrapException.FromOwnedTrap(trap);
}
}
if (Handle.IsInvalid)
{
throw new WasmtimeException($"Failed to instantiate module '{module.Name}'.");
}
Interop.wasm_instance_exports(Handle, out _externs);
Externs = new Wasmtime.Externs.Externs(Module.Exports, _externs);
_functions = Externs.Functions.ToDictionary(f => f.Name);
_globals = Externs.Globals.ToDictionary(g => g.Name);
}
/// <summary>
/// The host associated with this instance.
/// </summary>
public IHost Host { get; private set; }
/// <summary>
/// The WebAssembly module associated with the instantiation.
/// </summary>
@@ -75,15 +31,6 @@ namespace Wasmtime
Handle.SetHandleAsInvalid();
}
if (!(_bindings is null))
{
foreach (var binding in _bindings)
{
binding.Dispose();
}
_bindings = null;
}
if (!(_externs.data is null))
{
Interop.wasm_extern_vec_delete(ref _externs);
@@ -127,29 +74,35 @@ namespace Wasmtime
return true;
}
private static unsafe IntPtr ToExtern(SafeHandle handle)
internal Instance(Interop.LinkerHandle linker, Module module)
{
switch (handle)
Module = module;
unsafe
{
case Interop.FunctionHandle f:
return Interop.wasm_func_as_extern(f);
Handle = Interop.wasmtime_linker_instantiate(linker, module.Handle, out var trap);
case Interop.GlobalHandle g:
return Interop.wasm_global_as_extern(g);
case Interop.MemoryHandle m:
return Interop.wasm_memory_as_extern(m);
case Interop.WasiExportHandle w:
return w.DangerousGetHandle();
default:
throw new NotSupportedException("Unexpected handle type.");
if (trap != IntPtr.Zero)
{
throw TrapException.FromOwnedTrap(trap);
}
}
if (Handle.IsInvalid)
{
throw new WasmtimeException("Failed to create Wasmtime instance.");
}
Interop.wasm_instance_exports(Handle, out _externs);
Externs = new Wasmtime.Externs.Externs(Module.Exports, _externs);
_functions = Externs.Functions.ToDictionary(f => f.Name);
_globals = Externs.Globals.ToDictionary(g => g.Name);
}
internal Interop.InstanceHandle Handle { get; private set; }
private SafeHandle[] _bindings;
private Interop.wasm_extern_vec_t _externs;
private Dictionary<string, ExternFunction> _functions;
private Dictionary<string, ExternGlobal> _globals;

View File

@@ -64,8 +64,6 @@ namespace Wasmtime
{
}
public WasmFuncCallback Callback { get; set; } = null;
public override bool IsInvalid => handle == IntPtr.Zero;
protected override bool ReleaseHandle()
@@ -240,6 +238,36 @@ namespace Wasmtime
}
}
internal class LinkerHandle : SafeHandle
{
public LinkerHandle() : base(IntPtr.Zero, true)
{
}
public override bool IsInvalid => handle == IntPtr.Zero;
protected override bool ReleaseHandle()
{
Interop.wasmtime_linker_delete(handle);
return true;
}
}
internal class ExternHandle : SafeHandle
{
public ExternHandle() : base(IntPtr.Zero, true)
{
}
public override bool IsInvalid => handle == IntPtr.Zero;
protected override bool ReleaseHandle()
{
Interop.wasm_extern_delete(handle);
return true;
}
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct wasm_byte_vec_t
{
@@ -474,6 +502,8 @@ namespace Wasmtime
internal unsafe delegate IntPtr WasmFuncCallback(wasm_val_t* parameters, wasm_val_t* results);
internal unsafe delegate IntPtr WasmtimeFuncCallback(IntPtr caller, wasm_val_t* parameters, wasm_val_t* results);
internal enum wasm_externkind_t : byte
{
WASM_EXTERN_FUNC,
@@ -706,6 +736,11 @@ namespace Wasmtime
[DllImport(LibraryName)]
public static extern IntPtr wasm_extern_as_memory(IntPtr ext);
// Externs
[DllImport(LibraryName)]
public static extern void wasm_extern_delete(IntPtr ext);
// Extern type imports
[DllImport(LibraryName)]
@@ -976,13 +1011,41 @@ namespace Wasmtime
[DllImport(LibraryName)]
public static extern void wasmtime_config_cranelift_opt_level_set(WasmConfigHandle config, wasmtime_opt_level_t level);
// Utility functions
[DllImport(LibraryName, CharSet=CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.I1)]
public static extern bool wasmtime_wat2wasm(
EngineHandle engine,
ref wasm_byte_vec_t wat,
out wasm_byte_vec_t vec,
out wasm_byte_vec_t error_message
);
public static extern bool wasmtime_wat2wasm(ref wasm_byte_vec_t text, out wasm_byte_vec_t bytes, out wasm_byte_vec_t error);
// Linking functions
[DllImport(LibraryName)]
public static extern LinkerHandle wasmtime_linker_new(StoreHandle store, [MarshalAs(UnmanagedType.I1)] bool allowShadowing);
[DllImport(LibraryName)]
public static extern void wasmtime_linker_delete(IntPtr linker);
[DllImport(LibraryName)]
[return: MarshalAs(UnmanagedType.I1)]
public static extern bool wasmtime_linker_define(LinkerHandle linker, ref wasm_byte_vec_t module, ref wasm_byte_vec_t name, IntPtr externType);
[DllImport(LibraryName)]
[return: MarshalAs(UnmanagedType.I1)]
public static extern bool wasmtime_linker_define_wasi(LinkerHandle linker, WasiInstanceHandle wasi);
[DllImport(LibraryName)]
[return: MarshalAs(UnmanagedType.I1)]
public static extern bool wasmtime_linker_define_instance(LinkerHandle linker, ref wasm_byte_vec_t name, InstanceHandle instance);
[DllImport(LibraryName)]
public static extern InstanceHandle wasmtime_linker_instantiate(LinkerHandle linker, ModuleHandle module, out IntPtr trap);
// Caller functions
[DllImport(LibraryName)]
public static extern FunctionHandle wasmtime_func_new(StoreHandle store, FuncTypeHandle type, WasmtimeFuncCallback callback);
[DllImport(LibraryName)]
public static extern ExternHandle wasmtime_caller_export_get(IntPtr caller, ref wasm_byte_vec_t name);
}
}

View File

@@ -7,7 +7,7 @@ namespace Wasmtime
/// <summary>
/// Represents a WebAssembly memory.
/// </summary>
public class Memory
public class Memory : MemoryBase, IDisposable
{
/// <summary>
/// The size, in bytes, of a WebAssembly memory page.
@@ -15,11 +15,26 @@ namespace Wasmtime
public const int PageSize = 65536;
/// <summary>
/// Creates a new memory with the given minimum and maximum page counts.
/// The minimum memory size (in WebAssembly page units).
/// </summary>
/// <param name="minimum"></param>
/// <param name="maximum"></param>
public Memory(uint minimum = 1, uint maximum = uint.MaxValue)
public uint Minimum { get; private set; }
/// <summary>
/// The minimum memory size (in WebAssembly page units).
/// </summary>
public uint Maximum { get; private set; }
/// <inheritdoc/>
public void Dispose()
{
if (!Handle.IsInvalid)
{
Handle.Dispose();
Handle.SetHandleAsInvalid();
}
}
internal Memory(Interop.StoreHandle store, uint minimum = 1, uint maximum = uint.MaxValue)
{
if (minimum == 0)
{
@@ -33,238 +48,25 @@ namespace Wasmtime
Minimum = minimum;
Maximum = maximum;
}
/// <summary>
/// The minimum memory size (in WebAssembly page units).
/// </summary>
public uint Minimum { get; private set; }
/// <summary>
/// The minimum memory size (in WebAssembly page units).
/// </summary>
public uint Maximum { get; private set; }
/// <summary>
/// The span of the memory.
/// </summary>
/// <remarks>
/// The span may become invalid if the memory grows.
///
/// This may happen if the memory is explicitly requested to grow or
/// grows as a result of WebAssembly execution.
///
/// Therefore, the returned Span should not be stored.
/// </remarks>
public unsafe Span<byte> Span
{
get
{
var data = Interop.wasm_memory_data(_handle.DangerousGetHandle());
var size = Convert.ToInt32(Interop.wasm_memory_data_size(_handle.DangerousGetHandle()).ToUInt32());
return new Span<byte>(data, size);
}
}
/// <summary>
/// Reads a UTF-8 string from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <param name="length">The length of bytes to read.</param>
/// <returns>Returns the string read from memory.</returns>
public string ReadString(int address, int length)
{
return Encoding.UTF8.GetString(Span.Slice(address, length));
}
/// <summary>
/// Writes a UTF-8 string at the given address.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The string to write.</param>
/// <return>Returns the number of bytes written.</return>
public int WriteString(int address, string value)
{
return Encoding.UTF8.GetBytes(value, Span.Slice(address));
}
/// <summary>
/// Reads a byte from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the byte read from memory.</returns>
public byte ReadByte(int address)
{
return Span[address];
}
/// <summary>
/// Writes a byte to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The byte to write.</param>
public void WriteByte(int address, byte value)
{
Span[address] = value;
}
/// <summary>
/// Reads a short from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the short read from memory.</returns>
public short ReadInt16(int address)
{
return BinaryPrimitives.ReadInt16LittleEndian(Span.Slice(address, 2));
}
/// <summary>
/// Writes a short to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The short to write.</param>
public void WriteInt16(int address, short value)
{
BinaryPrimitives.WriteInt16LittleEndian(Span.Slice(address, 2), value);
}
/// <summary>
/// Reads an int from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the int read from memory.</returns>
public int ReadInt32(int address)
{
return BinaryPrimitives.ReadInt32LittleEndian(Span.Slice(address, 4));
}
/// <summary>
/// Writes an int to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The int to write.</param>
public void WriteInt32(int address, int value)
{
BinaryPrimitives.WriteInt32LittleEndian(Span.Slice(address, 4), value);
}
/// <summary>
/// Reads a long from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the long read from memory.</returns>
public long ReadInt64(int address)
{
return BinaryPrimitives.ReadInt64LittleEndian(Span.Slice(address, 8));
}
/// <summary>
/// Writes a long to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The long to write.</param>
public void WriteInt64(int address, long value)
{
BinaryPrimitives.WriteInt64LittleEndian(Span.Slice(address, 8), value);
}
/// <summary>
/// Reads an IntPtr from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the IntPtr read from memory.</returns>
public IntPtr ReadIntPtr(int address)
{
if (IntPtr.Size == 4)
{
return (IntPtr)ReadInt32(address);
}
return (IntPtr)ReadInt64(address);
}
/// <summary>
/// Writes an IntPtr to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The IntPtr to write.</param>
public void WriteIntPtr(int address, IntPtr value)
{
if (IntPtr.Size == 4)
{
WriteInt32(address, value.ToInt32());
}
else
{
WriteInt64(address, value.ToInt64());
}
}
/// <summary>
/// Reads a long from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the long read from memory.</returns>
public float ReadSingle(int address)
{
unsafe
{
var i = ReadInt32(address);
return *((float*)&i);
Interop.wasm_limits_t limits = new Interop.wasm_limits_t();
limits.min = minimum;
limits.max = maximum;
using var memoryType = Interop.wasm_memorytype_new(&limits);
Handle = Interop.wasm_memory_new(store, memoryType);
if (Handle.IsInvalid)
{
throw new WasmtimeException("Failed to create Wasmtime memory.");
}
}
}
/// <summary>
/// Writes a single to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The single to write.</param>
public void WriteSingle(int address, float value)
{
unsafe
{
WriteInt32(address, *(int*)&value);
}
}
protected override IntPtr MemoryHandle => Handle.DangerousGetHandle();
/// <summary>
/// Reads a double from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the double read from memory.</returns>
public double ReadDouble(int address)
{
unsafe
{
var i = ReadInt64(address);
return *((double*)&i);
}
}
/// <summary>
/// Writes a double to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The double to write.</param>
public void WriteDouble(int address, double value)
{
unsafe
{
WriteInt64(address, *(long*)&value);
}
}
internal Interop.MemoryHandle Handle
{
get
{
return _handle;
}
set
{
_handle = value;
}
}
private Interop.MemoryHandle _handle;
internal Interop.MemoryHandle Handle { get; private set; }
}
}

View File

@@ -0,0 +1,236 @@
using System;
using System.Text;
using System.Buffers.Binary;
namespace Wasmtime
{
public abstract class MemoryBase
{
/// <summary>
/// The span of the memory.
/// </summary>
/// <remarks>
/// The span may become invalid if the memory grows.
///
/// This may happen if the memory is explicitly requested to grow or
/// grows as a result of WebAssembly execution.
///
/// Therefore, the returned Span should not be stored.
/// </remarks>
public unsafe Span<byte> Span
{
get
{
var data = Interop.wasm_memory_data(MemoryHandle);
var size = Convert.ToInt32(Interop.wasm_memory_data_size(MemoryHandle).ToUInt32());
return new Span<byte>(data, size);
}
}
/// <summary>
/// Reads a UTF-8 string from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <param name="length">The length of bytes to read.</param>
/// <returns>Returns the string read from memory.</returns>
public string ReadString(int address, int length)
{
return Encoding.UTF8.GetString(Span.Slice(address, length));
}
/// <summary>
/// Reads a null-terminated UTF-8 string from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the string read from memory.</returns>
public string ReadNullTerminatedString(int address)
{
var slice = Span.Slice(address);
var terminator = slice.IndexOf((byte)0);
if (terminator == -1)
{
throw new InvalidOperationException("string is not null terminated");
}
return Encoding.UTF8.GetString(slice.Slice(0, terminator));
}
/// <summary>
/// Writes a UTF-8 string at the given address.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The string to write.</param>
/// <return>Returns the number of bytes written.</return>
public int WriteString(int address, string value)
{
return Encoding.UTF8.GetBytes(value, Span.Slice(address));
}
/// <summary>
/// Reads a byte from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the byte read from memory.</returns>
public byte ReadByte(int address)
{
return Span[address];
}
/// <summary>
/// Writes a byte to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The byte to write.</param>
public void WriteByte(int address, byte value)
{
Span[address] = value;
}
/// <summary>
/// Reads a short from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the short read from memory.</returns>
public short ReadInt16(int address)
{
return BinaryPrimitives.ReadInt16LittleEndian(Span.Slice(address, 2));
}
/// <summary>
/// Writes a short to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The short to write.</param>
public void WriteInt16(int address, short value)
{
BinaryPrimitives.WriteInt16LittleEndian(Span.Slice(address, 2), value);
}
/// <summary>
/// Reads an int from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the int read from memory.</returns>
public int ReadInt32(int address)
{
return BinaryPrimitives.ReadInt32LittleEndian(Span.Slice(address, 4));
}
/// <summary>
/// Writes an int to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The int to write.</param>
public void WriteInt32(int address, int value)
{
BinaryPrimitives.WriteInt32LittleEndian(Span.Slice(address, 4), value);
}
/// <summary>
/// Reads a long from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the long read from memory.</returns>
public long ReadInt64(int address)
{
return BinaryPrimitives.ReadInt64LittleEndian(Span.Slice(address, 8));
}
/// <summary>
/// Writes a long to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The long to write.</param>
public void WriteInt64(int address, long value)
{
BinaryPrimitives.WriteInt64LittleEndian(Span.Slice(address, 8), value);
}
/// <summary>
/// Reads an IntPtr from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the IntPtr read from memory.</returns>
public IntPtr ReadIntPtr(int address)
{
if (IntPtr.Size == 4)
{
return (IntPtr)ReadInt32(address);
}
return (IntPtr)ReadInt64(address);
}
/// <summary>
/// Writes an IntPtr to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The IntPtr to write.</param>
public void WriteIntPtr(int address, IntPtr value)
{
if (IntPtr.Size == 4)
{
WriteInt32(address, value.ToInt32());
}
else
{
WriteInt64(address, value.ToInt64());
}
}
/// <summary>
/// Reads a long from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the long read from memory.</returns>
public float ReadSingle(int address)
{
unsafe
{
var i = ReadInt32(address);
return *((float*)&i);
}
}
/// <summary>
/// Writes a single to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The single to write.</param>
public void WriteSingle(int address, float value)
{
unsafe
{
WriteInt32(address, *(int*)&value);
}
}
/// <summary>
/// Reads a double from memory.
/// </summary>
/// <param name="address">The zero-based address to read from.</param>
/// <returns>Returns the double read from memory.</returns>
public double ReadDouble(int address)
{
unsafe
{
var i = ReadInt64(address);
return *((double*)&i);
}
}
/// <summary>
/// Writes a double to memory.
/// </summary>
/// <param name="address">The zero-based address to write to.</param>
/// <param name="value">The double to write.</param>
public void WriteDouble(int address, double value)
{
unsafe
{
WriteInt64(address, *(long*)&value);
}
}
protected abstract IntPtr MemoryHandle { get; }
}
}

View File

@@ -8,75 +8,6 @@ namespace Wasmtime
/// </summary>
public class Module : IDisposable
{
internal Module(Store store, string name, byte[] bytes)
{
if (store.Handle.IsInvalid)
{
throw new ArgumentNullException(nameof(store));
}
unsafe
{
fixed (byte *ptr = bytes)
{
Interop.wasm_byte_vec_t vec;
vec.size = (UIntPtr)bytes.Length;
vec.data = ptr;
Handle = Interop.wasm_module_new(store.Handle, ref vec);
}
if (Handle.IsInvalid)
{
throw new WasmtimeException($"WebAssembly module '{name}' is not valid.");
}
}
Store = store;
Name = name;
Imports = new Wasmtime.Imports.Imports(this);
Exports = new Wasmtime.Exports.Exports(this);
}
/// <summary>
/// Instantiates a WebAssembly module for the given host.
/// </summary>
/// <param name="host">The host to use for the WebAssembly module's instance.</param>
/// <returns>Returns a new <see cref="Instance" />.</returns>
public Instance Instantiate(IHost host = null)
{
return Instantiate(null, host);
}
/// <summary>
/// Instantiates a WebAssembly module for the given host.
/// </summary>
/// <param name="wasi">The WASI instance to use for WASI imports.</param>
/// <param name="host">The host to use for the WebAssembly module's instance.</param>
/// <returns>Returns a new <see cref="Instance" />.</returns>
public Instance Instantiate(Wasi wasi, IHost host = null)
{
if (!(host?.Instance is null))
{
throw new InvalidOperationException("The host has already been associated with an instantiated module.");
}
var instance = new Instance(this, wasi, host);
if (!(host is null))
{
host.Instance = instance;
return instance;
}
return instance;
}
/// <summary>
/// The <see cref="Store"/> associated with the module.
/// </summary>
public Store Store { get; private set; }
/// <summary>
/// The name of the module.
/// </summary>
@@ -108,6 +39,30 @@ namespace Wasmtime
}
}
internal Module(Interop.StoreHandle store, string name, byte[] bytes)
{
unsafe
{
fixed (byte *ptr = bytes)
{
Interop.wasm_byte_vec_t vec;
vec.size = (UIntPtr)bytes.Length;
vec.data = ptr;
Handle = Interop.wasm_module_new(store, ref vec);
}
if (Handle.IsInvalid)
{
throw new WasmtimeException($"WebAssembly module '{name}' is not valid.");
}
}
Name = name;
Imports = new Wasmtime.Imports.Imports(this);
Exports = new Wasmtime.Exports.Exports(this);
}
internal Interop.ModuleHandle Handle { get; private set; }
}
}

View File

@@ -5,18 +5,8 @@ namespace Wasmtime
/// <summary>
/// Represents a mutable WebAssembly global value.
/// </summary>
public class MutableGlobal<T>
public class MutableGlobal<T> : IDisposable
{
/// <summary>
/// Creates a new <see cref="MutableGlobal&lt;T&gt;" /> with the given initial value.
/// </summary>
/// <param name="initialValue">The initial value of the global.</param>
public MutableGlobal(T initialValue)
{
InitialValue = initialValue;
Kind = Interop.ToValueKind(typeof(T));
}
/// <summary>
/// The value of the global.
/// </summary>
@@ -32,10 +22,7 @@ namespace Wasmtime
unsafe
{
var v = stackalloc Interop.wasm_val_t[1];
Interop.wasm_global_get(Handle.DangerousGetHandle(), v);
// TODO: figure out a way that doesn't box the value
return (T)Interop.ToObject(v);
}
}
@@ -46,7 +33,6 @@ namespace Wasmtime
throw new InvalidOperationException("The global cannot be used before it is instantiated.");
}
// TODO: figure out a way that doesn't box the value
var v = Interop.ToValue(value, Kind);
unsafe
@@ -56,10 +42,53 @@ namespace Wasmtime
}
}
internal ValueKind Kind { get; private set; }
/// <summary>
/// Gets the value kind of the global.
/// </summary>
/// <value></value>
public ValueKind Kind { get; private set; }
/// <inheritdoc/>
public void Dispose()
{
if (!Handle.IsInvalid)
{
Handle.Dispose();
Handle.SetHandleAsInvalid();
}
}
internal MutableGlobal(Interop.StoreHandle store, T initialValue)
{
if (!Interop.TryGetValueKind(typeof(T), out var kind))
{
throw new WasmtimeException($"Mutable global variables cannot be of type '{typeof(T).ToString()}'.");
}
Kind = kind;
var value = Interop.ToValue((object)initialValue, Kind);
var valueType = Interop.wasm_valtype_new(value.kind);
var valueTypeHandle = valueType.DangerousGetHandle();
valueType.SetHandleAsInvalid();
using var globalType = Interop.wasm_globaltype_new(
valueTypeHandle,
Interop.wasm_mutability_t.WASM_VAR
);
unsafe
{
Handle = Interop.wasm_global_new(store, globalType, &value);
if (Handle.IsInvalid)
{
throw new WasmtimeException("Failed to create mutable Wasmtime global.");
}
}
}
internal Interop.GlobalHandle Handle { get; set; }
internal T InitialValue { get; private set; }
}
}

View File

@@ -1,75 +0,0 @@
using System;
using System.IO;
namespace Wasmtime
{
/// <summary>
/// Represents the Wasmtime store.
/// </summary>
public sealed class Store : IDisposable
{
internal Store(Engine engine)
{
Handle = Interop.wasm_store_new(engine.Handle);
if (Handle.IsInvalid)
{
throw new WasmtimeException("Failed to create Wasmtime store.");
}
}
/// <summary>
/// Create a <see cref="Module"/> given the module name and bytes.
/// </summary>
/// <param name="name">The name of the module.</param>
/// <param name="bytes">The bytes of the module.</param>
/// <returns>Retuw <see cref="Module"/>.</returns>
public Module CreateModule(string name, byte[] bytes)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException(nameof(name));
}
if (bytes is null)
{
throw new ArgumentNullException(nameof(bytes));
}
return new Module(this, name, bytes);
}
/// <summary>
/// Create a <see cref="Module"/> given the module name and path to the WebAssembly file.
/// </summary>
/// <param name="name">The name of the module.</param>
/// <param name="path">The path to the WebAssembly file.</param>
/// <returns>Returns a new <see cref="Module"/>.</returns>
public Module CreateModule(string name, string path)
{
return CreateModule(name, File.ReadAllBytes(path));
}
/// <summary>
/// Create a <see cref="Module"/> given the path to the WebAssembly file.
/// </summary>
/// <param name="path">The path to the WebAssembly file.</param>
/// <returns>Returns a new <see cref="Module"/>.</returns>
public Module CreateModule(string path)
{
return CreateModule(Path.GetFileNameWithoutExtension(path), path);
}
/// <inheritdoc/>
public void Dispose()
{
if (!Handle.IsInvalid)
{
Handle.Dispose();
Handle.SetHandleAsInvalid();
}
}
internal Interop.StoreHandle Handle { get; private set; }
}
}

View File

@@ -1,52 +0,0 @@
using System;
using Wasmtime.Bindings;
using Wasmtime.Imports;
namespace Wasmtime
{
public class Wasi
{
/// <summary>
/// Creates a default <see cref="Wasi"/> instance.
/// </summary>
/// <param name="store">The store to use for the new WASI instance.</param>
/// <param name="name">The name of the WASI module to create.</param>
public Wasi(Store store, string name) :
this(
(store ?? throw new ArgumentNullException(nameof(store))).Handle,
Interop.wasi_config_new(),
name
)
{
}
internal Wasi(Interop.StoreHandle store, Interop.WasiConfigHandle config, string name)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("Name cannot be null or empty.", nameof(name));
}
IntPtr trap;
Handle = Interop.wasi_instance_new(store, name, config, out trap);
config.SetHandleAsInvalid();
if (trap != IntPtr.Zero)
{
throw TrapException.FromOwnedTrap(trap);
}
}
internal WasiBinding Bind(Import import)
{
var export = Interop.wasi_instance_bind_import(Handle, import.Handle);
if (export == IntPtr.Zero)
{
return null;
}
return new WasiBinding(export);
}
internal Interop.WasiInstanceHandle Handle { get; private set; }
}
}

View File

@@ -5,35 +5,16 @@ using System.Linq;
namespace Wasmtime
{
/// <summary>
/// Represents a builder of <see cref="Wasi"/> instances.
/// Represents a WASI configuration.
/// </summary>
public class WasiBuilder
public class WasiConfiguration
{
/// <summary>
/// Constructs a new <see cref="WasiBuilder" />.
/// </summary>
public WasiBuilder(string name)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("Name cannot be null or empty.", nameof(name));
}
Name = name;
}
/// <summary>
/// Gets the name of the WASI module being built.
/// </summary>
/// <value>The name of the WASI module.</value>
public string Name { get; private set; }
/// <summary>
/// Adds a command line argument to the builder.
/// Adds a command line argument to the configuration.
/// </summary>
/// <param name="arg">The command line argument to add.</param>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithArg(string arg)
/// <returns>Returns the current configuration.</returns>
public WasiConfiguration WithArg(string arg)
{
if (arg is null)
{
@@ -51,11 +32,11 @@ namespace Wasmtime
}
/// <summary>
/// Adds multiple command line arguments to the builder.
/// Adds multiple command line arguments to the configuration.
/// </summary>
/// <param name="args">The command line arguments to add.</param>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithArgs(IEnumerable<string> args)
/// <returns>Returns the current configuration.</returns>
public WasiConfiguration WithArgs(IEnumerable<string> args)
{
if (args is null)
{
@@ -76,21 +57,21 @@ namespace Wasmtime
}
/// <summary>
/// Adds multiple command line arguments to the builder.
/// Adds multiple command line arguments to the configuration.
/// </summary>
/// <param name="args">The command line arguments to add.</param>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithArgs(params string[] args)
/// <returns>Returns the current configuration.</returns>
public WasiConfiguration WithArgs(params string[] args)
{
return WithArgs((IEnumerable<string>)args);
}
/// <summary>
/// Sets the builder to inherit command line arguments.
/// Sets the configuration to inherit command line arguments.
/// </summary>
/// <remarks>Any explicitly specified command line arguments will be removed.</remarks>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithInheritedArgs()
/// <returns>Returns the current configuration.</returns>
public WasiConfiguration WithInheritedArgs()
{
_inheritArgs = true;
_args.Clear();
@@ -99,12 +80,12 @@ namespace Wasmtime
}
/// <summary>
/// Adds an environment variable to the builder.
/// Adds an environment variable to the configuration.
/// </summary>
/// <param name="name">The name of the environment variable.</param>
/// <param name="value">The value of the environment variable.</param>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithEnvironmentVariable(string name, string value)
/// <returns>Returns the current configuration.</returns>
public WasiConfiguration WithEnvironmentVariable(string name, string value)
{
if (name is null)
{
@@ -126,11 +107,11 @@ namespace Wasmtime
}
/// <summary>
/// Adds multiple environment variables to the builder.
/// Adds multiple environment variables to the configuration.
/// </summary>
/// <param name="vars">The name-value tuples of the environment variables to add.</param>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithEnvironmentVariables(IEnumerable<(string,string)> vars)
/// <returns>Returns the current configuration.</returns>
public WasiConfiguration WithEnvironmentVariables(IEnumerable<(string,string)> vars)
{
if (vars is null)
{
@@ -148,11 +129,11 @@ namespace Wasmtime
}
/// <summary>
/// Sets the builder to inherit environment variables.
/// Sets the configuration to inherit environment variables.
/// </summary>
/// <remarks>Any explicitly specified environment variables will be removed.</remarks>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithInheritedEnvironment()
/// <returns>Returns the current configuration.</returns>
public WasiConfiguration WithInheritedEnvironment()
{
_inheritEnv = true;
_vars.Clear();
@@ -160,11 +141,11 @@ namespace Wasmtime
}
/// <summary>
/// Sets the builder to use the given file path as stdin.
/// Sets the configuration to use the given file path as stdin.
/// </summary>
/// <param name="path">The file to use as stdin.</param>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithStandardInput(string path)
/// <returns>Returns the current configuration.</returns>
public WasiConfiguration WithStandardInput(string path)
{
if (string.IsNullOrEmpty(path))
{
@@ -177,11 +158,11 @@ namespace Wasmtime
}
/// <summary>
/// Sets the builder to inherit stdin.
/// Sets the configuration to inherit stdin.
/// </summary>
/// <remarks>Any explicitly specified stdin file will be removed.</remarks>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithInheritedStandardInput()
/// <returns>Returns the current configuration.</returns>
public WasiConfiguration WithInheritedStandardInput()
{
_inheritStandardInput = true;
_standardInputPath = null;
@@ -189,11 +170,11 @@ namespace Wasmtime
}
/// <summary>
/// Sets the builder to use the given file path as stdout.
/// Sets the configuration to use the given file path as stdout.
/// </summary>
/// <param name="path">The file to use as stdout.</param>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithStandardOutput(string path)
/// <returns>Returns the current configuration.</returns>
public WasiConfiguration WithStandardOutput(string path)
{
if (string.IsNullOrEmpty(path))
{
@@ -206,11 +187,11 @@ namespace Wasmtime
}
/// <summary>
/// Sets the builder to inherit stdout.
/// Sets the configuration to inherit stdout.
/// </summary>
/// <remarks>Any explicitly specified stdout file will be removed.</remarks>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithInheritedStandardOutput()
/// <returns>Returns the current configuration.</returns>
public WasiConfiguration WithInheritedStandardOutput()
{
_inheritStandardOutput = true;
_standardOutputPath = null;
@@ -218,11 +199,11 @@ namespace Wasmtime
}
/// <summary>
/// Sets the builder to use the given file path as stderr.
/// Sets the configuration to use the given file path as stderr.
/// </summary>
/// <param name="path">The file to use as stderr.</param>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithStandardError(string path)
/// <returns>Returns the current configuration.</returns>
public WasiConfiguration WithStandardError(string path)
{
if (string.IsNullOrEmpty(path))
{
@@ -235,11 +216,11 @@ namespace Wasmtime
}
/// <summary>
/// Sets the builder to inherit stderr.
/// Sets the configuration to inherit stderr.
/// </summary>
/// <remarks>Any explicitly specified stderr file will be removed.</remarks>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithInheritedStandardError()
/// <returns>Returns the current configuration.</returns>
public WasiConfiguration WithInheritedStandardError()
{
_inheritStandardError = true;
_standardErrorPath = null;
@@ -247,12 +228,12 @@ namespace Wasmtime
}
/// <summary>
/// Adds a preopen directory to the builder.
/// Adds a preopen directory to the configuration.
/// </summary>
/// <param name="path">The path to the directory to add.</param>
/// <param name="guestPath">The path the guest will use to open the directory.</param>
/// <returns>Returns the current builder.</returns>
public WasiBuilder WithPreopenedDirectory(string path, string guestPath)
/// <returns>Returns the current configuration.</returns>
public WasiConfiguration WithPreopenedDirectory(string path, string guestPath)
{
if (string.IsNullOrEmpty(path))
{
@@ -267,12 +248,7 @@ namespace Wasmtime
return this;
}
/// <summary>
/// Builds the <see cref="Wasi" /> instance.
/// </summary>
/// <param name="store">The <see cref="Store" /> to use.</param>
/// <returns>Returns the new <see cref="Wasi" /> instance.</returns>
public Wasi Build(Store store)
internal Interop.WasiInstanceHandle CreateWasi(Interop.StoreHandle store, string name)
{
var config = Interop.wasi_config_new();
@@ -282,8 +258,22 @@ namespace Wasmtime
SetStandardOut(config);
SetStandardError(config);
SetPreopenDirectories(config);
return new Wasi(store.Handle, config, Name);
IntPtr trap;
var wasi = Interop.wasi_instance_new(store, name, config, out trap);
config.SetHandleAsInvalid();
if (trap != IntPtr.Zero)
{
throw TrapException.FromOwnedTrap(trap);
}
if (wasi.IsInvalid)
{
throw new WasmtimeException($"Failed to create instance for WASI module '{name}'.");
}
return wasi;
}
private unsafe void SetConfigArgs(Interop.WasiConfigHandle config)