feat(analysis): add coverage profiler and report

This commit is contained in:
dualfroz committed 2026-08-25 18:19:10 +02:00
1 parent 6ff06cfe6e
commit c9c9bbe68b
2 files changed
+161

No files matched your search

+69
View File
@@ -0,0 +1,69 @@
using System.Collections.Generic;
using Chip8.Core;
namespace Chip8.Analysis;
public sealed class CoverageProfiler
{
public CoverageProfiler(ushort entry = InstructionDecoder.defaultBaseAddress, ushort baseAddress = InstructionDecoder.defaultBaseAddress)
{
this.entry = entry;
this.baseAddress = baseAddress;
}
public ushort entry { get; }
public ushort baseAddress { get; }
public CoverageReport profile(byte[] program, int maxSteps, Platform platform = Platform.Chip8, Quirks? quirks = null, int seed = 1, bool stopOnSpin = true)
{
var cfg = ControlFlowGraph.build(program, entry, baseAddress);
var reachable = new SortedSet<ushort>(cfg.reachableAddresses);
var machine = new Machine(quirks, seed, platform);
machine.loadRom(program);
var executionCounts = new Dictionary<ushort, int>();
var opcodeHistogram = new Dictionary<ushort, int>();
int steps = 0;
int spinRun = 0;
bool haltedOnSpin = false;
for (int index = 0; index < maxSteps; index++)
{
if (machine.exited)
{
break;
}
ushort address = machine.pc;
ushort opcode = (ushort)((machine.memory[address] << 8) | machine.memory[address + 1]);
increment(executionCounts, address);
increment(opcodeHistogram, opcode);
steps++;
machine.step();
if (stopOnSpin && machine.pc == address)
{
spinRun++;
if (spinRun >= 2)
{
haltedOnSpin = true;
break;
}
}
else
{
spinRun = 0;
}
}
return new CoverageReport(steps, haltedOnSpin, executionCounts, opcodeHistogram, reachable);
}
static void increment(Dictionary<ushort, int> counter, ushort key)
{
counter[key] = counter.TryGetValue(key, out int value) ? value + 1 : 1;
}
}
+92
View File
@@ -0,0 +1,92 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
namespace Chip8.Analysis;
public sealed class CoverageReport
{
readonly Dictionary<ushort, int> executionCounts;
readonly Dictionary<ushort, int> opcodeHistogram;
readonly SortedSet<ushort> reachable;
public CoverageReport(
int totalSteps,
bool haltedOnSpin,
Dictionary<ushort, int> executionCounts,
Dictionary<ushort, int> opcodeHistogram,
SortedSet<ushort> reachable)
{
this.totalSteps = totalSteps;
this.haltedOnSpin = haltedOnSpin;
this.executionCounts = executionCounts;
this.opcodeHistogram = opcodeHistogram;
this.reachable = reachable;
}
public int totalSteps { get; }
public bool haltedOnSpin { get; }
public IReadOnlyDictionary<ushort, int> executionCountByAddress => executionCounts;
public IReadOnlyDictionary<ushort, int> opcodeCounts => opcodeHistogram;
public IReadOnlyCollection<ushort> executedAddresses => executionCounts.Keys;
public IReadOnlyCollection<ushort> reachableAddresses => reachable;
public int executedInstructionCount => executionCounts.Count;
public int reachableInstructionCount => reachable.Count;
public int executionCount(ushort address) =>
executionCounts.TryGetValue(address, out int value) ? value : 0;
public IReadOnlyCollection<ushort> coveredReachableAddresses =>
executionCounts.Keys.Where(address => reachable.Contains(address)).ToList();
public IReadOnlyCollection<ushort> executedOutsideReachable =>
executionCounts.Keys.Where(address => !reachable.Contains(address)).OrderBy(address => address).ToList();
public double coveragePercent
{
get
{
if (reachable.Count == 0)
{
return 0.0;
}
int covered = executionCounts.Keys.Count(address => reachable.Contains(address));
return 100.0 * covered / reachable.Count;
}
}
public IReadOnlyList<ushort> reachableButNotExecuted =>
reachable.Where(address => !executionCounts.ContainsKey(address)).ToList();
public (ushort address, int count) hotspot()
{
ushort best = 0;
int bestCount = -1;
foreach (var pair in executionCounts)
{
if (pair.Value > bestCount)
{
bestCount = pair.Value;
best = pair.Key;
}
}
return (best, bestCount < 0 ? 0 : bestCount);
}
public string renderHeatmap()
{
var builder = new StringBuilder();
builder.Append("coverage: ").Append(coveragePercent.ToString("F1", CultureInfo.InvariantCulture)).Append("% over ");
builder.Append(totalSteps).Append(" steps\n");
foreach (var address in executionCounts.Keys.OrderBy(value => value))
{
builder.Append(" 0x").Append(address.ToString("X4", CultureInfo.InvariantCulture));
builder.Append(" x").Append(executionCounts[address].ToString(CultureInfo.InvariantCulture)).Append('\n');
}
return builder.ToString();
}
}