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:
parent
973a615d40
commit
49f970d5bd
33 changed files with 1337 additions and 401 deletions
|
@ -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; }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue