SyscallGenerator.cs 20 KB

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