/* Copyright (c) 2016 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Author: Leonardo de Moura */ #include "library/vm/vm.h" namespace lean { static void del_instr_at(unsigned pc, buffer & code) { code.erase(pc); // we must adjust pc of other instructions for (unsigned i = 0; i < code.size(); i++) { vm_instr & c = code[i]; for (unsigned j = 0; j < c.get_num_pcs(); j++) { if (c.get_pc(j) > pc) c.set_pc(j, c.get_pc(j) - 1); } } } typedef rb_tree addr_set; /* Collect addresses in addr_set that are goto/branching targets */ static void collect_targets(buffer & code, addr_set & r) { for (auto c : code) { for (unsigned j = 0; j < c.get_num_pcs(); j++) r.insert(c.get_pc(j)); } } /** \brief Applies the following transformation ... pc: drop n pc+1: drop m ... ===> ... pc: drop n+m ... */ static void compress_drop_drop(buffer & code) { if (code.empty()) return; addr_set targets; collect_targets(code, targets); unsigned i = code.size() - 1; while (i > 0) { --i; if (code[i].op() == opcode::Drop && code[i+1].op() == opcode::Drop && /* If i+1 is a goto/branch target, then we should not merge the two Drops */ !targets.contains(i+1)) { code[i] = mk_drop_instr(code[i].get_num() + code[i+1].get_num()); del_instr_at(i+1, code); } } } /** \brief Applies the following transformation pc_1 : goto pc_2 ... pc_2 : ret ===> pc_1 : ret ... pc_2 : ret */ static void compress_goto_ret(buffer & code) { unsigned i = code.size(); while (i > 0) { --i; if (code[i].op() == opcode::Goto) { unsigned pc = code[i].get_goto_pc(); if (code[pc].op() == opcode::Ret) { code[i] = mk_ret_instr(); } } } } typedef rb_tree live_var_set; class live_vars_fn { buffer const & m_code; buffer> m_result; live_var_set collect(unsigned pc) { check_system("live variable analysis"); if (pc >= m_code.size()) return live_var_set(); if (auto s = m_result[pc]) return *s; /* already processed */ auto const & instr = m_code[pc]; live_var_set s; switch (instr.op()) { case opcode::Ret: case opcode::Unreachable: break; case opcode::Goto: s = collect(instr.get_goto_pc()); break; case opcode::Drop: case opcode::SConstructor: case opcode::Constructor: case opcode::Num: case opcode::String: case opcode::Destruct: case opcode::Proj: case opcode::Apply: case opcode::InvokeGlobal: case opcode::InvokeBuiltin: case opcode::InvokeCFun: case opcode::Closure: case opcode::Expr: case opcode::LocalInfo: s = collect(pc+1); break; case opcode::Push: case opcode::Move: s = collect(pc+1); s.insert(instr.get_idx()); break; case opcode::Cases2: s = collect(instr.get_cases2_pc(0)); s.merge(collect(instr.get_cases2_pc(1))); break; case opcode::CasesN: s = collect(instr.get_casesn_pc(0)); for (unsigned i = 1; i < instr.get_casesn_size(); i++) s.merge(collect(instr.get_casesn_pc(i))); break; } m_result[pc] = optional(s); return s; } public: live_vars_fn(buffer const & c):m_code(c) { m_result.resize(c.size()); } buffer> const & operator()() { collect(0); return m_result; } }; void convert_push_to_move(buffer & code) { live_vars_fn fn(code); buffer> live_vars_buffer = fn(); for (unsigned pc = 0; pc < code.size(); pc++) { vm_instr & instr = code[pc]; if (instr.op() == opcode::Push && live_vars_buffer[pc+1] && !live_vars_buffer[pc+1]->contains(instr.get_idx())) { instr = mk_move_instr(instr.get_idx()); } } } void optimize(environment const &, buffer & code) { compress_goto_ret(code); compress_drop_drop(code); convert_push_to_move(code); } }