| // Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'package:cfg/ir/constant_value.dart'; |
| import 'package:cfg/ir/dominators.dart'; |
| import 'package:cfg/ir/functions.dart'; |
| import 'package:cfg/ir/instructions.dart'; |
| import 'package:cfg/ir/local_variable.dart'; |
| import 'package:cfg/ir/loops.dart'; |
| import 'package:cfg/ir/use_lists.dart'; |
| import 'package:cfg/utils/arena.dart'; |
| |
| /// Control-flow graph of instructions implementing a single function. |
| /// |
| /// [FlowGraph] is also an arena to allocate use lists. |
| class FlowGraph extends Uint32Arena { |
| final CFunction function; |
| |
| /// Local variables used in this graph. |
| final List<LocalVariable> localVariables = []; |
| |
| /// All instructions used in this graph. |
| /// [Instruction.id] is an index in this list. |
| final List<Instruction> instructions = []; |
| |
| /// Empty array of uses. Used by all instructions with 0 inputs. |
| late final UsesArray emptyUsesArray = UsesArray.allocate(this, 0); |
| |
| /// Mapping between constant values and corresponding [Constant] |
| /// instructions in the graph. |
| final Map<ConstantValue, Constant> _constants = {}; |
| |
| /// New [Constant] instructions are inserted after this instruction. |
| late Instruction _constantInsertionPoint; |
| |
| /// Entry basic block in this control-flow graph. |
| late final EntryBlock entryBlock; |
| |
| /// Basic block preorder. |
| final List<Block> preorder = []; |
| |
| /// Basic block postorder. |
| final List<Block> postorder = []; |
| |
| /// Basic block reverse postorder. |
| Iterable<Block> get reversePostorder => postorder.reversed; |
| |
| /// Computed dominators. |
| Dominators? _dominators; |
| |
| /// Computed loops. |
| Loops? _loops; |
| |
| /// Whether this graph was converted to SSA form. |
| bool inSSAForm = false; |
| |
| /// Create a new control-flow graph for the given [function]. |
| FlowGraph(this.function) { |
| entryBlock = EntryBlock(this, function.sourcePosition); |
| _constantInsertionPoint = entryBlock; |
| } |
| |
| /// Return a [Constant] instruction with given [value], adding it to |
| /// the graph if necessary. |
| Constant getConstant(ConstantValue value) => |
| _constants[value] ??= _createConstant(value); |
| |
| Constant _createConstant(ConstantValue value) { |
| final instr = Constant(this, value); |
| final insertionPoint = _constantInsertionPoint; |
| if (insertionPoint.next != null) { |
| instr.insertAfter(insertionPoint); |
| } else { |
| insertionPoint.appendInstruction(instr); |
| } |
| _constantInsertionPoint = instr; |
| return instr; |
| } |
| |
| /// Remove given [Constant] instruction from the graph. |
| /// The instruction should be unused. |
| void removeConstant(Constant instr) { |
| assert(!instr.hasUses); |
| _constants.remove(instr.value); |
| if (instr == _constantInsertionPoint) { |
| _constantInsertionPoint = instr.previous!; |
| } |
| } |
| |
| /// Populate [preorder], [postorder], [Block.predecessors], |
| /// [Block.preorderNumber] and [Block.postorderNumber]. |
| void discoverBlocks() { |
| preorder.clear(); |
| postorder.clear(); |
| |
| final stack = <(Block, int)>[]; |
| _discoverBlock(null, entryBlock); |
| stack.add((entryBlock, entryBlock.successors.length - 1)); |
| while (stack.isNotEmpty) { |
| final (block, successorIndex) = stack.removeLast(); |
| if (successorIndex >= 0) { |
| stack.add((block, successorIndex - 1)); |
| final successor = block.successors[successorIndex]; |
| if (_discoverBlock(block, successor)) { |
| stack.add((successor, successor.successors.length - 1)); |
| } |
| } else { |
| // All successors have been processed. |
| block.postorderNumber = postorder.length; |
| postorder.add(block); |
| } |
| } |
| assert(postorder.length == preorder.length); |
| |
| invalidateDominators(); |
| invalidateLoops(); |
| } |
| |
| /// Detect that a block has been visited as part of the current |
| /// [discoverBlocks] ([discoverBlocks] can be called multiple times). |
| /// The block will be 'marked' by (1) having a preorder number in the range of the |
| /// [preorder] and (2) being in the preorder list at that index. |
| bool _isVisited(Block block) { |
| final preorderNumber = block.preorderNumber; |
| return preorderNumber >= 0 && |
| preorderNumber < preorder.length && |
| preorder[preorderNumber] == block; |
| } |
| |
| /// Visit control-flow edge between [predecessor] and [block]. |
| /// |
| /// Must be called on all graph blocks in preorder. |
| /// Sets [Block.preorderNumber], populates [Block.predecessors] and |
| /// [FlowGraph.preorder]. |
| /// Returns true when called the first time on this particular block |
| /// within one graph traversal, and false on all successive calls. |
| bool _discoverBlock(Block? predecessor, Block block) { |
| // If this block has a predecessor (i.e., is not the graph entry) we can |
| // assume the preorder array is non-empty. |
| assert(predecessor == null || preorder.isNotEmpty); |
| // Blocks with a single predecessor cannot have been reached before. |
| assert(block is JoinBlock || !_isVisited(block)); |
| |
| // If the block has already been reached, add current block as a |
| // basic-block predecessor and we are done. |
| if (_isVisited(block)) { |
| block.predecessors.add(predecessor!); |
| return false; |
| } |
| |
| // Otherwise, clear the predecessors which might have been computed on |
| // some earlier call to DiscoverBlocks and record this predecessor. |
| block.predecessors.clear(); |
| if (predecessor != null) block.predecessors.add(predecessor); |
| |
| // Assign the preorder number and add the block to the list. |
| block.preorderNumber = preorder.length; |
| preorder.add(block); |
| |
| return true; |
| } |
| |
| /// Returns dominators for this graph, computing them if necessary. |
| Dominators get dominators => _dominators ??= computeDominators(this); |
| |
| /// Invalidate all information about dominators. |
| /// Dominators will be recalculated once again when needed. |
| /// |
| /// Should be called when blocks have changed. |
| void invalidateDominators() { |
| _dominators = null; |
| } |
| |
| /// Invalidate numbering of instructions which is |
| /// used to implement [Instruction.isDominatedBy]. |
| /// Numbering of instructions will be recalculated once again when needed. |
| /// |
| /// Should be called when instructions were added or moved. |
| void invalidateInstructionNumbering() { |
| _dominators?.invalidateInstructionNumbering(); |
| } |
| |
| /// Returns loops for this graph, computing them if necessary. |
| Loops get loops => _loops ??= computeLoops(this); |
| |
| /// Invalidate all information about loops. |
| /// Loops will be recalculated once again when needed. |
| /// |
| /// Should be called when blocks have changed. |
| void invalidateLoops() { |
| _loops = null; |
| } |
| } |