Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
361 changes: 303 additions & 58 deletions src/Function.cs

Large diffs are not rendered by default.

11 changes: 7 additions & 4 deletions src/Linker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -756,14 +756,17 @@ private void DefineFunction(string module, string name, Delegate callback, bool
var parameterKinds = new List<ValueKind>();
var resultKinds = new List<ValueKind>();

using var funcType = Function.GetFunctionType(callback.GetType(), hasReturn, parameterKinds, resultKinds, out var hasCaller);
using var funcType = Function.GetFunctionType(callback.GetType(), hasReturn, parameterKinds, resultKinds, out var hasCaller, out var returnsTuple);

// Generate code for invoking the callback without reflection.
var generatedDelegate = Function.GenerateInvokeCallbackDelegate(callback, hasCaller, returnsTuple, parameterKinds, resultKinds);

unsafe
{
Function.Native.WasmtimeFuncCallback func = (env, callerPtr, args, nargs, results, nresults) =>
{
using var caller = new Caller(callerPtr);
return Function.InvokeCallback(callback, caller, hasCaller, args, (int)nargs, results, (int)nresults, resultKinds);
return Function.InvokeCallback(generatedDelegate, caller, args, (int)nargs, results, (int)nresults);
};

var moduleBytes = Encoding.UTF8.GetBytes(module);
Expand All @@ -779,7 +782,7 @@ private void DefineFunction(string module, string name, Delegate callback, bool
funcType,
func,
GCHandle.ToIntPtr(GCHandle.Alloc(func)),
Function.Finalizer
&Function.Finalize
);

if (error != IntPtr.Zero)
Expand Down Expand Up @@ -839,7 +842,7 @@ internal static class Native
public static unsafe extern IntPtr wasmtime_linker_define_instance(Handle linker, IntPtr context, byte* name, UIntPtr len, in ExternInstance instance);

[DllImport(Engine.LibraryName)]
public static unsafe extern IntPtr wasmtime_linker_define_func(Handle linker, byte* module, UIntPtr moduleLen, byte* name, UIntPtr nameLen, Function.TypeHandle type, Function.Native.WasmtimeFuncCallback callback, IntPtr data, Function.Native.Finalizer? finalizer);
public static unsafe extern IntPtr wasmtime_linker_define_func(Handle linker, byte* module, UIntPtr moduleLen, byte* name, UIntPtr nameLen, Function.TypeHandle type, Function.Native.WasmtimeFuncCallback callback, IntPtr data, delegate* unmanaged<IntPtr, void> finalizer);

[DllImport(Engine.LibraryName)]
public static extern IntPtr wasmtime_linker_instantiate(Handle linker, IntPtr context, Module.Handle module, out ExternInstance instance, out IntPtr trap);
Expand Down
6 changes: 2 additions & 4 deletions src/Store.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ public class Store : IStore, IDisposable
/// Constructs a new store.
/// </summary>
/// <param name="engine">The engine to use for the store.</param>
public Store(Engine engine)
public unsafe Store(Engine engine)
{
if (engine is null)
{
Expand Down Expand Up @@ -289,10 +289,8 @@ protected override bool ReleaseHandle()

private static class Native
{
public delegate void Finalizer(IntPtr data);

[DllImport(Engine.LibraryName)]
public static extern IntPtr wasmtime_store_new(Engine.Handle engine, IntPtr data, Finalizer? finalizer);
public static extern unsafe IntPtr wasmtime_store_new(Engine.Handle engine, IntPtr data, delegate* unmanaged<IntPtr, void> finalizer);

[DllImport(Engine.LibraryName)]
public static extern IntPtr wasmtime_store_context(Handle store);
Expand Down
18 changes: 10 additions & 8 deletions src/Value.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ public static bool TryGetKind(Type type, out ValueKind kind)
return false;
}

public static Value FromValueBox(ValueBox box)
public static unsafe Value FromValueBox(ValueBox box)
{
var value = new Value();
value.kind = box.Kind;
Expand All @@ -231,7 +231,7 @@ public static Value FromValueBox(ValueBox box)
{
value.of.externref = Native.wasmtime_externref_new(
GCHandle.ToIntPtr(GCHandle.Alloc(box.ExternRefObject)),
Finalizer
&Finalize
);
}
}
Expand All @@ -251,7 +251,7 @@ public ValueBox ToValueBox()
}
}

public static Value FromObject(object? o, ValueKind kind)
public static unsafe Value FromObject(object? o, ValueKind kind)
{
var value = new Value();
value.kind = kind;
Expand Down Expand Up @@ -301,7 +301,7 @@ public static Value FromObject(object? o, ValueKind kind)
{
value.of.externref = Native.wasmtime_externref_new(
GCHandle.ToIntPtr(GCHandle.Alloc(o)),
Value.Finalizer
&Finalize
);
}
break;
Expand Down Expand Up @@ -386,20 +386,22 @@ public static Value FromObject(object? o, ValueKind kind)

private static class Native
{
public delegate void Finalizer(IntPtr data);

[DllImport(Engine.LibraryName)]
public static extern void wasmtime_val_delete(in Value val);

[DllImport(Engine.LibraryName)]
public static extern IntPtr wasmtime_externref_new(IntPtr data, Finalizer? finalizer);
public static extern unsafe IntPtr wasmtime_externref_new(IntPtr data, delegate* unmanaged<IntPtr, void> finalizer);

[DllImport(Engine.LibraryName)]
public static extern IntPtr wasmtime_externref_data(IntPtr externref);

}

private static readonly Native.Finalizer Finalizer = (p) => GCHandle.FromIntPtr(p).Free();
[UnmanagedCallersOnly]
private static void Finalize(IntPtr p)
{
GCHandle.FromIntPtr(p).Free();
}

private ValueKind kind;
private ValueUnion of;
Expand Down
18 changes: 17 additions & 1 deletion tests/ExternRefTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ public void ItHandlesNullReferences()
(nullref.Invoke()).Should().BeNull();
}

[Fact]
public void ItReturnsBoxedValueTupleAsExternRef()
{
// Test for issue #158
var instance = Linker.Instantiate(Store, Fixture.Module);

var inout = instance.GetFunction<object, object>("inout");
inout.Should().NotBeNull();

var input = (object)(1, 2, 3);
inout(input).Should().BeSameAs(input);
}

unsafe class Value
{
internal Value(int* counter)
Expand Down Expand Up @@ -111,10 +124,13 @@ public void ItThrowsForMismatchedTypes()

Action action = () => inout.Invoke(ValueBox.AsBox((object)5));

// The first message occurs when using DynamicMethod to call the callback,
// the second one occurs when using reflection.
action
.Should()
.Throw<Wasmtime.TrapException>()
.WithMessage("Object of type 'System.Int32' cannot be converted to type 'System.String'*");
.Where(e => e.Message.StartsWith("Unable to cast object of type 'System.Int32' to type 'System.String'") ||
e.Message.StartsWith("Object of type 'System.Int32' cannot be converted to type 'System.String'"));
}

public void Dispose()
Expand Down
55 changes: 55 additions & 0 deletions tests/FunctionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,36 @@ public FunctionTests(FunctionsFixture fixture)
{
caller.GetMemory("mem").ReadString(address, length).Should().Be("Hello World");
}));

