| /* |
| * 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 |