Implement CAL and RET shader instructions (#1618)

* Add support for CAL and RET shader instructions

* Remove unused stuff

* Fix a bug that could cause the wrong values to be passed to a function

* Avoid repopulating function id dictionary every time

* PR feedback

* Fix vertex shader A/B merge
This commit is contained in:
gdkchan 2020-10-25 17:00:44 -03:00 committed by GitHub
parent 973a615d40
commit 49f970d5bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 1337 additions and 401 deletions

View file

@ -9,149 +9,172 @@ namespace Ryujinx.Graphics.Shader.Decoders
{
static class Decoder
{
public static Block[] Decode(IGpuAccessor gpuAccessor, ulong startAddress)
public static Block[][] Decode(IGpuAccessor gpuAccessor, ulong startAddress)
{
List<Block> blocks = new List<Block>();
List<Block[]> funcs = new List<Block[]>();
Queue<Block> workQueue = new Queue<Block>();
Queue<ulong> funcQueue = new Queue<ulong>();
HashSet<ulong> funcVisited = new HashSet<ulong>();
Dictionary<ulong, Block> visited = new Dictionary<ulong, Block>();
Block GetBlock(ulong blkAddress)
void EnqueueFunction(ulong funcAddress)
{
if (!visited.TryGetValue(blkAddress, out Block block))
if (funcVisited.Add(funcAddress))
{
block = new Block(blkAddress);
workQueue.Enqueue(block);
visited.Add(blkAddress, block);
}
return block;
}
GetBlock(0);
while (workQueue.TryDequeue(out Block currBlock))
{
// Check if the current block is inside another block.
if (BinarySearch(blocks, currBlock.Address, out int nBlkIndex))
{
Block nBlock = blocks[nBlkIndex];
if (nBlock.Address == currBlock.Address)
{
throw new InvalidOperationException("Found duplicate block address on the list.");
}
nBlock.Split(currBlock);
blocks.Insert(nBlkIndex + 1, currBlock);
continue;
}
// If we have a block after the current one, set the limit address.
ulong limitAddress = ulong.MaxValue;
if (nBlkIndex != blocks.Count)
{
Block nBlock = blocks[nBlkIndex];
int nextIndex = nBlkIndex + 1;
if (nBlock.Address < currBlock.Address && nextIndex < blocks.Count)
{
limitAddress = blocks[nextIndex].Address;
}
else if (nBlock.Address > currBlock.Address)
{
limitAddress = blocks[nBlkIndex].Address;
}
}
FillBlock(gpuAccessor, currBlock, limitAddress, startAddress);
if (currBlock.OpCodes.Count != 0)
{
// We should have blocks for all possible branch targets,
// including those from SSY/PBK instructions.
foreach (OpCodePush pushOp in currBlock.PushOpCodes)
{
GetBlock(pushOp.GetAbsoluteAddress());
}
// Set child blocks. "Branch" is the block the branch instruction
// points to (when taken), "Next" is the block at the next address,
// executed when the branch is not taken. For Unconditional Branches
// or end of program, Next is null.
OpCode lastOp = currBlock.GetLastOp();
if (lastOp is OpCodeBranch opBr)
{
currBlock.Branch = GetBlock(opBr.GetAbsoluteAddress());
}
else if (lastOp is OpCodeBranchIndir opBrIndir)
{
// An indirect branch could go anywhere, we don't know the target.
// Those instructions are usually used on a switch to jump table
// compiler optimization, and in those cases the possible targets
// seems to be always right after the BRX itself. We can assume
// that the possible targets are all the blocks in-between the
// instruction right after the BRX, and the common target that
// all the "cases" should eventually jump to, acting as the
// switch break.
Block firstTarget = GetBlock(currBlock.EndAddress);
firstTarget.BrIndir = opBrIndir;
opBrIndir.PossibleTargets.Add(firstTarget);
}
if (!IsUnconditionalBranch(lastOp))
{
currBlock.Next = GetBlock(currBlock.EndAddress);
}
}
// Insert the new block on the list (sorted by address).
if (blocks.Count != 0)
{
Block nBlock = blocks[nBlkIndex];
blocks.Insert(nBlkIndex + (nBlock.Address < currBlock.Address ? 1 : 0), currBlock);
}
else
{
blocks.Add(currBlock);
}
// Do we have a block after the current one?
if (currBlock.BrIndir != null && HasBlockAfter(gpuAccessor, currBlock, startAddress))
{
bool targetVisited = visited.ContainsKey(currBlock.EndAddress);
Block possibleTarget = GetBlock(currBlock.EndAddress);
currBlock.BrIndir.PossibleTargets.Add(possibleTarget);
if (!targetVisited)
{
possibleTarget.BrIndir = currBlock.BrIndir;
}
funcQueue.Enqueue(funcAddress);
}
}
foreach (Block block in blocks.Where(x => x.PushOpCodes.Count != 0))
funcQueue.Enqueue(0);
while (funcQueue.TryDequeue(out ulong funcAddress))
{
for (int pushOpIndex = 0; pushOpIndex < block.PushOpCodes.Count; pushOpIndex++)
List<Block> blocks = new List<Block>();
Queue<Block> workQueue = new Queue<Block>();
Dictionary<ulong, Block> visited = new Dictionary<ulong, Block>();
Block GetBlock(ulong blkAddress)
{
PropagatePushOp(visited, block, pushOpIndex);
if (!visited.TryGetValue(blkAddress, out Block block))
{
block = new Block(blkAddress);
workQueue.Enqueue(block);
visited.Add(blkAddress, block);
}
return block;
}
GetBlock(funcAddress);
while (workQueue.TryDequeue(out Block currBlock))
{
// Check if the current block is inside another block.
if (BinarySearch(blocks, currBlock.Address, out int nBlkIndex))
{
Block nBlock = blocks[nBlkIndex];
if (nBlock.Address == currBlock.Address)
{
throw new InvalidOperationException("Found duplicate block address on the list.");
}
nBlock.Split(currBlock);
blocks.Insert(nBlkIndex + 1, currBlock);
continue;
}
// If we have a block after the current one, set the limit address.
ulong limitAddress = ulong.MaxValue;
if (nBlkIndex != blocks.Count)
{
Block nBlock = blocks[nBlkIndex];
int nextIndex = nBlkIndex + 1;
if (nBlock.Address < currBlock.Address && nextIndex < blocks.Count)
{
limitAddress = blocks[nextIndex].Address;
}
else if (nBlock.Address > currBlock.Address)
{
limitAddress = blocks[nBlkIndex].Address;
}
}
FillBlock(gpuAccessor, currBlock, limitAddress, startAddress);
if (currBlock.OpCodes.Count != 0)
{
// We should have blocks for all possible branch targets,
// including those from SSY/PBK instructions.
foreach (OpCodePush pushOp in currBlock.PushOpCodes)
{
GetBlock(pushOp.GetAbsoluteAddress());
}
// Set child blocks. "Branch" is the block the branch instruction
// points to (when taken), "Next" is the block at the next address,
// executed when the branch is not taken. For Unconditional Branches
// or end of program, Next is null.
OpCode lastOp = currBlock.GetLastOp();
if (lastOp is OpCodeBranch opBr)
{
if (lastOp.Emitter == InstEmit.Cal)
{
EnqueueFunction(opBr.GetAbsoluteAddress());
}
else
{
currBlock.Branch = GetBlock(opBr.GetAbsoluteAddress());
}
}
else if (lastOp is OpCodeBranchIndir opBrIndir)
{
// An indirect branch could go anywhere, we don't know the target.
// Those instructions are usually used on a switch to jump table
// compiler optimization, and in those cases the possible targets
// seems to be always right after the BRX itself. We can assume
// that the possible targets are all the blocks in-between the
// instruction right after the BRX, and the common target that
// all the "cases" should eventually jump to, acting as the
// switch break.
Block firstTarget = GetBlock(currBlock.EndAddress);
firstTarget.BrIndir = opBrIndir;
opBrIndir.PossibleTargets.Add(firstTarget);
}
if (!IsUnconditionalBranch(lastOp))
{
currBlock.Next = GetBlock(currBlock.EndAddress);
}
}
// Insert the new block on the list (sorted by address).
if (blocks.Count != 0)
{
Block nBlock = blocks[nBlkIndex];
blocks.Insert(nBlkIndex + (nBlock.Address < currBlock.Address ? 1 : 0), currBlock);
}
else
{
blocks.Add(currBlock);
}
// Do we have a block after the current one?
if (currBlock.BrIndir != null && HasBlockAfter(gpuAccessor, currBlock, startAddress))
{
bool targetVisited = visited.ContainsKey(currBlock.EndAddress);
Block possibleTarget = GetBlock(currBlock.EndAddress);
currBlock.BrIndir.PossibleTargets.Add(possibleTarget);
if (!targetVisited)
{
possibleTarget.BrIndir = currBlock.BrIndir;
}
}
}
foreach (Block block in blocks.Where(x => x.PushOpCodes.Count != 0))
{
for (int pushOpIndex = 0; pushOpIndex < block.PushOpCodes.Count; pushOpIndex++)
{
PropagatePushOp(visited, block, pushOpIndex);
}
}
funcs.Add(blocks.ToArray());
}
return blocks.ToArray();
return funcs.ToArray();
}
private static bool HasBlockAfter(IGpuAccessor gpuAccessor, Block currBlock, ulong startAdddress)
@ -251,7 +274,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
block.OpCodes.Add(op);
}
while (!IsBranch(block.GetLastOp()));
while (!IsControlFlowChange(block.GetLastOp()));
block.EndAddress = address;
@ -260,7 +283,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
private static bool IsUnconditionalBranch(OpCode opCode)
{
return IsUnconditional(opCode) && IsBranch(opCode);
return IsUnconditional(opCode) && IsControlFlowChange(opCode);
}
private static bool IsUnconditional(OpCode opCode)
@ -273,7 +296,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
return opCode.Predicate.Index == RegisterConsts.PredicateTrueIndex && !opCode.InvertPredicate;
}
private static bool IsBranch(OpCode opCode)
private static bool IsControlFlowChange(OpCode opCode)
{
return (opCode is OpCodeBranch opBranch && !opBranch.PushTarget) ||
opCode is OpCodeBranchIndir ||
@ -281,11 +304,6 @@ namespace Ryujinx.Graphics.Shader.Decoders
opCode is OpCodeExit;
}
private static bool IsExit(OpCode opCode)
{
return opCode is OpCodeExit;
}
private struct PathBlockState
{
public Block Block { get; }