SyscallGenerator.cs 20 KB

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