using System; using System.Collections.Generic; using System.Linq; using Cpp2IL.Core.ISIL; namespace Cpp2IL.Core.Graphs; public class ISILControlFlowGraph { public Block EntryBlock; public Block ExitBlock; public int Count => Blocks.Count; public List Blocks; public List Instructions { get { // BFS search var visited = new HashSet(); var queue = new Queue(); var result = new List(); queue.Enqueue(EntryBlock); while (queue.Count > 0) { var current = queue.Dequeue(); if (!visited.Add(current)) continue; result.AddRange(current.Instructions); foreach (var successor in current.Successors) { if (!visited.Contains(successor)) queue.Enqueue(successor); } } return result; // Should this be cached? } } private int idCounter; public ISILControlFlowGraph(List instructions) { EntryBlock = new Block { ID = idCounter++, BlockType = BlockType.Entry }; ExitBlock = new Block { ID = idCounter++, BlockType = BlockType.Exit }; Blocks = [ EntryBlock, ExitBlock ]; Build(instructions); } private bool TryGetTargetJumpInstructionIndex(Instruction instruction, out int jumpInstructionIndex) { jumpInstructionIndex = 0; try { jumpInstructionIndex = ((Instruction)instruction.Operands[0]).Index; return true; } catch { // ignore } return false; } public void RemoveUnreachableBlocks() { if (Blocks.Count == 0) return; // Get blocks reachable from entry var reachable = new List(); var visited = new List { EntryBlock }; reachable.Add(EntryBlock); var total = 0; while (total < reachable.Count) { var block = reachable[total]; total++; foreach (var successor in block.Successors) { if (visited.Contains(successor)) continue; visited.Add(successor); reachable.Add(successor); } } // Get unreachable blocks var unreachable = Blocks.Where(block => !visited.Remove(block)).ToList(); // Remove those foreach (var block in unreachable) { // Don't remove entry or exit if (block == EntryBlock || block == ExitBlock) continue; // Fully detach the block so no remaining block keeps a dangling reference to it. // (A reachable block can have an unreachable predecessor; leaving that reference // behind makes later passes such as dominator computation throw.) foreach (var successor in block.Successors) successor.Predecessors.Remove(block); foreach (var predecessor in block.Predecessors) predecessor.Successors.Remove(block); block.Successors.Clear(); block.Predecessors.Clear(); Blocks.Remove(block); } } public void RemoveNops() { var usedAsTarget = new HashSet(); // Get all instructions used as branch targets foreach (var block in Blocks) { foreach (var instr in block.Instructions) { foreach (var operand in instr.Operands) { if (operand is Instruction target) usedAsTarget.Add(target); } } } // Build replacement map for NOPs that are safe to replace var instructionReplacement = new Dictionary(); foreach (var block in Blocks) { Instruction? replacement = null; for (var i = block.Instructions.Count - 1; i >= 0; i--) { var instr = block.Instructions[i]; if (instr.OpCode == OpCode.Nop) { if (replacement != null && !usedAsTarget.Contains(instr)) instructionReplacement[instr] = replacement; } else { replacement = instr; } } } // Update operands foreach (var block in Blocks) { foreach (var instr in block.Instructions) { for (var i = 0; i < instr.Operands.Count; i++) { if (instr.Operands[i] is Instruction target && instructionReplacement.TryGetValue(target, out var newTarget)) instr.SetOperand(i, newTarget); } } } // Remove NOPs foreach (var block in Blocks) { block.Instructions.RemoveAll(i => i.OpCode == OpCode.Nop && !usedAsTarget.Contains(i)); } } public void RemoveEmptyBlocks() { var toRemove = new List(); foreach (var block in Blocks) { if (block == EntryBlock || block == ExitBlock) continue; if (block.Instructions.Count == 0) { // jumps into the removed block must be retargeted, which needs an unambiguous successor if (block.Successors.Count != 1 && HasJumpOperandTo(block)) continue; var jumpTarget = block.Successors.Count == 1 ? block.Successors[0] : null; // Redirect predecessors to successors foreach (var pred in block.Predecessors) { if (pred.Instructions.Count > 0 && pred.Instructions[^1] is { OpCode: OpCode.Jump or OpCode.ConditionalJump } jump && ReferenceEquals(jump.Operands[0], block)) jump.SetOperand(0, jumpTarget!); pred.Successors.Remove(block); foreach (var succ in block.Successors) { if (!pred.Successors.Contains(succ)) pred.Successors.Add(succ); } } // Redirect successors to predecessors foreach (var succ in block.Successors) { succ.Predecessors.Remove(block); foreach (var pred in block.Predecessors) { if (!succ.Predecessors.Contains(pred)) succ.Predecessors.Add(pred); } } toRemove.Add(block); } } foreach (var block in toRemove) Blocks.Remove(block); } private bool HasJumpOperandTo(Block block) => block.Predecessors.Any(pred => pred.Instructions.Count > 0 && pred.Instructions[^1] is { OpCode: OpCode.Jump or OpCode.ConditionalJump } jump && ReferenceEquals(jump.Operands[0], block)); public void BuildUseDefLists(HashSet? clobberingAddressTakes = null) { foreach (var block in Blocks) { var use = new List(); var def = new List(); foreach (var instruction in block.Instructions) { foreach (var operand in instruction.Sources.Where(operand => !use.Contains(operand))) use.Add(operand); if (instruction.Destination != null && !def.Contains(instruction.Destination)) def.Add(instruction.Destination); if (instruction.ImplicitDefinition is { } clobbered && !def.Contains(clobbered)) def.Add(clobbered); if (clobberingAddressTakes?.Contains(instruction) == true) { foreach (var operand in instruction.Operands) if (operand is AddressOf { Target: { } addressed } && !def.Contains(addressed)) def.Add(addressed); } } block.Use = use; block.Def = def; } } public void MergeCallBlocks() { var toRemove = new List(); for (var i = 0; i < Blocks.Count; i++) { var block = Blocks[i]; if (block.BlockType != BlockType.Call) continue; if (block.Successors.Count != 1) continue; var nextBlock = block.Successors[0]; // make sure that the next block only has one predecessor (this) if (nextBlock.Predecessors.Count != 1 || nextBlock.Predecessors[0] != block) continue; // merge instructions block.Instructions.AddRange(nextBlock.Instructions); block.Successors = nextBlock.Successors; // fix up successors predecessors foreach (var successor in nextBlock.Successors) { for (var j = 0; j < successor.Predecessors.Count; j++) { if (successor.Predecessors[j] == nextBlock) successor.Predecessors[j] = block; } } toRemove.Add(nextBlock); } // Remove all merged blocks foreach (var removed in toRemove) Blocks.Remove(removed); foreach (var block in Blocks) block.CalculateBlockType(); } private void Build(List instructions) { if (instructions == null) throw new ArgumentNullException(nameof(instructions)); var currentBlock = new Block() { ID = idCounter++ }; AddBlock(currentBlock); AddDirectedEdge(EntryBlock, currentBlock); for (var i = 0; i < instructions.Count; i++) { var isLast = i == instructions.Count - 1; Block newBlock; switch (instructions[i].OpCode) { case OpCode.Jump: case OpCode.ConditionalJump: case OpCode.IndirectJump: currentBlock.AddInstruction(instructions[i]); if (!isLast) { newBlock = new Block() { ID = idCounter++ }; AddBlock(newBlock); if (instructions[i].OpCode is OpCode.Jump or OpCode.IndirectJump) { if (TryGetTargetJumpInstructionIndex(instructions[i], out int jumpTargetIndex)) currentBlock.Dirty = true; else AddDirectedEdge(currentBlock, ExitBlock); } else { AddDirectedEdge(currentBlock, newBlock); currentBlock.Dirty = true; } currentBlock.CalculateBlockType(); currentBlock = newBlock; } else { AddDirectedEdge(currentBlock, ExitBlock); if (instructions[i].OpCode == OpCode.Jump) currentBlock.Dirty = true; } break; case OpCode.Call: case OpCode.CallVoid: case OpCode.Return: var isReturn = instructions[i].OpCode == OpCode.Return; currentBlock.AddInstruction(instructions[i]); if (!isLast) { newBlock = new Block() { ID = idCounter++ }; AddBlock(newBlock); AddDirectedEdge(currentBlock, isReturn ? ExitBlock : newBlock); currentBlock.CalculateBlockType(); currentBlock = newBlock; } else { AddDirectedEdge(currentBlock, ExitBlock); currentBlock.CalculateBlockType(); } break; default: currentBlock.AddInstruction(instructions[i]); if (isLast) { AddDirectedEdge(currentBlock, ExitBlock); currentBlock.CalculateBlockType(); } break; } } for (var index = 0; index < Blocks.Count; index++) { var node = Blocks[index]; if (node.Dirty) FixBlock(node); } // Connect blocks without successors to exit foreach (var block in Blocks) { if (block.Successors.Count == 0 && block != EntryBlock && block != ExitBlock) AddDirectedEdge(block, ExitBlock); } // Change branch targets to blocks foreach (var instruction in Blocks.SelectMany(block => block.Instructions)) { if (instruction.Operands.Count > 0 && instruction.Operands[0] is Instruction target) instruction.SetOperand(0, FindBlockByInstruction(target)!); } } private void FixBlock(Block block, bool removeJmp = false) { if (block.BlockType is BlockType.Fall) return; var jump = block.Instructions.Last(); var targetInstruction = jump.Operands[0] as Instruction; var destination = FindBlockByInstruction(targetInstruction); if (destination == null) { //We assume that we're tail calling another method somewhere. Need to verify if this breaks anywhere but it shouldn't in general block.BlockType = BlockType.TailCall; return; } int index = destination.Instructions.FindIndex(instruction => instruction == targetInstruction); var targetNode = SplitAndCreate(destination, index); AddDirectedEdge(block, targetNode); block.Dirty = false; if (removeJmp) block.Instructions.Remove(jump); } public Block? FindBlockByInstruction(Instruction? instruction) { if (instruction == null) return null; for (var i = 0; i < Blocks.Count; i++) { var block = Blocks[i]; for (var j = 0; j < block.Instructions.Count; j++) { var instr = block.Instructions[j]; if (instr == instruction) { return block; } } } return null; } private Block SplitAndCreate(Block target, int index) { if (index < 0 || index >= target.Instructions.Count) throw new ArgumentOutOfRangeException(nameof(index)); // Don't need to split... if (index == 0) return target; var newBlock = new Block() { ID = idCounter++ }; // target split in two // targetFirstPart -> targetSecondPart aka newNode // Take the instructions for the secondPart var instructions = target.Instructions.GetRange(index, target.Instructions.Count - index); target.Instructions.RemoveRange(index, target.Instructions.Count - index); // Add those to the newNode newBlock.Instructions.AddRange(instructions); // Transfer control flow newBlock.BlockType = target.BlockType; target.BlockType = BlockType.Fall; // Transfer successors newBlock.Successors = target.Successors; if (target.Dirty) newBlock.Dirty = true; target.Dirty = false; target.Successors = []; // Correct the predecessors for all the successors foreach (var successor in newBlock.Successors) { for (int i = 0; i < successor.Predecessors.Count; i++) { if (successor.Predecessors[i].ID == target.ID) successor.Predecessors[i] = newBlock; } } // Add newNode and connect it AddBlock(newBlock); AddDirectedEdge(target, newBlock); return newBlock; } private void AddDirectedEdge(Block from, Block to) { from.Successors.Add(to); to.Predecessors.Add(from); } protected void AddBlock(Block block) => Blocks.Add(block); }