From 4b210515bfdbf66a5292ae9986e2c6c12dbdbcf7 Mon Sep 17 00:00:00 2001 From: kuba Date: Wed, 30 Sep 2026 10:06:24 +0200 Subject: [PATCH] [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. --- .../Ir/IrControlFlow.cs | 35 +++++- .../Resources/BindingLayout.cs | 13 ++- .../Resources/ResourceBranchBlock.cs | 13 ++- .../Resources/ScalarValueGraph.Builder.cs | 104 ++++++++++++------ 4 files changed, 121 insertions(+), 44 deletions(-) diff --git a/src/SharpEmu.ShaderCompiler/Ir/IrControlFlow.cs b/src/SharpEmu.ShaderCompiler/Ir/IrControlFlow.cs index 4a3a495..0523872 100644 --- a/src/SharpEmu.ShaderCompiler/Ir/IrControlFlow.cs +++ b/src/SharpEmu.ShaderCompiler/Ir/IrControlFlow.cs @@ -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 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 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; diff --git a/src/SharpEmu.ShaderCompiler/Resources/BindingLayout.cs b/src/SharpEmu.ShaderCompiler/Resources/BindingLayout.cs index b803a52..e21c1b8 100644 --- a/src/SharpEmu.ShaderCompiler/Resources/BindingLayout.cs +++ b/src/SharpEmu.ShaderCompiler/Resources/BindingLayout.cs @@ -373,13 +373,14 @@ public sealed class BindingLayout : IEquatable { 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]); } } diff --git a/src/SharpEmu.ShaderCompiler/Resources/ResourceBranchBlock.cs b/src/SharpEmu.ShaderCompiler/Resources/ResourceBranchBlock.cs index b1f6b22..2dd0954 100644 --- a/src/SharpEmu.ShaderCompiler/Resources/ResourceBranchBlock.cs +++ b/src/SharpEmu.ShaderCompiler/Resources/ResourceBranchBlock.cs @@ -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(); + 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 []; diff --git a/src/SharpEmu.ShaderCompiler/Resources/ScalarValueGraph.Builder.cs b/src/SharpEmu.ShaderCompiler/Resources/ScalarValueGraph.Builder.cs index 4db3ae1..5596b6f 100644 --- a/src/SharpEmu.ShaderCompiler/Resources/ScalarValueGraph.Builder.cs +++ b/src/SharpEmu.ShaderCompiler/Resources/ScalarValueGraph.Builder.cs @@ -29,6 +29,10 @@ public sealed partial class ScalarValueGraph private readonly Dictionary _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 _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[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)