/* * Copyright (C) 2014 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 "gvn.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 GVNTest : public OptimizingUnitTest {}; TEST_F(GVNTest, LocalFieldElimination) { HBasicBlock* block = InitEntryMainExitGraphWithReturnVoid(); HInstruction* parameter = MakeParam(DataType::Type::kReference); MakeIFieldGet(block, parameter, DataType::Type::kReference, MemberOffset(42)); HInstruction* to_remove = MakeIFieldGet(block, parameter, DataType::Type::kReference, MemberOffset(42)); HInstruction* different_offset = MakeIFieldGet(block, parameter, DataType::Type::kReference, MemberOffset(43)); // Kill the value. MakeIFieldSet(block, parameter, parameter, MemberOffset(42)); HInstruction* use_after_kill = MakeIFieldGet(block, parameter, DataType::Type::kReference, MemberOffset(42)); ASSERT_EQ(to_remove->GetBlock(), block); ASSERT_EQ(different_offset->GetBlock(), block); ASSERT_EQ(use_after_kill->GetBlock(), block); graph_->BuildDominatorTree(); SideEffectsAnalysis side_effects(graph_); side_effects.Run(); GVNOptimization(graph_, side_effects).Run(); ASSERT_TRUE(to_remove->GetBlock() == nullptr); ASSERT_EQ(different_offset->GetBlock(), block); ASSERT_EQ(use_after_kill->GetBlock(), block); } TEST_F(GVNTest, GlobalFieldElimination) { HBasicBlock* join = InitEntryMainExitGraphWithReturnVoid(); auto [block, then, else_] = CreateDiamondPattern(join); HInstruction* parameter = MakeParam(DataType::Type::kReference); HInstruction* field_get = MakeIFieldGet(block, parameter, DataType::Type::kBool, MemberOffset(42)); MakeIf(block, field_get); MakeIFieldGet(then, parameter, DataType::Type::kBool, MemberOffset(42)); MakeIFieldGet(else_, parameter, DataType::Type::kBool, MemberOffset(42)); MakeIFieldGet(join, parameter, DataType::Type::kBool, MemberOffset(42)); graph_->BuildDominatorTree(); SideEffectsAnalysis side_effects(graph_); side_effects.Run(); GVNOptimization(graph_, side_effects).Run(); // Check that all field get instructions have been GVN'ed. ASSERT_TRUE(then->GetFirstInstruction()->IsGoto()); ASSERT_TRUE(else_->GetFirstInstruction()->IsGoto()); ASSERT_TRUE(join->GetFirstInstruction()->IsReturnVoid()); } TEST_F(GVNTest, LoopFieldElimination) { HBasicBlock* return_block = InitEntryMainExitGraphWithReturnVoid(); auto [pre_header, loop_header, loop_body] = CreateWhileLoop(return_block); loop_header->SwapSuccessors(); // Move the loop exit to the "else" successor. HInstruction* parameter = MakeParam(DataType::Type::kReference); MakeIFieldGet(pre_header, parameter, DataType::Type::kBool, MemberOffset(42)); HInstruction* field_get_in_loop_header = MakeIFieldGet(loop_header, parameter, DataType::Type::kBool, MemberOffset(42)); MakeIf(loop_header, field_get_in_loop_header); // Kill inside the loop body to prevent field gets inside the loop header // and the body to be GVN'ed. HInstruction* field_set = MakeIFieldSet(loop_body, parameter, parameter, DataType::Type::kBool, MemberOffset(42)); HInstruction* field_get_in_loop_body = MakeIFieldGet(loop_body, parameter, DataType::Type::kBool, MemberOffset(42)); HInstruction* field_get_in_return_block = MakeIFieldGet(return_block, parameter, DataType::Type::kBool, MemberOffset(42)); ASSERT_EQ(field_get_in_loop_header->GetBlock(), loop_header); ASSERT_EQ(field_get_in_loop_body->GetBlock(), loop_body); ASSERT_EQ(field_get_in_return_block->GetBlock(), return_block); graph_->BuildDominatorTree(); { SideEffectsAnalysis side_effects(graph_); side_effects.Run(); GVNOptimization(graph_, side_effects).Run(); } // Check that all field get instructions are still there. ASSERT_EQ(field_get_in_loop_header->GetBlock(), loop_header); ASSERT_EQ(field_get_in_loop_body->GetBlock(), loop_body); // The `return_block` is dominated by the `loop_header`, whose field get // does not get killed by the loop flags. ASSERT_TRUE(field_get_in_return_block->GetBlock() == nullptr); // Now remove the field set, and check that all field get instructions have been GVN'ed. loop_body->RemoveInstruction(field_set); { SideEffectsAnalysis side_effects(graph_); side_effects.Run(); GVNOptimization(graph_, side_effects).Run(); } ASSERT_TRUE(field_get_in_loop_header->GetBlock() == nullptr); ASSERT_TRUE(field_get_in_loop_body->GetBlock() == nullptr); ASSERT_TRUE(field_get_in_return_block->GetBlock() == nullptr); } // Test that inner loops affect the side effects of the outer loop. TEST_F(GVNTest, LoopSideEffects) { static const SideEffects kCanTriggerGC = SideEffects::CanTriggerGC(); HBasicBlock* outer_loop_exit = InitEntryMainExitGraphWithReturnVoid(); auto [outer_preheader, outer_loop_header, inner_loop_exit] = CreateWhileLoop(outer_loop_exit); outer_loop_header->SwapSuccessors(); // Move the loop exit to the "else" successor. auto [outer_loop_body, inner_loop_header, inner_loop_body] = CreateWhileLoop(inner_loop_exit); inner_loop_header->SwapSuccessors(); // Move the loop exit to the "else" successor. HInstruction* parameter = MakeParam(DataType::Type::kBool); MakeSuspendCheck(outer_loop_header); MakeIf(outer_loop_header, parameter); MakeSuspendCheck(inner_loop_header); MakeIf(inner_loop_header, parameter); graph_->BuildDominatorTree(); ASSERT_TRUE(inner_loop_header->GetLoopInformation()->IsIn( *outer_loop_header->GetLoopInformation())); // Check that the only side effect of loops is to potentially trigger GC. { // Make one block with a side effect. MakeIFieldSet(entry_block_, parameter, parameter, DataType::Type::kReference, MemberOffset(42)); SideEffectsAnalysis side_effects(graph_); side_effects.Run(); ASSERT_TRUE(side_effects.GetBlockEffects(entry_block_).DoesAnyWrite()); ASSERT_FALSE(side_effects.GetBlockEffects(outer_loop_body).DoesAnyWrite()); ASSERT_FALSE(side_effects.GetLoopEffects(outer_loop_header).DoesAnyWrite()); ASSERT_FALSE(side_effects.GetLoopEffects(inner_loop_header).DoesAnyWrite()); ASSERT_TRUE(side_effects.GetLoopEffects(outer_loop_header).Equals(kCanTriggerGC)); ASSERT_TRUE(side_effects.GetLoopEffects(inner_loop_header).Equals(kCanTriggerGC)); } // Check that the side effects of the outer loop does not affect the inner loop. { MakeIFieldSet( outer_loop_body, parameter, parameter, DataType::Type::kReference, MemberOffset(42)); SideEffectsAnalysis side_effects(graph_); side_effects.Run(); ASSERT_TRUE(side_effects.GetBlockEffects(entry_block_).DoesAnyWrite()); ASSERT_TRUE(side_effects.GetBlockEffects(outer_loop_body).DoesAnyWrite()); ASSERT_TRUE(side_effects.GetLoopEffects(outer_loop_header).DoesAnyWrite()); ASSERT_FALSE(side_effects.GetLoopEffects(inner_loop_header).DoesAnyWrite()); ASSERT_TRUE(side_effects.GetLoopEffects(inner_loop_header).Equals(kCanTriggerGC)); } // Check that the side effects of the inner loop affects the outer loop. { outer_loop_body->RemoveInstruction(outer_loop_body->GetFirstInstruction()); MakeIFieldSet( inner_loop_body, parameter, parameter, DataType::Type::kReference, MemberOffset(42)); SideEffectsAnalysis side_effects(graph_); side_effects.Run(); ASSERT_TRUE(side_effects.GetBlockEffects(entry_block_).DoesAnyWrite()); ASSERT_FALSE(side_effects.GetBlockEffects(outer_loop_body).DoesAnyWrite()); ASSERT_TRUE(side_effects.GetLoopEffects(outer_loop_header).DoesAnyWrite()); ASSERT_TRUE(side_effects.GetLoopEffects(inner_loop_header).DoesAnyWrite()); } } } // namespace art