feat(analysis): add ROM analyzer and profile aggregation
This commit is contained in:
1 parent
f7c48098ce
commit
05b0cfafbd
2 files changed
+201
No files matched your search
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user