PInvokeTableGenerator.cs 11.9 KB
Newer Older
Z
Zoltan Varga 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
// 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.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Reflection;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

public class PInvokeTableGenerator : Task
{
    [Required]
    public ITaskItem[]? Modules { get; set; }
    [Required]
    public ITaskItem[]? Assemblies { get; set; }
    [Required]
    public string? OutputPath { get; set; }

25 26
    public override bool Execute()
    {
27
        Log.LogMessage(MessageImportance.Normal, $"Generating pinvoke table to '{OutputPath}'.");
28
        GenPInvokeTable(Modules!.Select(item => item.ItemSpec).ToArray(), Assemblies!.Select(item => item.ItemSpec).ToArray());
Z
Zoltan Varga 已提交
29 30 31
        return true;
    }

32
    public void GenPInvokeTable(string[] pinvokeModules, string[] assemblies)
33
    {
34
        var modules = new Dictionary<string, string>();
Z
Zoltan Varga 已提交
35 36 37
        foreach (var module in pinvokeModules)
            modules [module] = module;

38
        var pinvokes = new List<PInvoke>();
39
        var callbacks = new List<PInvokeCallback>();
Z
Zoltan Varga 已提交
40

41 42 43 44 45 46
        var resolver = new PathAssemblyResolver(assemblies);
        var mlc = new MetadataLoadContext(resolver, "System.Private.CoreLib");
        foreach (var aname in assemblies)
        {
            var a = mlc.LoadFromAssemblyPath(aname);
            foreach (var type in a.GetTypes())
47
                CollectPInvokes(pinvokes, callbacks, type);
Z
Zoltan Varga 已提交
48 49
        }

50 51 52
        using (var w = File.CreateText(OutputPath!))
        {
            EmitPInvokeTable(w, modules, pinvokes);
53
            EmitNativeToInterp(w, callbacks);
Z
Zoltan Varga 已提交
54 55 56
        }
    }

57
    private void CollectPInvokes(List<PInvoke> pinvokes, List<PInvokeCallback> callbacks, Type type)
58
    {
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
        foreach (var method in type.GetMethods(BindingFlags.DeclaredOnly|BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Static|BindingFlags.Instance)) {
            if ((method.Attributes & MethodAttributes.PinvokeImpl) != 0)
            {
                var dllimport = method.CustomAttributes.First(attr => attr.AttributeType.Name == "DllImportAttribute");
                var module = (string)dllimport.ConstructorArguments[0].Value!;
                var entrypoint = (string)dllimport.NamedArguments.First(arg => arg.MemberName == "EntryPoint").TypedValue.Value!;
                pinvokes.Add(new PInvoke(entrypoint, module, method));
            }

            foreach (CustomAttributeData cattr in CustomAttributeData.GetCustomAttributes(method))
            {
                try
                {
                    if (cattr.AttributeType.FullName == "System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute" ||
                        cattr.AttributeType.Name == "MonoPInvokeCallbackAttribute")
                        callbacks.Add(new PInvokeCallback(method));
                }
                catch
                {
                    // Assembly not found, ignore
                }
            }
Z
Zoltan Varga 已提交
81 82 83
        }
    }

84 85 86 87 88
    private void EmitPInvokeTable(StreamWriter w, Dictionary<string, string> modules, List<PInvoke> pinvokes)
    {
        w.WriteLine("// GENERATED FILE, DO NOT MODIFY");
        w.WriteLine();

89 90
        var decls = new HashSet<string>();
        foreach (var pinvoke in pinvokes.OrderBy(l => l.EntryPoint))
91
        {
92 93 94 95 96 97 98 99
            if (modules.ContainsKey(pinvoke.Module)) {
                var decl = GenPInvokeDecl(pinvoke);
                if (decls.Contains(decl))
                    continue;

                w.WriteLine(decl);
                decls.Add(decl);
            }
Z
Zoltan Varga 已提交
100 101
        }

102 103 104 105
        foreach (var module in modules.Keys)
        {
            string symbol = module.Replace(".", "_") + "_imports";
            w.WriteLine("static PinvokeImport " + symbol + " [] = {");
106 107 108 109 110 111 112 113 114

            var assemblies_pinvokes = pinvokes.
                Where(l => l.Module == module).
                OrderBy(l => l.EntryPoint).
                GroupBy(d => d.EntryPoint).
                Select (l => "{\"" + l.Key + "\", " + l.Key + "}, // " + string.Join (", ", l.Select(c => c.Method.DeclaringType!.Module!.Assembly!.GetName ()!.Name!).Distinct()));

            foreach (var pinvoke in assemblies_pinvokes) {
                w.WriteLine (pinvoke);
Z
Zoltan Varga 已提交
115
            }
116

117 118
            w.WriteLine("{NULL, NULL}");
            w.WriteLine("};");
Z
Zoltan Varga 已提交
119
        }
120
        w.Write("static void *pinvoke_tables[] = { ");
121 122 123 124
        foreach (var module in modules.Keys)
        {
            string symbol = module.Replace(".", "_") + "_imports";
            w.Write(symbol + ",");
Z
Zoltan Varga 已提交
125
        }
126
        w.WriteLine("};");
127
        w.Write("static char *pinvoke_names[] = { ");
128 129 130
        foreach (var module in modules.Keys)
        {
            w.Write("\"" + module + "\"" + ",");
Z
Zoltan Varga 已提交
131
        }
132
        w.WriteLine("};");
Z
Zoltan Varga 已提交
133 134
    }

135 136
    private string MapType (Type t)
    {
Z
Zoltan Varga 已提交
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
        string name = t.Name;
        if (name == "Void")
            return "void";
        else if (name == "Double")
            return "double";
        else if (name == "Single")
            return "float";
        else if (name == "Int64")
            return "int64_t";
        else if (name == "UInt64")
            return "uint64_t";
        else
            return "int";
    }

152 153 154
    private string GenPInvokeDecl(PInvoke pinvoke)
    {
        var sb = new StringBuilder();
Z
Zoltan Varga 已提交
155
        var method = pinvoke.Method;
156 157 158 159 160 161
        if (method.Name == "EnumCalendarInfo") {
            // FIXME: System.Reflection.MetadataLoadContext can't decode function pointer types
            // https://github.com/dotnet/runtime/issues/43791
            sb.Append($"int {pinvoke.EntryPoint} (int, int, int, int, int);");
            return sb.ToString();
        }
162
        sb.Append(MapType(method.ReturnType));
163
        sb.Append($" {pinvoke.EntryPoint} (");
Z
Zoltan Varga 已提交
164
        int pindex = 0;
165
        var pars = method.GetParameters();
Z
Zoltan Varga 已提交
166 167
        foreach (var p in pars) {
            if (pindex > 0)
168
                sb.Append(',');
169
            sb.Append(MapType(pars[pindex].ParameterType));
170
            pindex++;
Z
Zoltan Varga 已提交
171
        }
172 173
        sb.Append(");");
        return sb.ToString();
Z
Zoltan Varga 已提交
174
    }
175

176
    private void EmitNativeToInterp(StreamWriter w, List<PInvokeCallback> callbacks)
177
    {
178 179 180 181 182 183 184 185 186 187 188 189 190 191
        // Generate native->interp entry functions
        // These are called by native code, so they need to obtain
        // the interp entry function/arg from a global array
        // 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.
        // 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 + "];");

        foreach (var cb in callbacks) {
192 193
            MethodInfo method = cb.Method;
            bool isVoid = method.ReturnType.FullName == "System.Void";
194

195 196
            if (!isVoid && !IsBlittable(method.ReturnType))
                Error($"The return type '{method.ReturnType.FullName}' of pinvoke callback method '{method}' needs to be blittable.");
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
            foreach (var p in method.GetParameters()) {
                if (!IsBlittable(p.ParameterType))
                    Error("Parameter types of pinvoke callback method '" + method + "' needs to be blittable.");
            }
        }

        var callbackNames = new HashSet<string>();

        foreach (var cb in callbacks) {
            var sb = new StringBuilder();
            var method = cb.Method;

            // The signature of the interp entry function
            // This is a gsharedvt_in signature
            sb.Append("typedef void ");
            sb.Append(" (*WasmInterpEntrySig_" + cb_index + ") (");
            int pindex = 0;
            if (method.ReturnType.Name != "Void") {
                sb.Append("int");
216
                pindex++;
217 218 219
            }
            foreach (var p in method.GetParameters()) {
                if (pindex > 0)
220
                    sb.Append(',');
221
                sb.Append("int*");
222
                pindex++;
223 224
            }
            if (pindex > 0)
225
                sb.Append(',');
226
            // Extra arg
227
            sb.Append("int*");
228 229 230 231 232 233 234 235 236 237 238 239 240 241
            sb.Append(");\n");

            bool is_void = method.ReturnType.Name == "Void";

            string module_symbol = method.DeclaringType!.Module!.Assembly!.GetName()!.Name!.Replace(".", "_");
            uint token = (uint)method.MetadataToken;
            string class_name = method.DeclaringType.Name;
            string method_name = method.Name;
            string entry_name = $"wasm_native_to_interp_{module_symbol}_{class_name}_{method_name}";
            if (callbackNames.Contains (entry_name))
            {
                Error($"Two callbacks with the same name '{method_name}' are not supported.");
            }
            callbackNames.Add (entry_name);
242
            cb.EntryName = entry_name;
243 244 245 246 247
            sb.Append(MapType(method.ReturnType));
            sb.Append($" {entry_name} (");
            pindex = 0;
            foreach (var p in method.GetParameters()) {
                if (pindex > 0)
248
                    sb.Append(',');
249 250
                sb.Append(MapType(method.GetParameters()[pindex].ParameterType));
                sb.Append(" arg" + pindex);
251
                pindex++;
252 253 254 255 256 257 258 259
            }
            sb.Append(") { \n");
            if (!is_void)
                sb.Append(MapType(method.ReturnType) + " res;\n");
            sb.Append("((WasmInterpEntrySig_" + cb_index + ")wasm_native_to_interp_ftndescs [" + cb_index + "].func) (");
            pindex = 0;
            if (!is_void) {
                sb.Append("&res");
260
                pindex++;
261 262 263 264 265 266
            }
            int aindex = 0;
            foreach (var p in method.GetParameters()) {
                if (pindex > 0)
                    sb.Append(", ");
                sb.Append("&arg" + aindex);
267 268
                pindex++;
                aindex++;
269 270 271 272 273 274 275
            }
            if (pindex > 0)
                sb.Append(", ");
            sb.Append($"wasm_native_to_interp_ftndescs [{cb_index}].arg");
            sb.Append(");\n");
            if (!is_void)
                sb.Append("return res;\n");
276
            sb.Append('}');
277
            w.WriteLine(sb);
278
            cb_index++;
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
        }

        // Array of function pointers
        w.Write ("static void *wasm_native_to_interp_funcs[] = { ");
        foreach (var cb in callbacks) {
            w.Write (cb.EntryName + ",");
        }
        w.WriteLine ("};");

        // Lookup table from method->interp entry
        // The key is a string of the form <assembly name>_<method token>
        // FIXME: Use a better encoding
        w.Write ("static const char *wasm_native_to_interp_map[] = { ");
        foreach (var cb in callbacks) {
            var method = cb.Method;
            string module_symbol = method.DeclaringType!.Module!.Assembly!.GetName()!.Name!.Replace(".", "_");
            string class_name = method.DeclaringType.Name;
            string method_name = method.Name;
            w.WriteLine ($"\"{module_symbol}_{class_name}_{method_name}\",");
        }
        w.WriteLine ("};");
    }
301

302
    private static bool IsBlittable (Type type)
303 304 305 306 307 308 309
    {
        if (type.IsPrimitive || type.IsByRef || type.IsPointer)
            return true;
        else
            return false;
    }

310
    private static void Error (string msg)
311 312 313 314
    {
        // FIXME:
        throw new Exception(msg);
    }
Z
Zoltan Varga 已提交
315 316
}

317
internal class PInvoke
Z
Zoltan Varga 已提交
318
{
319 320 321
    public PInvoke(string entryPoint, string module, MethodInfo method)
    {
        EntryPoint = entryPoint;
Z
Zoltan Varga 已提交
322 323 324 325 326 327 328 329
        Module = module;
        Method = method;
    }

    public string EntryPoint;
    public string Module;
    public MethodInfo Method;
}
330

331
internal class PInvokeCallback
332 333 334 335 336 337 338 339 340
{
    public PInvokeCallback(MethodInfo method)
    {
        Method = method;
    }

    public MethodInfo Method;
    public string? EntryName;
}