diff options
| author | TSR Berry <20988865+TSRBerry@users.noreply.github.com> | 2023-04-08 01:22:00 +0200 |
|---|---|---|
| committer | Mary <thog@protonmail.com> | 2023-04-27 23:51:14 +0200 |
| commit | cee712105850ac3385cd0091a923438167433f9f (patch) | |
| tree | 4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.Graphics.Shader/StructuredIr | |
| parent | cd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff) | |
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.Graphics.Shader/StructuredIr')
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 |
