feat(analysis): add instruction info and call graph node types

This commit is contained in:
dualfroz committed 2026-08-24 23:10:57 +02:00
1 parent 95dcacd18a
commit a3e9a99c27
2 files changed
+149

No files matched your search

+47
View File
@@ -0,0 +1,47 @@
using System.Collections.Generic;
using System.Globalization;
namespace Chip8.Analysis;
public sealed class CallNode
{
readonly SortedSet<ushort> calleeSet = new();
readonly SortedSet<ushort> callerSet = new();
public CallNode(ushort address, bool isEntry)
{
this.address = address;
this.isEntry = isEntry;
}
public ushort address { get; }
public bool isEntry { get; }
public bool isRecursive { get; internal set; }
public IReadOnlyCollection<ushort> callees => calleeSet;
public IReadOnlyCollection<ushort> callers => callerSet;
public int outDegree => calleeSet.Count;
public int inDegree => callerSet.Count;
public bool isLeaf => calleeSet.Count == 0;
public bool isSelfRecursive => calleeSet.Contains(address);
internal void addCallee(ushort callee) => calleeSet.Add(callee);
internal void addCaller(ushort caller) => callerSet.Add(caller);
public override string ToString()
{
string label = "sub@0x" + address.ToString("X4", CultureInfo.InvariantCulture);
if (isEntry)
{
label += " (entry)";
}
if (isRecursive)
{
label += " (recursive)";
}
return label;
}
}
+102
View File
@@ -0,0 +1,102 @@
using System.Globalization;
namespace Chip8.Analysis;
public sealed class InstructionInfo
{
public InstructionInfo(
ushort address,
ushort opcode,
ushort? extraWord,
int byteLength,
InstructionKind kind,
InstructionCategory category,
FlowEffect flow,
string mnemonic)
{
this.address = address;
this.opcode = opcode;
this.extraWord = extraWord;
this.byteLength = byteLength;
this.kind = kind;
this.category = category;
this.flow = flow;
this.mnemonic = mnemonic;
fields = new OpcodeFields(opcode);
}
public ushort address { get; }
public ushort opcode { get; }
public ushort? extraWord { get; }
public int byteLength { get; }
public InstructionKind kind { get; }
public InstructionCategory category { get; }
public FlowEffect flow { get; }
public string mnemonic { get; }
public OpcodeFields fields { get; }
public int x => fields.x;
public int y => fields.y;
public int n => fields.n;
public int nn => fields.nn;
public ushort nnn => fields.nnn;
public ushort nextAddress => (ushort)(address + byteLength);
public bool isTerminator =>
flow is FlowEffect.Jump
or FlowEffect.Branch
or FlowEffect.Call
or FlowEffect.Return
or FlowEffect.IndirectJump
or FlowEffect.Terminate;
public bool isBranch => flow == FlowEffect.Branch;
public bool fallsThrough => flow is FlowEffect.Fallthrough or FlowEffect.Branch or FlowEffect.Call;
public bool hasStaticTarget => flow is FlowEffect.Jump or FlowEffect.Call;
public bool writesMemory =>
kind is InstructionKind.StoreRegisters
or InstructionKind.StoreBcd
or InstructionKind.SaveRange;
public bool readsMemory =>
kind is InstructionKind.LoadRegisters or InstructionKind.LoadRange;
public bool loadsIndex => kind is InstructionKind.LoadIndex or InstructionKind.LoadLong;
public ushort? jumpTarget => kind == InstructionKind.Jump ? nnn : null;
public ushort? callTarget => kind == InstructionKind.Call ? nnn : null;
public ushort? indexValue
{
get
{
if (kind == InstructionKind.LoadIndex)
{
return nnn;
}
if (kind == InstructionKind.LoadLong)
{
return extraWord;
}
return null;
}
}
public int? spriteRows => kind == InstructionKind.Draw ? (n == 0 ? 16 : n) : null;
public override string ToString()
{
string body = extraWord.HasValue
? opcode.ToString("X4", CultureInfo.InvariantCulture) + " " + extraWord.Value.ToString("X4", CultureInfo.InvariantCulture)
: opcode.ToString("X4", CultureInfo.InvariantCulture);
return "0x" + address.ToString("X4", CultureInfo.InvariantCulture)
+ " " + body.PadRight(9)
+ " " + mnemonic.PadRight(18)
+ " [" + kind + "/" + flow + "]";
}
}