| /* |
| * 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 HIDDEN { |
| |
| 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 |