Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Improve icall/pinvoke generation performance #777

Merged
merged 1 commit into from
Oct 3, 2023
Merged
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
133 changes: 62 additions & 71 deletions src/Uno.Wasm.Tuner/IcallTableGenerator.net5.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
// Based on https://github.com/dotnet/runtime/commit/4f7a096dce6bb1d69b844b539678fa25ed7b8e20
// Based on https://github.com/dotnet/runtime/commit/711447a
#pragma warning disable CS8632
#pragma warning disable IDE0022
#pragma warning disable IDE0011
#pragma warning disable IDE0007
#pragma warning disable IDE0018
// Based on https://github.com/dotnet/runtime/commit/7b4b23269b0
#pragma warning disable IDE0270

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Reflection;

#if ORIGINAL_NETCORE_SOURCE
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
Expand All @@ -30,38 +26,35 @@ internal sealed class IcallTableGenerator
public string[]? Cookies { get; private set; }

private List<Icall> _icalls = new List<Icall>();
private List<string> _signatures = new List<string>();
private readonly HashSet<string> _signatures = new();
private Dictionary<string, IcallClass> _runtimeIcalls = new Dictionary<string, IcallClass>();
private object _gate = new();

#if ORIGINAL_NETCORE_SOURCE
private TaskLoggingHelper Log { get; set; }

public IcallTableGenerator(TaskLoggingHelper log) => Log = log;
#endif
private readonly Func<string, string> _fixupSymbolName;

//
// Given the runtime generated icall table, and a set of assemblies, generate
// a smaller linked icall table mapping tokens to C function names
// The runtime icall table should be generated using
// mono --print-icall-table
//
public IEnumerable<string> Generate(string? runtimeIcallTableFile, string[] assemblies, string? outputPath)
public IcallTableGenerator(string? runtimeIcallTableFile, Func<string, string> fixupSymbolName, TaskLoggingHelper log)
{
_icalls.Clear();
_signatures.Clear();

Log = log;
_fixupSymbolName = fixupSymbolName;
if (runtimeIcallTableFile != null)
ReadTable(runtimeIcallTableFile);
}

var resolver = new PathAssemblyResolver(assemblies);
using var mlc = new MetadataLoadContext(resolver, "System.Private.CoreLib");
foreach (var aname in assemblies)
{
var a = mlc.LoadFromAssemblyPath(aname);
foreach (var type in a.GetTypes())
ProcessType(type);
}
public void ScanAssembly(Assembly asm)
{
foreach (Type type in asm.GetTypes())
ProcessType(type);
}

public IEnumerable<string> Generate(string? outputPath)
{
if (outputPath != null)
{
string tmpFileName = Path.GetTempFileName();
Expand All @@ -70,12 +63,10 @@ public IEnumerable<string> Generate(string? runtimeIcallTableFile, string[] asse
using (var w = File.CreateText(tmpFileName))
EmitTable(w);

#if ORIGINAL_NETCORE_SOURCE
if (Utils.CopyIfDifferent(tmpFileName, outputPath, useHash: false))
Log.LogMessage(MessageImportance.Low, $"Generating icall table to '{outputPath}'.");
else
Log.LogMessage(MessageImportance.Low, $"Icall table in {outputPath} is unchanged.");
#endif
}
finally
{
Expand All @@ -101,7 +92,7 @@ private void EmitTable(StreamWriter w)
if (assembly == "System.Private.CoreLib")
aname = "corlib";
else
aname = assembly.Replace(".", "_");
aname = _fixupSymbolName(assembly);
w.WriteLine($"#define ICALL_TABLE_{aname} 1\n");

w.WriteLine($"static int {aname}_icall_indexes [] = {{");
Expand All @@ -117,9 +108,9 @@ private void EmitTable(StreamWriter w)
w.WriteLine(string.Format("{0},", icall.Func));
}
w.WriteLine("};");
w.WriteLine($"static uint8_t {aname}_icall_handles [] = {{");
w.WriteLine($"static uint8_t {aname}_icall_flags [] = {{");
foreach (var icall in sorted)
w.WriteLine(string.Format("{0},", icall.Handles ? "1" : "0"));
w.WriteLine(string.Format("{0},", icall.Flags));
w.WriteLine("};");
}
}
Expand All @@ -139,7 +130,12 @@ private void ReadTable(string filename)
continue;

var icallClass = new IcallClass(className);
_runtimeIcalls[icallClass.Name] = icallClass;

