| // Copyright 2016 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/compiler/load-elimination.h" |
| #include "src/compiler/access-builder.h" |
| #include "src/compiler/js-graph.h" |
| #include "src/compiler/node.h" |
| #include "src/compiler/simplified-operator.h" |
| #include "test/unittests/compiler/graph-reducer-unittest.h" |
| #include "test/unittests/compiler/graph-unittest.h" |
| #include "test/unittests/compiler/node-test-utils.h" |
| #include "testing/gmock-support.h" |
| |
| using testing::_; |
| using testing::StrictMock; |
| |
| namespace v8 { |
| namespace internal { |
| namespace compiler { |
| |
| class LoadEliminationTest : public TypedGraphTest { |
| public: |
| LoadEliminationTest() |
| : TypedGraphTest(3), |
| simplified_(zone()), |
| jsgraph_(isolate(), graph(), common(), nullptr, simplified(), nullptr) { |
| } |
| ~LoadEliminationTest() override = default; |
| |
| protected: |
| JSGraph* jsgraph() { return &jsgraph_; } |
| SimplifiedOperatorBuilder* simplified() { return &simplified_; } |
| |
| private: |
| SimplifiedOperatorBuilder simplified_; |
| JSGraph jsgraph_; |
| }; |
| |
| TEST_F(LoadEliminationTest, LoadElementAndLoadElement) { |
| Node* object = Parameter(Type::Any(), 0); |
| Node* effect = graph()->start(); |
| Node* control = graph()->start(); |
| Node* index = Parameter(Type::UnsignedSmall(), 1); |
| ElementAccess const access = {kTaggedBase, kTaggedSize, Type::Any(), |
| MachineType::AnyTagged(), kNoWriteBarrier}; |
| |
| StrictMock<MockAdvancedReducerEditor> editor; |
| LoadElimination load_elimination(&editor, jsgraph(), zone()); |
| |
| load_elimination.Reduce(graph()->start()); |
| |
| Node* load1 = effect = graph()->NewNode(simplified()->LoadElement(access), |
| object, index, effect, control); |
| load_elimination.Reduce(load1); |
| |
| Node* load2 = effect = graph()->NewNode(simplified()->LoadElement(access), |
| object, index, effect, control); |
| EXPECT_CALL(editor, ReplaceWithValue(load2, load1, load1, _)); |
| Reduction r = load_elimination.Reduce(load2); |
| ASSERT_TRUE(r.Changed()); |
| EXPECT_EQ(load1, r.replacement()); |
| } |
| |
| TEST_F(LoadEliminationTest, StoreElementAndLoadElement) { |
| Node* object = Parameter(Type::Any(), 0); |
| Node* effect = graph()->start(); |
| Node* control = graph()->start(); |
| Node* index = Parameter(Type::UnsignedSmall(), 1); |
| Node* value = Parameter(Type::Any(), 2); |
| ElementAccess const access = {kTaggedBase, kTaggedSize, Type::Any(), |
| MachineType::AnyTagged(), kNoWriteBarrier}; |
| |
| StrictMock<MockAdvancedReducerEditor> editor; |
| LoadElimination load_elimination(&editor, jsgraph(), zone()); |
| |
| load_elimination.Reduce(graph()->start()); |
| |
| Node* store = effect = |
| graph()->NewNode(simplified()->StoreElement(access), object, index, value, |
| effect, control); |
| load_elimination.Reduce(store); |
| |
| Node* load = effect = graph()->NewNode(simplified()->LoadElement(access), |
| object, index, effect, control); |
| EXPECT_CALL(editor, ReplaceWithValue(load, value, store, _)); |
| Reduction r = load_elimination.Reduce(load); |
| ASSERT_TRUE(r.Changed()); |
| EXPECT_EQ(value, r.replacement()); |
| } |
| |
| TEST_F(LoadEliminationTest, StoreElementAndStoreFieldAndLoadElement) { |
| Node* object = Parameter(Type::Any(), 0); |
| Node* effect = graph()->start(); |
| Node* control = graph()->start(); |
| Node* index = Parameter(Type::UnsignedSmall(), 1); |
| Node* value = Parameter(Type::Any(), 2); |
| ElementAccess const access = {kTaggedBase, kTaggedSize, Type::Any(), |
| MachineType::AnyTagged(), kNoWriteBarrier}; |
| |
| StrictMock<MockAdvancedReducerEditor> editor; |
| LoadElimination load_elimination(&editor, jsgraph(), zone()); |
| |
| load_elimination.Reduce(graph()->start()); |
| |
| Node* store1 = effect = |
| graph()->NewNode(simplified()->StoreElement(access), object, index, value, |
| effect, control); |
| load_elimination.Reduce(store1); |
| |
| Node* store2 = effect = |
| graph()->NewNode(simplified()->StoreField(AccessBuilder::ForMap()), |
| object, value, effect, control); |
| load_elimination.Reduce(store2); |
| |
| Node* load = effect = graph()->NewNode(simplified()->LoadElement(access), |
| object, index, effect, control); |
| EXPECT_CALL(editor, ReplaceWithValue(load, value, store2, _)); |
| Reduction r = load_elimination.Reduce(load); |
| ASSERT_TRUE(r.Changed()); |
| EXPECT_EQ(value, r.replacement()); |
| } |
| |
| TEST_F(LoadEliminationTest, LoadFieldAndLoadField) { |
| Node* object = Parameter(Type::Any(), 0); |
| Node* effect = graph()->start(); |
| Node* control = graph()->start(); |
| FieldAccess const access = {kTaggedBase, kTaggedSize, |
| MaybeHandle<Name>(), MaybeHandle<Map>(), |
| Type::Any(), MachineType::AnyTagged(), |
| kNoWriteBarrier}; |
| |
| StrictMock<MockAdvancedReducerEditor> editor; |
| LoadElimination load_elimination(&editor, jsgraph(), zone()); |
| |
| load_elimination.Reduce(graph()->start()); |
| |
| Node* load1 = effect = graph()->NewNode(simplified()->LoadField(access), |
| object, effect, control); |
| load_elimination.Reduce(load1); |
| |
| Node* load2 = effect = graph()->NewNode(simplified()->LoadField(access), |
| object, effect, control); |
| EXPECT_CALL(editor, ReplaceWithValue(load2, load1, load1, _)); |
| Reduction r = load_elimination.Reduce(load2); |
| ASSERT_TRUE(r.Changed()); |
| EXPECT_EQ(load1, r.replacement()); |
| } |
| |
| TEST_F(LoadEliminationTest, StoreFieldAndLoadField) { |
| Node* object = Parameter(Type::Any(), 0); |
| Node* value = Parameter(Type::Any(), 1); |
| Node* effect = graph()->start(); |
| Node* control = graph()->start(); |
| FieldAccess access = {kTaggedBase, kTaggedSize, |
| MaybeHandle<Name>(), MaybeHandle<Map>(), |
| Type::Any(), MachineType::AnyTagged(), |
| kNoWriteBarrier}; |
| |
| StrictMock<MockAdvancedReducerEditor> editor; |
| LoadElimination load_elimination(&editor, jsgraph(), zone()); |
| |
| load_elimination.Reduce(graph()->start()); |
| |
| Node* store = effect = graph()->NewNode(simplified()->StoreField(access), |
| object, value, effect, control); |
| load_elimination.Reduce(store); |
| |
| Node* load = effect = graph()->NewNode(simplified()->LoadField(access), |
| object, effect, control); |
| EXPECT_CALL(editor, ReplaceWithValue(load, value, store, _)); |
| Reduction r = load_elimination.Reduce(load); |
| ASSERT_TRUE(r.Changed()); |
| EXPECT_EQ(value, r.replacement()); |
| } |
| |
| TEST_F(LoadEliminationTest, StoreFieldAndKillFields) { |
| Node* object = Parameter(Type::Any(), 0); |
| Node* value = Parameter(Type::Any(), 1); |
| Node* effect = graph()->start(); |
| Node* control = graph()->start(); |
| |
| FieldAccess access1 = {kTaggedBase, kTaggedSize, |
| MaybeHandle<Name>(), MaybeHandle<Map>(), |
| Type::Any(), MachineType::AnyTagged(), |
| kNoWriteBarrier}; |
| |
| // Offset that out of field cache size. |
| FieldAccess access2 = {kTaggedBase, 2048 * kTaggedSize, |
| MaybeHandle<Name>(), MaybeHandle<Map>(), |
| Type::Any(), MachineType::AnyTagged(), |
| kNoWriteBarrier}; |
| |
| StrictMock<MockAdvancedReducerEditor> editor; |
| LoadElimination load_elimination(&editor, jsgraph(), zone()); |
| |
| load_elimination.Reduce(graph()->start()); |
| |
| Node* store1 = effect = graph()->NewNode(simplified()->StoreField(access1), |
| object, value, effect, control); |
| load_elimination.Reduce(store1); |
| |
| // Invalidate caches of object. |
| Node* store2 = effect = graph()->NewNode(simplified()->StoreField(access2), |
| object, value, effect, control); |
| load_elimination.Reduce(store2); |
| |
| Node* store3 = graph()->NewNode(simplified()->StoreField(access1), |
| object, value, effect, control); |
| |
| Reduction r = load_elimination.Reduce(store3); |
| |
| // store3 shall not be replaced, since caches were invalidated. |
| EXPECT_EQ(store3, r.replacement()); |
| } |
| |
| TEST_F(LoadEliminationTest, StoreFieldAndStoreElementAndLoadField) { |
| Node* object = Parameter(Type::Any(), 0); |
| Node* value = Parameter(Type::Any(), 1); |
| Node* index = Parameter(Type::UnsignedSmall(), 2); |
| Node* effect = graph()->start(); |
| Node* control = graph()->start(); |
| FieldAccess access = {kTaggedBase, kTaggedSize, |
| MaybeHandle<Name>(), MaybeHandle<Map>(), |
| Type::Any(), MachineType::AnyTagged(), |
| kNoWriteBarrier}; |
| |
| StrictMock<MockAdvancedReducerEditor> editor; |
| LoadElimination load_elimination(&editor, jsgraph(), zone()); |
| |
| load_elimination.Reduce(graph()->start()); |
| |
| Node* store1 = effect = graph()->NewNode(simplified()->StoreField(access), |
| object, value, effect, control); |
| load_elimination.Reduce(store1); |
| |
| Node* store2 = effect = graph()->NewNode( |
| simplified()->StoreElement(AccessBuilder::ForFixedArrayElement()), object, |
| index, object, effect, control); |
| load_elimination.Reduce(store2); |
| |
| Node* load = effect = graph()->NewNode(simplified()->LoadField(access), |
| object, effect, control); |
| EXPECT_CALL(editor, ReplaceWithValue(load, value, store2, _)); |
| Reduction r = load_elimination.Reduce(load); |
| ASSERT_TRUE(r.Changed()); |
| EXPECT_EQ(value, r.replacement()); |
| } |
| |
| TEST_F(LoadEliminationTest, LoadElementOnTrueBranchOfDiamond) { |
| Node* object = Parameter(Type::Any(), 0); |
| Node* index = Parameter(Type::UnsignedSmall(), 1); |
| Node* check = Parameter(Type::Boolean(), 2); |
| Node* effect = graph()->start(); |
| Node* control = graph()->start(); |
| ElementAccess const access = {kTaggedBase, kTaggedSize, Type::Any(), |
| MachineType::AnyTagged(), kNoWriteBarrier}; |
| |
| StrictMock<MockAdvancedReducerEditor> editor; |
| LoadElimination load_elimination(&editor, jsgraph(), zone()); |
| |
| load_elimination.Reduce(graph()->start()); |
| |
| Node* branch = graph()->NewNode(common()->Branch(), check, control); |
| |
| Node* if_true = graph()->NewNode(common()->IfTrue(), branch); |
| Node* etrue = graph()->NewNode(simplified()->LoadElement(access), object, |
| index, effect, if_true); |
| load_elimination.Reduce(etrue); |
| |
| Node* if_false = graph()->NewNode(common()->IfFalse(), branch); |
| Node* efalse = effect; |
| |
| control = graph()->NewNode(common()->Merge(2), if_true, if_false); |
| effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); |
| load_elimination.Reduce(effect); |
| |
| Node* load = graph()->NewNode(simplified()->LoadElement(access), object, |
| index, effect, control); |
| Reduction r = load_elimination.Reduce(load); |
| ASSERT_TRUE(r.Changed()); |
| EXPECT_EQ(load, r.replacement()); |
| } |
| |
| TEST_F(LoadEliminationTest, LoadElementOnFalseBranchOfDiamond) { |
| Node* object = Parameter(Type::Any(), 0); |
| Node* index = Parameter(Type::UnsignedSmall(), 1); |
| Node* check = Parameter(Type::Boolean(), 2); |
| Node* effect = graph()->start(); |
| Node* control = graph()->start(); |
| ElementAccess const access = {kTaggedBase, kTaggedSize, Type::Any(), |
| MachineType::AnyTagged(), kNoWriteBarrier}; |
| |
| StrictMock<MockAdvancedReducerEditor> editor; |
| LoadElimination load_elimination(&editor, jsgraph(), zone()); |
| |
| load_elimination.Reduce(graph()->start()); |
| |
| Node* branch = graph()->NewNode(common()->Branch(), check, control); |
| |
| Node* if_true = graph()->NewNode(common()->IfTrue(), branch); |
| Node* etrue = effect; |
| |
| Node* if_false = graph()->NewNode(common()->IfFalse(), branch); |
| Node* efalse = graph()->NewNode(simplified()->LoadElement(access), object, |
| index, effect, if_false); |
| load_elimination.Reduce(efalse); |
| |
| control = graph()->NewNode(common()->Merge(2), if_true, if_false); |
| effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); |
| load_elimination.Reduce(effect); |
| |
| Node* load = graph()->NewNode(simplified()->LoadElement(access), object, |
| index, effect, control); |
| Reduction r = load_elimination.Reduce(load); |
| ASSERT_TRUE(r.Changed()); |
| EXPECT_EQ(load, r.replacement()); |
| } |
| |
| TEST_F(LoadEliminationTest, LoadFieldOnFalseBranchOfDiamond) { |
| Node* object = Parameter(Type::Any(), 0); |
| Node* check = Parameter(Type::Boolean(), 1); |
| Node* effect = graph()->start(); |
| Node* control = graph()->start(); |
| FieldAccess const access = {kTaggedBase, kTaggedSize, |
| MaybeHandle<Name>(), MaybeHandle<Map>(), |
| Type::Any(), MachineType::AnyTagged(), |
| kNoWriteBarrier}; |
| |
| StrictMock<MockAdvancedReducerEditor> editor; |
| LoadElimination load_elimination(&editor, jsgraph(), zone()); |
| |
| load_elimination.Reduce(graph()->start()); |
| |
| Node* branch = graph()->NewNode(common()->Branch(), check, control); |
| |
| Node* if_true = graph()->NewNode(common()->IfTrue(), branch); |
| Node* etrue = effect; |
| |
| Node* if_false = graph()->NewNode(common()->IfFalse(), branch); |
| Node* efalse = graph()->NewNode(simplified()->LoadField(access), object, |
| effect, if_false); |
| load_elimination.Reduce(efalse); |
| |
| control = graph()->NewNode(common()->Merge(2), if_true, if_false); |
| effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); |
| load_elimination.Reduce(effect); |
| |
| Node* load = graph()->NewNode(simplified()->LoadField(access), object, effect, |
| control); |
| Reduction r = load_elimination.Reduce(load); |
| ASSERT_TRUE(r.Changed()); |
| EXPECT_EQ(load, r.replacement()); |
| } |
| |
| TEST_F(LoadEliminationTest, LoadFieldOnTrueBranchOfDiamond) { |
| Node* object = Parameter(Type::Any(), 0); |
| Node* check = Parameter(Type::Boolean(), 1); |
| Node* effect = graph()->start(); |
| Node* control = graph()->start(); |
| FieldAccess const access = {kTaggedBase, kTaggedSize, |
| MaybeHandle<Name>(), MaybeHandle<Map>(), |
| Type::Any(), MachineType::AnyTagged(), |
| kNoWriteBarrier}; |
| |
| StrictMock<MockAdvancedReducerEditor> editor; |
| LoadElimination load_elimination(&editor, jsgraph(), zone()); |
| |
| load_elimination.Reduce(graph()->start()); |
| |
| Node* branch = graph()->NewNode(common()->Branch(), check, control); |
| |
| Node* if_true = graph()->NewNode(common()->IfTrue(), branch); |
| Node* etrue = graph()->NewNode(simplified()->LoadField(access), object, |
| effect, if_true); |
| load_elimination.Reduce(etrue); |
| |
| Node* if_false = graph()->NewNode(common()->IfFalse(), branch); |
| Node* efalse = effect; |
| |
| control = graph()->NewNode(common()->Merge(2), if_true, if_false); |
| effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); |
| load_elimination.Reduce(effect); |
| |
| Node* load = graph()->NewNode(simplified()->LoadField(access), object, effect, |
| control); |
| Reduction r = load_elimination.Reduce(load); |
| ASSERT_TRUE(r.Changed()); |
| EXPECT_EQ(load, r.replacement()); |
| } |
| |
| TEST_F(LoadEliminationTest, LoadFieldWithTypeMismatch) { |
| Node* object = Parameter(Type::Any(), 0); |
| Node* value = Parameter(Type::Signed32(), 1); |
| Node* effect = graph()->start(); |
| Node* control = graph()->start(); |
| FieldAccess const access = {kTaggedBase, kTaggedSize, |
| MaybeHandle<Name>(), MaybeHandle<Map>(), |
| Type::Unsigned31(), MachineType::AnyTagged(), |
| kNoWriteBarrier}; |
| |
| StrictMock<MockAdvancedReducerEditor> editor; |
| LoadElimination load_elimination(&editor, jsgraph(), zone()); |
| |
| load_elimination.Reduce(graph()->start()); |
| |
| effect = graph()->NewNode(simplified()->StoreField(access), object, value, |
| effect, control); |
| load_elimination.Reduce(effect); |
| |
| Node* load = graph()->NewNode(simplified()->LoadField(access), object, effect, |
| control); |
| EXPECT_CALL(editor, ReplaceWithValue(load, IsTypeGuard(value, _), _, _)); |
| Reduction r = load_elimination.Reduce(load); |
| ASSERT_TRUE(r.Changed()); |
| EXPECT_THAT(r.replacement(), IsTypeGuard(value, _)); |
| } |
| |
| TEST_F(LoadEliminationTest, LoadElementWithTypeMismatch) { |
| Node* object = Parameter(Type::Any(), 0); |
| Node* index = Parameter(Type::UnsignedSmall(), 1); |
| Node* value = Parameter(Type::Signed32(), 2); |
| Node* effect = graph()->start(); |
| Node* control = graph()->start(); |
| ElementAccess const access = {kTaggedBase, kTaggedSize, Type::Unsigned31(), |
| MachineType::AnyTagged(), kNoWriteBarrier}; |
| |
| StrictMock<MockAdvancedReducerEditor> editor; |
| LoadElimination load_elimination(&editor, jsgraph(), zone()); |
| |
| load_elimination.Reduce(graph()->start()); |
| |
| effect = graph()->NewNode(simplified()->StoreElement(access), object, index, |
| value, effect, control); |
| load_elimination.Reduce(effect); |
| |
| Node* load = graph()->NewNode(simplified()->LoadElement(access), object, |
| index, effect, control); |
| Reduction r = load_elimination.Reduce(load); |
| ASSERT_TRUE(r.Changed()); |
| EXPECT_EQ(load, r.replacement()); |
| } |
| |
| TEST_F(LoadEliminationTest, AliasAnalysisForFinishRegion) { |
| Node* value0 = Parameter(Type::Signed32(), 0); |
| Node* value1 = Parameter(Type::Signed32(), 1); |
| Node* effect = graph()->start(); |
| Node* control = graph()->start(); |
| FieldAccess const access = {kTaggedBase, kTaggedSize, |
| MaybeHandle<Name>(), MaybeHandle<Map>(), |
| Type::Signed32(), MachineType::AnyTagged(), |
| kNoWriteBarrier}; |
| |
| StrictMock<MockAdvancedReducerEditor> editor; |
| LoadElimination load_elimination(&editor, jsgraph(), zone()); |
| |
| load_elimination.Reduce(effect); |
| |
| effect = graph()->NewNode( |
| common()->BeginRegion(RegionObservability::kNotObservable), effect); |
| load_elimination.Reduce(effect); |
| |
| Node* object0 = effect = graph()->NewNode( |
| simplified()->Allocate(Type::Any(), AllocationType::kYoung), |
| jsgraph()->Constant(16), effect, control); |
| load_elimination.Reduce(effect); |
| |
| Node* region0 = effect = |
| graph()->NewNode(common()->FinishRegion(), object0, effect); |
| load_elimination.Reduce(effect); |
| |
| effect = graph()->NewNode( |
| common()->BeginRegion(RegionObservability::kNotObservable), effect); |
| load_elimination.Reduce(effect); |
| |
| Node* object1 = effect = graph()->NewNode( |
| simplified()->Allocate(Type::Any(), AllocationType::kYoung), |
| jsgraph()->Constant(16), effect, control); |
| load_elimination.Reduce(effect); |
| |
| Node* region1 = effect = |
| graph()->NewNode(common()->FinishRegion(), object1, effect); |
| load_elimination.Reduce(effect); |
| |
| effect = graph()->NewNode(simplified()->StoreField(access), region0, value0, |
| effect, control); |
| load_elimination.Reduce(effect); |
| |
| effect = graph()->NewNode(simplified()->StoreField(access), region1, value1, |
| effect, control); |
| load_elimination.Reduce(effect); |
| |
| Node* load = graph()->NewNode(simplified()->LoadField(access), region0, |
| effect, control); |
| EXPECT_CALL(editor, ReplaceWithValue(load, value0, effect, _)); |
| Reduction r = load_elimination.Reduce(load); |
| ASSERT_TRUE(r.Changed()); |
| EXPECT_EQ(value0, r.replacement()); |
| } |
| |
| } // namespace compiler |
| } // namespace internal |
| } // namespace v8 |