[wasm] Add support for pinvokes in user assemblies. (#14253)
[mono-project.git] / mcs / tools / wasm-tuner / tuner.cs
blob99c1f0479416274672e8ac4aece598c74dc3e47b
1 //
2 // tuner.cs: WebAssembly build time helpers
3 //
4 //
5 using System;
6 using System.IO;
7 using System.Linq;
8 using System.Text;
9 using System.Json;
10 using System.Collections.Generic;
11 using Mono.Cecil;
13 class Icall : IComparable<Icall>
15 public Icall (string name, string func, bool handles) {
16 Name = name;
17 Func = func;
18 Handles = handles;
21 public string Name;
22 public string Func;
23 public string Assembly;
24 public bool Handles;
25 public int TokenIndex;
27 public int CompareTo (Icall other) {
28 return TokenIndex - other.TokenIndex;
32 class IcallClass {
33 public IcallClass (string name) {
34 Name = name;
35 Icalls = new Dictionary<string, Icall> ();
38 public string Name;
39 public Dictionary<string, Icall> Icalls;
42 class Pinvoke
44 public Pinvoke (string entry_point, string module, MethodReference method) {
45 EntryPoint = entry_point;
46 Module = module;
47 Method = method;
50 public string EntryPoint;
51 public string Module;
52 public MethodReference Method;
55 public class WasmTuner
57 public static int Main (String[] args) {
58 return new WasmTuner ().Run (args);
61 List<Icall> icalls;
63 Dictionary<string, IcallClass> runtime_icalls;
65 List<Pinvoke> pinvokes;
67 // Read the icall table generated by mono --print-icall-table
68 void ReadTable (string filename) {
69 JsonValue json;
70 using (var stream = File.OpenText (filename)) {
71 json = JsonValue.Load (stream);
74 runtime_icalls = new Dictionary<string, IcallClass> ();
75 var arr = (JsonArray)json;
76 foreach (var v in arr) {
77 if ((string)v ["klass"] == "")
78 // Dummy value
79 continue;
80 var icall_class = new IcallClass ((string)v ["klass"]);
81 runtime_icalls [icall_class.Name] = icall_class;
82 foreach (JsonObject icall_j in v ["icalls"]) {
83 if (icall_j.Count == 0)
84 continue;
85 string name = (string)icall_j ["name"];
86 string func = (string)icall_j ["func"];
87 bool handles = (bool)icall_j ["handles"];
89 icall_class.Icalls [name] = new Icall (name, func, handles);
94 void Usage () {
95 Console.WriteLine ("Usage: tuner.exe <arguments>");
96 Console.WriteLine ("Arguments:");
97 Console.WriteLine ("--gen-icall-table icall-table.json <assemblies>.");
98 Console.WriteLine ("--gen-pinvoke-table <list of native library names separated by commas> <assemblies>.");
101 int Run (String[] args) {
102 if (args.Length < 1) {
103 Usage ();
104 return 1;
106 string cmd = args [0];
107 if (cmd == "--gen-icall-table") {
108 if (args.Length < 3) {
109 Usage ();
110 return 1;
112 return GenIcallTable (args);
113 } else if (cmd == "--gen-pinvoke-table") {
114 return GenPinvokeTable (args);
115 } else {
116 Usage ();
117 return 1;
121 static string MapType (TypeReference t) {
122 if (t.Name == "Void")
123 return "void";
124 else if (t.Name == "Double")
125 return "double";
126 else if (t.Name == "Float")
127 return "float";
128 else
129 return "int";
132 static string GenPinvokeDecl (Pinvoke pinvoke) {
133 var sb = new StringBuilder ();
134 var method = pinvoke.Method;
135 sb.Append (MapType (method.ReturnType));
136 sb.Append ($" {pinvoke.EntryPoint} (");
137 int pindex = 0;
138 foreach (var p in method.Parameters) {
139 if (pindex > 0)
140 sb.Append (",");
141 sb.Append (MapType (method.Parameters [pindex].ParameterType));
142 pindex ++;
144 sb.Append (");");
145 return sb.ToString ();
148 int GenPinvokeTable (String[] args) {
149 var modules = new Dictionary<string, string> ();
150 foreach (var module in args [1].Split (','))
151 modules [module] = module;
153 args = args.Skip (2).ToArray ();
154 pinvokes = new List<Pinvoke> ();
155 foreach (var fname in args) {
156 var a = AssemblyDefinition.ReadAssembly (fname);
158 foreach (var type in a.MainModule.Types) {
159 ProcessTypeForPinvoke (type);
160 foreach (var nested in type.NestedTypes)
161 ProcessTypeForPinvoke (nested);
165 Console.WriteLine ("// GENERATED FILE, DO NOT MODIFY");
166 Console.WriteLine ("typedef struct {");
167 Console.WriteLine ("const char *name;");
168 Console.WriteLine ("void *func;");
169 Console.WriteLine ("} PinvokeImport;");
170 Console.WriteLine ();
172 foreach (var pinvoke in pinvokes) {
173 if (modules.ContainsKey (pinvoke.Module))
174 Console.WriteLine (GenPinvokeDecl (pinvoke));
177 foreach (var module in modules.Keys) {
178 string symbol = module.Replace (".", "_") + "_imports";
179 Console.WriteLine ("static PinvokeImport " + symbol + " [] = {");
180 foreach (var pinvoke in pinvokes) {
181 if (pinvoke.Module == module)
182 Console.WriteLine ("{\"" + pinvoke.EntryPoint + "\", " + pinvoke.EntryPoint + "},");
184 Console.WriteLine ("{NULL, NULL}");
185 Console.WriteLine ("};");
187 Console.Write ("static void *pinvoke_tables[] = { ");
188 foreach (var module in modules.Keys) {
189 string symbol = module.Replace (".", "_") + "_imports";
190 Console.Write (symbol + ",");
192 Console.WriteLine ("};");
193 Console.Write ("static char *pinvoke_names[] = { ");
194 foreach (var module in modules.Keys) {
195 Console.Write ("\"" + module + "\"" + ",");
197 Console.WriteLine ("};");
199 return 0;
202 void ProcessTypeForPinvoke (TypeDefinition type) {
203 foreach (var method in type.Methods) {
204 var info = method.PInvokeInfo;
205 if (info == null)
206 continue;
207 pinvokes.Add (new Pinvoke (info.EntryPoint, info.Module.Name, method));
212 // Given the runtime generated icall table, and a set of assemblies, generate
213 // a smaller linked icall table mapping tokens to C function names
215 int GenIcallTable (String[] args) {
216 var icall_table_filename = args [1];
217 args = args.Skip (2).ToArray ();
219 ReadTable (icall_table_filename);
221 icalls = new List<Icall> ();
223 foreach (var fname in args) {
224 var a = AssemblyDefinition.ReadAssembly (fname);
226 foreach (var type in a.MainModule.Types) {
227 ProcessType (type);
228 foreach (var nested in type.NestedTypes)
229 ProcessType (nested);
233 var assemblies = new Dictionary<string, string> ();
234 foreach (var icall in icalls)
235 assemblies [icall.Assembly] = icall.Assembly;
237 foreach (var assembly in assemblies.Keys) {
238 var sorted = icalls.Where (i => i.Assembly == assembly).ToArray ();
239 Array.Sort (sorted);
241 Console.WriteLine ($"#define ICALL_TABLE_{assembly} 1\n");
243 Console.WriteLine ($"static int {assembly}_icall_indexes [] = {{");
244 foreach (var icall in sorted)
245 Console.WriteLine (String.Format ("{0},", icall.TokenIndex));
246 Console.WriteLine ("};");
247 foreach (var icall in sorted)
248 Console.WriteLine (String.Format ("void {0} ();", icall.Func));
249 Console.WriteLine ($"static void *{assembly}_icall_funcs [] = {{");
250 foreach (var icall in sorted) {
251 Console.WriteLine (String.Format ("// token {0},", icall.TokenIndex));
252 Console.WriteLine (String.Format ("{0},", icall.Func));
254 Console.WriteLine ("};");
255 Console.WriteLine ($"static uint8_t {assembly}_icall_handles [] = {{");
256 foreach (var icall in sorted)
257 Console.WriteLine (String.Format ("{0},", icall.Handles ? "1" : "0"));
258 Console.WriteLine ("};");
261 return 0;
264 // Append the type name used by the runtime icall tables
265 void AppendType (StringBuilder sb, TypeReference t) {
266 switch (t.MetadataType) {
267 case MetadataType.Char:
268 sb.Append("char");
269 break;
270 case MetadataType.Boolean:
271 sb.Append("bool");
272 break;
273 case MetadataType.Byte:
274 sb.Append ("byte");
275 break;
276 case MetadataType.SByte:
277 sb.Append ("sbyte");
278 break;
279 case MetadataType.Int16:
280 sb.Append ("int16");
281 break;
282 case MetadataType.UInt16:
283 sb.Append ("uint16");
284 break;
285 case MetadataType.Int32:
286 sb.Append ("int");
287 break;
288 case MetadataType.UInt32:
289 sb.Append ("uint");
290 break;
291 case MetadataType.Int64:
292 sb.Append ("long");
293 break;
294 case MetadataType.UInt64:
295 sb.Append ("ulong");
296 break;
297 case MetadataType.IntPtr:
298 sb.Append ("intptr");
299 break;
300 case MetadataType.UIntPtr:
301 sb.Append ("uintptr");
302 break;
303 case MetadataType.Single:
304 sb.Append ("single");
305 break;
306 case MetadataType.Double:
307 sb.Append ("double");
308 break;
309 case MetadataType.Object:
310 sb.Append ("object");
311 break;
312 case MetadataType.String:
313 sb.Append ("string");
314 break;
315 case MetadataType.Array:
316 AppendType (sb, (t as TypeSpecification).ElementType);
317 sb.Append ("[]");
318 break;
319 case MetadataType.ByReference:
320 AppendType (sb, (t as TypeSpecification).ElementType);
321 sb.Append ("&");
322 break;
323 case MetadataType.Pointer:
324 AppendType (sb, (t as TypeSpecification).ElementType);
325 sb.Append ("*");
326 break;
327 default:
328 sb.Append (t.FullName);
329 break;
333 void ProcessType (TypeDefinition type) {
334 foreach (var method in type.Methods) {
335 if ((method.ImplAttributes & MethodImplAttributes.InternalCall) == 0)
336 continue;
338 if (method.Name == ".ctor")
339 continue;
341 var klass_name = method.DeclaringType.FullName;
342 if (!runtime_icalls.ContainsKey (klass_name))
343 // Registered at runtime
344 continue;
346 var klass = runtime_icalls [method.DeclaringType.FullName];
348 Icall icall = null;
350 // Try name first
351 if (klass.Icalls.ContainsKey (method.Name)) {
352 icall = klass.Icalls [method.Name];
354 if (icall == null) {
355 // Then with signature
356 var sig = new StringBuilder (method.Name + "(");
357 int pindex = 0;
358 foreach (var par in method.Parameters) {
359 if (pindex > 0)
360 sig.Append (",");
361 var t = par.ParameterType;
362 AppendType (sig, t);
363 pindex ++;
365 sig.Append (")");
367 if (klass.Icalls.ContainsKey (sig.ToString ())) {
368 icall = klass.Icalls [sig.ToString ()];
371 if (icall == null)
372 // Registered at runtime
373 continue;
375 icall.TokenIndex = (int)method.MetadataToken.RID;
376 icall.Assembly = method.DeclaringType.Module.Assembly.Name.Name;
377 icalls.Add (icall);