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 }