| Alex Light | 86fe9b8 | 2020-11-16 16:54:01 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright (C) 2020 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #include "execution_subgraph_test.h" |
| 18 | |
| 19 | #include <array> |
| 20 | #include <sstream> |
| 21 | #include <string_view> |
| 22 | #include <unordered_map> |
| 23 | #include <unordered_set> |
| 24 | |
| 25 | #include "base/scoped_arena_allocator.h" |
| 26 | #include "base/stl_util.h" |
| 27 | #include "class_root.h" |
| 28 | #include "dex/dex_file_types.h" |
| 29 | #include "dex/method_reference.h" |
| 30 | #include "entrypoints/quick/quick_entrypoints_enum.h" |
| 31 | #include "execution_subgraph.h" |
| 32 | #include "gtest/gtest.h" |
| 33 | #include "handle.h" |
| 34 | #include "handle_scope.h" |
| 35 | #include "nodes.h" |
| 36 | #include "optimizing/data_type.h" |
| 37 | #include "optimizing_unit_test.h" |
| 38 | #include "scoped_thread_state_change.h" |
| 39 | |
| 40 | namespace art { |
| 41 | |
| 42 | using BlockSet = std::unordered_set<const HBasicBlock*>; |
| 43 | |
| 44 | // Helper that checks validity directly. |
| 45 | bool ExecutionSubgraphTestHelper::CalculateValidity(HGraph* graph, const ExecutionSubgraph* esg) { |
| 46 | bool reached_end = false; |
| 47 | std::queue<const HBasicBlock*> worklist; |
| 48 | std::unordered_set<const HBasicBlock*> visited; |
| 49 | worklist.push(graph->GetEntryBlock()); |
| 50 | while (!worklist.empty()) { |
| 51 | const HBasicBlock* cur = worklist.front(); |
| 52 | worklist.pop(); |
| 53 | if (visited.find(cur) != visited.end()) { |
| 54 | continue; |
| 55 | } else { |
| 56 | visited.insert(cur); |
| 57 | } |
| 58 | if (cur == graph->GetExitBlock()) { |
| 59 | reached_end = true; |
| 60 | continue; |
| 61 | } |
| 62 | bool has_succ = false; |
| 63 | for (const HBasicBlock* succ : cur->GetSuccessors()) { |
| 64 | DCHECK(succ != nullptr) << "Bad successors on block " << cur->GetBlockId(); |
| 65 | if (!esg->ContainsBlock(succ)) { |
| 66 | continue; |
| 67 | } |
| 68 | has_succ = true; |
| 69 | worklist.push(succ); |
| 70 | } |
| 71 | if (!has_succ) { |
| 72 | // We aren't at the end and have nowhere to go so fail. |
| 73 | return false; |
| 74 | } |
| 75 | } |
| 76 | return reached_end; |
| 77 | } |
| 78 | |
| 79 | class ExecutionSubgraphTest : public OptimizingUnitTest { |
| 80 | public: |
| 81 | ExecutionSubgraphTest() : graph_(CreateGraph()) {} |
| 82 | |
| 83 | AdjacencyListGraph SetupFromAdjacencyList(const std::string_view entry_name, |
| 84 | const std::string_view exit_name, |
| 85 | const std::vector<AdjacencyListGraph::Edge>& adj) { |
| 86 | return AdjacencyListGraph(graph_, GetAllocator(), entry_name, exit_name, adj); |
| 87 | } |
| 88 | |
| 89 | bool IsValidSubgraph(const ExecutionSubgraph* esg) { |
| 90 | return ExecutionSubgraphTestHelper::CalculateValidity(graph_, esg); |
| 91 | } |
| 92 | |
| 93 | bool IsValidSubgraph(const ExecutionSubgraph& esg) { |
| 94 | return ExecutionSubgraphTestHelper::CalculateValidity(graph_, &esg); |
| 95 | } |
| 96 | |
| 97 | HGraph* graph_; |
| 98 | }; |
| 99 | |
| 100 | // Some comparators used by these tests to avoid having to deal with various set types. |
| 101 | template <typename BLKS, typename = std::enable_if_t<!std::is_same_v<BlockSet, BLKS>>> |
| 102 | bool operator==(const BlockSet& bs, const BLKS& sas) { |
| 103 | std::unordered_set<const HBasicBlock*> us(sas.begin(), sas.end()); |
| 104 | return bs == us; |
| 105 | } |
| 106 | template <typename BLKS, typename = std::enable_if_t<!std::is_same_v<BlockSet, BLKS>>> |
| 107 | bool operator==(const BLKS& sas, const BlockSet& bs) { |
| 108 | return bs == sas; |
| 109 | } |
| 110 | template <typename BLKS, typename = std::enable_if_t<!std::is_same_v<BlockSet, BLKS>>> |
| 111 | bool operator!=(const BlockSet& bs, const BLKS& sas) { |
| 112 | return !(bs == sas); |
| 113 | } |
| 114 | template <typename BLKS, typename = std::enable_if_t<!std::is_same_v<BlockSet, BLKS>>> |
| 115 | bool operator!=(const BLKS& sas, const BlockSet& bs) { |
| 116 | return !(bs == sas); |
| 117 | } |
| 118 | |
| 119 | // +-------+ +-------+ |
| 120 | // | right | <-- | entry | |
| 121 | // +-------+ +-------+ |
| 122 | // | | |
| 123 | // | | |
| 124 | // | v |
| 125 | // | + - - - - - + |
| 126 | // | ' removed ' |
| 127 | // | ' ' |
| 128 | // | ' +-------+ ' |
| 129 | // | ' | left | ' |
| 130 | // | ' +-------+ ' |
| 131 | // | ' ' |
| 132 | // | + - - - - - + |
| 133 | // | | |
| 134 | // | | |
| 135 | // | v |
| 136 | // | +-------+ |
| 137 | // +---------> | exit | |
| 138 | // +-------+ |
| 139 | TEST_F(ExecutionSubgraphTest, Basic) { |
| 140 | AdjacencyListGraph blks(SetupFromAdjacencyList( |
| 141 | "entry", |
| 142 | "exit", |
| 143 | { { "entry", "left" }, { "entry", "right" }, { "left", "exit" }, { "right", "exit" } })); |
| 144 | ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_)); |
| 145 | ExecutionSubgraph esg(graph_, true, GetScopedAllocator()); |
| 146 | esg.RemoveBlock(blks.Get("left")); |
| 147 | esg.Finalize(); |
| 148 | ASSERT_TRUE(esg.IsValid()); |
| 149 | ASSERT_TRUE(IsValidSubgraph(esg)); |
| 150 | std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(), |
| 151 | esg.ReachableBlocks().end()); |
| 152 | |
| 153 | ASSERT_EQ(contents.size(), 3u); |
| 154 | ASSERT_TRUE(contents.find(blks.Get("left")) == contents.end()); |
| 155 | |
| 156 | ASSERT_TRUE(contents.find(blks.Get("right")) != contents.end()); |
| 157 | ASSERT_TRUE(contents.find(blks.Get("entry")) != contents.end()); |
| 158 | ASSERT_TRUE(contents.find(blks.Get("exit")) != contents.end()); |
| 159 | esg.RemoveBlock(blks.Get("right")); |
| 160 | esg.Finalize(); |
| 161 | std::unordered_set<const HBasicBlock*> contents_2(esg.ReachableBlocks().begin(), |
| 162 | esg.ReachableBlocks().end()); |
| 163 | ASSERT_EQ(contents_2.size(), 0u); |
| 164 | } |
| 165 | |
| 166 | // +-------+ +-------+ |
| 167 | // | right | <-- | entry | |
| 168 | // +-------+ +-------+ |
| 169 | // | | |
| 170 | // | | |
| 171 | // | v |
| 172 | // | + - - - - - - - - - - - - - - - - - - - -+ |
| 173 | // | ' indirectly_removed ' |
| 174 | // | ' ' |
| 175 | // | ' +-------+ +-----+ ' |
| 176 | // | ' | l1 | -------------------> | l1r | ' |
| 177 | // | ' +-------+ +-----+ ' |
| 178 | // | ' | | ' |
| 179 | // | ' | | ' |
| 180 | // | ' v | ' |
| 181 | // | ' +-------+ | ' |
| 182 | // | ' | l1l | | ' |
| 183 | // | ' +-------+ | ' |
| 184 | // | ' | | ' |
| 185 | // | ' | | ' |
| 186 | // | ' | | ' |
| 187 | // + - - - - - - - -+ | +- - - | | ' |
| 188 | // ' ' | +- v | ' |
| 189 | // ' +-----+ | +----------------+ | ' |
| 190 | // ' | l2r | <---------+-------------- | l2 (removed) | <-------------+ ' |
| 191 | // ' +-----+ | +----------------+ ' |
| 192 | // ' | ' | +- | ' |
| 193 | // ' | - - -+ | +- - - | - - - - - - - - - - - - - -+ |
| 194 | // ' | ' | ' | ' |
| 195 | // ' | ' | ' | ' |
| 196 | // ' | ' | ' v ' |
| 197 | // ' | ' | ' +-------+ ' |
| 198 | // ' | ' | ' | l2l | ' |
| 199 | // ' | ' | ' +-------+ ' |
| 200 | // ' | ' | ' | ' |
| 201 | // ' | ' | ' | ' |
| 202 | // ' | ' | ' | ' |
| 203 | // ' | - - -+ | +- - - | ' |
| 204 | // ' | ' | +- v ' |
| 205 | // ' | | +-------+ ' |
| 206 | // ' +---------------+-------------> | l3 | ' |
| 207 | // ' | +-------+ ' |
| 208 | // ' ' | +- ' |
| 209 | // + - - - - - - - -+ | +- - - - - - - - - + |
| 210 | // | | |
| 211 | // | | |
| 212 | // | v |
| 213 | // | +-------+ |
| 214 | // +-----------> | exit | |
| 215 | // +-------+ |
| 216 | TEST_F(ExecutionSubgraphTest, Propagation) { |
| 217 | AdjacencyListGraph blks(SetupFromAdjacencyList("entry", |
| 218 | "exit", |
| 219 | { { "entry", "l1" }, |
| 220 | { "l1", "l1l" }, |
| 221 | { "l1", "l1r" }, |
| 222 | { "l1l", "l2" }, |
| 223 | { "l1r", "l2" }, |
| 224 | { "l2", "l2l" }, |
| 225 | { "l2", "l2r" }, |
| 226 | { "l2l", "l3" }, |
| 227 | { "l2r", "l3" }, |
| 228 | { "l3", "exit" }, |
| 229 | { "entry", "right" }, |
| 230 | { "right", "exit" } })); |
| 231 | ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_)); |
| 232 | ExecutionSubgraph esg(graph_, /*analysis_possible=*/true, GetScopedAllocator()); |
| 233 | esg.RemoveBlock(blks.Get("l2")); |
| 234 | esg.Finalize(); |
| 235 | ASSERT_TRUE(esg.IsValid()); |
| 236 | ASSERT_TRUE(IsValidSubgraph(esg)); |
| 237 | std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(), |
| 238 | esg.ReachableBlocks().end()); |
| 239 | |
| 240 | // ASSERT_EQ(contents.size(), 3u); |
| 241 | // Not present, no path through. |
| 242 | ASSERT_TRUE(contents.find(blks.Get("l1")) == contents.end()); |
| 243 | ASSERT_TRUE(contents.find(blks.Get("l2")) == contents.end()); |
| 244 | ASSERT_TRUE(contents.find(blks.Get("l3")) == contents.end()); |
| 245 | ASSERT_TRUE(contents.find(blks.Get("l1l")) == contents.end()); |
| 246 | ASSERT_TRUE(contents.find(blks.Get("l1r")) == contents.end()); |
| 247 | ASSERT_TRUE(contents.find(blks.Get("l2l")) == contents.end()); |
| 248 | ASSERT_TRUE(contents.find(blks.Get("l2r")) == contents.end()); |
| 249 | |
| 250 | // present, path through. |
| 251 | ASSERT_TRUE(contents.find(blks.Get("right")) != contents.end()); |
| 252 | ASSERT_TRUE(contents.find(blks.Get("entry")) != contents.end()); |
| 253 | ASSERT_TRUE(contents.find(blks.Get("exit")) != contents.end()); |
| 254 | } |
| 255 | |
| 256 | // +------------------------------------+ |
| 257 | // | | |
| 258 | // | +-------+ +-------+ | |
| 259 | // | | right | <-- | entry | | |
| 260 | // | +-------+ +-------+ | |
| 261 | // | | | | |
| 262 | // | | | | |
| 263 | // | | v | |
| 264 | // | | +-------+ +--------+ |
| 265 | // +----+---------> | l1 | --> | l1loop | |
| 266 | // | +-------+ +--------+ |
| 267 | // | | |
| 268 | // | | |
| 269 | // | v |
| 270 | // | +- - - - - -+ |
| 271 | // | ' removed ' |
| 272 | // | ' ' |
| 273 | // | ' +-------+ ' |
| 274 | // | ' | l2 | ' |
| 275 | // | ' +-------+ ' |
| 276 | // | ' ' |
| 277 | // | +- - - - - -+ |
| 278 | // | | |
| 279 | // | | |
| 280 | // | v |
| 281 | // | +-------+ |
| 282 | // +---------> | exit | |
| 283 | // +-------+ |
| 284 | TEST_F(ExecutionSubgraphTest, PropagationLoop) { |
| 285 | AdjacencyListGraph blks(SetupFromAdjacencyList("entry", |
| 286 | "exit", |
| 287 | { { "entry", "l1" }, |
| 288 | { "l1", "l2" }, |
| 289 | { "l1", "l1loop" }, |
| 290 | { "l1loop", "l1" }, |
| 291 | { "l2", "exit" }, |
| 292 | { "entry", "right" }, |
| 293 | { "right", "exit" } })); |
| 294 | ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_)); |
| 295 | ExecutionSubgraph esg(graph_, /*analysis_possible=*/true, GetScopedAllocator()); |
| 296 | esg.RemoveBlock(blks.Get("l2")); |
| 297 | esg.Finalize(); |
| 298 | ASSERT_TRUE(esg.IsValid()); |
| 299 | ASSERT_TRUE(IsValidSubgraph(esg)); |
| 300 | std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(), |
| 301 | esg.ReachableBlocks().end()); |
| 302 | |
| 303 | ASSERT_EQ(contents.size(), 5u); |
| 304 | |
| 305 | // Not present, no path through. |
| 306 | ASSERT_TRUE(contents.find(blks.Get("l2")) == contents.end()); |
| 307 | |
| 308 | // present, path through. |
| 309 | // Since the loop can diverge we should leave it in the execution subgraph. |
| 310 | ASSERT_TRUE(contents.find(blks.Get("l1")) != contents.end()); |
| 311 | ASSERT_TRUE(contents.find(blks.Get("l1loop")) != contents.end()); |
| 312 | ASSERT_TRUE(contents.find(blks.Get("right")) != contents.end()); |
| 313 | ASSERT_TRUE(contents.find(blks.Get("entry")) != contents.end()); |
| 314 | ASSERT_TRUE(contents.find(blks.Get("exit")) != contents.end()); |
| 315 | } |
| 316 | |
| 317 | // +--------------------------------+ |
| 318 | // | | |
| 319 | // | +-------+ +-------+ | |
| 320 | // | | right | <-- | entry | | |
| 321 | // | +-------+ +-------+ | |
| 322 | // | | | | |
| 323 | // | | | | |
| 324 | // | | v | |
| 325 | // | | +-------+ +--------+ |
| 326 | // +----+---------> | l1 | --> | l1loop | |
| 327 | // | +-------+ +--------+ |
| 328 | // | | |
| 329 | // | | |
| 330 | // | v |
| 331 | // | +-------+ |
| 332 | // | | l2 | |
| 333 | // | +-------+ |
| 334 | // | | |
| 335 | // | | |
| 336 | // | v |
| 337 | // | +-------+ |
| 338 | // +---------> | exit | |
| 339 | // +-------+ |
| 340 | TEST_F(ExecutionSubgraphTest, PropagationLoop2) { |
| 341 | AdjacencyListGraph blks(SetupFromAdjacencyList("entry", |
| 342 | "exit", |
| 343 | { { "entry", "l1" }, |
| 344 | { "l1", "l2" }, |
| 345 | { "l1", "l1loop" }, |
| 346 | { "l1loop", "l1" }, |
| 347 | { "l2", "exit" }, |
| 348 | { "entry", "right" }, |
| 349 | { "right", "exit" } })); |
| 350 | ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_)); |
| 351 | ExecutionSubgraph esg(graph_, /*analysis_possible=*/true, GetScopedAllocator()); |
| 352 | esg.RemoveBlock(blks.Get("l1")); |
| 353 | esg.Finalize(); |
| 354 | ASSERT_TRUE(esg.IsValid()); |
| 355 | ASSERT_TRUE(IsValidSubgraph(esg)); |
| 356 | std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(), |
| 357 | esg.ReachableBlocks().end()); |
| 358 | |
| 359 | ASSERT_EQ(contents.size(), 3u); |
| 360 | |
| 361 | // Not present, no path through. |
| 362 | ASSERT_TRUE(contents.find(blks.Get("l1")) == contents.end()); |
| 363 | ASSERT_TRUE(contents.find(blks.Get("l1loop")) == contents.end()); |
| 364 | ASSERT_TRUE(contents.find(blks.Get("l2")) == contents.end()); |
| 365 | |
| 366 | // present, path through. |
| 367 | ASSERT_TRUE(contents.find(blks.Get("right")) != contents.end()); |
| 368 | ASSERT_TRUE(contents.find(blks.Get("entry")) != contents.end()); |
| 369 | ASSERT_TRUE(contents.find(blks.Get("exit")) != contents.end()); |
| 370 | } |
| 371 | |
| 372 | // +--------------------------------+ |
| 373 | // | | |
| 374 | // | +-------+ +-------+ | |
| 375 | // | | right | <-- | entry | | |
| 376 | // | +-------+ +-------+ | |
| 377 | // | | | | |
| 378 | // | | | | |
| 379 | // | | v | |
| 380 | // | | +-------+ +--------+ |
| 381 | // +----+---------> | l1 | --> | l1loop | |
| 382 | // | +-------+ +--------+ |
| 383 | // | | |
| 384 | // | | |
| 385 | // | v |
| 386 | // | +-------+ |
| 387 | // | | l2 | |
| 388 | // | +-------+ |
| 389 | // | | |
| 390 | // | | |
| 391 | // | v |
| 392 | // | +-------+ |
| 393 | // +---------> | exit | |
| 394 | // +-------+ |
| 395 | TEST_F(ExecutionSubgraphTest, PropagationLoop3) { |
| 396 | AdjacencyListGraph blks(SetupFromAdjacencyList("entry", |
| 397 | "exit", |
| 398 | { { "entry", "l1" }, |
| 399 | { "l1", "l2" }, |
| 400 | { "l1", "l1loop" }, |
| 401 | { "l1loop", "l1" }, |
| 402 | { "l2", "exit" }, |
| 403 | { "entry", "right" }, |
| 404 | { "right", "exit" } })); |
| 405 | ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_)); |
| 406 | ExecutionSubgraph esg(graph_, /*analysis_possible=*/true, GetScopedAllocator()); |
| 407 | esg.RemoveBlock(blks.Get("l1loop")); |
| 408 | esg.Finalize(); |
| 409 | ASSERT_TRUE(esg.IsValid()); |
| 410 | ASSERT_TRUE(IsValidSubgraph(esg)); |
| 411 | std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(), |
| 412 | esg.ReachableBlocks().end()); |
| 413 | |
| 414 | ASSERT_EQ(contents.size(), 3u); |
| 415 | |
| 416 | // Not present, no path through. If we got to l1 loop then we must merge back |
| 417 | // with l1 and l2 so they're bad too. |
| 418 | ASSERT_TRUE(contents.find(blks.Get("l1loop")) == contents.end()); |
| 419 | ASSERT_TRUE(contents.find(blks.Get("l1")) == contents.end()); |
| 420 | ASSERT_TRUE(contents.find(blks.Get("l2")) == contents.end()); |
| 421 | |
| 422 | // present, path through. |
| 423 | ASSERT_TRUE(contents.find(blks.Get("right")) != contents.end()); |
| 424 | ASSERT_TRUE(contents.find(blks.Get("entry")) != contents.end()); |
| 425 | ASSERT_TRUE(contents.find(blks.Get("exit")) != contents.end()); |
| 426 | } |
| 427 | |
| 428 | TEST_F(ExecutionSubgraphTest, Invalid) { |
| 429 | AdjacencyListGraph blks(SetupFromAdjacencyList( |
| 430 | "entry", |
| 431 | "exit", |
| 432 | { { "entry", "left" }, { "entry", "right" }, { "left", "exit" }, { "right", "exit" } })); |
| 433 | ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_)); |
| 434 | ExecutionSubgraph esg(graph_, /*analysis_possible=*/true, GetScopedAllocator()); |
| 435 | esg.RemoveBlock(blks.Get("left")); |
| 436 | esg.RemoveBlock(blks.Get("right")); |
| 437 | esg.Finalize(); |
| 438 | |
| 439 | ASSERT_FALSE(esg.IsValid()); |
| 440 | ASSERT_FALSE(IsValidSubgraph(esg)); |
| 441 | std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(), |
| 442 | esg.ReachableBlocks().end()); |
| 443 | |
| 444 | ASSERT_EQ(contents.size(), 0u); |
| 445 | } |
| 446 | // Sibling branches are disconnected. |
| 447 | TEST_F(ExecutionSubgraphTest, Exclusions) { |
| 448 | AdjacencyListGraph blks(SetupFromAdjacencyList("entry", |
| 449 | "exit", |
| 450 | { { "entry", "a" }, |
| 451 | { "entry", "b" }, |
| 452 | { "entry", "c" }, |
| 453 | { "a", "exit" }, |
| 454 | { "b", "exit" }, |
| 455 | { "c", "exit" } })); |
| 456 | ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_)); |
| 457 | ExecutionSubgraph esg(graph_, /*analysis_possible=*/true, GetScopedAllocator()); |
| 458 | esg.RemoveBlock(blks.Get("a")); |
| 459 | esg.RemoveBlock(blks.Get("c")); |
| 460 | esg.Finalize(); |
| 461 | ASSERT_TRUE(esg.IsValid()); |
| 462 | ASSERT_TRUE(IsValidSubgraph(esg)); |
| 463 | std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(), |
| 464 | esg.ReachableBlocks().end()); |
| 465 | |
| 466 | ASSERT_EQ(contents.size(), 3u); |
| 467 | // Not present, no path through. |
| 468 | ASSERT_TRUE(contents.find(blks.Get("a")) == contents.end()); |
| 469 | ASSERT_TRUE(contents.find(blks.Get("c")) == contents.end()); |
| 470 | |
| 471 | // present, path through. |
| 472 | ASSERT_TRUE(contents.find(blks.Get("entry")) != contents.end()); |
| 473 | ASSERT_TRUE(contents.find(blks.Get("exit")) != contents.end()); |
| 474 | ASSERT_TRUE(contents.find(blks.Get("b")) != contents.end()); |
| 475 | |
| 476 | ArrayRef<const ExecutionSubgraph::ExcludedCohort> exclusions(esg.GetExcludedCohorts()); |
| 477 | ASSERT_EQ(exclusions.size(), 2u); |
| 478 | std::unordered_set<const HBasicBlock*> exclude_a({ blks.Get("a") }); |
| 479 | std::unordered_set<const HBasicBlock*> exclude_c({ blks.Get("c") }); |
| 480 | ASSERT_TRUE(std::find_if(exclusions.cbegin(), |
| 481 | exclusions.cend(), |
| 482 | [&](const ExecutionSubgraph::ExcludedCohort& it) { |
| 483 | return it.Blocks() == exclude_a; |
| 484 | }) != exclusions.cend()); |
| 485 | ASSERT_TRUE(std::find_if(exclusions.cbegin(), |
| 486 | exclusions.cend(), |
| 487 | [&](const ExecutionSubgraph::ExcludedCohort& it) { |
| 488 | return it.Blocks() == exclude_c; |
| 489 | }) != exclusions.cend()); |
| 490 | } |
| 491 | |
| 492 | // Sibling branches are disconnected. |
| 493 | // +- - - - - - - - - - - - - - - - - - - - - - + |
| 494 | // ' remove_c ' |
| 495 | // ' ' |
| 496 | // ' +-----------+ ' |
| 497 | // ' | c_begin_2 | -------------------------+ ' |
| 498 | // ' +-----------+ | ' |
| 499 | // ' | ' |
| 500 | // +- - - - - - - - - - - - - - - - - - | ' |
| 501 | // ^ ' | ' |
| 502 | // | ' | ' |
| 503 | // | ' | ' |
| 504 | // + - - - - - -+ ' | ' |
| 505 | // ' remove_a ' ' | ' |
| 506 | // ' ' ' | ' |
| 507 | // ' +--------+ ' +-----------+ +---+' | ' |
| 508 | // ' | **a** | ' <-- | entry | --> | b |' | ' |
| 509 | // ' +--------+ ' +-----------+ +---+' | ' |
| 510 | // ' ' ' | ' |
| 511 | // + - - - - - -+ ' | ' |
| 512 | // | | | ' | ' |
| 513 | // | | | ' | ' |
| 514 | // | v | ' | ' |
| 515 | // | +- - - - - - - -+ | ' | ' |
| 516 | // | ' ' | ' | ' |
| 517 | // | ' +-----------+ ' | ' | ' |
| 518 | // | ' | c_begin_1 | ' | ' | ' |
| 519 | // | ' +-----------+ ' | ' | ' |
| 520 | // | ' | ' | ' | ' |
| 521 | // | ' | ' | ' | ' |
| 522 | // | ' | ' | ' | ' |
| 523 | // + - - - - - - - - -+ | + - - - | - - - - - - - + | ' | ' |
| 524 | // ' ' | + v ' | + | ' |
| 525 | // ' +---------+ | +-----------+ | | ' |
| 526 | // ' | c_end_2 | <-------+--------------- | **c_mid** | <-----------------+------+ ' |
| 527 | // ' +---------+ | +-----------+ | ' |
| 528 | // ' ' | + | ' | + ' |
| 529 | // + - - - - - - - - -+ | + - - - | - - - - - - - + | + - - - + |
| 530 | // | | ' | ' | |
| 531 | // | | ' | ' | |
| 532 | // | | ' v ' | |
| 533 | // | | ' +-----------+ ' | |
| 534 | // | | ' | c_end_1 | ' | |
| 535 | // | | ' +-----------+ ' | |
| 536 | // | | ' ' | |
| 537 | // | | +- - - - - - - -+ | |
| 538 | // | | | | |
| 539 | // | | | | |
| 540 | // | | v v |
| 541 | // | | +---------------------------------+ |
| 542 | // | +------------> | exit | |
| 543 | // | +---------------------------------+ |
| 544 | // | ^ |
| 545 | // +------------------------------------+ |
| 546 | TEST_F(ExecutionSubgraphTest, ExclusionExtended) { |
| 547 | AdjacencyListGraph blks(SetupFromAdjacencyList("entry", |
| 548 | "exit", |
| 549 | { { "entry", "a" }, |
| 550 | { "entry", "b" }, |
| 551 | { "entry", "c_begin_1" }, |
| 552 | { "entry", "c_begin_2" }, |
| 553 | { "c_begin_1", "c_mid" }, |
| 554 | { "c_begin_2", "c_mid" }, |
| 555 | { "c_mid", "c_end_1" }, |
| 556 | { "c_mid", "c_end_2" }, |
| 557 | { "a", "exit" }, |
| 558 | { "b", "exit" }, |
| 559 | { "c_end_1", "exit" }, |
| 560 | { "c_end_2", "exit" } })); |
| 561 | ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_)); |
| 562 | ExecutionSubgraph esg(graph_, /*analysis_possible=*/true, GetScopedAllocator()); |
| 563 | esg.RemoveBlock(blks.Get("a")); |
| 564 | esg.RemoveBlock(blks.Get("c_mid")); |
| 565 | esg.Finalize(); |
| 566 | ASSERT_TRUE(esg.IsValid()); |
| 567 | ASSERT_TRUE(IsValidSubgraph(esg)); |
| 568 | std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(), |
| 569 | esg.ReachableBlocks().end()); |
| 570 | |
| 571 | ASSERT_EQ(contents.size(), 3u); |
| 572 | // Not present, no path through. |
| 573 | ASSERT_TRUE(contents.find(blks.Get("a")) == contents.end()); |
| 574 | ASSERT_TRUE(contents.find(blks.Get("c_begin_1")) == contents.end()); |
| 575 | ASSERT_TRUE(contents.find(blks.Get("c_begin_2")) == contents.end()); |
| 576 | ASSERT_TRUE(contents.find(blks.Get("c_mid")) == contents.end()); |
| 577 | ASSERT_TRUE(contents.find(blks.Get("c_end_1")) == contents.end()); |
| 578 | ASSERT_TRUE(contents.find(blks.Get("c_end_2")) == contents.end()); |
| 579 | |
| 580 | // present, path through. |
| 581 | ASSERT_TRUE(contents.find(blks.Get("entry")) != contents.end()); |
| 582 | ASSERT_TRUE(contents.find(blks.Get("exit")) != contents.end()); |
| 583 | ASSERT_TRUE(contents.find(blks.Get("b")) != contents.end()); |
| 584 | |
| 585 | ArrayRef<const ExecutionSubgraph::ExcludedCohort> exclusions(esg.GetExcludedCohorts()); |
| 586 | ASSERT_EQ(exclusions.size(), 2u); |
| 587 | BlockSet exclude_a({ blks.Get("a") }); |
| 588 | BlockSet exclude_c({ blks.Get("c_begin_1"), |
| 589 | blks.Get("c_begin_2"), |
| 590 | blks.Get("c_mid"), |
| 591 | blks.Get("c_end_1"), |
| 592 | blks.Get("c_end_2") }); |
| 593 | ASSERT_TRUE(std::find_if(exclusions.cbegin(), |
| 594 | exclusions.cend(), |
| 595 | [&](const ExecutionSubgraph::ExcludedCohort& it) { |
| 596 | return it.Blocks() == exclude_a; |
| 597 | }) != exclusions.cend()); |
| 598 | ASSERT_TRUE( |
| 599 | std::find_if( |
| 600 | exclusions.cbegin(), exclusions.cend(), [&](const ExecutionSubgraph::ExcludedCohort& it) { |
| 601 | return it.Blocks() == exclude_c && |
| 602 | BlockSet({ blks.Get("c_begin_1"), blks.Get("c_begin_2") }) == it.EntryBlocks() && |
| 603 | BlockSet({ blks.Get("c_end_1"), blks.Get("c_end_2") }) == it.ExitBlocks(); |
| 604 | }) != exclusions.cend()); |
| 605 | } |
| 606 | |
| 607 | // ┌───────┐ ┌────────────┐ |
| 608 | // ┌─ │ right │ ◀── │ entry │ |
| 609 | // │ └───────┘ └────────────┘ |
| 610 | // │ │ |
| 611 | // │ │ |
| 612 | // │ ▼ |
| 613 | // │ ┌────────────┐ |
| 614 | // │ │ esc_top │ |
| 615 | // │ └────────────┘ |
| 616 | // │ │ |
| 617 | // │ │ |
| 618 | // │ ▼ |
| 619 | // │ ┌────────────┐ |
| 620 | // └──────────────▶ │ middle │ ─┐ |
| 621 | // └────────────┘ │ |
| 622 | // │ │ |
| 623 | // │ │ |
| 624 | // ▼ │ |
| 625 | // ┌────────────┐ │ |
| 626 | // │ esc_bottom │ │ |
| 627 | // └────────────┘ │ |
| 628 | // │ │ |
| 629 | // │ │ |
| 630 | // ▼ │ |
| 631 | // ┌────────────┐ │ |
| 632 | // │ exit │ ◀┘ |
| 633 | // └────────────┘ |
| 634 | TEST_F(ExecutionSubgraphTest, InAndOutEscape) { |
| 635 | AdjacencyListGraph blks(SetupFromAdjacencyList("entry", |
| 636 | "exit", |
| 637 | { { "entry", "esc_top" }, |
| 638 | { "entry", "right" }, |
| 639 | { "esc_top", "middle" }, |
| 640 | { "right", "middle" }, |
| 641 | { "middle", "exit" }, |
| 642 | { "middle", "esc_bottom" }, |
| 643 | { "esc_bottom", "exit" } })); |
| 644 | |
| 645 | ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_)); |
| 646 | ExecutionSubgraph esg(graph_, /*analysis_possible=*/true, GetScopedAllocator()); |
| 647 | esg.RemoveBlock(blks.Get("esc_top")); |
| 648 | esg.RemoveBlock(blks.Get("esc_bottom")); |
| 649 | esg.Finalize(); |
| 650 | |
| 651 | std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(), |
| 652 | esg.ReachableBlocks().end()); |
| 653 | ASSERT_EQ(contents.size(), 0u); |
| 654 | ASSERT_FALSE(esg.IsValid()); |
| 655 | ASSERT_FALSE(IsValidSubgraph(esg)); |
| 656 | |
| 657 | ASSERT_EQ(contents.size(), 0u); |
| 658 | } |
| 659 | |
| 660 | // Test with max number of successors and no removals. |
| 661 | TEST_F(ExecutionSubgraphTest, BigNodes) { |
| 662 | std::vector<std::string> mid_blocks; |
| 663 | for (auto i : Range(ExecutionSubgraph::kMaxFilterableSuccessors)) { |
| 664 | std::ostringstream oss; |
| 665 | oss << "blk" << i; |
| 666 | mid_blocks.push_back(oss.str().c_str()); |
| 667 | } |
| 668 | ASSERT_EQ(mid_blocks.size(), ExecutionSubgraph::kMaxFilterableSuccessors); |
| 669 | std::vector<AdjacencyListGraph::Edge> edges; |
| 670 | for (const auto& mid : mid_blocks) { |
| 671 | edges.emplace_back("entry", mid); |
| 672 | edges.emplace_back(mid, "exit"); |
| 673 | } |
| 674 | AdjacencyListGraph blks(SetupFromAdjacencyList("entry", "exit", edges)); |
| 675 | ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_)); |
| 676 | ExecutionSubgraph esg(graph_, /*analysis_possible=*/true, GetScopedAllocator()); |
| 677 | esg.Finalize(); |
| 678 | ASSERT_TRUE(esg.IsValid()); |
| 679 | ASSERT_TRUE(IsValidSubgraph(esg)); |
| 680 | std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(), |
| 681 | esg.ReachableBlocks().end()); |
| 682 | |
| 683 | for (const auto& mid : mid_blocks) { |
| 684 | EXPECT_TRUE(contents.find(blks.Get(mid)) != contents.end()) << mid; |
| 685 | } |
| 686 | // + 2 for entry and exit nodes. |
| 687 | ASSERT_EQ(contents.size(), ExecutionSubgraph::kMaxFilterableSuccessors + 2); |
| 688 | } |
| 689 | |
| 690 | // Test with max number of successors and some removals. |
| 691 | TEST_F(ExecutionSubgraphTest, BigNodesMissing) { |
| 692 | std::vector<std::string> mid_blocks; |
| 693 | for (auto i : Range(ExecutionSubgraph::kMaxFilterableSuccessors)) { |
| 694 | std::ostringstream oss; |
| 695 | oss << "blk" << i; |
| 696 | mid_blocks.push_back(oss.str()); |
| 697 | } |
| 698 | std::vector<AdjacencyListGraph::Edge> edges; |
| 699 | for (const auto& mid : mid_blocks) { |
| 700 | edges.emplace_back("entry", mid); |
| 701 | edges.emplace_back(mid, "exit"); |
| 702 | } |
| 703 | AdjacencyListGraph blks(SetupFromAdjacencyList("entry", "exit", edges)); |
| 704 | ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_)); |
| 705 | ExecutionSubgraph esg(graph_, /*analysis_possible=*/true, GetScopedAllocator()); |
| 706 | esg.RemoveBlock(blks.Get("blk2")); |
| 707 | esg.RemoveBlock(blks.Get("blk4")); |
| 708 | esg.Finalize(); |
| 709 | ASSERT_TRUE(esg.IsValid()); |
| 710 | ASSERT_TRUE(IsValidSubgraph(esg)); |
| 711 | std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(), |
| 712 | esg.ReachableBlocks().end()); |
| 713 | |
| 714 | ASSERT_EQ(contents.size(), ExecutionSubgraph::kMaxFilterableSuccessors + 2 - 2); |
| 715 | |
| 716 | // Not present, no path through. |
| 717 | ASSERT_TRUE(contents.find(blks.Get("blk2")) == contents.end()); |
| 718 | ASSERT_TRUE(contents.find(blks.Get("blk4")) == contents.end()); |
| 719 | } |
| 720 | |
| 721 | // Test with max number of successors and all successors removed. |
| 722 | TEST_F(ExecutionSubgraphTest, BigNodesNoPath) { |
| 723 | std::vector<std::string> mid_blocks; |
| 724 | for (auto i : Range(ExecutionSubgraph::kMaxFilterableSuccessors)) { |
| 725 | std::ostringstream oss; |
| 726 | oss << "blk" << i; |
| 727 | mid_blocks.push_back(oss.str()); |
| 728 | } |
| 729 | std::vector<AdjacencyListGraph::Edge> edges; |
| 730 | for (const auto& mid : mid_blocks) { |
| 731 | edges.emplace_back("entry", mid); |
| 732 | edges.emplace_back(mid, "exit"); |
| 733 | } |
| 734 | AdjacencyListGraph blks(SetupFromAdjacencyList("entry", "exit", edges)); |
| 735 | ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_)); |
| 736 | ExecutionSubgraph esg(graph_, /*analysis_possible=*/true, GetScopedAllocator()); |
| 737 | for (const auto& mid : mid_blocks) { |
| 738 | esg.RemoveBlock(blks.Get(mid)); |
| 739 | } |
| 740 | esg.Finalize(); |
| 741 | ASSERT_FALSE(esg.IsValid()); |
| 742 | ASSERT_FALSE(IsValidSubgraph(esg)); |
| 743 | } |
| 744 | |
| 745 | // Test with max number of successors |
| 746 | TEST_F(ExecutionSubgraphTest, CanAnalyseBig) { |
| 747 | // Make an absurdly huge and well connected graph. This should be pretty worst-case scenario. |
| 748 | constexpr size_t kNumBlocks = ExecutionSubgraph::kMaxFilterableSuccessors + 1000; |
| 749 | std::vector<std::string> mid_blocks; |
| 750 | for (auto i : Range(kNumBlocks)) { |
| 751 | std::ostringstream oss; |
| 752 | oss << "blk" << i; |
| 753 | mid_blocks.push_back(oss.str()); |
| 754 | } |
| 755 | std::vector<AdjacencyListGraph::Edge> edges; |
| 756 | for (auto cur : Range(kNumBlocks)) { |
| 757 | for (auto nxt : |
| 758 | Range(cur + 1, |
| 759 | std::min(cur + ExecutionSubgraph::kMaxFilterableSuccessors + 1, kNumBlocks))) { |
| 760 | edges.emplace_back(mid_blocks[cur], mid_blocks[nxt]); |
| 761 | } |
| 762 | } |
| 763 | AdjacencyListGraph blks(SetupFromAdjacencyList(mid_blocks.front(), mid_blocks.back(), edges)); |
| 764 | ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_)); |
| 765 | |
| 766 | ExecutionSubgraph esg(graph_, /*analysis_possible=*/true, GetScopedAllocator()); |
| 767 | esg.Finalize(); |
| 768 | ASSERT_TRUE(esg.IsValid()); |
| 769 | ASSERT_TRUE(IsValidSubgraph(esg)); |
| 770 | std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(), |
| 771 | esg.ReachableBlocks().end()); |
| 772 | |
| 773 | ASSERT_EQ(contents.size(), kNumBlocks); |
| 774 | } |
| 775 | |
| 776 | // Test with many successors |
| 777 | TEST_F(ExecutionSubgraphTest, CanAnalyseBig2) { |
| 778 | // Make an absurdly huge and well connected graph. This should be pretty worst-case scenario. |
| 779 | constexpr size_t kNumBlocks = ExecutionSubgraph::kMaxFilterableSuccessors + 1000; |
| 780 | constexpr size_t kTestMaxSuccessors = ExecutionSubgraph::kMaxFilterableSuccessors - 1; |
| 781 | std::vector<std::string> mid_blocks; |
| 782 | for (auto i : Range(kNumBlocks)) { |
| 783 | std::ostringstream oss; |
| 784 | oss << "blk" << i; |
| 785 | mid_blocks.push_back(oss.str()); |
| 786 | } |
| 787 | std::vector<AdjacencyListGraph::Edge> edges; |
| 788 | for (auto cur : Range(kNumBlocks)) { |
| 789 | for (auto nxt : Range(cur + 1, std::min(cur + 1 + kTestMaxSuccessors, kNumBlocks))) { |
| 790 | edges.emplace_back(mid_blocks[cur], mid_blocks[nxt]); |
| 791 | } |
| 792 | } |
| 793 | edges.emplace_back(mid_blocks.front(), mid_blocks.back()); |
| 794 | AdjacencyListGraph blks(SetupFromAdjacencyList(mid_blocks.front(), mid_blocks.back(), edges)); |
| 795 | ASSERT_TRUE(ExecutionSubgraph::CanAnalyse(graph_)); |
| 796 | ExecutionSubgraph esg(graph_, /*analysis_possible=*/true, GetScopedAllocator()); |
| 797 | constexpr size_t kToRemoveIdx = kNumBlocks / 2; |
| 798 | HBasicBlock* remove_implicit = blks.Get(mid_blocks[kToRemoveIdx]); |
| 799 | for (HBasicBlock* pred : remove_implicit->GetPredecessors()) { |
| 800 | esg.RemoveBlock(pred); |
| 801 | } |
| 802 | esg.Finalize(); |
| 803 | EXPECT_TRUE(esg.IsValid()); |
| 804 | EXPECT_TRUE(IsValidSubgraph(esg)); |
| 805 | std::unordered_set<const HBasicBlock*> contents(esg.ReachableBlocks().begin(), |
| 806 | esg.ReachableBlocks().end()); |
| 807 | |
| 808 | // Only entry and exit. The middle ones should eliminate everything else. |
| 809 | EXPECT_EQ(contents.size(), 2u); |
| 810 | EXPECT_TRUE(contents.find(remove_implicit) == contents.end()); |
| 811 | EXPECT_TRUE(contents.find(blks.Get(mid_blocks.front())) != contents.end()); |
| 812 | EXPECT_TRUE(contents.find(blks.Get(mid_blocks.back())) != contents.end()); |
| 813 | } |
| 814 | |
| 815 | // Test with too many successors |
| 816 | TEST_F(ExecutionSubgraphTest, CanNotAnalyseBig) { |
| 817 | std::vector<std::string> mid_blocks; |
| 818 | for (auto i : Range(ExecutionSubgraph::kMaxFilterableSuccessors + 4)) { |
| 819 | std::ostringstream oss; |
| 820 | oss << "blk" << i; |
| 821 | mid_blocks.push_back(oss.str()); |
| 822 | } |
| 823 | std::vector<AdjacencyListGraph::Edge> edges; |
| 824 | for (const auto& mid : mid_blocks) { |
| 825 | edges.emplace_back("entry", mid); |
| 826 | edges.emplace_back(mid, "exit"); |
| 827 | } |
| 828 | AdjacencyListGraph blks(SetupFromAdjacencyList("entry", "exit", edges)); |
| 829 | ASSERT_FALSE(ExecutionSubgraph::CanAnalyse(graph_)); |
| 830 | } |
| 831 | } // namespace art |