/ src / Ryujinx.Graphics.Shader / StructuredIr / GotoElimination.cs
GotoElimination.cs
  1  using Ryujinx.Graphics.Shader.IntermediateRepresentation;
  2  using System;
  3  using System.Collections.Generic;
  4  using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
  5  
  6  namespace Ryujinx.Graphics.Shader.StructuredIr
  7  {
  8      static class GotoElimination
  9      {
 10          // This is a modified version of the algorithm presented on the paper
 11          // "Taming Control Flow: A Structured Approach to Eliminating Goto Statements".
 12          public static void Eliminate(GotoStatement[] gotos)
 13          {
 14              for (int index = gotos.Length - 1; index >= 0; index--)
 15              {
 16                  GotoStatement stmt = gotos[index];
 17  
 18                  AstBlock gBlock = ParentBlock(stmt.Goto);
 19                  AstBlock lBlock = ParentBlock(stmt.Label);
 20  
 21                  int gLevel = Level(gBlock);
 22                  int lLevel = Level(lBlock);
 23  
 24                  if (IndirectlyRelated(gBlock, lBlock, gLevel, lLevel))
 25                  {
 26                      AstBlock drBlock = gBlock;
 27  
 28                      int drLevel = gLevel;
 29  
 30                      do
 31                      {
 32                          drBlock = drBlock.Parent;
 33  
 34                          drLevel--;
 35                      }
 36                      while (!DirectlyRelated(drBlock, lBlock, drLevel, lLevel));
 37  
 38                      MoveOutward(stmt, gLevel, drLevel);
 39  
 40                      gBlock = drBlock;
 41                      gLevel = drLevel;
 42  
 43                      if (Previous(stmt.Goto) is AstBlock elseBlock && elseBlock.Type == AstBlockType.Else)
 44                      {
 45                          // It's possible that the label was enclosed inside an else block,
 46                          // in this case we need to update the block and level.
 47                          // We also need to set the IsLoop for the case when the label is
 48                          // now before the goto, due to the newly introduced else block.
 49                          lBlock = ParentBlock(stmt.Label);
 50  
 51                          lLevel = Level(lBlock);
 52  
 53                          if (!IndirectlyRelated(elseBlock, lBlock, gLevel + 1, lLevel))
 54                          {
 55                              stmt.IsLoop = true;
 56                          }
 57                      }
 58                  }
 59  
 60                  if (DirectlyRelated(gBlock, lBlock, gLevel, lLevel))
 61                  {
 62                      if (gLevel > lLevel)
 63                      {
 64                          MoveOutward(stmt, gLevel, lLevel);
 65                      }
 66                      else
 67                      {
 68                          if (stmt.IsLoop)
 69                          {
 70                              Lift(stmt);
 71                          }
 72  
 73                          MoveInward(stmt);
 74                      }
 75                  }
 76  
 77                  gBlock = ParentBlock(stmt.Goto);
 78  
 79                  if (stmt.IsLoop)
 80                  {
 81                      EncloseDoWhile(stmt, gBlock, stmt.Label);
 82                  }
 83                  else
 84                  {
 85                      Enclose(gBlock, AstBlockType.If, stmt.Condition, Next(stmt.Goto), stmt.Label);
 86                  }
 87  
 88                  gBlock.Remove(stmt.Goto);
 89              }
 90          }
 91  
 92          private static bool IndirectlyRelated(AstBlock lBlock, AstBlock rBlock, int lLevel, int rlevel)
 93          {
 94              return !(lBlock == rBlock || DirectlyRelated(lBlock, rBlock, lLevel, rlevel));
 95          }
 96  
 97          private static bool DirectlyRelated(AstBlock lBlock, AstBlock rBlock, int lLevel, int rLevel)
 98          {
 99              // If the levels are equal, they can be either siblings or indirectly related.
100              if (lLevel == rLevel)
101              {
102                  return false;
103              }
104  
105              IAstNode block;
106              IAstNode other;
107  
108              int blockLvl, otherLvl;
109  
110              if (lLevel > rLevel)
111              {
112                  block = lBlock;
113                  blockLvl = lLevel;
114                  other = rBlock;
115                  otherLvl = rLevel;
116              }
117              else /* if (rLevel > lLevel) */
118              {
119                  block = rBlock;
120                  blockLvl = rLevel;
121                  other = lBlock;
122                  otherLvl = lLevel;
123              }
124  
125              while (blockLvl >= otherLvl)
126              {
127                  if (block == other)
128                  {
129                      return true;
130                  }
131  
132                  block = block.Parent;
133  
134                  blockLvl--;
135              }
136  
137              return false;
138          }
139  
140          private static void Lift(GotoStatement stmt)
141          {
142              AstBlock block = ParentBlock(stmt.Goto);
143  
144              AstBlock[] path = BackwardsPath(block, ParentBlock(stmt.Label));
145  
146              AstBlock loopFirstStmt = path[^1];
147  
148              if (loopFirstStmt.Type == AstBlockType.Else)
149              {
150                  loopFirstStmt = Previous(loopFirstStmt) as AstBlock;
151  
152                  if (loopFirstStmt == null || loopFirstStmt.Type != AstBlockType.If)
153                  {
154                      throw new InvalidOperationException("Found an else without a matching if.");
155                  }
156              }
157  
158              AstBlock newBlock = EncloseDoWhile(stmt, block, loopFirstStmt);
159  
160              block.Remove(stmt.Goto);
161  
162              newBlock.AddFirst(stmt.Goto);
163  
164              stmt.IsLoop = false;
165          }
166  
167          private static void MoveOutward(GotoStatement stmt, int gLevel, int lLevel)
168          {
169              AstBlock origin = ParentBlock(stmt.Goto);
170  
171              AstBlock block = origin;
172  
173              // Check if a loop is enclosing the goto, and the block that is
174              // directly related to the label is above the loop block.
175              // In that case, we need to introduce a break to get out of the loop.
176              AstBlock loopBlock = origin;
177  
178              int loopLevel = gLevel;
179  
180              while (loopLevel > lLevel)
181              {
182                  AstBlock child = loopBlock;
183  
184                  loopBlock = loopBlock.Parent;
185  
186                  loopLevel--;
187  
188                  if (child.Type == AstBlockType.DoWhile)
189                  {
190                      EncloseSingleInst(stmt, Instruction.LoopBreak);
191  
192                      block.Remove(stmt.Goto);
193  
194                      loopBlock.AddAfter(child, stmt.Goto);
195  
196                      block = loopBlock;
197                      gLevel = loopLevel;
198                  }
199              }
200  
201              // Insert ifs to skip the parts that shouldn't be executed due to the goto.
202              bool tryInsertElse = stmt.IsUnconditional && origin.Type == AstBlockType.If;
203  
204              while (gLevel > lLevel)
205              {
206                  Enclose(block, AstBlockType.If, stmt.Condition, Next(stmt.Goto));
207  
208                  block.Remove(stmt.Goto);
209  
210                  AstBlock child = block;
211  
212                  // We can't move the goto in the middle of a if and a else block, in
213                  // this case we need to move it after the else.
214                  // IsLoop may need to be updated if the label is inside the else, as
215                  // introducing a loop is the only way to ensure the else will be executed.
216                  if (Next(child) is AstBlock elseBlock && elseBlock.Type == AstBlockType.Else)
217                  {
218                      child = elseBlock;
219                  }
220  
221                  block = block.Parent;
222  
223                  block.AddAfter(child, stmt.Goto);
224  
225                  gLevel--;
226  
227                  if (tryInsertElse && child == origin)
228                  {
229                      AstBlock lBlock = ParentBlock(stmt.Label);
230  
231                      IAstNode last = block == lBlock && !stmt.IsLoop ? stmt.Label : null;
232  
233                      AstBlock newBlock = Enclose(block, AstBlockType.Else, null, Next(stmt.Goto), last);
234  
235                      if (newBlock != null)
236                      {
237                          block.Remove(stmt.Goto);
238  
239                          block.AddAfter(newBlock, stmt.Goto);
240                      }
241                  }
242              }
243          }
244  
245          private static void MoveInward(GotoStatement stmt)
246          {
247              AstBlock block = ParentBlock(stmt.Goto);
248  
249              AstBlock[] path = BackwardsPath(block, ParentBlock(stmt.Label));
250  
251              for (int index = path.Length - 1; index >= 0; index--)
252              {
253                  AstBlock child = path[index];
254                  AstBlock last = child;
255  
256                  if (child.Type == AstBlockType.If)
257                  {
258                      // Modify the if condition to allow it to be entered by the goto.
259                      if (!ContainsCondComb(child.Condition, Instruction.LogicalOr, stmt.Condition))
260                      {
261                          child.OrCondition(stmt.Condition);
262                      }
263                  }
264                  else if (child.Type == AstBlockType.Else)
265                  {
266                      // Modify the matching if condition to force the else to be entered by the goto.
267                      if (Previous(child) is not AstBlock ifBlock || ifBlock.Type != AstBlockType.If)
268                      {
269                          throw new InvalidOperationException("Found an else without a matching if.");
270                      }
271  
272                      IAstNode cond = InverseCond(stmt.Condition);
273  
274                      if (!ContainsCondComb(ifBlock.Condition, Instruction.LogicalAnd, cond))
275                      {
276                          ifBlock.AndCondition(cond);
277                      }
278  
279                      last = ifBlock;
280                  }
281  
282                  Enclose(block, AstBlockType.If, stmt.Condition, Next(stmt.Goto), last);
283  
284                  block.Remove(stmt.Goto);
285  
286                  child.AddFirst(stmt.Goto);
287  
288                  block = child;
289              }
290          }
291  
292          private static bool ContainsCondComb(IAstNode node, Instruction inst, IAstNode newCond)
293          {
294              while (node is AstOperation operation && operation.SourcesCount == 2)
295              {
296                  if (operation.Inst == inst && IsSameCond(operation.GetSource(1), newCond))
297                  {
298                      return true;
299                  }
300  
301                  node = operation.GetSource(0);
302              }
303  
304              return false;
305          }
306  
307          private static AstBlock EncloseDoWhile(GotoStatement stmt, AstBlock block, IAstNode first)
308          {
309              if (block.Type == AstBlockType.DoWhile && first == block.First)
310              {
311                  // We only need to insert the continue if we're not at the end of the loop,
312                  // or if our condition is different from the loop condition.
313                  if (Next(stmt.Goto) != null || block.Condition != stmt.Condition)
314                  {
315                      EncloseSingleInst(stmt, Instruction.LoopContinue);
316                  }
317  
318                  // Modify the do-while condition to allow it to continue.
319                  if (!ContainsCondComb(block.Condition, Instruction.LogicalOr, stmt.Condition))
320                  {
321                      block.OrCondition(stmt.Condition);
322                  }
323  
324                  return block;
325              }
326  
327              return Enclose(block, AstBlockType.DoWhile, stmt.Condition, first, stmt.Goto);
328          }
329  
330          private static void EncloseSingleInst(GotoStatement stmt, Instruction inst)
331          {
332              AstBlock block = ParentBlock(stmt.Goto);
333  
334              AstBlock newBlock = new(AstBlockType.If, stmt.Condition);
335  
336              block.AddAfter(stmt.Goto, newBlock);
337  
338              newBlock.AddFirst(new AstOperation(inst));
339          }
340  
341          private static AstBlock Enclose(
342              AstBlock block,
343              AstBlockType type,
344              IAstNode cond,
345              IAstNode first,
346              IAstNode last = null)
347          {
348              if (first == last)
349              {
350                  return null;
351              }
352  
353              if (type == AstBlockType.If)
354              {
355                  cond = InverseCond(cond);
356              }
357  
358              // Do a quick check, if we are enclosing a single block,
359              // and the block type/condition matches the one we're going
360              // to create, then we don't need a new block, we can just
361              // return the old one.
362              bool hasSingleNode = Next(first) == last;
363  
364              if (hasSingleNode && BlockMatches(first, type, cond))
365              {
366                  return first as AstBlock;
367              }
368  
369              AstBlock newBlock = new(type, cond);
370  
371              block.AddBefore(first, newBlock);
372  
373              while (first != last)
374              {
375                  IAstNode next = Next(first);
376  
377                  block.Remove(first);
378  
379                  newBlock.Add(first);
380  
381                  first = next;
382              }
383  
384              return newBlock;
385          }
386  
387          private static bool BlockMatches(IAstNode node, AstBlockType type, IAstNode cond)
388          {
389              if (node is not AstBlock block)
390              {
391                  return false;
392              }
393  
394              return block.Type == type && IsSameCond(block.Condition, cond);
395          }
396  
397          private static bool IsSameCond(IAstNode lCond, IAstNode rCond)
398          {
399              if (lCond is AstOperation lCondOp && lCondOp.Inst == Instruction.LogicalNot)
400              {
401                  if (rCond is not AstOperation rCondOp || rCondOp.Inst != lCondOp.Inst)
402                  {
403                      return false;
404                  }
405  
406                  lCond = lCondOp.GetSource(0);
407                  rCond = rCondOp.GetSource(0);
408              }
409  
410              return lCond == rCond;
411          }
412  
413          private static AstBlock ParentBlock(IAstNode node)
414          {
415              if (node is AstBlock block)
416              {
417                  return block.Parent;
418              }
419  
420              while (node is not AstBlock)
421              {
422                  node = node.Parent;
423              }
424  
425              return node as AstBlock;
426          }
427  
428          private static AstBlock[] BackwardsPath(AstBlock top, AstBlock bottom)
429          {
430              AstBlock block = bottom;
431  
432              List<AstBlock> path = new();
433  
434              while (block != top)
435              {
436                  path.Add(block);
437  
438                  block = block.Parent;
439              }
440  
441              return path.ToArray();
442          }
443  
444          private static int Level(IAstNode node)
445          {
446              int level = 0;
447  
448              while (node != null)
449              {
450                  level++;
451  
452                  node = node.Parent;
453              }
454  
455              return level;
456          }
457      }
458  }