lock (_gate)
{
_runtimeIcalls[icallClass.Name] = icallClass;
}

foreach (var icall_j in v.GetProperty("icalls").EnumerateArray())
{
if (!icall_j.TryGetProperty("name", out var nameElem))
Expand All @@ -148,8 +144,9 @@ private void ReadTable(string filename)
string name = nameElem.GetString()!;
string func = icall_j.GetProperty("func").GetString()!;
bool handles = icall_j.GetProperty("handles").GetBoolean();
int flags = icall_j.TryGetProperty("flags", out var _) ? int.Parse(icall_j.GetProperty("flags").GetString()!) : 0;

icallClass.Icalls.Add(name, new Icall(name, func, handles));
icallClass.Icalls.Add(name, new Icall(name, func, handles, flags));
}
}
}
Expand All @@ -165,42 +162,42 @@ private void ProcessType(Type type)
{
AddSignature(type, method);
}
#if ORIGINAL_NETCORE_SOURCE
catch (Exception ex) when (ex is not LogAsErrorException)
{
Log.LogWarning(null, "WASM0001", "", "", 0, 0, 0, 0, $"Could not get icall, or callbacks for method '{type.FullName}::{method.Name}' because '{ex.Message}'");
#else
catch (Exception)
{
#endif
continue;
}

var className = method.DeclaringType!.FullName!;
if (!_runtimeIcalls.ContainsKey(className))
// Registered at runtime
continue;
lock (_gate)
{
var className = method.DeclaringType!.FullName!;
if (!_runtimeIcalls.ContainsKey(className))
// Registered at runtime
continue;

var icallClass = _runtimeIcalls[className];
var icallClass = _runtimeIcalls[className];

Icall? icall = null;
Icall? icall = null;

// Try name first
icallClass.Icalls.TryGetValue(method.Name, out icall);
if (icall == null)
{
string? methodSig = BuildSignature(method, className);
if (methodSig != null && icallClass.Icalls.ContainsKey(methodSig))
icall = icallClass.Icalls[methodSig];
}
if (icall == null)
// Registered at runtime
continue;
// Try name first
icallClass.Icalls.TryGetValue(method.Name, out icall);
if (icall == null)
{
string? methodSig = BuildSignature(method, className);
if (methodSig != null)
icallClass.Icalls.TryGetValue(methodSig, out icall);

if (icall == null)
// Registered at runtime
continue;
}

icall.Method = method;
icall.TokenIndex = (int)method.MetadataToken & 0xffffff;
icall.Assembly = method.DeclaringType.Module.Assembly.GetName().Name;

icall.Method = method;
icall.TokenIndex = (int)method.MetadataToken & 0xffffff;
icall.Assembly = method.DeclaringType.Module.Assembly.GetName().Name;
_icalls.Add(icall);
_icalls.Add(icall);
}
}

foreach (var nestedType in type.GetNestedTypes())
Expand All @@ -223,13 +220,8 @@ private void ProcessType(Type type)
}
catch (NotImplementedException nie)
{
#if ORIGINAL_NETCORE_SOURCE
Log.LogWarning($"Failed to generate icall function for method '[{method.DeclaringType!.Assembly.GetName().Name}] {className}::{method.Name}'" +
$" because type '{nie.Message}' is not supported for parameter named '{par.Name}'. Ignoring.");
#else
Console.WriteLine($"Failed to generate icall function for method '[{method.DeclaringType!.Assembly.GetName().Name}] {className}::{method.Name}'" +
Log.LogWarning(null, "WASM0001", "", "", 0, 0, 0, 0, $"Failed to generate icall function for method '[{method.DeclaringType!.Assembly.GetName().Name}] {className}::{method.Name}'" +
$" because type '{nie.Message}' is not supported for parameter named '{par.Name}'. Ignoring.");
#endif
return null;
}
pindex++;
Expand All @@ -244,17 +236,14 @@ void AddSignature(Type type, MethodInfo method)
string? signature = SignatureMapper.MethodToSignature(method);
if (signature == null)
{
#if ORIGINAL_NETCORE_SOURCE
throw new LogAsErrorException($"Unsupported parameter type in method '{type.FullName}.{method.Name}'");
#else
throw new InvalidOperationException($"Unsupported parameter type in method '{type.FullName}.{method.Name}'");
#endif
}

#if ORIGINAL_NETCORE_SOURCE
Log.LogMessage(MessageImportance.Normal, $"[icall] Adding signature {signature} for method '{type.FullName}.{method.Name}'");
#endif
_signatures.Add(signature);
lock (_gate)
{
if (_signatures.Add(signature))
Log.LogMessage(MessageImportance.Low, $"Adding icall signature {signature} for method '{type.FullName}.{method.Name}'");
}
}
}

