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 { /// /// Represents a host function binding. /// internal class FunctionBinding : Binding { /// /// Constructs a new function binding. /// /// The function import of the binding. /// The method the import is bound to. 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(); } /// /// The function import of the binding. /// public FunctionImport Import { get; private set; } /// /// The method the import is bound to. /// 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."); } } } }