mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2025-06-28 15:00:48 -07:00
Move solution and projects to src
This commit is contained in:
35
src/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs
Normal file
35
src/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
117
src/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs
Normal file
117
src/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
12
src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs
Normal file
12
src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
{
|
||||
enum AstBlockType
|
||||
{
|
||||
DoWhile,
|
||||
If,
|
||||
Else,
|
||||
ElseIf,
|
||||
Main,
|
||||
While
|
||||
}
|
||||
}
|
68
src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs
Normal file
68
src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
src/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs
Normal file
12
src/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
{
|
||||
class AstComment : AstNode
|
||||
{
|
||||
public string Comment { get; }
|
||||
|
||||
public AstComment(string comment)
|
||||
{
|
||||
Comment = comment;
|
||||
}
|
||||
}
|
||||
}
|
74
src/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs
Normal file
74
src/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
11
src/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs
Normal file
11
src/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs
Normal file
@ -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; }
|
||||
}
|
||||
}
|
50
src/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs
Normal file
50
src/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
80
src/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs
Normal file
80
src/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
155
src/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs
Normal file
155
src/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
459
src/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs
Normal file
459
src/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
23
src/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs
Normal file
23
src/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
11
src/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs
Normal file
11
src/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
{
|
||||
interface IAstNode
|
||||
{
|
||||
AstBlock Parent { get; set; }
|
||||
|
||||
LinkedListNode<IAstNode> LLNode { get; set; }
|
||||
}
|
||||
}
|
216
src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs
Normal file
216
src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
44
src/Ryujinx.Graphics.Shader/StructuredIr/IoDefinition.cs
Normal file
44
src/Ryujinx.Graphics.Shader/StructuredIr/IoDefinition.cs
Normal file
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
33
src/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs
Normal file
33
src/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs
Normal file
@ -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}\".")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
45
src/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs
Normal file
45
src/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
421
src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
Normal file
421
src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
Normal file
@ -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}\".")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user