/ src / Ryujinx.Horizon.Kernel.Generators / SyscallGenerator.cs
SyscallGenerator.cs
  1  using Microsoft.CodeAnalysis;
  2  using Microsoft.CodeAnalysis.CSharp;
  3  using Microsoft.CodeAnalysis.CSharp.Syntax;
  4  using System;
  5  using System.Collections.Generic;
  6  using System.Diagnostics;
  7  using System.Linq;
  8  
  9  namespace Ryujinx.Horizon.Kernel.Generators
 10  {
 11      [Generator]
 12      class SyscallGenerator : ISourceGenerator
 13      {
 14          private const string ClassNamespace = "Ryujinx.HLE.HOS.Kernel.SupervisorCall";
 15          private const string ClassName = "SyscallDispatch";
 16          private const string A32Suffix = "32";
 17          private const string A64Suffix = "64";
 18          private const string ResultVariableName = "result";
 19          private const string ArgVariablePrefix = "arg";
 20          private const string ResultCheckHelperName = "LogResultAsTrace";
 21  
 22          private const string TypeSystemBoolean = "System.Boolean";
 23          private const string TypeSystemInt32 = "System.Int32";
 24          private const string TypeSystemInt64 = "System.Int64";
 25          private const string TypeSystemUInt32 = "System.UInt32";
 26          private const string TypeSystemUInt64 = "System.UInt64";
 27  
 28          private const string NamespaceKernel = "Ryujinx.HLE.HOS.Kernel";
 29          private const string NamespaceHorizonCommon = "Ryujinx.Horizon.Common";
 30          private const string TypeSvcAttribute = NamespaceKernel + ".SupervisorCall.SvcAttribute";
 31          private const string TypePointerSizedAttribute = NamespaceKernel + ".SupervisorCall.PointerSizedAttribute";
 32          private const string TypeResultName = "Result";
 33          private const string TypeKernelResultName = "KernelResult";
 34          private const string TypeResult = NamespaceHorizonCommon + "." + TypeResultName;
 35          private const string TypeExecutionContext = "IExecutionContext";
 36  
 37          private static readonly string[] _expectedResults = new string[]
 38          {
 39              $"{TypeResultName}.Success",
 40              $"{TypeKernelResultName}.TimedOut",
 41              $"{TypeKernelResultName}.Cancelled",
 42              $"{TypeKernelResultName}.PortRemoteClosed",
 43              $"{TypeKernelResultName}.InvalidState",
 44          };
 45  
 46          private readonly struct OutParameter
 47          {
 48              public readonly string Identifier;
 49              public readonly bool NeedsSplit;
 50  
 51              public OutParameter(string identifier, bool needsSplit = false)
 52              {
 53                  Identifier = identifier;
 54                  NeedsSplit = needsSplit;
 55              }
 56          }
 57  
 58          private struct RegisterAllocatorA32
 59          {
 60              private uint _useSet;
 61              private int _linearIndex;
 62  
 63              public int AllocateSingle()
 64              {
 65                  return Allocate();
 66              }
 67  
 68              public (int, int) AllocatePair()
 69              {
 70                  _linearIndex += _linearIndex & 1;
 71  
 72                  return (Allocate(), Allocate());
 73              }
 74  
 75              private int Allocate()
 76              {
 77                  int regIndex;
 78  
 79                  if (_linearIndex < 4)
 80                  {
 81                      regIndex = _linearIndex++;
 82                  }
 83                  else
 84                  {
 85                      regIndex = -1;
 86  
 87                      for (int i = 0; i < 32; i++)
 88                      {
 89                          if ((_useSet & (1 << i)) == 0)
 90                          {
 91                              regIndex = i;
 92                              break;
 93                          }
 94                      }
 95  
 96                      Debug.Assert(regIndex != -1);
 97                  }
 98  
 99                  _useSet |= 1u << regIndex;
100  
101                  return regIndex;
102              }
103  
104              public void AdvanceLinearIndex()
105              {
106                  _linearIndex++;
107              }
108          }
109  
110          private readonly struct SyscallIdAndName : IComparable<SyscallIdAndName>
111          {
112              public readonly int Id;
113              public readonly string Name;
114  
115              public SyscallIdAndName(int id, string name)
116              {
117                  Id = id;
118                  Name = name;
119              }
120  
121              public int CompareTo(SyscallIdAndName other)
122              {
123                  return Id.CompareTo(other.Id);
124              }
125          }
126  
127          public void Execute(GeneratorExecutionContext context)
128          {
129              SyscallSyntaxReceiver syntaxReceiver = (SyscallSyntaxReceiver)context.SyntaxReceiver;
130  
131              CodeGenerator generator = new CodeGenerator();
132  
133              generator.AppendLine("using Ryujinx.Common.Logging;");
134              generator.AppendLine("using Ryujinx.Cpu;");
135              generator.AppendLine($"using {NamespaceKernel}.Common;");
136              generator.AppendLine($"using {NamespaceKernel}.Memory;");
137              generator.AppendLine($"using {NamespaceKernel}.Process;");
138              generator.AppendLine($"using {NamespaceKernel}.Threading;");
139              generator.AppendLine($"using {NamespaceHorizonCommon};");
140              generator.AppendLine("using System;");
141              generator.AppendLine();
142              generator.EnterScope($"namespace {ClassNamespace}");
143              generator.EnterScope($"static class {ClassName}");
144  
145              GenerateResultCheckHelper(generator);
146              generator.AppendLine();
147  
148              List<SyscallIdAndName> syscalls = new List<SyscallIdAndName>();
149  
150              foreach (var method in syntaxReceiver.SvcImplementations)
151              {
152                  GenerateMethod32(generator, context.Compilation, method);
153                  GenerateMethod64(generator, context.Compilation, method);
154  
155                  foreach (AttributeSyntax attribute in method.AttributeLists.SelectMany(attributeList =>
156                               attributeList.Attributes.Where(attribute =>
157                                   GetCanonicalTypeName(context.Compilation, attribute) == TypeSvcAttribute)))
158                  {
159                      syscalls.AddRange(from attributeArg in attribute.ArgumentList.Arguments
160                                        where attributeArg.Expression.Kind() == SyntaxKind.NumericLiteralExpression
161                                        select (LiteralExpressionSyntax)attributeArg.Expression
162                                        into numericLiteral
163                                        select new SyscallIdAndName((int)numericLiteral.Token.Value, method.Identifier.Text));
164                  }
165              }
166  
167              syscalls.Sort();
168  
169              GenerateDispatch(generator, syscalls, A32Suffix);
170              generator.AppendLine();
171              GenerateDispatch(generator, syscalls, A64Suffix);
172  
173              generator.LeaveScope();
174              generator.LeaveScope();
175  
176              context.AddSource($"{ClassName}.g.cs", generator.ToString());
177          }
178  
179          private static void GenerateResultCheckHelper(CodeGenerator generator)
180          {
181              generator.EnterScope($"private static bool {ResultCheckHelperName}({TypeResultName} {ResultVariableName})");
182  
183              string[] expectedChecks = new string[_expectedResults.Length];
184  
185              for (int i = 0; i < expectedChecks.Length; i++)
186              {
187                  expectedChecks[i] = $"{ResultVariableName} == {_expectedResults[i]}";
188              }
189  
190              string checks = string.Join(" || ", expectedChecks);
191  
192              generator.AppendLine($"return {checks};");
193              generator.LeaveScope();
194          }
195  
196          private static void GenerateMethod32(CodeGenerator generator, Compilation compilation, MethodDeclarationSyntax method)
197          {
198              generator.EnterScope($"private static void {method.Identifier.Text}{A32Suffix}(Syscall syscall, {TypeExecutionContext} context)");
199  
200              string[] args = new string[method.ParameterList.Parameters.Count];
201              int index = 0;
202  
203              RegisterAllocatorA32 regAlloc = new RegisterAllocatorA32();
204  
205              List<OutParameter> outParameters = new List<OutParameter>();
206              List<string> logInArgs = new List<string>();
207              List<string> logOutArgs = new List<string>();
208  
209              foreach (var methodParameter in method.ParameterList.Parameters)
210              {
211                  string name = methodParameter.Identifier.Text;
212                  string argName = GetPrefixedArgName(name);
213                  string typeName = methodParameter.Type.ToString();
214                  string canonicalTypeName = GetCanonicalTypeName(compilation, methodParameter.Type);
215  
216                  if (methodParameter.Modifiers.Any(SyntaxKind.OutKeyword))
217                  {
218                      bool needsSplit = Is64BitInteger(canonicalTypeName) && !IsPointerSized(compilation, methodParameter);
219                      outParameters.Add(new OutParameter(argName, needsSplit));
220                      logOutArgs.Add($"{name}: {GetFormattedLogValue(argName, canonicalTypeName)}");
221  
222                      argName = $"out {typeName} {argName}";
223  
224                      regAlloc.AdvanceLinearIndex();
225                  }
226                  else
227                  {
228                      if (Is64BitInteger(canonicalTypeName))
229                      {
230                          if (IsPointerSized(compilation, methodParameter))
231                          {
232                              int registerIndex = regAlloc.AllocateSingle();
233  
234                              generator.AppendLine($"var {argName} = (uint)context.GetX({registerIndex});");
235                          }
236                          else
237                          {
238                              (int registerIndex, int registerIndex2) = regAlloc.AllocatePair();
239  
240                              string valueLow = $"(ulong)(uint)context.GetX({registerIndex})";
241                              string valueHigh = $"(ulong)(uint)context.GetX({registerIndex2})";
242                              string value = $"{valueLow} | ({valueHigh} << 32)";
243  
244                              generator.AppendLine($"var {argName} = ({typeName})({value});");
245                          }
246                      }
247                      else
248                      {
249                          int registerIndex = regAlloc.AllocateSingle();
250  
251                          string value = GenerateCastFromUInt64($"context.GetX({registerIndex})", canonicalTypeName, typeName);
252  
253                          generator.AppendLine($"var {argName} = {value};");
254                      }
255  
256                      logInArgs.Add($"{name}: {GetFormattedLogValue(argName, canonicalTypeName)}");
257                  }
258  
259                  args[index++] = argName;
260              }
261  
262              GenerateLogPrintBeforeCall(generator, method.Identifier.Text, logInArgs);
263  
264              string argsList = string.Join(", ", args);
265              int returnRegisterIndex = 0;
266              string result = null;
267              string canonicalReturnTypeName = null;
268  
269              if (method.ReturnType.ToString() != "void")
270              {
271                  generator.AppendLine($"var {ResultVariableName} = syscall.{method.Identifier.Text}({argsList});");
272                  canonicalReturnTypeName = GetCanonicalTypeName(compilation, method.ReturnType);
273  
274                  if (canonicalReturnTypeName == TypeResult)
275                  {
276                      generator.AppendLine($"context.SetX({returnRegisterIndex++}, (uint){ResultVariableName}.ErrorCode);");
277                  }
278                  else
279                  {
280                      generator.AppendLine($"context.SetX({returnRegisterIndex++}, (uint){ResultVariableName});");
281                  }
282  
283                  if (Is64BitInteger(canonicalReturnTypeName))
284                  {
285                      generator.AppendLine($"context.SetX({returnRegisterIndex++}, (uint)({ResultVariableName} >> 32));");
286                  }
287  
288                  result = GetFormattedLogValue(ResultVariableName, canonicalReturnTypeName);
289              }
290              else
291              {
292                  generator.AppendLine($"syscall.{method.Identifier.Text}({argsList});");
293              }
294  
295              foreach (OutParameter outParameter in outParameters)
296              {
297                  generator.AppendLine($"context.SetX({returnRegisterIndex++}, (uint){outParameter.Identifier});");
298  
299                  if (outParameter.NeedsSplit)
300                  {
301                      generator.AppendLine($"context.SetX({returnRegisterIndex++}, (uint)({outParameter.Identifier} >> 32));");
302                  }
303              }
304  
305              while (returnRegisterIndex < 4)
306              {
307                  generator.AppendLine($"context.SetX({returnRegisterIndex++}, 0);");
308              }
309  
310              GenerateLogPrintAfterCall(generator, method.Identifier.Text, logOutArgs, result, canonicalReturnTypeName);
311  
312              generator.LeaveScope();
313              generator.AppendLine();
314          }
315  
316          private static void GenerateMethod64(CodeGenerator generator, Compilation compilation, MethodDeclarationSyntax method)
317          {
318              generator.EnterScope($"private static void {method.Identifier.Text}{A64Suffix}(Syscall syscall, {TypeExecutionContext} context)");
319  
320              string[] args = new string[method.ParameterList.Parameters.Count];
321              int registerIndex = 0;
322              int index = 0;
323  
324              List<OutParameter> outParameters = new List<OutParameter>();
325              List<string> logInArgs = new List<string>();
326              List<string> logOutArgs = new List<string>();
327  
328              foreach (var methodParameter in method.ParameterList.Parameters)
329              {
330                  string name = methodParameter.Identifier.Text;
331                  string argName = GetPrefixedArgName(name);
332                  string typeName = methodParameter.Type.ToString();
333                  string canonicalTypeName = GetCanonicalTypeName(compilation, methodParameter.Type);
334  
335                  if (methodParameter.Modifiers.Any(SyntaxKind.OutKeyword))
336                  {
337                      outParameters.Add(new OutParameter(argName));
338                      logOutArgs.Add($"{name}: {GetFormattedLogValue(argName, canonicalTypeName)}");
339                      argName = $"out {typeName} {argName}";
340                      registerIndex++;
341                  }
342                  else
343                  {
344                      string value = GenerateCastFromUInt64($"context.GetX({registerIndex++})", canonicalTypeName, typeName);
345                      generator.AppendLine($"var {argName} = {value};");
346                      logInArgs.Add($"{name}: {GetFormattedLogValue(argName, canonicalTypeName)}");
347                  }
348  
349                  args[index++] = argName;
350              }
351  
352              GenerateLogPrintBeforeCall(generator, method.Identifier.Text, logInArgs);
353  
354              string argsList = string.Join(", ", args);
355              int returnRegisterIndex = 0;
356              string result = null;
357              string canonicalReturnTypeName = null;
358  
359              if (method.ReturnType.ToString() != "void")
360              {
361                  generator.AppendLine($"var {ResultVariableName} = syscall.{method.Identifier.Text}({argsList});");
362                  canonicalReturnTypeName = GetCanonicalTypeName(compilation, method.ReturnType);
363  
364                  if (canonicalReturnTypeName == TypeResult)
365                  {
366                      generator.AppendLine($"context.SetX({returnRegisterIndex++}, (ulong){ResultVariableName}.ErrorCode);");
367                  }
368                  else
369                  {
370                      generator.AppendLine($"context.SetX({returnRegisterIndex++}, (ulong){ResultVariableName});");
371                  }
372  
373                  result = GetFormattedLogValue(ResultVariableName, canonicalReturnTypeName);
374              }
375              else
376              {
377                  generator.AppendLine($"syscall.{method.Identifier.Text}({argsList});");
378              }
379  
380              foreach (OutParameter outParameter in outParameters)
381              {
382                  generator.AppendLine($"context.SetX({returnRegisterIndex++}, (ulong){outParameter.Identifier});");
383              }
384  
385              while (returnRegisterIndex < 8)
386              {
387                  generator.AppendLine($"context.SetX({returnRegisterIndex++}, 0);");
388              }
389  
390              GenerateLogPrintAfterCall(generator, method.Identifier.Text, logOutArgs, result, canonicalReturnTypeName);
391  
392              generator.LeaveScope();
393              generator.AppendLine();
394          }
395  
396          private static string GetFormattedLogValue(string value, string canonicalTypeName)
397          {
398              if (Is32BitInteger(canonicalTypeName))
399              {
400                  return $"0x{{{value}:X8}}";
401              }
402              else if (Is64BitInteger(canonicalTypeName))
403              {
404                  return $"0x{{{value}:X16}}";
405              }
406  
407              return $"{{{value}}}";
408          }
409  
410          private static string GetPrefixedArgName(string name)
411          {
412              return ArgVariablePrefix + char.ToUpperInvariant(name[0]) + name.Substring(1);
413          }
414  
415          private static string GetCanonicalTypeName(Compilation compilation, SyntaxNode syntaxNode)
416          {
417              TypeInfo typeInfo = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetTypeInfo(syntaxNode);
418              if (typeInfo.Type.ContainingNamespace == null)
419              {
420                  return typeInfo.Type.Name;
421              }
422  
423              return $"{typeInfo.Type.ContainingNamespace.ToDisplayString()}.{typeInfo.Type.Name}";
424          }
425  
426          private static void GenerateLogPrintBeforeCall(CodeGenerator generator, string methodName, List<string> argList)
427          {
428              string log = $"{methodName}({string.Join(", ", argList)})";
429              GenerateLogPrint(generator, "Trace", "KernelSvc", log);
430          }
431  
432          private static void GenerateLogPrintAfterCall(
433              CodeGenerator generator,
434              string methodName,
435              List<string> argList,
436              string result,
437              string canonicalResultTypeName)
438          {
439              string log = $"{methodName}({string.Join(", ", argList)})";
440  
441              if (result != null)
442              {
443                  log += $" = {result}";
444              }
445  
446              if (canonicalResultTypeName == TypeResult)
447              {
448                  generator.EnterScope($"if ({ResultCheckHelperName}({ResultVariableName}))");
449                  GenerateLogPrint(generator, "Trace", "KernelSvc", log);
450                  generator.LeaveScope();
451                  generator.EnterScope("else");
452                  GenerateLogPrint(generator, "Warning", "KernelSvc", log);
453                  generator.LeaveScope();
454              }
455              else
456              {
457                  GenerateLogPrint(generator, "Trace", "KernelSvc", log);
458              }
459          }
460  
461          private static void GenerateLogPrint(CodeGenerator generator, string logLevel, string logClass, string log)
462          {
463              generator.AppendLine($"Logger.{logLevel}?.PrintMsg(LogClass.{logClass}, $\"{log}\");");
464          }
465  
466          private static void GenerateDispatch(CodeGenerator generator, List<SyscallIdAndName> syscalls, string suffix)
467          {
468              generator.EnterScope($"public static void Dispatch{suffix}(Syscall syscall, {TypeExecutionContext} context, int id)");
469              generator.EnterScope("switch (id)");
470  
471              foreach (var syscall in syscalls)
472              {
473                  generator.AppendLine($"case {syscall.Id}:");
474                  generator.IncreaseIndentation();
475  
476                  generator.AppendLine($"{syscall.Name}{suffix}(syscall, context);");
477                  generator.AppendLine("break;");
478  
479                  generator.DecreaseIndentation();
480              }
481  
482              generator.AppendLine($"default:");
483              generator.IncreaseIndentation();
484  
485              generator.AppendLine("throw new NotImplementedException($\"SVC 0x{id:X4} is not implemented.\");");
486  
487              generator.DecreaseIndentation();
488  
489              generator.LeaveScope();
490              generator.LeaveScope();
491          }
492  
493          private static bool Is32BitInteger(string canonicalTypeName)
494          {
495              return canonicalTypeName == TypeSystemInt32 || canonicalTypeName == TypeSystemUInt32;
496          }
497  
498          private static bool Is64BitInteger(string canonicalTypeName)
499          {
500              return canonicalTypeName == TypeSystemInt64 || canonicalTypeName == TypeSystemUInt64;
501          }
502  
503          private static string GenerateCastFromUInt64(string value, string canonicalTargetTypeName, string targetTypeName)
504          {
505              return canonicalTargetTypeName == TypeSystemBoolean ? $"({value} & 1) != 0" : $"({targetTypeName}){value}";
506          }
507  
508          private static bool IsPointerSized(Compilation compilation, ParameterSyntax parameterSyntax)
509          {
510              return parameterSyntax.AttributeLists.Any(attributeList =>
511                  attributeList.Attributes.Any(attribute =>
512                      GetCanonicalTypeName(compilation, attribute) == TypePointerSizedAttribute));
513          }
514  
515          public void Initialize(GeneratorInitializationContext context)
516          {
517              context.RegisterForSyntaxNotifications(() => new SyscallSyntaxReceiver());
518          }
519      }
520  }