/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "graph_checker.h" #include "nodes.h" #include "optimizing_unit_test.h" #include "superblock_cloner.h" #include "gtest/gtest.h" namespace art { using HBasicBlockMap = SuperblockCloner::HBasicBlockMap; using HInstructionMap = SuperblockCloner::HInstructionMap; // This class provides methods and helpers for testing various cloning and copying routines: // individual instruction cloning and cloning of the more coarse-grain structures. class SuperblockClonerTest : public OptimizingUnitTest { public: SuperblockClonerTest() : graph_(CreateGraph()), entry_block_(nullptr), exit_block_(nullptr), parameter_(nullptr) {} void CreateBasicLoopControlFlow(/* out */ HBasicBlock** header_p, /* out */ HBasicBlock** body_p) { entry_block_ = new (GetAllocator()) HBasicBlock(graph_); graph_->AddBlock(entry_block_); graph_->SetEntryBlock(entry_block_); HBasicBlock* loop_preheader = new (GetAllocator()) HBasicBlock(graph_); HBasicBlock* loop_header = new (GetAllocator()) HBasicBlock(graph_); HBasicBlock* loop_body = new (GetAllocator()) HBasicBlock(graph_); HBasicBlock* loop_exit = new (GetAllocator()) HBasicBlock(graph_); graph_->AddBlock(loop_preheader); graph_->AddBlock(loop_header); graph_->AddBlock(loop_body); graph_->AddBlock(loop_exit); exit_block_ = new (GetAllocator()) HBasicBlock(graph_); graph_->AddBlock(exit_block_); graph_->SetExitBlock(exit_block_); entry_block_->AddSuccessor(loop_preheader); loop_preheader->AddSuccessor(loop_header); // Loop exit first to have a proper exit condition/target for HIf. loop_header->AddSuccessor(loop_exit); loop_header->AddSuccessor(loop_body); loop_body->AddSuccessor(loop_header); loop_exit->AddSuccessor(exit_block_); *header_p = loop_header; *body_p = loop_body; parameter_ = new (GetAllocator()) HParameterValue(graph_->GetDexFile(), dex::TypeIndex(0), 0, DataType::Type::kInt32); entry_block_->AddInstruction(parameter_); loop_exit->AddInstruction(new (GetAllocator()) HReturnVoid()); exit_block_->AddInstruction(new (GetAllocator()) HExit()); } void CreateBasicLoopDataFlow(HBasicBlock* loop_header, HBasicBlock* loop_body) { uint32_t dex_pc = 0; // Entry block. HIntConstant* const_0 = graph_->GetIntConstant(0); HIntConstant* const_1 = graph_->GetIntConstant(1); HIntConstant* const_128 = graph_->GetIntConstant(128); // Header block. HPhi* phi = new (GetAllocator()) HPhi(GetAllocator(), 0, 0, DataType::Type::kInt32); HInstruction* suspend_check = new (GetAllocator()) HSuspendCheck(); loop_header->AddPhi(phi); loop_header->AddInstruction(suspend_check); loop_header->AddInstruction(new (GetAllocator()) HGreaterThanOrEqual(phi, const_128)); loop_header->AddInstruction(new (GetAllocator()) HIf(parameter_)); // Loop body block. HInstruction* null_check = new (GetAllocator()) HNullCheck(parameter_, dex_pc); HInstruction* array_length = new (GetAllocator()) HArrayLength(null_check, dex_pc); HInstruction* bounds_check = new (GetAllocator()) HBoundsCheck(phi, array_length, dex_pc); HInstruction* array_get = new (GetAllocator()) HArrayGet(null_check, bounds_check, DataType::Type::kInt32, dex_pc); HInstruction* add = new (GetAllocator()) HAdd(DataType::Type::kInt32, array_get, const_1); HInstruction* array_set = new (GetAllocator()) HArraySet(null_check, bounds_check, add, DataType::Type::kInt32, dex_pc); HInstruction* induction_inc = new (GetAllocator()) HAdd(DataType::Type::kInt32, phi, const_1); loop_body->AddInstruction(null_check); loop_body->AddInstruction(array_length); loop_body->AddInstruction(bounds_check); loop_body->AddInstruction(array_get); loop_body->AddInstruction(add); loop_body->AddInstruction(array_set); loop_body->AddInstruction(induction_inc); loop_body->AddInstruction(new (GetAllocator()) HGoto()); phi->AddInput(const_0); phi->AddInput(induction_inc); graph_->SetHasBoundsChecks(true); // Adjust HEnvironment for each instruction which require that. ArenaVector current_locals({phi, const_128, parameter_}, GetAllocator()->Adapter(kArenaAllocInstruction)); HEnvironment* env = ManuallyBuildEnvFor(suspend_check, ¤t_locals); null_check->CopyEnvironmentFrom(env); bounds_check->CopyEnvironmentFrom(env); } HEnvironment* ManuallyBuildEnvFor(HInstruction* instruction, ArenaVector* current_locals) { HEnvironment* environment = new (GetAllocator()) HEnvironment( (GetAllocator()), current_locals->size(), graph_->GetArtMethod(), instruction->GetDexPc(), instruction); environment->CopyFrom(ArrayRef(*current_locals)); instruction->SetRawEnvironment(environment); return environment; } bool CheckGraph() { GraphChecker checker(graph_); checker.Run(); if (!checker.IsValid()) { for (const std::string& error : checker.GetErrors()) { std::cout << error << std::endl; } return false; } return true; } HGraph* graph_; HBasicBlock* entry_block_; HBasicBlock* exit_block_; HInstruction* parameter_; }; TEST_F(SuperblockClonerTest, IndividualInstrCloner) { HBasicBlock* header = nullptr; HBasicBlock* loop_body = nullptr; CreateBasicLoopControlFlow(&header, &loop_body); CreateBasicLoopDataFlow(header, loop_body); graph_->BuildDominatorTree(); ASSERT_TRUE(CheckGraph()); HSuspendCheck* old_suspend_check = header->GetLoopInformation()->GetSuspendCheck(); CloneAndReplaceInstructionVisitor visitor(graph_); // Do instruction cloning and replacement twice with different visiting order. visitor.VisitInsertionOrder(); size_t instr_replaced_by_clones_count = visitor.GetInstrReplacedByClonesCount(); EXPECT_EQ(instr_replaced_by_clones_count, 12u); EXPECT_TRUE(CheckGraph()); visitor.VisitReversePostOrder(); instr_replaced_by_clones_count = visitor.GetInstrReplacedByClonesCount(); EXPECT_EQ(instr_replaced_by_clones_count, 24u); EXPECT_TRUE(CheckGraph()); HSuspendCheck* new_suspend_check = header->GetLoopInformation()->GetSuspendCheck(); EXPECT_NE(new_suspend_check, old_suspend_check); EXPECT_NE(new_suspend_check, nullptr); } // Tests SuperblockCloner::CloneBasicBlocks - check instruction cloning and initial remapping of // instructions' inputs. TEST_F(SuperblockClonerTest, CloneBasicBlocks) { HBasicBlock* header = nullptr; HBasicBlock* loop_body = nullptr; ArenaAllocator* arena = graph_->GetAllocator(); CreateBasicLoopControlFlow(&header, &loop_body); CreateBasicLoopDataFlow(header, loop_body); graph_->BuildDominatorTree(); ASSERT_TRUE(CheckGraph()); ArenaBitVector orig_bb_set( arena, graph_->GetBlocks().size(), false, kArenaAllocSuperblockCloner); HBasicBlockMap bb_map(std::less(), arena->Adapter(kArenaAllocSuperblockCloner)); HInstructionMap hir_map(std::less(), arena->Adapter(kArenaAllocSuperblockCloner)); HLoopInformation* loop_info = header->GetLoopInformation(); orig_bb_set.Union(&loop_info->GetBlocks()); SuperblockCloner cloner(graph_, &orig_bb_set, &bb_map, &hir_map); EXPECT_TRUE(cloner.IsSubgraphClonable()); cloner.CloneBasicBlocks(); EXPECT_EQ(bb_map.size(), 2u); EXPECT_EQ(hir_map.size(), 12u); for (auto it : hir_map) { HInstruction* orig_instr = it.first; HInstruction* copy_instr = it.second; EXPECT_EQ(cloner.GetBlockCopy(orig_instr->GetBlock()), copy_instr->GetBlock()); EXPECT_EQ(orig_instr->GetKind(), copy_instr->GetKind()); EXPECT_EQ(orig_instr->GetType(), copy_instr->GetType()); if (orig_instr->IsPhi()) { continue; } EXPECT_EQ(orig_instr->InputCount(), copy_instr->InputCount()); // Check that inputs match. for (size_t i = 0, e = orig_instr->InputCount(); i < e; i++) { HInstruction* orig_input = orig_instr->InputAt(i); HInstruction* copy_input = copy_instr->InputAt(i); if (cloner.IsInOrigBBSet(orig_input->GetBlock())) { EXPECT_EQ(cloner.GetInstrCopy(orig_input), copy_input); } else { EXPECT_EQ(orig_input, copy_input); } } EXPECT_EQ(orig_instr->HasEnvironment(), copy_instr->HasEnvironment()); // Check that environments match. if (orig_instr->HasEnvironment()) { HEnvironment* orig_env = orig_instr->GetEnvironment(); HEnvironment* copy_env = copy_instr->GetEnvironment(); EXPECT_EQ(copy_env->GetParent(), nullptr); EXPECT_EQ(orig_env->Size(), copy_env->Size()); for (size_t i = 0, e = orig_env->Size(); i < e; i++) { HInstruction* orig_input = orig_env->GetInstructionAt(i); HInstruction* copy_input = copy_env->GetInstructionAt(i); if (cloner.IsInOrigBBSet(orig_input->GetBlock())) { EXPECT_EQ(cloner.GetInstrCopy(orig_input), copy_input); } else { EXPECT_EQ(orig_input, copy_input); } } } } } // SuperblockCloner::CleanUpControlFlow - checks algorithms of local adjustments of the control // flow. TEST_F(SuperblockClonerTest, AdjustControlFlowInfo) { HBasicBlock* header = nullptr; HBasicBlock* loop_body = nullptr; ArenaAllocator* arena = graph_->GetAllocator(); CreateBasicLoopControlFlow(&header, &loop_body); CreateBasicLoopDataFlow(header, loop_body); graph_->BuildDominatorTree(); ASSERT_TRUE(CheckGraph()); ArenaBitVector orig_bb_set( arena, graph_->GetBlocks().size(), false, kArenaAllocSuperblockCloner); HLoopInformation* loop_info = header->GetLoopInformation(); orig_bb_set.Union(&loop_info->GetBlocks()); SuperblockCloner cloner(graph_, &orig_bb_set, nullptr, nullptr); EXPECT_TRUE(cloner.IsSubgraphClonable()); cloner.FindAndSetLocalAreaForAdjustments(); cloner.CleanUpControlFlow(); EXPECT_TRUE(CheckGraph()); EXPECT_TRUE(entry_block_->Dominates(header)); EXPECT_TRUE(entry_block_->Dominates(exit_block_)); EXPECT_EQ(header->GetLoopInformation(), loop_info); EXPECT_EQ(loop_info->GetHeader(), header); EXPECT_TRUE(loop_info->Contains(*loop_body)); EXPECT_TRUE(loop_info->IsBackEdge(*loop_body)); } } // namespace art