Skip to content

Commit

Permalink
Use unchecked function callbacks for better performance (#186)
Browse files Browse the repository at this point in the history
* Use unchecked functions with raw values for better performance.

This is a follow-up to PR #163.

This also improves the NRT annotations for callback delegates, and fixes an regression that prevented to define callbacks taking or returning an interface.

* Follow-Up: Pass the ValueRaw as reference when boxing a value, for better performance.

* Follow-Up: Always set the first 64 bit of the ValueRaw, to match the behavior of the Rust implementation (and this doesn't seem to affect performance).

* Only create the Caller instance if it is actually needed.
This allows callbacks that don't use a Caller, Function or object parameter to be allocation-free.

* Empty commit to retrigger CI.

* Refactor to always pass a StoreContext to the IValueRawConverter<T>, so that allocating a Caller is now only necessary when unboxing Functions.

* Simplify handling of V128.
  • Loading branch information
kpreisser authored Nov 28, 2022
1 parent 415438c commit 1160b5e
Show file tree
Hide file tree
Showing 16 changed files with 7,802 additions and 5,353 deletions.
2 changes: 1 addition & 1 deletion src/Caller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ private IntPtr NativeHandle
/// <returns>Returns the fuel consumed by the executing WebAssembly code or 0 if fuel consumption was not enabled.</returns>
public ulong GetConsumedFuel() => ((IStore)this).Context.GetConsumedFuel();

private static class Native
internal static class Native
{
[DllImport(Engine.LibraryName)]
[return: MarshalAs(UnmanagedType.I1)]
Expand Down
6,027 changes: 3,502 additions & 2,525 deletions src/Function.FromCallback.cs

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions src/Function.FromCallback.tt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
// Do not modify it directly.
// </auto-generated>

#nullable enable

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -64,12 +66,12 @@ foreach (var (hasCaller, resultCount, parameterCount, methodGenerics, delegateTy
#>
unsafe
{
Function.Native.WasmtimeFuncCallback func = (env, callerPtr, args, nargs, results, nresults) =>
Function.Native.WasmtimeFuncUncheckedCallback func = (env, callerPtr, args_and_results, num_args_and_results) =>
{
<# GenerateCallbackContent(hasCaller, resultCount, parameterCount); #>
};

Native.wasmtime_func_new(
Native.wasmtime_func_new_unchecked(
store.Context.handle,
funcType,
func,
Expand All @@ -81,6 +83,7 @@ foreach (var (hasCaller, resultCount, parameterCount, methodGenerics, delegateTy
return new Function(store, externFunc, parameterKinds, resultKinds);
}
}

<#
}
#>
Expand Down
17 changes: 14 additions & 3 deletions src/Function.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1999,17 +1999,28 @@ internal static class Native
{
public delegate void Finalizer(IntPtr data);

public unsafe delegate IntPtr WasmtimeFuncCallback(IntPtr env, IntPtr caller, Value* args, UIntPtr nargs, Value* results, UIntPtr nresults);
public unsafe delegate IntPtr WasmtimeFuncCallback(IntPtr env, IntPtr caller, Value* args, nuint nargs, Value* results, nuint nresults);

public unsafe delegate IntPtr WasmtimeFuncUncheckedCallback(IntPtr env, IntPtr caller, ValueRaw* args_and_results, nuint num_args_and_results);

[DllImport(Engine.LibraryName)]
public static extern void wasmtime_func_new(IntPtr context, TypeHandle type, WasmtimeFuncCallback callback, IntPtr env, Finalizer? finalizer, out ExternFunc func);

[DllImport(Engine.LibraryName)]
public static unsafe extern IntPtr wasmtime_func_call(IntPtr context, in ExternFunc func, Value* args, UIntPtr nargs, Value* results, UIntPtr nresults, out IntPtr trap);
public static extern void wasmtime_func_new_unchecked(IntPtr context, TypeHandle type, WasmtimeFuncUncheckedCallback callback, IntPtr env, Finalizer? finalizer, out ExternFunc func);

[DllImport(Engine.LibraryName)]
public static unsafe extern IntPtr wasmtime_func_call(IntPtr context, in ExternFunc func, Value* args, nuint nargs, Value* results, nuint nresults, out IntPtr trap);

[DllImport(Engine.LibraryName)]
public static unsafe extern IntPtr wasmtime_func_type(IntPtr context, in ExternFunc func);

[DllImport(Engine.LibraryName)]
public static unsafe extern void wasmtime_func_from_raw(IntPtr context, nuint raw, out ExternFunc func);

[DllImport(Engine.LibraryName)]
public static unsafe extern nuint wasmtime_func_to_raw(IntPtr context, in ExternFunc func);

[DllImport(Engine.LibraryName)]
public static extern IntPtr wasm_functype_new(in ValueTypeArray parameters, in ValueTypeArray results);

Expand All @@ -2027,7 +2038,7 @@ internal static class Native
public static unsafe extern IntPtr wasmtime_trap_new(byte* bytes, nuint len);
}

private readonly IStore? store;
internal readonly IStore? store;
internal readonly ExternFunc func;
internal readonly List<ValueKind> parameters = new List<ValueKind>();
internal readonly List<ValueKind> results = new List<ValueKind>();
Expand Down
44 changes: 38 additions & 6 deletions src/FunctionCallbackOverloadTemplates.t4
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

void GenerateCallbackContent(bool hasCaller, int resultCount, int parameterCount) {
#>
using var caller = new Caller(callerPtr);
var storeContext = new StoreContext(Caller.Native.wasmtime_caller_context(callerPtr));
using var caller = <#= hasCaller ? "" : "!converterRequiresStore ? null : " #>new Caller(callerPtr);

try
{
Expand All @@ -30,7 +31,7 @@ void GenerateCallbackContent(bool hasCaller, int resultCount, int parameterCount
genericType += (i + 1).ToString(CultureInfo.InvariantCulture);
}

#>conv<#= genericType #>.Unbox(caller, args[<#= i.ToString(CultureInfo.InvariantCulture) #>].ToValueBox())<#
#>conv<#= genericType #>.Unbox(storeContext, caller, args_and_results[<#= i.ToString(CultureInfo.InvariantCulture) #>])<#

if (i + 1 < parameterCount)
{
Expand All @@ -56,7 +57,7 @@ void GenerateCallbackContent(bool hasCaller, int resultCount, int parameterCount
tupleAccessor = ".Item" + (i + 1).ToString(CultureInfo.InvariantCulture);
}

#>results[<#= i.ToString(CultureInfo.InvariantCulture) #>] = Value.FromValueBox(conv<#= genericType #>.Box(result<#= tupleAccessor #>));
#>conv<#= genericType #>.Box(storeContext, caller, ref args_and_results[<#= i.ToString(CultureInfo.InvariantCulture) #>], result<#= tupleAccessor #>);
<#
}
#>
Expand Down Expand Up @@ -137,9 +138,9 @@ IEnumerable<(
}

methodGenerics.Append(genericType);
delegateType.Append(genericType);
delegateType.Append(genericType + '?');

parameterConverters.AppendLine($" var conv{genericType} = ValueBox.Converter<{genericType}>();");
parameterConverters.AppendLine($" var conv{genericType} = ValueRaw.Converter<{genericType}>();");
}

if (parameterCount > 0 && resultCount > 0)
Expand Down Expand Up @@ -170,7 +171,7 @@ IEnumerable<(
methodGenerics.Append(genericType);
delegateReturnType.Append(genericType);

parameterConverters.AppendLine($" var conv{genericType} = ValueBox.Converter<{genericType}>();");
parameterConverters.AppendLine($" var conv{genericType} = ValueRaw.Converter<{genericType}>();");
}

if (resultCount > 1)
Expand Down Expand Up @@ -211,6 +212,37 @@ IEnumerable<(
{
callbackReturnTypeExpression = $"typeof({delegateReturnType})";
}

// Generate a bool value that specifies whether any of the converters needs the IStore.
if (!hasCaller)
{
parameterConverters.AppendLine();
parameterConverters.AppendLine($" var converterRequiresStore =");

for (int x = 0; x < parameterCount; x++)
{
string genericType = "T";
if (parameterCount > 1)
{
genericType += (x + 1).ToString(CultureInfo.InvariantCulture);
}

parameterConverters.AppendLine($" conv{genericType}.RequiresStore(forBoxing: false) ||");
}

for (int x = 0; x < resultCount; x++)
{
string genericType = "TResult";
if (resultCount > 1)
{
genericType += (x + 1).ToString(CultureInfo.InvariantCulture);
}

parameterConverters.AppendLine($" conv{genericType}.RequiresStore(forBoxing: true) ||");
}

parameterConverters.AppendLine($" false;");
}

yield return (
hasCaller,
Expand Down
Loading

0 comments on commit 1160b5e

Please sign in to comment.