Linker.Define("env", "return_i32", Function.FromCallback(Store, GetBoundFuncIntDelegate()));

Linker.Define("env", "return_15_values", Function.FromCallback(Store, () =>
{
return (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
}));

Linker.Define("env", "accept_15_values", Function.FromCallback(Store,
(int i1, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9, int i10, int i11, int i12, int i13, int i14, int i15) =>
{
(i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15)
.Should().Be((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15));
}));

Func<int> GetBoundFuncIntDelegate()
{
// Get a delegate that is bound over an argument.
// See #159
var getLengthDelegate = GetLength;
var getLengthMethod = getLengthDelegate.Method;

string str = "abc";
return (Func<int>)Delegate.CreateDelegate(typeof(Func<int>), str, getLengthMethod);

int GetLength(string s)
{
return s.Length;
}
}
}

private FunctionsFixture Fixture { get; }
Expand Down Expand Up @@ -316,6 +346,31 @@ public void ItReturnsNullForVeryLongTuples()
.BeNull();
}

[Fact]
public void ItReturnsInt32WithBoundDelegate()
{
// Test for issue #159: It should be possible to defined a Delegate that is bound with
// a first argument.
var instance = Linker.Instantiate(Store, Fixture.Module);
var echo = instance.GetFunction<int>("return_i32");
echo.Should().NotBeNull();

var result = echo.Invoke();
result.Should().Be(3);
}

