Skip to content

Commit 6d4681d

Browse files
committed
Allow to specify functions using untyped callbacks, which allows to specify a callback of any type without the need to use a specific delegate type.
The untyped callback will receive arguments and can set results as a span of ValueBox. For example, this allows to define trapping functions for a module's import without having to know the function time at compile-time, similar to Wasmtime's define_unknown_imports_as_traps.
1 parent 9a24f3c commit 6d4681d

File tree

7 files changed

+434
-27
lines changed

7 files changed

+434
-27
lines changed

src/Function.cs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Buffers;
23
using System.Collections.Generic;
34
using System.Linq;
45
using System.Reflection;
@@ -14,6 +15,14 @@ namespace Wasmtime
1415
/// </summary>
1516
public partial class Function : IExternal
1617
{
18+
/// <summary>
19+
/// Encapsulates an untyped method that receives arguments and can set results via a span of <see cref="ValueBox"/>.
20+
/// </summary>
21+
/// <param name="caller">The caller.</param>
22+
/// <param name="arguments">The function arguments.</param>
23+
/// <param name="results">The function results. These must be set (using the correct type) before returning, except when the method throws (in which case they are ignored).</param>
24+
public delegate void UntypedCallbackDelegate(Caller caller, ReadOnlySpan<ValueBox> arguments, Span<ValueBox> results);
25+
1726
#region FromCallback
1827
/// <summary>
1928
/// Creates a function given a callback.
@@ -57,6 +66,51 @@ out var externFunc
5766
return new Function(store, externFunc, parameterKinds, resultKinds);
5867
}
5968
}
69+
70+
/// <summary>
71+
/// Creates an function given an untyped callback.
72+
/// </summary>
73+
/// <param name="store">The store to create the function in.</param>
74+
/// <param name="callback">The callback for when the function is invoked.</param>
75+
/// <param name="parameters">The function parameter kinds.</param>
76+
/// <param name="results">The function result kinds.</param>
77+
public static Function FromCallback(IStore store, UntypedCallbackDelegate callback, IReadOnlyList<ValueKind> parameters, IReadOnlyList<ValueKind> results)
78+
{
79+
if (store is null)
80+
{
81+
throw new ArgumentNullException(nameof(store));
82+
}
83+
84+
if (callback is null)
85+
{
86+
throw new ArgumentNullException(nameof(callback));
87+
}
88+
89+
// Copy the lists to ensure they are not externally modified.
90+
var parameterKinds = new List<ValueKind>(parameters);
91+
var resultKinds = new List<ValueKind>(results);
92+
93+
using var funcType = GetFunctionType(parameterKinds, resultKinds);
94+
95+
unsafe
96+
{
97+
Native.WasmtimeFuncCallback func = (env, callerPtr, args, nargs, results, nresults) =>
98+
{
99+
return InvokeUntypedCallback(callback, callerPtr, args, nargs, results, nresults, resultKinds);
100+
};
101+
102+
Native.wasmtime_func_new(
103+
store.Context.handle,
104+
funcType,
105+
func,
106+
GCHandle.ToIntPtr(GCHandle.Alloc(func)),
107+
Finalizer,
108+
out var externFunc
109+
);
110+
111+
return new Function(store, externFunc, parameterKinds, resultKinds);
112+
}
113+
}
60114
#endregion
61115

62116
/// <summary>
@@ -563,6 +617,11 @@ private static bool IsTuple(Type type)
563617
return null;
564618
}
565619

620+
return GetFunctionType(parameters, results);
621+
}
622+
623+
internal static TypeHandle GetFunctionType(IReadOnlyList<ValueKind> parameters, IReadOnlyList<ValueKind> results)
624+
{
566625
return new Function.TypeHandle(Function.Native.wasm_functype_new(new ValueTypeArray(parameters), new ValueTypeArray(results)));
567626
}
568627

@@ -611,6 +670,52 @@ internal unsafe static IntPtr InvokeCallback(Delegate callback, MethodInfo callb
611670
}
612671
}
613672

