From a7a1c697778982f4c728b5752cb749e5207d6c10 Mon Sep 17 00:00:00 2001
From: William Sanville
Date: Fri, 15 Dec 2023 15:31:43 -0800
Subject: [PATCH] Move SwitchMethodPartitioning
Summary:
SwitchMethodPartitioning was moved from its optimization to open source since it was touted as a general purpose utility. SwitchEquivFinder came later and is better at handling such jobs, and in the dependent diffs SwitchMethodPartitioning became just a holder for SwitchEquivFinder results that is catered to its caller.
I see no value in having SwitchMethodPartitioning as a general purpose thing, move it to its use and further specialize it to do that task. Anything that wanted to use its functionality should be using SwitchEquivFinder anyways.
Reviewed By: thezhangwei
Differential Revision: D52213332
fbshipit-source-id: d198909c168431c2803ea89f3e163223a95b1b56
---
Makefile.am | 1 -
.../SwitchMethodPartitioning.cpp | 105 ------------------
.../SwitchMethodPartitioning.h | 71 ------------
test/unit/Makefile.am | 4 -
test/unit/SwitchPartitioningTest.cpp | 65 -----------
5 files changed, 246 deletions(-)
delete mode 100644 service/switch-partitioning/SwitchMethodPartitioning.cpp
delete mode 100644 service/switch-partitioning/SwitchMethodPartitioning.h
delete mode 100644 test/unit/SwitchPartitioningTest.cpp
diff --git a/Makefile.am b/Makefile.am
index 9fa22042a51..cf9b7ba8115 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -310,7 +310,6 @@ libredex_la_SOURCES = \
service/shrinker/Shrinker.cpp \
service/switch-dispatch/SwitchDispatch.cpp \
service/switch-partitioning/SwitchEquivFinder.cpp \
- service/switch-partitioning/SwitchMethodPartitioning.cpp \
service/type-analysis/GlobalTypeAnalyzer.cpp \
service/type-analysis/LocalTypeAnalyzer.cpp \
service/type-analysis/WholeProgramState.cpp \
diff --git a/service/switch-partitioning/SwitchMethodPartitioning.cpp b/service/switch-partitioning/SwitchMethodPartitioning.cpp
deleted file mode 100644
index 93471787b56..00000000000
--- a/service/switch-partitioning/SwitchMethodPartitioning.cpp
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-#include "SwitchMethodPartitioning.h"
-
-#include
-#include
-
-#include "ConstantEnvironment.h"
-#include "ConstantPropagationAnalysis.h"
-#include "ControlFlow.h"
-#include "SwitchEquivFinder.h"
-#include "SwitchEquivPrerequisites.h"
-
-namespace cp = constant_propagation;
-
-namespace {
-// See comments in SwitchEquivFinder.h for an explanation.
-constexpr size_t DEFAULT_LEAF_DUP_THRESHOLD = 50;
-
-// Check whether, possibly at the end of a chain of gotos, the block will
-// unconditionally throw.
-bool throws(cfg::Block* block) {
- std::unordered_set visited{block};
- for (; block->goes_to_only_edge(); block = block->goes_to_only_edge()) {
- if (!visited.insert(block->goes_to_only_edge()).second) {
- // non-terminating loop
- return false;
- }
- }
- auto last_insn_it = block->get_last_insn();
- return last_insn_it != block->end() &&
- last_insn_it->insn->opcode() == OPCODE_THROW;
-}
-} // namespace
-
-std::unique_ptr SwitchMethodPartitioning::create(
- IRCode* code, bool verify_default_case_throws) {
- cfg::ScopedCFG cfg(code);
- // Check for a throw only method up front. SwitchEquivFinder will not
- // represent this out of the box, so convert directly to
- // SwitchMethodPartitioning representation.
- if (throws(cfg->entry_block())) {
- TRACE(SW, 3, "Special case: method always throws");
- std::vector entry_blocks;
- entry_blocks.emplace_back(cfg->entry_block());
- return std::unique_ptr(
- new SwitchMethodPartitioning(std::move(cfg), std::move(entry_blocks),
- {}));
- }
-
- // Note that a single-case switch can be compiled as either a switch opcode or
- // a series of if-* opcodes. We can use constant propagation to handle these
- // cases uniformly: to determine the case key, we use the inferred value of
- // the operand to the branching opcode in the successor blocks.
- std::vector prologue_blocks;
- if (!gather_linear_prologue_blocks(cfg.get(), &prologue_blocks)) {
- TRACE(SW, 3, "Prologue blocks do not have expected branching");
- return nullptr;
- }
- auto fixpoint = std::make_shared(
- *cfg, SwitchEquivFinder::Analyzer());
- fixpoint->run(ConstantEnvironment());
- reg_t determining_reg;
- if (!find_determining_reg(*fixpoint, prologue_blocks.back(),
- &determining_reg)) {
- TRACE(SW, 3, "Unknown const for branching");
- return nullptr;
- }
- auto last_prologue_block = prologue_blocks.back();
- auto last_prologue_insn = last_prologue_block->get_last_insn();
- auto root_branch = cfg->find_insn(last_prologue_insn->insn);
- auto finder = std::make_unique(
- cfg.get(), root_branch, determining_reg, DEFAULT_LEAF_DUP_THRESHOLD,
- fixpoint, SwitchEquivFinder::EXECUTION_ORDER);
- if (!finder->success() ||
- !finder->are_keys_uniform(SwitchEquivFinder::KeyKind::INT)) {
- TRACE(SW, 3, "Cannot represent method as switch equivalent");
- return nullptr;
- }
-
- if (verify_default_case_throws) {
- always_assert_log(finder->default_case() != boost::none,
- "Method does not have default case");
- auto default_block = *finder->default_case();
- always_assert_log(throws(default_block), "Default case B%zu should throw",
- default_block->id());
- }
-
- // Method is supported, munge into simpler format expected by callers.
- std::map key_to_block;
- for (auto&& [key, block] : finder->key_to_case()) {
- if (!SwitchEquivFinder::is_default_case(key)) {
- auto i = boost::get(key);
- key_to_block.emplace(i, block);
- }
- }
- // Can't use make_unique because constructor is private
- return std::unique_ptr(new SwitchMethodPartitioning(
- std::move(cfg), std::move(prologue_blocks), std::move(key_to_block)));
-}
diff --git a/service/switch-partitioning/SwitchMethodPartitioning.h b/service/switch-partitioning/SwitchMethodPartitioning.h
deleted file mode 100644
index 7d94ee0524c..00000000000
--- a/service/switch-partitioning/SwitchMethodPartitioning.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-#pragma once
-
-#include
-#include
-#include
-
-#include "ConstantPropagationAnalysis.h"
-#include "DexClass.h"
-#include "IRCode.h"
-#include "ScopedCFG.h"
-
-/*
- * This is designed to work on methods with a very specific control-flow graph
- * -- methods whose sources contain a single switch statement (or if-else tree)
- * and no other control-flow structures (like catch blocks). We expect the CFG
- * to be of the following form:
- *
- * [Prologue block(s)] ____
- * _/ | \_ \______
- * / | ... \ \
- * [case 0] [case 1] ... [case N] [default case (may throw)]
- * \_ | ... _/ _______/
- * \ | / __/
- * [Exit block(s)]
- *
- * We partition the method into these prologue blocks and case blocks. The
- * default case and the exit blocks, if any, are omitted. The current usages
- * of SMP have no need for the default case, and they can find the exit blocks
- * easily enough by following the successor edges from the case blocks.
- *
- * It's also possible that there are no exit blocks, rather each case has a
- * return opcode.
- *
- * SwitchMethodPartitioning is slightly a misnomer. It was originally designed
- * for methods that had a single switch statement, but was later extended to
- * support methods that use an if-else tree to choose a case block (instead of
- * a switch). These methods may have been switch-only in source code, but have
- * been compiled into if-else trees (usually by d8).
- */
-class SwitchMethodPartitioning final {
- public:
- static std::unique_ptr create(
- IRCode* code, bool verify_default_case_throws = true);
-
- const std::vector& get_prologue_blocks() const {
- return m_prologue_blocks;
- }
-
- const std::map& get_key_to_block() const {
- return m_key_to_block;
- }
-
- private:
- SwitchMethodPartitioning(cfg::ScopedCFG cfg,
- std::vector prologue_blocks,
- std::map key_to_block)
- : m_prologue_blocks(std::move(prologue_blocks)),
- m_key_to_block(std::move(key_to_block)),
- m_cfg(std::move(cfg)) {}
-
- std::vector m_prologue_blocks;
- std::map m_key_to_block;
- cfg::ScopedCFG m_cfg;
-};
diff --git a/test/unit/Makefile.am b/test/unit/Makefile.am
index 7c8f0027f89..303a5b582cf 100644
--- a/test/unit/Makefile.am
+++ b/test/unit/Makefile.am
@@ -132,7 +132,6 @@ check_PROGRAMS = \
strip_debug_info_test \
switch_dispatch_test \
switch_equiv_test \
- switch_partitioning_test \
timer_test \
trace_multithreading_test \
true_virtuals_test \
@@ -430,8 +429,6 @@ switch_dispatch_test_SOURCES = SwitchDispatchTest.cpp
switch_equiv_test_SOURCES = SwitchEquivFinderTest.cpp
switch_equiv_test_LDADD = $(COMMON_MOCK_TEST_LIBS)
-switch_partitioning_test_SOURCES = SwitchPartitioningTest.cpp
-
# throw_propagation_test_SOURCES = ThrowPropagationTest.cpp
# throw_propagation_test_LDADD = $(COMMON_MOCK_TEST_LIBS)
@@ -591,7 +588,6 @@ TESTS = \
strip_debug_info_test \
switch_dispatch_test \
switch_equiv_test \
- switch_partitioning_test \
timer_test \
trace_multithreading_test \
true_virtuals_test \
diff --git a/test/unit/SwitchPartitioningTest.cpp b/test/unit/SwitchPartitioningTest.cpp
deleted file mode 100644
index 096c869d0ea..00000000000
--- a/test/unit/SwitchPartitioningTest.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-#include "IRAssembler.h"
-#include "RedexTest.h"
-#include "Show.h"
-#include "SwitchMethodPartitioning.h"
-
-class SwitchPartitioningTest : public RedexTest {};
-
-TEST_F(SwitchPartitioningTest, if_chains) {
- auto code1 = assembler::ircode_from_string(R"(
- (
- (load-param v1)
- (const v0 1)
- (if-eq v1 v0 :if-1)
- (const v0 2)
- (if-eq v1 v0 :if-2)
- (const v1 48)
- (return v1)
- (:if-2)
- (const v1 47)
- (return v1)
- (:if-1)
- (const v1 46)
- (return v1)
- )
- )");
- auto smp1 = SwitchMethodPartitioning::create(code1.get(),
- /* verify_default_case */ false);
- ASSERT_TRUE(smp1);
- const auto& key_to_block1 = smp1->get_key_to_block();
- EXPECT_EQ(key_to_block1.size(), 2);
-
- auto code2 = assembler::ircode_from_string(R"(
- (
- (load-param v0)
- (switch v0 (:case-1 :case-2))
- (const v1 48)
- (return v1)
- (:case-1 1)
- (const v1 46)
- (return v1)
- (:case-2 2)
- (const v1 47)
- (return v1)
- )
- )");
- auto smp2 = SwitchMethodPartitioning::create(code2.get(),
- /* verify_default_case */ false);
- ASSERT_TRUE(smp2);
- const auto& key_to_block2 = smp2->get_key_to_block();
- EXPECT_EQ(key_to_block2.size(), 2);
- for (size_t key = 1; key <= 2; key++) {
- auto* block1 = key_to_block1.at(key);
- auto* block2 = key_to_block2.at(key);
- EXPECT_TRUE(block1->structural_equals(block2)) << key << " : \n"
- << show(block1) << "v.s.\n"
- << show(block2);
- }
-}