blob: 079e2acc832007cec0955ea9e0844be922a0a721 [file] [log] [blame]
// 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;
}
}