Move Wasmtime for .NET to the Wasmtime repo.

This moves the Wasmtime for .NET implementation to the Wasmtime repo.

Wasmtime for .NET is a binding of the Wasmtime API for use in .NET.
This commit is contained in:
Peter Huene
2019-11-22 17:11:00 -08:00
parent bbe2a797ba
commit 9fdf5bce8e
100 changed files with 6391 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
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>
public abstract class Binding
{
internal abstract SafeHandle Bind(Store store, IHost host);
internal static void ThrowBindingException(Import import, MemberInfo member, string message)
{
throw new WasmtimeException($"Unable to bind '{member.DeclaringType.Name}.{member.Name}' to WebAssembly import '{import}': {message}.");
}
internal static List<Binding> GetImportBindings(IHost host, Module module)
{
if (host is null)
{
throw new ArgumentNullException(nameof(host));
}
if (module is null)
{
throw new ArgumentNullException(nameof(module));
}
var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;
var type = host.GetType();
var methods = type.GetMethods(flags).Where(m => !m.IsSpecialName && Attribute.IsDefined(m, typeof(ImportAttribute)));
var fields = type.GetFields(flags).Where(m => !m.IsSpecialName && Attribute.IsDefined(m, typeof(ImportAttribute)));
var bindings = new List<Binding>();
foreach (var import in module.Imports.All)
{
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

@@ -0,0 +1,346 @@
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>
public 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; }
internal override SafeHandle Bind(Store store, IHost host)
{
unsafe
{
if (_callback != null)
{
throw new InvalidOperationException("Cannot bind more than once.");
}
_callback = CreateCallback(store, host);
var parameters = Interop.ToValueTypeVec(Import.Parameters);
var results = Interop.ToValueTypeVec(Import.Results);
using (var funcType = Interop.wasm_functype_new(ref parameters, ref results))
{
return Interop.wasm_func_new(store.Handle, funcType, _callback);
}
}
}
private void Validate()
{
if (Method.IsStatic)
{
ThrowBindingException(Import, Method, "method cannot be static");
}
if (Method.IsGenericMethod)
{
ThrowBindingException(Import, Method, "method cannot be generic");
}
if (Method.IsConstructor)
{
ThrowBindingException(Import, Method, "method cannot be a constructor");
}
ValidateParameters();
ValidateReturnType();
}
private void ValidateParameters()
{
var parameters = Method.GetParameters();
if (parameters.Length != Import.Parameters.Count)
{
ThrowBindingException(
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)
{
ThrowBindingException(Import, Method, $"parameter '{parameter.Name}' cannot be an 'out' parameter");
}
else
{
ThrowBindingException(Import, Method, $"parameter '{parameter.Name}' cannot be a 'ref' parameter");
}
}
var expected = Import.Parameters[i];
if (!Interop.TryGetValueKind(parameter.ParameterType, out var kind) || !Interop.IsMatchingKind(kind, expected))
{
ThrowBindingException(Import, Method, $"method parameter '{parameter.Name}' is expected to be of type '{Interop.ToString(expected)}'");
}
}
}
private void ValidateReturnType()
{
int resultsCount = Import.Results.Count();
if (resultsCount == 0)
{
if (Method.ReturnType != typeof(void))
{
ThrowBindingException(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))
{
ThrowBindingException(Import, Method, $"return type is expected to be '{Interop.ToString(expected)}'");
}
}
else
{
if (!IsTupleOfSize(Method.ReturnType, resultsCount))
{
ThrowBindingException(Import, Method, $"return type is expected to be a tuple of size {resultsCount}");
}
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))
{
ThrowBindingException(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, args);
if (hasReturn)
{
SetResults(result, results);
}
return IntPtr.Zero;
}
catch (TargetInvocationException ex)
{
var bytes = Encoding.UTF8.GetBytes(ex.InnerException.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.");
}
}
private Interop.WasmFuncCallback _callback;
}
}

View File

@@ -0,0 +1,125 @@
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using Wasmtime.Imports;
namespace Wasmtime.Bindings
{
/// <summary>
/// Represents a host global binding.
/// </summary>
public 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; }
internal override SafeHandle Bind(Store store, IHost host)
{
unsafe
{
dynamic global = Field.GetValue(host);
if (global.Handle != 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)
{
ThrowBindingException(Import, Field, "field cannot be static");
}
if (!Field.IsInitOnly)
{
ThrowBindingException(Import, Field, "field must be readonly");
}
if (!Field.FieldType.IsGenericType)
{
ThrowBindingException(Import, Field, "field is expected to be of type 'Global<T>'");
}
var definition = Field.FieldType.GetGenericTypeDefinition();
if (definition == typeof(Global<>))
{
if (Import.IsMutable)
{
ThrowBindingException(Import, Field, "the import is mutable (use the 'MutableGlobal' type)");
}
}
else if (definition == typeof(MutableGlobal<>))
{
if (!Import.IsMutable)
{
ThrowBindingException(Import, Field, "the import is constant (use the 'Global' type)");
}
}
else
{
ThrowBindingException(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))
{
ThrowBindingException(Import, Field, $"global type argument is expected to be of type '{Interop.ToString(Import.Kind)}'");
}
}
else
{
ThrowBindingException(Import, Field, $"'{arg}' is not a valid global type");
}
}
}
}

View File

@@ -0,0 +1,98 @@
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using Wasmtime.Imports;
namespace Wasmtime.Bindings
{
/// <summary>
/// Represents a host memory binding.
/// </summary>
public 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; }
internal override SafeHandle Bind(Store store, IHost host)
{
dynamic memory = Field.GetValue(host);
if (memory.Handle != null)
{
throw new InvalidOperationException("Cannot bind more than once.");
}
uint min = memory.Minimum;
uint max = memory.Maximum;
if (min != Import.Minimum)
{
ThrowBindingException(Import, Field, $"Memory does not have the expected minimum of {Import.Minimum} page(s)");
}
if (max != Import.Maximum)
{
ThrowBindingException(Import, Field, $"Memory does not have the expected maximum of {Import.Maximum} page(s)");
}
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)
{
ThrowBindingException(Import, Field, "field cannot be static");
}
if (!Field.IsInitOnly)
{
ThrowBindingException(Import, Field, "field must be readonly");
}
if (Field.FieldType != typeof(Memory))
{
ThrowBindingException(Import, Field, "field is expected to be of type 'Memory'");
}
}
}
}