blob: d90567ae7ef7fe5691804eef0330090adc34e457 [file] [log] [blame]
/*
* Copyright (C) 2016 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 "reference_type_propagation.h"
#include <random>
#include "base/arena_allocator.h"
#include "base/transform_array_ref.h"
#include "base/transform_iterator.h"
#include "builder.h"
#include "nodes.h"
#include "object_lock.h"
#include "optimizing_unit_test.h"
namespace art {
// TODO It would be good to use the following but there is a miniscule amount of
// chance for flakiness so we'll just use a set seed instead.
constexpr bool kUseTrueRandomness = false;
/**
* Fixture class for unit testing the ReferenceTypePropagation phase. Used to verify the
* functionality of methods and situations that are hard to set up with checker tests.
*/
template<typename SuperTest>
class ReferenceTypePropagationTestBase : public SuperTest, public OptimizingUnitTestHelper {
public:
ReferenceTypePropagationTestBase() : graph_(nullptr), propagation_(nullptr) { }
~ReferenceTypePropagationTestBase() { }
void SetupPropagation(VariableSizedHandleScope* handles) {
graph_ = CreateGraph(handles);
propagation_ = new (GetAllocator()) ReferenceTypePropagation(graph_,
Handle<mirror::ClassLoader>(),
Handle<mirror::DexCache>(),
true,
"test_prop");
}
// Relay method to merge type in reference type propagation.
ReferenceTypeInfo MergeTypes(const ReferenceTypeInfo& a,
const ReferenceTypeInfo& b) REQUIRES_SHARED(Locks::mutator_lock_) {
return propagation_->MergeTypes(a, b, graph_->GetHandleCache());
}
// Helper method to construct an invalid type.
ReferenceTypeInfo InvalidType() {
return ReferenceTypeInfo::CreateInvalid();
}
// Helper method to construct the Object type.
ReferenceTypeInfo ObjectType(bool is_exact = true) REQUIRES_SHARED(Locks::mutator_lock_) {
return ReferenceTypeInfo::Create(graph_->GetHandleCache()->GetObjectClassHandle(), is_exact);
}
// Helper method to construct the String type.
ReferenceTypeInfo StringType(bool is_exact = true) REQUIRES_SHARED(Locks::mutator_lock_) {
return ReferenceTypeInfo::Create(graph_->GetHandleCache()->GetStringClassHandle(), is_exact);
}
// General building fields.
HGraph* graph_;
ReferenceTypePropagation* propagation_;
};
class ReferenceTypePropagationTest : public ReferenceTypePropagationTestBase<CommonCompilerTest> {};
enum class ShuffleOrder {
kTopological,
kReverseTopological,
kAlmostTopological,
kTrueRandom,
kRandomSetSeed,
kRandom = kUseTrueRandomness ? kTrueRandom : kRandomSetSeed,
};
std::ostream& operator<<(std::ostream& os, ShuffleOrder so) {
switch (so) {
case ShuffleOrder::kAlmostTopological:
return os << "AlmostTopological";
case ShuffleOrder::kReverseTopological:
return os << "ReverseTopological";
case ShuffleOrder::kTopological:
return os << "Topological";
case ShuffleOrder::kTrueRandom:
return os << "TrueRandom";
case ShuffleOrder::kRandomSetSeed:
return os << "RandomSetSeed";
}
}
template <typename Param>
class ParamReferenceTypePropagationTest
: public ReferenceTypePropagationTestBase<CommonCompilerTestWithParam<Param>> {
public:
void MutateList(std::vector<HInstruction*>& lst, ShuffleOrder type);
};
class NonLoopReferenceTypePropagationTestGroup
: public ParamReferenceTypePropagationTest<ShuffleOrder> {
public:
template <typename Func>
void RunVisitListTest(Func mutator);
};
enum class InitialNullState {
kAllNull,
kAllNonNull,
kHalfNull,
kTrueRandom,
kRandomSetSeed,
kRandom = kUseTrueRandomness ? kTrueRandom : kRandomSetSeed,
};
std::ostream& operator<<(std::ostream& os, InitialNullState ni) {
switch (ni) {
case InitialNullState::kAllNull:
return os << "AllNull";
case InitialNullState::kAllNonNull:
return os << "AllNonNull";
case InitialNullState::kHalfNull:
return os << "HalfNull";
case InitialNullState::kTrueRandom:
return os << "TrueRandom";
case InitialNullState::kRandomSetSeed:
return os << "RandomSetSeed";
}
}
struct LoopOptions {
public:
using GtestParam = std::tuple<ShuffleOrder, ssize_t, size_t, InitialNullState>;
explicit LoopOptions(GtestParam in) {
std::tie(shuffle_, null_insertion_, null_phi_arg_, initial_null_state_) = in;
}
ShuffleOrder shuffle_;
// Where in the list of phis we put the null. -1 if don't insert
ssize_t null_insertion_;
// Where in the phi arg-list we put the null.
size_t null_phi_arg_;
// What to set the initial null-state of all the phis to.
InitialNullState initial_null_state_;
};
class LoopReferenceTypePropagationTestGroup
: public ParamReferenceTypePropagationTest<LoopOptions::GtestParam> {
public:
template <typename Func>
void RunVisitListTest(Func mutator);
};
//
// The actual ReferenceTypePropgation unit tests.
//
TEST_F(ReferenceTypePropagationTest, ProperSetup) {
ScopedObjectAccess soa(Thread::Current());
VariableSizedHandleScope handles(soa.Self());
SetupPropagation(&handles);
EXPECT_TRUE(propagation_ != nullptr);
EXPECT_TRUE(graph_->GetInexactObjectRti().IsEqual(ObjectType(false)));
}
TEST_F(ReferenceTypePropagationTest, MergeInvalidTypes) {
ScopedObjectAccess soa(Thread::Current());
VariableSizedHandleScope handles(soa.Self());
SetupPropagation(&handles);
// Two invalid types.
ReferenceTypeInfo t1(MergeTypes(InvalidType(), InvalidType()));
EXPECT_FALSE(t1.IsValid());
EXPECT_FALSE(t1.IsExact());
EXPECT_TRUE(t1.IsEqual(InvalidType()));
// Valid type on right.
ReferenceTypeInfo t2(MergeTypes(InvalidType(), ObjectType()));
EXPECT_TRUE(t2.IsValid());
EXPECT_TRUE(t2.IsExact());
EXPECT_TRUE(t2.IsEqual(ObjectType()));
ReferenceTypeInfo t3(MergeTypes(InvalidType(), StringType()));
EXPECT_TRUE(t3.IsValid());
EXPECT_TRUE(t3.IsExact());
EXPECT_TRUE(t3.IsEqual(StringType()));
// Valid type on left.
ReferenceTypeInfo t4(MergeTypes(ObjectType(), InvalidType()));
EXPECT_TRUE(t4.IsValid());
EXPECT_TRUE(t4.IsExact());
EXPECT_TRUE(t4.IsEqual(ObjectType()));
ReferenceTypeInfo t5(MergeTypes(StringType(), InvalidType()));
EXPECT_TRUE(t5.IsValid());
EXPECT_TRUE(t5.IsExact());
EXPECT_TRUE(t5.IsEqual(StringType()));
}
TEST_F(ReferenceTypePropagationTest, MergeValidTypes) {
ScopedObjectAccess soa(Thread::Current());
VariableSizedHandleScope handles(soa.Self());
SetupPropagation(&handles);
// Same types.
ReferenceTypeInfo t1(MergeTypes(ObjectType(), ObjectType()));
EXPECT_TRUE(t1.IsValid());
EXPECT_TRUE(t1.IsExact());
EXPECT_TRUE(t1.IsEqual(ObjectType()));
ReferenceTypeInfo t2(MergeTypes(StringType(), StringType()));
EXPECT_TRUE(t2.IsValid());
EXPECT_TRUE(t2.IsExact());
EXPECT_TRUE(t2.IsEqual(StringType()));
// Left is super class of right.
ReferenceTypeInfo t3(MergeTypes(ObjectType(), StringType()));
EXPECT_TRUE(t3.IsValid());
EXPECT_FALSE(t3.IsExact());
EXPECT_TRUE(t3.IsEqual(ObjectType(false)));
// Right is super class of left.
ReferenceTypeInfo t4(MergeTypes(StringType(), ObjectType()));
EXPECT_TRUE(t4.IsValid());
EXPECT_FALSE(t4.IsExact());
EXPECT_TRUE(t4.IsEqual(ObjectType(false)));
// Same types, but one or both are inexact.
ReferenceTypeInfo t5(MergeTypes(ObjectType(false), ObjectType()));
EXPECT_TRUE(t5.IsValid());
EXPECT_FALSE(t5.IsExact());
EXPECT_TRUE(t5.IsEqual(ObjectType(false)));
ReferenceTypeInfo t6(MergeTypes(ObjectType(), ObjectType(false)));
EXPECT_TRUE(t6.IsValid());
EXPECT_FALSE(t6.IsExact());
EXPECT_TRUE(t6.IsEqual(ObjectType(false)));
ReferenceTypeInfo t7(MergeTypes(ObjectType(false), ObjectType(false)));
EXPECT_TRUE(t7.IsValid());
EXPECT_FALSE(t7.IsExact());
EXPECT_TRUE(t7.IsEqual(ObjectType(false)));
}
// This generates a large graph with a ton of phis including loop-phis. It then
// calls the 'mutator' function with the list of all the phis and a CanBeNull
// instruction and then tries to propagate the types. mutator should reorder the
// list in some way and modify some phis in whatever way it wants. We verify
// everything worked by making sure every phi has valid type information.
template <typename Func>
void LoopReferenceTypePropagationTestGroup::RunVisitListTest(Func mutator) {
ScopedObjectAccess soa(Thread::Current());
VariableSizedHandleScope handles(soa.Self());
SetupPropagation(&handles);
// Make a well-connected graph with a lot of edges.
constexpr size_t kNumBlocks = 100;
constexpr size_t kTestMaxSuccessors = 3;
std::vector<std::string> mid_blocks;
for (auto i : Range(kNumBlocks)) {
std::ostringstream oss;
oss << "blk" << i;
mid_blocks.push_back(oss.str());
}
// Create the edge list.
std::vector<AdjacencyListGraph::Edge> edges;
for (auto cur : Range(kNumBlocks)) {
for (auto nxt : Range(cur + 1, std::min(cur + 1 + kTestMaxSuccessors, kNumBlocks))) {
edges.emplace_back(mid_blocks[cur], mid_blocks[nxt]);
}
}
// Add a loop.
edges.emplace_back("start", mid_blocks.front());
edges.emplace_back(mid_blocks.back(), mid_blocks.front());
edges.emplace_back(mid_blocks.front(), "exit");
AdjacencyListGraph alg(graph_, GetAllocator(), "start", "exit", edges);
std::unordered_map<HBasicBlock*, HInstruction*> single_value;
HInstruction* maybe_null_val = new (GetAllocator())
HParameterValue(graph_->GetDexFile(), dex::TypeIndex(1), 1, DataType::Type::kReference);
ASSERT_TRUE(maybe_null_val->CanBeNull());
// Setup the entry-block with the type to be propagated.
HInstruction* cls =
new (GetAllocator()) HLoadClass(graph_->GetCurrentMethod(),
dex::TypeIndex(10),
graph_->GetDexFile(),
graph_->GetHandleCache()->GetObjectClassHandle(),
false,
0,
false);
HInstruction* new_inst =
new (GetAllocator()) HNewInstance(cls,
0,
dex::TypeIndex(10),
graph_->GetDexFile(),
false,
QuickEntrypointEnum::kQuickAllocObjectInitialized);
single_value[alg.Get(mid_blocks.front())] = new_inst;
HBasicBlock* start = alg.Get("start");
start->AddInstruction(maybe_null_val);
start->AddInstruction(cls);
start->AddInstruction(new_inst);
new_inst->SetReferenceTypeInfo(ObjectType(true));
maybe_null_val->SetReferenceTypeInfo(ObjectType(true));
single_value[start] = new_inst;
// Setup all the other blocks with a single PHI
auto range = MakeIterationRange(mid_blocks);
auto succ_blocks = MakeTransformRange(range, [&](const auto& sv) { return alg.Get(sv); });
for (HBasicBlock* blk : succ_blocks) {
HPhi* phi_inst = new (GetAllocator()) HPhi(
GetAllocator(), kNoRegNumber, blk->GetPredecessors().size(), DataType::Type::kReference);
single_value[blk] = phi_inst;
}
for (HBasicBlock* blk : succ_blocks) {
HInstruction* my_val = single_value[blk];
for (const auto& [pred, index] : ZipCount(MakeIterationRange(blk->GetPredecessors()))) {
CHECK(single_value[pred] != nullptr) << pred->GetBlockId() << " " << alg.GetName(pred);
my_val->SetRawInputAt(index, single_value[pred]);
}
}
for (HBasicBlock* blk : succ_blocks) {
CHECK(single_value[blk]->IsPhi()) << blk->GetBlockId();
blk->AddPhi(single_value[blk]->AsPhi());
}
auto vals = MakeTransformRange(succ_blocks, [&](HBasicBlock* blk) {
DCHECK(single_value[blk]->IsPhi());
return single_value[blk];
});
std::vector<HInstruction*> ins(vals.begin(), vals.end());
CHECK(std::none_of(ins.begin(), ins.end(), [](auto x) { return x == nullptr; }));
mutator(ins, maybe_null_val);
propagation_->Visit(ArrayRef<HInstruction* const>(ins));
bool is_nullable = !maybe_null_val->GetUses().empty();
for (auto [blk, i] : single_value) {
if (blk == start) {
continue;
}
EXPECT_TRUE(i->GetReferenceTypeInfo().IsValid())
<< i->GetId() << " blk: " << alg.GetName(i->GetBlock());
if (is_nullable) {
EXPECT_TRUE(i->CanBeNull());
} else {
EXPECT_FALSE(i->CanBeNull());
}
}
}
// This generates a large graph with a ton of phis. It then calls the 'mutator'
// function with the list of all the phis and then tries to propagate the types.
// mutator should reorder the list in some way. We verify everything worked by
// making sure every phi has valid type information.
template <typename Func>
void NonLoopReferenceTypePropagationTestGroup::RunVisitListTest(Func mutator) {
ScopedObjectAccess soa(Thread::Current());
VariableSizedHandleScope handles(soa.Self());
SetupPropagation(&handles);
// Make a well-connected graph with a lot of edges.
constexpr size_t kNumBlocks = 5000;
constexpr size_t kTestMaxSuccessors = 2;
std::vector<std::string> mid_blocks;
for (auto i : Range(kNumBlocks)) {
std::ostringstream oss;
oss << "blk" << i;
mid_blocks.push_back(oss.str());
}
// Create the edge list.
std::vector<AdjacencyListGraph::Edge> edges;
for (auto cur : Range(kNumBlocks)) {
for (auto nxt : Range(cur + 1, std::min(cur + 1 + kTestMaxSuccessors, kNumBlocks))) {
edges.emplace_back(mid_blocks[cur], mid_blocks[nxt]);
}
}
AdjacencyListGraph alg(graph_, GetAllocator(), mid_blocks.front(), mid_blocks.back(), edges);
std::unordered_map<HBasicBlock*, HInstruction*> single_value;
// Setup the entry-block with the type to be propagated.
HInstruction* cls =
new (GetAllocator()) HLoadClass(graph_->GetCurrentMethod(),
dex::TypeIndex(10),
graph_->GetDexFile(),
graph_->GetHandleCache()->GetObjectClassHandle(),
false,
0,
false);
HInstruction* new_inst =
new (GetAllocator()) HNewInstance(cls,
0,
dex::TypeIndex(10),
graph_->GetDexFile(),
false,
QuickEntrypointEnum::kQuickAllocObjectInitialized);
single_value[alg.Get(mid_blocks.front())] = new_inst;
HBasicBlock* start = alg.Get(mid_blocks.front());
start->AddInstruction(cls);
start->AddInstruction(new_inst);
new_inst->SetReferenceTypeInfo(ObjectType(true));
// Setup all the other blocks with a single PHI
auto succ_blk_names = MakeIterationRange(mid_blocks.begin() + 1, mid_blocks.end());
auto succ_blocks =
MakeTransformRange(succ_blk_names, [&](const auto& sv) { return alg.Get(sv); });
for (HBasicBlock* blk : succ_blocks) {
HPhi* phi_inst = new (GetAllocator()) HPhi(
GetAllocator(), kNoRegNumber, blk->GetPredecessors().size(), DataType::Type::kReference);
single_value[blk] = phi_inst;
}
for (HBasicBlock* blk : succ_blocks) {
HInstruction* my_val = single_value[blk];
for (const auto& [pred, index] : ZipCount(MakeIterationRange(blk->GetPredecessors()))) {
my_val->SetRawInputAt(index, single_value[pred]);
}
blk->AddPhi(my_val->AsPhi());
}
auto vals = MakeTransformRange(succ_blocks, [&](HBasicBlock* blk) { return single_value[blk]; });
std::vector<HInstruction*> ins(vals.begin(), vals.end());
graph_->ClearReachabilityInformation();
graph_->ComputeReachabilityInformation();
mutator(ins);
propagation_->Visit(ArrayRef<HInstruction* const>(ins));
for (auto [blk, i] : single_value) {
if (blk == start) {
continue;
}
EXPECT_TRUE(i->GetReferenceTypeInfo().IsValid())
<< i->GetId() << " blk: " << alg.GetName(i->GetBlock());
}
}
template <typename Param>
void ParamReferenceTypePropagationTest<Param>::MutateList(std::vector<HInstruction*>& lst,
ShuffleOrder type) {
DCHECK(std::none_of(lst.begin(), lst.end(), [](auto* i) { return i == nullptr; }));
std::default_random_engine g(type != ShuffleOrder::kTrueRandom ? 42 : std::rand());
switch (type) {
case ShuffleOrder::kTopological: {
// Input is topologically sorted due to the way we create the phis.
break;
}
case ShuffleOrder::kReverseTopological: {
std::reverse(lst.begin(), lst.end());
break;
}
case ShuffleOrder::kAlmostTopological: {
std::swap(lst.front(), lst.back());
break;
}
case ShuffleOrder::kRandomSetSeed:
case ShuffleOrder::kTrueRandom: {
std::shuffle(lst.begin(), lst.end(), g);
break;
}
}
}
TEST_P(LoopReferenceTypePropagationTestGroup, RunVisitTest) {
LoopOptions lo(GetParam());
std::default_random_engine g(
lo.initial_null_state_ != InitialNullState::kTrueRandom ? 42 : std::rand());
std::uniform_int_distribution<bool> uid(false, true);
RunVisitListTest([&](std::vector<HInstruction*>& lst, HInstruction* null_input) {
auto pred_null = false;
auto next_null = [&]() {
switch (lo.initial_null_state_) {
case InitialNullState::kAllNonNull:
return false;
case InitialNullState::kAllNull:
return true;
case InitialNullState::kHalfNull:
pred_null = !pred_null;
return pred_null;
case InitialNullState::kRandomSetSeed:
case InitialNullState::kTrueRandom:
return uid(g);
}
};
HPhi* nulled_phi = lo.null_insertion_ >= 0 ? lst[lo.null_insertion_]->AsPhi() : nullptr;
if (nulled_phi != nullptr) {
nulled_phi->ReplaceInput(null_input, lo.null_phi_arg_);
}
MutateList(lst, lo.shuffle_);
std::for_each(lst.begin(), lst.end(), [&](HInstruction* ins) {
ins->AsPhi()->SetCanBeNull(next_null());
});
});
}
INSTANTIATE_TEST_SUITE_P(ReferenceTypePropagationTest,
LoopReferenceTypePropagationTestGroup,
testing::Combine(testing::Values(ShuffleOrder::kAlmostTopological,
ShuffleOrder::kReverseTopological,
ShuffleOrder::kTopological,
ShuffleOrder::kRandom),
testing::Values(-1, 10, 40),
testing::Values(0, 1),
testing::Values(InitialNullState::kAllNonNull,
InitialNullState::kAllNull,
InitialNullState::kHalfNull,
InitialNullState::kRandom)));
TEST_P(NonLoopReferenceTypePropagationTestGroup, RunVisitTest) {
RunVisitListTest([&](std::vector<HInstruction*>& lst) { MutateList(lst, GetParam()); });
}
INSTANTIATE_TEST_SUITE_P(ReferenceTypePropagationTest,
NonLoopReferenceTypePropagationTestGroup,
testing::Values(ShuffleOrder::kAlmostTopological,
ShuffleOrder::kReverseTopological,
ShuffleOrder::kTopological,
ShuffleOrder::kRandom));
} // namespace art