blob: 74c243b5b472a3f3049c51fc52666e3cf39218ed [file] [log] [blame]
/*
* Copyright (C) 2020 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 "execution_subgraph_test.h"
#include <array>
#include <sstream>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include "base/scoped_arena_allocator.h"
#include "base/stl_util.h"
#include "class_root.h"
#include "dex/dex_file_types.h"
#include "dex/method_reference.h"
#include "entrypoints/quick/quick_entrypoints_enum.h"
#include "execution_subgraph.h"
#include "gtest/gtest.h"
#include "handle.h"
#include "handle_scope.h"
#include "nodes.h"
#include "optimizing/data_type.h"
#include "optimizing_unit_test.h"
#include "scoped_thread_state_change.h"
namespace art {
using BlockSet = std::unordered_set<const HBasicBlock*>;
// Helper that checks validity directly.
bool ExecutionSubgraphTestHelper::CalculateValidity(HGraph* graph, const ExecutionSubgraph* esg) {
bool reached_end = false;
std::queue<const HBasicBlock*> worklist;
std::unordered_set<const HBasicBlock*> visited;
worklist.push(graph->GetEntryBlock());
while (!worklist.empty()) {
const HBasicBlock* cur = worklist.front();
worklist.pop();
if (visited.find(cur) != visited.end()) {
continue;
} else {
visited.insert(cur);
}
if (cur == graph->GetExitBlock()) {
reached_end = true;
continue;
}
bool has_succ = false;
for (const HBasicBlock* succ : cur->GetSuccessors()) {
DCHECK(succ != nullptr) << "Bad successors on block " << cur->GetBlockId();
if (!esg->ContainsBlock(succ)) {
continue;
}
has_succ = true;
worklist.push(succ);
}
if (!has_succ) {
// We aren't at the end and have nowhere to go so fail.
return false;
}
}
return reached_end;
}
class ExecutionSubgraphTest : public OptimizingUnitTest {
public:
ExecutionSubgraphTest() : graph_(CreateGraph()) {}
AdjacencyListGraph SetupFromAdjacencyList(const std::string_view entry_name,
const std::string_view exit_name,
const std::vector<AdjacencyListGraph::Edge>& adj) {
return AdjacencyListGraph(graph_, GetAllocator(), entry_name, exit_name, adj);
}
bool IsValidSubgraph(const ExecutionSubgraph* esg) {
return ExecutionSubgraphTestHelper::CalculateValidity(graph_, esg);
}
bool IsValidSubgraph(const ExecutionSubgraph& esg) {
return ExecutionSubgraphTestHelper::CalculateValidity(graph_, &esg);
}
HGraph* graph_;
};
// Some comparators used by these tests to avoid having to deal with various set types.
template <typename BLKS, typename = std::enable_if_t<!std::is_same_v<BlockSet, BLKS>>>
bool operator==(const BlockSet& bs, const BLKS& sas) {
std::unordered_set<const HBasicBlock*> us(sas.begin(), sas.end());
return bs == us;
}
template <typename BLKS, typename = std::enable_if_t<!std::is_same_v<BlockSet, BLKS>>>
bool operator==(const BLKS& sas, const BlockSet& bs) {
return bs == sas;
}
template <typename BLKS, typename = std::enable_if_t<!std::is_same_v<BlockSet, BLKS>>>
bool operator!=(const BlockSet& bs, const BLKS& sas) {
return !(bs == sas);
}
template <typename BLKS, typename = std::enable_if_t<!std::is_same_v<BlockSet, BLKS>>>
bool operator!=(const BLKS& sas, const BlockSet& bs) {
return !(bs == sas);
}
// +-------+ +-------+
// | right | <-- | entry |
// +-------+ +-------+
// | |
// | |
// | v
// | + - - - - - +
// | ' removed '
// | ' '
// | ' +-------+ '
// | ' | left | '
// | ' +-------+ '
// | ' '
// | + - - - - - +
// | |
// | |
// | v
// | +-------+
// +---------> | exit |
// +-------+
TEST_F(ExecutionSubgraphTest, Basic) {
AdjacencyListGraph blks(SetupFromAdjacencyList(
"entry",
"exit",
{ { "entry", "left" }, { "entry", "right" }, { "left", "exit" }, { "right", "exit" } }));
ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_));
ExecutionSubgraph esg(graph_, GetScopedAllocator());
esg.RemoveBlock(blks.Get("left"));
esg.Finalize();
ASSERT_TRUE(esg.IsValid());
ASSERT_TRUE(IsValidSubgraph(esg));
std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(),
esg.ReachableBlocks().end());
ASSERT_EQ(contents.size(), 3u);
ASSERT_TRUE(contents.find(blks.Get("left")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("right")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("entry")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("exit")) != contents.end());
esg.RemoveBlock(blks.Get("right"));
esg.Finalize();
std::unordered_set<const HBasicBlock*> contents_2(esg.ReachableBlocks().begin(),
esg.ReachableBlocks().end());
ASSERT_EQ(contents_2.size(), 0u);
}
// +-------+ +-------+
// | right | <-- | entry |
// +-------+ +-------+
// | |
// | |
// | v
// | + - - - - - - - - - - - - - - - - - - - -+
// | ' indirectly_removed '
// | ' '
// | ' +-------+ +-----+ '
// | ' | l1 | -------------------> | l1r | '
// | ' +-------+ +-----+ '
// | ' | | '
// | ' | | '
// | ' v | '
// | ' +-------+ | '
// | ' | l1l | | '
// | ' +-------+ | '
// | ' | | '
// | ' | | '
// | ' | | '
// + - - - - - - - -+ | +- - - | | '
// ' ' | +- v | '
// ' +-----+ | +----------------+ | '
// ' | l2r | <---------+-------------- | l2 (removed) | <-------------+ '
// ' +-----+ | +----------------+ '
// ' | ' | +- | '
// ' | - - -+ | +- - - | - - - - - - - - - - - - - -+
// ' | ' | ' | '
// ' | ' | ' | '
// ' | ' | ' v '
// ' | ' | ' +-------+ '
// ' | ' | ' | l2l | '
// ' | ' | ' +-------+ '
// ' | ' | ' | '
// ' | ' | ' | '
// ' | ' | ' | '
// ' | - - -+ | +- - - | '
// ' | ' | +- v '
// ' | | +-------+ '
// ' +---------------+-------------> | l3 | '
// ' | +-------+ '
// ' ' | +- '
// + - - - - - - - -+ | +- - - - - - - - - +
// | |
// | |
// | v
// | +-------+
// +-----------> | exit |
// +-------+
TEST_F(ExecutionSubgraphTest, Propagation) {
AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
"exit",
{ { "entry", "l1" },
{ "l1", "l1l" },
{ "l1", "l1r" },
{ "l1l", "l2" },
{ "l1r", "l2" },
{ "l2", "l2l" },
{ "l2", "l2r" },
{ "l2l", "l3" },
{ "l2r", "l3" },
{ "l3", "exit" },
{ "entry", "right" },
{ "right", "exit" } }));
ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_));
ExecutionSubgraph esg(graph_, GetScopedAllocator());
esg.RemoveBlock(blks.Get("l2"));
esg.Finalize();
ASSERT_TRUE(esg.IsValid());
ASSERT_TRUE(IsValidSubgraph(esg));
std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(),
esg.ReachableBlocks().end());
// ASSERT_EQ(contents.size(), 3u);
// Not present, no path through.
ASSERT_TRUE(contents.find(blks.Get("l1")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l2")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l3")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l1l")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l1r")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l2l")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l2r")) == contents.end());
// present, path through.
ASSERT_TRUE(contents.find(blks.Get("right")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("entry")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("exit")) != contents.end());
}
// +------------------------------------+
// | |
// | +-------+ +-------+ |
// | | right | <-- | entry | |
// | +-------+ +-------+ |
// | | | |
// | | | |
// | | v |
// | | +-------+ +--------+
// +----+---------> | l1 | --> | l1loop |
// | +-------+ +--------+
// | |
// | |
// | v
// | +- - - - - -+
// | ' removed '
// | ' '
// | ' +-------+ '
// | ' | l2 | '
// | ' +-------+ '
// | ' '
// | +- - - - - -+
// | |
// | |
// | v
// | +-------+
// +---------> | exit |
// +-------+
TEST_F(ExecutionSubgraphTest, PropagationLoop) {
AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
"exit",
{ { "entry", "l1" },
{ "l1", "l2" },
{ "l1", "l1loop" },
{ "l1loop", "l1" },
{ "l2", "exit" },
{ "entry", "right" },
{ "right", "exit" } }));
ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_));
ExecutionSubgraph esg(graph_, GetScopedAllocator());
esg.RemoveBlock(blks.Get("l2"));
esg.Finalize();
ASSERT_TRUE(esg.IsValid());
ASSERT_TRUE(IsValidSubgraph(esg));
std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(),
esg.ReachableBlocks().end());
ASSERT_EQ(contents.size(), 5u);
// Not present, no path through.
ASSERT_TRUE(contents.find(blks.Get("l2")) == contents.end());
// present, path through.
// Since the loop can diverge we should leave it in the execution subgraph.
ASSERT_TRUE(contents.find(blks.Get("l1")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("l1loop")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("right")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("entry")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("exit")) != contents.end());
}
// +--------------------------------+
// | |
// | +-------+ +-------+ |
// | | right | <-- | entry | |
// | +-------+ +-------+ |
// | | | |
// | | | |
// | | v |
// | | +-------+ +--------+
// +----+---------> | l1 | --> | l1loop |
// | +-------+ +--------+
// | |
// | |
// | v
// | +-------+
// | | l2 |
// | +-------+
// | |
// | |
// | v
// | +-------+
// +---------> | exit |
// +-------+
TEST_F(ExecutionSubgraphTest, PropagationLoop2) {
AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
"exit",
{ { "entry", "l1" },
{ "l1", "l2" },
{ "l1", "l1loop" },
{ "l1loop", "l1" },
{ "l2", "exit" },
{ "entry", "right" },
{ "right", "exit" } }));
ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_));
ExecutionSubgraph esg(graph_, GetScopedAllocator());
esg.RemoveBlock(blks.Get("l1"));
esg.Finalize();
ASSERT_TRUE(esg.IsValid());
ASSERT_TRUE(IsValidSubgraph(esg));
std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(),
esg.ReachableBlocks().end());
ASSERT_EQ(contents.size(), 3u);
// Not present, no path through.
ASSERT_TRUE(contents.find(blks.Get("l1")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l1loop")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l2")) == contents.end());
// present, path through.
ASSERT_TRUE(contents.find(blks.Get("right")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("entry")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("exit")) != contents.end());
}
// +--------------------------------+
// | |
// | +-------+ +-------+ |
// | | right | <-- | entry | |
// | +-------+ +-------+ |
// | | | |
// | | | |
// | | v |
// | | +-------+ +--------+
// +----+---------> | l1 | --> | l1loop |
// | +-------+ +--------+
// | |
// | |
// | v
// | +-------+
// | | l2 |
// | +-------+
// | |
// | |
// | v
// | +-------+
// +---------> | exit |
// +-------+
TEST_F(ExecutionSubgraphTest, PropagationLoop3) {
AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
"exit",
{ { "entry", "l1" },
{ "l1", "l2" },
{ "l1", "l1loop" },
{ "l1loop", "l1" },
{ "l2", "exit" },
{ "entry", "right" },
{ "right", "exit" } }));
ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_));
ExecutionSubgraph esg(graph_, GetScopedAllocator());
esg.RemoveBlock(blks.Get("l1loop"));
esg.Finalize();
ASSERT_TRUE(esg.IsValid());
ASSERT_TRUE(IsValidSubgraph(esg));
std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(),
esg.ReachableBlocks().end());
ASSERT_EQ(contents.size(), 3u);
// Not present, no path through. If we got to l1 loop then we must merge back
// with l1 and l2 so they're bad too.
ASSERT_TRUE(contents.find(blks.Get("l1loop")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l1")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l2")) == contents.end());
// present, path through.
ASSERT_TRUE(contents.find(blks.Get("right")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("entry")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("exit")) != contents.end());
}
// ┌───────┐ ┌──────────────┐
// │ right │ ◀── │ entry │
// └───────┘ └──────────────┘
// │ │
// │ │
// ▼ ▼
// ┌────┐ ┌───────┐ ┌──────────────┐
// │ l2 │ ──▶ │ exit │ ┌─ │ l1 │ ◀┐
// └────┘ └───────┘ │ └──────────────┘ │
// ▲ │ │ │
// └───────────────────┘ │ │
// ▼ │
// ┌──────────────┐ │ ┌──────────────┐
// ┌─ │ l1loop │ │ │ l1loop_right │ ◀┐
// │ └──────────────┘ │ └──────────────┘ │
// │ │ │ │ │
// │ │ │ │ │
// │ ▼ │ │ │
// │ ┌−−−−−−−−−−−−−−−−−−┐ │ │ │
// │ ╎ removed ╎ │ │ │
// │ ╎ ╎ │ │ │
// │ ╎ ┌──────────────┐ ╎ │ │ │
// │ ╎ │ l1loop_left │ ╎ │ │ │
// │ ╎ └──────────────┘ ╎ │ │ │
// │ ╎ ╎ │ │ │
// │ └−−−−−−−−−−−−−−−−−−┘ │ │ │
// │ │ │ │ │
// │ │ │ │ │
// │ ▼ │ │ │
// │ ┌──────────────┐ │ │ │
// │ │ l1loop_merge │ ─┘ │ │
// │ └──────────────┘ │ │
// │ ▲ │ │
// │ └──────────────────────┘ │
// │ │
// │ │
// └─────────────────────────────────────────────┘
TEST_F(ExecutionSubgraphTest, PropagationLoop4) {
AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
"exit",
{{"entry", "l1"},
{"l1", "l2"},
{"l1", "l1loop"},
{"l1loop", "l1loop_left"},
{"l1loop", "l1loop_right"},
{"l1loop_left", "l1loop_merge"},
{"l1loop_right", "l1loop_merge"},
{"l1loop_merge", "l1"},
{"l2", "exit"},
{"entry", "right"},
{"right", "exit"}}));
ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_));
ExecutionSubgraph esg(graph_, GetScopedAllocator());
esg.RemoveBlock(blks.Get("l1loop_left"));
esg.Finalize();
ASSERT_TRUE(esg.IsValid());
ASSERT_TRUE(IsValidSubgraph(esg));
std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(),
esg.ReachableBlocks().end());
ASSERT_EQ(contents.size(), 3u);
// Not present, no path through. If we got to l1 loop then we must merge back
// with l1 and l2 so they're bad too.
ASSERT_TRUE(contents.find(blks.Get("l1loop")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l1")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l1loop_left")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l1loop_right")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l1loop_merge")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l2")) == contents.end());
// present, path through.
ASSERT_TRUE(contents.find(blks.Get("right")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("entry")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("exit")) != contents.end());
}
// +------------------------------------------------------+
// | |
// | +--------------+ +-------------+ |
// | | right | <-- | entry | |
// | +--------------+ +-------------+ |
// | | | |
// | | | |
// | v v |
// | +--------------+ +--------------------+ +----+
// +> | exit | +> | l1 | --> | l2 |
// +--------------+ | +--------------------+ +----+
// | | ^
// +---------------+ | |
// | v |
// +--------------+ +-------------+ |
// | l1loop_right | <-- | l1loop | |
// +--------------+ +-------------+ |
// | |
// | |
// v |
// + - - - - - - - - + |
// ' removed ' |
// ' ' |
// ' +-------------+ ' |
// ' | l1loop_left | ' -+
// ' +-------------+ '
// ' '
// + - - - - - - - - +
TEST_F(ExecutionSubgraphTest, PropagationLoop5) {
AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
"exit",
{{"entry", "l1"},
{"l1", "l2"},
{"l1", "l1loop"},
{"l1loop", "l1loop_left"},
{"l1loop", "l1loop_right"},
{"l1loop_left", "l1"},
{"l1loop_right", "l1"},
{"l2", "exit"},
{"entry", "right"},
{"right", "exit"}}));
ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_));
ExecutionSubgraph esg(graph_, GetScopedAllocator());
esg.RemoveBlock(blks.Get("l1loop_left"));
esg.Finalize();
ASSERT_TRUE(esg.IsValid());
ASSERT_TRUE(IsValidSubgraph(esg));
std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(),
esg.ReachableBlocks().end());
ASSERT_EQ(contents.size(), 3u);
// Not present, no path through. If we got to l1 loop then we must merge back
// with l1 and l2 so they're bad too.
ASSERT_TRUE(contents.find(blks.Get("l1loop")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l1")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l1loop_left")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l1loop_right")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("l2")) == contents.end());
// present, path through.
ASSERT_TRUE(contents.find(blks.Get("right")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("entry")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("exit")) != contents.end());
}
TEST_F(ExecutionSubgraphTest, Invalid) {
AdjacencyListGraph blks(SetupFromAdjacencyList(
"entry",
"exit",
{ { "entry", "left" }, { "entry", "right" }, { "left", "exit" }, { "right", "exit" } }));
ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_));
ExecutionSubgraph esg(graph_, GetScopedAllocator());
esg.RemoveBlock(blks.Get("left"));
esg.RemoveBlock(blks.Get("right"));
esg.Finalize();
ASSERT_FALSE(esg.IsValid());
ASSERT_FALSE(IsValidSubgraph(esg));
std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(),
esg.ReachableBlocks().end());
ASSERT_EQ(contents.size(), 0u);
}
// Sibling branches are disconnected.
TEST_F(ExecutionSubgraphTest, Exclusions) {
AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
"exit",
{ { "entry", "a" },
{ "entry", "b" },
{ "entry", "c" },
{ "a", "exit" },
{ "b", "exit" },
{ "c", "exit" } }));
ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_));
ExecutionSubgraph esg(graph_, GetScopedAllocator());
esg.RemoveBlock(blks.Get("a"));
esg.RemoveBlock(blks.Get("c"));
esg.Finalize();
ASSERT_TRUE(esg.IsValid());
ASSERT_TRUE(IsValidSubgraph(esg));
std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(),
esg.ReachableBlocks().end());
ASSERT_EQ(contents.size(), 3u);
// Not present, no path through.
ASSERT_TRUE(contents.find(blks.Get("a")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("c")) == contents.end());
// present, path through.
ASSERT_TRUE(contents.find(blks.Get("entry")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("exit")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("b")) != contents.end());
ArrayRef<const ExecutionSubgraph::ExcludedCohort> exclusions(esg.GetExcludedCohorts());
ASSERT_EQ(exclusions.size(), 2u);
std::unordered_set<const HBasicBlock*> exclude_a({ blks.Get("a") });
std::unordered_set<const HBasicBlock*> exclude_c({ blks.Get("c") });
ASSERT_TRUE(std::find_if(exclusions.cbegin(),
exclusions.cend(),
[&](const ExecutionSubgraph::ExcludedCohort& it) {
return it.Blocks() == exclude_a;
}) != exclusions.cend());
ASSERT_TRUE(std::find_if(exclusions.cbegin(),
exclusions.cend(),
[&](const ExecutionSubgraph::ExcludedCohort& it) {
return it.Blocks() == exclude_c;
}) != exclusions.cend());
}
// Sibling branches are disconnected.
// +- - - - - - - - - - - - - - - - - - - - - - +
// ' remove_c '
// ' '
// ' +-----------+ '
// ' | c_begin_2 | -------------------------+ '
// ' +-----------+ | '
// ' | '
// +- - - - - - - - - - - - - - - - - - | '
// ^ ' | '
// | ' | '
// | ' | '
// + - - - - - -+ ' | '
// ' remove_a ' ' | '
// ' ' ' | '
// ' +--------+ ' +-----------+ +---+' | '
// ' | **a** | ' <-- | entry | --> | b |' | '
// ' +--------+ ' +-----------+ +---+' | '
// ' ' ' | '
// + - - - - - -+ ' | '
// | | | ' | '
// | | | ' | '
// | v | ' | '
// | +- - - - - - - -+ | ' | '
// | ' ' | ' | '
// | ' +-----------+ ' | ' | '
// | ' | c_begin_1 | ' | ' | '
// | ' +-----------+ ' | ' | '
// | ' | ' | ' | '
// | ' | ' | ' | '
// | ' | ' | ' | '
// + - - - - - - - - -+ | + - - - | - - - - - - - + | ' | '
// ' ' | + v ' | + | '
// ' +---------+ | +-----------+ | | '
// ' | c_end_2 | <-------+--------------- | **c_mid** | <-----------------+------+ '
// ' +---------+ | +-----------+ | '
// ' ' | + | ' | + '
// + - - - - - - - - -+ | + - - - | - - - - - - - + | + - - - +
// | | ' | ' |
// | | ' | ' |
// | | ' v ' |
// | | ' +-----------+ ' |
// | | ' | c_end_1 | ' |
// | | ' +-----------+ ' |
// | | ' ' |
// | | +- - - - - - - -+ |
// | | | |
// | | | |
// | | v v
// | | +---------------------------------+
// | +------------> | exit |
// | +---------------------------------+
// | ^
// +------------------------------------+
TEST_F(ExecutionSubgraphTest, ExclusionExtended) {
AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
"exit",
{ { "entry", "a" },
{ "entry", "b" },
{ "entry", "c_begin_1" },
{ "entry", "c_begin_2" },
{ "c_begin_1", "c_mid" },
{ "c_begin_2", "c_mid" },
{ "c_mid", "c_end_1" },
{ "c_mid", "c_end_2" },
{ "a", "exit" },
{ "b", "exit" },
{ "c_end_1", "exit" },
{ "c_end_2", "exit" } }));
ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_));
ExecutionSubgraph esg(graph_, GetScopedAllocator());
esg.RemoveBlock(blks.Get("a"));
esg.RemoveBlock(blks.Get("c_mid"));
esg.Finalize();
ASSERT_TRUE(esg.IsValid());
ASSERT_TRUE(IsValidSubgraph(esg));
std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(),
esg.ReachableBlocks().end());
ASSERT_EQ(contents.size(), 3u);
// Not present, no path through.
ASSERT_TRUE(contents.find(blks.Get("a")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("c_begin_1")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("c_begin_2")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("c_mid")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("c_end_1")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("c_end_2")) == contents.end());
// present, path through.
ASSERT_TRUE(contents.find(blks.Get("entry")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("exit")) != contents.end());
ASSERT_TRUE(contents.find(blks.Get("b")) != contents.end());
ArrayRef<const ExecutionSubgraph::ExcludedCohort> exclusions(esg.GetExcludedCohorts());
ASSERT_EQ(exclusions.size(), 2u);
BlockSet exclude_a({ blks.Get("a") });
BlockSet exclude_c({ blks.Get("c_begin_1"),
blks.Get("c_begin_2"),
blks.Get("c_mid"),
blks.Get("c_end_1"),
blks.Get("c_end_2") });
ASSERT_TRUE(std::find_if(exclusions.cbegin(),
exclusions.cend(),
[&](const ExecutionSubgraph::ExcludedCohort& it) {
return it.Blocks() == exclude_a;
}) != exclusions.cend());
ASSERT_TRUE(
std::find_if(
exclusions.cbegin(), exclusions.cend(), [&](const ExecutionSubgraph::ExcludedCohort& it) {
return it.Blocks() == exclude_c &&
BlockSet({ blks.Get("c_begin_1"), blks.Get("c_begin_2") }) == it.EntryBlocks() &&
BlockSet({ blks.Get("c_end_1"), blks.Get("c_end_2") }) == it.ExitBlocks();
}) != exclusions.cend());
}
// ┌───────┐ ┌────────────┐
// ┌─ │ right │ ◀── │ entry │
// │ └───────┘ └────────────┘
// │ │
// │ │
// │ ▼
// │ ┌────────────┐
// │ │ esc_top │
// │ └────────────┘
// │ │
// │ │
// │ ▼
// │ ┌────────────┐
// └──────────────▶ │ middle │ ─┐
// └────────────┘ │
// │ │
// │ │
// ▼ │
// ┌────────────┐ │
// │ esc_bottom │ │
// └────────────┘ │
// │ │
// │ │
// ▼ │
// ┌────────────┐ │
// │ exit │ ◀┘
// └────────────┘
TEST_F(ExecutionSubgraphTest, InAndOutEscape) {
AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
"exit",
{ { "entry", "esc_top" },
{ "entry", "right" },
{ "esc_top", "middle" },
{ "right", "middle" },
{ "middle", "exit" },
{ "middle", "esc_bottom" },
{ "esc_bottom", "exit" } }));
ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_));
ExecutionSubgraph esg(graph_, GetScopedAllocator());
esg.RemoveBlock(blks.Get("esc_top"));
esg.RemoveBlock(blks.Get("esc_bottom"));
esg.Finalize();
std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(),
esg.ReachableBlocks().end());
ASSERT_EQ(contents.size(), 0u);
ASSERT_FALSE(esg.IsValid());
ASSERT_FALSE(IsValidSubgraph(esg));
ASSERT_EQ(contents.size(), 0u);
}
// Test with max number of successors and no removals.
TEST_F(ExecutionSubgraphTest, BigNodes) {
std::vector<std::string> mid_blocks;
for (auto i : Range(ExecutionSubgraph::kMaxFilterableSuccessors)) {
std::ostringstream oss;
oss << "blk" << i;
mid_blocks.push_back(oss.str().c_str());
}
ASSERT_EQ(mid_blocks.size(), ExecutionSubgraph::kMaxFilterableSuccessors);
std::vector<AdjacencyListGraph::Edge> edges;
for (const auto& mid : mid_blocks) {
edges.emplace_back("entry", mid);
edges.emplace_back(mid, "exit");
}
AdjacencyListGraph blks(SetupFromAdjacencyList("entry", "exit", edges));
ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_));
ExecutionSubgraph esg(graph_, GetScopedAllocator());
esg.Finalize();
ASSERT_TRUE(esg.IsValid());
ASSERT_TRUE(IsValidSubgraph(esg));
std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(),
esg.ReachableBlocks().end());
for (const auto& mid : mid_blocks) {
EXPECT_TRUE(contents.find(blks.Get(mid)) != contents.end()) << mid;
}
// + 2 for entry and exit nodes.
ASSERT_EQ(contents.size(), ExecutionSubgraph::kMaxFilterableSuccessors + 2);
}
// Test with max number of successors and some removals.
TEST_F(ExecutionSubgraphTest, BigNodesMissing) {
std::vector<std::string> mid_blocks;
for (auto i : Range(ExecutionSubgraph::kMaxFilterableSuccessors)) {
std::ostringstream oss;
oss << "blk" << i;
mid_blocks.push_back(oss.str());
}
std::vector<AdjacencyListGraph::Edge> edges;
for (const auto& mid : mid_blocks) {
edges.emplace_back("entry", mid);
edges.emplace_back(mid, "exit");
}
AdjacencyListGraph blks(SetupFromAdjacencyList("entry", "exit", edges));
ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_));
ExecutionSubgraph esg(graph_, GetScopedAllocator());
esg.RemoveBlock(blks.Get("blk2"));
esg.RemoveBlock(blks.Get("blk4"));
esg.Finalize();
ASSERT_TRUE(esg.IsValid());
ASSERT_TRUE(IsValidSubgraph(esg));
std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(),
esg.ReachableBlocks().end());
ASSERT_EQ(contents.size(), ExecutionSubgraph::kMaxFilterableSuccessors + 2 - 2);
// Not present, no path through.
ASSERT_TRUE(contents.find(blks.Get("blk2")) == contents.end());
ASSERT_TRUE(contents.find(blks.Get("blk4")) == contents.end());
}
// Test with max number of successors and all successors removed.
TEST_F(ExecutionSubgraphTest, BigNodesNoPath) {
std::vector<std::string> mid_blocks;
for (auto i : Range(ExecutionSubgraph::kMaxFilterableSuccessors)) {
std::ostringstream oss;
oss << "blk" << i;
mid_blocks.push_back(oss.str());
}
std::vector<AdjacencyListGraph::Edge> edges;
for (const auto& mid : mid_blocks) {
edges.emplace_back("entry", mid);
edges.emplace_back(mid, "exit");
}
AdjacencyListGraph blks(SetupFromAdjacencyList("entry", "exit", edges));
ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_));
ExecutionSubgraph esg(graph_, GetScopedAllocator());
for (const auto& mid : mid_blocks) {
esg.RemoveBlock(blks.Get(mid));
}
esg.Finalize();
ASSERT_FALSE(esg.IsValid());
ASSERT_FALSE(IsValidSubgraph(esg));
}
// Test with max number of successors
TEST_F(ExecutionSubgraphTest, CanAnalyseBig) {
// Make an absurdly huge and well connected graph. This should be pretty worst-case scenario.
constexpr size_t kNumBlocks = ExecutionSubgraph::kMaxFilterableSuccessors + 1000;
std::vector<std::string> mid_blocks;
for (auto i : Range(kNumBlocks)) {
std::ostringstream oss;
oss << "blk" << i;
mid_blocks.push_back(oss.str());
}
std::vector<AdjacencyListGraph::Edge> edges;
for (auto cur : Range(kNumBlocks)) {
for (auto nxt :
Range(cur + 1,
std::min(cur + ExecutionSubgraph::kMaxFilterableSuccessors + 1, kNumBlocks))) {
edges.emplace_back(mid_blocks[cur], mid_blocks[nxt]);
}
}
AdjacencyListGraph blks(SetupFromAdjacencyList(mid_blocks.front(), mid_blocks.back(), edges));
ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_));
ExecutionSubgraph esg(graph_, GetScopedAllocator());
esg.Finalize();
ASSERT_TRUE(esg.IsValid());
ASSERT_TRUE(IsValidSubgraph(esg));
std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(),
esg.ReachableBlocks().end());
ASSERT_EQ(contents.size(), kNumBlocks);
}
// Test with many successors
TEST_F(ExecutionSubgraphTest, CanAnalyseBig2) {
// Make an absurdly huge and well connected graph. This should be pretty worst-case scenario.
constexpr size_t kNumBlocks = ExecutionSubgraph::kMaxFilterableSuccessors + 1000;
constexpr size_t kTestMaxSuccessors = ExecutionSubgraph::kMaxFilterableSuccessors - 1;
std::vector<std::string> mid_blocks;
for (auto i : Range(kNumBlocks)) {
std::ostringstream oss;
oss << "blk" << i;
mid_blocks.push_back(oss.str());
}
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]);
}
}
edges.emplace_back(mid_blocks.front(), mid_blocks.back());
AdjacencyListGraph blks(SetupFromAdjacencyList(mid_blocks.front(), mid_blocks.back(), edges));
ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_));
ExecutionSubgraph esg(graph_, GetScopedAllocator());
constexpr size_t kToRemoveIdx = kNumBlocks / 2;
HBasicBlock* remove_implicit = blks.Get(mid_blocks[kToRemoveIdx]);
for (HBasicBlock* pred : remove_implicit->GetPredecessors()) {
esg.RemoveBlock(pred);
}
esg.Finalize();
EXPECT_TRUE(esg.IsValid());
EXPECT_TRUE(IsValidSubgraph(esg));
std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(),
esg.ReachableBlocks().end());
// Only entry and exit. The middle ones should eliminate everything else.
EXPECT_EQ(contents.size(), 2u);
EXPECT_TRUE(contents.find(remove_implicit) == contents.end());
EXPECT_TRUE(contents.find(blks.Get(mid_blocks.front())) != contents.end());
EXPECT_TRUE(contents.find(blks.Get(mid_blocks.back())) != contents.end());
}
// Test with too many successors
TEST_F(ExecutionSubgraphTest, CanNotAnalyseBig) {
std::vector<std::string> mid_blocks;
for (auto i : Range(ExecutionSubgraph::kMaxFilterableSuccessors + 4)) {
std::ostringstream oss;
oss << "blk" << i;
mid_blocks.push_back(oss.str());
}
std::vector<AdjacencyListGraph::Edge> edges;
for (const auto& mid : mid_blocks) {
edges.emplace_back("entry", mid);
edges.emplace_back(mid, "exit");
}
AdjacencyListGraph blks(SetupFromAdjacencyList("entry", "exit", edges));
ASSERT_FALSE(ExecutionSubgraph::CanAnalyse(graph_));
}
} // namespace art