| /* |
| * Copyright (C) 2019 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 "load_store_elimination.h" |
| |
| #include <initializer_list> |
| #include <memory> |
| #include <tuple> |
| #include <variant> |
| |
| #include "base/iteration_range.h" |
| #include "compilation_kind.h" |
| #include "dex/dex_file_types.h" |
| #include "entrypoints/quick/quick_entrypoints.h" |
| #include "entrypoints/quick/quick_entrypoints_enum.h" |
| #include "gtest/gtest.h" |
| #include "handle_scope.h" |
| #include "load_store_analysis.h" |
| #include "nodes.h" |
| #include "optimizing/data_type.h" |
| #include "optimizing/instruction_simplifier.h" |
| #include "optimizing/optimizing_compiler_stats.h" |
| #include "optimizing_unit_test.h" |
| #include "scoped_thread_state_change.h" |
| |
| namespace art HIDDEN { |
| |
| static constexpr bool kDebugLseTests = false; |
| |
| #define CHECK_SUBROUTINE_FAILURE() \ |
| do { \ |
| if (HasFatalFailure()) { \ |
| return; \ |
| } \ |
| } while (false) |
| |
| template <typename SuperTest> |
| class LoadStoreEliminationTestBase : public SuperTest, public OptimizingUnitTestHelper { |
| public: |
| LoadStoreEliminationTestBase() { |
| this->use_boot_image_ = true; // Make the Runtime creation cheaper. |
| } |
| |
| void SetUp() override { |
| SuperTest::SetUp(); |
| if (kDebugLseTests) { |
| gLogVerbosity.compiler = true; |
| } |
| } |
| |
| void TearDown() override { |
| SuperTest::TearDown(); |
| if (kDebugLseTests) { |
| gLogVerbosity.compiler = false; |
| } |
| } |
| |
| void PerformLSE() { |
| graph_->BuildDominatorTree(); |
| LoadStoreElimination lse(graph_, /*stats=*/nullptr); |
| lse.Run(); |
| std::ostringstream oss; |
| EXPECT_TRUE(CheckGraph(oss)) << oss.str(); |
| } |
| |
| void PerformLSE(const AdjacencyListGraph& blks) { |
| // PerformLSE expects this to be empty, and the creation of |
| // an `AdjacencyListGraph` computes it. |
| graph_->ClearDominanceInformation(); |
| if (kDebugLseTests) { |
| LOG(INFO) << "Pre LSE " << blks; |
| } |
| PerformLSE(); |
| if (kDebugLseTests) { |
| LOG(INFO) << "Post LSE " << blks; |
| } |
| } |
| |
| // Create instructions shared among tests. |
| void CreateEntryBlockInstructions() { |
| HInstruction* c1 = graph_->GetIntConstant(1); |
| HInstruction* c4 = graph_->GetIntConstant(4); |
| i_add1_ = MakeBinOp<HAdd>(entry_block_, DataType::Type::kInt32, i_, c1); |
| i_add4_ = MakeBinOp<HAdd>(entry_block_, DataType::Type::kInt32, i_, c4); |
| MakeGoto(entry_block_); |
| } |
| |
| // Create suspend check, linear loop variable and loop condition. |
| // The `HPhi` for the loop variable can be easily retrieved as the only `HPhi` in the loop block. |
| // The `HSuspendCheck` can be retrieved as the first non-Phi instruction from the loop block. |
| void MakeSimpleLoopInstructions(HBasicBlock* loop, |
| HBasicBlock* body, |
| std::initializer_list<HInstruction*> suspend_check_env = {}) { |
| CHECK(loop->GetInstructions().IsEmpty()); |
| CHECK_IMPLIES(loop != body, body->IsSingleGoto()); |
| HInstruction* c128 = graph_->GetIntConstant(128); |
| MakeSuspendCheck(loop, suspend_check_env); |
| auto [phi, increment] = MakeLinearLoopVar(loop, body, /*initial=*/ 0, /*increment=*/ 1); |
| HInstruction* cmp = MakeCondition(loop, kCondGE, phi, c128); |
| MakeIf(loop, cmp); |
| } |
| |
| // Create a do-while loop with instructions: |
| // i = 0; |
| // do { |
| // HSuspendCheck; |
| // cmp = i < 128; |
| // ++i; |
| // } while (cmp); |
| // Return the pre-header and loop block. |
| std::tuple<HBasicBlock*, HBasicBlock*> CreateDoWhileLoopWithInstructions( |
| HBasicBlock* loop_exit, std::initializer_list<HInstruction*> suspend_check_env = {}) { |
| auto [pre_header, loop, back_edge] = CreateWhileLoop(loop_exit); |
| MakeSimpleLoopInstructions(loop, loop, suspend_check_env); |
| return {pre_header, loop}; |
| } |
| |
| // Create a for loop with instructions: |
| // for (int i = 0; i < 128; ++i) { |
| // HSuspendCheck; |
| // } |
| // Return the pre-header, header and body blocks. |
| std::tuple<HBasicBlock*, HBasicBlock*, HBasicBlock*> CreateForLoopWithInstructions( |
| HBasicBlock* loop_exit, std::initializer_list<HInstruction*> suspend_check_env = {}) { |
| auto [pre_header, loop_header, loop_body] = CreateWhileLoop(loop_exit); |
| MakeSimpleLoopInstructions(loop_header, loop_body, suspend_check_env); |
| return {pre_header, loop_header, loop_body}; |
| } |
| |
| // Create the major CFG used by tests: |
| // entry |
| // | |
| // pre_header |
| // | |
| // loop[] |
| // | |
| // return |
| // | |
| // exit |
| void CreateTestControlFlowGraph() { |
| InitGraphAndParameters(); |
| CreateEntryBlockInstructions(); |
| std::tie(pre_header_, loop_) = |
| CreateDoWhileLoopWithInstructions(return_block_, /*suspend_check_env=*/ {array_, i_, j_}); |
| phi_ = loop_->GetFirstPhi()->AsPhi(); |
| suspend_check_ = loop_->GetFirstInstruction()->AsSuspendCheck(); |
| } |
| |
| // Create the diamond-shaped CFG: |
| // upper |
| // / \ |
| // left right |
| // \ / |
| // down |
| // |
| // Return: the basic blocks forming the CFG in the following order {upper, left, right, down}. |
| std::tuple<HBasicBlock*, HBasicBlock*, HBasicBlock*, HBasicBlock*> CreateDiamondShapedCFG() { |
| InitGraphAndParameters(); |
| CreateEntryBlockInstructions(); |
| |
| auto [upper, left, right] = CreateDiamondPattern(return_block_); |
| |
| HInstruction* cmp = MakeCondition(upper, kCondGE, i_, j_); |
| MakeIf(upper, cmp); |
| |
| return std::make_tuple(upper, left, right, return_block_); |
| } |
| |
| // Add a HVecLoad instruction to the end of the provided basic block. |
| // |
| // Return: the created HVecLoad instruction. |
| 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; |
| } |
| |
| // Add a HVecStore instruction to the end of the provided basic block. |
| // If no vdata is specified, generate HVecStore: array[index] = [1,1,1,1]. |
| // |
| // Return: the created HVecStore instruction. |
| HInstruction* AddVecStore(HBasicBlock* block, |
| HInstruction* array, |
| HInstruction* index, |
| HInstruction* 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; |
| } |
| |
| void InitGraphAndParameters() { |
| return_block_ = InitEntryMainExitGraphWithReturnVoid(); |
| array_ = MakeParam(DataType::Type::kInt32); |
| i_ = MakeParam(DataType::Type::kInt32); |
| j_ = MakeParam(DataType::Type::kInt32); |
| } |
| |
| HBasicBlock* return_block_; |
| HBasicBlock* pre_header_; |
| HBasicBlock* loop_; |
| |
| HInstruction* array_; |
| HInstruction* i_; |
| HInstruction* j_; |
| HInstruction* i_add1_; |
| HInstruction* i_add4_; |
| HInstruction* suspend_check_; |
| |
| HPhi* phi_; |
| }; |
| |
| class LoadStoreEliminationTest : public LoadStoreEliminationTestBase<CommonCompilerTest> {}; |
| |
| 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. |
| MakeArraySet(entry_block_, array_, c1, c1); |
| HInstruction* load1 = MakeArrayGet(entry_block_, array_, c1, DataType::Type::kInt32); |
| HInstruction* load2 = MakeArrayGet(entry_block_, array_, c2, DataType::Type::kInt32); |
| HInstruction* store1 = MakeArraySet(entry_block_, array_, c1, c1); |
| MakeArraySet(entry_block_, array_, i_, c3); |
| HInstruction* store2 = MakeArraySet(entry_block_, array_, c1, c1); |
| |
| PerformLSE(); |
| |
| ASSERT_TRUE(IsRemoved(load1)); |
| ASSERT_FALSE(IsRemoved(load2)); |
| ASSERT_TRUE(IsRemoved(store1)); |
| ASSERT_FALSE(IsRemoved(store2)); |
| } |
| |
| TEST_F(LoadStoreEliminationTest, SameHeapValue1) { |
| 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. |
| MakeArraySet(entry_block_, array_, c1, c1); |
| MakeArraySet(entry_block_, array_, c2, c1); |
| HInstruction* store1 = MakeArraySet(entry_block_, array_, c1, c1); |
| HInstruction* store2 = MakeArraySet(entry_block_, array_, c1, c2); |
| |
| PerformLSE(); |
| |
| ASSERT_TRUE(IsRemoved(store1)); |
| ASSERT_FALSE(IsRemoved(store2)); |
| } |
| |
| TEST_F(LoadStoreEliminationTest, SameHeapValue2) { |
| CreateTestControlFlowGraph(); |
| |
| // 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_block_, array_, i_); |
| AddVecStore(entry_block_, array_, j_); |
| HInstruction* vstore = AddVecStore(entry_block_, array_, i_); |
| |
| // TODO: enable LSE for graphs with predicated SIMD. |
| graph_->SetHasTraditionalSIMD(true); |
| PerformLSE(); |
| |
| ASSERT_FALSE(IsRemoved(vstore)); |
| } |
| |
| TEST_F(LoadStoreEliminationTest, SameHeapValue3) { |
| CreateTestControlFlowGraph(); |
| |
| // 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_block_, array_, i_); |
| AddVecStore(entry_block_, array_, i_add1_); |
| HInstruction* vstore = AddVecStore(entry_block_, array_, i_); |
| |
| // TODO: enable LSE for graphs with predicated SIMD. |
| graph_->SetHasTraditionalSIMD(true); |
| PerformLSE(); |
| |
| ASSERT_FALSE(IsRemoved(vstore)); |
| } |
| |
| 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. |
| MakeArraySet(entry_block_, array_, i_, c1); |
| HInstruction* load1 = MakeArrayGet(entry_block_, array_, i_, DataType::Type::kInt32); |
| AddVecStore(entry_block_, array_, i_); |
| HInstruction* load2 = MakeArrayGet(entry_block_, array_, i_, DataType::Type::kInt32); |
| |
| // 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_block_, array_, i_); |
| AddVecStore(entry_block_, array_, i_add4_); |
| HInstruction* vload1 = AddVecLoad(entry_block_, array_, i_); |
| HInstruction* vload2 = AddVecLoad(entry_block_, array_, i_add4_); |
| AddVecStore(entry_block_, array_, i_add1_); |
| HInstruction* vload3 = AddVecLoad(entry_block_, array_, i_); |
| HInstruction* vload4 = AddVecLoad(entry_block_, 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_block_, array_, i_); |
| MakeArraySet(entry_block_, array_, i_, c1); |
| HInstruction* vload5 = AddVecLoad(entry_block_, array_, i_); |
| |
| // TODO: enable LSE for graphs with predicated SIMD. |
| graph_->SetHasTraditionalSIMD(true); |
| 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, StoreAfterLoopWithoutSideEffects) { |
| CreateTestControlFlowGraph(); |
| |
| HInstruction* c1 = graph_->GetIntConstant(1); |
| |
| // a[j] = 1 |
| MakeArraySet(pre_header_, array_, j_, c1); |
| |
| // LOOP BODY: |
| // .. = a[i,i+1,i+2,i+3]; |
| AddVecLoad(loop_, array_, phi_); |
| |
| // a[j] = 1; |
| HInstruction* array_set = MakeArraySet(return_block_, array_, j_, c1); |
| |
| // TODO: enable LSE for graphs with predicated SIMD. |
| graph_->SetHasTraditionalSIMD(true); |
| PerformLSE(); |
| |
| ASSERT_TRUE(IsRemoved(array_set)); |
| } |
| |
| // function (int[] a, int j) { |
| // int[] b = new int[128]; |
| // a[j] = 0; |
| // for (int phi=0; phi<128; phi++) { |
| // a[phi,phi+1,phi+2,phi+3] = [1,1,1,1]; |
| // b[phi,phi+1,phi+2,phi+3] = a[phi,phi+1,phi+2,phi+3]; |
| // } |
| // a[j] = 0; |
| // } |
| TEST_F(LoadStoreEliminationTest, StoreAfterSIMDLoopWithSideEffects) { |
| CreateTestControlFlowGraph(); |
| |
| HInstruction* c0 = graph_->GetIntConstant(0); |
| HInstruction* c128 = graph_->GetIntConstant(128); |
| |
| HInstruction* array_b = new (GetAllocator()) HNewArray(c0, c128, 0, 0); |
| pre_header_->InsertInstructionBefore(array_b, pre_header_->GetLastInstruction()); |
| array_b->CopyEnvironmentFrom(suspend_check_->GetEnvironment()); |
| |
| // a[j] = 0; |
| MakeArraySet(pre_header_, array_, j_, c0); |
| |
| // LOOP BODY: |
| // a[phi,phi+1,phi+2,phi+3] = [1,1,1,1]; |
| // b[phi,phi+1,phi+2,phi+3] = a[phi,phi+1,phi+2,phi+3]; |
| AddVecStore(loop_, array_, phi_); |
| HInstruction* vload = AddVecLoad(loop_, array_, phi_); |
| AddVecStore(loop_, array_b, phi_, vload); |
| |
| // a[j] = 0; |
| HInstruction* a_set = MakeArraySet(return_block_, array_, j_, c0); |
| |
| // TODO: enable LSE for graphs with predicated SIMD. |
| graph_->SetHasTraditionalSIMD(true); |
| PerformLSE(); |
| |
| ASSERT_TRUE(IsRemoved(vload)); |
| ASSERT_FALSE(IsRemoved(a_set)); // Cannot remove due to write side-effect in the loop. |
| } |
| |
| // function (int[] a, int j) { |
| // int[] b = new int[128]; |
| // a[j] = 0; |
| // for (int phi=0; phi<128; phi++) { |
| // a[phi,phi+1,phi+2,phi+3] = [1,1,1,1]; |
| // b[phi,phi+1,phi+2,phi+3] = a[phi,phi+1,phi+2,phi+3]; |
| // } |
| // x = a[j]; |
| // } |
| TEST_F(LoadStoreEliminationTest, LoadAfterSIMDLoopWithSideEffects) { |
| CreateTestControlFlowGraph(); |
| |
| HInstruction* c0 = graph_->GetIntConstant(0); |
| HInstruction* c128 = graph_->GetIntConstant(128); |
| |
| HInstruction* array_b = new (GetAllocator()) HNewArray(c0, c128, 0, 0); |
| pre_header_->InsertInstructionBefore(array_b, pre_header_->GetLastInstruction()); |
| array_b->CopyEnvironmentFrom(suspend_check_->GetEnvironment()); |
| |
| // a[j] = 0; |
| MakeArraySet(pre_header_, array_, j_, c0); |
| |
| // LOOP BODY: |
| // a[phi,phi+1,phi+2,phi+3] = [1,1,1,1]; |
| // b[phi,phi+1,phi+2,phi+3] = a[phi,phi+1,phi+2,phi+3]; |
| AddVecStore(loop_, array_, phi_); |
| HInstruction* vload = AddVecLoad(loop_, array_, phi_); |
| AddVecStore(loop_, array_b, phi_, vload); |
| |
| // x = a[j]; |
| HInstruction* load = MakeArrayGet(return_block_, array_, j_, DataType::Type::kInt32); |
| |
| // TODO: enable LSE for graphs with predicated SIMD. |
| graph_->SetHasTraditionalSIMD(true); |
| PerformLSE(); |
| |
| ASSERT_TRUE(IsRemoved(vload)); |
| ASSERT_FALSE(IsRemoved(load)); // Cannot remove due to write side-effect in the loop. |
| } |
| |
| // Check that merging works correctly when there are VecStors in predecessors. |
| // |
| // vstore1: a[i,... i + 3] = [1,...1] |
| // / \ |
| // / \ |
| // vstore2: a[i,... i + 3] = [1,...1] vstore3: a[i+1, ... i + 4] = [1, ... 1] |
| // \ / |
| // \ / |
| // vstore4: a[i,... i + 3] = [1,...1] |
| // |
| // Expected: |
| // 'vstore2' is removed. |
| // 'vstore3' is not removed. |
| // 'vstore4' is not removed. Such cases are not supported at the moment. |
| TEST_F(LoadStoreEliminationTest, MergePredecessorVecStores) { |
| auto [upper, left, right, down] = CreateDiamondShapedCFG(); |
| |
| // upper: a[i,... i + 3] = [1,...1] |
| HInstruction* vstore1 = AddVecStore(upper, array_, i_); |
| HInstruction* vdata = vstore1->InputAt(2); |
| |
| // left: a[i,... i + 3] = [1,...1] |
| HInstruction* vstore2 = AddVecStore(left, array_, i_, vdata); |
| |
| // right: a[i+1, ... i + 4] = [1, ... 1] |
| HInstruction* vstore3 = AddVecStore(right, array_, i_add1_, vdata); |
| |
| // down: a[i,... i + 3] = [1,...1] |
| HInstruction* vstore4 = AddVecStore(down, array_, i_, vdata); |
| |
| // TODO: enable LSE for graphs with predicated SIMD. |
| graph_->SetHasTraditionalSIMD(true); |
| PerformLSE(); |
| |
| ASSERT_TRUE(IsRemoved(vstore2)); |
| ASSERT_FALSE(IsRemoved(vstore3)); |
| ASSERT_FALSE(IsRemoved(vstore4)); |
| } |
| |
| // Check that merging works correctly when there are ArraySets in predecessors. |
| // |
| // a[i] = 1 |
| // / \ |
| // / \ |
| // store1: a[i] = 1 store2: a[i+1] = 1 |
| // \ / |
| // \ / |
| // store3: a[i] = 1 |
| // |
| // Expected: |
| // 'store1' is removed. |
| // 'store2' is not removed. |
| // 'store3' is removed. |
| TEST_F(LoadStoreEliminationTest, MergePredecessorStores) { |
| auto [upper, left, right, down] = CreateDiamondShapedCFG(); |
| |
| HInstruction* c1 = graph_->GetIntConstant(1); |
| |
| // upper: a[i] = 1 |
| MakeArraySet(upper, array_, i_, c1); |
| |
| // left: a[i] = 1 |
| HInstruction* store1 = MakeArraySet(left, array_, i_, c1); |
| |
| // right: a[i+1] = 1 |
| HInstruction* store2 = MakeArraySet(right, array_, i_add1_, c1); |
| |
| // down: a[i] = 1 |
| HInstruction* store3 = MakeArraySet(down, array_, i_, c1); |
| |
| PerformLSE(); |
| |
| ASSERT_TRUE(IsRemoved(store1)); |
| ASSERT_FALSE(IsRemoved(store2)); |
| ASSERT_TRUE(IsRemoved(store3)); |
| } |
| |
| // Check that redundant VStore/VLoad are removed from a SIMD loop. |
| // |
| // LOOP BODY |
| // vstore1: a[i,... i + 3] = [1,...1] |
| // vload: x = a[i,... i + 3] |
| // vstore2: b[i,... i + 3] = x |
| // vstore3: a[i,... i + 3] = [1,...1] |
| // |
| // Return 'a' from the method to make it escape. |
| // |
| // Expected: |
| // 'vstore1' is not removed. |
| // 'vload' is removed. |
| // 'vstore2' is removed because 'b' does not escape. |
| // 'vstore3' is removed. |
| TEST_F(LoadStoreEliminationTest, RedundantVStoreVLoadInLoop) { |
| CreateTestControlFlowGraph(); |
| |
| HInstruction* c0 = graph_->GetIntConstant(0); |
| HInstruction* c128 = graph_->GetIntConstant(128); |
| |
| HInstruction* array_a = new (GetAllocator()) HNewArray(c0, c128, 0, 0); |
| pre_header_->InsertInstructionBefore(array_a, pre_header_->GetLastInstruction()); |
| array_a->CopyEnvironmentFrom(suspend_check_->GetEnvironment()); |
| |
| ASSERT_TRUE(return_block_->GetLastInstruction()->IsReturnVoid()); |
| HInstruction* ret = new (GetAllocator()) HReturn(array_a); |
| return_block_->ReplaceAndRemoveInstructionWith(return_block_->GetLastInstruction(), ret); |
| |
| HInstruction* array_b = new (GetAllocator()) HNewArray(c0, c128, 0, 0); |
| pre_header_->InsertInstructionBefore(array_b, pre_header_->GetLastInstruction()); |
| array_b->CopyEnvironmentFrom(suspend_check_->GetEnvironment()); |
| |
| // LOOP BODY: |
| // a[i,... i + 3] = [1,...1] |
| // x = a[i,... i + 3] |
| // b[i,... i + 3] = x |
| // a[i,... i + 3] = [1,...1] |
| HInstruction* vstore1 = AddVecStore(loop_, array_a, phi_); |
| HInstruction* vload = AddVecLoad(loop_, array_a, phi_); |
| HInstruction* vstore2 = AddVecStore(loop_, array_b, phi_, vload); |
| HInstruction* vstore3 = AddVecStore(loop_, array_a, phi_, vstore1->InputAt(2)); |
| |
| // TODO: enable LSE for graphs with predicated SIMD. |
| graph_->SetHasTraditionalSIMD(true); |
| PerformLSE(); |
| |
| ASSERT_FALSE(IsRemoved(vstore1)); |
| ASSERT_TRUE(IsRemoved(vload)); |
| ASSERT_TRUE(IsRemoved(vstore2)); |
| ASSERT_TRUE(IsRemoved(vstore3)); |
| } |
| |
| // Loop writes invalidate only possibly aliased heap locations. |
| TEST_F(LoadStoreEliminationTest, StoreAfterLoopWithSideEffects) { |
| CreateTestControlFlowGraph(); |
| |
| HInstruction* c0 = graph_->GetIntConstant(0); |
| HInstruction* c2 = graph_->GetIntConstant(2); |
| HInstruction* c128 = graph_->GetIntConstant(128); |
| |
| // array[0] = 2; |
| // loop: |
| // b[i] = array[i] |
| // array[0] = 2 |
| HInstruction* store1 = MakeArraySet(entry_block_, array_, c0, c2); |
| |
| HInstruction* array_b = new (GetAllocator()) HNewArray(c0, c128, 0, 0); |
| pre_header_->InsertInstructionBefore(array_b, pre_header_->GetLastInstruction()); |
| array_b->CopyEnvironmentFrom(suspend_check_->GetEnvironment()); |
| |
| HInstruction* load = MakeArrayGet(loop_, array_, phi_, DataType::Type::kInt32); |
| HInstruction* store2 = MakeArraySet(loop_, array_b, phi_, load); |
| |
| HInstruction* store3 = MakeArraySet(return_block_, array_, c0, c2); |
| |
| PerformLSE(); |
| |
| ASSERT_FALSE(IsRemoved(store1)); |
| ASSERT_TRUE(IsRemoved(store2)); |
| ASSERT_TRUE(IsRemoved(store3)); |
| } |
| |
| // Loop writes invalidate only possibly aliased heap locations. |
| TEST_F(LoadStoreEliminationTest, StoreAfterLoopWithSideEffects2) { |
| CreateTestControlFlowGraph(); |
| |
| // Add another array parameter that may alias with `array_`. |
| // Note: We're not adding it to the suspend check environment. |
| HInstruction* array2 = MakeParam(DataType::Type::kInt32); |
| |
| HInstruction* c0 = graph_->GetIntConstant(0); |
| HInstruction* c2 = graph_->GetIntConstant(2); |
| |
| // array[0] = 2; |
| // loop: |
| // array2[i] = array[i] |
| // array[0] = 2 |
| HInstruction* store1 = MakeArraySet(pre_header_, array_, c0, c2); |
| |
| HInstruction* load = MakeArrayGet(loop_, array_, phi_, DataType::Type::kInt32); |
| HInstruction* store2 = MakeArraySet(loop_, array2, phi_, load); |
| |
| HInstruction* store3 = MakeArraySet(return_block_, array_, c0, c2); |
| |
| PerformLSE(); |
| |
| ASSERT_FALSE(IsRemoved(store1)); |
| ASSERT_FALSE(IsRemoved(store2)); |
| ASSERT_FALSE(IsRemoved(store3)); |
| } |
| |
| // As it is not allowed to use defaults for VecLoads, check if there is a new created array |
| // a VecLoad used in a loop and after it is not replaced with a default. |
| TEST_F(LoadStoreEliminationTest, VLoadDefaultValueInLoopWithoutWriteSideEffects) { |
| CreateTestControlFlowGraph(); |
| |
| HInstruction* c0 = graph_->GetIntConstant(0); |
| HInstruction* c128 = graph_->GetIntConstant(128); |
| |
| HInstruction* array_a = new (GetAllocator()) HNewArray(c0, c128, 0, 0); |
| pre_header_->InsertInstructionBefore(array_a, pre_header_->GetLastInstruction()); |
| array_a->CopyEnvironmentFrom(suspend_check_->GetEnvironment()); |
| |
| // LOOP BODY: |
| // v = a[i,... i + 3] |
| // array[0,... 3] = v |
| HInstruction* vload = AddVecLoad(loop_, array_a, phi_); |
| HInstruction* vstore = AddVecStore(return_block_, array_, c0, vload); |
| |
| // TODO: enable LSE for graphs with predicated SIMD. |
| graph_->SetHasTraditionalSIMD(true); |
| PerformLSE(); |
| |
| ASSERT_FALSE(IsRemoved(vload)); |
| ASSERT_FALSE(IsRemoved(vstore)); |
| } |
| |
| // As it is not allowed to use defaults for VecLoads, check if there is a new created array |
| // a VecLoad is not replaced with a default. |
| TEST_F(LoadStoreEliminationTest, VLoadDefaultValue) { |
| CreateTestControlFlowGraph(); |
| |
| HInstruction* c0 = graph_->GetIntConstant(0); |
| HInstruction* c128 = graph_->GetIntConstant(128); |
| |
| HInstruction* array_a = new (GetAllocator()) HNewArray(c0, c128, 0, 0); |
| pre_header_->InsertInstructionBefore(array_a, pre_header_->GetLastInstruction()); |
| array_a->CopyEnvironmentFrom(suspend_check_->GetEnvironment()); |
| |
| // v = a[0,... 3] |
| // array[0,... 3] = v |
| HInstruction* vload = AddVecLoad(pre_header_, array_a, c0); |
| HInstruction* vstore = AddVecStore(return_block_, array_, c0, vload); |
| |
| // TODO: enable LSE for graphs with predicated SIMD. |
| graph_->SetHasTraditionalSIMD(true); |
| PerformLSE(); |
| |
| ASSERT_FALSE(IsRemoved(vload)); |
| ASSERT_FALSE(IsRemoved(vstore)); |
| } |
| |
| // As it is allowed to use defaults for ordinary loads, check if there is a new created array |
| // a load used in a loop and after it is replaced with a default. |
| TEST_F(LoadStoreEliminationTest, LoadDefaultValueInLoopWithoutWriteSideEffects) { |
| CreateTestControlFlowGraph(); |
| |
| HInstruction* c0 = graph_->GetIntConstant(0); |
| HInstruction* c128 = graph_->GetIntConstant(128); |
| |
| HInstruction* array_a = new (GetAllocator()) HNewArray(c0, c128, 0, 0); |
| pre_header_->InsertInstructionBefore(array_a, pre_header_->GetLastInstruction()); |
| array_a->CopyEnvironmentFrom(suspend_check_->GetEnvironment()); |
| |
| // LOOP BODY: |
| // v = a[i] |
| // array[0] = v |
| HInstruction* load = MakeArrayGet(loop_, array_a, phi_, DataType::Type::kInt32); |
| HInstruction* store = MakeArraySet(return_block_, array_, c0, load); |
| |
| PerformLSE(); |
| |
| ASSERT_TRUE(IsRemoved(load)); |
| ASSERT_FALSE(IsRemoved(store)); |
| } |
| |
| // As it is allowed to use defaults for ordinary loads, check if there is a new created array |
| // a load is replaced with a default. |
| TEST_F(LoadStoreEliminationTest, LoadDefaultValue) { |
| CreateTestControlFlowGraph(); |
| |
| HInstruction* c0 = graph_->GetIntConstant(0); |
| HInstruction* c128 = graph_->GetIntConstant(128); |
| |
| HInstruction* array_a = new (GetAllocator()) HNewArray(c0, c128, 0, 0); |
| pre_header_->InsertInstructionBefore(array_a, pre_header_->GetLastInstruction()); |
| array_a->CopyEnvironmentFrom(suspend_check_->GetEnvironment()); |
| |
| // v = a[0] |
| // array[0] = v |
| HInstruction* load = MakeArrayGet(pre_header_, array_a, c0, DataType::Type::kInt32); |
| HInstruction* store = MakeArraySet(return_block_, array_, c0, load); |
| |
| PerformLSE(); |
| |
| ASSERT_TRUE(IsRemoved(load)); |
| ASSERT_FALSE(IsRemoved(store)); |
| } |
| |
| // As it is not allowed to use defaults for VecLoads but allowed for regular loads, |
| // check if there is a new created array, a VecLoad and a load used in a loop and after it, |
| // VecLoad is not replaced with a default but the load is. |
| TEST_F(LoadStoreEliminationTest, VLoadAndLoadDefaultValueInLoopWithoutWriteSideEffects) { |
| CreateTestControlFlowGraph(); |
| |
| HInstruction* c0 = graph_->GetIntConstant(0); |
| HInstruction* c128 = graph_->GetIntConstant(128); |
| |
| HInstruction* array_a = new (GetAllocator()) HNewArray(c0, c128, 0, 0); |
| pre_header_->InsertInstructionBefore(array_a, pre_header_->GetLastInstruction()); |
| array_a->CopyEnvironmentFrom(suspend_check_->GetEnvironment()); |
| |
| // LOOP BODY: |
| // v = a[i,... i + 3] |
| // v1 = a[i] |
| // array[0,... 3] = v |
| // array[0] = v1 |
| HInstruction* vload = AddVecLoad(loop_, array_a, phi_); |
| HInstruction* load = MakeArrayGet(loop_, array_a, phi_, DataType::Type::kInt32); |
| HInstruction* vstore = AddVecStore(return_block_, array_, c0, vload); |
| HInstruction* store = MakeArraySet(return_block_, array_, c0, load); |
| |
| // TODO: enable LSE for graphs with predicated SIMD. |
| graph_->SetHasTraditionalSIMD(true); |
| PerformLSE(); |
| |
| ASSERT_FALSE(IsRemoved(vload)); |
| ASSERT_TRUE(IsRemoved(load)); |
| ASSERT_FALSE(IsRemoved(vstore)); |
| ASSERT_FALSE(IsRemoved(store)); |
| } |
| |
| // As it is not allowed to use defaults for VecLoads but allowed for regular loads, |
| // check if there is a new created array, a VecLoad and a load, |
| // VecLoad is not replaced with a default but the load is. |
| TEST_F(LoadStoreEliminationTest, VLoadAndLoadDefaultValue) { |
| CreateTestControlFlowGraph(); |
| |
| HInstruction* c0 = graph_->GetIntConstant(0); |
| HInstruction* c128 = graph_->GetIntConstant(128); |
| |
| HInstruction* array_a = new (GetAllocator()) HNewArray(c0, c128, 0, 0); |
| pre_header_->InsertInstructionBefore(array_a, pre_header_->GetLastInstruction()); |
| array_a->CopyEnvironmentFrom(suspend_check_->GetEnvironment()); |
| |
| // v = a[0,... 3] |
| // v1 = a[0] |
| // array[0,... 3] = v |
| // array[0] = v1 |
| HInstruction* vload = AddVecLoad(pre_header_, array_a, c0); |
| HInstruction* load = MakeArrayGet(pre_header_, array_a, c0, DataType::Type::kInt32); |
| HInstruction* vstore = AddVecStore(return_block_, array_, c0, vload); |
| HInstruction* store = MakeArraySet(return_block_, array_, c0, load); |
| |
| // TODO: enable LSE for graphs with predicated SIMD. |
| graph_->SetHasTraditionalSIMD(true); |
| PerformLSE(); |
| |
| ASSERT_FALSE(IsRemoved(vload)); |
| ASSERT_TRUE(IsRemoved(load)); |
| ASSERT_FALSE(IsRemoved(vstore)); |
| ASSERT_FALSE(IsRemoved(store)); |
| } |
| |
| // It is not allowed to use defaults for VecLoads. However it should not prevent from removing |
| // loads getting the same value. |
| // Check a load getting a known value is eliminated (a loop test case). |
| TEST_F(LoadStoreEliminationTest, VLoadDefaultValueAndVLoadInLoopWithoutWriteSideEffects) { |
| CreateTestControlFlowGraph(); |
| |
| HInstruction* c0 = graph_->GetIntConstant(0); |
| HInstruction* c128 = graph_->GetIntConstant(128); |
| |
| HInstruction* array_a = new (GetAllocator()) HNewArray(c0, c128, 0, 0); |
| pre_header_->InsertInstructionBefore(array_a, pre_header_->GetLastInstruction()); |
| array_a->CopyEnvironmentFrom(suspend_check_->GetEnvironment()); |
| |
| // LOOP BODY: |
| // v = a[i,... i + 3] |
| // v1 = a[i,... i + 3] |
| // array[0,... 3] = v |
| // array[128,... 131] = v1 |
| HInstruction* vload1 = AddVecLoad(loop_, array_a, phi_); |
| HInstruction* vload2 = AddVecLoad(loop_, array_a, phi_); |
| HInstruction* vstore1 = AddVecStore(return_block_, array_, c0, vload1); |
| HInstruction* vstore2 = AddVecStore(return_block_, array_, c128, vload2); |
| |
| // TODO: enable LSE for graphs with predicated SIMD. |
| graph_->SetHasTraditionalSIMD(true); |
| PerformLSE(); |
| |
| ASSERT_FALSE(IsRemoved(vload1)); |
| ASSERT_TRUE(IsRemoved(vload2)); |
| ASSERT_FALSE(IsRemoved(vstore1)); |
| ASSERT_FALSE(IsRemoved(vstore2)); |
| } |
| |
| // It is not allowed to use defaults for VecLoads. However it should not prevent from removing |
| // loads getting the same value. |
| // Check a load getting a known value is eliminated. |
| TEST_F(LoadStoreEliminationTest, VLoadDefaultValueAndVLoad) { |
| CreateTestControlFlowGraph(); |
| |
| HInstruction* c0 = graph_->GetIntConstant(0); |
| HInstruction* c128 = graph_->GetIntConstant(128); |
| |
| HInstruction* array_a = new (GetAllocator()) HNewArray(c0, c128, 0, 0); |
| pre_header_->InsertInstructionBefore(array_a, pre_header_->GetLastInstruction()); |
| array_a->CopyEnvironmentFrom(suspend_check_->GetEnvironment()); |
| |
| // v = a[0,... 3] |
| // v1 = a[0,... 3] |
| // array[0,... 3] = v |
| // array[128,... 131] = v1 |
| HInstruction* vload1 = AddVecLoad(pre_header_, array_a, c0); |
| HInstruction* vload2 = AddVecLoad(pre_header_, array_a, c0); |
| HInstruction* vstore1 = AddVecStore(return_block_, array_, c0, vload1); |
| HInstruction* vstore2 = AddVecStore(return_block_, array_, c128, vload2); |
| |
| // TODO: enable LSE for graphs with predicated SIMD. |
| graph_->SetHasTraditionalSIMD(true); |
| PerformLSE(); |
| |
| ASSERT_FALSE(IsRemoved(vload1)); |
| ASSERT_TRUE(IsRemoved(vload2)); |
| ASSERT_FALSE(IsRemoved(vstore1)); |
| ASSERT_FALSE(IsRemoved(vstore2)); |
| } |
| |
| // Object o = new Obj(); |
| // // Needed because otherwise we short-circuit LSA since GVN would get almost |
| // // everything other than this. Also since this isn't expected to be a very |
| // // common pattern it's not worth changing the LSA logic. |
| // o.foo = 3; |
| // return o.shadow$_klass_; |
| TEST_F(LoadStoreEliminationTest, DefaultShadowClass) { |
| HBasicBlock* main = InitEntryMainExitGraph(); |
| |
| HInstruction* suspend_check = MakeSuspendCheck(entry_block_); |
| |
| HInstruction* cls = MakeLoadClass(main); |
| HInstruction* new_inst = MakeNewInstance(main, cls); |
| HInstruction* const_fence = new (GetAllocator()) HConstructorFence(new_inst, 0, GetAllocator()); |
| main->AddInstruction(const_fence); |
| HInstruction* set_field = |
| MakeIFieldSet(main, new_inst, graph_->GetIntConstant(33), MemberOffset(32)); |
| HInstruction* get_field = |
| MakeIFieldGet(main, new_inst, DataType::Type::kReference, mirror::Object::ClassOffset()); |
| HReturn* return_val = MakeReturn(main, get_field); |
| |
| PerformLSE(); |
| |
| EXPECT_INS_REMOVED(new_inst); |
| EXPECT_INS_REMOVED(const_fence); |
| EXPECT_INS_REMOVED(get_field); |
| EXPECT_INS_REMOVED(set_field); |
| EXPECT_INS_RETAINED(cls); |
| EXPECT_INS_EQ(cls, return_val->InputAt(0)); |
| } |
| |
| // Object o = new Obj(); |
| // // Needed because otherwise we short-circuit LSA since GVN would get almost |
| // // everything other than this. Also since this isn't expected to be a very |
| // // common pattern (only a single java function, Object.identityHashCode, |
| // // ever reads this field) it's not worth changing the LSA logic. |
| // o.foo = 3; |
| // return o.shadow$_monitor_; |
| TEST_F(LoadStoreEliminationTest, DefaultShadowMonitor) { |
| HBasicBlock* main = InitEntryMainExitGraph(); |
| |
| HInstruction* suspend_check = MakeSuspendCheck(entry_block_); |
| |
| HInstruction* cls = MakeLoadClass(main); |
| HInstruction* new_inst = MakeNewInstance(main, cls); |
| HInstruction* const_fence = new (GetAllocator()) HConstructorFence(new_inst, 0, GetAllocator()); |
| main->AddInstruction(const_fence); |
| HInstruction* set_field = |
| MakeIFieldSet(main, new_inst, graph_->GetIntConstant(33), MemberOffset(32)); |
| HInstruction* get_field = |
| MakeIFieldGet(main, new_inst, DataType::Type::kInt32, mirror::Object::MonitorOffset()); |
| HReturn* return_val = MakeReturn(main, get_field); |
| |
| PerformLSE(); |
| |
| EXPECT_INS_REMOVED(new_inst); |
| EXPECT_INS_REMOVED(const_fence); |
| EXPECT_INS_REMOVED(get_field); |
| EXPECT_INS_REMOVED(set_field); |
| EXPECT_INS_RETAINED(cls); |
| EXPECT_INS_EQ(graph_->GetIntConstant(0), return_val->InputAt(0)); |
| } |
| |
| // void DO_CAL() { |
| // int i = 1; |
| // int[] w = new int[80]; |
| // int t = 0; |
| // while (i < 80) { |
| // w[i] = PLEASE_INTERLEAVE(w[i - 1], 1) |
| // t = PLEASE_SELECT(w[i], t); |
| // i++; |
| // } |
| // return t; |
| // } |
| TEST_F(LoadStoreEliminationTest, ArrayLoopOverlap) { |
| ScopedObjectAccess soa(Thread::Current()); |
| VariableSizedHandleScope vshs(soa.Self()); |
| HBasicBlock* ret = InitEntryMainExitGraph(&vshs); |
| auto [preheader, loop, body] = CreateWhileLoop(ret); |
| |
| HInstruction* zero_const = graph_->GetIntConstant(0); |
| HInstruction* one_const = graph_->GetIntConstant(1); |
| HInstruction* eighty_const = graph_->GetIntConstant(80); |
| |
| // preheader |
| HInstruction* alloc_w = MakeNewArray(preheader, zero_const, eighty_const); |
| |
| // loop-start |
| auto [i_phi, i_add] = MakeLinearLoopVar(loop, body, one_const, one_const); |
| HPhi* t_phi = MakePhi(loop, {zero_const, /* placeholder */ zero_const}); |
| std::initializer_list<HInstruction*> common_env{alloc_w, i_phi, t_phi}; |
| HInstruction* suspend = MakeSuspendCheck(loop, common_env); |
| HInstruction* i_cmp_top = MakeCondition(loop, kCondGE, i_phi, eighty_const); |
| HIf* loop_if = MakeIf(loop, i_cmp_top); |
| CHECK(loop_if->IfTrueSuccessor() == ret); |
| |
| // BODY |
| HInstruction* last_i = MakeBinOp<HSub>(body, DataType::Type::kInt32, i_phi, one_const); |
| HInstruction* last_get = MakeArrayGet(body, alloc_w, last_i, DataType::Type::kInt32); |
| HInvoke* body_value = |
| MakeInvokeStatic(body, DataType::Type::kInt32, { last_get, one_const }, common_env); |
| HInstruction* body_set = MakeArraySet(body, alloc_w, i_phi, body_value, DataType::Type::kInt32); |
| HInstruction* body_get = MakeArrayGet(body, alloc_w, i_phi, DataType::Type::kInt32); |
| HInvoke* t_next = MakeInvokeStatic(body, DataType::Type::kInt32, { body_get, t_phi }, common_env); |
| |
| t_phi->ReplaceInput(t_next, 1u); // Update back-edge input. |
| |
| // ret |
| MakeReturn(ret, t_phi); |
| |
| PerformLSE(); |
| |
| // TODO Technically this is optimizable. LSE just needs to add phis to keep |
| // track of the last `N` values set where `N` is how many locations we can go |
| // back into the array. |
| if (IsRemoved(last_get)) { |
| // If we were able to remove the previous read the entire array should be removable. |
| EXPECT_INS_REMOVED(body_set); |
| EXPECT_INS_REMOVED(alloc_w); |
| } else { |
| // This is the branch we actually take for now. If we rely on being able to |
| // read the array we'd better remember to write to it as well. |
| EXPECT_INS_RETAINED(body_set); |
| } |
| // The last 'get' should always be removable. |
| EXPECT_INS_REMOVED(body_get); |
| } |
| |
| // void DO_CAL2() { |
| // int i = 1; |
| // int[] w = new int[80]; |
| // int t = 0; |
| // while (i < 80) { |
| // w[i] = PLEASE_INTERLEAVE(w[i - 1], 1) // <-- removed |
| // t = PLEASE_SELECT(w[i], t); |
| // w[i] = PLEASE_INTERLEAVE(w[i - 1], 1) // <-- removed |
| // t = PLEASE_SELECT(w[i], t); |
| // w[i] = PLEASE_INTERLEAVE(w[i - 1], 1) // <-- kept |
| // t = PLEASE_SELECT(w[i], t); |
| // i++; |
| // } |
| // return t; |
| // } |
| TEST_F(LoadStoreEliminationTest, ArrayLoopOverlap2) { |
| ScopedObjectAccess soa(Thread::Current()); |
| VariableSizedHandleScope vshs(soa.Self()); |
| HBasicBlock* ret = InitEntryMainExitGraph(&vshs); |
| auto [preheader, loop, body] = CreateWhileLoop(ret); |
| |
| HInstruction* zero_const = graph_->GetIntConstant(0); |
| HInstruction* one_const = graph_->GetIntConstant(1); |
| HInstruction* eighty_const = graph_->GetIntConstant(80); |
| |
| // preheader |
| HInstruction* alloc_w = MakeNewArray(preheader, zero_const, eighty_const); |
| |
| // loop-start |
| auto [i_phi, i_add] = MakeLinearLoopVar(loop, body, one_const, one_const); |
| HPhi* t_phi = MakePhi(loop, {zero_const, /* placeholder */ zero_const}); |
| std::initializer_list<HInstruction*> common_env{alloc_w, i_phi, t_phi}; |
| HInstruction* suspend = MakeSuspendCheck(loop, common_env); |
| HInstruction* i_cmp_top = MakeCondition(loop, kCondGE, i_phi, eighty_const); |
| HIf* loop_if = MakeIf(loop, i_cmp_top); |
| CHECK(loop_if->IfTrueSuccessor() == ret); |
| |
| // BODY |
| HInstruction* last_i = MakeBinOp<HSub>(body, DataType::Type::kInt32, i_phi, one_const); |
| auto make_instructions = [&](HInstruction* last_t_value) { |
| HInstruction* last_get = MakeArrayGet(body, alloc_w, last_i, DataType::Type::kInt32); |
| HInvoke* body_value = |
| MakeInvokeStatic(body, DataType::Type::kInt32, { last_get, one_const }, common_env); |
| HInstruction* body_set = MakeArraySet(body, alloc_w, i_phi, body_value, DataType::Type::kInt32); |
| HInstruction* body_get = MakeArrayGet(body, alloc_w, i_phi, DataType::Type::kInt32); |
| HInvoke* t_next = |
| MakeInvokeStatic(body, DataType::Type::kInt32, { body_get, last_t_value }, common_env); |
| return std::make_tuple(last_get, body_value, body_set, body_get, t_next); |
| }; |
| auto [last_get_1, body_value_1, body_set_1, body_get_1, t_next_1] = make_instructions(t_phi); |
| auto [last_get_2, body_value_2, body_set_2, body_get_2, t_next_2] = make_instructions(t_next_1); |
| auto [last_get_3, body_value_3, body_set_3, body_get_3, t_next_3] = make_instructions(t_next_2); |
| |
| t_phi->ReplaceInput(t_next_3, 1u); // Update back-edge input. |
| |
| // ret |
| MakeReturn(ret, t_phi); |
| |
| PerformLSE(); |
| |
| // TODO Technically this is optimizable. LSE just needs to add phis to keep |
| // track of the last `N` values set where `N` is how many locations we can go |
| // back into the array. |
| if (IsRemoved(last_get_1)) { |
| // If we were able to remove the previous read the entire array should be removable. |
| EXPECT_INS_REMOVED(body_set_1); |
| EXPECT_INS_REMOVED(body_set_2); |
| EXPECT_INS_REMOVED(body_set_3); |
| EXPECT_INS_REMOVED(last_get_1); |
| EXPECT_INS_REMOVED(last_get_2); |
| EXPECT_INS_REMOVED(alloc_w); |
| } else { |
| // This is the branch we actually take for now. If we rely on being able to |
| // read the array we'd better remember to write to it as well. |
| EXPECT_INS_RETAINED(body_set_3); |
| } |
| // The last 'get' should always be removable. |
| EXPECT_INS_REMOVED(body_get_1); |
| EXPECT_INS_REMOVED(body_get_2); |
| EXPECT_INS_REMOVED(body_get_3); |
| // shadowed writes should always be removed |
| EXPECT_INS_REMOVED(body_set_1); |
| EXPECT_INS_REMOVED(body_set_2); |
| } |
| |
| TEST_F(LoadStoreEliminationTest, ArrayNonLoopPhi) { |
| ScopedObjectAccess soa(Thread::Current()); |
| VariableSizedHandleScope vshs(soa.Self()); |
| HBasicBlock* ret = InitEntryMainExitGraph(&vshs); |
| |
| HInstruction* param = MakeParam(DataType::Type::kBool); |
| HInstruction* zero_const = graph_->GetIntConstant(0); |
| HInstruction* one_const = graph_->GetIntConstant(1); |
| HInstruction* two_const = graph_->GetIntConstant(2); |
| |
| auto [start, left, right] = CreateDiamondPattern(ret, param); |
| |
| // start |
| HInstruction* alloc_w = MakeNewArray(start, zero_const, two_const); |
| |
| // left |
| HInvoke* left_value = |
| MakeInvokeStatic(left, DataType::Type::kInt32, { zero_const }, /*env=*/ { alloc_w }); |
| HInstruction* left_set_1 = |
| MakeArraySet(left, alloc_w, zero_const, left_value, DataType::Type::kInt32); |
| HInstruction* left_set_2 = |
| MakeArraySet(left, alloc_w, one_const, zero_const, DataType::Type::kInt32); |
| |
| // right |
| HInvoke* right_value = |
| MakeInvokeStatic(right, DataType::Type::kInt32, { one_const }, /*env=*/ { alloc_w }); |
| HInstruction* right_set_1 = |
| MakeArraySet(right, alloc_w, zero_const, right_value, DataType::Type::kInt32); |
| HInstruction* right_set_2 = |
| MakeArraySet(right, alloc_w, one_const, zero_const, DataType::Type::kInt32); |
| |
| // ret |
| HInstruction* read_1 = MakeArrayGet(ret, alloc_w, zero_const, DataType::Type::kInt32); |
| HInstruction* read_2 = MakeArrayGet(ret, alloc_w, one_const, DataType::Type::kInt32); |
| HInstruction* add = MakeBinOp<HAdd>(ret, DataType::Type::kInt32, read_1, read_2); |
| MakeReturn(ret, add); |
| |
| PerformLSE(); |
| |
| EXPECT_INS_REMOVED(read_1); |
| EXPECT_INS_REMOVED(read_2); |
| EXPECT_INS_REMOVED(left_set_1); |
| EXPECT_INS_REMOVED(left_set_2); |
| EXPECT_INS_REMOVED(right_set_1); |
| EXPECT_INS_REMOVED(right_set_2); |
| EXPECT_INS_REMOVED(alloc_w); |
| |
| EXPECT_INS_RETAINED(left_value); |
| EXPECT_INS_RETAINED(right_value); |
| } |
| |
| TEST_F(LoadStoreEliminationTest, ArrayMergeDefault) { |
| ScopedObjectAccess soa(Thread::Current()); |
| VariableSizedHandleScope vshs(soa.Self()); |
| HBasicBlock* ret = InitEntryMainExitGraph(&vshs); |
| |
| HInstruction* param = MakeParam(DataType::Type::kBool); |
| HInstruction* zero_const = graph_->GetIntConstant(0); |
| HInstruction* one_const = graph_->GetIntConstant(1); |
| HInstruction* two_const = graph_->GetIntConstant(2); |
| |
| auto [start, left, right] = CreateDiamondPattern(ret, param); |
| |
| // start |
| HInstruction* alloc_w = MakeNewArray(start, zero_const, two_const); |
| ArenaVector<HInstruction*> alloc_locals({}, GetAllocator()->Adapter(kArenaAllocInstruction)); |
| |
| // left |
| HInstruction* left_set_1 = |
| MakeArraySet(left, alloc_w, zero_const, one_const, DataType::Type::kInt32); |
| HInstruction* left_set_2 = |
| MakeArraySet(left, alloc_w, zero_const, zero_const, DataType::Type::kInt32); |
| |
| // right |
| HInstruction* right_set_1 = |
| MakeArraySet(right, alloc_w, one_const, one_const, DataType::Type::kInt32); |
| HInstruction* right_set_2 = |
| MakeArraySet(right, alloc_w, one_const, zero_const, DataType::Type::kInt32); |
| |
| // ret |
| HInstruction* read_1 = MakeArrayGet(ret, alloc_w, zero_const, DataType::Type::kInt32); |
| HInstruction* read_2 = MakeArrayGet(ret, alloc_w, one_const, DataType::Type::kInt32); |
| HInstruction* add = MakeBinOp<HAdd>(ret, DataType::Type::kInt32, read_1, read_2); |
| MakeReturn(ret, add); |
| |
| PerformLSE(); |
| |
| EXPECT_INS_REMOVED(read_1); |
| EXPECT_INS_REMOVED(read_2); |
| EXPECT_INS_REMOVED(left_set_1); |
| EXPECT_INS_REMOVED(left_set_2); |
| EXPECT_INS_REMOVED(right_set_1); |
| EXPECT_INS_REMOVED(right_set_2); |
| EXPECT_INS_REMOVED(alloc_w); |
| } |
| |
| // Regression test for b/187487955. |
| // We previusly failed to consider aliasing between an array location |
| // with index `idx` defined in the loop (such as a loop Phi) and another |
| // array location with index `idx + constant`. This could have led to |
| // replacing the load with, for example, the default value 0. |
| TEST_F(LoadStoreEliminationTest, ArrayLoopAliasing1) { |
| ScopedObjectAccess soa(Thread::Current()); |
| VariableSizedHandleScope vshs(soa.Self()); |
| HBasicBlock* ret = InitEntryMainExitGraph(&vshs); |
| auto [preheader, loop, body] = CreateWhileLoop(ret); |
| loop->SwapSuccessors(); // Move the loop exit to the "else" successor. |
| |
| HInstruction* n = MakeParam(DataType::Type::kInt32); |
| HInstruction* c0 = graph_->GetIntConstant(0); |
| HInstruction* c1 = graph_->GetIntConstant(1); |
| |
| // preheader |
| HInstruction* cls = MakeLoadClass(preheader); |
| HInstruction* array = MakeNewArray(preheader, cls, n); |
| |
| // loop |
| auto [i_phi, i_add] = MakeLinearLoopVar(loop, body, c0, c1); |
| HInstruction* loop_suspend_check = MakeSuspendCheck(loop); |
| HInstruction* loop_cond = MakeCondition(loop, kCondLT, i_phi, n); |
| HIf* loop_if = MakeIf(loop, loop_cond); |
| CHECK(loop_if->IfTrueSuccessor() == body); |
| |
| // body |
| HInstruction* body_set = MakeArraySet(body, array, i_phi, i_phi, DataType::Type::kInt32); |
| |
| // ret |
| HInstruction* ret_sub = MakeBinOp<HSub>(ret, DataType::Type::kInt32, i_phi, c1); |
| HInstruction* ret_get = MakeArrayGet(ret, array, ret_sub, DataType::Type::kInt32); |
| MakeReturn(ret, ret_get); |
| |
| PerformLSE(); |
| |
| EXPECT_INS_RETAINED(cls); |
| EXPECT_INS_RETAINED(array); |
| EXPECT_INS_RETAINED(body_set); |
| EXPECT_INS_RETAINED(ret_get); |
| } |
| |
| // Regression test for b/187487955. |
| // Similar to the `ArrayLoopAliasing1` test above but with additional load |
| // that marks a loop Phi placeholder as kept which used to trigger a DCHECK(). |
| // There is also an LSE run-test for this but it relies on BCE eliminating |
| // BoundsCheck instructions and adds extra code in loop body to avoid |
| // loop unrolling. This gtest does not need to jump through those hoops |
| // as we do not unnecessarily run those optimization passes. |
| TEST_F(LoadStoreEliminationTest, ArrayLoopAliasing2) { |
| ScopedObjectAccess soa(Thread::Current()); |
| VariableSizedHandleScope vshs(soa.Self()); |
| HBasicBlock* ret = InitEntryMainExitGraph(&vshs); |
| auto [preheader, loop, body] = CreateWhileLoop(ret); |
| loop->SwapSuccessors(); // Move the loop exit to the "else" successor. |
| |
| HInstruction* n = MakeParam(DataType::Type::kInt32); |
| HInstruction* c0 = graph_->GetIntConstant(0); |
| HInstruction* c1 = graph_->GetIntConstant(1); |
| |
| // preheader |
| HInstruction* cls = MakeLoadClass(preheader); |
| HInstruction* array = MakeNewArray(preheader, cls, n); |
| |
| // loop |
| auto [i_phi, i_add] = MakeLinearLoopVar(loop, body, c0, c1); |
| HInstruction* loop_suspend_check = MakeSuspendCheck(loop); |
| HInstruction* loop_cond = MakeCondition(loop, kCondLT, i_phi, n); |
| HIf* loop_if = MakeIf(loop, loop_cond); |
| CHECK(loop_if->IfTrueSuccessor() == body); |
| |
| // body |
| HInstruction* body_set = MakeArraySet(body, array, i_phi, i_phi, DataType::Type::kInt32); |
| |
| // ret |
| HInstruction* ret_sub = MakeBinOp<HSub>(ret, DataType::Type::kInt32, i_phi, c1); |
| HInstruction* ret_get1 = MakeArrayGet(ret, array, ret_sub, DataType::Type::kInt32); |
| HInstruction* ret_get2 = MakeArrayGet(ret, array, i_phi, DataType::Type::kInt32); |
| HInstruction* ret_add = MakeBinOp<HAdd>(ret, DataType::Type::kInt32, ret_get1, ret_get2); |
| MakeReturn(ret, ret_add); |
| |
| PerformLSE(); |
| |
| EXPECT_INS_RETAINED(cls); |
| EXPECT_INS_RETAINED(array); |
| EXPECT_INS_RETAINED(body_set); |
| EXPECT_INS_RETAINED(ret_get1); |
| EXPECT_INS_RETAINED(ret_get2); |
| } |
| |
| class TwoTypesConversionsTestGroup : public LoadStoreEliminationTestBase< |
| CommonCompilerTestWithParam<std::tuple<DataType::Type, DataType::Type>>> { |
| protected: |
| DataType::Type FieldTypeForLoadType(DataType::Type load_type) { |
| // `Uint8` is not a valid field type but it's a valid load type we can set for |
| // a `HInstanceFieldGet` after constructing it. |
| return (load_type == DataType::Type::kUint8) ? DataType::Type::kInt8 : load_type; |
| } |
| |
| void MergingTwiceConvertedValueStoreTest(bool extra_diamond); |
| }; |
| |
| TEST_P(TwoTypesConversionsTestGroup, StoreLoad) { |
| auto [param_type, load_type] = GetParam(); |
| DataType::Type field_type = FieldTypeForLoadType(load_type); |
| |
| HBasicBlock* main = InitEntryMainExitGraph(); |
| HInstruction* param = MakeParam(param_type); |
| HInstruction* object = MakeParam(DataType::Type::kReference); |
| |
| HInstruction* write = MakeIFieldSet(main, object, param, field_type, MemberOffset(32)); |
| HInstanceFieldGet* read = MakeIFieldGet(main, object, field_type, MemberOffset(32)); |
| read->SetType(load_type); |
| HInstruction* ret = MakeReturn(main, read); |
| |
| PerformLSE(); |
| |
| EXPECT_INS_RETAINED(write); |
| EXPECT_INS_REMOVED(read); |
| |
| HInstruction* ret_input = ret->InputAt(0); |
| if (DataType::IsTypeConversionImplicit(param_type, load_type)) { |
| ASSERT_EQ(param, ret_input) << ret_input->DebugName(); |
| } else { |
| ASSERT_TRUE(ret_input->IsTypeConversion()) << ret_input->DebugName(); |
| ASSERT_EQ(load_type, ret_input->GetType()); |
| ASSERT_EQ(param, ret_input->InputAt(0)) << ret_input->InputAt(0)->DebugName(); |
| } |
| } |
| |
| TEST_P(TwoTypesConversionsTestGroup, StoreLoadStoreLoad) { |
| auto [load_type1, load_type2] = GetParam(); |
| DataType::Type field_type1 = FieldTypeForLoadType(load_type1); |
| DataType::Type field_type2 = FieldTypeForLoadType(load_type2); |
| |
| HBasicBlock* main = InitEntryMainExitGraph(); |
| HInstruction* param = MakeParam(DataType::Type::kInt32); |
| HInstruction* object = MakeParam(DataType::Type::kReference); |
| |
| HInstruction* write1 = MakeIFieldSet(main, object, param, field_type1, MemberOffset(32)); |
| HInstanceFieldGet* read1 = MakeIFieldGet(main, object, field_type1, MemberOffset(32)); |
| read1->SetType(load_type1); |
| HInstruction* write2 = MakeIFieldSet(main, object, read1, field_type2, MemberOffset(40)); |
| HInstanceFieldGet* read2 = MakeIFieldGet(main, object, field_type2, MemberOffset(40)); |
| read2->SetType(load_type2); |
| HInstruction* ret = MakeReturn(main, read2); |
| |
| PerformLSE(); |
| |
| EXPECT_INS_RETAINED(write1); |
| EXPECT_INS_RETAINED(write2); |
| EXPECT_INS_REMOVED(read1); |
| EXPECT_INS_REMOVED(read2); |
| |
| // Note: Sometimes we create two type conversions when one is enough (Int32->Int16->Int8). |
| // We currently rely on the instruction simplifier to remove the intermediate conversion. |
| HInstruction* current = ret->InputAt(0); |
| if (!DataType::IsTypeConversionImplicit(load_type1, load_type2)) { |
| ASSERT_TRUE(current->IsTypeConversion()) << current->DebugName(); |
| ASSERT_EQ(load_type2, current->GetType()); |
| current = current->InputAt(0); |
| } |
| if (!DataType::IsTypeConversionImplicit(DataType::Type::kInt32, load_type1)) { |
| ASSERT_TRUE(current->IsTypeConversion()) << current->DebugName(); |
| ASSERT_EQ(load_type1, current->GetType()); |
| current = current->InputAt(0); |
| } |
| ASSERT_EQ(param, current) << current->DebugName(); |
| } |
| |
| TEST_P(TwoTypesConversionsTestGroup, DefaultValueStores_LoadAfterLoop) { |
| auto [default_load_type, load_type] = GetParam(); |
| DataType::Type default_field_type = FieldTypeForLoadType(default_load_type); |
| DataType::Type field_type = FieldTypeForLoadType(load_type); |
| |
| HBasicBlock* return_block = InitEntryMainExitGraph(); |
| auto [pre_header, loop] = CreateDoWhileLoopWithInstructions(return_block); |
| |
| HInstruction* object = MakeParam(DataType::Type::kReference); |
| HInstruction* cls = MakeLoadClass(pre_header); |
| HInstruction* default_object = MakeNewInstance(pre_header, cls); |
| HInstanceFieldGet* default_value = |
| MakeIFieldGet(pre_header, default_object, default_field_type, MemberOffset(40)); |
| default_value->SetType(default_load_type); |
| // Make the `default_object` escape to avoid write elimination (test only load elimination). |
| HInstruction* invoke = MakeInvokeStatic(return_block, DataType::Type::kVoid, {default_object}); |
| |
| HInstruction* write = |
| MakeIFieldSet(return_block, object, default_value, field_type, MemberOffset(32)); |
| HInstanceFieldGet* read = MakeIFieldGet(return_block, object, field_type, MemberOffset(32)); |
| read->SetType(load_type); |
| HInstruction* ret = MakeReturn(return_block, read); |
| |
| PerformLSE(); |
| |
| EXPECT_INS_RETAINED(default_object); |
| EXPECT_INS_REMOVED(default_value); |
| EXPECT_INS_RETAINED(write); |
| EXPECT_INS_REMOVED(read); |
| |
| HInstruction* ret_input = ret->InputAt(0); |
| ASSERT_TRUE(ret_input->IsIntConstant()) << ret_input->DebugName(); |
| ASSERT_EQ(ret_input->AsIntConstant()->GetValue(), 0); |
| } |
| |
| TEST_P(TwoTypesConversionsTestGroup, SingleValueStores_LoadAfterLoop) { |
| auto [param_type, load_type] = GetParam(); |
| DataType::Type field_type = FieldTypeForLoadType(load_type); |
| |
| HBasicBlock* return_block = InitEntryMainExitGraph(); |
| auto [pre_header, loop_header, loop_body] = CreateForLoopWithInstructions(return_block); |
| |
| HInstruction* param = MakeParam(param_type); |
| HInstruction* object = MakeParam(DataType::Type::kReference); |
| |
| // Write the value in pre-header. |
| HInstruction* write1 = |
| MakeIFieldSet(pre_header, object, param, field_type, MemberOffset(32)); |
| |
| // In the body, make a call to clobber all fields, then write the same value as in pre-header. |
| MakeInvokeStatic(loop_body, DataType::Type::kVoid, {object}); |
| HInstruction* write2 = |
| MakeIFieldSet(loop_body, object, param, field_type, MemberOffset(32)); |
| |
| HInstanceFieldGet* read = MakeIFieldGet(return_block, object, field_type, MemberOffset(32)); |
| read->SetType(load_type); |
| HInstruction* ret = MakeReturn(return_block, read); |
| |
| PerformLSE(); |
| |
| EXPECT_INS_RETAINED(write1); |
| EXPECT_INS_RETAINED(write2); |
| EXPECT_INS_REMOVED(read); |
| |
| HInstruction* ret_input = ret->InputAt(0); |
| if (DataType::IsTypeConversionImplicit(param_type, load_type)) { |
| ASSERT_EQ(param, ret_input) << ret_input->DebugName(); |
| } else { |
| ASSERT_TRUE(ret_input->IsTypeConversion()) << ret_input->DebugName(); |
| ASSERT_EQ(load_type, ret_input->GetType()); |
| ASSERT_EQ(param, ret_input->InputAt(0)) << ret_input->InputAt(0)->DebugName(); |
| } |
| } |
| |
| TEST_P(TwoTypesConversionsTestGroup, StoreLoopLoad) { |
| auto [param_type, load_type] = GetParam(); |
| DataType::Type field_type = FieldTypeForLoadType(load_type); |
| |
| HBasicBlock* return_block = InitEntryMainExitGraph(); |
| auto [pre_header, loop] = CreateDoWhileLoopWithInstructions(return_block); |
| |
| HInstruction* param = MakeParam(param_type); |
| HInstruction* object = MakeParam(DataType::Type::kReference); |
| |
| HInstruction* write = MakeIFieldSet(pre_header, object, param, field_type, MemberOffset(32)); |
| |
| HInstanceFieldGet* read = MakeIFieldGet(return_block, object, field_type, MemberOffset(32)); |
| read->SetType(load_type); |
| HInstruction* ret = MakeReturn(return_block, read); |
| |
| PerformLSE(); |
| |
| EXPECT_INS_RETAINED(write); |
| EXPECT_INS_REMOVED(read); |
| |
| HInstruction* ret_input = ret->InputAt(0); |
| if (DataType::IsTypeConversionImplicit(param_type, load_type)) { |
| ASSERT_EQ(param, ret_input) << ret_input->DebugName(); |
| } else { |
| ASSERT_TRUE(ret_input->IsTypeConversion()) << ret_input->DebugName(); |
| ASSERT_EQ(load_type, ret_input->GetType()); |
| ASSERT_EQ(param, ret_input->InputAt(0)) << ret_input->InputAt(0)->DebugName(); |
| } |
| } |
| |
| TEST_P(TwoTypesConversionsTestGroup, StoreLoopLoadStoreLoad) { |
| auto [load_type1, load_type2] = GetParam(); |
| DataType::Type field_type1 = FieldTypeForLoadType(load_type1); |
| DataType::Type field_type2 = FieldTypeForLoadType(load_type2); |
| |
| HBasicBlock* return_block = InitEntryMainExitGraph(); |
| auto [pre_header, loop] = CreateDoWhileLoopWithInstructions(return_block); |
| HInstruction* param = MakeParam(DataType::Type::kInt32); |
| HInstruction* object = MakeParam(DataType::Type::kReference); |
| |
| HInstruction* write1 = MakeIFieldSet(pre_header, object, param, field_type1, MemberOffset(32)); |
| |
| HInstanceFieldGet* read1 = MakeIFieldGet(return_block, object, field_type1, MemberOffset(32)); |
| read1->SetType(load_type1); |
| HInstruction* write2 = MakeIFieldSet(return_block, object, read1, field_type2, MemberOffset(40)); |
| HInstanceFieldGet* read2 = MakeIFieldGet(return_block, object, field_type2, MemberOffset(40)); |
| read2->SetType(load_type2); |
| HInstruction* ret = MakeReturn(return_block, read2); |
| |
| PerformLSE(); |
| |
| EXPECT_INS_RETAINED(write1); |
| EXPECT_INS_RETAINED(write2); |
| EXPECT_INS_REMOVED(read1); |
| EXPECT_INS_REMOVED(read2); |
| |
| // Note: If the `load_type2` is not larger than the `load_type1`, we avoid |
| // the intermediate conversion and use `param` directly for the second load. |
| DataType::Type read2_input_type = DataType::Size(load_type2) <= DataType::Size(load_type1) |
| ? DataType::Type::kInt32 |
| : load_type1; |
| HInstruction* current = ret->InputAt(0); |
| if (!DataType::IsTypeConversionImplicit(read2_input_type, load_type2)) { |
| ASSERT_TRUE(current->IsTypeConversion()) << current->DebugName(); |
| ASSERT_EQ(load_type2, current->GetType()); |
| current = current->InputAt(0); |
| } |
| if (!DataType::IsTypeConversionImplicit(DataType::Type::kInt32, read2_input_type)) { |
| ASSERT_EQ(read2_input_type, load_type1); |
| ASSERT_TRUE(current->IsTypeConversion()) << current->DebugName(); |
| ASSERT_EQ(load_type1, current->GetType()) << load_type2; |
| current = current->InputAt(0); |
| } |
| ASSERT_EQ(param, current) << current->DebugName(); |
| } |
| |
| TEST_P(TwoTypesConversionsTestGroup, MergingConvertedValueStore) { |
| auto [param_type, load_type] = GetParam(); |
| DataType::Type field_type = FieldTypeForLoadType(load_type); |
| DataType::Type phi_field_type = DataType::Type::kInt32; // "phi field" can hold the full value. |
| CHECK(DataType::IsTypeConversionImplicit(param_type, phi_field_type)); |
| CHECK(DataType::IsTypeConversionImplicit(load_type, phi_field_type)); |
| |
| HBasicBlock* return_block = InitEntryMainExitGraph(); |
| auto [pre_header, loop_header, loop_body] = CreateForLoopWithInstructions(return_block); |
| |
| HInstruction* param = MakeParam(param_type); |
| HInstruction* object = MakeParam(DataType::Type::kReference); |
| |
| // Initialize the "phi field". |
| HInstruction* pre_header_write = |
| MakeIFieldSet(pre_header, object, param, phi_field_type, MemberOffset(40)); |
| |
| // In the body, we read the "phi field", store and load the value to a different field |
| // to force type conversion, and store back to the "phi field". |
| HInstanceFieldGet* phi_read = MakeIFieldGet(loop_body, object, phi_field_type, MemberOffset(40)); |
| HInstruction* conversion_write = |
| MakeIFieldSet(loop_body, object, phi_read, field_type, MemberOffset(32)); |
| HInstanceFieldGet* conversion_read = |
| MakeIFieldGet(loop_body, object, field_type, MemberOffset(32)); |
| conversion_read->SetType(load_type); |
| HInstruction* phi_write = |
| MakeIFieldSet(loop_body, object, conversion_read, phi_field_type, MemberOffset(40)); |
| |
| HInstanceFieldGet* final_read = |
| MakeIFieldGet(return_block, object, phi_field_type, MemberOffset(40)); |
| HInstruction* ret = MakeReturn(return_block, final_read); |
| |
| PerformLSE(); |
| |
| EXPECT_INS_RETAINED(pre_header_write); |
| EXPECT_INS_RETAINED(conversion_write); |
| EXPECT_INS_REMOVED(phi_read); |
| EXPECT_INS_REMOVED(conversion_read); |
| EXPECT_INS_REMOVED(final_read); |
| |
| HInstruction* ret_input = ret->InputAt(0); |
| if (DataType::IsTypeConversionImplicit(param_type, load_type)) { |
| EXPECT_INS_REMOVED(phi_write) << "\n" << param_type << "/" << load_type; |
| ASSERT_EQ(param, ret_input) << ret_input->DebugName(); |
| } else { |
| EXPECT_INS_RETAINED(phi_write) << "\n" << param_type << "/" << load_type; |
| ASSERT_TRUE(ret_input->IsPhi()) << ret_input->DebugName(); |
| HInstruction* pre_header_input = ret_input->InputAt(0); |
| HInstruction* loop_body_input = ret_input->InputAt(1); |
| ASSERT_EQ(param, pre_header_input) << pre_header_input->DebugName(); |
| ASSERT_TRUE(loop_body_input->IsTypeConversion()); |
| ASSERT_EQ(load_type, loop_body_input->GetType()); |
| ASSERT_EQ(ret_input, loop_body_input->InputAt(0)); |
| } |
| } |
| |
| void TwoTypesConversionsTestGroup::MergingTwiceConvertedValueStoreTest(bool extra_diamond) { |
| auto [load_type1, load_type2] = GetParam(); |
| DataType::Type field_type1 = FieldTypeForLoadType(load_type1); |
| DataType::Type field_type2 = FieldTypeForLoadType(load_type2); |
| DataType::Type phi_field_type = DataType::Type::kInt32; // "phi field" can hold the full value. |
| CHECK(DataType::IsTypeConversionImplicit(load_type1, phi_field_type)); |
| CHECK(DataType::IsTypeConversionImplicit(load_type2, phi_field_type)); |
| |
| HBasicBlock* return_block = InitEntryMainExitGraph(); |
| auto [pre_header, loop_header, loop_body] = CreateForLoopWithInstructions(return_block); |
| if (extra_diamond) { |
| // Out-of-date debug check in `MergePredecessorRecords()` used to crash when the merged |
| // values from all incoming paths needed the same phi placeholder with a conversion load. |
| // Create an extra diamond to check such merging. b/405552185 |
| HInstruction* bool_param = MakeParam(DataType::Type::kBool); |
| std::tie(loop_body, std::ignore, std::ignore) = CreateDiamondPattern(loop_body, bool_param); |
| } |
| |
| HInstruction* param = MakeParam(DataType::Type::kInt32); |
| HInstruction* object = MakeParam(DataType::Type::kReference); |
| |
| // Initialize the "phi field". |
| HInstruction* pre_header_write = |
| MakeIFieldSet(pre_header, object, param, phi_field_type, MemberOffset(40)); |
| |
| // In the body, we read the "phi field", store and load the value to a different field |
| // to force type conversion - twice, and store back to the "phi field". |
| HInstanceFieldGet* phi_read = MakeIFieldGet(loop_body, object, phi_field_type, MemberOffset(40)); |
| HInstruction* conversion_write1 = |
| MakeIFieldSet(loop_body, object, phi_read, field_type1, MemberOffset(32)); |
| HInstanceFieldGet* conversion_read1 = |
| MakeIFieldGet(loop_body, object, field_type1, MemberOffset(32)); |
| conversion_read1->SetType(load_type1); |
| HInstruction* conversion_write2 = |
| MakeIFieldSet(loop_body, object, conversion_read1, field_type2, MemberOffset(36)); |
| HInstanceFieldGet* conversion_read2 = |
| MakeIFieldGet(loop_body, object, field_type2, MemberOffset(36)); |
| conversion_read2->SetType(load_type2); |
| HInstruction* phi_write = |
| MakeIFieldSet(loop_body, object, conversion_read2, phi_field_type, MemberOffset(40)); |
| |
| HInstanceFieldGet* final_read = |
| MakeIFieldGet(return_block, object, phi_field_type, MemberOffset(40)); |
| HInstruction* ret = MakeReturn(return_block, final_read); |
| |
| PerformLSE(); |
| |
| EXPECT_INS_RETAINED(pre_header_write); |
| EXPECT_INS_RETAINED(conversion_write1); |
| EXPECT_INS_RETAINED(conversion_write2); |
| EXPECT_INS_REMOVED(phi_read); |
| EXPECT_INS_REMOVED(conversion_read1); |
| EXPECT_INS_REMOVED(conversion_read2); |
| EXPECT_INS_REMOVED(final_read); |
| |
| HInstruction* ret_input = ret->InputAt(0); |
| if (load_type1 == DataType::Type::kInt32 && load_type2 == DataType::Type::kInt32) { |
| EXPECT_INS_REMOVED(phi_write) << "\n" << load_type1 << "/" << load_type2; |
| ASSERT_EQ(param, ret_input) << ret_input->DebugName(); |
| } else { |
| EXPECT_INS_RETAINED(phi_write) << "\n" << load_type1 << "/" << load_type2; |
| ASSERT_TRUE(ret_input->IsPhi()) << ret_input->DebugName(); |
| HInstruction* pre_header_input = ret_input->InputAt(0); |
| HInstruction* loop_body_input = ret_input->InputAt(1); |
| ASSERT_EQ(param, pre_header_input) << pre_header_input->DebugName(); |
| ASSERT_TRUE(loop_body_input->IsTypeConversion()); |
| HInstruction* current = loop_body_input; |
| // Note: If the `load_type2` is not larger than the `load_type1`, we avoid |
| // the intermediate conversion and use Phi directly for the second load. |
| DataType::Type read2_input_type = DataType::Size(load_type2) <= DataType::Size(load_type1) |
| ? DataType::Type::kInt32 |
| : load_type1; |
| if (!DataType::IsTypeConversionImplicit(read2_input_type, load_type2)) { |
| ASSERT_TRUE(current->IsTypeConversion()) << current->DebugName(); |
| ASSERT_EQ(load_type2, current->GetType()); |
| current = current->InputAt(0); |
| } |
| if (!DataType::IsTypeConversionImplicit(DataType::Type::kInt32, read2_input_type)) { |
| ASSERT_EQ(read2_input_type, load_type1); |
| ASSERT_TRUE(current->IsTypeConversion()) << current->DebugName(); |
| ASSERT_EQ(load_type1, current->GetType()) << load_type2; |
| current = current->InputAt(0); |
| } |
| ASSERT_EQ(current, ret_input); |
| } |
| } |
| |
| TEST_P(TwoTypesConversionsTestGroup, MergingTwiceConvertedValueStore) { |
| MergingTwiceConvertedValueStoreTest(/*extra_diamond=*/ false); |
| } |
| |
| TEST_P(TwoTypesConversionsTestGroup, MergingTwiceConvertedValueStoreWithExtraDiamond) { |
| MergingTwiceConvertedValueStoreTest(/*extra_diamond=*/ true); |
| } |
| |
| TEST_P(TwoTypesConversionsTestGroup, UnreplacedConversionLoadDuringStoreElimination) { |
| auto [load_type1, load_type2] = GetParam(); |
| DataType::Type field_type1 = FieldTypeForLoadType(load_type1); |
| // Note: `load_type2` is not actually used for any load, we just write with `field_type2`. |
| DataType::Type field_type2 = FieldTypeForLoadType(load_type2); |
| |
| HBasicBlock* return_block = InitEntryMainExitGraph(); |
| HInstruction* param = MakeParam(DataType::Type::kInt32); |
| HInstruction* object = MakeParam(DataType::Type::kReference); |
| HInstruction* bool_param = MakeParam(DataType::Type::kBool); |
| |
| auto [pre_header1, header1, body1] = CreateForLoopWithInstructions(return_block); |
| auto [pre_header2, header2, body2_end] = CreateForLoopWithInstructions(return_block); |
| auto [body2_start, body2_left, body2_right] = CreateDiamondPattern(body2_end, bool_param); |
| |
| // Write the first field in the `pre_header1`, clobber it in `body1` and read it |
| // in `pre_header2`. LSE shall initially mark the load as depending on a loop |
| // phi placeholder but later determine that the load must be retained as is. |
| HInstruction* pre_header1_write1 = |
| MakeIFieldSet(pre_header1, object, param, field_type1, MemberOffset(40)); |
| HInvoke* body1_invoke = MakeInvokeStatic(body1, DataType::Type::kVoid, {}); |
| HInstanceFieldGet* pre_header2_read1 = |
| MakeIFieldGet(pre_header2, object, field_type1, MemberOffset(40)); |
| pre_header2_read1->SetType(load_type1); |
| |
| // Write the second field in `pre_header2`, `body2_start` and `body2_right`, making |
| // LSE keep these writes because the loop phi placeholder they feed shall remain |
| // live at the return instruction. The value written in `body2_start` is marked as |
| // requiring a loop phi, with or without type conversion based on the load types. |
| // When looking for stores to eliminate, we used to crash when processing the |
| // `body2_right_write2` because a loop phi requiring a type conversion as the "old |
| // value" was not handled correctly with these writes marked as kept. b/405553153 |
| HInstruction* pre_header2_write2 = |
| MakeIFieldSet(pre_header2, object, param, field_type2, MemberOffset(40)); |
| HInstruction* body2_start_write2 = |
| MakeIFieldSet(body2_start, object, pre_header2_read1, field_type2, MemberOffset(40)); |
| HInstruction* body2_right_write2 = |
| MakeIFieldSet(body2_right, object, param, field_type2, MemberOffset(40)); |
| |
| HInstruction* ret = MakeReturn(return_block, param); |
| PerformLSE(); |
| |
| EXPECT_INS_RETAINED(pre_header1_write1); |
| EXPECT_INS_RETAINED(body1_invoke); |
| EXPECT_INS_RETAINED(pre_header2_read1); |
| EXPECT_INS_RETAINED(pre_header2_write2); |
| EXPECT_INS_RETAINED(body2_start_write2); |
| EXPECT_INS_RETAINED(body2_right_write2); |
| } |
| |
| TEST_P(TwoTypesConversionsTestGroup, MergingConvertedValueStorePhiDeduplication) { |
| auto [load_type1, load_type2] = GetParam(); |
| DataType::Type field_type1 = FieldTypeForLoadType(load_type1); |
| DataType::Type field_type2 = FieldTypeForLoadType(load_type2); |
| DataType::Type phi_field_type = DataType::Type::kInt32; // "phi field" can hold the full value. |
| CHECK(DataType::IsTypeConversionImplicit(load_type1, phi_field_type)); |
| CHECK(DataType::IsTypeConversionImplicit(load_type2, phi_field_type)); |
| DataType::Type param_type = DataType::Type::kInt32; |
| |
| HBasicBlock* return_block = InitEntryMainExitGraph(); |
| auto [pre_header, loop_header, loop_body] = CreateForLoopWithInstructions(return_block); |
| |
| HInstruction* param = MakeParam(param_type); |
| HInstruction* object = MakeParam(DataType::Type::kReference); |
| |
| // Initialize the two "phi fields". |
| HInstruction* pre_header_write1 = |
| MakeIFieldSet(pre_header, object, param, phi_field_type, MemberOffset(40)); |
| HInstruction* pre_header_write2 = |
| MakeIFieldSet(pre_header, object, param, phi_field_type, MemberOffset(60)); |
| |
| // In the body, we read the "phi fields", store and load the values to different fields |
| // to force type conversion, and store back to the "phi fields". |
| HInstanceFieldGet* phi_read1 = MakeIFieldGet(loop_body, object, phi_field_type, MemberOffset(40)); |
| HInstanceFieldGet* phi_read2 = MakeIFieldGet(loop_body, object, phi_field_type, MemberOffset(60)); |
| HInstruction* conversion_write1 = |
| MakeIFieldSet(loop_body, object, phi_read1, field_type1, MemberOffset(32)); |
| HInstruction* conversion_write2 = |
| MakeIFieldSet(loop_body, object, phi_read2, field_type2, MemberOffset(52)); |
| HInstanceFieldGet* conversion_read1 = |
| MakeIFieldGet(loop_body, object, field_type1, MemberOffset(32)); |
| conversion_read1->SetType(load_type1); |
| HInstanceFieldGet* conversion_read2 = |
| MakeIFieldGet(loop_body, object, field_type2, MemberOffset(52)); |
| conversion_read2->SetType(load_type2); |
| HInstruction* phi_write1 = |
| MakeIFieldSet(loop_body, object, conversion_read1, phi_field_type, MemberOffset(40)); |
| HInstruction* phi_write2 = |
| MakeIFieldSet(loop_body, object, conversion_read2, phi_field_type, MemberOffset(60)); |
| |
| HInstanceFieldGet* final_read1 = |
| MakeIFieldGet(return_block, object, phi_field_type, MemberOffset(40)); |
| HInstanceFieldGet* final_read2 = |
| MakeIFieldGet(return_block, object, phi_field_type, MemberOffset(60)); |
| HAdd* add = MakeBinOp<HAdd>(return_block, DataType::Type::kInt32, final_read1, final_read2); |
| HInstruction* ret = MakeReturn(return_block, add); |
| |
| PerformLSE(); |
| |
| EXPECT_INS_RETAINED(pre_header_write1); |
| EXPECT_INS_RETAINED(pre_header_write2); |
| EXPECT_INS_RETAINED(conversion_write1); |
| EXPECT_INS_RETAINED(conversion_write2); |
| EXPECT_INS_REMOVED(phi_read1); |
| EXPECT_INS_REMOVED(phi_read2); |
| EXPECT_INS_REMOVED(conversion_read1); |
| EXPECT_INS_REMOVED(conversion_read2); |
| EXPECT_INS_REMOVED(final_read1); |
| EXPECT_INS_REMOVED(final_read2); |
| |
| HInstruction* ret_input = ret->InputAt(0); |
| ASSERT_EQ(add, ret_input) << ret_input->DebugName(); |
| HInstruction* add_lhs = add->InputAt(0); |
| HInstruction* add_rhs = add->InputAt(1); |
| if (load_type1 == load_type2) { |
| ASSERT_EQ(add_lhs, add_rhs); |
| } else { |
| ASSERT_NE(add_lhs, add_rhs); |
| } |
| if (DataType::IsTypeConversionImplicit(param_type, load_type1)) { |
| EXPECT_INS_REMOVED(phi_write1) << "\n" << load_type1 << "/" << load_type2; |
| ASSERT_EQ(param, add_lhs) << ret_input->DebugName(); |
| } else { |
| EXPECT_INS_RETAINED(phi_write1) << "\n" << load_type1 << "/" << load_type2; |
| ASSERT_TRUE(add_lhs->IsPhi()) << add_lhs->DebugName(); |
| HInstruction* pre_header_input = add_lhs->InputAt(0); |
| HInstruction* loop_body_input = add_lhs->InputAt(1); |
| ASSERT_EQ(param, pre_header_input) << pre_header_input->DebugName(); |
| ASSERT_TRUE(loop_body_input->IsTypeConversion()); |
| ASSERT_EQ(load_type1, loop_body_input->GetType()); |
| ASSERT_EQ(add_lhs, loop_body_input->InputAt(0)); |
| } |
| if (DataType::IsTypeConversionImplicit(param_type, load_type2)) { |
| EXPECT_INS_REMOVED(phi_write2) << "\n" << load_type1 << "/" << load_type2; |
| ASSERT_EQ(param, add_rhs) << ret_input->DebugName(); |
| } else { |
| EXPECT_INS_RETAINED(phi_write2) << "\n" << load_type1 << "/" << load_type2; |
| ASSERT_TRUE(add_rhs->IsPhi()) << add_rhs->DebugName(); |
| HInstruction* pre_header_input = add_rhs->InputAt(0); |
| HInstruction* loop_body_input = add_rhs->InputAt(1); |
| ASSERT_EQ(param, pre_header_input) << pre_header_input->DebugName(); |
| ASSERT_TRUE(loop_body_input->IsTypeConversion()); |
| ASSERT_EQ(load_type2, loop_body_input->GetType()); |
| ASSERT_EQ(add_rhs, loop_body_input->InputAt(0)); |
| } |
| } |
| |
| auto Int32AndSmallerTypesGenerator() { |
| return ::testing::Values(DataType::Type::kInt32, |
| DataType::Type::kInt16, |
| DataType::Type::kInt8, |
| DataType::Type::kUint16, |
| DataType::Type::kUint8); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(LoadStoreEliminationTest, |
| TwoTypesConversionsTestGroup, |
| ::testing::Combine(Int32AndSmallerTypesGenerator(), |
| Int32AndSmallerTypesGenerator())); |
| |
| // // ENTRY |
| // obj = new Obj(); |
| // // ALL should be kept |
| // switch (parameter_value) { |
| // case 1: |
| // // Case1 |
| // obj.field = 1; |
| // call_func(obj); |
| // break; |
| // case 2: |
| // // Case2 |
| // obj.field = 2; |
| // call_func(obj); |
| // // We don't know what obj.field is now we aren't able to eliminate the read below! |
| // break; |
| // default: |
| // // Case3 |
| // // TODO This only happens because of limitations on our LSE which is unable |
| // // to materialize co-dependent loop and non-loop phis. |
| // // Ideally we'd want to generate |
| // // P1 = PHI[3, loop_val] |
| // // while (test()) { |
| // // if (test2()) { goto; } else { goto; } |
| // // loop_val = [P1, 5] |
| // // } |
| // // Currently we aren't able to unfortunately. |
| // obj.field = 3; |
| // while (test()) { |
| // if (test2()) { } else { obj.field = 5; } |
| // } |
| // break; |
| // } |
| // EXIT |
| // return obj.field |
| TEST_F(LoadStoreEliminationTest, PartialUnknownMerge) { |
| CreateGraph(); |
| AdjacencyListGraph blks(SetupFromAdjacencyList("entry", |
| "exit", |
| {{"entry", "bswitch"}, |
| {"bswitch", "case1"}, |
| {"bswitch", "case2"}, |
| {"bswitch", "case3"}, |
| {"case1", "breturn"}, |
| {"case2", "breturn"}, |
| {"case3", "loop_pre_header"}, |
| {"loop_pre_header", "loop_header"}, |
| {"loop_header", "loop_body"}, |
| {"loop_body", "loop_if_left"}, |
| {"loop_body", "loop_if_right"}, |
| {"loop_if_left", "loop_end"}, |
| {"loop_if_right", "loop_end"}, |
| {"loop_end", "loop_header"}, |
| {"loop_header", "breturn"}, |
| {"breturn", "exit"}})); |
| #define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name) |
| GET_BLOCK(entry); |
| GET_BLOCK(bswitch); |
| GET_BLOCK(exit); |
| GET_BLOCK(breturn); |
| GET_BLOCK(case1); |
| GET_BLOCK(case2); |
| GET_BLOCK(case3); |
| |
| GET_BLOCK(loop_pre_header); |
| GET_BLOCK(loop_header); |
| GET_BLOCK(loop_body); |
| GET_BLOCK(loop_if_left); |
| GET_BLOCK(loop_if_right); |
| GET_BLOCK(loop_end); |
| #undef GET_BLOCK |
| HInstruction* switch_val = MakeParam(DataType::Type::kInt32); |
| HInstruction* c1 = graph_->GetIntConstant(1); |
| HInstruction* c2 = graph_->GetIntConstant(2); |
| HInstruction* c3 = graph_->GetIntConstant(3); |
| HInstruction* c5 = graph_->GetIntConstant(5); |
| |
| HInstruction* cls = MakeLoadClass(entry); |
| HInstruction* new_inst = MakeNewInstance(entry, cls); |
| MakeGoto(entry); |
| |
| HInstruction* switch_inst = new (GetAllocator()) HPackedSwitch(0, 2, switch_val); |
| bswitch->AddInstruction(switch_inst); |
| |
| HInstruction* write_c1 = MakeIFieldSet(case1, new_inst, c1, MemberOffset(32)); |
| HInstruction* call_c1 = MakeInvokeStatic(case1, DataType::Type::kVoid, { new_inst }); |
| MakeGoto(case1); |
| |
| HInstruction* write_c2 = MakeIFieldSet(case2, new_inst, c2, MemberOffset(32)); |
| HInstruction* call_c2 = MakeInvokeStatic(case2, DataType::Type::kVoid, { new_inst }); |
| MakeGoto(case2); |
| |
| HInstruction* write_c3 = MakeIFieldSet(case3, new_inst, c3, MemberOffset(32)); |
| MakeGoto(case3); |
| |
| MakeGoto(loop_pre_header); |
| |
| HInstruction* suspend_check_header = MakeSuspendCheck(loop_header); |
| HInstruction* call_loop_header = MakeInvokeStatic(loop_header, DataType::Type::kBool, {}); |
| MakeIf(loop_header, call_loop_header); |
| |
| HInstruction* call_loop_body = MakeInvokeStatic(loop_body, DataType::Type::kBool, {}); |
| MakeIf(loop_body, call_loop_body); |
| |
| MakeGoto(loop_if_left); |
| |
| HInstruction* write_loop_right = MakeIFieldSet(loop_if_right, new_inst, c5, MemberOffset(32)); |
| MakeGoto(loop_if_right); |
| |
| MakeGoto(loop_end); |
| |
| HInstruction* read_bottom = |
| MakeIFieldGet(breturn, new_inst, DataType::Type::kInt32, MemberOffset(32)); |
| MakeReturn(breturn, read_bottom); |
| |
| MakeExit(exit); |
| |
| PerformLSE(blks); |
| |
| EXPECT_INS_RETAINED(read_bottom); |
| EXPECT_INS_RETAINED(write_c1); |
| EXPECT_INS_RETAINED(write_c2); |
| EXPECT_INS_RETAINED(write_c3); |
| EXPECT_INS_RETAINED(write_loop_right); |
| } |
| |
| // // ENTRY |
| // obj = new Obj(); |
| // if (parameter_value) { |
| // // LEFT |
| // obj.field = 1; |
| // call_func(obj); |
| // // We don't know what obj.field is now we aren't able to eliminate the read below! |
| // } else { |
| // // DO NOT ELIMINATE |
| // obj.field = 2; |
| // // RIGHT |
| // } |
| // EXIT |
| // return obj.field |
| // This test runs with partial LSE disabled. |
| TEST_F(LoadStoreEliminationTest, PartialLoadPreserved) { |
| ScopedObjectAccess soa(Thread::Current()); |
| VariableSizedHandleScope vshs(soa.Self()); |
| HBasicBlock* ret = InitEntryMainExitGraph(&vshs); |
| |
| HInstruction* bool_value = MakeParam(DataType::Type::kBool); |
| HInstruction* c1 = graph_->GetIntConstant(1); |
| HInstruction* c2 = graph_->GetIntConstant(2); |
| |
| auto [start, left, right] = CreateDiamondPattern(ret, bool_value); |
| |
| HInstruction* cls = MakeLoadClass(start); |
| HInstruction* new_inst = MakeNewInstance(start, cls); |
| |
| HInstruction* write_left = MakeIFieldSet(left, new_inst, c1, MemberOffset(32)); |
| HInstruction* call_left = MakeInvokeStatic(left, DataType::Type::kVoid, { new_inst }); |
| |
| HInstruction* write_right = MakeIFieldSet(right, new_inst, c2, MemberOffset(32)); |
| |
| HInstruction* read_bottom = |
| MakeIFieldGet(ret, new_inst, DataType::Type::kInt32, MemberOffset(32)); |
| MakeReturn(ret, read_bottom); |
| |
| PerformLSE(); |
| |
| EXPECT_INS_RETAINED(read_bottom) << *read_bottom; |
| EXPECT_INS_RETAINED(write_right) << *write_right; |
| } |
| |
| // // ENTRY |
| // obj = new Obj(); |
| // if (parameter_value) { |
| // // LEFT |
| // obj.field = 1; |
| // call_func(obj); |
| // // We don't know what obj.field is now we aren't able to eliminate the read below! |
| // } else { |
| // // DO NOT ELIMINATE |
| // if (param2) { |
| // obj.field = 2; |
| // } else { |
| // obj.field = 3; |
| // } |
| // // RIGHT |
| // } |
| // EXIT |
| // return obj.field |
| // NB This test is for non-partial LSE flow. Normally the obj.field writes will be removed |
| TEST_F(LoadStoreEliminationTest, PartialLoadPreserved2) { |
| ScopedObjectAccess soa(Thread::Current()); |
| VariableSizedHandleScope vshs(soa.Self()); |
| HBasicBlock* ret = InitEntryMainExitGraph(&vshs); |
| |
| HInstruction* bool_value = MakeParam(DataType::Type::kBool); |
| HInstruction* bool_value_2 = MakeParam(DataType::Type::kBool); |
| HInstruction* c1 = graph_->GetIntConstant(1); |
| HInstruction* c2 = graph_->GetIntConstant(2); |
| HInstruction* c3 = graph_->GetIntConstant(3); |
| |
| auto [start, left, right_end] = CreateDiamondPattern(ret, bool_value); |
| auto [right_start, right_first, right_second] = CreateDiamondPattern(right_end, bool_value_2); |
| |
| HInstruction* cls = MakeLoadClass(start); |
| HInstruction* new_inst = MakeNewInstance(start, cls); |
| |
| HInstruction* write_left = MakeIFieldSet(left, new_inst, c1, MemberOffset(32)); |
| HInstruction* call_left = MakeInvokeStatic(left, DataType::Type::kVoid, { new_inst }); |
| |
| HInstruction* write_right_first = MakeIFieldSet(right_first, new_inst, c2, MemberOffset(32)); |
| |
| HInstruction* write_right_second = MakeIFieldSet(right_second, new_inst, c3, MemberOffset(32)); |
| |
| HInstruction* read_bottom = |
| MakeIFieldGet(ret, new_inst, DataType::Type::kInt32, MemberOffset(32)); |
| MakeReturn(ret, read_bottom); |
| |
| PerformLSE(); |
| |
| EXPECT_INS_RETAINED(read_bottom); |
| EXPECT_INS_RETAINED(write_right_first); |
| EXPECT_INS_RETAINED(write_right_second); |
| } |
| |
| // // ENTRY |
| // obj = new Obj(); |
| // if (parameter_value) { |
| // // LEFT |
| // // DO NOT ELIMINATE |
| // obj.field = 1; |
| // while (true) { |
| // bool esc = escape(obj); |
| // if (esc) break; |
| // // DO NOT ELIMINATE |
| // obj.field = 3; |
| // } |
| // } else { |
| // // RIGHT |
| // // DO NOT ELIMINATE |
| // obj.field = 2; |
| // } |
| // // DO NOT ELIMINATE |
| // return obj.field; |
| // EXIT |
| TEST_F(LoadStoreEliminationTest, PartialLoadPreserved3) { |
| ScopedObjectAccess soa(Thread::Current()); |
| VariableSizedHandleScope vshs(soa.Self()); |
| CreateGraph(&vshs); |
| AdjacencyListGraph blks(SetupFromAdjacencyList("entry", |
| "exit", |
| {{"entry", "entry_post"}, |
| {"entry_post", "right"}, |
| {"right", "return_block"}, |
| {"entry_post", "left_pre"}, |
| {"left_pre", "left_loop"}, |
| {"left_loop", "left_loop_post"}, |
| {"left_loop_post", "left_loop"}, |
| {"left_loop", "return_block"}, |
| {"return_block", "exit"}})); |
| #define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name) |
| GET_BLOCK(entry); |
| GET_BLOCK(entry_post); |
| GET_BLOCK(exit); |
| GET_BLOCK(return_block); |
| GET_BLOCK(left_pre); |
| GET_BLOCK(left_loop); |
| GET_BLOCK(left_loop_post); |
| GET_BLOCK(right); |
| #undef GET_BLOCK |
| // Left-loops first successor is the break. |
| if (left_loop->GetSuccessors()[0] != return_block) { |
| left_loop->SwapSuccessors(); |
| } |
| HInstruction* bool_value = MakeParam(DataType::Type::kBool); |
| HInstruction* c1 = graph_->GetIntConstant(1); |
| HInstruction* c2 = graph_->GetIntConstant(2); |
| HInstruction* c3 = graph_->GetIntConstant(3); |
| |
| HInstruction* cls = MakeLoadClass(entry); |
| HInstruction* new_inst = MakeNewInstance(entry, cls); |
| MakeGoto(entry); |
| |
| MakeIf(entry_post, bool_value); |
| |
| HInstruction* write_left_pre = MakeIFieldSet(left_pre, new_inst, c1, MemberOffset(32)); |
| MakeGoto(left_pre); |
| |
| HInstruction* suspend_left_loop = MakeSuspendCheck(left_loop); |
| HInstruction* call_left_loop = MakeInvokeStatic(left_loop, DataType::Type::kBool, {new_inst}); |
| MakeIf(left_loop, call_left_loop); |
| |
| HInstruction* write_left_loop = MakeIFieldSet(left_loop_post, new_inst, c3, MemberOffset(32)); |
| MakeGoto(left_loop_post); |
| |
| HInstruction* write_right = MakeIFieldSet(right, new_inst, c2, MemberOffset(32)); |
| MakeGoto(right); |
| |
| HInstruction* read_return = |
| MakeIFieldGet(return_block, new_inst, DataType::Type::kInt32, MemberOffset(32)); |
| MakeReturn(return_block, read_return); |
| |
| MakeExit(exit); |
| |
| PerformLSE(blks); |
| |
| EXPECT_INS_RETAINED(write_left_pre) << *write_left_pre; |
| EXPECT_INS_RETAINED(read_return) << *read_return; |
| EXPECT_INS_RETAINED(write_right) << *write_right; |
| EXPECT_INS_RETAINED(write_left_loop) << *write_left_loop; |
| EXPECT_INS_RETAINED(call_left_loop) << *call_left_loop; |
| } |
| |
| // // ENTRY |
| // obj = new Obj(); |
| // if (parameter_value) { |
| // // LEFT |
| // // ELIMINATE (not visible since always overridden by obj.field = 3) |
| // obj.field = 1; |
| // while (true) { |
| // bool stop = should_stop(); |
| // // DO NOT ELIMINATE (visible by read at end) |
| // obj.field = 3; |
| // if (stop) break; |
| // } |
| // } else { |
| // // RIGHT |
| // // DO NOT ELIMINATE |
| // obj.field = 2; |
| // escape(obj); |
| // } |
| // // DO NOT ELIMINATE |
| // return obj.field; |
| // EXIT |
| // Disabled due to b/205813546. |
| TEST_F(LoadStoreEliminationTest, DISABLED_PartialLoadPreserved4) { |
| ScopedObjectAccess soa(Thread::Current()); |
| VariableSizedHandleScope vshs(soa.Self()); |
| CreateGraph(&vshs); |
| AdjacencyListGraph blks(SetupFromAdjacencyList("entry", |
| "exit", |
| {{"entry", "entry_post"}, |
| {"entry_post", "right"}, |
| {"right", "return_block"}, |
| {"entry_post", "left_pre"}, |
| {"left_pre", "left_loop"}, |
| {"left_loop", "left_loop"}, |
| {"left_loop", "return_block"}, |
| {"return_block", "exit"}})); |
| #define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name) |
| GET_BLOCK(entry); |
| GET_BLOCK(entry_post); |
| GET_BLOCK(exit); |
| GET_BLOCK(return_block); |
| GET_BLOCK(left_pre); |
| GET_BLOCK(left_loop); |
| GET_BLOCK(right); |
| #undef GET_BLOCK |
| // Left-loops first successor is the break. |
| if (left_loop->GetSuccessors()[0] != return_block) { |
| left_loop->SwapSuccessors(); |
| } |
| HInstruction* bool_value = MakeParam(DataType::Type::kBool); |
| HInstruction* c1 = graph_->GetIntConstant(1); |
| HInstruction* c2 = graph_->GetIntConstant(2); |
| HInstruction* c3 = graph_->GetIntConstant(3); |
| |
| HInstruction* cls = MakeLoadClass(entry); |
| HInstruction* new_inst = MakeNewInstance(entry, cls); |
| MakeGoto(entry); |
| |
| MakeIf(entry_post, bool_value); |
| |
| HInstruction* write_left_pre = MakeIFieldSet(left_pre, new_inst, c1, MemberOffset(32)); |
| MakeGoto(left_pre); |
| |
| HInstruction* suspend_left_loop = MakeSuspendCheck(left_loop); |
| HInstruction* call_left_loop = MakeInvokeStatic(left_loop, DataType::Type::kBool, {}); |
| HInstruction* write_left_loop = MakeIFieldSet(left_loop, new_inst, c3, MemberOffset(32)); |
| MakeIf(left_loop, call_left_loop); |
| |
| HInstruction* write_right = MakeIFieldSet(right, new_inst, c2, MemberOffset(32)); |
| HInstruction* call_right = MakeInvokeStatic(right, DataType::Type::kBool, {new_inst}); |
| MakeGoto(right); |
| |
| HInstruction* read_return = |
| MakeIFieldGet(return_block, new_inst, DataType::Type::kInt32, MemberOffset(32)); |
| MakeReturn(return_block, read_return); |
| |
| MakeExit(exit); |
| |
| PerformLSE(blks); |
| |
| EXPECT_INS_RETAINED(read_return); |
| EXPECT_INS_RETAINED(write_right); |
| EXPECT_INS_RETAINED(write_left_loop); |
| EXPECT_INS_RETAINED(call_left_loop); |
| EXPECT_INS_REMOVED(write_left_pre); |
| EXPECT_INS_RETAINED(call_right); |
| } |
| |
| // // ENTRY |
| // obj = new Obj(); |
| // if (parameter_value) { |
| // // LEFT |
| // // DO NOT ELIMINATE |
| // escape(obj); |
| // obj.field = 1; |
| // // obj has already escaped so can't use field = 1 for value |
| // noescape(); |
| // } else { |
| // // RIGHT |
| // // obj is needed for read since we don't know what the left value is |
| // // DO NOT ELIMINATE |
| // obj.field = 2; |
| // noescape(); |
| // } |
| // EXIT |
| // ELIMINATE |
| // return obj.field |
| TEST_F(LoadStoreEliminationTest, PartialLoadPreserved5) { |
| ScopedObjectAccess soa(Thread::Current()); |
| VariableSizedHandleScope vshs(soa.Self()); |
| HBasicBlock* breturn = InitEntryMainExitGraph(&vshs); |
| |
| HInstruction* bool_value = MakeParam(DataType::Type::kBool); |
| HInstruction* c1 = graph_->GetIntConstant(1); |
| HInstruction* c2 = graph_->GetIntConstant(2); |
| |
| auto [start, left, right] = CreateDiamondPattern(breturn, bool_value); |
| |
| // start |
| HInstruction* cls = MakeLoadClass(start); |
| HInstruction* new_inst = MakeNewInstance(start, cls); |
| |
| HInstruction* call_left = MakeInvokeStatic(left, DataType::Type::kVoid, { new_inst }); |
| HInstruction* write_left = MakeIFieldSet(left, new_inst, c1, MemberOffset(32)); |
| HInstruction* call2_left = MakeInvokeStatic(left, DataType::Type::kVoid, {}); |
| |
| HInstruction* write_right = MakeIFieldSet(right, new_inst, c2, MemberOffset(32)); |
| HInstruction* call_right = MakeInvokeStatic(right, DataType::Type::kVoid, {}); |
| |
| HInstruction* read_bottom = |
| MakeIFieldGet(breturn, new_inst, DataType::Type::kInt32, MemberOffset(32)); |
| MakeReturn(breturn, read_bottom); |
| |
| PerformLSE(); |
| |
| EXPECT_INS_RETAINED(read_bottom); |
| EXPECT_INS_RETAINED(write_right); |
| EXPECT_INS_RETAINED(write_left); |
| EXPECT_INS_RETAINED(call_left); |
| EXPECT_INS_RETAINED(call_right); |
| } |
| |
| // // ENTRY |
| // obj = new Obj(); |
| // DO NOT ELIMINATE. Kept by escape. |
| // obj.field = 3; |
| // noescape(); |
| // if (parameter_value) { |
| // // LEFT |
| // // DO NOT ELIMINATE |
| // escape(obj); |
| // obj.field = 1; |
| // } else { |
| // // RIGHT |
| // // ELIMINATE |
| // obj.field = 2; |
| // } |
| // EXIT |
| // ELIMINATE |
| // return obj.field |
| // Disabled due to b/205813546. |
| TEST_F(LoadStoreEliminationTest, DISABLED_PartialLoadPreserved6) { |
| CreateGraph(); |
| AdjacencyListGraph blks(SetupFromAdjacencyList("entry", |
| "exit", |
| {{"entry", "left"}, |
| {"entry", "right"}, |
| {"left", "breturn"}, |
| {"right", "breturn"}, |
| {"breturn", "exit"}})); |
| #define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name) |
| GET_BLOCK(entry); |
| GET_BLOCK(exit); |
| GET_BLOCK(breturn); |
| GET_BLOCK(left); |
| GET_BLOCK(right); |
| #undef GET_BLOCK |
| HInstruction* bool_value = MakeParam(DataType::Type::kBool); |
| HInstruction* c1 = graph_->GetIntConstant(1); |
| HInstruction* c2 = graph_->GetIntConstant(2); |
| HInstruction* c3 = graph_->GetIntConstant(3); |
| |
| HInstruction* cls = MakeLoadClass(entry); |
| HInstruction* new_inst = MakeNewInstance(entry, cls); |
| HInstruction* write_entry = MakeIFieldSet(entry, new_inst, c3, MemberOffset(32)); |
| HInstruction* call_entry = MakeInvokeStatic(entry, DataType::Type::kVoid, {}); |
| MakeIf(entry, bool_value); |
| |
| HInstruction* call_left = MakeInvokeStatic(left, DataType::Type::kVoid, { new_inst }); |
| HInstruction* write_left = MakeIFieldSet(left, new_inst, c1, MemberOffset(32)); |
| MakeGoto(left); |
| |
| HInstruction* write_right = MakeIFieldSet(right, new_inst, c2, MemberOffset(32)); |
| MakeGoto(right); |
| |
| HInstruction* read_bottom = |
| MakeIFieldGet(breturn, new_inst, DataType::Type::kInt32, MemberOffset(32)); |
| MakeReturn(breturn, read_bottom); |
| |
| MakeExit(exit); |
| |
| PerformLSE(blks); |
| |
| EXPECT_INS_REMOVED(read_bottom); |
| EXPECT_INS_REMOVED(write_right); |
| EXPECT_INS_RETAINED(write_entry); |
| EXPECT_INS_RETAINED(write_left); |
| EXPECT_INS_RETAINED(call_left); |
| EXPECT_INS_RETAINED(call_entry); |
| } |
| } // namespace art |