Add a branch elimination pass, and misc small fixes
This commit is contained in:
parent
f045885b25
commit
c561235925
18 changed files with 948 additions and 212 deletions
|
@ -23,9 +23,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||||
{
|
{
|
||||||
context.AppendLine("layout (points) in;");
|
context.AppendLine("layout (points) in;");
|
||||||
context.AppendLine("layout (triangle_strip, max_vertices = 4) out;");
|
context.AppendLine("layout (triangle_strip, max_vertices = 4) out;");
|
||||||
}
|
|
||||||
|
|
||||||
context.AppendLine();
|
context.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
context.AppendLine("layout (std140) uniform Extra");
|
context.AppendLine("layout (std140) uniform Extra");
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||||
|
|
||||||
context.EnterScope();
|
context.EnterScope();
|
||||||
|
|
||||||
foreach (IAstNode node in block.Nodes)
|
foreach (IAstNode node in block)
|
||||||
{
|
{
|
||||||
if (node is AstBlock subBlock)
|
if (node is AstBlock subBlock)
|
||||||
{
|
{
|
||||||
|
|
|
@ -145,6 +145,24 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||||
case Instruction.LogarithmB2:
|
case Instruction.LogarithmB2:
|
||||||
return GetUnaryCallExpr(context, operation, "log2");
|
return GetUnaryCallExpr(context, operation, "log2");
|
||||||
|
|
||||||
|
case Instruction.LogicalAnd:
|
||||||
|
return GetBinaryExpr(context, operation, "&&");
|
||||||
|
|
||||||
|
case Instruction.LogicalExclusiveOr:
|
||||||
|
return GetBinaryExpr(context, operation, "^^");
|
||||||
|
|
||||||
|
case Instruction.LogicalNot:
|
||||||
|
return GetUnaryExpr(context, operation, "!");
|
||||||
|
|
||||||
|
case Instruction.LogicalOr:
|
||||||
|
return GetBinaryExpr(context, operation, "||");
|
||||||
|
|
||||||
|
case Instruction.LoopBreak:
|
||||||
|
return "break";
|
||||||
|
|
||||||
|
case Instruction.LoopContinue:
|
||||||
|
return "continue";
|
||||||
|
|
||||||
case Instruction.Maximum:
|
case Instruction.Maximum:
|
||||||
case Instruction.MaximumU32:
|
case Instruction.MaximumU32:
|
||||||
return GetBinaryCallExpr(context, operation, "max");
|
return GetBinaryCallExpr(context, operation, "max");
|
||||||
|
@ -188,7 +206,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||||
return GetUnaryCallExpr(context, operation, "trunc");
|
return GetUnaryCallExpr(context, operation, "trunc");
|
||||||
}
|
}
|
||||||
|
|
||||||
return "// " + operation.Inst;
|
throw new ArgumentException($"Operation has invalid instruction \"{operation.Inst}\".");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetUnaryCallExpr(CodeGenContext context, AstOperation operation, string funcName)
|
private static string GetUnaryCallExpr(CodeGenContext context, AstOperation operation, string funcName)
|
||||||
|
|
|
@ -45,6 +45,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||||
|
|
||||||
formatted = value.ToString("G9", CultureInfo.InvariantCulture);
|
formatted = value.ToString("G9", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
if (!(formatted.Contains('.') ||
|
||||||
|
formatted.Contains('e') ||
|
||||||
|
formatted.Contains('E')))
|
||||||
|
{
|
||||||
|
formatted += ".0";
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,12 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
||||||
LoadGlobal,
|
LoadGlobal,
|
||||||
LoadLocal,
|
LoadLocal,
|
||||||
LogarithmB2,
|
LogarithmB2,
|
||||||
|
LogicalAnd,
|
||||||
|
LogicalExclusiveOr,
|
||||||
|
LogicalNot,
|
||||||
|
LogicalOr,
|
||||||
|
LoopBreak,
|
||||||
|
LoopContinue,
|
||||||
MarkLabel,
|
MarkLabel,
|
||||||
Maximum,
|
Maximum,
|
||||||
MaximumU32,
|
MaximumU32,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
namespace Ryujinx.Graphics.Shader.StructuredIr
|
namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
{
|
{
|
||||||
class AstAssignment : IAstNode
|
class AstAssignment : AstNode
|
||||||
{
|
{
|
||||||
public IAstNode Destination { get; }
|
public IAstNode Destination { get; }
|
||||||
public IAstNode Source { get; }
|
public IAstNode Source { get; }
|
||||||
|
|
|
@ -1,22 +1,86 @@
|
||||||
|
using System;
|
||||||
|
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Shader.StructuredIr
|
namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
{
|
{
|
||||||
class AstBlock : IAstNode
|
class AstBlock : AstNode, IEnumerable<IAstNode>
|
||||||
{
|
{
|
||||||
public AstBlockType Type { get; }
|
public AstBlockType Type { get; }
|
||||||
|
|
||||||
public IAstNode Condition { get; }
|
public IAstNode Condition { get; private set; }
|
||||||
|
|
||||||
public LinkedList<IAstNode> Nodes { get; }
|
private LinkedList<IAstNode> _nodes;
|
||||||
|
|
||||||
|
public IAstNode First => _nodes.First?.Value;
|
||||||
|
public IAstNode Last => _nodes.Last?.Value;
|
||||||
|
|
||||||
public AstBlock(AstBlockType type, IAstNode condition = null)
|
public AstBlock(AstBlockType type, IAstNode condition = null)
|
||||||
{
|
{
|
||||||
Type = type;
|
Type = type;
|
||||||
|
|
||||||
Condition = condition;
|
Condition = condition;
|
||||||
|
|
||||||
Nodes = new LinkedList<IAstNode>();
|
_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 oldNode, IAstNode node)
|
||||||
|
{
|
||||||
|
Add(node, _nodes.AddBefore(oldNode.LLNode, node));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddAfter(IAstNode oldNode, IAstNode node)
|
||||||
|
{
|
||||||
|
Add(node, _nodes.AddAfter(oldNode.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 IEnumerator<IAstNode> GetEnumerator()
|
||||||
|
{
|
||||||
|
return _nodes.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
namespace Ryujinx.Graphics.Shader.StructuredIr
|
namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
{
|
{
|
||||||
class AstDeclaration : IAstNode
|
class AstDeclaration : AstNode
|
||||||
{
|
{
|
||||||
public AstOperand Operand { get; }
|
public AstOperand Operand { get; }
|
||||||
|
|
||||||
|
|
26
Ryujinx.Graphics/Shader/StructuredIr/AstHelper.cs
Normal file
26
Ryujinx.Graphics/Shader/StructuredIr/AstHelper.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
|
{
|
||||||
|
static class AstHelper
|
||||||
|
{
|
||||||
|
public static AstAssignment Assign(IAstNode destination, IAstNode source)
|
||||||
|
{
|
||||||
|
return new AstAssignment(destination, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AstOperand Const(int value)
|
||||||
|
{
|
||||||
|
return new AstOperand(OperandType.Constant, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AstOperand Local(VariableType type)
|
||||||
|
{
|
||||||
|
AstOperand local = new AstOperand(OperandType.LocalVariable);
|
||||||
|
|
||||||
|
local.VarType = type;
|
||||||
|
|
||||||
|
return local;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Ryujinx.Graphics/Shader/StructuredIr/AstNode.cs
Normal file
11
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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Shader.StructuredIr
|
namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
{
|
{
|
||||||
class AstOperand : IAstNode
|
class AstOperand : AstNode
|
||||||
{
|
{
|
||||||
public OperandType Type { get; }
|
public OperandType Type { get; }
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public AstOperand(OperandType type, int value) : this()
|
public AstOperand(OperandType type, int value = 0) : this()
|
||||||
{
|
{
|
||||||
Type = type;
|
Type = type;
|
||||||
Value = value;
|
Value = value;
|
||||||
|
|
|
@ -2,7 +2,7 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Shader.StructuredIr
|
namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
{
|
{
|
||||||
class AstOperation : IAstNode
|
class AstOperation : AstNode
|
||||||
{
|
{
|
||||||
public Instruction Inst { get; }
|
public Instruction Inst { get; }
|
||||||
|
|
||||||
|
|
466
Ryujinx.Graphics/Shader/StructuredIr/GotoElimination.cs
Normal file
466
Ryujinx.Graphics/Shader/StructuredIr/GotoElimination.cs
Normal file
|
@ -0,0 +1,466 @@
|
||||||
|
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
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.Sources.Length == 2)
|
||||||
|
{
|
||||||
|
if (operation.Inst == inst && IsSameCond(operation.Sources[1], newCond))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = operation.Sources[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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Sources[0];
|
||||||
|
rCond = rCondOp.Sources[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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IAstNode InverseCond(IAstNode cond)
|
||||||
|
{
|
||||||
|
return new AstOperation(Instruction.LogicalNot, cond);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IAstNode Next(IAstNode node)
|
||||||
|
{
|
||||||
|
return node.LLNode.Next?.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IAstNode Previous(IAstNode node)
|
||||||
|
{
|
||||||
|
return node.LLNode.Previous?.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
Ryujinx.Graphics/Shader/StructuredIr/GotoStatement.cs
Normal file
23
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,11 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Shader.StructuredIr
|
namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
{
|
{
|
||||||
interface IAstNode
|
interface IAstNode
|
||||||
{
|
{
|
||||||
|
AstBlock Parent { get; set; }
|
||||||
|
|
||||||
|
LinkedListNode<IAstNode> LLNode { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -61,6 +61,10 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
Add(Instruction.IsNan, VariableType.Bool, VariableType.F32);
|
Add(Instruction.IsNan, VariableType.Bool, VariableType.F32);
|
||||||
Add(Instruction.LoadConstant, VariableType.F32, VariableType.S32, VariableType.S32);
|
Add(Instruction.LoadConstant, VariableType.F32, VariableType.S32, VariableType.S32);
|
||||||
Add(Instruction.LogarithmB2, VariableType.Scalar, VariableType.Scalar);
|
Add(Instruction.LogarithmB2, VariableType.Scalar, VariableType.Scalar);
|
||||||
|
Add(Instruction.LogicalAnd, VariableType.Bool, VariableType.Bool, VariableType.Bool);
|
||||||
|
Add(Instruction.LogicalExclusiveOr, VariableType.Bool, VariableType.Bool, VariableType.Bool);
|
||||||
|
Add(Instruction.LogicalNot, VariableType.Bool, VariableType.Bool);
|
||||||
|
Add(Instruction.LogicalOr, VariableType.Bool, VariableType.Bool, VariableType.Bool);
|
||||||
Add(Instruction.ShiftLeft, VariableType.Int, VariableType.Int, VariableType.Int);
|
Add(Instruction.ShiftLeft, VariableType.Int, VariableType.Int, VariableType.Int);
|
||||||
Add(Instruction.ShiftRightS32, VariableType.S32, VariableType.S32, VariableType.Int);
|
Add(Instruction.ShiftRightS32, VariableType.S32, VariableType.S32, VariableType.Int);
|
||||||
Add(Instruction.ShiftRightU32, VariableType.U32, VariableType.U32, VariableType.Int);
|
Add(Instruction.ShiftRightU32, VariableType.U32, VariableType.U32, VariableType.Int);
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Shader.StructuredIr
|
namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
{
|
{
|
||||||
|
@ -8,7 +10,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
{
|
{
|
||||||
PhiFunctions.Remove(blocks);
|
PhiFunctions.Remove(blocks);
|
||||||
|
|
||||||
StructuredProgramContext context = new StructuredProgramContext(blocks);
|
StructuredProgramContext context = new StructuredProgramContext(blocks.Length);
|
||||||
|
|
||||||
for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
|
for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
|
||||||
{
|
{
|
||||||
|
@ -24,6 +26,8 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
context.LeaveBlock(block);
|
context.LeaveBlock(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GotoElimination.Eliminate(context.GetGotos());
|
||||||
|
|
||||||
context.PrependLocalDeclarations();
|
context.PrependLocalDeclarations();
|
||||||
|
|
||||||
return context.Info;
|
return context.Info;
|
||||||
|
@ -33,10 +37,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
{
|
{
|
||||||
Instruction inst = operation.Inst;
|
Instruction inst = operation.Inst;
|
||||||
|
|
||||||
if (operation.Dest != null && !IsBranchInst(inst))
|
|
||||||
{
|
|
||||||
AstOperand dest = context.GetOperandDef(operation.Dest);
|
|
||||||
|
|
||||||
IAstNode[] sources = new IAstNode[operation.SourcesCount];
|
IAstNode[] sources = new IAstNode[operation.SourcesCount];
|
||||||
|
|
||||||
for (int index = 0; index < sources.Length; index++)
|
for (int index = 0; index < sources.Length; index++)
|
||||||
|
@ -44,28 +44,46 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
sources[index] = context.GetOperandUse(operation.GetSource(index));
|
sources[index] = context.GetOperandUse(operation.GetSource(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (operation.Dest != null && !IsBranchInst(inst))
|
||||||
|
{
|
||||||
|
AstOperand dest = context.GetOperandDef(operation.Dest);
|
||||||
|
|
||||||
if (inst == Instruction.LoadConstant)
|
if (inst == Instruction.LoadConstant)
|
||||||
{
|
{
|
||||||
context.Info.ConstantBuffers.Add((sources[0] as AstOperand).Value);
|
context.Info.ConstantBuffers.Add((sources[0] as AstOperand).Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
AstAssignment astAsg;
|
AstAssignment assignment;
|
||||||
|
|
||||||
if (inst == Instruction.Copy)
|
//If all the sources are bool, it's better to use short-circuiting
|
||||||
|
//logical operations, rather than forcing a cast to int and doing
|
||||||
|
//a bitwise operation with the value, as it is likely to be used as
|
||||||
|
//a bool in the end.
|
||||||
|
if (IsBitwiseInst(inst) && AreAllSourceTypesEqual(sources, VariableType.Bool))
|
||||||
{
|
{
|
||||||
//Copies are pretty much a typeless operation,
|
inst = GetLogicalFromBitwiseInst(inst);
|
||||||
//so it's better to get the type from the source
|
}
|
||||||
//operand used on the copy, to avoid unnecessary
|
|
||||||
//reinterpret casts on the generated code.
|
|
||||||
dest.VarType = GetVarTypeFromUses(operation.Dest);
|
|
||||||
|
|
||||||
astAsg = new AstAssignment(dest, sources[0]);
|
bool isCondSel = inst == Instruction.ConditionalSelect;
|
||||||
|
bool isCopy = inst == Instruction.Copy;
|
||||||
|
|
||||||
|
if (isCondSel || isCopy)
|
||||||
|
{
|
||||||
|
VariableType type = GetVarTypeFromUses(operation.Dest);
|
||||||
|
|
||||||
|
if (isCondSel && type == VariableType.F32)
|
||||||
|
{
|
||||||
|
inst |= Instruction.FP;
|
||||||
|
}
|
||||||
|
|
||||||
|
dest.VarType = type;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dest.VarType = InstructionInfo.GetDestVarType(inst);
|
dest.VarType = InstructionInfo.GetDestVarType(inst);
|
||||||
|
}
|
||||||
|
|
||||||
AstOperation astOperation;
|
IAstNode source;
|
||||||
|
|
||||||
if (operation is TextureOperation texOp)
|
if (operation is TextureOperation texOp)
|
||||||
{
|
{
|
||||||
|
@ -76,7 +94,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
|
|
||||||
int[] components = new int[] { texOp.ComponentIndex };
|
int[] components = new int[] { texOp.ComponentIndex };
|
||||||
|
|
||||||
astOperation = new AstTextureOperation(
|
source = new AstTextureOperation(
|
||||||
inst,
|
inst,
|
||||||
texOp.Type,
|
texOp.Type,
|
||||||
texOp.Flags,
|
texOp.Flags,
|
||||||
|
@ -84,24 +102,100 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
components,
|
components,
|
||||||
sources);
|
sources);
|
||||||
}
|
}
|
||||||
else
|
else if (!isCopy)
|
||||||
{
|
{
|
||||||
astOperation = new AstOperation(inst, sources);
|
source = new AstOperation(inst, sources);
|
||||||
}
|
|
||||||
|
|
||||||
astAsg = new AstAssignment(dest, astOperation);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.AddNode(astAsg);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//If dest is null, it's assumed that all the source
|
source = sources[0];
|
||||||
//operands are also null.
|
|
||||||
AstOperation astOperation = new AstOperation(inst);
|
|
||||||
|
|
||||||
context.AddNode(astOperation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assignment = new AstAssignment(dest, source);
|
||||||
|
|
||||||
|
context.AddNode(assignment);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.AddNode(new AstOperation(inst, sources));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static VariableType GetVarTypeFromUses(Operand dest)
|
||||||
|
{
|
||||||
|
HashSet<Operand> visited = new HashSet<Operand>();
|
||||||
|
|
||||||
|
Queue<Operand> pending = new Queue<Operand>();
|
||||||
|
|
||||||
|
bool Enqueue(Operand operand)
|
||||||
|
{
|
||||||
|
if (visited.Add(operand))
|
||||||
|
{
|
||||||
|
pending.Enqueue(operand);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Enqueue(dest);
|
||||||
|
|
||||||
|
while (pending.TryDequeue(out Operand operand))
|
||||||
|
{
|
||||||
|
foreach (INode useNode in operand.UseOps)
|
||||||
|
{
|
||||||
|
if (!(useNode is Operation operation))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation.Inst == Instruction.Copy)
|
||||||
|
{
|
||||||
|
if (operation.Dest.Type == OperandType.LocalVariable)
|
||||||
|
{
|
||||||
|
if (Enqueue(operation.Dest))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return OperandInfo.GetVarType(operation.Dest.Type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int index = 0; index < operation.SourcesCount; index++)
|
||||||
|
{
|
||||||
|
if (operation.GetSource(index) == operand)
|
||||||
|
{
|
||||||
|
return InstructionInfo.GetSrcVarType(operation.Inst, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return VariableType.S32;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool AreAllSourceTypesEqual(IAstNode[] sources, VariableType type)
|
||||||
|
{
|
||||||
|
foreach (IAstNode node in sources)
|
||||||
|
{
|
||||||
|
if (!(node is AstOperand operand))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operand.VarType != type)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsBranchInst(Instruction inst)
|
private static bool IsBranchInst(Instruction inst)
|
||||||
|
@ -117,34 +211,31 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static VariableType GetVarTypeFromUses(Operand dest)
|
private static bool IsBitwiseInst(Instruction inst)
|
||||||
{
|
{
|
||||||
foreach (INode useNode in dest.UseOps)
|
switch (inst)
|
||||||
{
|
{
|
||||||
if (useNode is Operation operation)
|
case Instruction.BitwiseAnd:
|
||||||
{
|
case Instruction.BitwiseExclusiveOr:
|
||||||
if (operation.Inst == Instruction.Copy)
|
case Instruction.BitwiseNot:
|
||||||
{
|
case Instruction.BitwiseOr:
|
||||||
if (operation.Dest.Type == OperandType.LocalVariable)
|
return true;
|
||||||
{
|
|
||||||
//TODO: Search through copies.
|
|
||||||
return VariableType.S32;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return OperandInfo.GetVarType(operation.Dest.Type);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int index = 0; index < operation.SourcesCount; index++)
|
private static Instruction GetLogicalFromBitwiseInst(Instruction inst)
|
||||||
{
|
{
|
||||||
if (operation.GetSource(index) == dest)
|
switch (inst)
|
||||||
{
|
{
|
||||||
return InstructionInfo.GetSrcVarType(operation.Inst, index);
|
case Instruction.BitwiseAnd: return Instruction.LogicalAnd;
|
||||||
}
|
case Instruction.BitwiseExclusiveOr: return Instruction.LogicalExclusiveOr;
|
||||||
}
|
case Instruction.BitwiseNot: return Instruction.LogicalNot;
|
||||||
}
|
case Instruction.BitwiseOr: return Instruction.LogicalOr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return VariableType.S32;
|
throw new ArgumentException($"Unexpected instruction \"{inst}\".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,17 +1,24 @@
|
||||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
|
using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Shader.StructuredIr
|
namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
{
|
{
|
||||||
class StructuredProgramContext
|
class StructuredProgramContext
|
||||||
{
|
{
|
||||||
private BasicBlock[] _blocks;
|
private HashSet<BasicBlock> _loopTails;
|
||||||
|
|
||||||
private List<(AstBlock Block, int EndIndex)> _blockStack;
|
private Stack<(AstBlock Block, int EndIndex)> _blockStack;
|
||||||
|
|
||||||
private Dictionary<Operand, AstOperand> _locals;
|
private Dictionary<Operand, AstOperand> _localsMap;
|
||||||
|
|
||||||
|
private List<AstOperand> _locals;
|
||||||
|
|
||||||
|
private Dictionary<int, AstAssignment> _gotoTempAsgs;
|
||||||
|
|
||||||
|
private List<GotoStatement> _gotos;
|
||||||
|
|
||||||
private AstBlock _currBlock;
|
private AstBlock _currBlock;
|
||||||
|
|
||||||
|
@ -19,17 +26,23 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
|
|
||||||
public StructuredProgramInfo Info { get; }
|
public StructuredProgramInfo Info { get; }
|
||||||
|
|
||||||
public StructuredProgramContext(BasicBlock[] blocks)
|
public StructuredProgramContext(int blocksCount)
|
||||||
{
|
{
|
||||||
_blocks = blocks;
|
_loopTails = new HashSet<BasicBlock>();
|
||||||
|
|
||||||
_blockStack = new List<(AstBlock, int)>();
|
_blockStack = new Stack<(AstBlock, int)>();
|
||||||
|
|
||||||
_locals = new Dictionary<Operand, AstOperand>();
|
_localsMap = new Dictionary<Operand, AstOperand>();
|
||||||
|
|
||||||
|
_locals = new List<AstOperand>();
|
||||||
|
|
||||||
|
_gotoTempAsgs = new Dictionary<int, AstAssignment>();
|
||||||
|
|
||||||
|
_gotos = new List<GotoStatement>();
|
||||||
|
|
||||||
_currBlock = new AstBlock(AstBlockType.Main);
|
_currBlock = new AstBlock(AstBlockType.Main);
|
||||||
|
|
||||||
_currEndIndex = blocks.Length;
|
_currEndIndex = blocksCount;
|
||||||
|
|
||||||
Info = new StructuredProgramInfo(_currBlock);
|
Info = new StructuredProgramInfo(_currBlock);
|
||||||
}
|
}
|
||||||
|
@ -38,7 +51,12 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
{
|
{
|
||||||
while (_currEndIndex == block.Index)
|
while (_currEndIndex == block.Index)
|
||||||
{
|
{
|
||||||
PopBlock();
|
(_currBlock, _currEndIndex) = _blockStack.Pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_gotoTempAsgs.TryGetValue(block.Index, out AstAssignment gotoTempAsg))
|
||||||
|
{
|
||||||
|
AddGotoTempReset(block, gotoTempAsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
LookForDoWhileStatements(block);
|
LookForDoWhileStatements(block);
|
||||||
|
@ -46,13 +64,15 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
|
|
||||||
public void LeaveBlock(BasicBlock block)
|
public void LeaveBlock(BasicBlock block)
|
||||||
{
|
{
|
||||||
LookForIfElseStatements(block);
|
LookForIfStatements(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LookForDoWhileStatements(BasicBlock block)
|
private void LookForDoWhileStatements(BasicBlock block)
|
||||||
{
|
{
|
||||||
//Check if we have any predecessor whose index is greater than the
|
//Check if we have any predecessor whose index is greater than the
|
||||||
//current block, this indicates a loop.
|
//current block, this indicates a loop.
|
||||||
|
bool done = false;
|
||||||
|
|
||||||
foreach (BasicBlock predecessor in block.Predecessors.OrderByDescending(x => x.Index))
|
foreach (BasicBlock predecessor in block.Predecessors.OrderByDescending(x => x.Index))
|
||||||
{
|
{
|
||||||
if (predecessor.Index < block.Index)
|
if (predecessor.Index < block.Index)
|
||||||
|
@ -60,183 +80,177 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (predecessor.Index < _currEndIndex)
|
if (predecessor.Index < _currEndIndex && !done)
|
||||||
{
|
{
|
||||||
Operation branchOp = (Operation)predecessor.GetLastOp();
|
Operation branchOp = (Operation)predecessor.GetLastOp();
|
||||||
|
|
||||||
NewBlock(AstBlockType.DoWhile, branchOp, predecessor.Index + 1);
|
NewBlock(AstBlockType.DoWhile, branchOp, predecessor.Index + 1);
|
||||||
|
|
||||||
|
_loopTails.Add(predecessor);
|
||||||
|
|
||||||
|
done = true;
|
||||||
}
|
}
|
||||||
else /* if (predecessor.Index >= _currEndIndex) */
|
else
|
||||||
{
|
{
|
||||||
//TODO: Handle unstructured case.
|
AddGotoTempReset(block, GetGotoTempAsg(block.Index));
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LookForIfElseStatements(BasicBlock block)
|
private void LookForIfStatements(BasicBlock block)
|
||||||
{
|
{
|
||||||
if (block.Branch == null || block.Branch.Index <= block.Index)
|
if (block.Branch == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation branchOp = (Operation)block.GetLastOp();
|
Operation branchOp = (Operation)block.GetLastOp();
|
||||||
|
|
||||||
if (block.Branch.Index <= _currEndIndex)
|
bool isLoop = block.Branch.Index <= block.Index;
|
||||||
|
|
||||||
|
AstOperation branch = _currBlock.Last as AstOperation;
|
||||||
|
|
||||||
|
if (block.Branch.Index <= _currEndIndex && !isLoop)
|
||||||
{
|
{
|
||||||
//If (conditional branch forward).
|
_currBlock.Remove(branch);
|
||||||
|
|
||||||
NewBlock(AstBlockType.If, branchOp, block.Branch.Index);
|
NewBlock(AstBlockType.If, branchOp, block.Branch.Index);
|
||||||
}
|
}
|
||||||
else if (IsElseSkipBlock(block))
|
else if (_loopTails.Contains(block))
|
||||||
{
|
{
|
||||||
//Else (unconditional branch forward).
|
//Loop handled by "LookForDoWhileStatements".
|
||||||
int topBlockIndex = TopBlockIndexOnStack();
|
//We can safely remove the branch as it was already taken care of.
|
||||||
|
_currBlock.Remove(branch);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AstAssignment gotoTempAsg = GetGotoTempAsg(block.Branch.Index);
|
||||||
|
|
||||||
//We need to pop enough elements so that the one at
|
IAstNode cond = GetBranchCond(AstBlockType.DoWhile, branchOp);
|
||||||
//"topBlockIndex" is the last one poped from the stack.
|
|
||||||
while (_blockStack.Count > topBlockIndex)
|
|
||||||
{
|
|
||||||
PopBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
NewBlock(AstBlockType.Else, branchOp, block.Branch.Index);
|
_currBlock.AddBefore(branch, Assign(gotoTempAsg.Destination, cond));
|
||||||
}
|
|
||||||
else if (IsElseSkipBlock(_blocks[_currEndIndex - 1]) && block.Branch == _blocks[_currEndIndex - 1].Branch)
|
GotoStatement gotoStmt = new GotoStatement(branch, gotoTempAsg, isLoop);
|
||||||
{
|
|
||||||
//If (conditional branch forward).
|
_gotos.Add(gotoStmt);
|
||||||
NewBlock(AstBlockType.If, branchOp, _currEndIndex);
|
|
||||||
}
|
|
||||||
else if (block.Branch.Index > _currEndIndex)
|
|
||||||
{
|
|
||||||
//TODO: Handle unstructured case.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsElseSkipBlock(BasicBlock block)
|
private AstAssignment GetGotoTempAsg(int index)
|
||||||
{
|
{
|
||||||
//Checks performed (in order):
|
if (_gotoTempAsgs.TryGetValue(index, out AstAssignment gotoTempAsg))
|
||||||
//- The block should end with a branch.
|
|
||||||
//- The branch should be unconditional.
|
|
||||||
//- This should be the last block on the current (if) statement.
|
|
||||||
//- The statement before the else must be an if statement.
|
|
||||||
//- The branch target must be before or at (but not after) the end of the enclosing block.
|
|
||||||
if (block.Branch == null || block.Next != null || block.Index + 1 != _currEndIndex)
|
|
||||||
{
|
{
|
||||||
return false;
|
return gotoTempAsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
(AstBlock parentBlock, int parentEndIndex) = _blockStack[TopBlockIndexOnStack()];
|
AstOperand gotoTemp = NewTemp(VariableType.Bool);
|
||||||
|
|
||||||
if ((parentBlock.Nodes.Last.Value as AstBlock).Type != AstBlockType.If)
|
gotoTempAsg = Assign(gotoTemp, Const(IrConsts.False));
|
||||||
{
|
|
||||||
return false;
|
_gotoTempAsgs.Add(index, gotoTempAsg);
|
||||||
|
|
||||||
|
return gotoTempAsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
return block.Branch.Index <= parentEndIndex;
|
private void AddGotoTempReset(BasicBlock block, AstAssignment gotoTempAsg)
|
||||||
|
{
|
||||||
|
AddNode(gotoTempAsg);
|
||||||
|
|
||||||
|
//For block 0, we don't need to add the extra "reset" at the beggining,
|
||||||
|
//because it is already the first node to be executed on the shader,
|
||||||
|
//so it is reset to false by the "local" assignment anyway.
|
||||||
|
if (block.Index != 0)
|
||||||
|
{
|
||||||
|
Info.MainBlock.AddFirst(Assign(gotoTempAsg.Destination, Const(IrConsts.False)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NewBlock(AstBlockType type, Operation branchOp, int endIndex)
|
private void NewBlock(AstBlockType type, Operation branchOp, int endIndex)
|
||||||
{
|
{
|
||||||
Instruction inst = branchOp.Inst;
|
NewBlock(type, GetBranchCond(type, branchOp), endIndex);
|
||||||
|
|
||||||
if (type == AstBlockType.If)
|
|
||||||
{
|
|
||||||
//For ifs, the if block is only executed executed if the
|
|
||||||
//branch is not taken, that is, if the condition is false.
|
|
||||||
//So, we invert the condition to take that into account.
|
|
||||||
if (inst == Instruction.BranchIfFalse)
|
|
||||||
{
|
|
||||||
inst = Instruction.BranchIfTrue;
|
|
||||||
}
|
|
||||||
else if (inst == Instruction.BranchIfTrue)
|
|
||||||
{
|
|
||||||
inst = Instruction.BranchIfFalse;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IAstNode cond;
|
private void NewBlock(AstBlockType type, IAstNode cond, int endIndex)
|
||||||
|
|
||||||
switch (inst)
|
|
||||||
{
|
{
|
||||||
case Instruction.Branch:
|
|
||||||
cond = new AstOperand(OperandType.Constant, IrConsts.True);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Instruction.BranchIfFalse:
|
|
||||||
cond = new AstOperation(Instruction.BitwiseNot, GetOperandUse(branchOp.GetSource(0)));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Instruction.BranchIfTrue:
|
|
||||||
cond = GetOperandUse(branchOp.GetSource(0));
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: throw new InvalidOperationException($"Invalid branch instruction \"{branchOp.Inst}\".");
|
|
||||||
}
|
|
||||||
|
|
||||||
AstBlock childBlock = new AstBlock(type, cond);
|
AstBlock childBlock = new AstBlock(type, cond);
|
||||||
|
|
||||||
AddNode(childBlock);
|
AddNode(childBlock);
|
||||||
|
|
||||||
PushBlock();
|
_blockStack.Push((_currBlock, _currEndIndex));
|
||||||
|
|
||||||
_currBlock = childBlock;
|
_currBlock = childBlock;
|
||||||
|
|
||||||
_currEndIndex = endIndex;
|
_currEndIndex = endIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IAstNode GetBranchCond(AstBlockType type, Operation branchOp)
|
||||||
|
{
|
||||||
|
IAstNode cond;
|
||||||
|
|
||||||
|
if (branchOp.Inst == Instruction.Branch)
|
||||||
|
{
|
||||||
|
cond = Const(type == AstBlockType.If ? IrConsts.False : IrConsts.True);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cond = GetOperandUse(branchOp.GetSource(0));
|
||||||
|
|
||||||
|
Instruction invInst = type == AstBlockType.If
|
||||||
|
? Instruction.BranchIfTrue
|
||||||
|
: Instruction.BranchIfFalse;
|
||||||
|
|
||||||
|
if (branchOp.Inst == invInst)
|
||||||
|
{
|
||||||
|
cond = new AstOperation(Instruction.LogicalNot, cond);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cond;
|
||||||
|
}
|
||||||
|
|
||||||
public void AddNode(IAstNode node)
|
public void AddNode(IAstNode node)
|
||||||
{
|
{
|
||||||
_currBlock.Nodes.AddLast(node);
|
_currBlock.Add(node);
|
||||||
}
|
|
||||||
|
|
||||||
private void PushBlock()
|
|
||||||
{
|
|
||||||
_blockStack.Add((_currBlock, _currEndIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PopBlock()
|
|
||||||
{
|
|
||||||
int lastIndex = _blockStack.Count - 1;
|
|
||||||
|
|
||||||
(_currBlock, _currEndIndex) = _blockStack[lastIndex];
|
|
||||||
|
|
||||||
_blockStack.RemoveAt(lastIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int TopBlockIndexOnStack()
|
|
||||||
{
|
|
||||||
for (int index = _blockStack.Count - 1; index >= 0; index--)
|
|
||||||
{
|
|
||||||
if (_blockStack[index].EndIndex > _currEndIndex)
|
|
||||||
{
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PrependLocalDeclarations()
|
public void PrependLocalDeclarations()
|
||||||
{
|
{
|
||||||
AstBlock mainBlock = Info.MainBlock;
|
AstBlock mainBlock = Info.MainBlock;
|
||||||
|
|
||||||
LinkedListNode<IAstNode> declNode = null;
|
AstDeclaration decl = null;
|
||||||
|
|
||||||
foreach (AstOperand operand in _locals.Values)
|
foreach (AstOperand operand in _locals)
|
||||||
{
|
{
|
||||||
AstDeclaration astDecl = new AstDeclaration(operand);
|
AstDeclaration oldDecl = decl;
|
||||||
|
|
||||||
if (declNode == null)
|
decl = new AstDeclaration(operand);
|
||||||
|
|
||||||
|
if (oldDecl == null)
|
||||||
{
|
{
|
||||||
declNode = mainBlock.Nodes.AddFirst(astDecl);
|
mainBlock.AddFirst(decl);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
declNode = mainBlock.Nodes.AddAfter(declNode, astDecl);
|
mainBlock.AddAfter(oldDecl, decl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GotoStatement[] GetGotos()
|
||||||
|
{
|
||||||
|
return _gotos.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AstOperand NewTemp(VariableType type)
|
||||||
|
{
|
||||||
|
AstOperand newTemp = Local(type);
|
||||||
|
|
||||||
|
_locals.Add(newTemp);
|
||||||
|
|
||||||
|
return newTemp;
|
||||||
|
}
|
||||||
|
|
||||||
public AstOperand GetOperandDef(Operand operand)
|
public AstOperand GetOperandDef(Operand operand)
|
||||||
{
|
{
|
||||||
if (TryGetUserAttributeIndex(operand, out int attrIndex))
|
if (TryGetUserAttributeIndex(operand, out int attrIndex))
|
||||||
|
@ -273,11 +287,13 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
return new AstOperand(operand);
|
return new AstOperand(operand);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_locals.TryGetValue(operand, out AstOperand astOperand))
|
if (!_localsMap.TryGetValue(operand, out AstOperand astOperand))
|
||||||
{
|
{
|
||||||
astOperand = new AstOperand(operand);
|
astOperand = new AstOperand(operand);
|
||||||
|
|
||||||
_locals.Add(operand, astOperand);
|
_localsMap.Add(operand, astOperand);
|
||||||
|
|
||||||
|
_locals.Add(astOperand);
|
||||||
}
|
}
|
||||||
|
|
||||||
return astOperand;
|
return astOperand;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue