diff --git a/src/Chip8.Analysis/ControlFlowEdge.cs b/src/Chip8.Analysis/ControlFlowEdge.cs new file mode 100644 index 0000000..78d5a1a --- /dev/null +++ b/src/Chip8.Analysis/ControlFlowEdge.cs @@ -0,0 +1,30 @@ +using System.Globalization; + +namespace Chip8.Analysis; + +public enum EdgeKind +{ + Fallthrough, + Jump, + BranchTaken, + Call, +} + +public readonly struct ControlFlowEdge +{ + public ControlFlowEdge(ushort source, ushort target, EdgeKind kind) + { + this.source = source; + this.target = target; + this.kind = kind; + } + + public ushort source { get; } + public ushort target { get; } + public EdgeKind kind { get; } + + public override string ToString() => + "0x" + source.ToString("X4", CultureInfo.InvariantCulture) + + " -> 0x" + target.ToString("X4", CultureInfo.InvariantCulture) + + " (" + kind + ")"; +} diff --git a/src/Chip8.Analysis/ControlFlowGraph.cs b/src/Chip8.Analysis/ControlFlowGraph.cs new file mode 100644 index 0000000..d5e916e --- /dev/null +++ b/src/Chip8.Analysis/ControlFlowGraph.cs @@ -0,0 +1,186 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace Chip8.Analysis; + +public sealed class ControlFlowGraph +{ + readonly Dictionary blockByAddress; + readonly List edgeList; + readonly FlowTraversal traversal; + + ControlFlowGraph( + ushort entry, + ushort baseAddress, + Dictionary blockByAddress, + List edgeList, + FlowTraversal traversal) + { + this.entry = entry; + this.baseAddress = baseAddress; + this.blockByAddress = blockByAddress; + this.edgeList = edgeList; + this.traversal = traversal; + } + + public ushort entry { get; } + public ushort baseAddress { get; } + + public IReadOnlyCollection blocks => blockByAddress.Values; + public IReadOnlyList edges => edgeList; + public IReadOnlyCollection leaders => traversal.leaders; + public IReadOnlyList indirectJumps => traversal.indirectJumps; + public IReadOnlyList externalTargets => traversal.externalTargets; + + public int blockCount => blockByAddress.Count; + public int edgeCount => edgeList.Count; + public int instructionCount => traversal.decoded.Count; + + public IReadOnlyCollection reachableAddresses => traversal.decoded.Keys.ToList(); + + public BasicBlock entryBlock => blockByAddress[entry]; + + public bool hasBlock(ushort address) => blockByAddress.ContainsKey(address); + + public BasicBlock? blockAt(ushort address) => + blockByAddress.TryGetValue(address, out var block) ? block : null; + + public InstructionInfo? instructionAt(ushort address) => + traversal.decoded.TryGetValue(address, out var info) ? info : null; + + public bool isReachable(ushort address) => traversal.decoded.ContainsKey(address); + + public IReadOnlyList blocksInOrder => + blockByAddress.Values.OrderBy(block => block.startAddress).ToList(); + + public HashSet reachableByteAddresses() + { + var bytes = new HashSet(); + foreach (var info in traversal.decoded.Values) + { + for (int offset = 0; offset < info.byteLength; offset++) + { + bytes.Add((ushort)(info.address + offset)); + } + } + return bytes; + } + + public static ControlFlowGraph build(byte[] program, ushort entry = InstructionDecoder.defaultBaseAddress, ushort baseAddress = InstructionDecoder.defaultBaseAddress) + { + var traversal = FlowTraversal.explore(program, entry, baseAddress); + var blockByAddress = partition(program, traversal, baseAddress); + var edgeList = wire(program, blockByAddress, baseAddress); + return new ControlFlowGraph(entry, baseAddress, blockByAddress, edgeList, traversal); + } + + static Dictionary partition(byte[] program, FlowTraversal traversal, ushort baseAddress) + { + var blockByAddress = new Dictionary(); + var decoded = traversal.decoded; + + foreach (ushort leader in traversal.leaders) + { + if (!decoded.ContainsKey(leader)) + { + continue; + } + + var block = new BasicBlock(leader); + ushort address = leader; + while (true) + { + var info = decoded[address]; + block.add(info); + + if (info.isTerminator) + { + break; + } + + ushort next = info.nextAddress; + if (!decoded.ContainsKey(next) || traversal.leaders.Contains(next)) + { + break; + } + address = next; + } + + blockByAddress[leader] = block; + } + + return blockByAddress; + } + + static List wire(byte[] program, Dictionary blockByAddress, ushort baseAddress) + { + var edgeList = new List(); + + foreach (var block in blockByAddress.Values) + { + var last = block.terminator; + switch (last.flow) + { + case FlowEffect.Jump: + link(blockByAddress, edgeList, block, last.jumpTarget!.Value, EdgeKind.Jump); + break; + + case FlowEffect.Branch: + { + ushort notTaken = last.nextAddress; + ushort taken = (ushort)(notTaken + InstructionDecoder.byteLengthAt(program, notTaken, baseAddress)); + link(blockByAddress, edgeList, block, notTaken, EdgeKind.Fallthrough); + link(blockByAddress, edgeList, block, taken, EdgeKind.BranchTaken); + break; + } + + case FlowEffect.Call: + link(blockByAddress, edgeList, block, last.callTarget!.Value, EdgeKind.Call); + link(blockByAddress, edgeList, block, last.nextAddress, EdgeKind.Fallthrough); + break; + + case FlowEffect.Fallthrough: + link(blockByAddress, edgeList, block, last.nextAddress, EdgeKind.Fallthrough); + break; + + case FlowEffect.Return: + case FlowEffect.Terminate: + case FlowEffect.IndirectJump: + break; + } + } + + return edgeList; + } + + static void link( + Dictionary blockByAddress, + List edgeList, + BasicBlock source, + ushort target, + EdgeKind kind) + { + if (!blockByAddress.TryGetValue(target, out var targetBlock)) + { + return; + } + var edge = new ControlFlowEdge(source.startAddress, target, kind); + source.addSuccessor(edge); + targetBlock.addPredecessor(source.startAddress); + edgeList.Add(edge); + } + + public string render() + { + var builder = new StringBuilder(); + builder.Append("control flow graph: entry 0x").Append(entry.ToString("X4", CultureInfo.InvariantCulture)); + builder.Append(", ").Append(blockCount).Append(" blocks, ").Append(edgeCount).Append(" edges\n"); + foreach (var block in blocksInOrder) + { + builder.Append(block.render()); + } + return builder.ToString(); + } +}