[Fact]
public void ItReturnsAndAccepts15Values()
{
// Verify that nested levels of ValueTuple are handled correctly. Returning 15
// values means that a ValueTuple<..., ValueTuple<..., ValueTuple<...>>> is used.
var instance = Linker.Instantiate(Store, Fixture.Module);
var action = instance.GetAction("get_and_pass_15_values");
action.Should().NotBeNull();

action.Invoke();
}

public void Dispose()
{
Store.Dispose();
Expand Down
35 changes: 34 additions & 1 deletion tests/LinkerFunctionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,36 @@ public LinkerFunctionTests(LinkerFunctionsFixture fixture)
{
caller.GetMemory("mem").ReadString(address, length).Should().Be("Hello World");
});

Linker.Define("env", "return_i32", Function.FromCallback(Store, GetBoundFuncIntDelegate()));

Linker.Define("env", "return_15_values", Function.FromCallback(Store, () =>
{
return (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
}));

Linker.Define("env", "accept_15_values", Function.FromCallback(Store,
(int i1, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9, int i10, int i11, int i12, int i13, int i14, int i15) =>
{
(i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15)
.Should().Be((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15));
}));

Func<int> GetBoundFuncIntDelegate()
{
// Get a delegate that is bound over an argument.
// See #159
var getLengthDelegate = GetLength;
var getLengthMethod = getLengthDelegate.Method;

string str = "abc";
return (Func<int>)Delegate.CreateDelegate(typeof(Func<int>), str, getLengthMethod);

int GetLength(string s)
{
return s.Length;
}
}
}

private LinkerFunctionsFixture Fixture { get; }
Expand All @@ -40,12 +70,15 @@ public void ItBindsImportMethodsAndCallsThemCorrectly()
var instance = Linker.Instantiate(Store, Fixture.Module);
var add = instance.GetFunction("add");
var swap = instance.GetFunction("swap");
var check = instance.GetFunction("check_string");
var check = instance.GetFunction("check_string"); ;
var getInt32 = instance.GetFunction<int>("return_i32");

int x = (int)add.Invoke(40, 2);
x.Should().Be(42);
x = (int)add.Invoke(22, 5);
x.Should().Be(27);
x = getInt32.Invoke();
x.Should().Be(3);

object[] results = (object[])swap.Invoke(10, 100);
results.Should().Equal(new object[] { 100, 10 });
Expand Down
9 changes: 9 additions & 0 deletions tests/Modules/Functions.wat
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
(import "env" "swap" (func $env.swap (param i32 i32) (result i32 i32)))
(import "env" "do_throw" (func $env.do_throw))
(import "env" "check_string" (func $env.check_string (param i32 i32)))
(import "env" "return_i32" (func $env.return_i32 (result i32)))
(import "env" "return_15_values" (func $env.return_15_values (result i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32)))
(import "env" "accept_15_values" (func $env.accept_15_values (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32)))
(memory (export "mem") 1)
(export "add" (func $add))
(export "swap" (func $swap))
(export "do_throw" (func $do_throw))
(export "check_string" (func $check_string))
(export "return_i32" (func $env.return_i32))
(export "get_and_pass_15_values" (func $get_and_pass_15_values))
(func $add (param i32 i32) (result i32)
(call $env.add (local.get 0) (local.get 1))
)
Expand All @@ -20,6 +25,10 @@
(func $check_string
(call $env.check_string (i32.const 0) (i32.const 11))
)
(func $get_and_pass_15_values
call $env.return_15_values
call $env.accept_15_values
)
(data (i32.const 0) "Hello World")

(func (export "noop"))
Expand Down