diff --git a/src/SharpEmu.ShaderCompiler/Ir/IrControlFlow.cs b/src/SharpEmu.ShaderCompiler/Ir/IrControlFlow.cs new file mode 100644 index 00000000..4a3a495d --- /dev/null +++ b/src/SharpEmu.ShaderCompiler/Ir/IrControlFlow.cs @@ -0,0 +1,161 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Collections.Generic; +using System.Linq; + +namespace SharpEmu.ShaderCompiler.Ir; + +public readonly record struct IrBlockRange(uint StartPc, uint EndPc); + +public sealed class IrControlFlowGraph +{ + private IrControlFlowGraph( + IReadOnlyList blocks, + IReadOnlyDictionary blockByStartPc, + IReadOnlyList> successors, + IReadOnlyList> predecessors, + IReadOnlySet loopHeaders) + { + Blocks = blocks; + BlockByStartPc = blockByStartPc; + Successors = successors; + Predecessors = predecessors; + LoopHeaders = loopHeaders; + } + + public IReadOnlyList Blocks { get; } + + public IReadOnlyDictionary BlockByStartPc { get; } + + public IReadOnlyList> Successors { get; } + + public IReadOnlyList> Predecessors { get; } + + public IReadOnlySet LoopHeaders { get; } + + public bool HasControlFlow => Blocks.Count > 1; + + public static IrControlFlowGraph Build( + IReadOnlyList instructions, + IIrBranchResolver resolver) + { + var leaders = new SortedSet(); + if (instructions.Count > 0) + { + leaders.Add(instructions[0].Pc); + } + + for (var index = 0; index < instructions.Count; index++) + { + var instruction = instructions[index]; + if (!resolver.IsBranch(instruction)) + { + continue; + } + + if (resolver.TryGetBranchTarget(instruction, out var target)) + { + leaders.Add(target); + } + + if (index + 1 < instructions.Count) + { + leaders.Add(instructions[index + 1].Pc); + } + } + + var ordered = leaders.ToList(); + var ranges = new List(ordered.Count); + var byStart = new Dictionary(); + for (var index = 0; index < ordered.Count; index++) + { + var start = ordered[index]; + var end = index + 1 < ordered.Count + ? ordered[index + 1] + : instructions.Count > 0 ? instructions[^1].Pc + 1 : start; + byStart[start] = ranges.Count; + ranges.Add(new IrBlockRange(start, end)); + } + + var successors = new List>(ranges.Count); + var predecessors = new List>(ranges.Count); + for (var index = 0; index < ranges.Count; index++) + { + successors.Add([]); + predecessors.Add([]); + } + + 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(); + if (last is null) + { + continue; + } + + var isBranch = resolver.IsBranch(last); + var hasTarget = isBranch && resolver.TryGetBranchTarget(last, out var target) && + byStart.TryGetValue(target, out var targetIndex); + if (hasTarget) + { + _ = resolver.TryGetBranchTarget(last, out var resolved); + Link(successors, predecessors, blockIndex, byStart[resolved]); + } + + var fallsThrough = !isBranch || resolver.IsConditional(last); + if (fallsThrough && blockIndex + 1 < ranges.Count) + { + Link(successors, predecessors, blockIndex, blockIndex + 1); + } + } + + var headers = new HashSet(); + for (var blockIndex = 0; blockIndex < ranges.Count; blockIndex++) + { + foreach (var successor in successors[blockIndex]) + { + if (successor <= blockIndex) + { + headers.Add(successor); + } + } + } + + return new IrControlFlowGraph( + ranges, + byStart, + successors.Select(list => (IReadOnlyList)list).ToList(), + predecessors.Select(list => (IReadOnlyList)list).ToList(), + headers); + } + + private static void Link( + List> successors, + List> predecessors, + int from, + int to) + { + if (!successors[from].Contains(to)) + { + successors[from].Add(to); + } + + if (!predecessors[to].Contains(from)) + { + predecessors[to].Add(from); + } + } +} + +public interface IIrBranchResolver +{ + bool IsBranch(Gen5ShaderInstruction instruction); + + bool IsConditional(Gen5ShaderInstruction instruction); + + bool TryGetBranchTarget(Gen5ShaderInstruction instruction, out uint targetPc); +}