#pragma once /*! * @file allocate.h * Interface for the register allocator. * * The IR is translated to RegAllocInstrs, which are added to a RegAllocFunc. * These, plus any additional info are put in a AllocationInput, which is processed by the * allocate_registers algorithm. */ #include #include #include "goalc/emitter/Register.h" #include "goalc/regalloc/IRegSet.h" #include "goalc/regalloc/IRegister.h" /*! * Information about an instruction needed for register allocation. * The model is this: * instruction reads all read registers * instruction writes junk into all clobber registers * instruction writes all write registers * * The "exclude" registers cannot be used at any time during this instruction, for any reason. * Possibly because the actual implementation requires using it. */ struct RegAllocInstr { std::vector clobber; // written, but safe to use as input/output std::vector exclude; // written, unsafe to use for input/output std::vector write; // results go in here std::vector read; // inputs go in here std::vector jumps; // RegAllocInstr indexes of possible jumps bool fallthrough = true; // can it fall through to the next instruction bool is_move = false; // is this a move? std::string print() const; /*! * Does this read IReg id? */ bool reads(int id) const { for (const auto& x : read) { if (x.id == id) return true; } return false; } /*! * Does this write IReg id? */ bool writes(int id) const { for (const auto& x : write) { if (x.id == id) return true; } return false; } }; /*! * An operation that's added to an Instruction so that it loads/stores things from the stack if * needed for spilling. */ struct StackOp { struct Op { int slot = -1; emitter::Register reg; RegClass reg_class = RegClass::INVALID; bool load = false; // load from reg before instruction? bool store = false; // store into reg after instruction? }; std::vector ops; std::string print() const; }; /*! * The assignment of an IRegister to a real Register. * For a single IR Instruction. */ struct Assignment { int stack_slot = -1; //! index of the slot, if we are ever spilled enum class Kind : u8 { STACK, REGISTER, UNASSIGNED } kind = Kind::UNASSIGNED; emitter::Register reg = -1; //! where the IRegister is now bool spilled = false; //! are we ever spilled std::string to_string() const; bool occupies_same_reg(const Assignment& other) const { return other.reg == reg && (reg != -1); } bool occupies_reg(emitter::Register other_reg) const { return reg == other_reg && (reg != -1); } bool is_assigned() const { return kind != Kind::UNASSIGNED; } }; class AssignmentRange { public: AssignmentRange(int start_instr, const std::vector& live, const std::vector& assignments) : m_start(start_instr), m_live(live), m_ass(assignments) { m_end = start_instr + live.size() - 1; ASSERT(m_live.size() == m_ass.size()); } bool is_live_at_instr(int instr) const { return has_info_at(instr) && m_live.at(instr - m_start); } const Assignment& get(int instr) const { return m_ass.at(instr - m_start); } bool has_info_at(int instr) const { return instr >= m_start && instr <= m_end; } int stack_slot() const { return m_ass.at(0).stack_slot; } private: int m_start = -1; int m_end = -1; // INCLUSIVE! std::vector m_live; std::vector m_ass; }; /*! * Result of the allocate_registers algorithm */ struct AllocationResult { bool ok = false; // did it work? // std::vector> assignment; // variable, instruction std::vector ass_as_ranges; // another format, maybe easier? std::vector used_saved_regs; // which saved regs get clobbered? int stack_slots_for_spills = 0; // how many space on the stack do we need? int stack_slots_for_vars = 0; std::vector stack_ops; // additional instructions to spill/restore bool needs_aligned_stack_for_spills = false; int num_spills = 0; int num_spilled_vars = 0; // we put the variables before the spills so the variables are 16-byte aligned. int total_stack_slots() const { return stack_slots_for_spills + stack_slots_for_vars; } int get_slot_for_var(int slot) const { ASSERT(slot < stack_slots_for_vars); return slot; } int get_slot_for_spill(int slot) const { ASSERT(slot < stack_slots_for_spills); return slot + stack_slots_for_vars; } }; /*! * Input to the allocate_registers algorithm */ struct AllocationInput { std::vector instructions; // all instructions in the function std::vector constraints; // all register constraints std::unordered_set force_on_stack_regs; // registers which must be on the stack int max_vars = -1; // maximum register id. std::vector debug_instruction_names; // optional, for debug prints int stack_slots_for_stack_vars = 0; bool is_asm_function = false; std::string function_name; int allocator_version = 1; struct { bool print_input = false; bool print_analysis = false; bool trace_debug_constraints = false; int allocate_log_level = 0; bool print_result = false; } debug_settings; /*! * Add instruction and return its idx. */ int add_instruction(const RegAllocInstr& instr) { instructions.push_back(instr); return int(instructions.size()) - 1; } }; struct RegAllocBasicBlock { std::vector instr_idx; std::vector succ; std::vector pred; std::vector live, dead; IRegSet use, defs, input, output; bool is_entry = false; bool is_exit = false; int idx = -1; void analyze_liveliness_phase1(const std::vector& instructions); bool analyze_liveliness_phase2(std::vector& blocks, const std::vector& instructions); void analyze_liveliness_phase3(std::vector& blocks, const std::vector& instructions); std::string print(const std::vector& insts); std::string print_summary(); }; struct ControlFlowAnalysisCache { std::vector basic_blocks; }; void print_allocate_input(const AllocationInput& in); void print_result(const AllocationInput& in, const AllocationResult& result); void find_basic_blocks(ControlFlowAnalysisCache* cache, const AllocationInput& in);