aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Shader/StructuredIr
diff options
context:
space:
mode:
authorgdk <gab.dark.100@gmail.com>2019-10-13 03:02:07 -0300
committerThog <thog@protonmail.com>2020-01-09 02:13:00 +0100
commit1876b346fea647e8284a66bb6d62c38801035cff (patch)
tree6eeff094298cda84d1613dc5ec0691e51d7b35f1 /Ryujinx.Graphics.Shader/StructuredIr
parentf617fb542a0e3d36012d77a4b5acbde7b08902f2 (diff)
Initial work
Diffstat (limited to 'Ryujinx.Graphics.Shader/StructuredIr')
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs35
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs116
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs12
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs68
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs12
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs73
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs11
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs52
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs49
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs155
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs25
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs459
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs23
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs11
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs150
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs33
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs74
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs281
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs303
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs40
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/VariableType.cs13
21 files changed, 1995 insertions, 0 deletions
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs
new file mode 100644
index 00000000..bb3fe7af
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs
@@ -0,0 +1,35 @@
+using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class AstAssignment : AstNode
+ {
+ public IAstNode Destination { get; }
+
+ private IAstNode _source;
+
+ public IAstNode Source
+ {
+ get
+ {
+ return _source;
+ }
+ set
+ {
+ RemoveUse(_source, this);
+
+ AddUse(value, this);
+
+ _source = value;
+ }
+ }
+
+ public AstAssignment(IAstNode destination, IAstNode source)
+ {
+ Destination = destination;
+ Source = source;
+
+ AddDef(destination, this);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs
new file mode 100644
index 00000000..fdef87de
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs
@@ -0,0 +1,116 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class AstBlock : AstNode, IEnumerable<IAstNode>
+ {
+ public AstBlockType Type { get; private set; }
+
+ private IAstNode _condition;
+
+ public IAstNode Condition
+ {
+ get
+ {
+ return _condition;
+ }
+ set
+ {
+ RemoveUse(_condition, this);
+
+ AddUse(value, this);
+
+ _condition = value;
+ }
+ }
+
+ private LinkedList<IAstNode> _nodes;
+
+ public IAstNode First => _nodes.First?.Value;
+
+ public int Count => _nodes.Count;
+
+ public AstBlock(AstBlockType type, IAstNode condition = null)
+ {
+ Type = type;
+ Condition = condition;
+
+ _nodes = new LinkedList<IAstNode>();
+ }
+
+ public void Add(IAstNode node)
+ {
+ Add(node, _nodes.AddLast(node));
+ }
+
+ public void AddFirst(IAstNode node)
+ {
+ Add(node, _nodes.AddFirst(node));
+ }
+
+ public void AddBefore(IAstNode next, IAstNode node)
+ {
+ Add(node, _nodes.AddBefore(next.LLNode, node));
+ }
+
+ public void AddAfter(IAstNode prev, IAstNode node)
+ {
+ Add(node, _nodes.AddAfter(prev.LLNode, node));
+ }
+
+ private void Add(IAstNode node, LinkedListNode<IAstNode> newNode)
+ {
+ if (node.Parent != null)
+ {
+ throw new ArgumentException("Node already belongs to a block.");
+ }
+
+ node.Parent = this;
+ node.LLNode = newNode;
+ }
+
+ public void Remove(IAstNode node)
+ {
+ _nodes.Remove(node.LLNode);
+
+ node.Parent = null;
+ node.LLNode = null;
+ }
+
+ public void AndCondition(IAstNode cond)
+ {
+ Condition = new AstOperation(Instruction.LogicalAnd, Condition, cond);
+ }
+
+ public void OrCondition(IAstNode cond)
+ {
+ Condition = new AstOperation(Instruction.LogicalOr, Condition, cond);
+ }
+ public void TurnIntoIf(IAstNode cond)
+ {
+ Condition = cond;
+
+ Type = AstBlockType.If;
+ }
+
+ public void TurnIntoElseIf()
+ {
+ Type = AstBlockType.ElseIf;
+ }
+
+ public IEnumerator<IAstNode> GetEnumerator()
+ {
+ return _nodes.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs
new file mode 100644
index 00000000..c12efda9
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ enum AstBlockType
+ {
+ DoWhile,
+ If,
+ Else,
+ ElseIf,
+ Main,
+ While
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs
new file mode 100644
index 00000000..10d5dce0
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+
+using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class AstBlockVisitor
+ {
+ public AstBlock Block { get; private set; }
+
+ public class BlockVisitationEventArgs : EventArgs
+ {
+ public AstBlock Block { get; }
+
+ public BlockVisitationEventArgs(AstBlock block)
+ {
+ Block = block;
+ }
+ }
+
+ public event EventHandler<BlockVisitationEventArgs> BlockEntered;
+ public event EventHandler<BlockVisitationEventArgs> BlockLeft;
+
+ public AstBlockVisitor(AstBlock mainBlock)
+ {
+ Block = mainBlock;
+ }
+
+ public IEnumerable<IAstNode> Visit()
+ {
+ IAstNode node = Block.First;
+
+ while (node != null)
+ {
+ // We reached a child block, visit the nodes inside.
+ while (node is AstBlock childBlock)
+ {
+ Block = childBlock;
+
+ node = childBlock.First;
+
+ BlockEntered?.Invoke(this, new BlockVisitationEventArgs(Block));
+ }
+
+ // Node may be null, if the block is empty.
+ if (node != null)
+ {
+ IAstNode next = Next(node);
+
+ yield return node;
+
+ node = next;
+ }
+
+ // We reached the end of the list, go up on tree to the parent blocks.
+ while (node == null && Block.Type != AstBlockType.Main)
+ {
+ BlockLeft?.Invoke(this, new BlockVisitationEventArgs(Block));
+
+ node = Next(Block);
+
+ Block = Block.Parent;
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs
new file mode 100644
index 00000000..dabe623f
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class AstComment : AstNode
+ {
+ public string Comment { get; }
+
+ public AstComment(string comment)
+ {
+ Comment = comment;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs
new file mode 100644
index 00000000..9d3148e1
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs
@@ -0,0 +1,73 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ static class AstHelper
+ {
+ public static void AddUse(IAstNode node, IAstNode parent)
+ {
+ if (node is AstOperand operand && operand.Type == OperandType.LocalVariable)
+ {
+ operand.Uses.Add(parent);
+ }
+ }
+
+ public static void AddDef(IAstNode node, IAstNode parent)
+ {
+ if (node is AstOperand operand && operand.Type == OperandType.LocalVariable)
+ {
+ operand.Defs.Add(parent);
+ }
+ }
+
+ public static void RemoveUse(IAstNode node, IAstNode parent)
+ {
+ if (node is AstOperand operand && operand.Type == OperandType.LocalVariable)
+ {
+ operand.Uses.Remove(parent);
+ }
+ }
+
+ public static void RemoveDef(IAstNode node, IAstNode parent)
+ {
+ if (node is AstOperand operand && operand.Type == OperandType.LocalVariable)
+ {
+ operand.Defs.Remove(parent);
+ }
+ }
+
+ public static AstAssignment Assign(IAstNode destination, IAstNode source)
+ {
+ return new AstAssignment(destination, source);
+ }
+
+ public static AstOperand Const(int value)
+ {
+ return new AstOperand(OperandType.Constant, value);
+ }
+
+ public static AstOperand Local(VariableType type)
+ {
+ AstOperand local = new AstOperand(OperandType.LocalVariable);
+
+ local.VarType = type;
+
+ return local;
+ }
+
+ public static IAstNode InverseCond(IAstNode cond)
+ {
+ return new AstOperation(Instruction.LogicalNot, cond);
+ }
+
+ public static IAstNode Next(IAstNode node)
+ {
+ return node.LLNode.Next?.Value;
+ }
+
+ public static IAstNode Previous(IAstNode node)
+ {
+ return node.LLNode.Previous?.Value;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs
new file mode 100644
index 00000000..c667aac9
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class AstNode : IAstNode
+ {
+ public AstBlock Parent { get; set; }
+
+ public LinkedListNode<IAstNode> LLNode { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs
new file mode 100644
index 00000000..25b09636
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs
@@ -0,0 +1,52 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class AstOperand : AstNode
+ {
+ public HashSet<IAstNode> Defs { get; }
+ public HashSet<IAstNode> Uses { get; }
+
+ public OperandType Type { get; }
+
+ public VariableType VarType { get; set; }
+
+ public InterpolationQualifier Interpolation { get; }
+
+ public int Value { get; }
+
+ public int CbufSlot { get; }
+ public int CbufOffset { get; }
+
+ private AstOperand()
+ {
+ Defs = new HashSet<IAstNode>();
+ Uses = new HashSet<IAstNode>();
+
+ VarType = VariableType.S32;
+ }
+
+ public AstOperand(Operand operand) : this()
+ {
+ Type = operand.Type;
+ Interpolation = operand.Interpolation;
+
+ if (Type == OperandType.ConstantBuffer)
+ {
+ CbufSlot = operand.GetCbufSlot();
+ CbufOffset = operand.GetCbufOffset();
+ }
+ else
+ {
+ Value = operand.Value;
+ }
+ }
+
+ public AstOperand(OperandType type, int value = 0) : this()
+ {
+ Type = type;
+ Value = value;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs
new file mode 100644
index 00000000..1607ffec
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs
@@ -0,0 +1,49 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+
+using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class AstOperation : AstNode
+ {
+ public Instruction Inst { get; }
+
+ public int ComponentMask { get; }
+
+ private IAstNode[] _sources;
+
+ public int SourcesCount => _sources.Length;
+
+ public AstOperation(Instruction inst, params IAstNode[] sources)
+ {
+ Inst = inst;
+ _sources = sources;
+
+ foreach (IAstNode source in sources)
+ {
+ AddUse(source, this);
+ }
+
+ ComponentMask = 1;
+ }
+
+ public AstOperation(Instruction inst, int compMask, params IAstNode[] sources) : this(inst, sources)
+ {
+ ComponentMask = compMask;
+ }
+
+ public IAstNode GetSource(int index)
+ {
+ return _sources[index];
+ }
+
+ public void SetSource(int index, IAstNode source)
+ {
+ RemoveUse(_sources[index], this);
+
+ AddUse(source, this);
+
+ _sources[index] = source;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs
new file mode 100644
index 00000000..a37e1a3e
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs
@@ -0,0 +1,155 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+using System.Collections.Generic;
+using System.Linq;
+
+using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ static class AstOptimizer
+ {
+ public static void Optimize(StructuredProgramContext context)
+ {
+ AstBlock mainBlock = context.Info.MainBlock;
+
+ // When debug mode is enabled, we disable expression propagation
+ // (this makes comparison with the disassembly easier).
+ if ((context.Config.Flags & TranslationFlags.DebugMode) == 0)
+ {
+ AstBlockVisitor visitor = new AstBlockVisitor(mainBlock);
+
+ foreach (IAstNode node in visitor.Visit())
+ {
+ if (node is AstAssignment assignment && assignment.Destination is AstOperand propVar)
+ {
+ bool isWorthPropagating = propVar.Uses.Count == 1 || IsWorthPropagating(assignment.Source);
+
+ if (propVar.Defs.Count == 1 && isWorthPropagating)
+ {
+ PropagateExpression(propVar, assignment.Source);
+ }
+
+ if (propVar.Type == OperandType.LocalVariable && propVar.Uses.Count == 0)
+ {
+ visitor.Block.Remove(assignment);
+
+ context.Info.Locals.Remove(propVar);
+ }
+ }
+ }
+ }
+
+ RemoveEmptyBlocks(mainBlock);
+ }
+
+ private static bool IsWorthPropagating(IAstNode source)
+ {
+ if (!(source is AstOperation srcOp))
+ {
+ return false;
+ }
+
+ if (!InstructionInfo.IsUnary(srcOp.Inst))
+ {
+ return false;
+ }
+
+ return srcOp.GetSource(0) is AstOperand || srcOp.Inst == Instruction.Copy;
+ }
+
+ private static void PropagateExpression(AstOperand propVar, IAstNode source)
+ {
+ IAstNode[] uses = propVar.Uses.ToArray();
+
+ foreach (IAstNode useNode in uses)
+ {
+ if (useNode is AstBlock useBlock)
+ {
+ useBlock.Condition = source;
+ }
+ else if (useNode is AstOperation useOperation)
+ {
+ for (int srcIndex = 0; srcIndex < useOperation.SourcesCount; srcIndex++)
+ {
+ if (useOperation.GetSource(srcIndex) == propVar)
+ {
+ useOperation.SetSource(srcIndex, source);
+ }
+ }
+ }
+ else if (useNode is AstAssignment useAssignment)
+ {
+ useAssignment.Source = source;
+ }
+ }
+ }
+
+ private static void RemoveEmptyBlocks(AstBlock mainBlock)
+ {
+ Queue<AstBlock> pending = new Queue<AstBlock>();
+
+ pending.Enqueue(mainBlock);
+
+ while (pending.TryDequeue(out AstBlock block))
+ {
+ foreach (IAstNode node in block)
+ {
+ if (node is AstBlock childBlock)
+ {
+ pending.Enqueue(childBlock);
+ }
+ }
+
+ AstBlock parent = block.Parent;
+
+ if (parent == null)
+ {
+ continue;
+ }
+
+ AstBlock nextBlock = Next(block) as AstBlock;
+
+ bool hasElse = nextBlock != null && nextBlock.Type == AstBlockType.Else;
+
+ bool isIf = block.Type == AstBlockType.If;
+
+ if (block.Count == 0)
+ {
+ if (isIf)
+ {
+ if (hasElse)
+ {
+ nextBlock.TurnIntoIf(InverseCond(block.Condition));
+ }
+
+ parent.Remove(block);
+ }
+ else if (block.Type == AstBlockType.Else)
+ {
+ parent.Remove(block);
+ }
+ }
+ else if (isIf && parent.Type == AstBlockType.Else && parent.Count == (hasElse ? 2 : 1))
+ {
+ AstBlock parentOfParent = parent.Parent;
+
+ parent.Remove(block);
+
+ parentOfParent.AddAfter(parent, block);
+
+ if (hasElse)
+ {
+ parent.Remove(nextBlock);
+
+ parentOfParent.AddAfter(block, nextBlock);
+ }
+
+ parentOfParent.Remove(parent);
+
+ block.TurnIntoElseIf();
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
new file mode 100644
index 00000000..c9bd5750
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
@@ -0,0 +1,25 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class AstTextureOperation : AstOperation
+ {
+ public TextureTarget Target { get; }
+ public TextureFlags Flags { get; }
+
+ public int Handle { get; }
+
+ public AstTextureOperation(
+ Instruction inst,
+ TextureTarget target,
+ TextureFlags flags,
+ int handle,
+ int compMask,
+ params IAstNode[] sources) : base(inst, compMask, sources)
+ {
+ Target = target;
+ Flags = flags;
+ Handle = handle;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs b/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs
new file mode 100644
index 00000000..8bcf9d9c
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs
@@ -0,0 +1,459 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System;
+using System.Collections.Generic;
+
+using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ static class GotoElimination
+ {
+ // This is a modified version of the algorithm presented on the paper
+ // "Taming Control Flow: A Structured Approach to Eliminating Goto Statements".
+ public static void Eliminate(GotoStatement[] gotos)
+ {
+ for (int index = gotos.Length - 1; index >= 0; index--)
+ {
+ GotoStatement stmt = gotos[index];
+
+ AstBlock gBlock = ParentBlock(stmt.Goto);
+ AstBlock lBlock = ParentBlock(stmt.Label);
+
+ int gLevel = Level(gBlock);
+ int lLevel = Level(lBlock);
+
+ if (IndirectlyRelated(gBlock, lBlock, gLevel, lLevel))
+ {
+ AstBlock drBlock = gBlock;
+
+ int drLevel = gLevel;
+
+ do
+ {
+ drBlock = drBlock.Parent;
+
+ drLevel--;
+ }
+ while (!DirectlyRelated(drBlock, lBlock, drLevel, lLevel));
+
+ MoveOutward(stmt, gLevel, drLevel);
+
+ gBlock = drBlock;
+ gLevel = drLevel;
+
+ if (Previous(stmt.Goto) is AstBlock elseBlock && elseBlock.Type == AstBlockType.Else)
+ {
+ // It's possible that the label was enclosed inside an else block,
+ // in this case we need to update the block and level.
+ // We also need to set the IsLoop for the case when the label is
+ // now before the goto, due to the newly introduced else block.
+ lBlock = ParentBlock(stmt.Label);
+
+ lLevel = Level(lBlock);
+
+ if (!IndirectlyRelated(elseBlock, lBlock, gLevel + 1, lLevel))
+ {
+ stmt.IsLoop = true;
+ }
+ }
+ }
+
+ if (DirectlyRelated(gBlock, lBlock, gLevel, lLevel))
+ {
+ if (gLevel > lLevel)
+ {
+ MoveOutward(stmt, gLevel, lLevel);
+ }
+ else
+ {
+ if (stmt.IsLoop)
+ {
+ Lift(stmt);
+ }
+
+ MoveInward(stmt);
+ }
+ }
+
+ gBlock = ParentBlock(stmt.Goto);
+
+ if (stmt.IsLoop)
+ {
+ EncloseDoWhile(stmt, gBlock, stmt.Label);
+ }
+ else
+ {
+ Enclose(gBlock, AstBlockType.If, stmt.Condition, Next(stmt.Goto), stmt.Label);
+ }
+
+ gBlock.Remove(stmt.Goto);
+ }
+ }
+
+ private static bool IndirectlyRelated(AstBlock lBlock, AstBlock rBlock, int lLevel, int rlevel)
+ {
+ return !(lBlock == rBlock || DirectlyRelated(lBlock, rBlock, lLevel, rlevel));
+ }
+
+ private static bool DirectlyRelated(AstBlock lBlock, AstBlock rBlock, int lLevel, int rLevel)
+ {
+ // If the levels are equal, they can be either siblings or indirectly related.
+ if (lLevel == rLevel)
+ {
+ return false;
+ }
+
+ IAstNode block;
+ IAstNode other;
+
+ int blockLvl, otherLvl;
+
+ if (lLevel > rLevel)
+ {
+ block = lBlock;
+ blockLvl = lLevel;
+ other = rBlock;
+ otherLvl = rLevel;
+ }
+ else /* if (rLevel > lLevel) */
+ {
+ block = rBlock;
+ blockLvl = rLevel;
+ other = lBlock;
+ otherLvl = lLevel;
+ }
+
+ while (blockLvl >= otherLvl)
+ {
+ if (block == other)
+ {
+ return true;
+ }
+
+ block = block.Parent;
+
+ blockLvl--;
+ }
+
+ return false;
+ }
+
+ private static void Lift(GotoStatement stmt)
+ {
+ AstBlock block = ParentBlock(stmt.Goto);
+
+ AstBlock[] path = BackwardsPath(block, ParentBlock(stmt.Label));
+
+ AstBlock loopFirstStmt = path[path.Length - 1];
+
+ if (loopFirstStmt.Type == AstBlockType.Else)
+ {
+ loopFirstStmt = Previous(loopFirstStmt) as AstBlock;
+
+ if (loopFirstStmt == null || loopFirstStmt.Type != AstBlockType.If)
+ {
+ throw new InvalidOperationException("Found an else without a matching if.");
+ }
+ }
+
+ AstBlock newBlock = EncloseDoWhile(stmt, block, loopFirstStmt);
+
+ block.Remove(stmt.Goto);
+
+ newBlock.AddFirst(stmt.Goto);
+
+ stmt.IsLoop = false;
+ }
+
+ private static void MoveOutward(GotoStatement stmt, int gLevel, int lLevel)
+ {
+ AstBlock origin = ParentBlock(stmt.Goto);
+
+ AstBlock block = origin;
+
+ // Check if a loop is enclosing the goto, and the block that is
+ // directly related to the label is above the loop block.
+ // In that case, we need to introduce a break to get out of the loop.
+ AstBlock loopBlock = origin;
+
+ int loopLevel = gLevel;
+
+ while (loopLevel > lLevel)
+ {
+ AstBlock child = loopBlock;
+
+ loopBlock = loopBlock.Parent;
+
+ loopLevel--;
+
+ if (child.Type == AstBlockType.DoWhile)
+ {
+ EncloseSingleInst(stmt, Instruction.LoopBreak);
+
+ block.Remove(stmt.Goto);
+
+ loopBlock.AddAfter(child, stmt.Goto);
+
+ block = loopBlock;
+ gLevel = loopLevel;
+ }
+ }
+
+ // Insert ifs to skip the parts that shouldn't be executed due to the goto.
+ bool tryInsertElse = stmt.IsUnconditional && origin.Type == AstBlockType.If;
+
+ while (gLevel > lLevel)
+ {
+ Enclose(block, AstBlockType.If, stmt.Condition, Next(stmt.Goto));
+
+ block.Remove(stmt.Goto);
+
+ AstBlock child = block;
+
+ // We can't move the goto in the middle of a if and a else block, in
+ // this case we need to move it after the else.
+ // IsLoop may need to be updated if the label is inside the else, as
+ // introducing a loop is the only way to ensure the else will be executed.
+ if (Next(child) is AstBlock elseBlock && elseBlock.Type == AstBlockType.Else)
+ {
+ child = elseBlock;
+ }
+
+ block = block.Parent;
+
+ block.AddAfter(child, stmt.Goto);
+
+ gLevel--;
+
+ if (tryInsertElse && child == origin)
+ {
+ AstBlock lBlock = ParentBlock(stmt.Label);
+
+ IAstNode last = block == lBlock && !stmt.IsLoop ? stmt.Label : null;
+
+ AstBlock newBlock = Enclose(block, AstBlockType.Else, null, Next(stmt.Goto), last);
+
+ if (newBlock != null)
+ {
+ block.Remove(stmt.Goto);
+
+ block.AddAfter(newBlock, stmt.Goto);
+ }
+ }
+ }
+ }
+
+ private static void MoveInward(GotoStatement stmt)
+ {
+ AstBlock block = ParentBlock(stmt.Goto);
+
+ AstBlock[] path = BackwardsPath(block, ParentBlock(stmt.Label));
+
+ for (int index = path.Length - 1; index >= 0; index--)
+ {
+ AstBlock child = path[index];
+ AstBlock last = child;
+
+ if (child.Type == AstBlockType.If)
+ {
+ // Modify the if condition to allow it to be entered by the goto.
+ if (!ContainsCondComb(child.Condition, Instruction.LogicalOr, stmt.Condition))
+ {
+ child.OrCondition(stmt.Condition);
+ }
+ }
+ else if (child.Type == AstBlockType.Else)
+ {
+ // Modify the matching if condition to force the else to be entered by the goto.
+ if (!(Previous(child) is AstBlock ifBlock) || ifBlock.Type != AstBlockType.If)
+ {
+ throw new InvalidOperationException("Found an else without a matching if.");
+ }
+
+ IAstNode cond = InverseCond(stmt.Condition);
+
+ if (!ContainsCondComb(ifBlock.Condition, Instruction.LogicalAnd, cond))
+ {
+ ifBlock.AndCondition(cond);
+ }
+
+ last = ifBlock;
+ }
+
+ Enclose(block, AstBlockType.If, stmt.Condition, Next(stmt.Goto), last);
+
+ block.Remove(stmt.Goto);
+
+ child.AddFirst(stmt.Goto);
+
+ block = child;
+ }
+ }
+
+ private static bool ContainsCondComb(IAstNode node, Instruction inst, IAstNode newCond)
+ {
+ while (node is AstOperation operation && operation.SourcesCount == 2)
+ {
+ if (operation.Inst == inst && IsSameCond(operation.GetSource(1), newCond))
+ {
+ return true;
+ }
+
+ node = operation.GetSource(0);
+ }
+
+ return false;
+ }
+
+ private static AstBlock EncloseDoWhile(GotoStatement stmt, AstBlock block, IAstNode first)
+ {
+ if (block.Type == AstBlockType.DoWhile && first == block.First)
+ {
+ // We only need to insert the continue if we're not at the end of the loop,
+ // or if our condition is different from the loop condition.
+ if (Next(stmt.Goto) != null || block.Condition != stmt.Condition)
+ {
+ EncloseSingleInst(stmt, Instruction.LoopContinue);
+ }
+
+ // Modify the do-while condition to allow it to continue.
+ if (!ContainsCondComb(block.Condition, Instruction.LogicalOr, stmt.Condition))
+ {
+ block.OrCondition(stmt.Condition);
+ }
+
+ return block;
+ }
+
+ return Enclose(block, AstBlockType.DoWhile, stmt.Condition, first, stmt.Goto);
+ }
+
+ private static void EncloseSingleInst(GotoStatement stmt, Instruction inst)
+ {
+ AstBlock block = ParentBlock(stmt.Goto);
+
+ AstBlock newBlock = new AstBlock(AstBlockType.If, stmt.Condition);
+
+ block.AddAfter(stmt.Goto, newBlock);
+
+ newBlock.AddFirst(new AstOperation(inst));
+ }
+
+ private static AstBlock Enclose(
+ AstBlock block,
+ AstBlockType type,
+ IAstNode cond,
+ IAstNode first,
+ IAstNode last = null)
+ {
+ if (first == last)
+ {
+ return null;
+ }
+
+ if (type == AstBlockType.If)
+ {
+ cond = InverseCond(cond);
+ }
+
+ // Do a quick check, if we are enclosing a single block,
+ // and the block type/condition matches the one we're going
+ // to create, then we don't need a new block, we can just
+ // return the old one.
+ bool hasSingleNode = Next(first) == last;
+
+ if (hasSingleNode && BlockMatches(first, type, cond))
+ {
+ return first as AstBlock;
+ }
+
+ AstBlock newBlock = new AstBlock(type, cond);
+
+ block.AddBefore(first, newBlock);
+
+ while (first != last)
+ {
+ IAstNode next = Next(first);
+
+ block.Remove(first);
+
+ newBlock.Add(first);
+
+ first = next;
+ }
+
+ return newBlock;
+ }
+
+ private static bool BlockMatches(IAstNode node, AstBlockType type, IAstNode cond)
+ {
+ if (!(node is AstBlock block))
+ {
+ return false;
+ }
+
+ return block.Type == type && IsSameCond(block.Condition, cond);
+ }
+
+ private static bool IsSameCond(IAstNode lCond, IAstNode rCond)
+ {
+ if (lCond is AstOperation lCondOp && lCondOp.Inst == Instruction.LogicalNot)
+ {
+ if (!(rCond is AstOperation rCondOp) || rCondOp.Inst != lCondOp.Inst)
+ {
+ return false;
+ }
+
+ lCond = lCondOp.GetSource(0);
+ rCond = rCondOp.GetSource(0);
+ }
+
+ return lCond == rCond;
+ }
+
+ private static AstBlock ParentBlock(IAstNode node)
+ {
+ if (node is AstBlock block)
+ {
+ return block.Parent;
+ }
+
+ while (!(node is AstBlock))
+ {
+ node = node.Parent;
+ }
+
+ return node as AstBlock;
+ }
+
+ private static AstBlock[] BackwardsPath(AstBlock top, AstBlock bottom)
+ {
+ AstBlock block = bottom;
+
+ List<AstBlock> path = new List<AstBlock>();
+
+ while (block != top)
+ {
+ path.Add(block);
+
+ block = block.Parent;
+ }
+
+ return path.ToArray();
+ }
+
+ private static int Level(IAstNode node)
+ {
+ int level = 0;
+
+ while (node != null)
+ {
+ level++;
+
+ node = node.Parent;
+ }
+
+ return level;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs b/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs
new file mode 100644
index 00000000..25216e55
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs
@@ -0,0 +1,23 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class GotoStatement
+ {
+ public AstOperation Goto { get; }
+ public AstAssignment Label { get; }
+
+ public IAstNode Condition => Label.Destination;
+
+ public bool IsLoop { get; set; }
+
+ public bool IsUnconditional => Goto.Inst == Instruction.Branch;
+
+ public GotoStatement(AstOperation branch, AstAssignment label, bool isLoop)
+ {
+ Goto = branch;
+ Label = label;
+ IsLoop = isLoop;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs b/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs
new file mode 100644
index 00000000..5ececbb5
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ interface IAstNode
+ {
+ AstBlock Parent { get; set; }
+
+ LinkedListNode<IAstNode> LLNode { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs
new file mode 100644
index 00000000..cb08a213
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs
@@ -0,0 +1,150 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ static class InstructionInfo
+ {
+ private struct InstInfo
+ {
+ public VariableType DestType { get; }
+
+ public VariableType[] SrcTypes { get; }
+
+ public InstInfo(VariableType destType, params VariableType[] srcTypes)
+ {
+ DestType = destType;
+ SrcTypes = srcTypes;
+ }
+ }
+
+ private static InstInfo[] _infoTbl;
+
+ static InstructionInfo()
+ {
+ _infoTbl = new InstInfo[(int)Instruction.Count];
+
+ // Inst Destination type Source 1 type Source 2 type Source 3 type Source 4 type
+ Add(Instruction.Absolute, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.Add, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.BitfieldExtractS32, VariableType.S32, VariableType.S32, VariableType.S32, VariableType.S32);
+ Add(Instruction.BitfieldExtractU32, VariableType.U32, VariableType.U32, VariableType.S32, VariableType.S32);
+ Add(Instruction.BitfieldInsert, VariableType.Int, VariableType.Int, VariableType.Int, VariableType.S32, VariableType.S32);
+ Add(Instruction.BitfieldReverse, VariableType.Int, VariableType.Int);
+ Add(Instruction.BitwiseAnd, VariableType.Int, VariableType.Int, VariableType.Int);
+ Add(Instruction.BitwiseExclusiveOr, VariableType.Int, VariableType.Int, VariableType.Int);
+ Add(Instruction.BitwiseNot, VariableType.Int, VariableType.Int);
+ Add(Instruction.BitwiseOr, VariableType.Int, VariableType.Int, VariableType.Int);
+ Add(Instruction.BranchIfTrue, VariableType.None, VariableType.Bool);
+ Add(Instruction.BranchIfFalse, VariableType.None, VariableType.Bool);
+ Add(Instruction.Ceiling, VariableType.F32, VariableType.F32, VariableType.F32);
+ Add(Instruction.Clamp, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.ClampU32, VariableType.U32, VariableType.U32, VariableType.U32, VariableType.U32);
+ Add(Instruction.CompareEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.CompareGreater, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.CompareGreaterOrEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.CompareGreaterOrEqualU32, VariableType.Bool, VariableType.U32, VariableType.U32);
+ Add(Instruction.CompareGreaterU32, VariableType.Bool, VariableType.U32, VariableType.U32);
+ Add(Instruction.CompareLess, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.CompareLessOrEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.CompareLessOrEqualU32, VariableType.Bool, VariableType.U32, VariableType.U32);
+ Add(Instruction.CompareLessU32, VariableType.Bool, VariableType.U32, VariableType.U32);
+ Add(Instruction.CompareNotEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.ConditionalSelect, VariableType.Scalar, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.ConvertFPToS32, VariableType.S32, VariableType.F32);
+ Add(Instruction.ConvertS32ToFP, VariableType.F32, VariableType.S32);
+ Add(Instruction.ConvertU32ToFP, VariableType.F32, VariableType.U32);
+ Add(Instruction.Cosine, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.Divide, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.ExponentB2, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.Floor, VariableType.F32, VariableType.F32);
+ Add(Instruction.FusedMultiplyAdd, VariableType.F32, VariableType.F32, VariableType.F32, VariableType.F32);
+ Add(Instruction.IsNan, VariableType.Bool, VariableType.F32);
+ Add(Instruction.LoadAttribute, VariableType.F32, VariableType.S32, VariableType.S32);
+ Add(Instruction.LoadConstant, VariableType.F32, VariableType.S32, VariableType.S32);
+ Add(Instruction.LoadGlobal, VariableType.F32, VariableType.S32, VariableType.S32);
+ Add(Instruction.LoadLocal, VariableType.F32, VariableType.S32);
+ Add(Instruction.LoadStorage, VariableType.F32, VariableType.S32, VariableType.S32);
+ Add(Instruction.LogarithmB2, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.LogicalAnd, VariableType.Bool, VariableType.Bool, VariableType.Bool);
+ Add(Instruction.LogicalExclusiveOr, VariableType.Bool, VariableType.Bool, VariableType.Bool);
+ Add(Instruction.LogicalNot, VariableType.Bool, VariableType.Bool);
+ Add(Instruction.LogicalOr, VariableType.Bool, VariableType.Bool, VariableType.Bool);
+ Add(Instruction.ShiftLeft, VariableType.Int, VariableType.Int, VariableType.Int);
+ Add(Instruction.ShiftRightS32, VariableType.S32, VariableType.S32, VariableType.Int);
+ Add(Instruction.ShiftRightU32, VariableType.U32, VariableType.U32, VariableType.Int);
+ Add(Instruction.Maximum, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.MaximumU32, VariableType.U32, VariableType.U32, VariableType.U32);
+ Add(Instruction.Minimum, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.MinimumU32, VariableType.U32, VariableType.U32, VariableType.U32);
+ Add(Instruction.Multiply, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.Negate, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.PackHalf2x16, VariableType.U32, VariableType.F32, VariableType.F32);
+ Add(Instruction.ReciprocalSquareRoot, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.Sine, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.SquareRoot, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.StoreGlobal, VariableType.None, VariableType.S32, VariableType.S32, VariableType.F32);
+ Add(Instruction.StoreLocal, VariableType.None, VariableType.S32, VariableType.F32);
+ Add(Instruction.StoreStorage, VariableType.None, VariableType.S32, VariableType.S32, VariableType.F32);
+ Add(Instruction.Subtract, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.TextureSample, VariableType.F32);
+ Add(Instruction.TextureSize, VariableType.S32, VariableType.S32, VariableType.S32);
+ Add(Instruction.Truncate, VariableType.F32, VariableType.F32);
+ Add(Instruction.UnpackHalf2x16, VariableType.F32, VariableType.U32);
+ }
+
+ private static void Add(Instruction inst, VariableType destType, params VariableType[] srcTypes)
+ {
+ _infoTbl[(int)inst] = new InstInfo(destType, srcTypes);
+ }
+
+ public static VariableType GetDestVarType(Instruction inst)
+ {
+ return GetFinalVarType(_infoTbl[(int)(inst & Instruction.Mask)].DestType, inst);
+ }
+
+ public static VariableType GetSrcVarType(Instruction inst, int index)
+ {
+ if (inst == Instruction.TextureSample)
+ {
+ return VariableType.F32;
+ }
+
+ return GetFinalVarType(_infoTbl[(int)(inst & Instruction.Mask)].SrcTypes[index], inst);
+ }
+
+ private static VariableType GetFinalVarType(VariableType type, Instruction inst)
+ {
+ if (type == VariableType.Scalar)
+ {
+ return (inst & Instruction.FP) != 0
+ ? VariableType.F32
+ : VariableType.S32;
+ }
+ else if (type == VariableType.Int)
+ {
+ return VariableType.S32;
+ }
+ else if (type == VariableType.None)
+ {
+ throw new ArgumentException($"Invalid operand for instruction \"{inst}\".");
+ }
+
+ return type;
+ }
+
+ public static bool IsUnary(Instruction inst)
+ {
+ if (inst == Instruction.Copy)
+ {
+ return true;
+ }
+ else if (inst == Instruction.TextureSample)
+ {
+ return false;
+ }
+
+ return _infoTbl[(int)(inst & Instruction.Mask)].SrcTypes.Length == 1;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs
new file mode 100644
index 00000000..95c5731a
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs
@@ -0,0 +1,33 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ static class OperandInfo
+ {
+ public static VariableType GetVarType(AstOperand operand)
+ {
+ if (operand.Type == OperandType.LocalVariable)
+ {
+ return operand.VarType;
+ }
+ else
+ {
+ return GetVarType(operand.Type);
+ }
+ }
+
+ public static VariableType GetVarType(OperandType type)
+ {
+ switch (type)
+ {
+ case OperandType.Attribute: return VariableType.F32;
+ case OperandType.Constant: return VariableType.S32;
+ case OperandType.ConstantBuffer: return VariableType.F32;
+ case OperandType.Undefined: return VariableType.S32;
+ }
+
+ throw new ArgumentException($"Invalid operand type \"{type}\".");
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs b/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs
new file mode 100644
index 00000000..53391b62
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs
@@ -0,0 +1,74 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ static class PhiFunctions
+ {
+ public static void Remove(BasicBlock[] blocks)
+ {
+ for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
+ {
+ BasicBlock block = blocks[blkIndex];
+
+ LinkedListNode<INode> node = block.Operations.First;
+
+ while (node != null)
+ {
+ LinkedListNode<INode> nextNode = node.Next;
+
+ if (!(node.Value is PhiNode phi))
+ {
+ node = nextNode;
+
+ continue;
+ }
+
+ for (int index = 0; index < phi.SourcesCount; index++)
+ {
+ Operand src = phi.GetSource(index);
+
+ BasicBlock srcBlock = phi.GetBlock(index);
+
+ Operation copyOp = new Operation(Instruction.Copy, phi.Dest, src);
+
+ AddBeforeBranch(srcBlock, copyOp);
+ }
+
+ block.Operations.Remove(node);
+
+ node = nextNode;
+ }
+ }
+ }
+
+ private static void AddBeforeBranch(BasicBlock block, INode node)
+ {
+ INode lastOp = block.GetLastOp();
+
+ if (lastOp is Operation operation && IsControlFlowInst(operation.Inst))
+ {
+ block.Operations.AddBefore(block.Operations.Last, node);
+ }
+ else
+ {
+ block.Operations.AddLast(node);
+ }
+ }
+
+ private static bool IsControlFlowInst(Instruction inst)
+ {
+ switch (inst)
+ {
+ case Instruction.Branch:
+ case Instruction.BranchIfFalse:
+ case Instruction.BranchIfTrue:
+ case Instruction.Discard:
+ case Instruction.Return:
+ return true;
+ }
+
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
new file mode 100644
index 00000000..dc822621
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
@@ -0,0 +1,281 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ static class StructuredProgram
+ {
+ public static StructuredProgramInfo MakeStructuredProgram(BasicBlock[] blocks, ShaderConfig config)
+ {
+ PhiFunctions.Remove(blocks);
+
+ StructuredProgramContext context = new StructuredProgramContext(blocks.Length, config);
+
+ for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
+ {
+ BasicBlock block = blocks[blkIndex];
+
+ context.EnterBlock(block);
+
+ foreach (INode node in block.Operations)
+ {
+ Operation operation = (Operation)node;
+
+ if (IsBranchInst(operation.Inst))
+ {
+ context.LeaveBlock(block, operation);
+ }
+ else
+ {
+ AddOperation(context, operation);
+ }
+ }
+ }
+
+ GotoElimination.Eliminate(context.GetGotos());
+
+ AstOptimizer.Optimize(context);
+
+ return context.Info;
+ }
+
+ private static void AddOperation(StructuredProgramContext context, Operation operation)
+ {
+ Instruction inst = operation.Inst;
+
+ IAstNode[] sources = new IAstNode[operation.SourcesCount];
+
+ for (int index = 0; index < sources.Length; index++)
+ {
+ sources[index] = context.GetOperandUse(operation.GetSource(index));
+ }
+
+ if (operation.Dest != null)
+ {
+ AstOperand dest = context.GetOperandDef(operation.Dest);
+
+ if (inst == Instruction.LoadConstant)
+ {
+ Operand slot = operation.GetSource(0);
+
+ if (slot.Type != OperandType.Constant)
+ {
+ throw new InvalidOperationException("Found load with non-constant constant buffer slot.");
+ }
+
+ context.Info.CBuffers.Add(slot.Value);
+ }
+ else if (inst == Instruction.LoadStorage)
+ {
+ Operand slot = operation.GetSource(0);
+
+ if (slot.Type != OperandType.Constant)
+ {
+ throw new InvalidOperationException("Found load or store with non-constant storage buffer slot.");
+ }
+
+ context.Info.SBuffers.Add(slot.Value);
+ }
+
+ AstAssignment assignment;
+
+ // If all the sources are bool, it's better to use short-circuiting
+ // logical operations, rather than forcing a cast to int and doing
+ // a bitwise operation with the value, as it is likely to be used as
+ // a bool in the end.
+ if (IsBitwiseInst(inst) && AreAllSourceTypesEqual(sources, VariableType.Bool))
+ {
+ inst = GetLogicalFromBitwiseInst(inst);
+ }
+
+ bool isCondSel = inst == Instruction.ConditionalSelect;
+ bool isCopy = inst == Instruction.Copy;
+
+ if (isCondSel || isCopy)
+ {
+ VariableType type = GetVarTypeFromUses(operation.Dest);
+
+ if (isCondSel && type == VariableType.F32)
+ {
+ inst |= Instruction.FP;
+ }
+
+ dest.VarType = type;
+ }
+ else
+ {
+ dest.VarType = InstructionInfo.GetDestVarType(inst);
+ }
+
+ int componentMask = 1 << operation.ComponentIndex;
+
+ IAstNode source;
+
+ if (operation is TextureOperation texOp)
+ {
+ AstTextureOperation astTexOp = new AstTextureOperation(
+ inst,
+ texOp.Target,
+ texOp.Flags,
+ texOp.Handle,
+ componentMask,
+ sources);
+
+ context.Info.Samplers.Add(astTexOp);
+
+ source = astTexOp;
+ }
+ else if (!isCopy)
+ {
+ source = new AstOperation(inst, componentMask, sources);
+ }
+ else
+ {
+ source = sources[0];
+ }
+
+ assignment = new AstAssignment(dest, source);
+
+ context.AddNode(assignment);
+ }
+ else if (operation.Inst == Instruction.Comment)
+ {
+ context.AddNode(new AstComment(((CommentNode)operation).Comment));
+ }
+ else
+ {
+ if (inst == Instruction.StoreStorage)
+ {
+ Operand slot = operation.GetSource(0);
+
+ if (slot.Type != OperandType.Constant)
+ {
+ throw new InvalidOperationException("Found load or store with non-constant storage buffer slot.");
+ }
+
+ context.Info.SBuffers.Add(slot.Value);
+ }
+
+ context.AddNode(new AstOperation(inst, sources));
+ }
+ }
+
+ private static VariableType GetVarTypeFromUses(Operand dest)
+ {
+ HashSet<Operand> visited = new HashSet<Operand>();
+
+ Queue<Operand> pending = new Queue<Operand>();
+
+ bool Enqueue(Operand operand)
+ {
+ if (visited.Add(operand))
+ {
+ pending.Enqueue(operand);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ Enqueue(dest);
+
+ while (pending.TryDequeue(out Operand operand))
+ {
+ foreach (INode useNode in operand.UseOps)
+ {
+ if (!(useNode is Operation operation))
+ {
+ continue;
+ }
+
+ if (operation.Inst == Instruction.Copy)
+ {
+ if (operation.Dest.Type == OperandType.LocalVariable)
+ {
+ if (Enqueue(operation.Dest))
+ {
+ break;
+ }
+ }
+ else
+ {
+ return OperandInfo.GetVarType(operation.Dest.Type);
+ }
+ }
+ else
+ {
+ for (int index = 0; index < operation.SourcesCount; index++)
+ {
+ if (operation.GetSource(index) == operand)
+ {
+ return InstructionInfo.GetSrcVarType(operation.Inst, index);
+ }
+ }
+ }
+ }
+ }
+
+ return VariableType.S32;
+ }
+
+ private static bool AreAllSourceTypesEqual(IAstNode[] sources, VariableType type)
+ {
+ foreach (IAstNode node in sources)
+ {
+ if (!(node is AstOperand operand))
+ {
+ return false;
+ }
+
+ if (operand.VarType != type)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static bool IsBranchInst(Instruction inst)
+ {
+ switch (inst)
+ {
+ case Instruction.Branch:
+ case Instruction.BranchIfFalse:
+ case Instruction.BranchIfTrue:
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool IsBitwiseInst(Instruction inst)
+ {
+ switch (inst)
+ {
+ case Instruction.BitwiseAnd:
+ case Instruction.BitwiseExclusiveOr:
+ case Instruction.BitwiseNot:
+ case Instruction.BitwiseOr:
+ return true;
+ }
+
+ return false;
+ }
+
+ private static Instruction GetLogicalFromBitwiseInst(Instruction inst)
+ {
+ switch (inst)
+ {
+ case Instruction.BitwiseAnd: return Instruction.LogicalAnd;
+ case Instruction.BitwiseExclusiveOr: return Instruction.LogicalExclusiveOr;
+ case Instruction.BitwiseNot: return Instruction.LogicalNot;
+ case Instruction.BitwiseOr: return Instruction.LogicalOr;
+ }
+
+ throw new ArgumentException($"Unexpected instruction \"{inst}\".");
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs
new file mode 100644
index 00000000..03ff8818
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs
@@ -0,0 +1,303 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+using System.Collections.Generic;
+using System.Linq;
+
+using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class StructuredProgramContext
+ {
+ private HashSet<BasicBlock> _loopTails;
+
+ private Stack<(AstBlock Block, int EndIndex)> _blockStack;
+
+ private Dictionary<Operand, AstOperand> _localsMap;
+
+ private Dictionary<int, AstAssignment> _gotoTempAsgs;
+
+ private List<GotoStatement> _gotos;
+
+ private AstBlock _currBlock;
+
+ private int _currEndIndex;
+
+ public StructuredProgramInfo Info { get; }
+
+ public ShaderConfig Config { get; }
+
+ public StructuredProgramContext(int blocksCount, ShaderConfig config)
+ {
+ _loopTails = new HashSet<BasicBlock>();
+
+ _blockStack = new Stack<(AstBlock, int)>();
+
+ _localsMap = new Dictionary<Operand, AstOperand>();
+
+ _gotoTempAsgs = new Dictionary<int, AstAssignment>();
+
+ _gotos = new List<GotoStatement>();
+
+ _currBlock = new AstBlock(AstBlockType.Main);
+
+ _currEndIndex = blocksCount;
+
+ Info = new StructuredProgramInfo(_currBlock);
+
+ Config = config;
+ }
+
+ public void EnterBlock(BasicBlock block)
+ {
+ while (_currEndIndex == block.Index)
+ {
+ (_currBlock, _currEndIndex) = _blockStack.Pop();
+ }
+
+ if (_gotoTempAsgs.TryGetValue(block.Index, out AstAssignment gotoTempAsg))
+ {
+ AddGotoTempReset(block, gotoTempAsg);
+ }
+
+ LookForDoWhileStatements(block);
+ }
+
+ public void LeaveBlock(BasicBlock block, Operation branchOp)
+ {
+ LookForIfStatements(block, branchOp);
+ }
+
+ private void LookForDoWhileStatements(BasicBlock block)
+ {
+ // Check if we have any predecessor whose index is greater than the
+ // current block, this indicates a loop.
+ bool done = false;
+
+ foreach (BasicBlock predecessor in block.Predecessors.OrderByDescending(x => x.Index))
+ {
+ if (predecessor.Index < block.Index)
+ {
+ break;
+ }
+
+ if (predecessor.Index < _currEndIndex && !done)
+ {
+ Operation branchOp = (Operation)predecessor.GetLastOp();
+
+ NewBlock(AstBlockType.DoWhile, branchOp, predecessor.Index + 1);
+
+ _loopTails.Add(predecessor);
+
+ done = true;
+ }
+ else
+ {
+ AddGotoTempReset(block, GetGotoTempAsg(block.Index));
+
+ break;
+ }
+ }
+ }
+
+ private void LookForIfStatements(BasicBlock block, Operation branchOp)
+ {
+ if (block.Branch == null)
+ {
+ return;
+ }
+
+ bool isLoop = block.Branch.Index <= block.Index;
+
+ if (block.Branch.Index <= _currEndIndex && !isLoop)
+ {
+ NewBlock(AstBlockType.If, branchOp, block.Branch.Index);
+ }
+ else if (!_loopTails.Contains(block))
+ {
+ AstAssignment gotoTempAsg = GetGotoTempAsg(block.Branch.Index);
+
+ IAstNode cond = GetBranchCond(AstBlockType.DoWhile, branchOp);
+
+ AddNode(Assign(gotoTempAsg.Destination, cond));
+
+ AstOperation branch = new AstOperation(branchOp.Inst);
+
+ AddNode(branch);
+
+ GotoStatement gotoStmt = new GotoStatement(branch, gotoTempAsg, isLoop);
+
+ _gotos.Add(gotoStmt);
+ }
+ }
+
+ private AstAssignment GetGotoTempAsg(int index)
+ {
+ if (_gotoTempAsgs.TryGetValue(index, out AstAssignment gotoTempAsg))
+ {
+ return gotoTempAsg;
+ }
+
+ AstOperand gotoTemp = NewTemp(VariableType.Bool);
+
+ gotoTempAsg = Assign(gotoTemp, Const(IrConsts.False));
+
+ _gotoTempAsgs.Add(index, gotoTempAsg);
+
+ return gotoTempAsg;
+ }
+
+ private void AddGotoTempReset(BasicBlock block, AstAssignment gotoTempAsg)
+ {
+ AddNode(gotoTempAsg);
+
+ // For block 0, we don't need to add the extra "reset" at the beginning,
+ // because it is already the first node to be executed on the shader,
+ // so it is reset to false by the "local" assignment anyway.
+ if (block.Index != 0)
+ {
+ Info.MainBlock.AddFirst(Assign(gotoTempAsg.Destination, Const(IrConsts.False)));
+ }
+ }
+
+ private void NewBlock(AstBlockType type, Operation branchOp, int endIndex)
+ {
+ NewBlock(type, GetBranchCond(type, branchOp), endIndex);
+ }
+
+ private void NewBlock(AstBlockType type, IAstNode cond, int endIndex)
+ {
+ AstBlock childBlock = new AstBlock(type, cond);
+
+ AddNode(childBlock);
+
+ _blockStack.Push((_currBlock, _currEndIndex));
+
+ _currBlock = childBlock;
+ _currEndIndex = endIndex;
+ }
+
+ private IAstNode GetBranchCond(AstBlockType type, Operation branchOp)
+ {
+ IAstNode cond;
+
+ if (branchOp.Inst == Instruction.Branch)
+ {
+ cond = Const(type == AstBlockType.If ? IrConsts.False : IrConsts.True);
+ }
+ else
+ {
+ cond = GetOperandUse(branchOp.GetSource(0));
+
+ Instruction invInst = type == AstBlockType.If
+ ? Instruction.BranchIfTrue
+ : Instruction.BranchIfFalse;
+
+ if (branchOp.Inst == invInst)
+ {
+ cond = new AstOperation(Instruction.LogicalNot, cond);
+ }
+ }
+
+ return cond;
+ }
+
+ public void AddNode(IAstNode node)
+ {
+ _currBlock.Add(node);
+ }
+
+ public GotoStatement[] GetGotos()
+ {
+ return _gotos.ToArray();
+ }
+
+ private AstOperand NewTemp(VariableType type)
+ {
+ AstOperand newTemp = Local(type);
+
+ Info.Locals.Add(newTemp);
+
+ return newTemp;
+ }
+
+ public AstOperand GetOperandDef(Operand operand)
+ {
+ if (TryGetUserAttributeIndex(operand, out int attrIndex))
+ {
+ Info.OAttributes.Add(attrIndex);
+ }
+
+ return GetOperand(operand);
+ }
+
+ public AstOperand GetOperandUse(Operand operand)
+ {
+ if (TryGetUserAttributeIndex(operand, out int attrIndex))
+ {
+ Info.IAttributes.Add(attrIndex);
+
+ Info.InterpolationQualifiers[attrIndex] = operand.Interpolation;
+ }
+ else if (operand.Type == OperandType.Attribute && operand.Value == AttributeConsts.InstanceId)
+ {
+ Info.UsesInstanceId = true;
+ }
+ else if (operand.Type == OperandType.ConstantBuffer)
+ {
+ Info.CBuffers.Add(operand.GetCbufSlot());
+ }
+
+ return GetOperand(operand);
+ }
+
+ private AstOperand GetOperand(Operand operand)
+ {
+ if (operand == null)
+ {
+ return null;
+ }
+
+ if (operand.Type != OperandType.LocalVariable)
+ {
+ return new AstOperand(operand);
+ }
+
+ if (!_localsMap.TryGetValue(operand, out AstOperand astOperand))
+ {
+ astOperand = new AstOperand(operand);
+
+ _localsMap.Add(operand, astOperand);
+
+ Info.Locals.Add(astOperand);
+ }
+
+ return astOperand;
+ }
+
+ private static bool TryGetUserAttributeIndex(Operand operand, out int attrIndex)
+ {
+ if (operand.Type == OperandType.Attribute)
+ {
+ if (operand.Value >= AttributeConsts.UserAttributeBase &&
+ operand.Value < AttributeConsts.UserAttributeEnd)
+ {
+ attrIndex = (operand.Value - AttributeConsts.UserAttributeBase) >> 4;
+
+ return true;
+ }
+ else if (operand.Value >= AttributeConsts.FragmentOutputColorBase &&
+ operand.Value < AttributeConsts.FragmentOutputColorEnd)
+ {
+ attrIndex = (operand.Value - AttributeConsts.FragmentOutputColorBase) >> 4;
+
+ return true;
+ }
+ }
+
+ attrIndex = 0;
+
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs
new file mode 100644
index 00000000..27fd1487
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs
@@ -0,0 +1,40 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class StructuredProgramInfo
+ {
+ public AstBlock MainBlock { get; }
+
+ public HashSet<AstOperand> Locals { get; }
+
+ public HashSet<int> CBuffers { get; }
+ public HashSet<int> SBuffers { get; }
+
+ public HashSet<int> IAttributes { get; }
+ public HashSet<int> OAttributes { get; }
+
+ public InterpolationQualifier[] InterpolationQualifiers { get; }
+
+ public bool UsesInstanceId { get; set; }
+
+ public HashSet<AstTextureOperation> Samplers { get; }
+
+ public StructuredProgramInfo(AstBlock mainBlock)
+ {
+ MainBlock = mainBlock;
+
+ Locals = new HashSet<AstOperand>();
+
+ CBuffers = new HashSet<int>();
+ SBuffers = new HashSet<int>();
+
+ IAttributes = new HashSet<int>();
+ OAttributes = new HashSet<int>();
+
+ InterpolationQualifiers = new InterpolationQualifier[32];
+
+ Samplers = new HashSet<AstTextureOperation>();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/VariableType.cs b/Ryujinx.Graphics.Shader/StructuredIr/VariableType.cs
new file mode 100644
index 00000000..4c7f3849
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/StructuredIr/VariableType.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ enum VariableType
+ {
+ None,
+ Bool,
+ Scalar,
+ Int,
+ F32,
+ S32,
+ U32
+ }
+} \ No newline at end of file