blob: 966f5b91cfc596150da1112ddea2af35a9124ac3 [file] [log] [blame]
/*
* 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 <initializer_list>
#include <tuple>
#include "gtest/gtest.h"
#include "class_root-inl.h"
#include "nodes.h"
#include "optimizing/data_type.h"
#include "optimizing_unit_test.h"
namespace art HIDDEN {
namespace mirror {
class ClassExt;
class Throwable;
} // namespace mirror
static constexpr bool kDebugSimplifierTests = false;
template<typename SuperClass>
class InstructionSimplifierTestBase : public SuperClass, public OptimizingUnitTestHelper {
public:
InstructionSimplifierTestBase() {
this->use_boot_image_ = true; // Make the Runtime creation cheaper.
}
void SetUp() override {
SuperClass::SetUp();
gLogVerbosity.compiler = true;
}
void TearDown() override {
SuperClass::TearDown();
gLogVerbosity.compiler = false;
}
void PerformSimplification(const AdjacencyListGraph& blks) {
if (kDebugSimplifierTests) {
LOG(INFO) << "Pre simplification " << blks;
}
graph_->ClearDominanceInformation();
graph_->BuildDominatorTree();
InstructionSimplifier simp(graph_, /*codegen=*/nullptr);
simp.Run();
if (kDebugSimplifierTests) {
LOG(INFO) << "Post simplify " << blks;
}
}
};
class InstructionSimplifierTest : public InstructionSimplifierTestBase<CommonCompilerTest> {};
// 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<CommonCompilerTestWithParam<InstanceOfKind>> {
public:
bool GetConstantResult() const {
switch (GetParam()) {
case InstanceOfKind::kSupertype:
case InstanceOfKind::kSelf:
return true;
case InstanceOfKind::kUnrelatedLoaded:
case InstanceOfKind::kUnrelatedUnloaded:
return false;
}
}
std::pair<HLoadClass*, HLoadClass*> 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<mirror::Class>(GetClassRoot<mirror::ClassExt>()));
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<mirror::Class>(kind == InstanceOfKind::kSupertype ?
GetClassRoot<mirror::Object>() :
GetClassRoot<mirror::Throwable>()));
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);
PerformSimplification(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);
PerformSimplification(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);
PerformSimplification(blks);
EXPECT_FALSE(obj1->CanBeNull());
EXPECT_FALSE(obj2->CanBeNull());
EXPECT_INS_REMOVED(read_end);
HInstanceFieldGet* ifget = FindSingleInstruction<HInstanceFieldGet>(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 <other>) {
// // 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);
PerformSimplification(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();
// (<other>)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);
PerformSimplification(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