SyscallGenerator.cs 21 KB

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