mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 00:57:44 -04:00
210 lines
6.7 KiB
C++
210 lines
6.7 KiB
C++
#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 <unordered_set>
|
|
#include <vector>
|
|
|
|
#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<emitter::Register> clobber; // written, but safe to use as input/output
|
|
std::vector<emitter::Register> exclude; // written, unsafe to use for input/output
|
|
std::vector<IRegister> write; // results go in here
|
|
std::vector<IRegister> read; // inputs go in here
|
|
std::vector<int> 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<Op> 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<bool>& live,
|
|
const std::vector<Assignment>& 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<bool> m_live;
|
|
std::vector<Assignment> m_ass;
|
|
};
|
|
|
|
/*!
|
|
* Result of the allocate_registers algorithm
|
|
*/
|
|
struct AllocationResult {
|
|
bool ok = false; // did it work?
|
|
// std::vector<std::vector<Assignment>> assignment; // variable, instruction
|
|
std::vector<AssignmentRange> ass_as_ranges; // another format, maybe easier?
|
|
std::vector<emitter::Register> 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<StackOp> 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<RegAllocInstr> instructions; // all instructions in the function
|
|
std::vector<IRegConstraint> constraints; // all register constraints
|
|
std::unordered_set<int> force_on_stack_regs; // registers which must be on the stack
|
|
int max_vars = -1; // maximum register id.
|
|
std::vector<std::string> 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<int> instr_idx;
|
|
std::vector<int> succ;
|
|
std::vector<int> pred;
|
|
std::vector<IRegSet> 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<RegAllocInstr>& instructions);
|
|
bool analyze_liveliness_phase2(std::vector<RegAllocBasicBlock>& blocks,
|
|
const std::vector<RegAllocInstr>& instructions);
|
|
void analyze_liveliness_phase3(std::vector<RegAllocBasicBlock>& blocks,
|
|
const std::vector<RegAllocInstr>& instructions);
|
|
std::string print(const std::vector<RegAllocInstr>& insts);
|
|
std::string print_summary();
|
|
};
|
|
|
|
struct ControlFlowAnalysisCache {
|
|
std::vector<RegAllocBasicBlock> 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); |