From a3e9a99c27b6058bd248ce48b65d4df71456a2b3 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Mon, 24 Aug 2026 23:10:57 +0200 Subject: [PATCH] feat(analysis): add instruction info and call graph node types --- src/Chip8.Analysis/CallNode.cs | 47 ++++++++++++ src/Chip8.Analysis/InstructionInfo.cs | 102 ++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 src/Chip8.Analysis/CallNode.cs create mode 100644 src/Chip8.Analysis/InstructionInfo.cs diff --git a/src/Chip8.Analysis/CallNode.cs b/src/Chip8.Analysis/CallNode.cs new file mode 100644 index 0000000..dbf5185 --- /dev/null +++ b/src/Chip8.Analysis/CallNode.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Globalization; + +namespace Chip8.Analysis; + +public sealed class CallNode +{ + readonly SortedSet calleeSet = new(); + readonly SortedSet 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 callees => calleeSet; + public IReadOnlyCollection 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; + } +} diff --git a/src/Chip8.Analysis/InstructionInfo.cs b/src/Chip8.Analysis/InstructionInfo.cs new file mode 100644 index 0000000..69e4af7 --- /dev/null +++ b/src/Chip8.Analysis/InstructionInfo.cs @@ -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 + "]"; + } +}