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:
@@ -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 &&
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
23
crates/misc/dotnet/src/Bindings/WasiBinding.cs
Normal file
23
crates/misc/dotnet/src/Bindings/WasiBinding.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user