/* * Copyright (C) 2021 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 "instruction_simplifier.h" #include #include #include "gtest/gtest.h" #include "class_root-inl.h" #include "nodes.h" #include "optimizing/data_type.h" #include "optimizing_unit_test.h" namespace art { namespace mirror { class ClassExt; class Throwable; } // namespace mirror template class InstructionSimplifierTestBase : public SuperClass, public OptimizingUnitTestHelper { public: void SetUp() override { SuperClass::SetUp(); gLogVerbosity.compiler = true; } void TearDown() override { SuperClass::TearDown(); gLogVerbosity.compiler = false; } }; class InstructionSimplifierTest : public InstructionSimplifierTestBase {}; // Various configs we can use for testing. Currently used in PartialComparison tests. enum class InstanceOfKind { kSelf, kUnrelatedLoaded, kUnrelatedUnloaded, kSupertype, }; std::ostream& operator<<(std::ostream& os, const InstanceOfKind& comp) { switch (comp) { case InstanceOfKind::kSupertype: return os << "kSupertype"; case InstanceOfKind::kSelf: return os << "kSelf"; case InstanceOfKind::kUnrelatedLoaded: return os << "kUnrelatedLoaded"; case InstanceOfKind::kUnrelatedUnloaded: return os << "kUnrelatedUnloaded"; } } class InstanceOfInstructionSimplifierTestGroup : public InstructionSimplifierTestBase> { public: bool GetConstantResult() const { switch (GetParam()) { case InstanceOfKind::kSupertype: case InstanceOfKind::kSelf: return true; case InstanceOfKind::kUnrelatedLoaded: case InstanceOfKind::kUnrelatedUnloaded: return false; } } std::pair GetLoadClasses(VariableSizedHandleScope* vshs) { InstanceOfKind kind = GetParam(); ScopedObjectAccess soa(Thread::Current()); // New inst always needs to have a valid rti since we dcheck that. HLoadClass* new_inst = MakeClassLoad( /* ti= */ std::nullopt, vshs->NewHandle(GetClassRoot())); new_inst->SetValidLoadedClassRTI(); if (kind == InstanceOfKind::kSelf) { return {new_inst, new_inst}; } if (kind == InstanceOfKind::kUnrelatedUnloaded) { HLoadClass* target_class = MakeClassLoad(); EXPECT_FALSE(target_class->GetLoadedClassRTI().IsValid()); return {new_inst, target_class}; } // Force both classes to be a real classes. // For simplicity we use class-roots as the types. The new-inst will always // be a ClassExt, unrelated-loaded will always be Throwable and super will // always be Object HLoadClass* target_class = MakeClassLoad( /* ti= */ std::nullopt, vshs->NewHandle(kind == InstanceOfKind::kSupertype ? GetClassRoot() : GetClassRoot())); target_class->SetValidLoadedClassRTI(); EXPECT_TRUE(target_class->GetLoadedClassRTI().IsValid()); return {new_inst, target_class}; } }; // // ENTRY // switch (param) { // case 1: // obj1 = param2; break; // case 2: // obj1 = param3; break; // default: // obj2 = new Obj(); // } // val_phi = PHI[3,4,10] // target_phi = PHI[param2, param3, obj2] // return PredFieldGet[val_phi, target_phi] => PredFieldGet[val_phi, target_phi] TEST_F(InstructionSimplifierTest, SimplifyPredicatedFieldGetNoMerge) { ScopedObjectAccess soa(Thread::Current()); VariableSizedHandleScope vshs(soa.Self()); CreateGraph(&vshs); AdjacencyListGraph blks(SetupFromAdjacencyList("entry", "exit", {{"entry", "case1"}, {"entry", "case2"}, {"entry", "case3"}, {"case1", "breturn"}, {"case2", "breturn"}, {"case3", "breturn"}, {"breturn", "exit"}})); #define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name) GET_BLOCK(entry); GET_BLOCK(exit); GET_BLOCK(case1); GET_BLOCK(case2); GET_BLOCK(case3); GET_BLOCK(breturn); #undef GET_BLOCK HInstruction* bool_value = MakeParam(DataType::Type::kInt32); HInstruction* obj1_param = MakeParam(DataType::Type::kReference); HInstruction* obj2_param = MakeParam(DataType::Type::kReference); HInstruction* c3 = graph_->GetIntConstant(3); HInstruction* c4 = graph_->GetIntConstant(4); HInstruction* c10 = graph_->GetIntConstant(10); HInstruction* cls = MakeClassLoad(); HInstruction* switch_inst = new (GetAllocator()) HPackedSwitch(0, 2, bool_value); entry->AddInstruction(cls); entry->AddInstruction(switch_inst); ManuallyBuildEnvFor(cls, {}); HInstruction* goto_c1 = new (GetAllocator()) HGoto(); case1->AddInstruction(goto_c1); HInstruction* goto_c2 = new (GetAllocator()) HGoto(); case2->AddInstruction(goto_c2); HInstruction* obj3 = MakeNewInstance(cls); HInstruction* goto_c3 = new (GetAllocator()) HGoto(); case3->AddInstruction(obj3); case3->AddInstruction(goto_c3); HPhi* val_phi = MakePhi({c3, c4, c10}); HPhi* obj_phi = MakePhi({obj1_param, obj2_param, obj3}); HPredicatedInstanceFieldGet* read_end = new (GetAllocator()) HPredicatedInstanceFieldGet(obj_phi, nullptr, val_phi, val_phi->GetType(), MemberOffset(10), false, 42, 0, graph_->GetDexFile(), 0); HInstruction* return_exit = new (GetAllocator()) HReturn(read_end); breturn->AddPhi(val_phi); breturn->AddPhi(obj_phi); breturn->AddInstruction(read_end); breturn->AddInstruction(return_exit); SetupExit(exit); LOG(INFO) << "Pre simplification " << blks; graph_->ClearDominanceInformation(); graph_->BuildDominatorTree(); InstructionSimplifier simp(graph_, /*codegen=*/nullptr); simp.Run(); LOG(INFO) << "Post simplify " << blks; EXPECT_INS_RETAINED(read_end); EXPECT_INS_EQ(read_end->GetTarget(), obj_phi); EXPECT_INS_EQ(read_end->GetDefaultValue(), val_phi); } // // ENTRY // switch (param) { // case 1: // obj1 = param2; break; // case 2: // obj1 = param3; break; // default: // obj2 = new Obj(); // } // val_phi = PHI[3,3,10] // target_phi = PHI[param2, param3, obj2] // return PredFieldGet[val_phi, target_phi] => PredFieldGet[3, target_phi] TEST_F(InstructionSimplifierTest, SimplifyPredicatedFieldGetMerge) { ScopedObjectAccess soa(Thread::Current()); VariableSizedHandleScope vshs(soa.Self()); CreateGraph(&vshs); AdjacencyListGraph blks(SetupFromAdjacencyList("entry", "exit", {{"entry", "case1"}, {"entry", "case2"}, {"entry", "case3"}, {"case1", "breturn"}, {"case2", "breturn"}, {"case3", "breturn"}, {"breturn", "exit"}})); #define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name) GET_BLOCK(entry); GET_BLOCK(exit); GET_BLOCK(case1); GET_BLOCK(case2); GET_BLOCK(case3); GET_BLOCK(breturn); #undef GET_BLOCK HInstruction* bool_value = MakeParam(DataType::Type::kInt32); HInstruction* obj1_param = MakeParam(DataType::Type::kReference); HInstruction* obj2_param = MakeParam(DataType::Type::kReference); HInstruction* c3 = graph_->GetIntConstant(3); HInstruction* c10 = graph_->GetIntConstant(10); HInstruction* cls = MakeClassLoad(); HInstruction* switch_inst = new (GetAllocator()) HPackedSwitch(0, 2, bool_value); entry->AddInstruction(cls); entry->AddInstruction(switch_inst); ManuallyBuildEnvFor(cls, {}); HInstruction* goto_c1 = new (GetAllocator()) HGoto(); case1->AddInstruction(goto_c1); HInstruction* goto_c2 = new (GetAllocator()) HGoto(); case2->AddInstruction(goto_c2); HInstruction* obj3 = MakeNewInstance(cls); HInstruction* goto_c3 = new (GetAllocator()) HGoto(); case3->AddInstruction(obj3); case3->AddInstruction(goto_c3); HPhi* val_phi = MakePhi({c3, c3, c10}); HPhi* obj_phi = MakePhi({obj1_param, obj2_param, obj3}); HPredicatedInstanceFieldGet* read_end = new (GetAllocator()) HPredicatedInstanceFieldGet(obj_phi, nullptr, val_phi, val_phi->GetType(), MemberOffset(10), false, 42, 0, graph_->GetDexFile(), 0); HInstruction* return_exit = new (GetAllocator()) HReturn(read_end); breturn->AddPhi(val_phi); breturn->AddPhi(obj_phi); breturn->AddInstruction(read_end); breturn->AddInstruction(return_exit); SetupExit(exit); LOG(INFO) << "Pre simplification " << blks; graph_->ClearDominanceInformation(); graph_->BuildDominatorTree(); InstructionSimplifier simp(graph_, /*codegen=*/nullptr); simp.Run(); LOG(INFO) << "Post simplify " << blks; EXPECT_FALSE(obj3->CanBeNull()); EXPECT_INS_RETAINED(read_end); EXPECT_INS_EQ(read_end->GetTarget(), obj_phi); EXPECT_INS_EQ(read_end->GetDefaultValue(), c3); } // // ENTRY // if (param) { // obj1 = new Obj(); // } else { // obj2 = new Obj(); // } // val_phi = PHI[3,10] // target_phi = PHI[obj1, obj2] // return PredFieldGet[val_phi, target_phi] => FieldGet[target_phi] TEST_F(InstructionSimplifierTest, SimplifyPredicatedFieldGetNoNull) { ScopedObjectAccess soa(Thread::Current()); VariableSizedHandleScope vshs(soa.Self()); CreateGraph(&vshs); AdjacencyListGraph blks(SetupFromAdjacencyList("entry", "exit", {{"entry", "left"}, {"entry", "right"}, {"left", "breturn"}, {"right", "breturn"}, {"breturn", "exit"}})); #define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name) GET_BLOCK(entry); GET_BLOCK(exit); GET_BLOCK(left); GET_BLOCK(right); GET_BLOCK(breturn); #undef GET_BLOCK HInstruction* bool_value = MakeParam(DataType::Type::kBool); HInstruction* c3 = graph_->GetIntConstant(3); HInstruction* c10 = graph_->GetIntConstant(10); HInstruction* cls = MakeClassLoad(); HInstruction* if_inst = new (GetAllocator()) HIf(bool_value); entry->AddInstruction(cls); entry->AddInstruction(if_inst); ManuallyBuildEnvFor(cls, {}); HInstruction* obj1 = MakeNewInstance(cls); HInstruction* goto_left = new (GetAllocator()) HGoto(); left->AddInstruction(obj1); left->AddInstruction(goto_left); HInstruction* obj2 = MakeNewInstance(cls); HInstruction* goto_right = new (GetAllocator()) HGoto(); right->AddInstruction(obj2); right->AddInstruction(goto_right); HPhi* val_phi = MakePhi({c3, c10}); HPhi* obj_phi = MakePhi({obj1, obj2}); obj_phi->SetCanBeNull(false); HInstruction* read_end = new (GetAllocator()) HPredicatedInstanceFieldGet(obj_phi, nullptr, val_phi, val_phi->GetType(), MemberOffset(10), false, 42, 0, graph_->GetDexFile(), 0); HInstruction* return_exit = new (GetAllocator()) HReturn(read_end); breturn->AddPhi(val_phi); breturn->AddPhi(obj_phi); breturn->AddInstruction(read_end); breturn->AddInstruction(return_exit); SetupExit(exit); LOG(INFO) << "Pre simplification " << blks; graph_->ClearDominanceInformation(); graph_->BuildDominatorTree(); InstructionSimplifier simp(graph_, /*codegen=*/nullptr); simp.Run(); LOG(INFO) << "Post simplify " << blks; EXPECT_FALSE(obj1->CanBeNull()); EXPECT_FALSE(obj2->CanBeNull()); EXPECT_INS_REMOVED(read_end); HInstanceFieldGet* ifget = FindSingleInstruction(graph_, breturn); ASSERT_NE(ifget, nullptr); EXPECT_INS_EQ(ifget->InputAt(0), obj_phi); } // // ENTRY // obj = new Obj(); // // Make sure this graph isn't broken // if (obj instanceof ) { // // LEFT // } else { // // RIGHT // } // EXIT // return obj.field TEST_P(InstanceOfInstructionSimplifierTestGroup, ExactClassInstanceOfOther) { ScopedObjectAccess soa(Thread::Current()); VariableSizedHandleScope vshs(soa.Self()); InitGraph(/*handles=*/&vshs); AdjacencyListGraph blks(SetupFromAdjacencyList("entry", "exit", {{"entry", "left"}, {"entry", "right"}, {"left", "breturn"}, {"right", "breturn"}, {"breturn", "exit"}})); #define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name) GET_BLOCK(entry); GET_BLOCK(exit); GET_BLOCK(breturn); GET_BLOCK(left); GET_BLOCK(right); #undef GET_BLOCK EnsurePredecessorOrder(breturn, {left, right}); HInstruction* test_res = graph_->GetIntConstant(GetConstantResult() ? 1 : 0); auto [new_inst_klass, target_klass] = GetLoadClasses(&vshs); HInstruction* new_inst = MakeNewInstance(new_inst_klass); new_inst->SetReferenceTypeInfo( ReferenceTypeInfo::Create(new_inst_klass->GetClass(), /*is_exact=*/true)); HInstanceOf* instance_of = new (GetAllocator()) HInstanceOf(new_inst, target_klass, TypeCheckKind::kClassHierarchyCheck, target_klass->GetClass(), 0u, GetAllocator(), nullptr, nullptr); if (target_klass->GetLoadedClassRTI().IsValid()) { instance_of->SetValidTargetClassRTI(); } HInstruction* if_inst = new (GetAllocator()) HIf(instance_of); entry->AddInstruction(new_inst_klass); if (new_inst_klass != target_klass) { entry->AddInstruction(target_klass); } entry->AddInstruction(new_inst); entry->AddInstruction(instance_of); entry->AddInstruction(if_inst); ManuallyBuildEnvFor(new_inst_klass, {}); if (new_inst_klass != target_klass) { target_klass->CopyEnvironmentFrom(new_inst_klass->GetEnvironment()); } new_inst->CopyEnvironmentFrom(new_inst_klass->GetEnvironment()); HInstruction* goto_left = new (GetAllocator()) HGoto(); left->AddInstruction(goto_left); HInstruction* goto_right = new (GetAllocator()) HGoto(); right->AddInstruction(goto_right); HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32)); HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom); breturn->AddInstruction(read_bottom); breturn->AddInstruction(return_exit); SetupExit(exit); // PerformLSE expects this to be empty. graph_->ClearDominanceInformation(); LOG(INFO) << "Pre simplification " << blks; graph_->ClearDominanceInformation(); graph_->BuildDominatorTree(); InstructionSimplifier simp(graph_, /*codegen=*/nullptr); simp.Run(); LOG(INFO) << "Post simplify " << blks; if (!GetConstantResult() || GetParam() == InstanceOfKind::kSelf) { EXPECT_INS_RETAINED(target_klass); } else { EXPECT_INS_REMOVED(target_klass); } EXPECT_INS_REMOVED(instance_of); EXPECT_INS_EQ(if_inst->InputAt(0), test_res); } // // ENTRY // obj = new Obj(); // ()obj; // // Make sure this graph isn't broken // EXIT // return obj TEST_P(InstanceOfInstructionSimplifierTestGroup, ExactClassCheckCastOther) { ScopedObjectAccess soa(Thread::Current()); VariableSizedHandleScope vshs(soa.Self()); InitGraph(/*handles=*/&vshs); AdjacencyListGraph blks(SetupFromAdjacencyList("entry", "exit", {{"entry", "exit"}})); #define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name) GET_BLOCK(entry); GET_BLOCK(exit); #undef GET_BLOCK auto [new_inst_klass, target_klass] = GetLoadClasses(&vshs); HInstruction* new_inst = MakeNewInstance(new_inst_klass); new_inst->SetReferenceTypeInfo( ReferenceTypeInfo::Create(new_inst_klass->GetClass(), /*is_exact=*/true)); HCheckCast* check_cast = new (GetAllocator()) HCheckCast(new_inst, target_klass, TypeCheckKind::kClassHierarchyCheck, target_klass->GetClass(), 0u, GetAllocator(), nullptr, nullptr); if (target_klass->GetLoadedClassRTI().IsValid()) { check_cast->SetValidTargetClassRTI(); } HInstruction* entry_return = new (GetAllocator()) HReturn(new_inst); entry->AddInstruction(new_inst_klass); if (new_inst_klass != target_klass) { entry->AddInstruction(target_klass); } entry->AddInstruction(new_inst); entry->AddInstruction(check_cast); entry->AddInstruction(entry_return); ManuallyBuildEnvFor(new_inst_klass, {}); if (new_inst_klass != target_klass) { target_klass->CopyEnvironmentFrom(new_inst_klass->GetEnvironment()); } new_inst->CopyEnvironmentFrom(new_inst_klass->GetEnvironment()); SetupExit(exit); // PerformLSE expects this to be empty. graph_->ClearDominanceInformation(); LOG(INFO) << "Pre simplification " << blks; graph_->ClearDominanceInformation(); graph_->BuildDominatorTree(); InstructionSimplifier simp(graph_, /*codegen=*/nullptr); simp.Run(); LOG(INFO) << "Post simplify " << blks; if (!GetConstantResult() || GetParam() == InstanceOfKind::kSelf) { EXPECT_INS_RETAINED(target_klass); } else { EXPECT_INS_REMOVED(target_klass); } if (GetConstantResult()) { EXPECT_INS_REMOVED(check_cast); } else { EXPECT_INS_RETAINED(check_cast); } } INSTANTIATE_TEST_SUITE_P(InstructionSimplifierTest, InstanceOfInstructionSimplifierTestGroup, testing::Values(InstanceOfKind::kSelf, InstanceOfKind::kUnrelatedLoaded, InstanceOfKind::kUnrelatedUnloaded, InstanceOfKind::kSupertype)); } // namespace art