673+
internal static unsafe IntPtr InvokeUntypedCallback(UntypedCallbackDelegate callback, IntPtr callerPtr, Value* args, UIntPtr nargs, Value* results, UIntPtr nresults, IReadOnlyList<ValueKind> resultKinds)
674+
{
675+
try
676+
{
677+
using var caller = new Caller(callerPtr);
678+
679+
// Rent ValueBox arrays from the array pool (as it's not possible to
680+
// stackalloc a managed type).
681+
var argumentsBuffer = ArrayPool<ValueBox>.Shared.Rent((int)nargs);
682+
var resultsBuffer = ArrayPool<ValueBox>.Shared.Rent((int)nresults);
683+
684+
try
685+
{
686+
var argumentsSpan = argumentsBuffer.AsSpan()[..(int)nargs];
687+
var resultsSpan = resultsBuffer.AsSpan()[..(int)nresults];
688+
689+
// Clear the results span to avoid the callback being able to observe
690+
// non-deterministic values.
691+
resultsSpan.Clear();
692+
693+
for (int i = 0; i < argumentsSpan.Length; i++)
694+
{
695+
argumentsSpan[i] = args[i].ToValueBox();
696+
}
697+
698+
callback(caller, argumentsSpan, resultsSpan);
699+
700+
for (int i = 0; i < resultsSpan.Length; i++)
701+
{
702+
results[i] = resultsSpan[i].ToValue(resultKinds[i]);
703+
}
704+
}
705+
finally
706+
{
707+
ArrayPool<ValueBox>.Shared.Return(argumentsBuffer);
708+
ArrayPool<ValueBox>.Shared.Return(resultsBuffer);
709+
}
710+
711+
return IntPtr.Zero;
712+
}
713+
catch (Exception ex)
714+
{
715+
return HandleCallbackException(ex);
716+
}
717+
}
718+
614719
internal static unsafe IntPtr HandleCallbackException(Exception ex)
615720
{
616721
try

src/Linker.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,69 @@ public void DefineFunction(string module, string name, Delegate callback)
389389
}
390390
}
391391

392+
/// <summary>
393+
/// Defines an function in the linker given an untyped callback.
394+
/// </summary>
395+
/// <remarks>Functions defined with this method are store-independent.</remarks>
396+
/// <param name="module">The module name of the function.</param>
397+
/// <param name="name">The name of the function.</param>
398+
/// <param name="callback">The callback for when the function is invoked.</param>
399+
/// <param name="parameters">The function parameter kinds.</param>
400+
/// <param name="results">The function result kinds.</param>
401+
public void DefineFunction(string module, string name, Function.UntypedCallbackDelegate callback, IReadOnlyList<ValueKind> parameters, IReadOnlyList<ValueKind> results)
402+
{
403+
if (module is null)
404+
{
405+
throw new ArgumentNullException(nameof(module));
406+
}
407+
408+
if (name is null)
409+
{
410+
throw new ArgumentNullException(nameof(name));
411+
}
412+
413+
if (callback is null)
414+
{
415+
throw new ArgumentNullException(nameof(callback));
416+
}
417+
418+
// Copy the lists to ensure they are not externally modified.
419+
var parameterKinds = new List<ValueKind>(parameters);
420+
var resultKinds = new List<ValueKind>(results);
421+
422+
using var funcType = Function.GetFunctionType(parameterKinds, resultKinds);
423+
424+
unsafe
425+
{
426+
Function.Native.WasmtimeFuncCallback func = (env, callerPtr, args, nargs, results, nresults) =>
427+
{
428+
return Function.InvokeUntypedCallback(callback, callerPtr, args, nargs, results, nresults, resultKinds);
429+
};
430+
431+
var moduleBytes = Encoding.UTF8.GetBytes(module);
432+
var nameBytes = Encoding.UTF8.GetBytes(name);
433+
fixed (byte* modulePtr = moduleBytes, namePtr = nameBytes)
434+
{
435+
var error = Native.wasmtime_linker_define_func(
436+
handle,
437+
modulePtr,
438+
(nuint)moduleBytes.Length,
439+
namePtr,
440+
(nuint)nameBytes.Length,
441+
funcType,
442+
func,
443+
GCHandle.ToIntPtr(GCHandle.Alloc(func)),
444+
Function.Finalizer
445+
);
446+
447+
if (error != IntPtr.Zero)
448+
{
449+
throw WasmtimeException.FromOwnedError(error);
450+
}
451+
}
452+
}
453+
}
454+
392455
private bool TryGetExtern(StoreContext context, string module, string name, out Extern ext)
393456
{
394457
unsafe

src/Value.cs

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -287,11 +287,7 @@ public static Value FromObject(object? o, ValueKind kind)
287287
case ValueKind.V128:
288288
if (o is null)
289289
throw new WasmtimeException($"The value `null` is not valid for WebAssembly type {kind}.");
290-
var bytes = (V128)o;
291-
unsafe
292-
{
293-
bytes.CopyTo(value.of.v128);
294-
}
290+
value.of.v128 = (V128)o;
295291
break;
296292

297293
case ValueKind.ExternRef:
@@ -351,13 +347,7 @@ public static Value FromObject(object? o, ValueKind kind)
351347
return of.f64;
352348

353349
case ValueKind.V128:
354-
unsafe
355-
{
356-
fixed (byte* v128 = of.v128)
357-
{
358-
return new V128(v128);
359-
}
360-
}
350+
return of.v128;
361351

362352
case ValueKind.ExternRef:
363353
return ResolveExternRef();
@@ -435,6 +425,6 @@ internal unsafe struct ValueUnion
435425
public IntPtr externref;
436426

437427
[FieldOffset(0)]
438-
public fixed byte v128[16];
428+
public V128 v128;
439429
}
440430
}

