Skip to content

Commit

Permalink
[vm/compiler] Fix incorrect DSE
Browse files Browse the repository at this point in the history
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 <alexmarkov@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
  • Loading branch information
alexmarkov authored and Commit Queue committed May 6, 2024
1 parent d9da943 commit 95d96cf
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 16 deletions.
81 changes: 81 additions & 0 deletions runtime/tests/vm/dart/regress_55607_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) 2024, 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.

// Verifies that Dead Store Elimination (DSE) doesn't eliminate
// stores after incorrectly propagating "dead store" state beyond
// definition of instance and index of a store via loop backedge.
// Regression test for https://github.com/dart-lang/sdk/issues/55607.

import 'package:expect/expect.dart';

// Original example.
// Verifies that "dead store" state is not be propagated for indexed place
// 'part[offset]' beyond definition of index 'offset' which changes in a loop.
@pragma('vm:never-inline')
List<int> test1() {
int offset = 0;
List<int> part = [0, 0, 0];
for (int i = 0; i < 2; i++) {
part[offset] = 2;
offset++;
}
part[offset] = 10;
return part;
}

class A {
int aField;
A(this.aField);
operator ==(Object other) => other is A && this.aField == other.aField;
String toString() => 'A($aField)';
}

// Verifies that "dead store" state is not be propagated for instance field
// place 'obj.aField' beyond definition of instance 'obj' which changes in
// a loop.
@pragma('vm:never-inline')
List<A> test2() {
A obj1 = A(0);
A obj2 = A(0);
final list = [obj1, obj2];
A obj = obj1;
for (int i = 0; i < 1; i++) {
obj.aField = 2;
obj = obj2;
}
obj.aField = 10;
return list;
}

class B {
A obj;
B(this.obj);
operator ==(Object other) => other is B && this.obj == other.obj;
String toString() => 'B($obj)';
}

@pragma('vm:never-inline')
confuse(x) => x;

// Same as previous, but 'obj' definition is LoadField, not a Phi.
@pragma('vm:never-inline')
List<B> test3() {
B b1 = confuse(B(A(0)));
B b2 = confuse(B(A(0)));
final list = [b1, b2];
A obj;
B bb = b1;
for (int i = 0; (obj = bb.obj) != null && i < 1; i++) {
obj.aField = 2;
bb = b2;
}
obj.aField = 10;
return list;
}

void main() {
Expect.deepEquals([2, 2, 10], test1());
Expect.deepEquals([A(2), A(10)], test2());
Expect.deepEquals([B(A(2)), B(A(10))], test3());
}
51 changes: 35 additions & 16 deletions runtime/vm/compiler/backend/redundancy_elimination.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3080,24 +3080,43 @@ class StoreOptimizer : public LivenessAnalysis {
new (zone) BitVector(zone, aliased_set_->max_place_id());
all_places->SetAll();

BitVector* all_aliased_places =
new (zone) BitVector(zone, aliased_set_->max_place_id());
BitVector* all_aliased_places = nullptr;
if (CompilerState::Current().is_aot()) {
const auto& places = aliased_set_->places();
// Go through all places and identify those which are escaping.
// We find such places by inspecting definition allocation
// [AliasIdentity] field, which is populated above by
// [AliasedSet::ComputeAliasing].
for (intptr_t i = 0; i < places.length(); i++) {
Place* place = places[i];
if (place->DependsOnInstance()) {
Definition* instance = place->instance();
if (Place::IsAllocation(instance) &&
!instance->Identity().IsAliased()) {
continue;
}
all_aliased_places =
new (zone) BitVector(zone, aliased_set_->max_place_id());
}
const auto& places = aliased_set_->places();
for (intptr_t i = 0; i < places.length(); i++) {
Place* place = places[i];
if (place->DependsOnInstance()) {
Definition* instance = place->instance();
// Find escaping places by inspecting definition allocation
// [AliasIdentity] field, which is populated above by
// [AliasedSet::ComputeAliasing].
if ((all_aliased_places != nullptr) &&
(!Place::IsAllocation(instance) ||
instance->Identity().IsAliased())) {
all_aliased_places->Add(i);
}
if (instance != nullptr) {
// Avoid incorrect propagation of "dead" state beyond definition
// of the instance. Otherwise it may eventually reach stores into
// this place via loop backedge.
live_in_[instance->GetBlock()->postorder_number()]->Add(i);
}
} else {
if (all_aliased_places != nullptr) {
all_aliased_places->Add(i);
}
}
if (place->kind() == Place::kIndexed) {
Definition* index = place->index();
if (index != nullptr) {
// Avoid incorrect propagation of "dead" state beyond definition
// of the index. Otherwise it may eventually reach stores into
// this place via loop backedge.
live_in_[index->GetBlock()->postorder_number()]->Add(i);
}
all_aliased_places->Add(i);
}
}

Expand Down

0 comments on commit 95d96cf

Please sign in to comment.