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); - } -}