Skip to content

Commit 9f72ed9

Browse files
alexmarkovCommit Queue
authored andcommitted
Dominator-based value numbering
Issue: #61635 Change-Id: Iec078ced4da549456ccaa7f3f6bd23926e5e4e6f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/465701 Reviewed-by: Slava Egorov <[email protected]> Commit-Queue: Alexander Markov <[email protected]>
1 parent 20c1d3a commit 9f72ed9

File tree

8 files changed

+251
-12
lines changed

8 files changed

+251
-12
lines changed

pkg/cfg/lib/ir/instructions.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,9 +233,9 @@ abstract base class Instruction {
233233
/// Whether this instruction can have any visible side-effects.
234234
bool get hasSideEffects;
235235

236-
/// Returns true if this instruction is idempotent (i.e.
237-
/// repeating this instruction does not have any effect),
238-
/// and it is a subject to value numbering.
236+
/// Returns true if this instruction is idempotent (i.e. repeating this
237+
/// instruction with the same inputs does not have any effects after
238+
/// executing it once), and it is a subject to value numbering.
239239
bool get isIdempotent => false;
240240

241241
/// Returns true if extra instruction attributes are equal.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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:collection';
6+
7+
import 'package:cfg/ir/flow_graph.dart';
8+
import 'package:cfg/ir/instructions.dart';
9+
import 'package:cfg/passes/pass.dart';
10+
import 'package:cfg/passes/simplification.dart';
11+
import 'package:cfg/utils/misc.dart';
12+
13+
/// Dominator-based value numbering.
14+
///
15+
/// Can be optionally combined with simplification pass.
16+
///
17+
/// The algorithm is described in
18+
/// Briggs, P.; Cooper, Keith D.; Simpson, L. Taylor (1997). "Value Numbering"
19+
/// (https://www.cs.tufts.edu/~nr/cs257/archive/keith-cooper/value-numbering.pdf).
20+
final class ValueNumbering extends Pass {
21+
final Simplification? simplification;
22+
23+
ValueNumbering({this.simplification}) : super('ValueNumbering');
24+
25+
@override
26+
void initialize(ErrorContext errorContext, FlowGraph graph) {
27+
super.initialize(errorContext, graph);
28+
simplification?.initialize(errorContext, graph);
29+
}
30+
31+
@override
32+
void run() {
33+
final workList = <(Block, ValueNumberingMap)>[];
34+
workList.add((graph.entryBlock, _createMap()));
35+
36+
while (workList.isNotEmpty) {
37+
final (block, map) = workList.removeLast();
38+
currentBlock = block;
39+
40+
for (var instr in block) {
41+
currentInstruction = instr;
42+
// Apply simplification first.
43+
// It can return the same instruction, previously seen
44+
// instruction or a new instruction.
45+
final simplification = this.simplification;
46+
if (simplification != null) {
47+
instr = simplification.simplify(instr);
48+
}
49+
if (!instr.isIdempotent) {
50+
continue;
51+
}
52+
final replacement = map[instr];
53+
if (replacement == null) {
54+
assert(instr.block == block);
55+
map[instr] = instr;
56+
} else if (replacement != instr) {
57+
assert(replacement.isIdempotent);
58+
if (instr is Definition) {
59+
instr.replaceUsesWith(replacement as Definition);
60+
}
61+
instr.removeFromGraph();
62+
}
63+
}
64+
65+
for (int i = 0, n = block.dominatedBlocks.length; i < n; ++i) {
66+
// Reuse map for the last dominated block.
67+
var newMap = map;
68+
if (i != n - 1) {
69+
newMap = _createMap();
70+
newMap.addAll(map);
71+
}
72+
workList.add((block.dominatedBlocks[i], newMap));
73+
}
74+
}
75+
76+
graph.invalidateInstructionNumbering();
77+
}
78+
}
79+
80+
/// Maps idempotent instructions to their first occurrence.
81+
///
82+
/// Unordered [HashMap] is used as these maps are not iterated
83+
/// (only looked up and copied).
84+
typedef ValueNumberingMap = HashMap<Instruction, Instruction>;
85+
86+
ValueNumberingMap _createMap() => ValueNumberingMap(
87+
equals: _instructionEquals,
88+
hashCode: _instructionHashCode,
89+
);
90+
91+
bool _instructionEquals(Instruction a, Instruction b) {
92+
assert(a.isIdempotent);
93+
assert(b.isIdempotent);
94+
if (a.runtimeType != b.runtimeType) {
95+
return false;
96+
}
97+
if (a.inputCount != b.inputCount) {
98+
return false;
99+
}
100+
for (int i = 0, n = a.inputCount; i < n; ++i) {
101+
if (a.inputDefAt(i) != b.inputDefAt(i)) {
102+
return false;
103+
}
104+
}
105+
return a.attributesEqual(b);
106+
}
107+
108+
int _instructionHashCode(Instruction instr) {
109+
assert(instr.isIdempotent);
110+
var hash = instr.runtimeType.hashCode;
111+
for (int i = 0, n = instr.inputCount; i < n; ++i) {
112+
hash = combineHash(hash, instr.inputDefAt(i).id);
113+
}
114+
return finalizeHash(hash);
115+
}

pkg/cfg/test/ir_test.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import 'package:cfg/ir/ir_to_text.dart';
2323
import 'package:cfg/ir/ssa_computation.dart';
2424
import 'package:cfg/passes/pass.dart';
2525
import 'package:cfg/passes/simplification.dart';
26+
import 'package:cfg/passes/value_numbering.dart';
2627
import 'package:kernel/type_environment.dart';
2728
import 'package:test/test.dart';
2829
import 'package:vm/modular/target/vm.dart';
@@ -111,7 +112,10 @@ class CompileAndDumpIr extends RecursiveVisitor {
111112
recognizedMethods,
112113
enableAsserts: true,
113114
).buildFlowGraph();
114-
final pipeline = Pipeline([SSAComputation(), Simplification()]);
115+
final pipeline = Pipeline([
116+
SSAComputation(),
117+
ValueNumbering(simplification: Simplification()),
118+
]);
115119
pipeline.run(graph);
116120
buffer.writeln('--- ${node.name}');
117121
buffer.writeln(

pkg/cfg/testcases/numbers.dart.expect

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ B14 = TargetBlock() idom:B8 in-loop:B8
6161
v28 = UnaryIntOp toDouble(v2)
6262
v29 = BinaryDoubleOp /(v26, v28)
6363
v30 = UnaryDoubleOp roundToDouble(v29)
64-
v31 = UnaryIntOp toDouble(v2)
65-
v32 = BinaryDoubleOp -(v31, v30)
64+
v32 = BinaryDoubleOp -(v28, v30)
6665
v33 = BinaryDoubleOp +(v44, v32)
6766
v37 = BinaryIntOp +(v45, v36)
6867
Goto(B8)

pkg/cfg/testcases/simplification.dart.expect

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,7 @@ B0 = EntryBlock()
163163
v89 = BinaryIntOp +(v1, v88)
164164
v91 = BinaryIntOp >>(v89, v90)
165165
DirectCall print(v91)
166-
v93 = UnaryIntOp -(v1)
167-
DirectCall print(v93)
166+
DirectCall print(v82)
168167
DirectCall print(v3)
169168
v34 = BinaryIntOp >>(v1, v85)
170169
DirectCall print(v34)

pkg/cfg/testcases/statements.dart.expect

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,7 @@ B30 = JoinBlock(B25) exception-handler:B2 idom:B25
322322
B26 = TargetBlock() exception-handler:B17 idom:B16 dominates:(B28) in-loop:B8
323323
Goto(B28)
324324
B28 = JoinBlock(B26) exception-handler:B17 idom:B26 dominates:(B37, B68, B36) in-loop:B8
325-
v33 = BinaryIntOp %(v88, v22)
326-
v35 = Comparison int ==(v33, v34)
325+
v35 = Comparison int ==(v23, v34)
327326
Branch(v35, true: B36, false: B37)
328327
B36 = TargetBlock() exception-handler:B17 idom:B28 dominates:(B41) in-loop:B8
329328
Goto(B41)
@@ -333,8 +332,7 @@ B41 = JoinBlock(B36) exception-handler:B2 idom:B36 in-loop:B8
333332
B37 = TargetBlock() exception-handler:B17 idom:B28 dominates:(B39) in-loop:B8
334333
Goto(B39)
335334
B39 = JoinBlock(B37) exception-handler:B17 idom:B37 dominates:(B48, B47) in-loop:B8
336-
v44 = BinaryIntOp %(v88, v22)
337-
v46 = Comparison int ==(v44, v45)
335+
v46 = Comparison int ==(v23, v45)
338336
Branch(v46, true: B47, false: B48)
339337
B47 = TargetBlock() exception-handler:B17 idom:B39 dominates:(B53)
340338
Goto(B53)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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+
int test1(List<int> data, int i) {
6+
if (data[i - 1] > 0) {
7+
return data[i - 1];
8+
}
9+
return 0;
10+
}
11+
12+
void test2(Object? obj) {
13+
if (obj is String) {
14+
print(obj.length);
15+
if (obj.isNotEmpty) {
16+
print(obj.codeUnitAt(0));
17+
}
18+
}
19+
}
20+
21+
void test3(Object value) {
22+
switch (value) {
23+
case int() when value > 5 && value < 10:
24+
print('case 1');
25+
case int() when value > 5:
26+
print('case 2');
27+
}
28+
}
29+
30+
void main() {}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
--- test1
2+
B0 = EntryBlock() dominates:(B11, B10)
3+
Constant(1)
4+
v8 = Constant(0)
5+
v21 = Constant(-1)
6+
v1 = Parameter(data)
7+
v2 = Parameter(i)
8+
v22 = BinaryIntOp +(v2, v21)
9+
v7 = InterfaceCall List.[](v1, v22)
10+
v9 = Comparison int >(v7, v8)
11+
Branch(v9, true: B10, false: B11)
12+
B10 = TargetBlock() idom:B0
13+
v18 = InterfaceCall List.[](v1, v22)
14+
Return(v18)
15+
B11 = TargetBlock() idom:B0 dominates:(B13)
16+
Goto(B13)
17+
B13 = JoinBlock(B11) idom:B11
18+
Return(v8)
19+
20+
--- test2
21+
B0 = EntryBlock() dominates:(B5, B7, B4)
22+
v23 = Constant(0)
23+
v28 = Constant(null)
24+
v1 = Parameter(obj)
25+
v3 = TypeTest(v1, String)
26+
Branch(v3, true: B4, false: B5)
27+
B4 = TargetBlock() idom:B0 dominates:(B17, B19, B16)
28+
v10 = TypeCast(v1, String, unchecked)
29+
v11 = InterfaceCall getter String.length(v10)
30+
DirectCall print(v11)
31+
v15 = InterfaceCall getter String.isNotEmpty(v10)
32+
Branch(v15, true: B16, false: B17)
33+
B16 = TargetBlock() idom:B4
34+
v24 = InterfaceCall String.codeUnitAt(v10, v23)
35+
DirectCall print(v24)
36+
Goto(B19)
37+
B17 = TargetBlock() idom:B4
38+
Goto(B19)
39+
B19 = JoinBlock(B17, B16) idom:B4
40+
Goto(B7)
41+
B5 = TargetBlock() idom:B0
42+
Goto(B7)
43+
B7 = JoinBlock(B5, B19) idom:B0
44+
Return(v28)
45+
46+
--- test3
47+
B0 = EntryBlock() dominates:(B7, B23, B29, B6)
48+
v11 = Constant(5)
49+
v18 = Constant(10)
50+
v27 = Constant("case 1")
51+
v45 = Constant("case 2")
52+
v49 = Constant(null)
53+
v1 = Parameter(value)
54+
v5 = TypeTest(v1, int)
55+
Branch(v5, true: B6, false: B7)
56+
B6 = TargetBlock() idom:B0 dominates:(B14, B13)
57+
v10 = TypeCast(v1, int, unchecked)
58+
v12 = Comparison int >(v10, v11)
59+
Branch(v12, true: B13, false: B14)
60+
B13 = TargetBlock() idom:B6 dominates:(B21, B20)
61+
v19 = Comparison int <(v10, v18)
62+
Branch(v19, true: B20, false: B21)
63+
B20 = TargetBlock() idom:B13
64+
DirectCall print(v27)
65+
Goto(B29)
66+
B21 = TargetBlock() idom:B13
67+
Goto(B23)
68+
B14 = TargetBlock() idom:B6
69+
Goto(B23)
70+
B7 = TargetBlock() idom:B0
71+
Goto(B23)
72+
B23 = JoinBlock(B7, B14, B21) idom:B0 dominates:(B34, B42, B33)
73+
Branch(v5, true: B33, false: B34)
74+
B33 = TargetBlock() idom:B23 dominates:(B40, B39)
75+
v37 = TypeCast(v1, int, unchecked)
76+
v38 = Comparison int >(v37, v11)
77+
Branch(v38, true: B39, false: B40)
78+
B39 = TargetBlock() idom:B33
79+
DirectCall print(v45)
80+
Goto(B42)
81+
B40 = TargetBlock() idom:B33
82+
Goto(B42)
83+
B34 = TargetBlock() idom:B23
84+
Goto(B42)
85+
B42 = JoinBlock(B34, B40, B39) idom:B23
86+
Goto(B29)
87+
B29 = JoinBlock(B42, B20) idom:B0
88+
Return(v49)
89+
90+
--- main
91+
B0 = EntryBlock()
92+
v1 = Constant(null)
93+
Return(v1)
94+

0 commit comments

Comments
 (0)