aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Shader/StructuredIr
diff options
context:
space:
mode:
authorTSR Berry <20988865+TSRBerry@users.noreply.github.com>2023-04-08 01:22:00 +0200
committerMary <thog@protonmail.com>2023-04-27 23:51:14 +0200
commitcee712105850ac3385cd0091a923438167433f9f (patch)
tree4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.Graphics.Shader/StructuredIr
parentcd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff)
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.Graphics.Shader/StructuredIr')
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs35
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs117
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs12
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs68
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs12
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs74
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs11
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs50
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs80
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs155
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs36
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs459
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs23
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs21
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs11
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs216
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/IoDefinition.cs44
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs33
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs45
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/StructuredFunction.cs42
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs421
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs330
-rw-r--r--src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs36
23 files changed, 2331 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs
new file mode 100644
index 00000000..bb3fe7af
--- /dev/null
+++ b/src/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/src/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs
new file mode 100644
index 00000000..2f34bee8
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs
@@ -0,0 +1,117 @@
+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 IAstNode Last => _nodes.Last?.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/src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs
new file mode 100644
index 00000000..c12efda9
--- /dev/null
+++ b/src/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/src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs
new file mode 100644
index 00000000..10d5dce0
--- /dev/null
+++ b/src/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/src/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs
new file mode 100644
index 00000000..dabe623f
--- /dev/null
+++ b/src/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/src/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs
new file mode 100644
index 00000000..7aa0409b
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs
@@ -0,0 +1,74 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+
+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(AggregateType 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/src/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs
new file mode 100644
index 00000000..c667aac9
--- /dev/null
+++ b/src/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/src/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs
new file mode 100644
index 00000000..1fc0035f
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs
@@ -0,0 +1,50 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+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 AggregateType VarType { get; set; }
+
+ public int Value { get; }
+
+ public int CbufSlot { get; }
+ public int CbufOffset { get; }
+
+ private AstOperand()
+ {
+ Defs = new HashSet<IAstNode>();
+ Uses = new HashSet<IAstNode>();
+
+ VarType = AggregateType.S32;
+ }
+
+ public AstOperand(Operand operand) : this()
+ {
+ Type = operand.Type;
+
+ 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/src/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs
new file mode 100644
index 00000000..2393fd8d
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs
@@ -0,0 +1,80 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+using System.Numerics;
+
+using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class AstOperation : AstNode
+ {
+ public Instruction Inst { get; }
+ public StorageKind StorageKind { get; }
+
+ public int Index { get; }
+
+ private IAstNode[] _sources;
+
+ public int SourcesCount => _sources.Length;
+
+ public AstOperation(Instruction inst, StorageKind storageKind, IAstNode[] sources, int sourcesCount)
+ {
+ Inst = inst;
+ StorageKind = storageKind;
+ _sources = sources;
+
+ for (int index = 0; index < sources.Length; index++)
+ {
+ if (index < sourcesCount)
+ {
+ AddUse(sources[index], this);
+ }
+ else
+ {
+ AddDef(sources[index], this);
+ }
+ }
+
+ Index = 0;
+ }
+
+ public AstOperation(Instruction inst, StorageKind storageKind, int index, IAstNode[] sources, int sourcesCount) : this(inst, storageKind, sources, sourcesCount)
+ {
+ Index = index;
+ }
+
+ public AstOperation(Instruction inst, params IAstNode[] sources) : this(inst, StorageKind.None, sources, sources.Length)
+ {
+ }
+
+ 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;
+ }
+
+ public AggregateType GetVectorType(AggregateType scalarType)
+ {
+ int componentsCount = BitOperations.PopCount((uint)Index);
+
+ AggregateType type = scalarType;
+
+ switch (componentsCount)
+ {
+ case 2: type |= AggregateType.Vector2; break;
+ case 3: type |= AggregateType.Vector3; break;
+ case 4: type |= AggregateType.Vector4; break;
+ }
+
+ return type;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs
new file mode 100644
index 00000000..b71ae2c4
--- /dev/null
+++ b/src/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.CurrentFunction.MainBlock;
+
+ // When debug mode is enabled, we disable expression propagation
+ // (this makes comparison with the disassembly easier).
+ if (!context.Config.Options.Flags.HasFlag(TranslationFlags.DebugMode))
+ {
+ 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.CurrentFunction.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/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
new file mode 100644
index 00000000..a44f13cc
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
@@ -0,0 +1,36 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class AstTextureOperation : AstOperation
+ {
+ public SamplerType Type { get; }
+ public TextureFormat Format { get; }
+ public TextureFlags Flags { get; }
+
+ public int CbufSlot { get; }
+ public int Handle { get; }
+
+ public AstTextureOperation(
+ Instruction inst,
+ SamplerType type,
+ TextureFormat format,
+ TextureFlags flags,
+ int cbufSlot,
+ int handle,
+ int index,
+ params IAstNode[] sources) : base(inst, StorageKind.None, index, sources, sources.Length)
+ {
+ Type = type;
+ Format = format;
+ Flags = flags;
+ CbufSlot = cbufSlot;
+ Handle = handle;
+ }
+
+ public AstTextureOperation WithType(SamplerType type)
+ {
+ return new AstTextureOperation(Inst, type, Format, Flags, CbufSlot, Handle, Index);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs
new file mode 100644
index 00000000..8bcf9d9c
--- /dev/null
+++ b/src/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/src/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs
new file mode 100644
index 00000000..25216e55
--- /dev/null
+++ b/src/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/src/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs
new file mode 100644
index 00000000..d45f8d4e
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ [Flags]
+ enum HelperFunctionsMask
+ {
+ AtomicMinMaxS32Shared = 1 << 0,
+ AtomicMinMaxS32Storage = 1 << 1,
+ MultiplyHighS32 = 1 << 2,
+ MultiplyHighU32 = 1 << 3,
+ Shuffle = 1 << 4,
+ ShuffleDown = 1 << 5,
+ ShuffleUp = 1 << 6,
+ ShuffleXor = 1 << 7,
+ StoreSharedSmallInt = 1 << 8,
+ StoreStorageSmallInt = 1 << 9,
+ SwizzleAdd = 1 << 10,
+ FSI = 1 << 11
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs
new file mode 100644
index 00000000..5ececbb5
--- /dev/null
+++ b/src/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/src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs
new file mode 100644
index 00000000..8eccef23
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs
@@ -0,0 +1,216 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ static class InstructionInfo
+ {
+ private readonly struct InstInfo
+ {
+ public AggregateType DestType { get; }
+
+ public AggregateType[] SrcTypes { get; }
+
+ public InstInfo(AggregateType destType, params AggregateType[] 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.AtomicAdd, AggregateType.U32, AggregateType.S32, AggregateType.S32, AggregateType.U32);
+ Add(Instruction.AtomicAnd, AggregateType.U32, AggregateType.S32, AggregateType.S32, AggregateType.U32);
+ Add(Instruction.AtomicCompareAndSwap, AggregateType.U32, AggregateType.S32, AggregateType.S32, AggregateType.U32, AggregateType.U32);
+ Add(Instruction.AtomicMaxS32, AggregateType.S32, AggregateType.S32, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.AtomicMaxU32, AggregateType.U32, AggregateType.S32, AggregateType.S32, AggregateType.U32);
+ Add(Instruction.AtomicMinS32, AggregateType.S32, AggregateType.S32, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.AtomicMinU32, AggregateType.U32, AggregateType.S32, AggregateType.S32, AggregateType.U32);
+ Add(Instruction.AtomicOr, AggregateType.U32, AggregateType.S32, AggregateType.S32, AggregateType.U32);
+ Add(Instruction.AtomicSwap, AggregateType.U32, AggregateType.S32, AggregateType.S32, AggregateType.U32);
+ Add(Instruction.AtomicXor, AggregateType.U32, AggregateType.S32, AggregateType.S32, AggregateType.U32);
+ Add(Instruction.Absolute, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.Add, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.Ballot, AggregateType.U32, AggregateType.Bool);
+ Add(Instruction.BitCount, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.BitfieldExtractS32, AggregateType.S32, AggregateType.S32, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.BitfieldExtractU32, AggregateType.U32, AggregateType.U32, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.BitfieldInsert, AggregateType.S32, AggregateType.S32, AggregateType.S32, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.BitfieldReverse, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.BitwiseAnd, AggregateType.S32, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.BitwiseExclusiveOr, AggregateType.S32, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.BitwiseNot, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.BitwiseOr, AggregateType.S32, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.BranchIfTrue, AggregateType.Void, AggregateType.Bool);
+ Add(Instruction.BranchIfFalse, AggregateType.Void, AggregateType.Bool);
+ Add(Instruction.Call, AggregateType.Scalar);
+ Add(Instruction.Ceiling, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.Clamp, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.ClampU32, AggregateType.U32, AggregateType.U32, AggregateType.U32, AggregateType.U32);
+ Add(Instruction.CompareEqual, AggregateType.Bool, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.CompareGreater, AggregateType.Bool, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.CompareGreaterOrEqual, AggregateType.Bool, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.CompareGreaterOrEqualU32, AggregateType.Bool, AggregateType.U32, AggregateType.U32);
+ Add(Instruction.CompareGreaterU32, AggregateType.Bool, AggregateType.U32, AggregateType.U32);
+ Add(Instruction.CompareLess, AggregateType.Bool, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.CompareLessOrEqual, AggregateType.Bool, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.CompareLessOrEqualU32, AggregateType.Bool, AggregateType.U32, AggregateType.U32);
+ Add(Instruction.CompareLessU32, AggregateType.Bool, AggregateType.U32, AggregateType.U32);
+ Add(Instruction.CompareNotEqual, AggregateType.Bool, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.ConditionalSelect, AggregateType.Scalar, AggregateType.Bool, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.ConvertFP32ToFP64, AggregateType.FP64, AggregateType.FP32);
+ Add(Instruction.ConvertFP64ToFP32, AggregateType.FP32, AggregateType.FP64);
+ Add(Instruction.ConvertFP32ToS32, AggregateType.S32, AggregateType.FP32);
+ Add(Instruction.ConvertFP32ToU32, AggregateType.U32, AggregateType.FP32);
+ Add(Instruction.ConvertFP64ToS32, AggregateType.S32, AggregateType.FP64);
+ Add(Instruction.ConvertFP64ToU32, AggregateType.U32, AggregateType.FP64);
+ Add(Instruction.ConvertS32ToFP32, AggregateType.FP32, AggregateType.S32);
+ Add(Instruction.ConvertS32ToFP64, AggregateType.FP64, AggregateType.S32);
+ Add(Instruction.ConvertU32ToFP32, AggregateType.FP32, AggregateType.U32);
+ Add(Instruction.ConvertU32ToFP64, AggregateType.FP64, AggregateType.U32);
+ Add(Instruction.Cosine, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.Ddx, AggregateType.FP32, AggregateType.FP32);
+ Add(Instruction.Ddy, AggregateType.FP32, AggregateType.FP32);
+ Add(Instruction.Divide, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.ExponentB2, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.FindLSB, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.FindMSBS32, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.FindMSBU32, AggregateType.S32, AggregateType.U32);
+ Add(Instruction.Floor, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.FusedMultiplyAdd, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.ImageLoad, AggregateType.FP32);
+ Add(Instruction.ImageStore, AggregateType.Void);
+ Add(Instruction.ImageAtomic, AggregateType.S32);
+ Add(Instruction.IsNan, AggregateType.Bool, AggregateType.Scalar);
+ Add(Instruction.Load, AggregateType.FP32);
+ Add(Instruction.LoadConstant, AggregateType.FP32, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.LoadGlobal, AggregateType.U32, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.LoadLocal, AggregateType.U32, AggregateType.S32);
+ Add(Instruction.LoadShared, AggregateType.U32, AggregateType.S32);
+ Add(Instruction.LoadStorage, AggregateType.U32, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.Lod, AggregateType.FP32);
+ Add(Instruction.LogarithmB2, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.LogicalAnd, AggregateType.Bool, AggregateType.Bool, AggregateType.Bool);
+ Add(Instruction.LogicalExclusiveOr, AggregateType.Bool, AggregateType.Bool, AggregateType.Bool);
+ Add(Instruction.LogicalNot, AggregateType.Bool, AggregateType.Bool);
+ Add(Instruction.LogicalOr, AggregateType.Bool, AggregateType.Bool, AggregateType.Bool);
+ Add(Instruction.Maximum, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.MaximumU32, AggregateType.U32, AggregateType.U32, AggregateType.U32);
+ Add(Instruction.Minimum, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.MinimumU32, AggregateType.U32, AggregateType.U32, AggregateType.U32);
+ Add(Instruction.Multiply, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.MultiplyHighS32, AggregateType.S32, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.MultiplyHighU32, AggregateType.U32, AggregateType.U32, AggregateType.U32);
+ Add(Instruction.Negate, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.PackDouble2x32, AggregateType.FP64, AggregateType.U32, AggregateType.U32);
+ Add(Instruction.PackHalf2x16, AggregateType.U32, AggregateType.FP32, AggregateType.FP32);
+ Add(Instruction.ReciprocalSquareRoot, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.Round, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.ShiftLeft, AggregateType.S32, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.ShiftRightS32, AggregateType.S32, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.ShiftRightU32, AggregateType.U32, AggregateType.U32, AggregateType.S32);
+ Add(Instruction.Shuffle, AggregateType.FP32, AggregateType.FP32, AggregateType.U32, AggregateType.U32, AggregateType.Bool);
+ Add(Instruction.ShuffleDown, AggregateType.FP32, AggregateType.FP32, AggregateType.U32, AggregateType.U32, AggregateType.Bool);
+ Add(Instruction.ShuffleUp, AggregateType.FP32, AggregateType.FP32, AggregateType.U32, AggregateType.U32, AggregateType.Bool);
+ Add(Instruction.ShuffleXor, AggregateType.FP32, AggregateType.FP32, AggregateType.U32, AggregateType.U32, AggregateType.Bool);
+ Add(Instruction.Sine, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.SquareRoot, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.Store, AggregateType.Void);
+ Add(Instruction.StoreGlobal, AggregateType.Void, AggregateType.S32, AggregateType.S32, AggregateType.U32);
+ Add(Instruction.StoreLocal, AggregateType.Void, AggregateType.S32, AggregateType.U32);
+ Add(Instruction.StoreShared, AggregateType.Void, AggregateType.S32, AggregateType.U32);
+ Add(Instruction.StoreShared16, AggregateType.Void, AggregateType.S32, AggregateType.U32);
+ Add(Instruction.StoreShared8, AggregateType.Void, AggregateType.S32, AggregateType.U32);
+ Add(Instruction.StoreStorage, AggregateType.Void, AggregateType.S32, AggregateType.S32, AggregateType.U32);
+ Add(Instruction.StoreStorage16, AggregateType.Void, AggregateType.S32, AggregateType.S32, AggregateType.U32);
+ Add(Instruction.StoreStorage8, AggregateType.Void, AggregateType.S32, AggregateType.S32, AggregateType.U32);
+ Add(Instruction.Subtract, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.SwizzleAdd, AggregateType.FP32, AggregateType.FP32, AggregateType.FP32, AggregateType.S32);
+ Add(Instruction.TextureSample, AggregateType.FP32);
+ Add(Instruction.TextureSize, AggregateType.S32, AggregateType.S32, AggregateType.S32);
+ Add(Instruction.Truncate, AggregateType.Scalar, AggregateType.Scalar);
+ Add(Instruction.UnpackDouble2x32, AggregateType.U32, AggregateType.FP64);
+ Add(Instruction.UnpackHalf2x16, AggregateType.FP32, AggregateType.U32);
+ Add(Instruction.VectorExtract, AggregateType.Scalar, AggregateType.Vector4, AggregateType.S32);
+ Add(Instruction.VoteAll, AggregateType.Bool, AggregateType.Bool);
+ Add(Instruction.VoteAllEqual, AggregateType.Bool, AggregateType.Bool);
+ Add(Instruction.VoteAny, AggregateType.Bool, AggregateType.Bool);
+ }
+
+ private static void Add(Instruction inst, AggregateType destType, params AggregateType[] srcTypes)
+ {
+ _infoTbl[(int)inst] = new InstInfo(destType, srcTypes);
+ }
+
+ public static AggregateType GetDestVarType(Instruction inst)
+ {
+ return GetFinalVarType(_infoTbl[(int)(inst & Instruction.Mask)].DestType, inst);
+ }
+
+ public static AggregateType GetSrcVarType(Instruction inst, int index)
+ {
+ // TODO: Return correct type depending on source index,
+ // that can improve the decompiler output.
+ if (inst == Instruction.ImageLoad ||
+ inst == Instruction.ImageStore ||
+ inst == Instruction.ImageAtomic ||
+ inst == Instruction.Lod ||
+ inst == Instruction.TextureSample)
+ {
+ return AggregateType.FP32;
+ }
+ else if (inst == Instruction.Call || inst == Instruction.Load || inst == Instruction.Store)
+ {
+ return AggregateType.S32;
+ }
+
+ return GetFinalVarType(_infoTbl[(int)(inst & Instruction.Mask)].SrcTypes[index], inst);
+ }
+
+ private static AggregateType GetFinalVarType(AggregateType type, Instruction inst)
+ {
+ if (type == AggregateType.Scalar)
+ {
+ if ((inst & Instruction.FP32) != 0)
+ {
+ return AggregateType.FP32;
+ }
+ else if ((inst & Instruction.FP64) != 0)
+ {
+ return AggregateType.FP64;
+ }
+ else
+ {
+ return AggregateType.S32;
+ }
+ }
+ else if (type == AggregateType.Void)
+ {
+ 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/src/Ryujinx.Graphics.Shader/StructuredIr/IoDefinition.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/IoDefinition.cs
new file mode 100644
index 00000000..21a1b3f0
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/IoDefinition.cs
@@ -0,0 +1,44 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ readonly struct IoDefinition : IEquatable<IoDefinition>
+ {
+ public StorageKind StorageKind { get; }
+ public IoVariable IoVariable { get; }
+ public int Location { get; }
+ public int Component { get; }
+
+ public IoDefinition(StorageKind storageKind, IoVariable ioVariable, int location = 0, int component = 0)
+ {
+ StorageKind = storageKind;
+ IoVariable = ioVariable;
+ Location = location;
+ Component = component;
+ }
+
+ public override bool Equals(object other)
+ {
+ return other is IoDefinition ioDefinition && Equals(ioDefinition);
+ }
+
+ public bool Equals(IoDefinition other)
+ {
+ return StorageKind == other.StorageKind &&
+ IoVariable == other.IoVariable &&
+ Location == other.Location &&
+ Component == other.Component;
+ }
+
+ public override int GetHashCode()
+ {
+ return (int)StorageKind | ((int)IoVariable << 8) | (Location << 16) | (Component << 24);
+ }
+
+ public override string ToString()
+ {
+ return $"{StorageKind}.{IoVariable}.{Location}.{Component}";
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs
new file mode 100644
index 00000000..38ed1584
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs
@@ -0,0 +1,33 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ static class OperandInfo
+ {
+ public static AggregateType GetVarType(AstOperand operand)
+ {
+ if (operand.Type == OperandType.LocalVariable)
+ {
+ return operand.VarType;
+ }
+ else
+ {
+ return GetVarType(operand.Type);
+ }
+ }
+
+ public static AggregateType GetVarType(OperandType type)
+ {
+ return type switch
+ {
+ OperandType.Argument => AggregateType.S32,
+ OperandType.Constant => AggregateType.S32,
+ OperandType.ConstantBuffer => AggregateType.FP32,
+ OperandType.Undefined => AggregateType.S32,
+ _ => throw new ArgumentException($"Invalid operand type \"{type}\".")
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs
new file mode 100644
index 00000000..541ca298
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs
@@ -0,0 +1,45 @@
+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 not 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);
+
+ srcBlock.Append(copyOp);
+ }
+
+ block.Operations.Remove(node);
+
+ node = nextNode;
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredFunction.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredFunction.cs
new file mode 100644
index 00000000..61c4fed7
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredFunction.cs
@@ -0,0 +1,42 @@
+using Ryujinx.Graphics.Shader.Translation;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class StructuredFunction
+ {
+ public AstBlock MainBlock { get; }
+
+ public string Name { get; }
+
+ public AggregateType ReturnType { get; }
+
+ public AggregateType[] InArguments { get; }
+ public AggregateType[] OutArguments { get; }
+
+ public HashSet<AstOperand> Locals { get; }
+
+ public StructuredFunction(
+ AstBlock mainBlock,
+ string name,
+ AggregateType returnType,
+ AggregateType[] inArguments,
+ AggregateType[] outArguments)
+ {
+ MainBlock = mainBlock;
+ Name = name;
+ ReturnType = returnType;
+ InArguments = inArguments;
+ OutArguments = outArguments;
+
+ Locals = new HashSet<AstOperand>();
+ }
+
+ public AggregateType GetArgumentType(int index)
+ {
+ return index >= InArguments.Length
+ ? OutArguments[index - InArguments.Length]
+ : InArguments[index];
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
new file mode 100644
index 00000000..b4ca8ee5
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
@@ -0,0 +1,421 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ static class StructuredProgram
+ {
+ public static StructuredProgramInfo MakeStructuredProgram(Function[] functions, ShaderConfig config)
+ {
+ StructuredProgramContext context = new StructuredProgramContext(config);
+
+ for (int funcIndex = 0; funcIndex < functions.Length; funcIndex++)
+ {
+ Function function = functions[funcIndex];
+
+ BasicBlock[] blocks = function.Blocks;
+
+ AggregateType returnType = function.ReturnsValue ? AggregateType.S32 : AggregateType.Void;
+
+ AggregateType[] inArguments = new AggregateType[function.InArgumentsCount];
+ AggregateType[] outArguments = new AggregateType[function.OutArgumentsCount];
+
+ for (int i = 0; i < inArguments.Length; i++)
+ {
+ inArguments[i] = AggregateType.S32;
+ }
+
+ for (int i = 0; i < outArguments.Length; i++)
+ {
+ outArguments[i] = AggregateType.S32;
+ }
+
+ context.EnterFunction(blocks.Length, function.Name, returnType, inArguments, outArguments);
+
+ PhiFunctions.Remove(blocks);
+
+ for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
+ {
+ BasicBlock block = blocks[blkIndex];
+
+ context.EnterBlock(block);
+
+ for (LinkedListNode<INode> opNode = block.Operations.First; opNode != null; opNode = opNode.Next)
+ {
+ Operation operation = (Operation)opNode.Value;
+
+ if (IsBranchInst(operation.Inst))
+ {
+ context.LeaveBlock(block, operation);
+ }
+ else
+ {
+ AddOperation(context, operation);
+ }
+ }
+ }
+
+ GotoElimination.Eliminate(context.GetGotos());
+
+ AstOptimizer.Optimize(context);
+
+ context.LeaveFunction();
+ }
+
+ return context.Info;
+ }
+
+ private static void AddOperation(StructuredProgramContext context, Operation operation)
+ {
+ Instruction inst = operation.Inst;
+ StorageKind storageKind = operation.StorageKind;
+
+ if ((inst == Instruction.Load || inst == Instruction.Store) && storageKind.IsInputOrOutput())
+ {
+ IoVariable ioVariable = (IoVariable)operation.GetSource(0).Value;
+ bool isOutput = storageKind.IsOutput();
+ bool perPatch = storageKind.IsPerPatch();
+ int location = 0;
+ int component = 0;
+
+ if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
+ {
+ location = operation.GetSource(1).Value;
+
+ if (operation.SourcesCount > 2 &&
+ operation.GetSource(2).Type == OperandType.Constant &&
+ context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, operation.GetSource(2).Value, isOutput))
+ {
+ component = operation.GetSource(2).Value;
+ }
+ }
+
+ context.Info.IoDefinitions.Add(new IoDefinition(storageKind, ioVariable, location, component));
+ }
+
+ bool vectorDest = IsVectorDestInst(inst);
+
+ int sourcesCount = operation.SourcesCount;
+ int outDestsCount = operation.DestsCount != 0 && !vectorDest ? operation.DestsCount - 1 : 0;
+
+ IAstNode[] sources = new IAstNode[sourcesCount + outDestsCount];
+
+ for (int index = 0; index < operation.SourcesCount; index++)
+ {
+ sources[index] = context.GetOperand(operation.GetSource(index));
+ }
+
+ for (int index = 0; index < outDestsCount; index++)
+ {
+ AstOperand oper = context.GetOperand(operation.GetDest(1 + index));
+
+ oper.VarType = InstructionInfo.GetSrcVarType(inst, sourcesCount + index);
+
+ sources[sourcesCount + index] = oper;
+ }
+
+ AstTextureOperation GetAstTextureOperation(TextureOperation texOp)
+ {
+ return new AstTextureOperation(
+ inst,
+ texOp.Type,
+ texOp.Format,
+ texOp.Flags,
+ texOp.CbufSlot,
+ texOp.Handle,
+ texOp.Index,
+ sources);
+ }
+
+ int componentsCount = BitOperations.PopCount((uint)operation.Index);
+
+ if (vectorDest && componentsCount > 1)
+ {
+ AggregateType destType = InstructionInfo.GetDestVarType(inst);
+
+ IAstNode source;
+
+ if (operation is TextureOperation texOp)
+ {
+ if (texOp.Inst == Instruction.ImageLoad)
+ {
+ destType = texOp.Format.GetComponentType();
+ }
+
+ source = GetAstTextureOperation(texOp);
+ }
+ else
+ {
+ source = new AstOperation(inst, operation.StorageKind, operation.Index, sources, operation.SourcesCount);
+ }
+
+ AggregateType destElemType = destType;
+
+ switch (componentsCount)
+ {
+ case 2: destType |= AggregateType.Vector2; break;
+ case 3: destType |= AggregateType.Vector3; break;
+ case 4: destType |= AggregateType.Vector4; break;
+ }
+
+ AstOperand destVec = context.NewTemp(destType);
+
+ context.AddNode(new AstAssignment(destVec, source));
+
+ for (int i = 0; i < operation.DestsCount; i++)
+ {
+ AstOperand dest = context.GetOperand(operation.GetDest(i));
+ AstOperand index = new AstOperand(OperandType.Constant, i);
+
+ dest.VarType = destElemType;
+
+ context.AddNode(new AstAssignment(dest, new AstOperation(Instruction.VectorExtract, StorageKind.None, new[] { destVec, index }, 2)));
+ }
+ }
+ else if (operation.Dest != null)
+ {
+ AstOperand dest = context.GetOperand(operation.Dest);
+
+ // 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, AggregateType.Bool))
+ {
+ inst = GetLogicalFromBitwiseInst(inst);
+ }
+
+ bool isCondSel = inst == Instruction.ConditionalSelect;
+ bool isCopy = inst == Instruction.Copy;
+
+ if (isCondSel || isCopy)
+ {
+ AggregateType type = GetVarTypeFromUses(operation.Dest);
+
+ if (isCondSel && type == AggregateType.FP32)
+ {
+ inst |= Instruction.FP32;
+ }
+
+ dest.VarType = type;
+ }
+ else
+ {
+ dest.VarType = InstructionInfo.GetDestVarType(inst);
+ }
+
+ IAstNode source;
+
+ if (operation is TextureOperation texOp)
+ {
+ if (texOp.Inst == Instruction.ImageLoad)
+ {
+ dest.VarType = texOp.Format.GetComponentType();
+ }
+
+ source = GetAstTextureOperation(texOp);
+ }
+ else if (!isCopy)
+ {
+ source = new AstOperation(inst, operation.StorageKind, operation.Index, sources, operation.SourcesCount);
+ }
+ else
+ {
+ source = sources[0];
+ }
+
+ context.AddNode(new AstAssignment(dest, source));
+ }
+ else if (operation.Inst == Instruction.Comment)
+ {
+ context.AddNode(new AstComment(((CommentNode)operation).Comment));
+ }
+ else if (operation is TextureOperation texOp)
+ {
+ AstTextureOperation astTexOp = GetAstTextureOperation(texOp);
+
+ context.AddNode(astTexOp);
+ }
+ else
+ {
+ context.AddNode(new AstOperation(inst, operation.StorageKind, operation.Index, sources, operation.SourcesCount));
+ }
+
+ // Those instructions needs to be emulated by using helper functions,
+ // because they are NVIDIA specific. Those flags helps the backend to
+ // decide which helper functions are needed on the final generated code.
+ switch (operation.Inst)
+ {
+ case Instruction.AtomicMaxS32:
+ case Instruction.AtomicMinS32:
+ if (operation.StorageKind == StorageKind.SharedMemory)
+ {
+ context.Info.HelperFunctionsMask |= HelperFunctionsMask.AtomicMinMaxS32Shared;
+ }
+ else if (operation.StorageKind == StorageKind.StorageBuffer)
+ {
+ context.Info.HelperFunctionsMask |= HelperFunctionsMask.AtomicMinMaxS32Storage;
+ }
+ break;
+ case Instruction.MultiplyHighS32:
+ context.Info.HelperFunctionsMask |= HelperFunctionsMask.MultiplyHighS32;
+ break;
+ case Instruction.MultiplyHighU32:
+ context.Info.HelperFunctionsMask |= HelperFunctionsMask.MultiplyHighU32;
+ break;
+ case Instruction.Shuffle:
+ context.Info.HelperFunctionsMask |= HelperFunctionsMask.Shuffle;
+ break;
+ case Instruction.ShuffleDown:
+ context.Info.HelperFunctionsMask |= HelperFunctionsMask.ShuffleDown;
+ break;
+ case Instruction.ShuffleUp:
+ context.Info.HelperFunctionsMask |= HelperFunctionsMask.ShuffleUp;
+ break;
+ case Instruction.ShuffleXor:
+ context.Info.HelperFunctionsMask |= HelperFunctionsMask.ShuffleXor;
+ break;
+ case Instruction.StoreShared16:
+ case Instruction.StoreShared8:
+ context.Info.HelperFunctionsMask |= HelperFunctionsMask.StoreSharedSmallInt;
+ break;
+ case Instruction.StoreStorage16:
+ case Instruction.StoreStorage8:
+ context.Info.HelperFunctionsMask |= HelperFunctionsMask.StoreStorageSmallInt;
+ break;
+ case Instruction.SwizzleAdd:
+ context.Info.HelperFunctionsMask |= HelperFunctionsMask.SwizzleAdd;
+ break;
+ case Instruction.FSIBegin:
+ case Instruction.FSIEnd:
+ context.Info.HelperFunctionsMask |= HelperFunctionsMask.FSI;
+ break;
+ }
+ }
+
+ private static AggregateType 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 not 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 AggregateType.S32;
+ }
+
+ private static bool AreAllSourceTypesEqual(IAstNode[] sources, AggregateType type)
+ {
+ foreach (IAstNode node in sources)
+ {
+ if (node is not AstOperand operand)
+ {
+ return false;
+ }
+
+ if (operand.VarType != type)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static bool IsVectorDestInst(Instruction inst)
+ {
+ return inst switch
+ {
+ Instruction.ImageLoad or
+ Instruction.TextureSample => true,
+ _ => false
+ };
+ }
+
+ private static bool IsBranchInst(Instruction inst)
+ {
+ return inst switch
+ {
+ Instruction.Branch or
+ Instruction.BranchIfFalse or
+ Instruction.BranchIfTrue => true,
+ _ => false
+ };
+ }
+
+ private static bool IsBitwiseInst(Instruction inst)
+ {
+ return inst switch
+ {
+ Instruction.BitwiseAnd or
+ Instruction.BitwiseExclusiveOr or
+ Instruction.BitwiseNot or
+ Instruction.BitwiseOr => true,
+ _ => false
+ };
+ }
+
+ private static Instruction GetLogicalFromBitwiseInst(Instruction inst)
+ {
+ return inst switch
+ {
+ Instruction.BitwiseAnd => Instruction.LogicalAnd,
+ Instruction.BitwiseExclusiveOr => Instruction.LogicalExclusiveOr,
+ Instruction.BitwiseNot => Instruction.LogicalNot,
+ Instruction.BitwiseOr => Instruction.LogicalOr,
+ _ => throw new ArgumentException($"Unexpected instruction \"{inst}\".")
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs
new file mode 100644
index 00000000..68bbdeb1
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs
@@ -0,0 +1,330 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class StructuredProgramContext
+ {
+ private HashSet<BasicBlock> _loopTails;
+
+ private Stack<(AstBlock Block, int CurrEndIndex, int LoopEndIndex)> _blockStack;
+
+ private Dictionary<Operand, AstOperand> _localsMap;
+
+ private Dictionary<int, AstAssignment> _gotoTempAsgs;
+
+ private List<GotoStatement> _gotos;
+
+ private AstBlock _currBlock;
+
+ private int _currEndIndex;
+ private int _loopEndIndex;
+
+ public StructuredFunction CurrentFunction { get; private set; }
+
+ public StructuredProgramInfo Info { get; }
+
+ public ShaderConfig Config { get; }
+
+ public StructuredProgramContext(ShaderConfig config)
+ {
+ Info = new StructuredProgramInfo();
+
+ Config = config;
+
+ if (config.GpPassthrough)
+ {
+ int passthroughAttributes = config.PassthroughAttributes;
+ while (passthroughAttributes != 0)
+ {
+ int index = BitOperations.TrailingZeroCount(passthroughAttributes);
+
+ Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.UserDefined, index));
+
+ passthroughAttributes &= ~(1 << index);
+ }
+
+ Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.Position));
+ Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.PointSize));
+ Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.ClipDistance));
+ }
+ else if (config.Stage == ShaderStage.Fragment)
+ {
+ // Potentially used for texture coordinate scaling.
+ Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.FragmentCoord));
+ }
+ }
+
+ public void EnterFunction(
+ int blocksCount,
+ string name,
+ AggregateType returnType,
+ AggregateType[] inArguments,
+ AggregateType[] outArguments)
+ {
+ _loopTails = new HashSet<BasicBlock>();
+
+ _blockStack = new Stack<(AstBlock, int, int)>();
+
+ _localsMap = new Dictionary<Operand, AstOperand>();
+
+ _gotoTempAsgs = new Dictionary<int, AstAssignment>();
+
+ _gotos = new List<GotoStatement>();
+
+ _currBlock = new AstBlock(AstBlockType.Main);
+
+ _currEndIndex = blocksCount;
+ _loopEndIndex = blocksCount;
+
+ CurrentFunction = new StructuredFunction(_currBlock, name, returnType, inArguments, outArguments);
+ }
+
+ public void LeaveFunction()
+ {
+ Info.Functions.Add(CurrentFunction);
+ }
+
+ public void EnterBlock(BasicBlock block)
+ {
+ while (_currEndIndex == block.Index)
+ {
+ (_currBlock, _currEndIndex, _loopEndIndex) = _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 not a loop, break.
+ if (predecessor.Index < block.Index)
+ {
+ break;
+ }
+
+ // Check if we can create a do-while loop here (only possible if the loop end
+ // falls inside the current scope), if not add a goto instead.
+ if (predecessor.Index < _currEndIndex && !done)
+ {
+ // Create do-while loop block. We must avoid inserting a goto at the end
+ // of the loop later, when the tail block is processed. So we add the predecessor
+ // to a list of loop tails to prevent it from being processed later.
+ Operation branchOp = (Operation)predecessor.GetLastOp();
+
+ NewBlock(AstBlockType.DoWhile, branchOp, predecessor.Index + 1);
+
+ _loopTails.Add(predecessor);
+
+ done = true;
+ }
+ else
+ {
+ // Failed to create loop. Since this block is the loop head, we reset the
+ // goto condition variable here. The variable is always reset on the jump
+ // target, and this block is the jump target for some loop.
+ AddGotoTempReset(block, GetGotoTempAsg(block.Index));
+
+ break;
+ }
+ }
+ }
+
+ private void LookForIfStatements(BasicBlock block, Operation branchOp)
+ {
+ if (block.Branch == null)
+ {
+ return;
+ }
+
+ // We can only enclose the "if" when the branch lands before
+ // the end of the current block. If the current enclosing block
+ // is not a loop, then we can also do so if the branch lands
+ // right at the end of the current block. When it is a loop,
+ // this is not valid as the loop condition would be evaluated,
+ // and it could erroneously jump back to the start of the loop.
+ bool inRange =
+ block.Branch.Index < _currEndIndex ||
+ (block.Branch.Index == _currEndIndex && block.Branch.Index < _loopEndIndex);
+
+ bool isLoop = block.Branch.Index <= block.Index;
+
+ if (inRange && !isLoop)
+ {
+ NewBlock(AstBlockType.If, branchOp, block.Branch.Index);
+ }
+ else if (!_loopTails.Contains(block))
+ {
+ AstAssignment gotoTempAsg = GetGotoTempAsg(block.Branch.Index);
+
+ // We use DoWhile type here, as the condition should be true for
+ // unconditional branches, or it should jump if the condition is true otherwise.
+ 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(AggregateType.Bool);
+
+ gotoTempAsg = Assign(gotoTemp, Const(IrConsts.False));
+
+ _gotoTempAsgs.Add(index, gotoTempAsg);
+
+ return gotoTempAsg;
+ }
+
+ private void AddGotoTempReset(BasicBlock block, AstAssignment gotoTempAsg)
+ {
+ // If it was already added, we don't need to add it again.
+ if (gotoTempAsg.Parent != null)
+ {
+ return;
+ }
+
+ 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)
+ {
+ CurrentFunction.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, _loopEndIndex));
+
+ _currBlock = childBlock;
+ _currEndIndex = endIndex;
+
+ if (type == AstBlockType.DoWhile)
+ {
+ _loopEndIndex = endIndex;
+ }
+ }
+
+ private IAstNode GetBranchCond(AstBlockType type, Operation branchOp)
+ {
+ IAstNode cond;
+
+ if (branchOp.Inst == Instruction.Branch)
+ {
+ // If the branch is not conditional, the condition is a constant.
+ // For if it's false (always jump over, if block never executed).
+ // For loops it's always true (always loop).
+ cond = Const(type == AstBlockType.If ? IrConsts.False : IrConsts.True);
+ }
+ else
+ {
+ cond = GetOperand(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();
+ }
+
+ public AstOperand NewTemp(AggregateType type)
+ {
+ AstOperand newTemp = Local(type);
+
+ CurrentFunction.Locals.Add(newTemp);
+
+ return newTemp;
+ }
+
+ public AstOperand GetOperand(Operand operand)
+ {
+ if (operand == null)
+ {
+ return null;
+ }
+
+ if (operand.Type != OperandType.LocalVariable)
+ {
+ if (operand.Type == OperandType.ConstantBuffer)
+ {
+ Config.SetUsedConstantBuffer(operand.GetCbufSlot());
+ }
+
+ return new AstOperand(operand);
+ }
+
+ if (!_localsMap.TryGetValue(operand, out AstOperand astOperand))
+ {
+ astOperand = new AstOperand(operand);
+
+ _localsMap.Add(operand, astOperand);
+
+ CurrentFunction.Locals.Add(astOperand);
+ }
+
+ return astOperand;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs
new file mode 100644
index 00000000..c5104146
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs
@@ -0,0 +1,36 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ readonly struct TransformFeedbackOutput
+ {
+ public readonly bool Valid;
+ public readonly int Buffer;
+ public readonly int Offset;
+ public readonly int Stride;
+
+ public TransformFeedbackOutput(int buffer, int offset, int stride)
+ {
+ Valid = true;
+ Buffer = buffer;
+ Offset = offset;
+ Stride = stride;
+ }
+ }
+
+ class StructuredProgramInfo
+ {
+ public List<StructuredFunction> Functions { get; }
+
+ public HashSet<IoDefinition> IoDefinitions { get; }
+
+ public HelperFunctionsMask HelperFunctionsMask { get; set; }
+
+ public StructuredProgramInfo()
+ {
+ Functions = new List<StructuredFunction>();
+
+ IoDefinitions = new HashSet<IoDefinition>();
+ }
+ }
+} \ No newline at end of file