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)