[ShaderCompiler] Find each instruction's block by search and reuse register files
Several passes found a block's instructions by scanning the whole program once per block, and the value graph did it again on every block visit. Blocks are sorted and contiguous, so IrControlFlowGraph.BlockOf finds an instruction's block by binary search and each pass groups the program once. The value graph also allocated two 256-entry register files per block visit. Large Demon's Souls compute shaders visit thousands of blocks, and the collections that caused stopped the renderer for about 2.6 s of its first-frame shader work. Replaced entry and exit states are now reused; a reused state is reset exactly as the constructor would, at the same point, so the undefined values it starts with and the instructions they are charged to are unchanged. The resource graphs and SPIR-V of the game's first 134 programs are identical.
This commit is contained in:
parent
a01cffeb7b
commit
4b210515bf
|
|
@ -36,6 +36,28 @@ public sealed class IrControlFlowGraph
|
|||
|
||||
public bool HasControlFlow => Blocks.Count > 1;
|
||||
|
||||
// Blocks are sorted by start and each ends where the next begins.
|
||||
public int BlockOf(uint pc) => BlockOf(Blocks, pc);
|
||||
|
||||
private static int BlockOf(IReadOnlyList<IrBlockRange> blocks, uint pc)
|
||||
{
|
||||
var low = 0;
|
||||
var high = blocks.Count - 1;
|
||||
while (low <= high)
|
||||
{
|
||||
var middle = low + ((high - low) >> 1);
|
||||
var range = blocks[middle];
|
||||
if (pc < range.StartPc)
|
||||
high = middle - 1;
|
||||
else if (pc >= range.EndPc)
|
||||
low = middle + 1;
|
||||
else
|
||||
return middle;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static IrControlFlowGraph Build(
|
||||
IReadOnlyList<Gen5ShaderInstruction> instructions,
|
||||
IIrBranchResolver resolver)
|
||||
|
|
@ -86,12 +108,17 @@ public sealed class IrControlFlowGraph
|
|||
predecessors.Add([]);
|
||||
}
|
||||
|
||||
var lastByBlock = new Gen5ShaderInstruction?[ranges.Count];
|
||||
foreach (var instruction in instructions)
|
||||
{
|
||||
var block = BlockOf(ranges, instruction.Pc);
|
||||
if (block >= 0)
|
||||
lastByBlock[block] = instruction;
|
||||
}
|
||||
|
||||
for (var blockIndex = 0; blockIndex < ranges.Count; blockIndex++)
|
||||
{
|
||||
var range = ranges[blockIndex];
|
||||
var last = instructions
|
||||
.Where(candidate => candidate.Pc >= range.StartPc && candidate.Pc < range.EndPc)
|
||||
.LastOrDefault();
|
||||
var last = lastByBlock[blockIndex];
|
||||
if (last is null)
|
||||
{
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -373,13 +373,14 @@ public sealed class BindingLayout : IEquatable<BindingLayout>
|
|||
{
|
||||
uses[block] = new bool[ScalarRegisterCount];
|
||||
definitions[block] = new bool[ScalarRegisterCount];
|
||||
var range = controlFlow.Blocks[block];
|
||||
foreach (var instruction in program.Instructions)
|
||||
}
|
||||
|
||||
foreach (var instruction in program.Instructions)
|
||||
{
|
||||
var block = controlFlow.BlockOf(instruction.Pc);
|
||||
if (block >= 0)
|
||||
{
|
||||
if (instruction.Pc >= range.StartPc && instruction.Pc < range.EndPc)
|
||||
{
|
||||
RecordUsesAndDefinitions(instruction, uses[block], definitions[block]);
|
||||
}
|
||||
RecordUsesAndDefinitions(instruction, uses[block], definitions[block]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,17 +18,26 @@ public sealed record ResourceBranchBlock(ScalarValue? Condition, int[] Successor
|
|||
var flow = plan.Graph.ControlFlow;
|
||||
var blocks = new ResourceBranchBlock[flow.Blocks.Count];
|
||||
var hasCondition = false;
|
||||
var lastByBlock = new Gen5ShaderInstruction?[blocks.Length];
|
||||
var instructionPcs = new HashSet<uint>();
|
||||
foreach (var instruction in instructions)
|
||||
{
|
||||
instructionPcs.Add(instruction.Pc);
|
||||
var block = flow.BlockOf(instruction.Pc);
|
||||
if (block >= 0) lastByBlock[block] = instruction;
|
||||
}
|
||||
|
||||
for (var blockIndex = 0; blockIndex < blocks.Length; blockIndex++)
|
||||
{
|
||||
var range = flow.Blocks[blockIndex];
|
||||
var last = instructions.LastOrDefault(instruction => instruction.Pc >= range.StartPc && instruction.Pc < range.EndPc);
|
||||
var last = lastByBlock[blockIndex];
|
||||
if (last is null) return [];
|
||||
var successors = flow.Successors[blockIndex].ToArray();
|
||||
ScalarValue? condition = null;
|
||||
var resolver = Gen5IrBranchResolver.Instance;
|
||||
if (resolver.IsConditional(last) || Gen5IrBranchResolver.IsUnconditionalBranch(last))
|
||||
{
|
||||
if (!resolver.TryGetBranchTarget(last, out var target) || !instructions.Any(instruction => instruction.Pc == target)) return [];
|
||||
if (!resolver.TryGetBranchTarget(last, out var target) || !instructionPcs.Contains(target)) return [];
|
||||
if (resolver.IsConditional(last))
|
||||
{
|
||||
if (blockIndex + 1 >= blocks.Length) return [];
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ public sealed partial class ScalarValueGraph
|
|||
private readonly Dictionary<uint, int> _blockByPc = [];
|
||||
private RegisterState?[] _entry = [];
|
||||
private RegisterState?[] _exit = [];
|
||||
private Gen5ShaderInstruction[][] _blockInstructions = [];
|
||||
// Replaced entry and exit states. A large shader visits thousands of blocks, and
|
||||
// allocating two register files per visit kept the collector stopping the renderer.
|
||||
private readonly Stack<RegisterState> _spareStates = new();
|
||||
private bool _recording;
|
||||
|
||||
public void Run()
|
||||
|
|
@ -37,15 +41,21 @@ public sealed partial class ScalarValueGraph
|
|||
var blockCount = controlFlow.Blocks.Count;
|
||||
_entry = new RegisterState?[blockCount];
|
||||
_exit = new RegisterState?[blockCount];
|
||||
var blockInstructions = new List<Gen5ShaderInstruction>[blockCount];
|
||||
foreach (var instruction in _program.Instructions)
|
||||
{
|
||||
var block = controlFlow.BlockOf(instruction.Pc);
|
||||
if (block >= 0)
|
||||
{
|
||||
_blockByPc[instruction.Pc] = block;
|
||||
(blockInstructions[block] ??= []).Add(instruction);
|
||||
}
|
||||
}
|
||||
|
||||
_blockInstructions = new Gen5ShaderInstruction[blockCount][];
|
||||
for (var block = 0; block < blockCount; block++)
|
||||
{
|
||||
foreach (var instruction in _program.Instructions)
|
||||
{
|
||||
if (instruction.Pc >= controlFlow.Blocks[block].StartPc && instruction.Pc < controlFlow.Blocks[block].EndPc)
|
||||
{
|
||||
_blockByPc[instruction.Pc] = block;
|
||||
}
|
||||
}
|
||||
_blockInstructions[block] = blockInstructions[block]?.ToArray() ?? [];
|
||||
}
|
||||
|
||||
_graph.Accesses = new MemoryAccessBinding?[_graph.Memory.Count];
|
||||
|
|
@ -71,9 +81,19 @@ public sealed partial class ScalarValueGraph
|
|||
}
|
||||
|
||||
var entry = MergePredecessors(block);
|
||||
if (_entry[block] is { } staleEntry)
|
||||
{
|
||||
_spareStates.Push(staleEntry);
|
||||
}
|
||||
|
||||
_entry[block] = entry;
|
||||
var exit = Transfer(block, entry.Clone());
|
||||
var exit = Transfer(block, CopyState(entry));
|
||||
var changed = _exit[block] is not { } previous || !previous.SameAs(exit);
|
||||
if (_exit[block] is { } staleExit)
|
||||
{
|
||||
_spareStates.Push(staleExit);
|
||||
}
|
||||
|
||||
_exit[block] = exit;
|
||||
if (!changed)
|
||||
{
|
||||
|
|
@ -95,11 +115,27 @@ public sealed partial class ScalarValueGraph
|
|||
{
|
||||
if (_entry[block] is { } entry)
|
||||
{
|
||||
Transfer(block, entry.Clone());
|
||||
_spareStates.Push(Transfer(block, CopyState(entry)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A spare state is reset exactly as the constructor would, at the same point, so the
|
||||
// undefined values it starts with (and the instructions they are charged to) match.
|
||||
private RegisterState NewState()
|
||||
{
|
||||
if (!_spareStates.TryPop(out var state))
|
||||
{
|
||||
return new RegisterState(_graph);
|
||||
}
|
||||
|
||||
state.Reset();
|
||||
return state;
|
||||
}
|
||||
|
||||
private RegisterState CopyState(RegisterState source) =>
|
||||
_spareStates.TryPop(out var state) ? state.CopyFrom(source) : source.Clone();
|
||||
|
||||
// Reverse postorder completes acyclic predecessors before their joins.
|
||||
// Loop back edges still use the convergence check in the worklist.
|
||||
private int[] BuildBlockProcessingPriorities()
|
||||
|
|
@ -174,7 +210,7 @@ public sealed partial class ScalarValueGraph
|
|||
}
|
||||
}
|
||||
|
||||
var merged = new RegisterState(_graph);
|
||||
var merged = NewState();
|
||||
if (block == 0)
|
||||
{
|
||||
// The entry block joins the initial registers with its back edges.
|
||||
|
|
@ -281,14 +317,8 @@ public sealed partial class ScalarValueGraph
|
|||
|
||||
private RegisterState Transfer(int block, RegisterState state)
|
||||
{
|
||||
var range = _graph.ControlFlow.Blocks[block];
|
||||
foreach (var instruction in _program.Instructions)
|
||||
foreach (var instruction in _blockInstructions[block])
|
||||
{
|
||||
if (instruction.Pc < range.StartPc || instruction.Pc >= range.EndPc)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Apply(instruction, state);
|
||||
}
|
||||
|
||||
|
|
@ -1681,10 +1711,17 @@ public sealed partial class ScalarValueGraph
|
|||
_graph = graph;
|
||||
Scalars = new ScalarValue[ScalarRegisterCount];
|
||||
Vectors = new ScalarValue[VectorRegisterCount];
|
||||
var undefined = graph.Undefined(ScalarValueType.U32);
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
var undefined = _graph.Undefined(ScalarValueType.U32);
|
||||
Array.Fill(Scalars, undefined);
|
||||
Array.Fill(Vectors, undefined);
|
||||
Exec = graph.Undefined(ScalarValueType.Bool);
|
||||
Lanes.Clear();
|
||||
ThreadBits.Clear();
|
||||
Exec = _graph.Undefined(ScalarValueType.Bool);
|
||||
Vcc = Exec;
|
||||
Scc = Exec;
|
||||
CarryOut = Exec;
|
||||
|
|
@ -1763,26 +1800,29 @@ public sealed partial class ScalarValueGraph
|
|||
}
|
||||
}
|
||||
|
||||
public RegisterState Clone()
|
||||
public RegisterState Clone() => new RegisterState(_graph).CopyFrom(this);
|
||||
|
||||
public RegisterState CopyFrom(RegisterState source)
|
||||
{
|
||||
var clone = new RegisterState(_graph);
|
||||
Array.Copy(Scalars, clone.Scalars, Scalars.Length);
|
||||
Array.Copy(Vectors, clone.Vectors, Vectors.Length);
|
||||
foreach (var (key, value) in Lanes)
|
||||
Array.Copy(source.Scalars, Scalars, Scalars.Length);
|
||||
Array.Copy(source.Vectors, Vectors, Vectors.Length);
|
||||
Lanes.Clear();
|
||||
foreach (var (key, value) in source.Lanes)
|
||||
{
|
||||
clone.Lanes[key] = value;
|
||||
Lanes[key] = value;
|
||||
}
|
||||
|
||||
foreach (var (key, value) in ThreadBits)
|
||||
ThreadBits.Clear();
|
||||
foreach (var (key, value) in source.ThreadBits)
|
||||
{
|
||||
clone.ThreadBits[key] = value;
|
||||
ThreadBits[key] = value;
|
||||
}
|
||||
|
||||
clone.Exec = Exec;
|
||||
clone.Vcc = Vcc;
|
||||
clone.Scc = Scc;
|
||||
clone.CarryOut = CarryOut;
|
||||
return clone;
|
||||
Exec = source.Exec;
|
||||
Vcc = source.Vcc;
|
||||
Scc = source.Scc;
|
||||
CarryOut = source.CarryOut;
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool SameAs(RegisterState other)
|
||||
|
|
|
|||
Loading…
Reference in New Issue