Decoder.cs
  1  using Ryujinx.Graphics.Shader.Translation;
  2  using System;
  3  using System.Collections.Generic;
  4  using System.Linq;
  5  using System.Runtime.CompilerServices;
  6  using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
  7  
  8  namespace Ryujinx.Graphics.Shader.Decoders
  9  {
 10      static class Decoder
 11      {
 12          private class Context
 13          {
 14              public AttributeUsage AttributeUsage { get; }
 15              public FeatureFlags UsedFeatures { get; private set; }
 16              public byte ClipDistancesWritten { get; private set; }
 17              public int Cb1DataSize { get; private set; }
 18  
 19              private readonly IGpuAccessor _gpuAccessor;
 20  
 21              public Context(IGpuAccessor gpuAccessor)
 22              {
 23                  _gpuAccessor = gpuAccessor;
 24                  AttributeUsage = new(gpuAccessor);
 25              }
 26  
 27              public uint ConstantBuffer1Read(int offset)
 28              {
 29                  if (Cb1DataSize < offset + 4)
 30                  {
 31                      Cb1DataSize = offset + 4;
 32                  }
 33  
 34                  return _gpuAccessor.ConstantBuffer1Read(offset);
 35              }
 36  
 37              public void SetUsedFeature(FeatureFlags flags)
 38              {
 39                  UsedFeatures |= flags;
 40              }
 41  
 42              public void SetClipDistanceWritten(int index)
 43              {
 44                  ClipDistancesWritten |= (byte)(1 << index);
 45              }
 46          }
 47  
 48          public static DecodedProgram Decode(ShaderDefinitions definitions, IGpuAccessor gpuAccessor, ulong startAddress)
 49          {
 50              Context context = new(gpuAccessor);
 51              Queue<DecodedFunction> functionsQueue = new();
 52              Dictionary<ulong, DecodedFunction> functionsVisited = new();
 53  
 54              DecodedFunction EnqueueFunction(ulong address)
 55              {
 56                  if (!functionsVisited.TryGetValue(address, out DecodedFunction function))
 57                  {
 58                      functionsVisited.Add(address, function = new DecodedFunction(address));
 59                      functionsQueue.Enqueue(function);
 60                  }
 61  
 62                  return function;
 63              }
 64  
 65              DecodedFunction mainFunction = EnqueueFunction(0);
 66  
 67              while (functionsQueue.TryDequeue(out DecodedFunction currentFunction))
 68              {
 69                  List<Block> blocks = new();
 70                  Queue<Block> workQueue = new();
 71                  Dictionary<ulong, Block> visited = new();
 72  
 73                  Block GetBlock(ulong blkAddress)
 74                  {
 75                      if (!visited.TryGetValue(blkAddress, out Block block))
 76                      {
 77                          block = new Block(blkAddress);
 78  
 79                          workQueue.Enqueue(block);
 80                          visited.Add(blkAddress, block);
 81                      }
 82  
 83                      return block;
 84                  }
 85  
 86                  GetBlock(currentFunction.Address);
 87  
 88                  bool hasNewTarget;
 89  
 90                  do
 91                  {
 92                      while (workQueue.TryDequeue(out Block currBlock))
 93                      {
 94                          // Check if the current block is inside another block.
 95                          if (BinarySearch(blocks, currBlock.Address, out int nBlkIndex))
 96                          {
 97                              Block nBlock = blocks[nBlkIndex];
 98  
 99                              if (nBlock.Address == currBlock.Address)
100                              {
101                                  throw new InvalidOperationException("Found duplicate block address on the list.");
102                              }
103  
104                              nBlock.Split(currBlock);
105                              blocks.Insert(nBlkIndex + 1, currBlock);
106  
107                              continue;
108                          }
109  
110                          // If we have a block after the current one, set the limit address.
111                          ulong limitAddress = ulong.MaxValue;
112  
113                          if (nBlkIndex != blocks.Count)
114                          {
115                              Block nBlock = blocks[nBlkIndex];
116  
117                              int nextIndex = nBlkIndex + 1;
118  
119                              if (nBlock.Address < currBlock.Address && nextIndex < blocks.Count)
120                              {
121                                  limitAddress = blocks[nextIndex].Address;
122                              }
123                              else if (nBlock.Address > currBlock.Address)
124                              {
125                                  limitAddress = blocks[nBlkIndex].Address;
126                              }
127                          }
128  
129                          FillBlock(definitions, gpuAccessor, context, currBlock, limitAddress, startAddress);
130  
131                          if (currBlock.OpCodes.Count != 0)
132                          {
133                              // We should have blocks for all possible branch targets,
134                              // including those from PBK/PCNT/SSY instructions.
135                              foreach (PushOpInfo pushOp in currBlock.PushOpCodes)
136                              {
137                                  GetBlock(pushOp.Op.GetAbsoluteAddress());
138                              }
139  
140                              // Set child blocks. "Branch" is the block the branch instruction
141                              // points to (when taken), "Next" is the block at the next address,
142                              // executed when the branch is not taken. For Unconditional Branches
143                              // or end of program, Next is null.
144                              InstOp lastOp = currBlock.GetLastOp();
145  
146                              if (lastOp.Name == InstName.Cal)
147                              {
148                                  EnqueueFunction(lastOp.GetAbsoluteAddress()).AddCaller(currentFunction);
149                              }
150                              else if (lastOp.Name == InstName.Bra)
151                              {
152                                  Block succBlock = GetBlock(lastOp.GetAbsoluteAddress());
153                                  currBlock.Successors.Add(succBlock);
154                                  succBlock.Predecessors.Add(currBlock);
155                              }
156  
157                              if (!IsUnconditionalBranch(ref lastOp))
158                              {
159                                  Block succBlock = GetBlock(currBlock.EndAddress);
160                                  currBlock.Successors.Insert(0, succBlock);
161                                  succBlock.Predecessors.Add(currBlock);
162                              }
163                          }
164  
165                          // Insert the new block on the list (sorted by address).
166                          if (blocks.Count != 0)
167                          {
168                              Block nBlock = blocks[nBlkIndex];
169  
170                              blocks.Insert(nBlkIndex + (nBlock.Address < currBlock.Address ? 1 : 0), currBlock);
171                          }
172                          else
173                          {
174                              blocks.Add(currBlock);
175                          }
176                      }
177  
178                      // Propagate SSY/PBK addresses into their uses (SYNC/BRK).
179                      foreach (Block block in blocks.Where(x => x.PushOpCodes.Count != 0))
180                      {
181                          for (int pushOpIndex = 0; pushOpIndex < block.PushOpCodes.Count; pushOpIndex++)
182                          {
183                              PropagatePushOp(visited, block, pushOpIndex);
184                          }
185                      }
186  
187                      // Try to find targets for BRX (indirect branch) instructions.
188                      hasNewTarget = FindBrxTargets(context, blocks, GetBlock);
189  
190                      // If we discovered new branch targets from the BRX instruction,
191                      // we need another round of decoding to decode the new blocks.
192                      // Additionally, we may have more SSY/PBK targets to propagate,
193                      // and new BRX instructions.
194                  }
195                  while (hasNewTarget);
196  
197                  currentFunction.SetBlocks(blocks.ToArray());
198              }
199  
200              return new DecodedProgram(
201                  mainFunction,
202                  functionsVisited,
203                  context.AttributeUsage,
204                  context.UsedFeatures,
205                  context.ClipDistancesWritten,
206                  context.Cb1DataSize);
207          }
208  
209          private static bool BinarySearch(List<Block> blocks, ulong address, out int index)
210          {
211              index = 0;
212  
213              int left = 0;
214              int right = blocks.Count - 1;
215  
216              while (left <= right)
217              {
218                  int size = right - left;
219  
220                  int middle = left + (size >> 1);
221  
222                  Block block = blocks[middle];
223  
224                  index = middle;
225  
226                  if (address >= block.Address && address < block.EndAddress)
227                  {
228                      return true;
229                  }
230  
231                  if (address < block.Address)
232                  {
233                      right = middle - 1;
234                  }
235                  else
236                  {
237                      left = middle + 1;
238                  }
239              }
240  
241              return false;
242          }
243  
244          private static void FillBlock(
245              ShaderDefinitions definitions,
246              IGpuAccessor gpuAccessor,
247              Context context,
248              Block block,
249              ulong limitAddress,
250              ulong startAddress)
251          {
252              ulong address = block.Address;
253              int bufferOffset = 0;
254              ReadOnlySpan<ulong> buffer = ReadOnlySpan<ulong>.Empty;
255  
256              InstOp op = default;
257  
258              do
259              {
260                  if (address + 7 >= limitAddress)
261                  {
262                      break;
263                  }
264  
265                  // Ignore scheduling instructions, which are written every 32 bytes.
266                  if ((address & 0x1f) == 0)
267                  {
268                      address += 8;
269                      bufferOffset++;
270                      continue;
271                  }
272  
273                  if (bufferOffset >= buffer.Length)
274                  {
275                      buffer = gpuAccessor.GetCode(startAddress + address, 8);
276                      bufferOffset = 0;
277                  }
278  
279                  ulong opCode = buffer[bufferOffset++];
280  
281                  op = InstTable.GetOp(address, opCode);
282  
283                  if (op.Props.HasFlag(InstProps.TexB))
284                  {
285                      context.SetUsedFeature(FeatureFlags.Bindless);
286                  }
287  
288                  switch (op.Name)
289                  {
290                      case InstName.Ald:
291                      case InstName.Ast:
292                      case InstName.Ipa:
293                          SetUserAttributeUses(definitions, context, op.Name, opCode);
294                          break;
295                      case InstName.Pbk:
296                      case InstName.Pcnt:
297                      case InstName.Ssy:
298                          block.AddPushOp(op);
299                          break;
300                      case InstName.Shfl:
301                          context.SetUsedFeature(FeatureFlags.Shuffle);
302                          break;
303                      case InstName.Ldl:
304                      case InstName.Stl:
305                          context.SetUsedFeature(FeatureFlags.LocalMemory);
306                          break;
307                      case InstName.Atoms:
308                      case InstName.AtomsCas:
309                      case InstName.Lds:
310                      case InstName.Sts:
311                          context.SetUsedFeature(FeatureFlags.SharedMemory);
312                          break;
313                      case InstName.Atom:
314                      case InstName.AtomCas:
315                      case InstName.Red:
316                      case InstName.Stg:
317                      case InstName.Suatom:
318                      case InstName.SuatomB:
319                      case InstName.SuatomB2:
320                      case InstName.SuatomCas:
321                      case InstName.SuatomCasB:
322                      case InstName.Sured:
323                      case InstName.SuredB:
324                      case InstName.Sust:
325                      case InstName.SustB:
326                      case InstName.SustD:
327                      case InstName.SustDB:
328                          context.SetUsedFeature(FeatureFlags.Store);
329                          break;
330                  }
331  
332                  block.OpCodes.Add(op);
333  
334                  address += 8;
335              }
336              while (!op.Props.HasFlag(InstProps.Bra));
337  
338              block.EndAddress = address;
339          }
340  
341          private static void SetUserAttributeUses(ShaderDefinitions definitions, Context context, InstName name, ulong opCode)
342          {
343              int offset;
344              int count = 1;
345              bool isStore = false;
346              bool indexed;
347              bool perPatch = false;
348  
349              if (name == InstName.Ast)
350              {
351                  InstAst opAst = new(opCode);
352                  count = (int)opAst.AlSize + 1;
353                  offset = opAst.Imm11;
354                  indexed = opAst.Phys;
355                  perPatch = opAst.P;
356                  isStore = true;
357              }
358              else if (name == InstName.Ald)
359              {
360                  InstAld opAld = new(opCode);
361                  count = (int)opAld.AlSize + 1;
362                  offset = opAld.Imm11;
363                  indexed = opAld.Phys;
364                  perPatch = opAld.P;
365                  isStore = opAld.O;
366              }
367              else /* if (name == InstName.Ipa) */
368              {
369                  InstIpa opIpa = new(opCode);
370                  offset = opIpa.Imm10;
371                  indexed = opIpa.Idx;
372              }
373  
374              if (indexed)
375              {
376                  if (isStore)
377                  {
378                      context.AttributeUsage.SetAllOutputUserAttributes();
379                      definitions.EnableOutputIndexing();
380                  }
381                  else
382                  {
383                      context.AttributeUsage.SetAllInputUserAttributes();
384                      definitions.EnableInputIndexing();
385                  }
386              }
387              else
388              {
389                  for (int elemIndex = 0; elemIndex < count; elemIndex++)
390                  {
391                      int attr = offset + elemIndex * 4;
392  
393                      if (perPatch)
394                      {
395                          if (attr >= AttributeConsts.UserAttributePerPatchBase && attr < AttributeConsts.UserAttributePerPatchEnd)
396                          {
397                              int userAttr = attr - AttributeConsts.UserAttributePerPatchBase;
398                              int index = userAttr / 16;
399  
400                              if (isStore)
401                              {
402                                  context.AttributeUsage.SetOutputUserAttributePerPatch(index);
403                              }
404                              else
405                              {
406                                  context.AttributeUsage.SetInputUserAttributePerPatch(index);
407                              }
408                          }
409                      }
410                      else if (attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd)
411                      {
412                          int userAttr = attr - AttributeConsts.UserAttributeBase;
413                          int index = userAttr / 16;
414  
415                          if (isStore)
416                          {
417                              context.AttributeUsage.SetOutputUserAttribute(index);
418                          }
419                          else
420                          {
421                              context.AttributeUsage.SetInputUserAttribute(index, (userAttr >> 2) & 3);
422                          }
423                      }
424  
425                      if (!isStore &&
426                          (attr == AttributeConsts.FogCoord ||
427                          (attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.ClipDistance0) ||
428                          (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)))
429                      {
430                          context.SetUsedFeature(FeatureFlags.FixedFuncAttr);
431                      }
432                      else
433                      {
434                          if (isStore)
435                          {
436                              switch (attr)
437                              {
438                                  case AttributeConsts.Layer:
439                                      if (definitions.Stage != ShaderStage.Compute && definitions.Stage != ShaderStage.Fragment)
440                                      {
441                                          context.SetUsedFeature(FeatureFlags.RtLayer);
442                                      }
443                                      break;
444                                  case AttributeConsts.ViewportIndex:
445                                      if (definitions.Stage != ShaderStage.Fragment)
446                                      {
447                                          context.SetUsedFeature(FeatureFlags.ViewportIndex);
448                                      }
449                                      break;
450                                  case AttributeConsts.ClipDistance0:
451                                  case AttributeConsts.ClipDistance1:
452                                  case AttributeConsts.ClipDistance2:
453                                  case AttributeConsts.ClipDistance3:
454                                  case AttributeConsts.ClipDistance4:
455                                  case AttributeConsts.ClipDistance5:
456                                  case AttributeConsts.ClipDistance6:
457                                  case AttributeConsts.ClipDistance7:
458                                      if (definitions.Stage.IsVtg())
459                                      {
460                                          context.SetClipDistanceWritten((attr - AttributeConsts.ClipDistance0) / 4);
461                                      }
462                                      break;
463                                  case AttributeConsts.ViewportMask:
464                                      if (definitions.Stage != ShaderStage.Fragment)
465                                      {
466                                          context.SetUsedFeature(FeatureFlags.ViewportMask);
467                                      }
468                                      break;
469                              }
470                          }
471                          else
472                          {
473                              switch (attr)
474                              {
475                                  case AttributeConsts.PositionX:
476                                  case AttributeConsts.PositionY:
477                                      if (definitions.Stage == ShaderStage.Fragment)
478                                      {
479                                          context.SetUsedFeature(FeatureFlags.FragCoordXY);
480                                      }
481                                      break;
482                                  case AttributeConsts.InstanceId:
483                                      if (definitions.Stage == ShaderStage.Vertex)
484                                      {
485                                          context.SetUsedFeature(FeatureFlags.InstanceId);
486                                      }
487                                      break;
488                              }
489                          }
490                      }
491                  }
492              }
493          }
494  
495          public static bool IsUnconditionalBranch(ref InstOp op)
496          {
497              return IsUnconditional(ref op) && op.Props.HasFlag(InstProps.Bra);
498          }
499  
500          private static bool IsUnconditional(ref InstOp op)
501          {
502              InstConditional condOp = new(op.RawOpCode);
503  
504              if ((op.Name == InstName.Bra || op.Name == InstName.Exit) && condOp.Ccc != Ccc.T)
505              {
506                  return false;
507              }
508  
509              return condOp.Pred == RegisterConsts.PredicateTrueIndex && !condOp.PredInv;
510          }
511  
512          private static bool FindBrxTargets(Context context, IEnumerable<Block> blocks, Func<ulong, Block> getBlock)
513          {
514              bool hasNewTarget = false;
515  
516              foreach (Block block in blocks)
517              {
518                  InstOp lastOp = block.GetLastOp();
519                  bool hasNext = block.HasNext();
520  
521                  if (lastOp.Name == InstName.Brx && block.Successors.Count == (hasNext ? 1 : 0))
522                  {
523                      HashSet<ulong> visited = new();
524  
525                      InstBrx opBrx = new(lastOp.RawOpCode);
526                      ulong baseOffset = lastOp.GetAbsoluteAddress();
527  
528                      // An indirect branch could go anywhere,
529                      // try to get the possible target offsets from the constant buffer.
530                      (int cbBaseOffset, int cbOffsetsCount) = FindBrxTargetRange(block, opBrx.SrcA);
531  
532                      if (cbOffsetsCount != 0)
533                      {
534                          hasNewTarget = true;
535                      }
536  
537                      for (int i = 0; i < cbOffsetsCount; i++)
538                      {
539                          uint targetOffset = context.ConstantBuffer1Read(cbBaseOffset + i * 4);
540                          ulong targetAddress = baseOffset + targetOffset;
541  
542                          if (visited.Add(targetAddress))
543                          {
544                              Block target = getBlock(targetAddress);
545                              target.Predecessors.Add(block);
546                              block.Successors.Add(target);
547                          }
548                      }
549                  }
550              }
551  
552              return hasNewTarget;
553          }
554  
555          private static (int, int) FindBrxTargetRange(Block block, int brxReg)
556          {
557              // Try to match the following pattern:
558              //
559              // IMNMX.U32 Rx, Rx, UpperBound, PT
560              // SHL Rx, Rx, 0x2
561              // LDC Rx, c[0x1][Rx+BaseOffset]
562              //
563              // Here, Rx is an arbitrary register, "UpperBound" and "BaseOffset" are constants.
564              // The above pattern is assumed to be generated by the compiler before BRX,
565              // as the instruction is usually used to implement jump tables for switch statement optimizations.
566              // On a successful match, "BaseOffset" is the offset in bytes where the jump offsets are
567              // located on the constant buffer, and "UpperBound" is the total number of offsets for the BRX, minus 1.
568  
569              HashSet<Block> visited = new();
570  
571              var ldcLocation = FindFirstRegWrite(visited, new BlockLocation(block, block.OpCodes.Count - 1), brxReg);
572              if (ldcLocation.Block == null || ldcLocation.Block.OpCodes[ldcLocation.Index].Name != InstName.Ldc)
573              {
574                  return (0, 0);
575              }
576  
577              GetOp<InstLdc>(ldcLocation, out var opLdc);
578  
579              if (opLdc.CbufSlot != 1 || opLdc.AddressMode != 0)
580              {
581                  return (0, 0);
582              }
583  
584              var shlLocation = FindFirstRegWrite(visited, ldcLocation, opLdc.SrcA);
585              if (shlLocation.Block == null || !shlLocation.IsImmInst(InstName.Shl))
586              {
587                  return (0, 0);
588              }
589  
590              GetOp<InstShlI>(shlLocation, out var opShl);
591  
592              if (opShl.Imm20 != 2)
593              {
594                  return (0, 0);
595              }
596  
597              var imnmxLocation = FindFirstRegWrite(visited, shlLocation, opShl.SrcA);
598              if (imnmxLocation.Block == null || !imnmxLocation.IsImmInst(InstName.Imnmx))
599              {
600                  return (0, 0);
601              }
602  
603              GetOp<InstImnmxI>(imnmxLocation, out var opImnmx);
604  
605              if (opImnmx.Signed || opImnmx.SrcPred != RegisterConsts.PredicateTrueIndex || opImnmx.SrcPredInv)
606              {
607                  return (0, 0);
608              }
609  
610              return (opLdc.CbufOffset, opImnmx.Imm20 + 1);
611          }
612  
613          private static void GetOp<T>(BlockLocation location, out T op) where T : unmanaged
614          {
615              ulong rawOp = location.Block.OpCodes[location.Index].RawOpCode;
616              op = Unsafe.As<ulong, T>(ref rawOp);
617          }
618  
619          private readonly struct BlockLocation
620          {
621              public Block Block { get; }
622              public int Index { get; }
623  
624              public BlockLocation(Block block, int index)
625              {
626                  Block = block;
627                  Index = index;
628              }
629  
630              public bool IsImmInst(InstName name)
631              {
632                  InstOp op = Block.OpCodes[Index];
633                  return op.Name == name && op.Props.HasFlag(InstProps.Ib);
634              }
635          }
636  
637          private static BlockLocation FindFirstRegWrite(HashSet<Block> visited, BlockLocation location, int regIndex)
638          {
639              Queue<BlockLocation> toVisit = new();
640              toVisit.Enqueue(location);
641              visited.Add(location.Block);
642  
643              while (toVisit.TryDequeue(out var currentLocation))
644              {
645                  Block block = currentLocation.Block;
646                  for (int i = currentLocation.Index - 1; i >= 0; i--)
647                  {
648                      if (WritesToRegister(block.OpCodes[i], regIndex))
649                      {
650                          return new BlockLocation(block, i);
651                      }
652                  }
653  
654                  foreach (Block predecessor in block.Predecessors)
655                  {
656                      if (visited.Add(predecessor))
657                      {
658                          toVisit.Enqueue(new BlockLocation(predecessor, predecessor.OpCodes.Count));
659                      }
660                  }
661              }
662  
663              return new BlockLocation(null, 0);
664          }
665  
666          private static bool WritesToRegister(InstOp op, int regIndex)
667          {
668              // Predicate instruction only ever writes to predicate, so we shouldn't check those.
669              if ((op.Props & (InstProps.Rd | InstProps.Rd2)) == 0)
670              {
671                  return false;
672              }
673  
674              if (op.Props.HasFlag(InstProps.Rd2) && (byte)(op.RawOpCode >> 28) == regIndex)
675              {
676                  return true;
677              }
678  
679              return (byte)op.RawOpCode == regIndex;
680          }
681  
682          private enum MergeType
683          {
684              Brk,
685              Cont,
686              Sync,
687          }
688  
689          private readonly struct PathBlockState
690          {
691              public Block Block { get; }
692  
693              private enum RestoreType
694              {
695                  None,
696                  PopPushOp,
697                  PushBranchOp,
698              }
699  
700              private readonly RestoreType _restoreType;
701  
702              private readonly ulong _restoreValue;
703              private readonly MergeType _restoreMergeType;
704  
705              public bool ReturningFromVisit => _restoreType != RestoreType.None;
706  
707              public PathBlockState(Block block)
708              {
709                  Block = block;
710                  _restoreType = RestoreType.None;
711                  _restoreValue = 0;
712                  _restoreMergeType = default;
713              }
714  
715              public PathBlockState(int oldStackSize)
716              {
717                  Block = null;
718                  _restoreType = RestoreType.PopPushOp;
719                  _restoreValue = (ulong)oldStackSize;
720                  _restoreMergeType = default;
721              }
722  
723              public PathBlockState(ulong syncAddress, MergeType mergeType)
724              {
725                  Block = null;
726                  _restoreType = RestoreType.PushBranchOp;
727                  _restoreValue = syncAddress;
728                  _restoreMergeType = mergeType;
729              }
730  
731              public void RestoreStackState(Stack<(ulong, MergeType)> branchStack)
732              {
733                  if (_restoreType == RestoreType.PushBranchOp)
734                  {
735                      branchStack.Push((_restoreValue, _restoreMergeType));
736                  }
737                  else if (_restoreType == RestoreType.PopPushOp)
738                  {
739                      while (branchStack.Count > (uint)_restoreValue)
740                      {
741                          branchStack.Pop();
742                      }
743                  }
744              }
745          }
746  
747          private static void PropagatePushOp(Dictionary<ulong, Block> blocks, Block currBlock, int pushOpIndex)
748          {
749              PushOpInfo pushOpInfo = currBlock.PushOpCodes[pushOpIndex];
750              InstOp pushOp = pushOpInfo.Op;
751  
752              Block target = blocks[pushOp.GetAbsoluteAddress()];
753  
754              Stack<PathBlockState> workQueue = new();
755              HashSet<Block> visited = new();
756              Stack<(ulong, MergeType)> branchStack = new();
757  
758              void Push(PathBlockState pbs)
759              {
760                  // When block is null, this means we are pushing a restore operation.
761                  // Restore operations are used to undo the work done inside a block
762                  // when we return from it, for example it pops addresses pushed by
763                  // SSY/PBK instructions inside the block, and pushes addresses poped
764                  // by SYNC/BRK.
765                  // For blocks, if it's already visited, we just ignore to avoid going
766                  // around in circles and getting stuck here.
767                  if (pbs.Block == null || !visited.Contains(pbs.Block))
768                  {
769                      workQueue.Push(pbs);
770                  }
771              }
772  
773              Push(new PathBlockState(currBlock));
774  
775              while (workQueue.TryPop(out PathBlockState pbs))
776              {
777                  if (pbs.ReturningFromVisit)
778                  {
779                      pbs.RestoreStackState(branchStack);
780  
781                      continue;
782                  }
783  
784                  Block current = pbs.Block;
785  
786                  // If the block was already processed, we just ignore it, otherwise
787                  // we would push the same child blocks of an already processed block,
788                  // and go around in circles until memory is exhausted.
789                  if (!visited.Add(current))
790                  {
791                      continue;
792                  }
793  
794                  int pushOpsCount = current.PushOpCodes.Count;
795                  if (pushOpsCount != 0)
796                  {
797                      Push(new PathBlockState(branchStack.Count));
798  
799                      for (int index = pushOpIndex; index < pushOpsCount; index++)
800                      {
801                          InstOp currentPushOp = current.PushOpCodes[index].Op;
802                          MergeType pushMergeType = GetMergeTypeFromPush(currentPushOp.Name);
803                          branchStack.Push((currentPushOp.GetAbsoluteAddress(), pushMergeType));
804                      }
805                  }
806  
807                  pushOpIndex = 0;
808  
809                  bool hasNext = current.HasNext();
810                  if (hasNext)
811                  {
812                      Push(new PathBlockState(current.Successors[0]));
813                  }
814  
815                  InstOp lastOp = current.GetLastOp();
816                  if (IsPopBranch(lastOp.Name))
817                  {
818                      MergeType popMergeType = GetMergeTypeFromPop(lastOp.Name);
819  
820                      bool found = true;
821                      ulong targetAddress = 0UL;
822                      MergeType mergeType;
823  
824                      do
825                      {
826                          if (branchStack.Count == 0)
827                          {
828                              found = false;
829                              break;
830                          }
831  
832                          (targetAddress, mergeType) = branchStack.Pop();
833  
834                          // Push the target address (this will be used to push the address
835                          // back into the PBK/PCNT/SSY stack when we return from that block),
836                          Push(new PathBlockState(targetAddress, mergeType));
837                      }
838                      while (mergeType != popMergeType);
839  
840                      // Make sure we found the correct address,
841                      // the push and pop instruction types must match, so:
842                      // - BRK can only consume addresses pushed by PBK.
843                      // - CONT can only consume addresses pushed by PCNT.
844                      // - SYNC can only consume addresses pushed by SSY.
845                      if (found)
846                      {
847                          if (branchStack.Count == 0)
848                          {
849                              // If the entire stack was consumed, then the current pop instruction
850                              // just consumed the address from our push instruction.
851                              if (current.SyncTargets.TryAdd(pushOp.Address, new SyncTarget(pushOpInfo, current.SyncTargets.Count)))
852                              {
853                                  pushOpInfo.Consumers.Add(current, Local());
854                                  target.Predecessors.Add(current);
855                                  current.Successors.Add(target);
856                              }
857                          }
858                          else
859                          {
860                              // Push the block itself into the work queue for processing.
861                              Push(new PathBlockState(blocks[targetAddress]));
862                          }
863                      }
864                  }
865                  else
866                  {
867                      // By adding them in descending order (sorted by address), we process the blocks
868                      // in order (of ascending address), since we work with a LIFO.
869                      foreach (Block possibleTarget in current.Successors.OrderByDescending(x => x.Address))
870                      {
871                          if (!hasNext || possibleTarget != current.Successors[0])
872                          {
873                              Push(new PathBlockState(possibleTarget));
874                          }
875                      }
876                  }
877              }
878          }
879  
880          public static bool IsPopBranch(InstName name)
881          {
882              return name == InstName.Brk || name == InstName.Cont || name == InstName.Sync;
883          }
884  
885          private static MergeType GetMergeTypeFromPush(InstName name)
886          {
887              return name switch
888              {
889                  InstName.Pbk => MergeType.Brk,
890                  InstName.Pcnt => MergeType.Cont,
891                  _ => MergeType.Sync,
892              };
893          }
894  
895          private static MergeType GetMergeTypeFromPop(InstName name)
896          {
897              return name switch
898              {
899                  InstName.Brk => MergeType.Brk,
900                  InstName.Cont => MergeType.Cont,
901                  _ => MergeType.Sync,
902              };
903          }
904      }
905  }