blob: 448dc32006adbe0ed37ada5c4b7cc2585a2c2317 [file]
/*
* 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() {
if (kDebugSimplifierTests) {
graph_->Dump(LOG_STREAM(INFO) << "Pre simplification ", /* codegen_= */ nullptr);
}
graph_->ClearDominanceInformation();
graph_->BuildDominatorTree();
InstructionSimplifier simp(graph_, /*codegen=*/nullptr);
simp.Run();
if (kDebugSimplifierTests) {
graph_->Dump(LOG_STREAM(INFO) << "Post simplify ", /* codegen_= */ nullptr);
}
}
};
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(HBasicBlock* block,
VariableSizedHandleScope* vshs)
REQUIRES_SHARED(Locks::mutator_lock_) {
InstanceOfKind kind = GetParam();
// New inst always needs to have a valid rti since we dcheck that.
HLoadClass* new_inst = MakeLoadClass(
block,
/* 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 = MakeLoadClass(block);
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 = MakeLoadClass(
block,
/* 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
// 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());
HBasicBlock* breturn = InitEntryMainExitGraph(/*handles=*/&vshs);
auto [if_block, left, right] = CreateDiamondPattern(breturn);
EnsurePredecessorOrder(breturn, {left, right});
HInstruction* test_res = graph_->GetIntConstant(GetConstantResult() ? 1 : 0);
auto [new_inst_klass, target_klass] = GetLoadClasses(if_block, &vshs);
HInstruction* new_inst = MakeNewInstance(if_block, 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();
}
if_block->AddInstruction(instance_of);
HIf* if_inst = MakeIf(if_block, instance_of);
HInstruction* read_bottom =
MakeIFieldGet(breturn, new_inst, DataType::Type::kInt32, MemberOffset(32));
MakeReturn(breturn, read_bottom);
PerformSimplification();
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());
HBasicBlock* main = InitEntryMainExitGraph(/*handles=*/&vshs);
auto [new_inst_klass, target_klass] = GetLoadClasses(main, &vshs);
HInstruction* new_inst = MakeNewInstance(main, 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();
}
main->AddInstruction(check_cast);
MakeReturn(main, new_inst);
PerformSimplification();
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