/ src / Ryujinx.Horizon.Generators / Hipc / HipcGenerator.cs
HipcGenerator.cs
  1  using Microsoft.CodeAnalysis;
  2  using Microsoft.CodeAnalysis.CSharp;
  3  using Microsoft.CodeAnalysis.CSharp.Syntax;
  4  using System.Collections.Generic;
  5  using System.Linq;
  6  
  7  namespace Ryujinx.Horizon.Generators.Hipc
  8  {
  9      [Generator]
 10      class HipcGenerator : ISourceGenerator
 11      {
 12          private const string ArgVariablePrefix = "arg";
 13          private const string ResultVariableName = "result";
 14          private const string IsBufferMapAliasVariableName = "isBufferMapAlias";
 15          private const string InObjectsVariableName = "inObjects";
 16          private const string OutObjectsVariableName = "outObjects";
 17          private const string ResponseVariableName = "response";
 18          private const string OutRawDataVariableName = "outRawData";
 19  
 20          private const string TypeSystemBuffersReadOnlySequence = "System.Buffers.ReadOnlySequence";
 21          private const string TypeSystemMemory = "System.Memory";
 22          private const string TypeSystemReadOnlySpan = "System.ReadOnlySpan";
 23          private const string TypeSystemSpan = "System.Span";
 24          private const string TypeStructLayoutAttribute = "System.Runtime.InteropServices.StructLayoutAttribute";
 25  
 26          public const string CommandAttributeName = "CmifCommandAttribute";
 27  
 28          private const string TypeResult = "Ryujinx.Horizon.Common.Result";
 29          private const string TypeBufferAttribute = "Ryujinx.Horizon.Sdk.Sf.BufferAttribute";
 30          private const string TypeCopyHandleAttribute = "Ryujinx.Horizon.Sdk.Sf.CopyHandleAttribute";
 31          private const string TypeMoveHandleAttribute = "Ryujinx.Horizon.Sdk.Sf.MoveHandleAttribute";
 32          private const string TypeClientProcessIdAttribute = "Ryujinx.Horizon.Sdk.Sf.ClientProcessIdAttribute";
 33          private const string TypeCommandAttribute = "Ryujinx.Horizon.Sdk.Sf." + CommandAttributeName;
 34          private const string TypeIServiceObject = "Ryujinx.Horizon.Sdk.Sf.IServiceObject";
 35  
 36          private enum Modifier
 37          {
 38              None,
 39              Ref,
 40              Out,
 41              In,
 42          }
 43  
 44          private readonly struct OutParameter
 45          {
 46              public readonly string Name;
 47              public readonly string TypeName;
 48              public readonly int Index;
 49              public readonly CommandArgType Type;
 50  
 51              public OutParameter(string name, string typeName, int index, CommandArgType type)
 52              {
 53                  Name = name;
 54                  TypeName = typeName;
 55                  Index = index;
 56                  Type = type;
 57              }
 58          }
 59  
 60          public void Execute(GeneratorExecutionContext context)
 61          {
 62              HipcSyntaxReceiver syntaxReceiver = (HipcSyntaxReceiver)context.SyntaxReceiver;
 63  
 64              foreach (var commandInterface in syntaxReceiver.CommandInterfaces)
 65              {
 66                  if (!NeedsIServiceObjectImplementation(context.Compilation, commandInterface.ClassDeclarationSyntax))
 67                  {
 68                      continue;
 69                  }
 70  
 71                  CodeGenerator generator = new CodeGenerator();
 72                  string className = commandInterface.ClassDeclarationSyntax.Identifier.ToString();
 73  
 74                  generator.AppendLine("using Ryujinx.Horizon.Common;");
 75                  generator.AppendLine("using Ryujinx.Horizon.Sdk.Sf;");
 76                  generator.AppendLine("using Ryujinx.Horizon.Sdk.Sf.Cmif;");
 77                  generator.AppendLine("using Ryujinx.Horizon.Sdk.Sf.Hipc;");
 78                  generator.AppendLine("using System;");
 79                  generator.AppendLine("using System.Collections.Frozen;");
 80                  generator.AppendLine("using System.Collections.Generic;");
 81                  generator.AppendLine("using System.Runtime.CompilerServices;");
 82                  generator.AppendLine("using System.Runtime.InteropServices;");
 83                  generator.AppendLine();
 84                  generator.EnterScope($"namespace {GetNamespaceName(commandInterface.ClassDeclarationSyntax)}");
 85                  generator.EnterScope($"partial class {className}");
 86  
 87                  GenerateMethodTable(generator, context.Compilation, commandInterface);
 88  
 89                  foreach (var method in commandInterface.CommandImplementations)
 90                  {
 91                      generator.AppendLine();
 92  
 93                      GenerateMethod(generator, context.Compilation, method);
 94                  }
 95  
 96                  generator.LeaveScope();
 97                  generator.LeaveScope();
 98  
 99                  context.AddSource($"{GetNamespaceName(commandInterface.ClassDeclarationSyntax)}.{className}.g.cs", generator.ToString());
100              }
101          }
102  
103          private static string GetNamespaceName(SyntaxNode syntaxNode)
104          {
105              while (syntaxNode != null && !(syntaxNode is NamespaceDeclarationSyntax))
106              {
107                  syntaxNode = syntaxNode.Parent;
108              }
109  
110              if (syntaxNode == null)
111              {
112                  return string.Empty;
113              }
114  
115              return ((NamespaceDeclarationSyntax)syntaxNode).Name.ToString();
116          }
117  
118          private static void GenerateMethodTable(CodeGenerator generator, Compilation compilation, CommandInterface commandInterface)
119          {
120              generator.EnterScope($"public IReadOnlyDictionary<int, CommandHandler> GetCommandHandlers()");
121  
122              if (commandInterface.CommandImplementations.Count == 0)
123              {
124                  generator.AppendLine("return FrozenDictionary<int, CommandHandler>.Empty;");
125              }
126              else
127              {
128                  generator.EnterScope($"return FrozenDictionary.ToFrozenDictionary(new []");
129  
130                  foreach (var method in commandInterface.CommandImplementations)
131                  {
132                      foreach (var commandId in GetAttributeArguments(compilation, method, TypeCommandAttribute, 0))
133                      {
134                          string[] args = new string[method.ParameterList.Parameters.Count];
135  
136                          if (args.Length == 0)
137                          {
138                              generator.AppendLine($"KeyValuePair.Create({commandId}, new CommandHandler({method.Identifier.Text}, Array.Empty<CommandArg>())),");
139                          }
140                          else
141                          {
142                              int index = 0;
143  
144                              foreach (var parameter in method.ParameterList.Parameters)
145                              {
146                                  string canonicalTypeName = GetCanonicalTypeNameWithGenericArguments(compilation, parameter.Type);
147                                  CommandArgType argType = GetCommandArgType(compilation, parameter);
148  
149                                  string arg;
150  
151                                  if (argType == CommandArgType.Buffer)
152                                  {
153                                      string bufferFlags = GetFirstAttributeArgument(compilation, parameter, TypeBufferAttribute, 0);
154                                      string bufferFixedSize = GetFirstAttributeArgument(compilation, parameter, TypeBufferAttribute, 1);
155  
156                                      if (bufferFixedSize != null)
157                                      {
158                                          arg = $"new CommandArg({bufferFlags} | HipcBufferFlags.FixedSize, {bufferFixedSize})";
159                                      }
160                                      else
161                                      {
162                                          arg = $"new CommandArg({bufferFlags})";
163                                      }
164                                  }
165                                  else if (argType == CommandArgType.InArgument || argType == CommandArgType.OutArgument)
166                                  {
167                                      string alignment = GetTypeAlignmentExpression(compilation, parameter.Type);
168  
169                                      arg = $"new CommandArg(CommandArgType.{argType}, Unsafe.SizeOf<{canonicalTypeName}>(), {alignment})";
170                                  }
171                                  else
172                                  {
173                                      arg = $"new CommandArg(CommandArgType.{argType})";
174                                  }
175  
176                                  args[index++] = arg;
177                              }
178  
179                              generator.AppendLine($"KeyValuePair.Create({commandId}, new CommandHandler({method.Identifier.Text}, {string.Join(", ", args)})),");
180                          }
181                      }
182                  }
183  
184                  generator.LeaveScope(");");
185              }
186  
187              generator.LeaveScope();
188          }
189  
190          private static IEnumerable<string> GetAttributeArguments(Compilation compilation, SyntaxNode syntaxNode, string attributeName, int argIndex)
191          {
192              ISymbol symbol = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetDeclaredSymbol(syntaxNode);
193  
194              foreach (var attribute in symbol.GetAttributes())
195              {
196                  if (attribute.AttributeClass.ToDisplayString() == attributeName && (uint)argIndex < (uint)attribute.ConstructorArguments.Length)
197                  {
198                      yield return attribute.ConstructorArguments[argIndex].ToCSharpString();
199                  }
200              }
201          }
202  
203          private static string GetFirstAttributeArgument(Compilation compilation, SyntaxNode syntaxNode, string attributeName, int argIndex)
204          {
205              return GetAttributeArguments(compilation, syntaxNode, attributeName, argIndex).FirstOrDefault();
206          }
207  
208          private static void GenerateMethod(CodeGenerator generator, Compilation compilation, MethodDeclarationSyntax method)
209          {
210              int inObjectsCount = 0;
211              int outObjectsCount = 0;
212              int buffersCount = 0;
213  
214              foreach (var parameter in method.ParameterList.Parameters)
215              {
216                  if (IsObject(compilation, parameter))
217                  {
218                      if (IsIn(parameter))
219                      {
220                          inObjectsCount++;
221                      }
222                      else
223                      {
224                          outObjectsCount++;
225                      }
226                  }
227                  else if (IsBuffer(compilation, parameter))
228                  {
229                      buffersCount++;
230                  }
231              }
232  
233              generator.EnterScope($"private Result {method.Identifier.Text}(" +
234                  "ref ServiceDispatchContext context, " +
235                  "HipcCommandProcessor processor, " +
236                  "ServerMessageRuntimeMetadata runtimeMetadata, " +
237                  "ReadOnlySpan<byte> inRawData, " +
238                  "ref Span<CmifOutHeader> outHeader)");
239  
240              bool returnsResult = method.ReturnType != null && GetCanonicalTypeName(compilation, method.ReturnType) == TypeResult;
241  
242              if (returnsResult || buffersCount != 0 || inObjectsCount != 0)
243              {
244                  generator.AppendLine($"Result {ResultVariableName};");
245  
246                  if (buffersCount != 0)
247                  {
248                      generator.AppendLine($"Span<bool> {IsBufferMapAliasVariableName} = stackalloc bool[{method.ParameterList.Parameters.Count}];");
249                      generator.AppendLine();
250  
251                      generator.AppendLine($"{ResultVariableName} = processor.ProcessBuffers(ref context, {IsBufferMapAliasVariableName}, runtimeMetadata);");
252                      generator.EnterScope($"if ({ResultVariableName}.IsFailure)");
253                      generator.AppendLine($"return {ResultVariableName};");
254                      generator.LeaveScope();
255                  }
256  
257                  generator.AppendLine();
258              }
259  
260              List<OutParameter> outParameters = new List<OutParameter>();
261  
262              string[] args = new string[method.ParameterList.Parameters.Count];
263  
264              if (inObjectsCount != 0)
265              {
266                  generator.AppendLine($"var {InObjectsVariableName} = new IServiceObject[{inObjectsCount}];");
267                  generator.AppendLine();
268  
269                  generator.AppendLine($"{ResultVariableName} = processor.GetInObjects(context.Processor, {InObjectsVariableName});");
270                  generator.EnterScope($"if ({ResultVariableName}.IsFailure)");
271                  generator.AppendLine($"return {ResultVariableName};");
272                  generator.LeaveScope();
273                  generator.AppendLine();
274              }
275  
276              if (outObjectsCount != 0)
277              {
278                  generator.AppendLine($"var {OutObjectsVariableName} = new IServiceObject[{outObjectsCount}];");
279              }
280  
281              int index = 0;
282              int inArgIndex = 0;
283              int outArgIndex = 0;
284              int inCopyHandleIndex = 0;
285              int inMoveHandleIndex = 0;
286              int inObjectIndex = 0;
287  
288              foreach (var parameter in method.ParameterList.Parameters)
289              {
290                  string name = parameter.Identifier.Text;
291                  string argName = GetPrefixedArgName(name);
292                  string canonicalTypeName = GetCanonicalTypeNameWithGenericArguments(compilation, parameter.Type);
293                  CommandArgType argType = GetCommandArgType(compilation, parameter);
294                  Modifier modifier = GetModifier(parameter);
295                  bool isNonSpanBuffer = false;
296  
297                  if (modifier == Modifier.Out)
298                  {
299                      if (IsNonSpanOutBuffer(compilation, parameter))
300                      {
301                          generator.AppendLine($"using var {argName} = CommandSerialization.GetWritableRegion(processor.GetBufferRange({index}));");
302  
303                          argName = $"out {GenerateSpanCastElement0(canonicalTypeName, $"{argName}.Memory.Span")}";
304                      }
305                      else
306                      {
307                          outParameters.Add(new OutParameter(argName, canonicalTypeName, outArgIndex++, argType));
308  
309                          argName = $"out {canonicalTypeName} {argName}";
310                      }
311                  }
312                  else
313                  {
314                      string value = $"default({canonicalTypeName})";
315  
316                      switch (argType)
317                      {
318                          case CommandArgType.InArgument:
319                              value = $"CommandSerialization.DeserializeArg<{canonicalTypeName}>(inRawData, processor.GetInArgOffset({inArgIndex++}))";
320                              break;
321                          case CommandArgType.InCopyHandle:
322                              value = $"CommandSerialization.DeserializeCopyHandle(ref context, {inCopyHandleIndex++})";
323                              break;
324                          case CommandArgType.InMoveHandle:
325                              value = $"CommandSerialization.DeserializeMoveHandle(ref context, {inMoveHandleIndex++})";
326                              break;
327                          case CommandArgType.ProcessId:
328                              value = "CommandSerialization.DeserializeClientProcessId(ref context)";
329                              break;
330                          case CommandArgType.InObject:
331                              value = $"{InObjectsVariableName}[{inObjectIndex++}]";
332                              break;
333                          case CommandArgType.Buffer:
334                              if (IsMemory(compilation, parameter))
335                              {
336                                  value = $"CommandSerialization.GetWritableRegion(processor.GetBufferRange({index}))";
337                              }
338                              else if (IsReadOnlySequence(compilation, parameter))
339                              {
340                                  value = $"CommandSerialization.GetReadOnlySequence(processor.GetBufferRange({index}))";
341                              }
342                              else if (IsReadOnlySpan(compilation, parameter))
343                              {
344                                  string spanGenericTypeName = GetCanonicalTypeNameOfGenericArgument(compilation, parameter.Type, 0);
345                                  value = GenerateSpanCast(spanGenericTypeName, $"CommandSerialization.GetReadOnlySpan(processor.GetBufferRange({index}))");
346                              }
347                              else if (IsSpan(compilation, parameter))
348                              {
349                                  value = $"CommandSerialization.GetWritableRegion(processor.GetBufferRange({index}))";
350                              }
351                              else
352                              {
353                                  value = $"CommandSerialization.GetRef<{canonicalTypeName}>(processor.GetBufferRange({index}))";
354                                  isNonSpanBuffer = true;
355                              }
356                              break;
357                      }
358  
359                      if (IsMemory(compilation, parameter))
360                      {
361                          generator.AppendLine($"using var {argName} = {value};");
362  
363                          argName = $"{argName}.Memory";
364                      }
365                      else if (IsSpan(compilation, parameter))
366                      {
367                          generator.AppendLine($"using var {argName} = {value};");
368  
369                          string spanGenericTypeName = GetCanonicalTypeNameOfGenericArgument(compilation, parameter.Type, 0);
370                          argName = GenerateSpanCast(spanGenericTypeName, $"{argName}.Memory.Span");
371                      }
372                      else if (isNonSpanBuffer)
373                      {
374                          generator.AppendLine($"ref var {argName} = ref {value};");
375                      }
376                      else if (argType == CommandArgType.InObject)
377                      {
378                          generator.EnterScope($"if (!({value} is {canonicalTypeName} {argName}))");
379                          generator.AppendLine("return SfResult.InvalidInObject;");
380                          generator.LeaveScope();
381                      }
382                      else
383                      {
384                          generator.AppendLine($"var {argName} = {value};");
385                      }
386                  }
387  
388                  if (modifier == Modifier.Ref)
389                  {
390                      argName = $"ref {argName}";
391                  }
392                  else if (modifier == Modifier.In)
393                  {
394                      argName = $"in {argName}";
395                  }
396  
397                  args[index++] = argName;
398              }
399  
400              if (args.Length - outParameters.Count > 0)
401              {
402                  generator.AppendLine();
403              }
404  
405              if (returnsResult)
406              {
407                  generator.AppendLine($"{ResultVariableName} = {method.Identifier.Text}({string.Join(", ", args)});");
408                  generator.AppendLine();
409  
410                  generator.AppendLine($"Span<byte> {OutRawDataVariableName};");
411                  generator.AppendLine();
412  
413                  generator.EnterScope($"if ({ResultVariableName}.IsFailure)");
414                  generator.AppendLine($"context.Processor.PrepareForErrorReply(ref context, out {OutRawDataVariableName}, runtimeMetadata);");
415                  generator.AppendLine($"CommandHandler.GetCmifOutHeaderPointer(ref outHeader, ref {OutRawDataVariableName});");
416                  generator.AppendLine($"return {ResultVariableName};");
417                  generator.LeaveScope();
418              }
419              else
420              {
421                  generator.AppendLine($"{method.Identifier.Text}({string.Join(", ", args)});");
422  
423                  generator.AppendLine();
424                  generator.AppendLine($"Span<byte> {OutRawDataVariableName};");
425              }
426  
427              generator.AppendLine();
428  
429              generator.AppendLine($"var {ResponseVariableName} = context.Processor.PrepareForReply(ref context, out {OutRawDataVariableName}, runtimeMetadata);");
430              generator.AppendLine($"CommandHandler.GetCmifOutHeaderPointer(ref outHeader, ref {OutRawDataVariableName});");
431              generator.AppendLine();
432  
433              generator.EnterScope($"if ({OutRawDataVariableName}.Length < processor.OutRawDataSize)");
434              generator.AppendLine("return SfResult.InvalidOutRawSize;");
435              generator.LeaveScope();
436  
437              if (outParameters.Count != 0)
438              {
439                  generator.AppendLine();
440  
441                  int outCopyHandleIndex = 0;
442                  int outMoveHandleIndex = outObjectsCount;
443                  int outObjectIndex = 0;
444  
445                  for (int outIndex = 0; outIndex < outParameters.Count; outIndex++)
446                  {
447                      OutParameter outParameter = outParameters[outIndex];
448  
449                      switch (outParameter.Type)
450                      {
451                          case CommandArgType.OutArgument:
452                              generator.AppendLine($"CommandSerialization.SerializeArg<{outParameter.TypeName}>({OutRawDataVariableName}, processor.GetOutArgOffset({outParameter.Index}), {outParameter.Name});");
453                              break;
454                          case CommandArgType.OutCopyHandle:
455                              generator.AppendLine($"CommandSerialization.SerializeCopyHandle({ResponseVariableName}, {outCopyHandleIndex++}, {outParameter.Name});");
456                              break;
457                          case CommandArgType.OutMoveHandle:
458                              generator.AppendLine($"CommandSerialization.SerializeMoveHandle({ResponseVariableName}, {outMoveHandleIndex++}, {outParameter.Name});");
459                              break;
460                          case CommandArgType.OutObject:
461                              generator.AppendLine($"{OutObjectsVariableName}[{outObjectIndex++}] = {outParameter.Name};");
462                              break;
463                      }
464                  }
465              }
466  
467              generator.AppendLine();
468  
469              if (outObjectsCount != 0 || buffersCount != 0)
470              {
471                  if (outObjectsCount != 0)
472                  {
473                      generator.AppendLine($"processor.SetOutObjects(ref context, {ResponseVariableName}, {OutObjectsVariableName});");
474                  }
475  
476                  if (buffersCount != 0)
477                  {
478                      generator.AppendLine($"processor.SetOutBuffers({ResponseVariableName}, {IsBufferMapAliasVariableName});");
479                  }
480  
481                  generator.AppendLine();
482              }
483  
484              generator.AppendLine("return Result.Success;");
485              generator.LeaveScope();
486          }
487  
488          private static string GetPrefixedArgName(string name)
489          {
490              return ArgVariablePrefix + name[0].ToString().ToUpperInvariant() + name.Substring(1);
491          }
492  
493          private static string GetCanonicalTypeNameOfGenericArgument(Compilation compilation, SyntaxNode syntaxNode, int argIndex)
494          {
495              if (syntaxNode is GenericNameSyntax genericNameSyntax)
496              {
497                  if ((uint)argIndex < (uint)genericNameSyntax.TypeArgumentList.Arguments.Count)
498                  {
499                      return GetCanonicalTypeNameWithGenericArguments(compilation, genericNameSyntax.TypeArgumentList.Arguments[argIndex]);
500                  }
501              }
502  
503              return GetCanonicalTypeName(compilation, syntaxNode);
504          }
505  
506          private static string GetCanonicalTypeNameWithGenericArguments(Compilation compilation, SyntaxNode syntaxNode)
507          {
508              TypeInfo typeInfo = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetTypeInfo(syntaxNode);
509  
510              return typeInfo.Type.ToDisplayString();
511          }
512  
513          private static string GetCanonicalTypeName(Compilation compilation, SyntaxNode syntaxNode)
514          {
515              TypeInfo typeInfo = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetTypeInfo(syntaxNode);
516              string typeName = typeInfo.Type.ToDisplayString();
517  
518              int genericArgsStartIndex = typeName.IndexOf('<');
519              if (genericArgsStartIndex >= 0)
520              {
521                  return typeName.Substring(0, genericArgsStartIndex);
522              }
523  
524              return typeName;
525          }
526  
527          private static SpecialType GetSpecialTypeName(Compilation compilation, SyntaxNode syntaxNode)
528          {
529              TypeInfo typeInfo = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetTypeInfo(syntaxNode);
530  
531              return typeInfo.Type.SpecialType;
532          }
533  
534          private static string GetTypeAlignmentExpression(Compilation compilation, SyntaxNode syntaxNode)
535          {
536              TypeInfo typeInfo = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetTypeInfo(syntaxNode);
537  
538              // Since there's no way to get the alignment for a arbitrary type here, let's assume that all
539              // "special" types are primitive types aligned to their own length.
540              // Otherwise, assume that the type is a custom struct, that either defines an explicit alignment
541              // or has an alignment of 1 which is the lowest possible value.
542              if (typeInfo.Type.SpecialType == SpecialType.None)
543              {
544                  string pack = GetTypeFirstNamedAttributeAgument(compilation, syntaxNode, TypeStructLayoutAttribute, "Pack");
545  
546                  return pack ?? "1";
547              }
548              else
549              {
550                  return $"Unsafe.SizeOf<{typeInfo.Type.ToDisplayString()}>()";
551              }
552          }
553  
554          private static string GetTypeFirstNamedAttributeAgument(Compilation compilation, SyntaxNode syntaxNode, string attributeName, string argName)
555          {
556              ISymbol symbol = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetTypeInfo(syntaxNode).Type;
557  
558              foreach (var attribute in symbol.GetAttributes())
559              {
560                  if (attribute.AttributeClass.ToDisplayString() == attributeName)
561                  {
562                      foreach (var kv in attribute.NamedArguments)
563                      {
564                          if (kv.Key == argName)
565                          {
566                              return kv.Value.ToCSharpString();
567                          }
568                      }
569                  }
570              }
571  
572              return null;
573          }
574  
575          private static CommandArgType GetCommandArgType(Compilation compilation, ParameterSyntax parameter)
576          {
577              CommandArgType type = CommandArgType.Invalid;
578  
579              if (IsIn(parameter))
580              {
581                  if (IsArgument(compilation, parameter))
582                  {
583                      type = CommandArgType.InArgument;
584                  }
585                  else if (IsBuffer(compilation, parameter))
586                  {
587                      type = CommandArgType.Buffer;
588                  }
589                  else if (IsCopyHandle(compilation, parameter))
590                  {
591                      type = CommandArgType.InCopyHandle;
592                  }
593                  else if (IsMoveHandle(compilation, parameter))
594                  {
595                      type = CommandArgType.InMoveHandle;
596                  }
597                  else if (IsObject(compilation, parameter))
598                  {
599                      type = CommandArgType.InObject;
600                  }
601                  else if (IsProcessId(compilation, parameter))
602                  {
603                      type = CommandArgType.ProcessId;
604                  }
605              }
606              else if (IsOut(parameter))
607              {
608                  if (IsArgument(compilation, parameter))
609                  {
610                      type = CommandArgType.OutArgument;
611                  }
612                  else if (IsNonSpanOutBuffer(compilation, parameter))
613                  {
614                      type = CommandArgType.Buffer;
615                  }
616                  else if (IsCopyHandle(compilation, parameter))
617                  {
618                      type = CommandArgType.OutCopyHandle;
619                  }
620                  else if (IsMoveHandle(compilation, parameter))
621                  {
622                      type = CommandArgType.OutMoveHandle;
623                  }
624                  else if (IsObject(compilation, parameter))
625                  {
626                      type = CommandArgType.OutObject;
627                  }
628              }
629  
630              return type;
631          }
632  
633          private static bool IsArgument(Compilation compilation, ParameterSyntax parameter)
634          {
635              return !IsBuffer(compilation, parameter) &&
636                     !IsHandle(compilation, parameter) &&
637                     !IsObject(compilation, parameter) &&
638                     !IsProcessId(compilation, parameter) &&
639                     IsUnmanagedType(compilation, parameter.Type);
640          }
641  
642          private static bool IsBuffer(Compilation compilation, ParameterSyntax parameter)
643          {
644              return HasAttribute(compilation, parameter, TypeBufferAttribute) &&
645                     IsValidTypeForBuffer(compilation, parameter);
646          }
647  
648          private static bool IsNonSpanOutBuffer(Compilation compilation, ParameterSyntax parameter)
649          {
650              return HasAttribute(compilation, parameter, TypeBufferAttribute) &&
651                     IsUnmanagedType(compilation, parameter.Type);
652          }
653  
654          private static bool IsValidTypeForBuffer(Compilation compilation, ParameterSyntax parameter)
655          {
656              return IsMemory(compilation, parameter) ||
657                     IsReadOnlySequence(compilation, parameter) ||
658                     IsReadOnlySpan(compilation, parameter) ||
659                     IsSpan(compilation, parameter) ||
660                     IsUnmanagedType(compilation, parameter.Type);
661          }
662  
663          private static bool IsUnmanagedType(Compilation compilation, SyntaxNode syntaxNode)
664          {
665              TypeInfo typeInfo = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetTypeInfo(syntaxNode);
666  
667              return typeInfo.Type.IsUnmanagedType;
668          }
669  
670          private static bool IsMemory(Compilation compilation, ParameterSyntax parameter)
671          {
672              return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemMemory;
673          }
674  
675          private static bool IsReadOnlySequence(Compilation compilation, ParameterSyntax parameter)
676          {
677              return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemBuffersReadOnlySequence;
678          }
679  
680          private static bool IsReadOnlySpan(Compilation compilation, ParameterSyntax parameter)
681          {
682              return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemReadOnlySpan;
683          }
684  
685          private static bool IsSpan(Compilation compilation, ParameterSyntax parameter)
686          {
687              return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemSpan;
688          }
689  
690          private static bool IsHandle(Compilation compilation, ParameterSyntax parameter)
691          {
692              return IsCopyHandle(compilation, parameter) || IsMoveHandle(compilation, parameter);
693          }
694  
695          private static bool IsCopyHandle(Compilation compilation, ParameterSyntax parameter)
696          {
697              return HasAttribute(compilation, parameter, TypeCopyHandleAttribute) &&
698                     GetSpecialTypeName(compilation, parameter.Type) == SpecialType.System_Int32;
699          }
700  
701          private static bool IsMoveHandle(Compilation compilation, ParameterSyntax parameter)
702          {
703              return HasAttribute(compilation, parameter, TypeMoveHandleAttribute) &&
704                     GetSpecialTypeName(compilation, parameter.Type) == SpecialType.System_Int32;
705          }
706  
707          private static bool IsObject(Compilation compilation, ParameterSyntax parameter)
708          {
709              SyntaxNode syntaxNode = parameter.Type;
710              TypeInfo typeInfo = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetTypeInfo(syntaxNode);
711  
712              return typeInfo.Type.ToDisplayString() == TypeIServiceObject ||
713                     typeInfo.Type.AllInterfaces.Any(x => x.ToDisplayString() == TypeIServiceObject);
714          }
715  
716          private static bool IsProcessId(Compilation compilation, ParameterSyntax parameter)
717          {
718              return HasAttribute(compilation, parameter, TypeClientProcessIdAttribute) &&
719                     GetSpecialTypeName(compilation, parameter.Type) == SpecialType.System_UInt64;
720          }
721  
722          private static bool IsIn(ParameterSyntax parameter)
723          {
724              return !IsOut(parameter);
725          }
726  
727          private static bool IsOut(ParameterSyntax parameter)
728          {
729              return parameter.Modifiers.Any(SyntaxKind.OutKeyword);
730          }
731  
732          private static Modifier GetModifier(ParameterSyntax parameter)
733          {
734              foreach (SyntaxToken syntaxToken in parameter.Modifiers)
735              {
736                  if (syntaxToken.IsKind(SyntaxKind.RefKeyword))
737                  {
738                      return Modifier.Ref;
739                  }
740                  else if (syntaxToken.IsKind(SyntaxKind.OutKeyword))
741                  {
742                      return Modifier.Out;
743                  }
744                  else if (syntaxToken.IsKind(SyntaxKind.InKeyword))
745                  {
746                      return Modifier.In;
747                  }
748              }
749  
750              return Modifier.None;
751          }
752  
753          private static string GenerateSpanCastElement0(string targetType, string input)
754          {
755              return $"{GenerateSpanCast(targetType, input)}[0]";
756          }
757  
758          private static string GenerateSpanCast(string targetType, string input)
759          {
760              return targetType == "byte"
761                  ? input
762                  : $"MemoryMarshal.Cast<byte, {targetType}>({input})";
763          }
764  
765          private static bool HasAttribute(Compilation compilation, ParameterSyntax parameterSyntax, string fullAttributeName)
766          {
767              foreach (var attributeList in parameterSyntax.AttributeLists)
768              {
769                  foreach (var attribute in attributeList.Attributes)
770                  {
771                      if (GetCanonicalTypeName(compilation, attribute) == fullAttributeName)
772                      {
773                          return true;
774                      }
775                  }
776              }
777  
778              return false;
779          }
780  
781          private static bool NeedsIServiceObjectImplementation(Compilation compilation, ClassDeclarationSyntax classDeclarationSyntax)
782          {
783              ITypeSymbol type = compilation.GetSemanticModel(classDeclarationSyntax.SyntaxTree).GetDeclaredSymbol(classDeclarationSyntax);
784              var serviceObjectInterface = type.AllInterfaces.FirstOrDefault(x => x.ToDisplayString() == TypeIServiceObject);
785              var interfaceMember = serviceObjectInterface?.GetMembers().FirstOrDefault(x => x.Name == "GetCommandHandlers");
786  
787              // Return true only if the class implements IServiceObject but does not actually implement the method
788              // that the interface defines, since this is the only case we want to handle, if the method already exists
789              // we have nothing to do.
790              return serviceObjectInterface != null && type.FindImplementationForInterfaceMember(interfaceMember) == null;
791          }
792  
793          public void Initialize(GeneratorInitializationContext context)
794          {
795              context.RegisterForSyntaxNotifications(() => new HipcSyntaxReceiver());
796          }
797      }
798  }