Skip to content

Commit

Permalink
Rework the key handling
Browse files Browse the repository at this point in the history
  • Loading branch information
lewing committed Sep 1, 2024
1 parent c7a2731 commit 0bda870
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 76 deletions.
6 changes: 3 additions & 3 deletions src/mono/browser/runtime/pinvoke.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ void*
wasm_dl_get_native_to_interp (const char *key, void *extra_arg)
{
#ifdef GEN_PINVOKE
for (int i = 0; i < sizeof (wasm_native_to_interp_map) / sizeof (void*); ++i) {
if (!strcmp (wasm_native_to_interp_map [i], key)) {
void *addr = wasm_native_to_interp_funcs [i];
for (int i = 0; wasm_native_to_interp_table [i].name != NULL; ++i) {
if (!strcmp (wasm_native_to_interp_table [i].name, key)) {
void *addr = wasm_native_to_interp_table [i].func;
wasm_native_to_interp_ftndescs [i] = *(InterpFtnDesc*)extra_arg;
return addr;
}
Expand Down
11 changes: 6 additions & 5 deletions src/mono/browser/runtime/runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -210,23 +210,24 @@ get_native_to_interp (MonoMethod *method, void *extra_arg)
const char *namespace = mono_class_get_namespace (klass);
const char *class_name = mono_class_get_name (klass);
const char *method_name = mono_method_get_name (method);
MonoMethodSignature *sig = mono_method_signature (method);
int param_count = mono_signature_get_param_count (sig);
char buf [128];
char *key = buf;
int len;
if (name == NULL)
return NULL;

len = snprintf (key, sizeof(buf), "%s_%s_%s_%s", name, namespace, class_name, method_name);
// the key must match the one used in PInvokeTableGenerator
len = snprintf (key, sizeof(buf), "%s::%s::%s::%s\U0001F412%d", name, namespace, class_name, method_name, param_count);

if (len >= sizeof (buf)) {
// The key is too long, try again with a larger buffer
key = g_new (char, len + 1);
snprintf (key, len + 1, "%s_%s_%s_%s", name, namespace, class_name, method_name);
snprintf (key, len + 1, "%s::%s::%s::%s\U0001F412%d", name, namespace, class_name, method_name, param_count);
}

char *fixedName = mono_fixup_symbol_name ("", key, "");
addr = wasm_dl_get_native_to_interp (fixedName, extra_arg);
free (fixedName);
addr = wasm_dl_get_native_to_interp (key, extra_arg);

if (len >= sizeof (buf))
free (key);
Expand Down
32 changes: 29 additions & 3 deletions src/tasks/WasmAppBuilder/PInvokeCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,23 @@ internal sealed class PInvokeCallback
public PInvokeCallback(MethodInfo method)
{
Method = method;
TypeName = method.DeclaringType!.Name!;
AssemblyName = method.DeclaringType!.Module!.Assembly!.GetName()!.Name!;
Namespace = method.DeclaringType!.Namespace!;
MethodName = method.Name!;
ReturnType = method.ReturnType!;
IsVoid = ReturnType.Name == "Void";

// FIXME: this is a hack, we need to encode this better
// and allow reflection in the interp case but either way
// it needs to match the key generated in get_native_to_interp
// since the key is used to look up the interp entry function
// it must be unique for each callback runtime errors can occur
// since it is used to look up the index in the wasm_native_to_interp_ftndescs
// and the signature of the interp entry function must match the native signature
Key = $"{AssemblyName}::{Namespace}::{TypeName}::{MethodName}\U0001F412{Method.GetParameters().Length}";

IsExport = false;
foreach (var attr in method.CustomAttributes)
{
if (attr.AttributeType.Name == "UnmanagedCallersOnlyAttribute")
Expand All @@ -225,15 +242,24 @@ public PInvokeCallback(MethodInfo method)
if (arg.MemberName == "EntryPoint")
{
EntryPoint = arg.TypedValue.Value!.ToString();
IsExport = true;
return;
}
}
}
}
}

public string? EntryPoint;
public MethodInfo Method;
public string? EntryName;
public string? EntryPoint { get; }
public MethodInfo Method { get; }
public string? EntrySymbol { get; set; }
public string AssemblyName { get; }
public string TypeName { get; }
public string Namespace { get;}
public string MethodName { get; }
public Type ReturnType { get;}
public bool IsExport { get; }
public bool IsVoid { get; }
public string Key { get; }
}
#pragma warning restore CS0649
154 changes: 89 additions & 65 deletions src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ private void EmitPInvokeTable(StreamWriter w, Dictionary<string, string> modules

w.WriteLine(
$"""
// GENERATED FILE, DO NOT MODIFY");
// GENERATED FILE, DO NOT MODIFY (PInvokeTableGenerator.cs)
#include <mono/utils/details/mono-error-types.h>
#include <mono/metadata/assembly.h>
#include <mono/utils/mono-error.h>
Expand Down Expand Up @@ -260,13 +260,6 @@ private static bool TryIsMethodGetParametersUnsupported(MethodInfo method, [NotN
{
var method = pinvoke.Method;

if (method.Name == "EnumCalendarInfo")
{
// FIXME: System.Reflection.MetadataLoadContext can't decode function pointer types
// https://github.com/dotnet/runtime/issues/43791
return $"int {_fixupSymbolName(pinvoke.EntryPoint)} (int, int, int, int, int);";
}

if (TryIsMethodGetParametersUnsupported(pinvoke.Method, out string? reason))
{
// Don't use method.ToString() or any of it's parameters, or return type
Expand Down Expand Up @@ -295,34 +288,63 @@ private static bool TryIsMethodGetParametersUnsupported(MethodInfo method, [NotN

private string CEntryPoint(PInvokeCallback export)
{
if (export.EntryPoint is not null)
if (export.IsExport)
{
return _fixupSymbolName(export.EntryPoint);
return _fixupSymbolName(export.EntryPoint!);
}

var method = export.Method;
string namespaceName = method.DeclaringType?.Namespace ?? string.Empty;
string assemblyName = method.DeclaringType?.Module?.Assembly?.GetName()?.Name ?? string.Empty;
string declaringTypeName = method.DeclaringType?.Name ?? string.Empty;

string entryPoint = $"wasm_native_to_interp_{namespaceName}_{assemblyName}_{declaringTypeName}_{method.Name}";

return _fixupSymbolName(entryPoint);
return _fixupSymbolName($"wasm_native_to_interp_{export.AssemblyName}_{export.Namespace}_{export.TypeName}_{export.MethodName}");
}

private string DelegateKey(PInvokeCallback export)
private static string EscapeLiteral(string input)
{
// FIXME: this is a hack, we need to encode this better
// and allow reflection in the interp case but either way
// it needs to match the key generated in get_native_to_interp
var method = export.Method;
string module_symbol = method.DeclaringType!.Module!.Assembly!.GetName()!.Name!;
return $"\"{_fixupSymbolName($"{module_symbol}_{method.DeclaringType.Namespace}_{method.DeclaringType.Name}_{method.Name}")}\"";
}
StringBuilder sb = new StringBuilder();

for (int i = 0; i < input.Length; i++)
{
char c = input[i];

if (char.IsHighSurrogate(c) && i + 1 < input.Length && char.IsLowSurrogate(input[i + 1]))
{
int codepoint = char.ConvertToUtf32(c, input[i + 1]);
sb.AppendFormat("\\U{0:X8}", codepoint);
i++; // Skip the low surrogate
}
else
{
switch (c)
{
case '\\':
sb.Append("\\\\");
break;
case '\"':
sb.Append("\\\"");
break;
case '\n':
sb.Append("\\n");
break;
case '\r':
sb.Append("\\r");
break;
case '\t':
sb.Append("\\t");
break;
default:
if (char.IsControl(c) || c > 127)
{
sb.AppendFormat("\\u{0:X4}", (int)c);
}
else
{
sb.Append(c);
}
break;
}
}
}

#pragma warning disable SYSLIB1045 // framework doesn't support GeneratedRegexAttribute
private static string EscapeLiteral(string s) => Regex.Replace(s, @"(\\|\"")", @"\$1");
#pragma warning restore SYSLIB1045
return sb.ToString();
}

private void EmitNativeToInterp(StreamWriter w, List<PInvokeCallback> callbacks)
{
Expand All @@ -332,57 +354,63 @@ private void EmitNativeToInterp(StreamWriter w, List<PInvokeCallback> callbacks)
// They also need to have a signature matching what the
// native code expects, which is the native signature
// of the delegate invoke in the [MonoPInvokeCallback]
// attribute.
// or [UnamanagedCallersOnly] attribute.
// Only blittable parameter/return types are supposed.
int cb_index = 0;

// Arguments to interp entry functions in the runtime
w.WriteLine($"InterpFtnDesc wasm_native_to_interp_ftndescs[{callbacks.Count}] = {{}};");
w.Write($$"""
InterpFtnDesc wasm_native_to_interp_ftndescs[{{callbacks.Count}}] = {};
""");

var callbackNames = new HashSet<string>();
var keys = new HashSet<string>();
foreach (var cb in callbacks)
{
var method = cb.Method;
bool is_void = method.ReturnType.Name == "Void";
bool attr = cb.EntryPoint is not null;
var parameters = method.GetParameters();
var numParams = parameters.Length;
var type = cb.Method.DeclaringType!;

cb.EntryName = CEntryPoint(cb);
if (callbackNames.Contains(cb.EntryName))
cb.EntrySymbol = CEntryPoint(cb);
if (callbackNames.Contains(cb.EntrySymbol))
{
Error($"Two callbacks with the same symbol '{cb.EntrySymbol}' are not supported.");
}
callbackNames.Add(cb.EntrySymbol);
var key = cb.Key;
if (keys.Contains(key))
{
Error($"Two callbacks with the same name '{cb.EntryName}' are not supported.");
Error($"Two callbacks with the same key '{key}' are not supported.");
}
callbackNames.Add(cb.EntryName);
keys.Add(key);

// The signature of the interp entry function
// This is a gsharedvt_in signature
var parameters = cb.Method.GetParameters();
var numParams = parameters.Length;
var unmanagedRange = Enumerable.Range(0, numParams);
var interpArgs = new List<string>();
if (!is_void)
var interpEntryArgs = new List<string>();
if (!cb.IsVoid)
{
interpArgs.Add("(int*)&result");
interpEntryArgs.Add("(int*)&result");
}
interpArgs.AddRange(unmanagedRange.Select(i => $"(int*)&arg{i}"));
interpArgs.Add($"wasm_native_to_interp_ftndescs [{cb_index}].arg");
interpEntryArgs.AddRange(unmanagedRange.Select(i => $"(int*)&arg{i}"));
interpEntryArgs.Add($"wasm_native_to_interp_ftndescs [{cb_index}].arg");

w.Write(
$$"""
{{(attr ? $"__attribute__((export_name(\"{EscapeLiteral(cb.EntryPoint!)}\")))" : "// no export name defined")}}
{{MapType(method.ReturnType)}}
{{cb.EntryName}} ({{string.Join(", ", unmanagedRange.Select(i => $"{MapType(parameters[i].ParameterType)} arg{i}"))}}) {
typedef void (*InterpEntry_T{{cb_index}}) ({{string.Join(", ", interpArgs.Select(_ => "int*"))}});
{{(!is_void ? $"{MapType(method.ReturnType)} result;" : "// void result")}}
{{(cb.IsExport ? $"__attribute__((export_name(\"{EscapeLiteral(cb.EntryPoint!)}\")))" : "// no export name defined")}}
{{MapType(cb.ReturnType)}}
{{cb.EntrySymbol}} ({{string.Join(", ", unmanagedRange.Select(i => $"{MapType(parameters[i].ParameterType)} arg{i}"))}}) {
typedef void (*InterpEntry_T{{cb_index}}) ({{string.Join(", ", interpEntryArgs.Select(_ => "int*"))}});
{{(!cb.IsVoid ? $"{MapType(cb.ReturnType)} result;" : "// void result")}}
if (!(InterpEntry_T{{cb_index}})wasm_native_to_interp_ftndescs [{{cb_index}}].func) {
{{(attr ? "initialize_runtime();" : "")}} // ensure the ftndescs and runtime are initialized when required
mono_wasm_marshal_get_managed_wrapper ("{{type!.Module!.Assembly!.GetName()!.Name!}}", "{{type.Namespace}}", "{{type.Name}}", "{{cb.Method.Name}}", {{numParams}});
}
if (!(InterpEntry_T{{cb_index}})wasm_native_to_interp_ftndescs [{{cb_index}}].func) {
{{(cb.IsExport ? "initialize_runtime(); " : "")}}// ensure the ftndescs and runtime are initialized when required
mono_wasm_marshal_get_managed_wrapper ("{{cb.AssemblyName}}", "{{cb.Namespace}}", "{{cb.TypeName}}", "{{cb.MethodName}}", {{numParams}});
}
((InterpEntry_T{{cb_index}})wasm_native_to_interp_ftndescs [{{cb_index}}].func) ({{string.Join(", ", interpArgs)}});
{{(!is_void ? "return result;" : "// void result")}}
((InterpEntry_T{{cb_index}})wasm_native_to_interp_ftndescs [{{cb_index}}].func) ({{string.Join(", ", interpEntryArgs)}});
{{(!cb.IsVoid ? "return result;" : "// void result")}}
}
""");
Expand All @@ -392,13 +420,9 @@ private void EmitNativeToInterp(StreamWriter w, List<PInvokeCallback> callbacks)
w.Write(
$$"""
static void *wasm_native_to_interp_funcs[] = {
{{string.Join($",{Environment.NewLine} ", callbacks.Select(cb => cb.EntryName))}}
};
// these strings need to match the keys generated in get_native_to_interp
static const char *wasm_native_to_interp_map[] = {
{{string.Join($",{Environment.NewLine} ", callbacks.Select(DelegateKey))}}
static PinvokeImport wasm_native_to_interp_table[] = {
{{string.Join($"{Environment.NewLine} ", callbacks.Select(cb => $"{{\"{EscapeLiteral(cb.Key)}\", {cb.EntrySymbol}}}, // {cb.AssemblyName}::{cb.Method.DeclaringType!.FullName}::{cb.Method.Name}")
.Append("{NULL, NULL}"))}}
};
""");
Expand Down

0 comments on commit 0bda870

Please sign in to comment.