| /* | |
| * Copyright (C) 2018 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 "control_flow_simplifier.h" | |
| #include "base/arena_allocator.h" | |
| #include "base/macros.h" | |
| #include "builder.h" | |
| #include "nodes.h" | |
| #include "optimizing_unit_test.h" | |
| #include "side_effects_analysis.h" | |
| namespace art HIDDEN { | |
| class ControlFlowSimplifierTest : public OptimizingUnitTest { | |
| protected: | |
| HPhi* ConstructBasicGraphForSelect(HBasicBlock* return_block, HInstruction* instr) { | |
| HParameterValue* bool_param = MakeParam(DataType::Type::kBool); | |
| HIntConstant* const1 = graph_->GetIntConstant(1); | |
| auto [if_block, then_block, else_block] = CreateDiamondPattern(return_block, bool_param); | |
| AddOrInsertInstruction(then_block, instr); | |
| HPhi* phi = MakePhi(return_block, {instr, const1}); | |
| return phi; | |
| } | |
| bool CheckGraphAndTryControlFlowSimplifier() { | |
| graph_->BuildDominatorTree(); | |
| EXPECT_TRUE(CheckGraph()); | |
| SideEffectsAnalysis side_effects(graph_); | |
| side_effects.Run(); | |
| return HControlFlowSimplifier(graph_, /*handles*/ nullptr, /*stats*/ nullptr).Run(); | |
| } | |
| }; | |
| // HDivZeroCheck might throw and should not be hoisted from the conditional to an unconditional. | |
| TEST_F(ControlFlowSimplifierTest, testZeroCheckPreventsSelect) { | |
| HBasicBlock* return_block = InitEntryMainExitGraphWithReturnVoid(); | |
| HParameterValue* param = MakeParam(DataType::Type::kInt32); | |
| HDivZeroCheck* instr = new (GetAllocator()) HDivZeroCheck(param, 0); | |
| HPhi* phi = ConstructBasicGraphForSelect(return_block, instr); | |
| ManuallyBuildEnvFor(instr, {param, graph_->GetIntConstant(1)}); | |
| EXPECT_FALSE(CheckGraphAndTryControlFlowSimplifier()); | |
| EXPECT_INS_RETAINED(phi); | |
| } | |
| // Test that ControlFlowSimplifier succeeds with HAdd. | |
| TEST_F(ControlFlowSimplifierTest, testSelectWithAdd) { | |
| HBasicBlock* return_block = InitEntryMainExitGraphWithReturnVoid(); | |
| HParameterValue* param = MakeParam(DataType::Type::kInt32); | |
| HAdd* instr = new (GetAllocator()) HAdd(DataType::Type::kInt32, param, param, /*dex_pc=*/ 0); | |
| HPhi* phi = ConstructBasicGraphForSelect(return_block, instr); | |
| EXPECT_TRUE(CheckGraphAndTryControlFlowSimplifier()); | |
| EXPECT_INS_REMOVED(phi); | |
| } | |
| // Test that ControlFlowSimplifier succeeds if there is an additional `HPhi` with identical inputs. | |
| TEST_F(ControlFlowSimplifierTest, testSelectWithAddAndExtraPhi) { | |
| // Create a graph with three blocks merging to the `return_block`. | |
| HBasicBlock* return_block = InitEntryMainExitGraphWithReturnVoid(); | |
| HParameterValue* bool_param1 = MakeParam(DataType::Type::kBool); | |
| HParameterValue* bool_param2 = MakeParam(DataType::Type::kBool); | |
| HParameterValue* param = MakeParam(DataType::Type::kInt32); | |
| HInstruction* const0 = graph_->GetIntConstant(0); | |
| auto [if_block1, left, mid] = CreateDiamondPattern(return_block, bool_param1); | |
| HBasicBlock* if_block2 = AddNewBlock(); | |
| if_block1->ReplaceSuccessor(mid, if_block2); | |
| HBasicBlock* right = AddNewBlock(); | |
| if_block2->AddSuccessor(mid); | |
| if_block2->AddSuccessor(right); | |
| HIf* if2 = MakeIf(if_block2, bool_param2); | |
| right->AddSuccessor(return_block); | |
| MakeGoto(right); | |
| ASSERT_TRUE(PredecessorsEqual(return_block, {left, mid, right})); | |
| HAdd* add = MakeBinOp<HAdd>(right, DataType::Type::kInt32, param, param); | |
| HPhi* phi1 = MakePhi(return_block, {param, param, add}); | |
| HPhi* phi2 = MakePhi(return_block, {param, const0, const0}); | |
| // Prevent second `HSelect` match. Do not rely on the "instructions per branch" limit. | |
| MakeInvokeStatic(left, DataType::Type::kVoid, {}, {}); | |
| EXPECT_TRUE(CheckGraphAndTryControlFlowSimplifier()); | |
| ASSERT_BLOCK_RETAINED(left); | |
| ASSERT_BLOCK_REMOVED(mid); | |
| ASSERT_BLOCK_REMOVED(right); | |
| HInstruction* select = if2->GetPrevious(); // `HSelect` is inserted before `HIf`. | |
| ASSERT_TRUE(select->IsSelect()); | |
| ASSERT_INS_RETAINED(phi1); | |
| ASSERT_TRUE(InputsEqual(phi1, {param, select})); | |
| ASSERT_INS_RETAINED(phi2); | |
| ASSERT_TRUE(InputsEqual(phi2, {param, const0})); | |
| } | |
| // Test `HSelect` optimization in an irreducible loop. | |
| TEST_F(ControlFlowSimplifierTest, testSelectInIrreducibleLoop) { | |
| HBasicBlock* return_block = InitEntryMainExitGraphWithReturnVoid(); | |
| auto [split, left_header, right_header, body] = CreateIrreducibleLoop(return_block); | |
| HParameterValue* split_param = MakeParam(DataType::Type::kBool); | |
| HParameterValue* bool_param = MakeParam(DataType::Type::kBool); | |
| HParameterValue* n_param = MakeParam(DataType::Type::kInt32); | |
| MakeIf(split, split_param); | |
| HInstruction* const0 = graph_->GetIntConstant(0); | |
| HInstruction* const1 = graph_->GetIntConstant(1); | |
| auto [left_phi, right_phi, add] = | |
| MakeLinearIrreducibleLoopVar(left_header, right_header, body, const1, const0, const1); | |
| HCondition* condition = MakeCondition(left_header, kCondGE, left_phi, n_param); | |
| MakeIf(left_header, condition); | |
| auto [if_block, then_block, else_block] = CreateDiamondPattern(body, bool_param); | |
| HPhi* phi = MakePhi(body, {const1, const0}); | |
| EXPECT_TRUE(CheckGraphAndTryControlFlowSimplifier()); | |
| HLoopInformation* loop_info = left_header->GetLoopInformation(); | |
| ASSERT_TRUE(loop_info != nullptr); | |
| ASSERT_TRUE(loop_info->IsIrreducible()); | |
| EXPECT_INS_REMOVED(phi); | |
| ASSERT_TRUE(if_block->GetFirstInstruction()->IsSelect()); | |
| ASSERT_EQ(if_block, add->GetBlock()); // Moved when merging blocks. | |
| for (HBasicBlock* removed_block : {then_block, else_block, body}) { | |
| ASSERT_BLOCK_REMOVED(removed_block); | |
| uint32_t removed_block_id = removed_block->GetBlockId(); | |
| ASSERT_FALSE(loop_info->GetBlocks().IsBitSet(removed_block_id)) << removed_block_id; | |
| } | |
| } | |
| } // namespace art |