Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 1e6debf

Browse files
alexmarkovCommit Queue
authored and
Commit Queue
committed
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)

AltStyle によって変換されたページ (->オリジナル) /