Implement WASI C API.

This commit implements an initial WASI C API that can be used to instantiate
and configure a WASI instance from C.

This also implements a `WasiBuilder` for the C# API enabling .NET hosts to bind
to Wasmtime's WASI implementation.
This commit is contained in:
Peter Huene
2020-02-07 11:24:26 -08:00
parent f8abe1169c
commit ae0b4090ce
28 changed files with 1303 additions and 268 deletions

View File

@@ -10,35 +10,37 @@ namespace Wasmtime.Bindings
/// <summary>
/// Represents an abstract host binding.
/// </summary>
public abstract class Binding
internal abstract class Binding
{
internal abstract SafeHandle Bind(Store store, IHost host);
public abstract SafeHandle Bind(Store store, IHost host);
internal static void ThrowBindingException(Import import, MemberInfo member, string message)
public static WasmtimeException CreateBindingException(Import import, MemberInfo member, string message)
{
throw new WasmtimeException($"Unable to bind '{member.DeclaringType.Name}.{member.Name}' to WebAssembly import '{import}': {message}.");
return new WasmtimeException($"Unable to bind '{member.DeclaringType.Name}.{member.Name}' to WebAssembly import '{import}': {message}.");
}
internal static List<Binding> GetImportBindings(IHost host, Module module)
public static List<Binding> GetImportBindings(Module module, Wasi wasi = null, IHost host = null)
{
if (host is null)
{
throw new ArgumentNullException(nameof(host));
}
if (module is null)
{
throw new ArgumentNullException(nameof(module));
}
var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;
var type = host.GetType();
var methods = type.GetMethods(flags).Where(m => !m.IsSpecialName && Attribute.IsDefined(m, typeof(ImportAttribute)));
var fields = type.GetFields(flags).Where(m => !m.IsSpecialName && Attribute.IsDefined(m, typeof(ImportAttribute)));
var bindings = new List<Binding>();
var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;
var type = host?.GetType();
var methods = type?.GetMethods(flags).Where(m => !m.IsSpecialName && Attribute.IsDefined(m, typeof(ImportAttribute)));
var fields = type?.GetFields(flags).Where(m => !m.IsSpecialName && Attribute.IsDefined(m, typeof(ImportAttribute)));
foreach (var import in module.Imports.All)
{
var wasiBinding = wasi?.Bind(import);
if (!(wasiBinding is null))
{
bindings.Add(wasiBinding);
continue;
}
switch (import)
{
case FunctionImport func:
@@ -63,7 +65,7 @@ namespace Wasmtime.Bindings
private static FunctionBinding BindFunction(FunctionImport import, IEnumerable<MethodInfo> methods)
{
var method = methods.Where(m =>
var method = methods?.Where(m =>
{
var attribute = (ImportAttribute)m.GetCustomAttribute(typeof(ImportAttribute));
if (attribute is null)
@@ -88,7 +90,7 @@ namespace Wasmtime.Bindings
private static GlobalBinding BindGlobal(GlobalImport import, IEnumerable<FieldInfo> fields)
{
var field = fields.Where(f =>
var field = fields?.Where(f =>
{
var attribute = (ImportAttribute)f.GetCustomAttribute(typeof(ImportAttribute));
return attribute.Name == import.Name &&
@@ -108,7 +110,7 @@ namespace Wasmtime.Bindings
private static MemoryBinding BindMemory(MemoryImport import, IEnumerable<FieldInfo> fields)
{
var field = fields.Where(f =>
var field = fields?.Where(f =>
{
var attribute = (ImportAttribute)f.GetCustomAttribute(typeof(ImportAttribute));
return attribute.Name == import.Name &&

View File

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

View File

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

View File

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

View File

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