Skip to content

Commit 1e6debf

Browse files
alexmarkovCommit Queue
authored andcommitted
Dominators, loops and printing IR
Issue: #61635 Change-Id: Ibd3fef2bb049921472841419e9f254393ce0f88f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/460160 Reviewed-by: Slava Egorov <vegorov@google.com> Commit-Queue: Alexander Markov <alexmarkov@google.com>
1 parent 4a41653 commit 1e6debf

File tree

5 files changed

+608
-0
lines changed

5 files changed

+608
-0
lines changed

pkg/cfg/lib/ir/dominators.dart

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:math' as math show min;
6+
import 'dart:typed_data';
7+
8+
import 'package:cfg/ir/flow_graph.dart';
9+
import 'package:cfg/ir/instructions.dart';
10+
import 'package:cfg/utils/bit_vector.dart';
11+
12+
class Dominators {
13+
final FlowGraph graph;
14+
15+
/// Immediate dominator (preorder block number -> preorder block number).
16+
final Int32List idom;
17+
18+
/// List of blocks immediately dominated by a block,
19+
/// indexed by preorder block number.
20+
final List<List<Block>> dominated;
21+
22+
/// (preorder, postorder) numbers for a block in a dominators tree.
23+
/// Note that they are different from preorder/postorder numbers of
24+
/// a block in the control flow graph.
25+
late Int32List _blockNums = _numberBlocks();
26+
27+
/// Instruction number in its block, initialized lazily.
28+
Int32List? _instructionNums;
29+
30+
Dominators._(this.graph, int numBlocks)
31+
: idom = Int32List(numBlocks),
32+
dominated = List<List<Block>>.generate(numBlocks, (_) => <Block>[]);
33+
34+
/// Return true if [b] dominates [a], i.e. every path from
35+
/// graph entry to [a] goes through [b].
36+
bool isDominatedBy(Instruction a, Instruction b) {
37+
if (a == b) {
38+
return false;
39+
}
40+
final blockA = a.block!;
41+
final blockB = b.block!;
42+
if (blockA != blockB) {
43+
return _getDomPreorderNumber(blockB) < _getDomPreorderNumber(blockA) &&
44+
_getDomPostorderNumber(blockA) < _getDomPostorderNumber(blockB);
45+
}
46+
_numberInstructionsInBlock(blockA);
47+
return _instructionNums![b.id] < _instructionNums![a.id];
48+
}
49+
50+
void invalidateInstructionNumbering() {
51+
_instructionNums = null;
52+
}
53+
54+
/// Calculate a list of (preorder, postorder) numbers in a dominators tree.
55+
Int32List _numberBlocks() {
56+
final blockNums = Int32List(graph.preorder.length * 2);
57+
final workList = <(Block, int)>[];
58+
workList.add((graph.entryBlock, 0));
59+
int preorderNumber = 0;
60+
int postorderNumber = 0;
61+
while (workList.isNotEmpty) {
62+
final (block, index) = workList.removeLast();
63+
if (index == 0) {
64+
blockNums[(block.preorderNumber << 1) + 0] = preorderNumber++;
65+
}
66+
if (index < block.dominatedBlocks.length) {
67+
workList.add((block, index + 1));
68+
final child = block.dominatedBlocks[index];
69+
workList.add((child, 0));
70+
} else {
71+
blockNums[(block.preorderNumber << 1) + 1] = postorderNumber++;
72+
}
73+
}
74+
assert(preorderNumber == graph.preorder.length);
75+
assert(postorderNumber == graph.preorder.length);
76+
return blockNums;
77+
}
78+
79+
/// Returns preorder number of [block] in the dominators tree.
80+
/// This is different from [block.preorderNumber] which gives preorder
81+
/// number of a block in the CFG.
82+
int _getDomPreorderNumber(Block block) =>
83+
_blockNums[(block.preorderNumber << 1) + 0];
84+
85+
/// Returns postorder number of [block] in the dominators tree.
86+
/// This is different from [block.postorderNumber] which gives postorder
87+
/// number of a block in the CFG.
88+
int _getDomPostorderNumber(Block block) =>
89+
_blockNums[(block.preorderNumber << 1) + 1];
90+
91+
/// Calculates and caches ordinal numbers of instructions in [block].
92+
void _numberInstructionsInBlock(Block block) {
93+
final instructionNums = (_instructionNums ??= Int32List(
94+
graph.instructions.length,
95+
));
96+
if (instructionNums[block.id] == 0) {
97+
int instrNumber = 1;
98+
instructionNums[block.id] = instrNumber++;
99+
for (final instr in block) {
100+
assert(instructionNums[instr.id] == 0);
101+
instructionNums[instr.id] = instrNumber++;
102+
}
103+
}
104+
}
105+
}
106+
107+
/// Compute immediate dominator and dominated blocks for each basic block.
108+
Dominators computeDominators(FlowGraph graph) {
109+
// Use the SEMI-NCA algorithm to compute dominators. This is a two-pass
110+
// version of the Lengauer-Tarjan algorithm (LT is normally three passes)
111+
// that eliminates a pass by using nearest-common ancestor (NCA) to
112+
// compute immediate dominators from semidominators. It also removes a
113+
// level of indirection in the link-eval forest data structure.
114+
//
115+
// The algorithm is described in Georgiadis, Tarjan, and Werneck's
116+
// "Finding Dominators in Practice".
117+
// https://renatowerneck.files.wordpress.com/2016/06/gtw06-dominators.pdf
118+
119+
// All lists are indexed by preorder block numbers.
120+
final preorder = graph.preorder;
121+
final int size = preorder.length;
122+
final dominators = Dominators._(graph, size);
123+
// Parent in the spanning tree.
124+
final parent = Int32List(size);
125+
// Immediate dominator.
126+
final idom = dominators.idom;
127+
// Semidominator.
128+
final semi = Int32List(size);
129+
// Label for link-eval forest.
130+
final label = Int32List(size);
131+
132+
for (int i = 0; i < size; ++i) {
133+
parent[i] = (i == 0) ? -1 : preorder[i].predecessors.first.preorderNumber;
134+
idom[i] = parent[i];
135+
semi[i] = i;
136+
label[i] = i;
137+
}
138+
139+
// 1. First pass: compute semidominators as in Lengauer-Tarjan.
140+
// Semidominators are computed from a depth-first spanning tree and are an
141+
// approximation of immediate dominators.
142+
143+
// Use a link-eval data structure with path compression. Implement path
144+
// compression in place by mutating [parent]. Each block has a
145+
// label, which is the minimum block number on the compressed path.
146+
void compressPath(int startIndex, int currentIndex) {
147+
int nextIndex = parent[currentIndex];
148+
if (nextIndex > startIndex) {
149+
compressPath(startIndex, nextIndex);
150+
label[currentIndex] = math.min(label[currentIndex], label[nextIndex]);
151+
parent[currentIndex] = parent[nextIndex];
152+
}
153+
}
154+
155+
// Loop over the blocks in reverse preorder (not including the graph entry).
156+
for (int blockIndex = size - 1; blockIndex >= 1; --blockIndex) {
157+
final block = preorder[blockIndex];
158+
for (final pred in block.predecessors) {
159+
// Look for the semidominator by ascending the semidominator path
160+
// starting from pred.
161+
int predIndex = pred.preorderNumber;
162+
int best = predIndex;
163+
if (predIndex > blockIndex) {
164+
compressPath(blockIndex, predIndex);
165+
best = label[predIndex];
166+
}
167+
168+
// Update the semidominator if we've found a better one.
169+
semi[blockIndex] = math.min(semi[blockIndex], semi[best]);
170+
}
171+
172+
// Now use label for the semidominator.
173+
label[blockIndex] = semi[blockIndex];
174+
}
175+
176+
// 2. Compute the immediate dominators as the nearest common ancestor of
177+
// spanning tree parent and semidominator, for all blocks except the entry.
178+
for (int blockIndex = 1; blockIndex < size; ++blockIndex) {
179+
int domIndex = idom[blockIndex];
180+
while (domIndex > semi[blockIndex]) {
181+
domIndex = idom[domIndex];
182+
}
183+
idom[blockIndex] = domIndex;
184+
dominators.dominated[domIndex].add(preorder[blockIndex]);
185+
}
186+
187+
return dominators;
188+
}
189+
190+
/// Compute the dominance frontier for each basic block.
191+
///
192+
/// Returns a list that maps the preorder block number
193+
/// to a set of blocks in the dominance frontier.
194+
///
195+
/// If [includeExceptionHandlers], also include exception handler
196+
/// to a dominance frontier of the block. This is needed in order to
197+
/// account for implicit control flow from the exceptions.
198+
/// (In the implicit control flow, block is a predecessor of its exception
199+
/// handler but block doesn't dominate its exception handler, so exception
200+
/// handler always belongs to a block's dominance frontier.)
201+
List<BitVector> computeDominanceFrontier(
202+
FlowGraph graph, {
203+
bool includeExceptionHandlers = false,
204+
}) {
205+
// This is algorithm in "A Simple, Fast Dominance Algorithm" (Figure 5),
206+
// which is attributed to a paper by Ferrante et al.
207+
//
208+
// There is no bookkeeping required to avoid adding a block twice to
209+
// the same block's dominance frontier because we use a set to represent
210+
// the dominance frontier.
211+
final preorder = graph.preorder;
212+
final int size = preorder.length;
213+
final dominanceFrontier = List<BitVector>.generate(
214+
size,
215+
(i) => BitVector(size),
216+
growable: false,
217+
);
218+
for (int blockIndex = 0; blockIndex < size; ++blockIndex) {
219+
final block = preorder[blockIndex];
220+
if (includeExceptionHandlers) {
221+
final exceptionHandler = block.exceptionHandler;
222+
if (exceptionHandler != null) {
223+
dominanceFrontier[blockIndex].add(exceptionHandler.preorderNumber);
224+
}
225+
}
226+
final count = block.predecessors.length;
227+
if (count <= 1) continue;
228+
for (int i = 0; i < count; ++i) {
229+
Block? runner = block.predecessors[i];
230+
while (runner != block.dominator) {
231+
dominanceFrontier[runner!.preorderNumber].add(blockIndex);
232+
runner = runner.dominator;
233+
}
234+
}
235+
}
236+
return dominanceFrontier;
237+
}

