blob: 6f42d96154c7393d0925bd588ee960b8ed863808 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "side_effects_analysis.h"
#include "load_store_analysis.h"
#include "load_store_elimination.h"
#include "nodes.h"
#include "optimizing_unit_test.h"
#include "gtest/gtest.h"
namespace art {
class LoadStoreEliminationTest : public OptimizingUnitTest {
public:
LoadStoreEliminationTest() : pool_() {}
void PerformLSE() {
graph_->BuildDominatorTree();
SideEffectsAnalysis side_effects(graph_);
side_effects.Run();
LoadStoreAnalysis lsa(graph_);
lsa.Run();
LoadStoreElimination lse(graph_, side_effects, lsa, nullptr);
lse.Run();
}
void CreateTestControlFlowGraph() {
graph_ = CreateGraph();
entry_ = new (GetAllocator()) HBasicBlock(graph_);
pre_header_ = new (GetAllocator()) HBasicBlock(graph_);
loop_header_ = new (GetAllocator()) HBasicBlock(graph_);
loop_body_ = new (GetAllocator()) HBasicBlock(graph_);
exit_ = new (GetAllocator()) HBasicBlock(graph_);
graph_->AddBlock(entry_);
graph_->AddBlock(pre_header_);
graph_->AddBlock(loop_header_);
graph_->AddBlock(loop_body_);
graph_->AddBlock(exit_);
graph_->SetEntryBlock(entry_);
// This common CFG has been used by all cases in this load_store_elimination_test.
// entry
// |
// pre_header
// |
// loop_header <--+
// | |
// loop_body -----+
// |
// exit
entry_->AddSuccessor(pre_header_);
pre_header_->AddSuccessor(loop_header_);
loop_header_->AddSuccessor(exit_); // true successor
loop_header_->AddSuccessor(loop_body_); // false successor
loop_body_->AddSuccessor(loop_header_);
HInstruction* c0 = graph_->GetIntConstant(0);
HInstruction* c1 = graph_->GetIntConstant(1);
HInstruction* c4 = graph_->GetIntConstant(4);
HInstruction* c128 = graph_->GetIntConstant(128);
// entry block has following instructions:
// array, i, j, i+1, i+4.
array_ = new (GetAllocator()) HParameterValue(graph_->GetDexFile(),
dex::TypeIndex(0),
0,
DataType::Type::kReference);
i_ = new (GetAllocator()) HParameterValue(graph_->GetDexFile(),
dex::TypeIndex(1),
1,
DataType::Type::kInt32);
j_ = new (GetAllocator()) HParameterValue(graph_->GetDexFile(),
dex::TypeIndex(1),
2,
DataType::Type::kInt32);
i_add1_ = new (GetAllocator()) HAdd(DataType::Type::kInt32, i_, c1);
i_add4_ = new (GetAllocator()) HAdd(DataType::Type::kInt32, i_, c4);
entry_->AddInstruction(array_);
entry_->AddInstruction(i_);
entry_->AddInstruction(j_);
entry_->AddInstruction(i_add1_);
entry_->AddInstruction(i_add4_);
entry_->AddInstruction(new (GetAllocator()) HGoto());
// pre_header block
pre_header_->AddInstruction(new (GetAllocator()) HGoto());
// loop header block has following instructions:
// phi = 0;
// if (phi >= 128);
phi_ = new (GetAllocator()) HPhi(GetAllocator(), 0, 0, DataType::Type::kInt32);
cmp_ = new (GetAllocator()) HGreaterThanOrEqual(phi_, c128);
if_ = new (GetAllocator()) HIf(cmp_);
loop_header_->AddPhi(phi_);
loop_header_->AddInstruction(cmp_);
loop_header_->AddInstruction(if_);
phi_->AddInput(c0);
// loop body block has following instructions:
// phi++;
HInstruction* inc_phi = new (GetAllocator()) HAdd(DataType::Type::kInt32, phi_, c1);
loop_body_->AddInstruction(inc_phi);
loop_body_->AddInstruction(new (GetAllocator()) HGoto());
phi_->AddInput(inc_phi);
// exit block
exit_->AddInstruction(new (GetAllocator()) HExit());
}
// To avoid tedious HIR assembly in test functions.
HInstruction* AddVecLoad(HBasicBlock* block, HInstruction* array, HInstruction* index) {
DCHECK(block != nullptr);
DCHECK(array != nullptr);
DCHECK(index != nullptr);
HInstruction* vload = new (GetAllocator()) HVecLoad(
GetAllocator(),
array,
index,
DataType::Type::kInt32,
SideEffects::ArrayReadOfType(DataType::Type::kInt32),
4,
/*is_string_char_at*/ false,
kNoDexPc);
block->InsertInstructionBefore(vload, block->GetLastInstruction());
return vload;
}
HInstruction* AddVecStore(HBasicBlock* block,
HInstruction* array,
HInstruction* index,
HVecOperation* vdata = nullptr) {
DCHECK(block != nullptr);
DCHECK(array != nullptr);
DCHECK(index != nullptr);
if (vdata == nullptr) {
HInstruction* c1 = graph_->GetIntConstant(1);
vdata = new (GetAllocator()) HVecReplicateScalar(GetAllocator(),
c1,
DataType::Type::kInt32,
4,
kNoDexPc);
block->InsertInstructionBefore(vdata, block->GetLastInstruction());
}
HInstruction* vstore = new (GetAllocator()) HVecStore(
GetAllocator(),
array,
index,
vdata,
DataType::Type::kInt32,
SideEffects::ArrayWriteOfType(DataType::Type::kInt32),
4,
kNoDexPc);
block->InsertInstructionBefore(vstore, block->GetLastInstruction());
return vstore;
}
HInstruction* AddArrayGet(HBasicBlock* block, HInstruction* array, HInstruction* index) {
DCHECK(block != nullptr);
DCHECK(array != nullptr);
DCHECK(index != nullptr);
HInstruction* get = new (GetAllocator()) HArrayGet(array, index, DataType::Type::kInt32, 0);
block->InsertInstructionBefore(get, block->GetLastInstruction());
return get;
}
HInstruction* AddArraySet(HBasicBlock* block,
HInstruction* array,
HInstruction* index,
HInstruction* data = nullptr) {
DCHECK(block != nullptr);
DCHECK(array != nullptr);
DCHECK(index != nullptr);
if (data == nullptr) {
data = graph_->GetIntConstant(1);
}
HInstruction* store = new (GetAllocator()) HArraySet(array,
index,
data,
DataType::Type::kInt32,
0);
block->InsertInstructionBefore(store, block->GetLastInstruction());
return store;
}
ArenaPool pool_;
HGraph* graph_;
HBasicBlock* entry_;
HBasicBlock* pre_header_;
HBasicBlock* loop_header_;
HBasicBlock* loop_body_;
HBasicBlock* exit_;
HInstruction* array_;
HInstruction* i_;
HInstruction* j_;
HInstruction* i_add1_;
HInstruction* i_add4_;
HPhi* phi_;
HInstruction* cmp_;
HInstruction* if_;
};
TEST_F(LoadStoreEliminationTest, ArrayGetSetElimination) {
CreateTestControlFlowGraph();
HInstruction* c1 = graph_->GetIntConstant(1);
HInstruction* c2 = graph_->GetIntConstant(2);
HInstruction* c3 = graph_->GetIntConstant(3);
// array[1] = 1;
// x = array[1]; <--- Remove.
// y = array[2];
// array[1] = 1; <--- Remove, since it stores same value.
// array[i] = 3; <--- MAY alias.
// array[1] = 1; <--- Cannot remove, even if it stores the same value.
AddArraySet(entry_, array_, c1, c1);
HInstruction* load1 = AddArrayGet(entry_, array_, c1);
HInstruction* load2 = AddArrayGet(entry_, array_, c2);
HInstruction* store1 = AddArraySet(entry_, array_, c1, c1);
AddArraySet(entry_, array_, i_, c3);
HInstruction* store2 = AddArraySet(entry_, array_, c1, c1);
PerformLSE();
ASSERT_TRUE(IsRemoved(load1));
ASSERT_FALSE(IsRemoved(load2));
ASSERT_TRUE(IsRemoved(store1));
ASSERT_FALSE(IsRemoved(store2));
}
TEST_F(LoadStoreEliminationTest, SameHeapValue) {
CreateTestControlFlowGraph();
HInstruction* c1 = graph_->GetIntConstant(1);
HInstruction* c2 = graph_->GetIntConstant(2);
// Test LSE handling same value stores on array.
// array[1] = 1;
// array[2] = 1;
// array[1] = 1; <--- Can remove.
// array[1] = 2; <--- Can NOT remove.
AddArraySet(entry_, array_, c1, c1);
AddArraySet(entry_, array_, c2, c1);
HInstruction* store1 = AddArraySet(entry_, array_, c1, c1);
HInstruction* store2 = AddArraySet(entry_, array_, c1, c2);
// Test LSE handling same value stores on vector.
// vdata = [0x1, 0x2, 0x3, 0x4, ...]
// VecStore array[i...] = vdata;
// VecStore array[j...] = vdata; <--- MAY ALIAS.
// VecStore array[i...] = vdata; <--- Cannot Remove, even if it's same value.
AddVecStore(entry_, array_, i_);
AddVecStore(entry_, array_, j_);
HInstruction* vstore1 = AddVecStore(entry_, array_, i_);
// VecStore array[i...] = vdata;
// VecStore array[i+1...] = vdata; <--- MAY alias due to partial overlap.
// VecStore array[i...] = vdata; <--- Cannot remove, even if it's same value.
AddVecStore(entry_, array_, i_);
AddVecStore(entry_, array_, i_add1_);
HInstruction* vstore2 = AddVecStore(entry_, array_, i_);
PerformLSE();
ASSERT_TRUE(IsRemoved(store1));
ASSERT_FALSE(IsRemoved(store2));
ASSERT_FALSE(IsRemoved(vstore1));
ASSERT_FALSE(IsRemoved(vstore2));
}
TEST_F(LoadStoreEliminationTest, OverlappingLoadStore) {
CreateTestControlFlowGraph();
HInstruction* c1 = graph_->GetIntConstant(1);
// Test LSE handling array LSE when there is vector store in between.
// a[i] = 1;
// .. = a[i]; <-- Remove.
// a[i,i+1,i+2,i+3] = data; <-- PARTIAL OVERLAP !
// .. = a[i]; <-- Cannot remove.
AddArraySet(entry_, array_, i_, c1);
HInstruction* load1 = AddArrayGet(entry_, array_, i_);
AddVecStore(entry_, array_, i_);
HInstruction* load2 = AddArrayGet(entry_, array_, i_);
// Test LSE handling vector load/store partial overlap.
// a[i,i+1,i+2,i+3] = data;
// a[i+4,i+5,i+6,i+7] = data;
// .. = a[i,i+1,i+2,i+3];
// .. = a[i+4,i+5,i+6,i+7];
// a[i+1,i+2,i+3,i+4] = data; <-- PARTIAL OVERLAP !
// .. = a[i,i+1,i+2,i+3];
// .. = a[i+4,i+5,i+6,i+7];
AddVecStore(entry_, array_, i_);
AddVecStore(entry_, array_, i_add4_);
HInstruction* vload1 = AddVecLoad(entry_, array_, i_);
HInstruction* vload2 = AddVecLoad(entry_, array_, i_add4_);
AddVecStore(entry_, array_, i_add1_);
HInstruction* vload3 = AddVecLoad(entry_, array_, i_);
HInstruction* vload4 = AddVecLoad(entry_, array_, i_add4_);
// Test LSE handling vector LSE when there is array store in between.
// a[i,i+1,i+2,i+3] = data;
// a[i+1] = 1; <-- PARTIAL OVERLAP !
// .. = a[i,i+1,i+2,i+3];
AddVecStore(entry_, array_, i_);
AddArraySet(entry_, array_, i_, c1);
HInstruction* vload5 = AddVecLoad(entry_, array_, i_);
PerformLSE();
ASSERT_TRUE(IsRemoved(load1));
ASSERT_FALSE(IsRemoved(load2));
ASSERT_TRUE(IsRemoved(vload1));
ASSERT_TRUE(IsRemoved(vload2));
ASSERT_FALSE(IsRemoved(vload3));
ASSERT_FALSE(IsRemoved(vload4));
ASSERT_FALSE(IsRemoved(vload5));
}
// function (int[] a, int j) {
// a[j] = 1;
// for (int i=0; i<128; i++) {
// /* doesn't do any write */
// }
// a[j] = 1;
TEST_F(LoadStoreEliminationTest, Loop1) {
CreateTestControlFlowGraph();
HInstruction* c1 = graph_->GetIntConstant(1);
// a[j] = 1
AddArraySet(pre_header_, array_, j_, c1);
// LOOP BODY:
// .. = a[i,i+1,i+2,i+3];
AddVecLoad(loop_body_, array_, phi_);
// a[j] = 1;
HInstruction* array_set = AddArraySet(exit_, array_, j_, c1);
PerformLSE();
ASSERT_TRUE(IsRemoved(array_set));
}
// function (int[] a, int index) {
// a[index] = 1;
// int[] b = new int[128];
// for (int i=0; i<128; i++) {
// a[i,i+1,i+2,i+3] = vdata;
// b[i,i+1,i+2,i+3] = a[i,i+1,i+2,i+3];
// }
// a[index] = 1;
// }
TEST_F(LoadStoreEliminationTest, Loop2) {
CreateTestControlFlowGraph();
HInstruction* c0 = graph_->GetIntConstant(0);
HInstruction* c1 = graph_->GetIntConstant(1);
HInstruction* c128 = graph_->GetIntConstant(128);
HInstruction* array_b = new (GetAllocator()) HNewArray(c0, c128, 0);
entry_->AddInstruction(array_b);
// a[index] = 1;
AddArraySet(pre_header_, array_, i_, c1);
// a[i,i+1,i+2,i+3] = vdata;
// b[i,i+1,i+2,i+3] = a[i,i+1,i+2,i+3];
AddVecStore(loop_body_, array_, phi_);
HInstruction* vload = AddVecLoad(loop_body_, array_, phi_);
AddVecStore(loop_body_, array_b, phi_, vload->AsVecLoad());
// a[index] = 1;
HInstruction* a_set = AddArraySet(exit_, array_, i_, c1);
PerformLSE();
ASSERT_TRUE(IsRemoved(vload));
ASSERT_FALSE(IsRemoved(a_set)); // Cannot remove due to side effect in loop.
}
} // namespace art