service/copy-propagation/CanonicalizeLocks.cpp (200 lines of code) (raw):

/* * 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 "CanonicalizeLocks.h" #include <boost/optional.hpp> #include "CFGMutation.h" #include "ControlFlow.h" #include "DexClass.h" #include "IRInstruction.h" #include "ReachingDefinitions.h" namespace copy_propagation_impl { namespace locks { namespace { using namespace cfg; struct MonitorData { IRInstruction* insn{nullptr}; IRInstruction* source{nullptr}; IRInstruction* immediate_in{nullptr}; }; struct RDefs { std::unordered_map<IRInstruction*, MonitorData> data; std::unordered_map<IRInstruction*, size_t> ordering; }; boost::optional<RDefs> compute_rdefs(ControlFlowGraph& cfg) { // Do not use MoveAware, we want to track the moves. std::unique_ptr<reaching_defs::FixpointIterator> rdefs; auto get_defs = [&](Block* b, const IRInstruction* i) { if (!rdefs) { rdefs = std::make_unique<reaching_defs::FixpointIterator>(cfg); rdefs->run(reaching_defs::Environment()); } auto defs_in = rdefs->get_entry_state_at(b); for (const auto& it : ir_list::InstructionIterable{b}) { if (it.insn == i) { break; } rdefs->analyze_instruction(it.insn, &defs_in); } return defs_in; }; auto get_singleton = [](auto& defs, reg_t reg) -> IRInstruction* { const auto& defs0 = defs.get(reg); if (defs0.is_top() || defs0.is_bottom()) { return nullptr; } if (defs0.elements().size() != 1) { return nullptr; } return *defs0.elements().begin(); }; std::unordered_map<const IRInstruction*, Block*> block_map; auto get_rdef = [&](IRInstruction* insn, reg_t reg) -> IRInstruction* { auto it = block_map.find(insn); redex_assert(it != block_map.end()); auto defs = get_defs(it->second, insn); return get_singleton(defs, reg); }; std::vector<IRInstruction*> monitor_insns; for (auto* b : cfg.blocks()) { for (auto& mie : *b) { if (mie.type != MFLOW_OPCODE) { continue; } block_map.emplace(mie.insn, b); if (opcode::is_a_monitor(mie.insn->opcode())) { monitor_insns.push_back(mie.insn); } } } RDefs ret; size_t idx{0}; for (auto* monitor_insn : monitor_insns) { auto find_root_def = [&](IRInstruction* cur) -> IRInstruction* { for (;;) { IRInstruction* next; switch (cur->opcode()) { case OPCODE_MONITOR_ENTER: case OPCODE_MONITOR_EXIT: next = get_rdef(cur, cur->src(0)); break; case OPCODE_MOVE_OBJECT: next = get_rdef(cur, cur->src(0)); break; case IOPCODE_MOVE_RESULT_PSEUDO_OBJECT: { // This is complicated. If it is the move-result-pseudo // of a check-cast, continue. Otherwise use it. auto it = cfg.find_insn(cur); redex_assert(!it.is_end()); auto prim_it = cfg.primary_instruction_of_move_result(it); redex_assert(!prim_it.is_end()); if (prim_it->insn->opcode() != OPCODE_CHECK_CAST) { return cur; } next = prim_it->insn; break; } // Ignore check-cast, go further. case OPCODE_CHECK_CAST: next = get_rdef(cur, cur->src(0)); break; // Includes move-result, which we take over the invoke etc. default: return cur; } if (next == nullptr) { return nullptr; } cur = next; } }; auto immediate_rdef = get_rdef(monitor_insn, monitor_insn->src(0)); if (immediate_rdef == nullptr) { return boost::none; } auto root_rdef = find_root_def(monitor_insn); if (root_rdef == nullptr) { return boost::none; } ret.data.emplace(monitor_insn, MonitorData{monitor_insn, root_rdef, immediate_rdef}); if (!ret.ordering.count(monitor_insn)) { ret.ordering[monitor_insn] = idx++; } if (!ret.ordering.count(root_rdef)) { ret.ordering[root_rdef] = idx++; } } return ret; } using MonitorGroups = std::vector<std::pair<IRInstruction*, std::vector<MonitorData*>>>; MonitorGroups create_groups(RDefs& rdefs) { std::unordered_map<IRInstruction*, std::vector<MonitorData*>> tmp; for (const auto& p : rdefs.data) { MonitorData& data = rdefs.data[p.first]; tmp[data.source].push_back(&data); } // Sort for determinism, use the instruction order from the data. MonitorGroups ret; ret.reserve(tmp.size()); for (auto& p : tmp) { std::sort(p.second.begin(), p.second.end(), [&](const auto& lhs, const auto& rhs) { return rdefs.ordering.at(lhs->insn) < rdefs.ordering.at(rhs->insn); }); ret.emplace_back(p.first, std::move(p.second)); } std::sort(ret.begin(), ret.end(), [&](const auto& lhs, const auto& rhs) { return rdefs.ordering.at(lhs.first) < rdefs.ordering.at(rhs.first); }); return ret; } } // namespace Result run(cfg::ControlFlowGraph& cfg) { Result res{}; // 1) Run ReachingDefs. auto rdefs_opt = compute_rdefs(cfg); if (!rdefs_opt) { res.has_locks = true; res.non_singleton_rdefs = true; return res; } if (rdefs_opt->data.empty()) { return res; } res.has_locks = true; // 2) Create sets over the same source. auto groups = create_groups(*rdefs_opt); // 3) Process groups. CFGMutation mutation(cfg); for (const auto& p : groups) { auto& group = p.second; std::unordered_set<IRInstruction*> immediate_srcs; for (auto monitor_data : group) { immediate_srcs.insert(monitor_data->immediate_in); } if (immediate_srcs.size() == 1) { // This group is OK. continue; } auto source = (*group.begin())->source; // Make a copy. reg_t temp = cfg.allocate_temp(); auto new_move = new IRInstruction(OPCODE_MOVE_OBJECT); new_move->set_src(0, source->dest()); new_move->set_dest(temp); // Need to insert soon after instruction. However, if it is a load-param, // it must be at the end of all those. if (source->opcode() == IOPCODE_LOAD_PARAM_OBJECT) { auto it = cfg.find_insn(source); auto source_block = it.block(); auto first_non_loading = source_block->get_first_non_param_loading_insn(); if (first_non_loading != source_block->end()) { mutation.insert_before( source_block->to_cfg_instruction_iterator(first_non_loading), {new_move}); } else { mutation.insert_after(source_block->to_cfg_instruction_iterator( source_block->get_last_insn()), {new_move}); } } else { mutation.insert_after(cfg.find_insn(source), {new_move}); } // Fix up the elements. for (auto monitor_data : group) { monitor_data->insn->set_src(0, temp); } ++res.fixups; } mutation.flush(); return res; } } // namespace locks } // namespace copy_propagation_impl