Expand Down Expand Up @@ -346,10 +335,11 @@ private static string GenIcallDecl(Icall icall)

private sealed class Icall : IComparable<Icall>
{
public Icall(string name, string func, bool handles)
public Icall(string name, string func, bool handles, int flags)
{
Name = name;
Func = func;
Flags = flags;
Handles = handles;
TokenIndex = 0;
}
Expand All @@ -358,6 +348,7 @@ public Icall(string name, string func, bool handles)
public string Func;
public string? Assembly;
public bool Handles;
public int Flags;
public int TokenIndex;
public MethodInfo? Method;

Expand Down
36 changes: 12 additions & 24 deletions src/Uno.Wasm.Tuner/InterpToNativeGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// based on https://github.com/dotnet/runtime/commit/4f7a096dce6bb1d69b844b539678fa25ed7b8e20
// Based on https://github.com/dotnet/runtime/commit/711447a

#pragma warning disable CS8632
#pragma warning disable IDE0022
#pragma warning disable IDE0011
#pragma warning disable IDE0007
#pragma warning disable IDE0018
#pragma warning disable IDE0021

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
Expand All @@ -15,12 +16,10 @@
using System.Text;
using System.Collections.Generic;
using System.Globalization;

#if ORIGINAL_NETCORE_SOURCE
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
#endif

using System.Diagnostics.CodeAnalysis;

//
Expand All @@ -32,11 +31,9 @@

internal sealed class InterpToNativeGenerator
{
#if ORIGINAL_NETCORE_SOURCE
private TaskLoggingHelper Log { get; set; }

public InterpToNativeGenerator(TaskLoggingHelper log) => Log = log;
#endif

public void Generate(IEnumerable<string> cookies, string outputPath)
{
Expand All @@ -49,15 +46,9 @@ public void Generate(IEnumerable<string> cookies, string outputPath)
}

if (Utils.CopyIfDifferent(tmpFileName, outputPath, useHash: false))
#if ORIGINAL_NETCORE_SOURCE
Log.LogMessage(MessageImportance.Low, $"Generating managed2native table to '{outputPath}'.");
else
Log.LogMessage(MessageImportance.Low, $"Managed2native table in {outputPath} is unchanged.");
#else
Console.WriteLine($"Generating managed2native table to '{outputPath}'.");
else
Console.WriteLine($"Managed2native table in {outputPath} is unchanged.");
#endif
}
finally
{
Expand All @@ -67,15 +58,16 @@ public void Generate(IEnumerable<string> cookies, string outputPath)

private static void Emit(StreamWriter w, IEnumerable<string> cookies)
{
w.WriteLine("""
/*
* GENERATED FILE, DON'T EDIT
* Generated by InterpToNativeGenerator
*/

# include "pinvoke.h"
# include <stdlib.h>
""");
w.WriteLine(
"""
/*
* GENERATED FILE, DON'T EDIT
* Generated by InterpToNativeGenerator
*/

# include "pinvoke.h"
# include <stdlib.h>
""");

var signatures = cookies.Distinct().ToArray();
foreach (var signature in signatures)
Expand Down Expand Up @@ -126,11 +118,7 @@ private static void Emit(StreamWriter w, IEnumerable<string> cookies)
}
catch (InvalidSignatureCharException e)
{
#if ORIGINAL_NETCORE_SOURCE
throw new LogAsErrorException($"Element '{e.Char}' of signature '{signature}' can't be handled by managed2native generator");
#else
throw new InvalidOperationException($"Element '{e.Char}' of signature '{signature}' can't be handled by managed2native generator");
#endif
}
}

Expand Down
11 changes: 11 additions & 0 deletions src/Uno.Wasm.Tuner/LogAsErrorException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Based on https://github.com/dotnet/runtime/commit/711447a

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

public class LogAsErrorException : System.Exception
{
public LogAsErrorException(string message) : base(message)
{
}
}
4 changes: 4 additions & 0 deletions src/Uno.Wasm.Tuner/MessageImportance.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
internal enum MessageImportance
{
Low
}
Loading