Skip to content

Commit 95d96cf

Browse files
alexmarkovCommit Queue
authored and
Commit Queue
committed
[vm/compiler] Fix incorrect DSE
Dead Store Elimination (DSE) propagates "dead store" state backwards across basic blocks to predecessors. If place is defined in a loop (either its instance or index are defined in a loop), propagating "dead store" state across backedge of the loop is incorrect, as each loop iteration effectively has its own place. This bug is fixed by disabling propagation of "dead store" state beyond defining blocks of instance and index of a place. This is done by setting bits in "live_in" of the blocks corresponding to the instance and index definitions of a place. TEST=runtime/tests/vm/dart/regress_55607_test.dart Fixes #55607 Change-Id: I223ff433daa3d7bcafefc9d91360b16c9554964e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/365302 Commit-Queue: Alexander Markov <[email protected]> Reviewed-by: Slava Egorov <[email protected]>
1 parent d9da943 commit 95d96cf

File tree

2 files changed

+116
-16
lines changed

2 files changed

+116
-16
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright (c) 2024, 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+
// Verifies that Dead Store Elimination (DSE) doesn't eliminate
6+
// stores after incorrectly propagating "dead store" state beyond
7+
// definition of instance and index of a store via loop backedge.
8+
// Regression test for https://github.com/dart-lang/sdk/issues/55607.
9+
10+
import 'package:expect/expect.dart';
11+
12+
// Original example.
13+
// Verifies that "dead store" state is not be propagated for indexed place
14+
// 'part[offset]' beyond definition of index 'offset' which changes in a loop.
15+
@pragma('vm:never-inline')
16+
List<int> test1() {
17+
int offset = 0;
18+
List<int> part = [0, 0, 0];
19+
for (int i = 0; i < 2; i++) {
20+
part[offset] = 2;
21+
offset++;
22+
}
23+
part[offset] = 10;
24+
return part;
25+
}
26+
27+
class A {
28+
int aField;
29+
A(this.aField);
30+
operator ==(Object other) => other is A && this.aField == other.aField;
31+
String toString() => 'A($aField)';
32+
}
33+
34+
// Verifies that "dead store" state is not be propagated for instance field
35+
// place 'obj.aField' beyond definition of instance 'obj' which changes in
36+
// a loop.
37+
@pragma('vm:never-inline')
38+
List<A> test2() {
39+
A obj1 = A(0);
40+
A obj2 = A(0);
41+
final list = [obj1, obj2];
42+
A obj = obj1;
43+
for (int i = 0; i < 1; i++) {
44+
obj.aField = 2;
45+
obj = obj2;
46+
}
47+
obj.aField = 10;
48+
return list;
49+
}
50+
51+
class B {
52+
A obj;
53+
B(this.obj);
54+
operator ==(Object other) => other is B && this.obj == other.obj;
55+
String toString() => 'B($obj)';
56+
}
57+
58+
@pragma('vm:never-inline')
59+
confuse(x) => x;
60+
61+
// Same as previous, but 'obj' definition is LoadField, not a Phi.
62+
@pragma('vm:never-inline')
63+
List<B> test3() {
64+
B b1 = confuse(B(A(0)));
65+
B b2 = confuse(B(A(0)));
66+
final list = [b1, b2];
67+
A obj;
68+
B bb = b1;
69+
for (int i = 0; (obj = bb.obj) != null && i < 1; i++) {
70+
obj.aField = 2;
71+
bb = b2;
72+
}
73+
obj.aField = 10;
74+
return list;
75+
}
76+
77+
void main() {
78+
Expect.deepEquals([2, 2, 10], test1());
79+
Expect.deepEquals([A(2), A(10)], test2());
80+
Expect.deepEquals([B(A(2)), B(A(10))], test3());
81+
}

runtime/vm/compiler/backend/redundancy_elimination.cc

+35-16
Original file line numberDiff line numberDiff line change
@@ -3080,24 +3080,43 @@ class StoreOptimizer : public LivenessAnalysis {
30803080
new (zone) BitVector(zone, aliased_set_->max_place_id());
30813081
all_places->SetAll();
30823082

3083-
BitVector* all_aliased_places =
3084-
new (zone) BitVector(zone, aliased_set_->max_place_id());
3083+
BitVector* all_aliased_places = nullptr;
30853084
if (CompilerState::Current().is_aot()) {
3086-
const auto& places = aliased_set_->places();
3087-
// Go through all places and identify those which are escaping.
3088-
// We find such places by inspecting definition allocation
3089-
// [AliasIdentity] field, which is populated above by
3090-
// [AliasedSet::ComputeAliasing].
3091-
for (intptr_t i = 0; i < places.length(); i++) {
3092-
Place* place = places[i];
3093-
if (place->DependsOnInstance()) {
3094-
Definition* instance = place->instance();
3095-
if (Place::IsAllocation(instance) &&
3096-
!instance->Identity().IsAliased()) {
3097-
continue;
3098-
}
3085+
all_aliased_places =
3086+
new (zone) BitVector(zone, aliased_set_->max_place_id());
3087+
}
3088+
const auto& places = aliased_set_->places();
3089+
for (intptr_t i = 0; i < places.length(); i++) {
3090+
Place* place = places[i];
3091+
if (place->DependsOnInstance()) {
3092+
Definition* instance = place->instance();
3093+
// Find escaping places by inspecting definition allocation
3094+
// [AliasIdentity] field, which is populated above by
3095+
// [AliasedSet::ComputeAliasing].
3096+
if ((all_aliased_places != nullptr) &&
3097+
(!Place::IsAllocation(instance) ||
3098+
instance->Identity().IsAliased())) {
3099+
all_aliased_places->Add(i);
3100+
}
3101+
if (instance != nullptr) {
3102+
// Avoid incorrect propagation of "dead" state beyond definition
3103+
// of the instance. Otherwise it may eventually reach stores into
3104+
// this place via loop backedge.
3105+
live_in_[instance->GetBlock()->postorder_number()]->Add(i);
3106+
}
3107+
} else {
3108+
if (all_aliased_places != nullptr) {
3109+
all_aliased_places->Add(i);
3110+
}
3111+
}
3112+
if (place->kind() == Place::kIndexed) {
3113+
Definition* index = place->index();
3114+
if (index != nullptr) {
3115+
// Avoid incorrect propagation of "dead" state beyond definition
3116+
// of the index. Otherwise it may eventually reach stores into
3117+
// this place via loop backedge.
3118+
live_in_[index->GetBlock()->postorder_number()]->Add(i);
30993119
}
3100-
all_aliased_places->Add(i);
31013120
}
31023121
}
31033122

0 commit comments

Comments
 (0)