feat(analysis): add ROM analyzer and profile aggregation

This commit is contained in:
dualfroz committed 2026-08-25 20:44:57 +02:00
1 parent f7c48098ce
commit 05b0cfafbd
2 files changed
+201

No files matched your search

+116
View File
@@ -0,0 +1,116 @@
using System.Collections.Generic;
using System.Linq;
using Chip8.Core;
namespace Chip8.Analysis;
public sealed class RomAnalyzer
{
static readonly HashSet<InstructionKind> superChipMarkers = new()
{
InstructionKind.ScrollRight,
InstructionKind.ScrollLeft,
InstructionKind.ScrollDown,
InstructionKind.Exit,
InstructionKind.LowRes,
InstructionKind.HiRes,
InstructionKind.LoadBigFont,
InstructionKind.StoreFlags,
InstructionKind.LoadFlags,
};
static readonly HashSet<InstructionKind> xoChipMarkers = new()
{
InstructionKind.LoadLong,
InstructionKind.Plane,
InstructionKind.Audio,
InstructionKind.Pitch,
InstructionKind.SaveRange,
InstructionKind.LoadRange,
InstructionKind.ScrollUp,
};
public RomAnalyzer(ushort entry = InstructionDecoder.defaultBaseAddress, ushort baseAddress = InstructionDecoder.defaultBaseAddress)
{
this.entry = entry;
this.baseAddress = baseAddress;
}
public ushort entry { get; }
public ushort baseAddress { get; }
public RomProfile analyze(byte[] program)
{
var cfg = ControlFlowGraph.build(program, entry, baseAddress);
var callGraph = CallGraph.build(program, entry, baseAddress);
var histogram = new Dictionary<InstructionKind, int>();
var superFeatures = new SortedSet<InstructionKind>();
var xoFeatures = new SortedSet<InstructionKind>();
int drawCount = 0;
int callCount = 0;
int jumpCount = 0;
int skipCount = 0;
foreach (var block in cfg.blocksInOrder)
{
foreach (var info in block.instructions)
{
histogram[info.kind] = histogram.TryGetValue(info.kind, out int value) ? value + 1 : 1;
if (info.kind == InstructionKind.Draw)
{
drawCount++;
if (info.n == 0)
{
superFeatures.Add(InstructionKind.Draw);
}
}
else if (info.kind == InstructionKind.Call)
{
callCount++;
}
else if (info.kind == InstructionKind.Jump)
{
jumpCount++;
}
else if (info.category == InstructionCategory.Skip)
{
skipCount++;
}
if (superChipMarkers.Contains(info.kind))
{
superFeatures.Add(info.kind);
}
if (xoChipMarkers.Contains(info.kind))
{
xoFeatures.Add(info.kind);
}
}
}
bool usesXoChip = xoFeatures.Count > 0;
bool usesSuperChip = superFeatures.Count > 0;
Platform guessed = usesXoChip
? Platform.XoChip
: usesSuperChip ? Platform.SuperChip : Platform.Chip8;
return new RomProfile(
program.Length,
entry,
cfg.instructionCount,
cfg.blockCount,
callGraph.subroutineCount,
drawCount,
callCount,
jumpCount,
skipCount,
usesSuperChip,
usesXoChip,
guessed,
histogram,
superFeatures.ToList(),
xoFeatures.ToList());
}
}
+85
View File
@@ -0,0 +1,85 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using Chip8.Core;
namespace Chip8.Analysis;
public sealed class RomProfile
{
public RomProfile(
int size,
ushort entry,
int instructionCount,
int blockCount,
int subroutineCount,
int drawCount,
int callCount,
int jumpCount,
int skipCount,
bool usesSuperChip,
bool usesXoChip,
Platform guessedPlatform,
IReadOnlyDictionary<InstructionKind, int> kindHistogram,
IReadOnlyList<InstructionKind> superChipFeatures,
IReadOnlyList<InstructionKind> xoChipFeatures)
{
this.size = size;
this.entry = entry;
this.instructionCount = instructionCount;
this.blockCount = blockCount;
this.subroutineCount = subroutineCount;
this.drawCount = drawCount;
this.callCount = callCount;
this.jumpCount = jumpCount;
this.skipCount = skipCount;
this.usesSuperChip = usesSuperChip;
this.usesXoChip = usesXoChip;
this.guessedPlatform = guessedPlatform;
this.kindHistogram = kindHistogram;
this.superChipFeatures = superChipFeatures;
this.xoChipFeatures = xoChipFeatures;
}
public int size { get; }
public ushort entry { get; }
public int instructionCount { get; }
public int blockCount { get; }
public int subroutineCount { get; }
public int drawCount { get; }
public int callCount { get; }
public int jumpCount { get; }
public int skipCount { get; }
public bool usesSuperChip { get; }
public bool usesXoChip { get; }
public Platform guessedPlatform { get; }
public IReadOnlyDictionary<InstructionKind, int> kindHistogram { get; }
public IReadOnlyList<InstructionKind> superChipFeatures { get; }
public IReadOnlyList<InstructionKind> xoChipFeatures { get; }
public int kindCount(InstructionKind kind) =>
kindHistogram.TryGetValue(kind, out int value) ? value : 0;
public string render()
{
var builder = new StringBuilder();
builder.Append("rom profile\n");
builder.Append(" size: ").Append(size).Append(" bytes\n");
builder.Append(" entry: 0x").Append(entry.ToString("X4", CultureInfo.InvariantCulture)).Append('\n');
builder.Append(" instructions: ").Append(instructionCount).Append('\n');
builder.Append(" blocks: ").Append(blockCount).Append('\n');
builder.Append(" subroutines: ").Append(subroutineCount).Append('\n');
builder.Append(" draws: ").Append(drawCount).Append('\n');
builder.Append(" calls: ").Append(callCount).Append('\n');
builder.Append(" jumps: ").Append(jumpCount).Append('\n');
builder.Append(" skips: ").Append(skipCount).Append('\n');
builder.Append(" platform: ").Append(guessedPlatform).Append('\n');
builder.Append(" histogram:\n");
foreach (var pair in kindHistogram.OrderByDescending(entry => entry.Value).ThenBy(entry => entry.Key.ToString()))
{
builder.Append(" ").Append(pair.Key).Append(": ").Append(pair.Value).Append('\n');
}
return builder.ToString();
}
}