src/ValueBox.cs

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,76 @@ internal ValueBox(object? externref)
3030
ExternRefObject = externref;
3131
}
3232

33+
/// <summary>
34+
/// "Unbox" an <see cref="int"/> value.
35+
/// </summary>
36+
/// <returns></returns>
37+
public int AsInt32()
38+
{
39+
return ConvertTo(ValueKind.Int32).Union.i32;
40+
}
41+
42+
/// <summary>
43+
/// "Unbox" a <see cref="long"/> value.
44+
/// </summary>
45+
/// <returns></returns>
46+
public long AsInt64()
47+
{
48+
return ConvertTo(ValueKind.Int64).Union.i64;
49+
}
50+
51+
/// <summary>
52+
/// "Unbox" a <see cref="float"/> value.
53+
/// </summary>
54+
/// <returns></returns>
55+
public float AsSingle()
56+
{
57+
return ConvertTo(ValueKind.Float32).Union.f32;
58+
}
59+
60+
/// <summary>
61+
/// "Unbox" a <see cref="double"/> value.
62+
/// </summary>
63+
/// <returns></returns>
64+
public double AsDouble()
65+
{
66+
return ConvertTo(ValueKind.Float64).Union.f64;
67+
}
68+
69+
/// <summary>
70+
/// "Unbox" a <see cref="V128"/> value.
71+
/// </summary>
72+
/// <returns></returns>
73+
public V128 AsV128()
74+
{
75+
ThrowIfNotOfCorrectKind(ValueKind.V128);
76+
77+
return Union.v128;
78+
}
79+
80+
/// <summary>
81+
/// "Unbox" a <see cref="Function"/> value.
82+
/// </summary>
83+
/// <returns></returns>
84+
public Function AsFunction(IStore store)
85+
{
86+
ThrowIfNotOfCorrectKind(ValueKind.FuncRef);
87+
88+
return new Function(store, Union.funcref);
89+
}
90+
91+
/// <summary>
92+
/// "Unbox" a reference type.
93+
/// </summary>
94+
/// <returns></returns>
95+
public T? As<T>()
96+
where T : class
97+
{
98+
ThrowIfNotOfCorrectKind(ValueKind.ExternRef);
99+
100+
return (T?)ExternRefObject;
101+
}
102+
33103
internal Value ToValue(ValueKind convertTo)
34104
{
35105
if (convertTo != Kind)
@@ -66,6 +136,12 @@ private ValueBox ConvertTo(ValueKind convertTo)
66136
};
67137
}
68138

139+
private void ThrowIfNotOfCorrectKind(ValueKind expectedKind)
140+
{
141+
if (Kind != expectedKind)
142+
throw new InvalidCastException($"Cannot convert from `{Kind}` to `{expectedKind}`");
143+
}
144+
69145
/// <summary>
70146
/// "Box" an int without any heap allocations
71147
/// </summary>
@@ -108,13 +184,7 @@ public static implicit operator ValueBox(double value)
108184
/// <param name="value"></param>
109185
public static implicit operator ValueBox(V128 value)
110186
{
111-
var union = new ValueUnion();
112-
unsafe
113-
{
114-
value.CopyTo(union.v128);
115-
}
116-
117-
return new ValueBox(ValueKind.V128, union);
187+
return new ValueBox(ValueKind.V128, new ValueUnion { v128 = value });
118188
}
119189

120190
/// <summary>
@@ -138,7 +208,7 @@ public static explicit operator ValueBox(ReadOnlySpan<byte> value)
138208
var union = new ValueUnion();
139209
unsafe
140210
{
141-
value.CopyTo(new Span<byte>(union.v128, 16));
211+
value.CopyTo(union.v128.AsSpan());
142212
}
143213

144214
return new ValueBox(ValueKind.V128, union);
@@ -338,10 +408,7 @@ public ValueBox Box(V128 value)
338408

339409
public V128 Unbox(IStore store, ValueBox value)
340410
{
341-
unsafe
342-
{
343-
return new V128(value.Union.v128);
344-
}
411+
return value.Union.v128;
345412
}
346413
}
347414

0 commit comments

Comments
 (0)