/* * 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 "base/arena_allocator.h" #include "builder.h" #include "gvn.h" #include "nodes.h" #include "optimizing_unit_test.h" #include "side_effects_analysis.h" namespace art { class GVNTest : public CommonCompilerTest {}; TEST_F(GVNTest, LocalFieldElimination) { ArenaPool pool; ArenaAllocator allocator(&pool); ScopedNullHandle<mirror::DexCache> dex_cache; HGraph* graph = CreateGraph(&allocator); HBasicBlock* entry = new (&allocator) HBasicBlock(graph); graph->AddBlock(entry); graph->SetEntryBlock(entry); HInstruction* parameter = new (&allocator) HParameterValue(graph->GetDexFile(), 0, 0, Primitive::kPrimNot); entry->AddInstruction(parameter); HBasicBlock* block = new (&allocator) HBasicBlock(graph); graph->AddBlock(block); entry->AddSuccessor(block); block->AddInstruction(new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimNot, MemberOffset(42), false, kUnknownFieldIndex, kUnknownClassDefIndex, graph->GetDexFile(), dex_cache, 0)); block->AddInstruction(new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimNot, MemberOffset(42), false, kUnknownFieldIndex, kUnknownClassDefIndex, graph->GetDexFile(), dex_cache, 0)); HInstruction* to_remove = block->GetLastInstruction(); block->AddInstruction(new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimNot, MemberOffset(43), false, kUnknownFieldIndex, kUnknownClassDefIndex, graph->GetDexFile(), dex_cache, 0)); HInstruction* different_offset = block->GetLastInstruction(); // Kill the value. block->AddInstruction(new (&allocator) HInstanceFieldSet(parameter, parameter, Primitive::kPrimNot, MemberOffset(42), false, kUnknownFieldIndex, kUnknownClassDefIndex, graph->GetDexFile(), dex_cache, 0)); block->AddInstruction(new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimNot, MemberOffset(42), false, kUnknownFieldIndex, kUnknownClassDefIndex, graph->GetDexFile(), dex_cache, 0)); HInstruction* use_after_kill = block->GetLastInstruction(); block->AddInstruction(new (&allocator) HExit()); 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) { ArenaPool pool; ArenaAllocator allocator(&pool); ScopedNullHandle<mirror::DexCache> dex_cache; HGraph* graph = CreateGraph(&allocator); HBasicBlock* entry = new (&allocator) HBasicBlock(graph); graph->AddBlock(entry); graph->SetEntryBlock(entry); HInstruction* parameter = new (&allocator) HParameterValue(graph->GetDexFile(), 0, 0, Primitive::kPrimNot); entry->AddInstruction(parameter); HBasicBlock* block = new (&allocator) HBasicBlock(graph); graph->AddBlock(block); entry->AddSuccessor(block); block->AddInstruction(new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimBoolean, MemberOffset(42), false, kUnknownFieldIndex, kUnknownClassDefIndex, graph->GetDexFile(), dex_cache, 0)); block->AddInstruction(new (&allocator) HIf(block->GetLastInstruction())); HBasicBlock* then = new (&allocator) HBasicBlock(graph); HBasicBlock* else_ = new (&allocator) HBasicBlock(graph); HBasicBlock* join = new (&allocator) HBasicBlock(graph); graph->AddBlock(then); graph->AddBlock(else_); graph->AddBlock(join); block->AddSuccessor(then); block->AddSuccessor(else_); then->AddSuccessor(join); else_->AddSuccessor(join); then->AddInstruction(new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimBoolean, MemberOffset(42), false, kUnknownFieldIndex, kUnknownClassDefIndex, graph->GetDexFile(), dex_cache, 0)); then->AddInstruction(new (&allocator) HGoto()); else_->AddInstruction(new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimBoolean, MemberOffset(42), false, kUnknownFieldIndex, kUnknownClassDefIndex, graph->GetDexFile(), dex_cache, 0)); else_->AddInstruction(new (&allocator) HGoto()); join->AddInstruction(new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimBoolean, MemberOffset(42), false, kUnknownFieldIndex, kUnknownClassDefIndex, graph->GetDexFile(), dex_cache, 0)); join->AddInstruction(new (&allocator) HExit()); 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()->IsExit()); } TEST_F(GVNTest, LoopFieldElimination) { ArenaPool pool; ArenaAllocator allocator(&pool); ScopedNullHandle<mirror::DexCache> dex_cache; HGraph* graph = CreateGraph(&allocator); HBasicBlock* entry = new (&allocator) HBasicBlock(graph); graph->AddBlock(entry); graph->SetEntryBlock(entry); HInstruction* parameter = new (&allocator) HParameterValue(graph->GetDexFile(), 0, 0, Primitive::kPrimNot); entry->AddInstruction(parameter); HBasicBlock* block = new (&allocator) HBasicBlock(graph); graph->AddBlock(block); entry->AddSuccessor(block); block->AddInstruction(new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimBoolean, MemberOffset(42), false, kUnknownFieldIndex, kUnknownClassDefIndex, graph->GetDexFile(), dex_cache, 0)); block->AddInstruction(new (&allocator) HGoto()); HBasicBlock* loop_header = new (&allocator) HBasicBlock(graph); HBasicBlock* loop_body = new (&allocator) HBasicBlock(graph); HBasicBlock* exit = new (&allocator) HBasicBlock(graph); graph->AddBlock(loop_header); graph->AddBlock(loop_body); graph->AddBlock(exit); block->AddSuccessor(loop_header); loop_header->AddSuccessor(loop_body); loop_header->AddSuccessor(exit); loop_body->AddSuccessor(loop_header); loop_header->AddInstruction(new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimBoolean, MemberOffset(42), false, kUnknownFieldIndex, kUnknownClassDefIndex, graph->GetDexFile(), dex_cache, 0)); HInstruction* field_get_in_loop_header = loop_header->GetLastInstruction(); loop_header->AddInstruction(new (&allocator) HIf(block->GetLastInstruction())); // Kill inside the loop body to prevent field gets inside the loop header // and the body to be GVN'ed. loop_body->AddInstruction(new (&allocator) HInstanceFieldSet(parameter, parameter, Primitive::kPrimBoolean, MemberOffset(42), false, kUnknownFieldIndex, kUnknownClassDefIndex, graph->GetDexFile(), dex_cache, 0)); HInstruction* field_set = loop_body->GetLastInstruction(); loop_body->AddInstruction(new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimBoolean, MemberOffset(42), false, kUnknownFieldIndex, kUnknownClassDefIndex, graph->GetDexFile(), dex_cache, 0)); HInstruction* field_get_in_loop_body = loop_body->GetLastInstruction(); loop_body->AddInstruction(new (&allocator) HGoto()); exit->AddInstruction(new (&allocator) HInstanceFieldGet(parameter, Primitive::kPrimBoolean, MemberOffset(42), false, kUnknownFieldIndex, kUnknownClassDefIndex, graph->GetDexFile(), dex_cache, 0)); HInstruction* field_get_in_exit = exit->GetLastInstruction(); exit->AddInstruction(new (&allocator) HExit()); 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_exit->GetBlock(), exit); 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 exit block is dominated by the loop header, whose field get // does not get killed by the loop flags. ASSERT_TRUE(field_get_in_exit->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_exit->GetBlock() == nullptr); } // Test that inner loops affect the side effects of the outer loop. TEST_F(GVNTest, LoopSideEffects) { ArenaPool pool; ArenaAllocator allocator(&pool); ScopedNullHandle<mirror::DexCache> dex_cache; static const SideEffects kCanTriggerGC = SideEffects::CanTriggerGC(); HGraph* graph = CreateGraph(&allocator); HBasicBlock* entry = new (&allocator) HBasicBlock(graph); graph->AddBlock(entry); graph->SetEntryBlock(entry); HBasicBlock* outer_loop_header = new (&allocator) HBasicBlock(graph); HBasicBlock* outer_loop_body = new (&allocator) HBasicBlock(graph); HBasicBlock* outer_loop_exit = new (&allocator) HBasicBlock(graph); HBasicBlock* inner_loop_header = new (&allocator) HBasicBlock(graph); HBasicBlock* inner_loop_body = new (&allocator) HBasicBlock(graph); HBasicBlock* inner_loop_exit = new (&allocator) HBasicBlock(graph); graph->AddBlock(outer_loop_header); graph->AddBlock(outer_loop_body); graph->AddBlock(outer_loop_exit); graph->AddBlock(inner_loop_header); graph->AddBlock(inner_loop_body); graph->AddBlock(inner_loop_exit); entry->AddSuccessor(outer_loop_header); outer_loop_header->AddSuccessor(outer_loop_body); outer_loop_header->AddSuccessor(outer_loop_exit); outer_loop_body->AddSuccessor(inner_loop_header); inner_loop_header->AddSuccessor(inner_loop_body); inner_loop_header->AddSuccessor(inner_loop_exit); inner_loop_body->AddSuccessor(inner_loop_header); inner_loop_exit->AddSuccessor(outer_loop_header); HInstruction* parameter = new (&allocator) HParameterValue(graph->GetDexFile(), 0, 0, Primitive::kPrimBoolean); entry->AddInstruction(parameter); entry->AddInstruction(new (&allocator) HGoto()); outer_loop_header->AddInstruction(new (&allocator) HSuspendCheck()); outer_loop_header->AddInstruction(new (&allocator) HIf(parameter)); outer_loop_body->AddInstruction(new (&allocator) HGoto()); inner_loop_header->AddInstruction(new (&allocator) HSuspendCheck()); inner_loop_header->AddInstruction(new (&allocator) HIf(parameter)); inner_loop_body->AddInstruction(new (&allocator) HGoto()); inner_loop_exit->AddInstruction(new (&allocator) HGoto()); outer_loop_exit->AddInstruction(new (&allocator) HExit()); 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. entry->AddInstruction(new (&allocator) HInstanceFieldSet(parameter, parameter, Primitive::kPrimNot, MemberOffset(42), false, kUnknownFieldIndex, kUnknownClassDefIndex, graph->GetDexFile(), dex_cache, 0)); SideEffectsAnalysis side_effects(graph); side_effects.Run(); ASSERT_TRUE(side_effects.GetBlockEffects(entry).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. { outer_loop_body->InsertInstructionBefore( new (&allocator) HInstanceFieldSet(parameter, parameter, Primitive::kPrimNot, MemberOffset(42), false, kUnknownFieldIndex, kUnknownClassDefIndex, graph->GetDexFile(), dex_cache, 0), outer_loop_body->GetLastInstruction()); SideEffectsAnalysis side_effects(graph); side_effects.Run(); ASSERT_TRUE(side_effects.GetBlockEffects(entry).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()); inner_loop_body->InsertInstructionBefore( new (&allocator) HInstanceFieldSet(parameter, parameter, Primitive::kPrimNot, MemberOffset(42), false, kUnknownFieldIndex, kUnknownClassDefIndex, graph->GetDexFile(), dex_cache, 0), inner_loop_body->GetLastInstruction()); SideEffectsAnalysis side_effects(graph); side_effects.Run(); ASSERT_TRUE(side_effects.GetBlockEffects(entry).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