pkg/cfg/lib/ir/flow_graph.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:cfg/ir/constant_value.dart';
6+
import 'package:cfg/ir/dominators.dart';
67
import 'package:cfg/ir/functions.dart';
78
import 'package:cfg/ir/instructions.dart';
89
import 'package:cfg/ir/local_variable.dart';
10+
import 'package:cfg/ir/loops.dart';
911
import 'package:cfg/ir/use_lists.dart';
1012
import 'package:cfg/utils/arena.dart';
1113

@@ -44,6 +46,12 @@ class FlowGraph extends Uint32Arena {
4446
/// Basic block reverse postorder.
4547
Iterable<Block> get reversePostorder => postorder.reversed;
4648

49+
/// Computed dominators.
50+
Dominators? _dominators;
51+
52+
/// Computed loops.
53+
Loops? _loops;
54+
4755
/// Whether this graph was converted to SSA form.
4856
bool inSSAForm = false;
4957

@@ -104,6 +112,9 @@ class FlowGraph extends Uint32Arena {
104112
}
105113
}
106114
assert(postorder.length == preorder.length);
115+
116+
invalidateDominators();
117+
invalidateLoops();
107118
}
108119

109120
/// Detect that a block has been visited as part of the current
@@ -149,4 +160,35 @@ class FlowGraph extends Uint32Arena {
149160

150161
return true;
151162
}
163+
164+
/// Returns dominators for this graph, computing them if necessary.
165+
Dominators get dominators => _dominators ??= computeDominators(this);
166+
167+
/// Invalidate all information about dominators.
168+
/// Dominators will be recalculated once again when needed.
169+
///
170+
/// Should be called when blocks have changed.
171+
void invalidateDominators() {
172+
_dominators = null;
173+
}
174+
175+
/// Invalidate numbering of instructions which is
176+
/// used to implement [Instruction.isDominatedBy].
177+
/// Numbering of instructions will be recalculated once again when needed.
178+
///
179+
/// Should be called when instructions were added or moved.
180+
void invalidateInstructionNumbering() {
181+
_dominators?.invalidateInstructionNumbering();
182+
}
183+
184+
/// Returns loops for this graph, computing them if necessary.
185+
Loops get loops => _loops ??= computeLoops(this);
186+
187+
/// Invalidate all information about loops.
188+
/// Loops will be recalculated once again when needed.
189+
///
190+
/// Should be called when blocks have changed.
191+
void invalidateLoops() {
192+
_loops = null;
193+
}
152194
}

pkg/cfg/lib/ir/instructions.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:cfg/ir/constant_value.dart';
77
import 'package:cfg/ir/flow_graph.dart';
88
import 'package:cfg/ir/functions.dart';
99
import 'package:cfg/ir/local_variable.dart';
10+
import 'package:cfg/ir/loops.dart';
1011
import 'package:cfg/ir/source_position.dart';
1112
import 'package:cfg/ir/types.dart';
1213
import 'package:cfg/ir/use_lists.dart';
@@ -242,6 +243,11 @@ abstract base class Instruction {
242243
bool attributesEqual(Instruction other) =>
243244
throw 'Not implemented for ${runtimeType}';
244245

246+
/// Return true if [other] dominates this instruction, i.e. every path from
247+
/// graph entry to this instruction goes through [other].
248+
bool isDominatedBy(Instruction other) =>
249+
graph.dominators.isDominatedBy(this, other);
250+
245251
R accept<R>(InstructionVisitor<R> v);
246252
}
247253

@@ -411,6 +417,17 @@ abstract base class Block extends Instruction
411417
this.lastInstruction = this;
412418
}
413419

420+
Block? get dominator {
421+
int idom = graph.dominators.idom[preorderNumber];
422+
return (idom < 0) ? null : graph.preorder[idom];
423+
}
424+
425+
List<Block> get dominatedBlocks => graph.dominators.dominated[preorderNumber];
426+
427+
Loop? get loop => graph.loops[this];
428+
429+
int get loopDepth => loop?.depth ?? 0;
430+
414431
/// Replace [oldPredecessor] with [newPredecessor].
415432
void replacePredecessor(Block oldPredecessor, Block newPredecessor) {
416433
final predecessors = this.predecessors;

0 commit comments

Comments
 (0)