mirror of
https://github.com/open-goal/jak-project
synced 2026-07-10 15:14:15 -04:00
[goalc] register allocator v2 (#731)
* clean up allocator interface to be simpler * working on functions without spills * working for all * fix missing includes for windows * more windows includes * initialize regs to zero so printing value unintiailized by game code is repeatable
This commit is contained in:
@@ -21,6 +21,8 @@ class Range {
|
||||
Range(const T& start, const T& end) : m_start(start), m_end(end) {}
|
||||
const T& first() const { return m_start; }
|
||||
const T& last() const { return m_end; }
|
||||
T& first() { return m_start; }
|
||||
T& last() { return m_end; }
|
||||
bool contains(T& val) const { return val >= m_start && val < m_end; }
|
||||
bool empty() const { return m_end <= m_start; }
|
||||
T size() const { return m_end - m_start; }
|
||||
|
||||
@@ -184,4 +184,6 @@
|
||||
- Multiple variables assigned to the same register using `:reg` in `rlet` (or overlapping with `self` in a behavior) will now be merged to a single variable instead of causing a compiler error. Variables will have their own type, but they will all be an alias of the same exact register.
|
||||
- Stack arrays of uint128 will now be 16-byte aligned instead of sometimes only 8.
|
||||
- Inline arrays of structures are now allowed with `stack-no-clear`.
|
||||
- Creating arrays on the stack now must be done with `stack-no-clear` as they are not memset to 0 or constructed in any way.
|
||||
- Creating arrays on the stack now must be done with `stack-no-clear` as they are not memset to 0 or constructed in any way.
|
||||
- The register allocator has been dramatically improved and generates ~5x fewer spill instructions and is able to eliminate more moves.
|
||||
- Added a `(print-debug-compiler-stats)` form to print out statistics related to register allocation and move elimination
|
||||
@@ -1262,6 +1262,14 @@
|
||||
(vf15 :class vf)
|
||||
)
|
||||
(init-vf0-vector)
|
||||
;; added to make the right most column always zero.
|
||||
;; it seems like they didn't care about these values
|
||||
;; but this makes the test repeatable, and there
|
||||
;; really could be anything here. This function is unused anyway
|
||||
(.xor.vf vf13 vf13 vf13)
|
||||
(.xor.vf vf14 vf14 vf14)
|
||||
(.xor.vf vf15 vf15 vf15)
|
||||
|
||||
(.lvf vf10 (&-> src vector 0 quad))
|
||||
(.lvf vf11 (&-> src vector 1 quad))
|
||||
(.lvf vf12 (&-> src vector 2 quad))
|
||||
|
||||
@@ -889,6 +889,8 @@
|
||||
(set! obj (the cpu-thread (-> obj process)))
|
||||
;; resume!
|
||||
(.jr temp)
|
||||
(.add a4 a4)
|
||||
(.add a5 a5)
|
||||
)
|
||||
(none)
|
||||
)
|
||||
@@ -2322,7 +2324,7 @@
|
||||
This stub remaps these saved registers to argument registers.
|
||||
It also creates a return trampoline to return-from-thread-dead"
|
||||
(declare (asm-func none)
|
||||
;(print-asm)
|
||||
;;(print-asm)
|
||||
)
|
||||
|
||||
(rlet ((s0 :reg rbx :type uint)
|
||||
@@ -2330,11 +2332,13 @@
|
||||
(s2 :reg r10 :type uint)
|
||||
(s3 :reg r11 :type uint)
|
||||
(s4 :reg r12 :type uint)
|
||||
(a0 :reg rdi :type uint) ; ok
|
||||
(a1 :reg rsi :type uint) ; ok
|
||||
(a2 :reg rdx :type uint) ; ok
|
||||
(a3 :reg rcx :type uint) ; ok
|
||||
(a0 :reg rdi :type uint) ; ok
|
||||
(a1 :reg rsi :type uint) ; ok
|
||||
(a2 :reg rdx :type uint) ; ok
|
||||
(a3 :reg rcx :type uint) ; ok
|
||||
(off :reg r15 :type uint)
|
||||
(a4 :reg r8 :type uint)
|
||||
(a5 :reg r9 :type uint)
|
||||
(temp :reg rax)
|
||||
)
|
||||
|
||||
@@ -2352,7 +2356,9 @@
|
||||
|
||||
(.add :color #f s0 off)
|
||||
(.jr :color #f s0)
|
||||
|
||||
|
||||
(.add a4 a4)
|
||||
(.add a5 a5)
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
@@ -39,8 +39,8 @@ add_library(compiler
|
||||
make/Tools.cpp
|
||||
regalloc/IRegister.cpp
|
||||
regalloc/Allocator.cpp
|
||||
regalloc/allocate.cpp
|
||||
regalloc/allocate_common.cpp
|
||||
regalloc/allocator_interface.cpp
|
||||
regalloc/Allocator_v2.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(compiler common Zydis)
|
||||
|
||||
@@ -17,6 +17,7 @@ class CodeGenerator {
|
||||
public:
|
||||
CodeGenerator(FileEnv* env, DebugInfo* debug_info);
|
||||
std::vector<u8> run(const TypeSystem* ts);
|
||||
emitter::ObjectGeneratorStats get_obj_stats() const { return m_gen.get_stats(); }
|
||||
|
||||
private:
|
||||
void do_function(FunctionEnv* env, int f_idx);
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
#include "IR.h"
|
||||
#include "common/link_types.h"
|
||||
#include "goalc/make/Tools.h"
|
||||
#include "goalc/regalloc/allocate.h"
|
||||
#include "goalc/regalloc/Allocator.h"
|
||||
#include "goalc/regalloc/Allocator_v2.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
|
||||
using namespace goos;
|
||||
@@ -198,6 +199,7 @@ Val* Compiler::compile_error_guard(const goos::Object& code, Env* env) {
|
||||
}
|
||||
|
||||
void Compiler::color_object_file(FileEnv* env) {
|
||||
int num_spills_in_file = 0;
|
||||
for (auto& f : env->functions()) {
|
||||
AllocationInput input;
|
||||
input.is_asm_function = f->is_asm_func;
|
||||
@@ -215,6 +217,7 @@ void Compiler::color_object_file(FileEnv* env) {
|
||||
input.max_vars = f->max_vars();
|
||||
input.constraints = f->constraints();
|
||||
input.stack_slots_for_stack_vars = f->stack_slots_used_for_stack_vars();
|
||||
input.function_name = f->name();
|
||||
|
||||
if (m_settings.debug_print_regalloc) {
|
||||
input.debug_settings.print_input = true;
|
||||
@@ -223,8 +226,31 @@ void Compiler::color_object_file(FileEnv* env) {
|
||||
input.debug_settings.allocate_log_level = 2;
|
||||
}
|
||||
|
||||
f->set_allocations(allocate_registers(input));
|
||||
m_debug_stats.total_funcs++;
|
||||
|
||||
auto regalloc_result_2 = allocate_registers_v2(input);
|
||||
|
||||
if (regalloc_result_2.ok) {
|
||||
if (regalloc_result_2.num_spilled_vars > 0) {
|
||||
// fmt::print("Function {} has {} spilled vars.\n", f->name(),
|
||||
// regalloc_result_2.num_spilled_vars);
|
||||
}
|
||||
num_spills_in_file += regalloc_result_2.num_spills;
|
||||
f->set_allocations(regalloc_result_2);
|
||||
} else {
|
||||
fmt::print(
|
||||
"Warning: function {} failed register allocation with the v2 allocator. Falling back to "
|
||||
"the v1 allocator.\n",
|
||||
f->name());
|
||||
m_debug_stats.funcs_requiring_v1_allocator++;
|
||||
auto regalloc_result = allocate_registers(input);
|
||||
m_debug_stats.num_spills_v1 += regalloc_result.num_spills;
|
||||
num_spills_in_file += regalloc_result.num_spills;
|
||||
f->set_allocations(regalloc_result);
|
||||
}
|
||||
}
|
||||
|
||||
m_debug_stats.num_spills += num_spills_in_file;
|
||||
}
|
||||
|
||||
std::vector<u8> Compiler::codegen_object_file(FileEnv* env) {
|
||||
@@ -239,6 +265,8 @@ std::vector<u8> Compiler::codegen_object_file(FileEnv* env) {
|
||||
fmt::print("{}\n", debug_info->disassemble_function_by_name(f->name(), &ok));
|
||||
}
|
||||
}
|
||||
auto stats = gen.get_obj_stats();
|
||||
m_debug_stats.num_moves_eliminated += stats.moves_eliminated;
|
||||
return result;
|
||||
} catch (std::exception& e) {
|
||||
throw_compiler_error_no_code("Error during codegen: {}", e.what());
|
||||
|
||||
@@ -79,6 +79,14 @@ class Compiler {
|
||||
std::unique_ptr<ReplWrapper> m_repl;
|
||||
MakeSystem m_make;
|
||||
|
||||
struct DebugStats {
|
||||
int num_spills = 0;
|
||||
int num_spills_v1 = 0;
|
||||
int num_moves_eliminated = 0;
|
||||
int total_funcs = 0;
|
||||
int funcs_requiring_v1_allocator = 0;
|
||||
} m_debug_stats;
|
||||
|
||||
std::set<std::string> lookup_symbol_infos_starting_with(const std::string& prefix) const;
|
||||
std::vector<SymbolInfo>* lookup_exact_name_info(const std::string& name) const;
|
||||
bool get_true_or_false(const goos::Object& form, const goos::Object& boolean);
|
||||
@@ -517,6 +525,9 @@ class Compiler {
|
||||
Env* env);
|
||||
Val* compile_load_project(const goos::Object& form, const goos::Object& rest, Env* env);
|
||||
Val* compile_make(const goos::Object& form, const goos::Object& rest, Env* env);
|
||||
Val* compile_print_debug_compiler_stats(const goos::Object& form,
|
||||
const goos::Object& rest,
|
||||
Env* env);
|
||||
|
||||
// ControlFlow
|
||||
Condition compile_condition(const goos::Object& condition, Env* env, bool invert);
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "common/type_system/TypeSpec.h"
|
||||
#include "goalc/regalloc/allocate.h"
|
||||
#include "goalc/regalloc/allocator_interface.h"
|
||||
#include "common/goos/Object.h"
|
||||
#include "StaticObject.h"
|
||||
#include "Label.h"
|
||||
|
||||
@@ -13,7 +13,7 @@ Register get_reg(const RegVal* rv, const AllocationResult& allocs, emitter::IR_R
|
||||
auto reg = rv->rlet_constraint().value();
|
||||
if (rv->ireg().id < int(range.size())) {
|
||||
auto& lr = range.at(rv->ireg().id);
|
||||
if (irec.ir_id >= lr.min && irec.ir_id <= lr.max) {
|
||||
if (lr.has_info_at(irec.ir_id)) {
|
||||
auto ass_reg = range.at(rv->ireg().id).get(irec.ir_id);
|
||||
if (ass_reg.kind == Assignment::Kind::REGISTER) {
|
||||
assert(ass_reg.reg == reg);
|
||||
@@ -41,7 +41,7 @@ int get_stack_offset(const RegVal* rv, const AllocationResult& allocs) {
|
||||
} else {
|
||||
assert(rv->forced_on_stack());
|
||||
auto& ass = allocs.ass_as_ranges.at(rv->ireg().id);
|
||||
auto stack_slot = allocs.get_slot_for_spill(ass.assignment.at(0).stack_slot);
|
||||
auto stack_slot = allocs.get_slot_for_spill(ass.stack_slot());
|
||||
assert(stack_slot >= 0);
|
||||
return stack_slot * 8;
|
||||
}
|
||||
@@ -103,6 +103,7 @@ void regset_common(emitter::ObjectGenerator* gen,
|
||||
if (src_class == RegClass::GPR_64 && dst_class == RegClass::GPR_64) {
|
||||
if (src_reg == dst_reg) {
|
||||
// eliminate move
|
||||
gen->count_eliminated_move();
|
||||
gen->add_instr(IGen::null(), irec);
|
||||
} else {
|
||||
gen->add_instr(IGen::mov_gpr64_gpr64(dst_reg, src_reg), irec);
|
||||
@@ -110,6 +111,7 @@ void regset_common(emitter::ObjectGenerator* gen,
|
||||
} else if (src_class == RegClass::FLOAT && dst_class == RegClass::FLOAT) {
|
||||
if (src_reg == dst_reg) {
|
||||
// eliminate move
|
||||
gen->count_eliminated_move();
|
||||
gen->add_instr(IGen::null(), irec);
|
||||
} else {
|
||||
gen->add_instr(IGen::mov_xmm32_xmm32(dst_reg, src_reg), irec);
|
||||
@@ -117,6 +119,7 @@ void regset_common(emitter::ObjectGenerator* gen,
|
||||
} else if (src_is_xmm128 && dst_is_xmm128) {
|
||||
if (src_reg == dst_reg) {
|
||||
// eliminate move
|
||||
gen->count_eliminated_move();
|
||||
gen->add_instr(IGen::null(), irec);
|
||||
} else {
|
||||
gen->add_instr(IGen::mov_vf_vf(dst_reg, src_reg), irec);
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <string>
|
||||
#include "CodeGenerator.h"
|
||||
#include "goalc/regalloc/allocate.h"
|
||||
#include "goalc/regalloc/allocator_interface.h"
|
||||
#include "Val.h"
|
||||
#include "goalc/emitter/ObjectGenerator.h"
|
||||
#include "goalc/emitter/Register.h"
|
||||
|
||||
@@ -153,6 +153,7 @@ const std::unordered_map<
|
||||
{"add-macro-to-autocomplete", &Compiler::compile_add_macro_to_autocomplete},
|
||||
{"load-project", &Compiler::compile_load_project},
|
||||
{"make", &Compiler::compile_make},
|
||||
{"print-debug-compiler-stats", &Compiler::compile_print_debug_compiler_stats},
|
||||
|
||||
// CONDITIONAL COMPILATION
|
||||
{"#cond", &Compiler::compile_gscond},
|
||||
|
||||
@@ -603,5 +603,21 @@ Val* Compiler::compile_make(const goos::Object& form, const goos::Object& rest,
|
||||
}
|
||||
|
||||
m_make.make(args.unnamed.at(0).as_string()->data, force, verbose);
|
||||
return get_none();
|
||||
}
|
||||
|
||||
Val* Compiler::compile_print_debug_compiler_stats(const goos::Object& form,
|
||||
const goos::Object& rest,
|
||||
Env*) {
|
||||
auto args = get_va(form, rest);
|
||||
va_check(form, args, {}, {});
|
||||
|
||||
fmt::print("Spill operations (total): {}\n", m_debug_stats.num_spills);
|
||||
fmt::print("Spill operations (v1 only): {}\n", m_debug_stats.num_spills_v1);
|
||||
fmt::print("Eliminated moves: {}\n", m_debug_stats.num_moves_eliminated);
|
||||
fmt::print("Total functions: {}\n", m_debug_stats.total_funcs);
|
||||
fmt::print("Functions requiring v1: {}\n", m_debug_stats.funcs_requiring_v1_allocator);
|
||||
fmt::print("Size of autocomplete prefix tree: {}\n", m_symbol_info.symbol_count());
|
||||
|
||||
return get_none();
|
||||
}
|
||||
@@ -206,14 +206,13 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
|
||||
for (u32 i = 0; i < lambda.params.size(); i++) {
|
||||
IRegConstraint constr;
|
||||
constr.instr_idx = 0; // constraint at function start
|
||||
auto ireg = new_func_env->make_ireg(
|
||||
auto ireg_arg = new_func_env->make_ireg(
|
||||
lambda.params.at(i).type, arg_regs.at(i).is_gpr() ? RegClass::GPR_64 : RegClass::INT_128);
|
||||
ireg->mark_as_settable();
|
||||
constr.ireg = ireg->ireg();
|
||||
ireg_arg->mark_as_settable();
|
||||
constr.ireg = ireg_arg->ireg();
|
||||
constr.desired_register = arg_regs.at(i);
|
||||
new_func_env->params[lambda.params.at(i).name] = ireg;
|
||||
new_func_env->constrain(constr);
|
||||
reset_args_for_coloring.push_back(ireg);
|
||||
reset_args_for_coloring.push_back(ireg_arg);
|
||||
}
|
||||
|
||||
if (args.has_named("behavior")) {
|
||||
@@ -244,6 +243,14 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
|
||||
func_block_env->end_label = Label(new_func_env.get());
|
||||
func_block_env->emit(std::make_unique<IR_ValueReset>(reset_args_for_coloring));
|
||||
|
||||
for (u32 i = 0; i < lambda.params.size(); i++) {
|
||||
auto ireg = new_func_env->make_ireg(
|
||||
lambda.params.at(i).type, arg_regs.at(i).is_gpr() ? RegClass::GPR_64 : RegClass::INT_128);
|
||||
ireg->mark_as_settable();
|
||||
new_func_env->params[lambda.params.at(i).name] = ireg;
|
||||
new_func_env->emit_ir<IR_RegSet>(ireg, reset_args_for_coloring.at(i));
|
||||
}
|
||||
|
||||
// compile the function, iterating through the body.
|
||||
Val* result = nullptr;
|
||||
bool first_thing = true;
|
||||
|
||||
@@ -419,6 +419,10 @@ Val* Compiler::compile_div(const goos::Object& form, const goos::Object& rest, E
|
||||
IntegerMathKind::UDIV_32, result,
|
||||
to_math_type(form, val, math_type, env)->to_gpr(env)));
|
||||
}
|
||||
|
||||
auto result_moved = env->make_gpr(first_type);
|
||||
env->emit_ir<IR_RegSet>(result_moved, result);
|
||||
return result_moved;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -215,15 +215,18 @@ Val* Compiler::generate_inspector_for_structure_type(const goos::Object& form,
|
||||
method_env->set_segment(DEBUG_SEGMENT);
|
||||
|
||||
// Create a register which will hold the input to the inspect method
|
||||
auto input = method_env->make_gpr(structure_type->get_name());
|
||||
auto input_arg = method_env->make_gpr(structure_type->get_name());
|
||||
// "Constrain" this register to be the register that the function argument is passed in
|
||||
IRegConstraint constraint;
|
||||
constraint.instr_idx = 0; // constraint at the start of the function
|
||||
constraint.ireg = input->ireg(); // constrain this register
|
||||
constraint.instr_idx = 0; // constraint at the start of the function
|
||||
constraint.ireg = input_arg->ireg(); // constrain this register
|
||||
constraint.desired_register = emitter::gRegInfo.get_gpr_arg_reg(0); // to the first argument
|
||||
method_env->constrain(constraint);
|
||||
// Inform the compiler that `input`'s value will be written to `rdi` (first arg register)
|
||||
method_env->emit(std::make_unique<IR_ValueReset>(std::vector<RegVal*>{input}));
|
||||
method_env->emit(std::make_unique<IR_ValueReset>(std::vector<RegVal*>{input_arg}));
|
||||
|
||||
auto input = method_env->make_gpr(structure_type->get_name());
|
||||
method_env->emit_ir<IR_RegSet>(input, input_arg);
|
||||
|
||||
// there's a special case for children of process.
|
||||
if (m_ts.fully_defined_type_exists("process") &&
|
||||
@@ -291,11 +294,11 @@ Val* Compiler::generate_inspector_for_bitfield_type(const goos::Object& form,
|
||||
method_env->set_segment(DEBUG_SEGMENT);
|
||||
|
||||
// Create a register which will hold the input to the inspect method
|
||||
auto input = method_env->make_gpr(bitfield_type->get_name());
|
||||
auto input_arg = method_env->make_gpr(bitfield_type->get_name());
|
||||
// "Constrain" this register to be the register that the function argument is passed in
|
||||
IRegConstraint constraint;
|
||||
constraint.instr_idx = 0; // constraint at the start of the function
|
||||
constraint.ireg = input->ireg(); // constrain this register
|
||||
constraint.instr_idx = 0; // constraint at the start of the function
|
||||
constraint.ireg = input_arg->ireg(); // constrain this register
|
||||
if (bitfield_128) {
|
||||
constraint.desired_register = emitter::gRegInfo.get_xmm_arg_reg(0); // to the first argument
|
||||
} else {
|
||||
@@ -304,7 +307,10 @@ Val* Compiler::generate_inspector_for_bitfield_type(const goos::Object& form,
|
||||
|
||||
method_env->constrain(constraint);
|
||||
// Inform the compiler that `input`'s value will be written to `rdi` (first arg register)
|
||||
method_env->emit(std::make_unique<IR_ValueReset>(std::vector<RegVal*>{input}));
|
||||
method_env->emit(std::make_unique<IR_ValueReset>(std::vector<RegVal*>{input_arg}));
|
||||
|
||||
auto input = method_env->make_gpr(bitfield_type->get_name());
|
||||
method_env->emit_ir<IR_RegSet>(input, input_arg);
|
||||
|
||||
RegVal* type_name =
|
||||
compile_get_sym_obj(bitfield_type->get_name(), method_env.get())->to_gpr(method_env.get());
|
||||
@@ -473,14 +479,14 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _
|
||||
for (u32 i = 0; i < lambda.params.size(); i++) {
|
||||
IRegConstraint constr;
|
||||
constr.instr_idx = 0; // constraint at function start
|
||||
auto ireg = new_func_env->make_ireg(
|
||||
auto ireg_arg = new_func_env->make_ireg(
|
||||
lambda.params.at(i).type, arg_regs.at(i).is_gpr() ? RegClass::GPR_64 : RegClass::INT_128);
|
||||
ireg->mark_as_settable();
|
||||
constr.ireg = ireg->ireg();
|
||||
ireg_arg->mark_as_settable();
|
||||
constr.ireg = ireg_arg->ireg();
|
||||
constr.desired_register = arg_regs.at(i);
|
||||
new_func_env->params[lambda.params.at(i).name] = ireg;
|
||||
|
||||
new_func_env->constrain(constr);
|
||||
reset_args_for_coloring.push_back(ireg);
|
||||
reset_args_for_coloring.push_back(ireg_arg);
|
||||
}
|
||||
|
||||
auto method_info = m_ts.lookup_method(symbol_string(type_name), symbol_string(method_name));
|
||||
@@ -511,6 +517,14 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _
|
||||
func_block_env->end_label = Label(new_func_env.get());
|
||||
func_block_env->emit(std::make_unique<IR_ValueReset>(reset_args_for_coloring));
|
||||
|
||||
for (u32 i = 0; i < lambda.params.size(); i++) {
|
||||
auto ireg = new_func_env->make_ireg(
|
||||
lambda.params.at(i).type, arg_regs.at(i).is_gpr() ? RegClass::GPR_64 : RegClass::INT_128);
|
||||
ireg->mark_as_settable();
|
||||
new_func_env->params[lambda.params.at(i).name] = ireg;
|
||||
new_func_env->emit_ir<IR_RegSet>(ireg, reset_args_for_coloring.at(i));
|
||||
}
|
||||
|
||||
// compile the function!
|
||||
Val* result = nullptr;
|
||||
bool first_thing = true;
|
||||
|
||||
@@ -615,4 +615,12 @@ std::vector<u8> ObjectGenerator::generate_header_v3() {
|
||||
push_data<uint32_t>(64 + 4 + total_link_size, result); // todo, make these numbers less magic.
|
||||
return result;
|
||||
}
|
||||
|
||||
ObjectGeneratorStats ObjectGenerator::get_stats() const {
|
||||
return m_stats;
|
||||
}
|
||||
|
||||
void ObjectGenerator::count_eliminated_move() {
|
||||
m_stats.moves_eliminated++;
|
||||
}
|
||||
} // namespace emitter
|
||||
@@ -5,9 +5,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef JAK_OBJECTGENERATOR_H
|
||||
#define JAK_OBJECTGENERATOR_H
|
||||
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <string>
|
||||
@@ -56,6 +53,10 @@ struct StaticRecord {
|
||||
int static_id = -1;
|
||||
};
|
||||
|
||||
struct ObjectGeneratorStats {
|
||||
int moves_eliminated = 0;
|
||||
};
|
||||
|
||||
class ObjectGenerator {
|
||||
public:
|
||||
ObjectGenerator() = default;
|
||||
@@ -90,6 +91,8 @@ class ObjectGenerator {
|
||||
int offset);
|
||||
void link_instruction_to_function(const InstructionRecord& instr,
|
||||
const FunctionRecord& target_func);
|
||||
ObjectGeneratorStats get_stats() const;
|
||||
void count_eliminated_move();
|
||||
|
||||
private:
|
||||
void handle_temp_static_type_links(int seg);
|
||||
@@ -226,7 +229,7 @@ class ObjectGenerator {
|
||||
seg_vector<PointerLink> m_pointer_links_by_seg;
|
||||
|
||||
std::vector<FunctionRecord> m_all_function_records;
|
||||
|
||||
ObjectGeneratorStats m_stats;
|
||||
};
|
||||
} // namespace emitter
|
||||
|
||||
#endif // JAK_OBJECTGENERATOR_H
|
||||
|
||||
+153
-218
@@ -3,87 +3,17 @@
|
||||
* Implementation of register allocation algorithms
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "Allocator.h"
|
||||
#include "goalc/regalloc/allocator_interface.h"
|
||||
|
||||
/*!
|
||||
* Find basic blocks and add block link info.
|
||||
*/
|
||||
void find_basic_blocks(RegAllocCache* cache, const AllocationInput& in) {
|
||||
std::vector<int> dividers;
|
||||
|
||||
dividers.push_back(0);
|
||||
dividers.push_back(in.instructions.size());
|
||||
|
||||
// loop over instructions, finding jump targets
|
||||
for (uint32_t i = 0; i < in.instructions.size(); i++) {
|
||||
const auto& instr = in.instructions[i];
|
||||
if (!instr.jumps.empty()) {
|
||||
dividers.push_back(i + 1);
|
||||
for (auto dest : instr.jumps) {
|
||||
dividers.push_back(dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sort dividers, and make blocks
|
||||
std::sort(dividers.begin(), dividers.end(), [](int a, int b) { return a < b; });
|
||||
|
||||
for (uint32_t i = 0; i < dividers.size() - 1; i++) {
|
||||
if (dividers[i] != dividers[i + 1]) {
|
||||
// new basic block!
|
||||
RegAllocBasicBlock block;
|
||||
for (int j = dividers[i]; j < dividers[i + 1]; j++) {
|
||||
block.instr_idx.push_back(j);
|
||||
}
|
||||
|
||||
block.idx = cache->basic_blocks.size();
|
||||
cache->basic_blocks.push_back(block);
|
||||
}
|
||||
}
|
||||
|
||||
if (!cache->basic_blocks.empty()) {
|
||||
cache->basic_blocks.front().is_entry = true;
|
||||
cache->basic_blocks.back().is_exit = true;
|
||||
}
|
||||
|
||||
auto find_basic_block_to_target = [&](int instr) {
|
||||
bool found = false;
|
||||
uint32_t result = -1;
|
||||
for (uint32_t i = 0; i < cache->basic_blocks.size(); i++) {
|
||||
if (!cache->basic_blocks[i].instr_idx.empty() &&
|
||||
cache->basic_blocks[i].instr_idx.front() == instr) {
|
||||
assert(!found);
|
||||
found = true;
|
||||
result = i;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
printf("[RegAlloc Error] couldn't find basic block beginning with instr %d of %d\n", instr,
|
||||
int(in.instructions.size()));
|
||||
}
|
||||
assert(found);
|
||||
return result;
|
||||
};
|
||||
|
||||
// link blocks
|
||||
for (auto& block : cache->basic_blocks) {
|
||||
assert(!block.instr_idx.empty());
|
||||
auto& last_instr = in.instructions.at(block.instr_idx.back());
|
||||
if (last_instr.fallthrough) {
|
||||
// try to link to next block:
|
||||
int next_idx = block.idx + 1;
|
||||
if (next_idx < (int)cache->basic_blocks.size()) {
|
||||
cache->basic_blocks.at(next_idx).pred.push_back(block.idx);
|
||||
block.succ.push_back(next_idx);
|
||||
}
|
||||
}
|
||||
for (auto target : last_instr.jumps) {
|
||||
cache->basic_blocks.at(find_basic_block_to_target(target)).pred.push_back(block.idx);
|
||||
block.succ.push_back(find_basic_block_to_target(target));
|
||||
}
|
||||
std::string LiveInfo::print_assignment() {
|
||||
std::string result = "Assignment for var " + std::to_string(var) + "\n";
|
||||
for (uint32_t i = 0; i < assignment.size(); i++) {
|
||||
result += fmt::format("i[{:3d}] {}\n", i + min, assignment.at(i).to_string());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -96,7 +26,7 @@ void compute_live_ranges(RegAllocCache* cache, const AllocationInput& in) {
|
||||
cache->live_ranges.resize(cache->max_var, LiveInfo(in.instructions.size(), 0));
|
||||
|
||||
// now compute the ranges
|
||||
for (auto& block : cache->basic_blocks) {
|
||||
for (auto& block : cache->control_flow.basic_blocks) {
|
||||
// from var use
|
||||
for (auto instr_id : block.instr_idx) {
|
||||
auto& inst = in.instructions.at(instr_id);
|
||||
@@ -147,7 +77,7 @@ void analyze_liveliness(RegAllocCache* cache, const AllocationInput& in) {
|
||||
}
|
||||
|
||||
// phase 1
|
||||
for (auto& block : cache->basic_blocks) {
|
||||
for (auto& block : cache->control_flow.basic_blocks) {
|
||||
block.live.resize(block.instr_idx.size());
|
||||
block.dead.resize(block.instr_idx.size());
|
||||
block.analyze_liveliness_phase1(in.instructions);
|
||||
@@ -157,16 +87,16 @@ void analyze_liveliness(RegAllocCache* cache, const AllocationInput& in) {
|
||||
bool changed = false;
|
||||
do {
|
||||
changed = false;
|
||||
for (auto& block : cache->basic_blocks) {
|
||||
if (block.analyze_liveliness_phase2(cache->basic_blocks, in.instructions)) {
|
||||
for (auto& block : cache->control_flow.basic_blocks) {
|
||||
if (block.analyze_liveliness_phase2(cache->control_flow.basic_blocks, in.instructions)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
} while (changed);
|
||||
|
||||
// phase 3
|
||||
for (auto& block : cache->basic_blocks) {
|
||||
block.analyze_liveliness_phase3(cache->basic_blocks, in.instructions);
|
||||
for (auto& block : cache->control_flow.basic_blocks) {
|
||||
block.analyze_liveliness_phase3(cache->control_flow.basic_blocks, in.instructions);
|
||||
}
|
||||
|
||||
// phase 4
|
||||
@@ -194,141 +124,6 @@ void analyze_liveliness(RegAllocCache* cache, const AllocationInput& in) {
|
||||
}
|
||||
}
|
||||
|
||||
void RegAllocBasicBlock::analyze_liveliness_phase1(const std::vector<RegAllocInstr>& instructions) {
|
||||
for (int i = instr_idx.size(); i-- > 0;) {
|
||||
auto ii = instr_idx.at(i);
|
||||
auto& instr = instructions.at(ii);
|
||||
auto& lv = live.at(i);
|
||||
auto& dd = dead.at(i);
|
||||
|
||||
// make all read live out
|
||||
lv.clear();
|
||||
for (auto& x : instr.read) {
|
||||
lv.insert(x.id);
|
||||
}
|
||||
|
||||
// kill things which are overwritten
|
||||
dd.clear();
|
||||
for (auto& x : instr.write) {
|
||||
if (!lv[x.id]) {
|
||||
dd.insert(x.id);
|
||||
}
|
||||
}
|
||||
|
||||
use.bitwise_and_not(dd);
|
||||
use.bitwise_or(lv);
|
||||
|
||||
defs.bitwise_and_not(lv);
|
||||
defs.bitwise_or(dd);
|
||||
}
|
||||
}
|
||||
|
||||
bool RegAllocBasicBlock::analyze_liveliness_phase2(std::vector<RegAllocBasicBlock>& blocks,
|
||||
const std::vector<RegAllocInstr>& instructions) {
|
||||
(void)instructions;
|
||||
bool changed = false;
|
||||
auto out = defs;
|
||||
|
||||
for (auto s : succ) {
|
||||
out.bitwise_or(blocks.at(s).input);
|
||||
}
|
||||
|
||||
IRegSet in = use;
|
||||
IRegSet temp = out;
|
||||
temp.bitwise_and_not(defs);
|
||||
in.bitwise_or(temp);
|
||||
|
||||
if (in != input || out != output) {
|
||||
changed = true;
|
||||
input = in;
|
||||
output = out;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
void RegAllocBasicBlock::analyze_liveliness_phase3(std::vector<RegAllocBasicBlock>& blocks,
|
||||
const std::vector<RegAllocInstr>& instructions) {
|
||||
(void)instructions;
|
||||
IRegSet live_local;
|
||||
for (auto s : succ) {
|
||||
live_local.bitwise_or(blocks.at(s).input);
|
||||
}
|
||||
|
||||
for (int i = instr_idx.size(); i-- > 0;) {
|
||||
auto& lv = live.at(i);
|
||||
auto& dd = dead.at(i);
|
||||
|
||||
IRegSet new_live = live_local;
|
||||
new_live.bitwise_and_not(dd);
|
||||
new_live.bitwise_or(lv);
|
||||
|
||||
lv = live_local;
|
||||
live_local = new_live;
|
||||
}
|
||||
}
|
||||
|
||||
std::string RegAllocBasicBlock::print_summary() {
|
||||
std::string result = "block " + std::to_string(idx) + "\nsucc: ";
|
||||
for (auto s : succ) {
|
||||
result += std::to_string(s) + " ";
|
||||
}
|
||||
result += "\npred: ";
|
||||
for (auto p : pred) {
|
||||
result += std::to_string(p) + " ";
|
||||
}
|
||||
result += "\nuse: ";
|
||||
for (int x = 0; x < use.size(); x++) {
|
||||
if (use[x]) {
|
||||
result += std::to_string(x) + " ";
|
||||
}
|
||||
}
|
||||
result += "\ndef: ";
|
||||
for (int x = 0; x < defs.size(); x++) {
|
||||
if (defs[x]) {
|
||||
result += std::to_string(x) + " ";
|
||||
}
|
||||
}
|
||||
result += "\ninput: ";
|
||||
for (int x = 0; x < input.size(); x++) {
|
||||
if (input[x]) {
|
||||
result += std::to_string(x) + " ";
|
||||
}
|
||||
}
|
||||
result += "\noutput: ";
|
||||
for (int x = 0; x < output.size(); x++) {
|
||||
if (output[x]) {
|
||||
result += std::to_string(x) + " ";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string RegAllocBasicBlock::print(const std::vector<RegAllocInstr>& insts) {
|
||||
std::string result = print_summary() + "\n";
|
||||
int k = 0;
|
||||
for (auto instr : instr_idx) {
|
||||
std::string line = insts.at(instr).print();
|
||||
constexpr int pad_len = 30;
|
||||
if (line.length() < pad_len) {
|
||||
// line.insert(line.begin(), pad_len - line.length(), ' ');
|
||||
line.append(pad_len - line.length(), ' ');
|
||||
}
|
||||
|
||||
result += " " + line + " live: ";
|
||||
for (int x = 0; x < live.at(k).size(); x++) {
|
||||
if (live.at(k)[x]) {
|
||||
result += std::to_string(x) + " ";
|
||||
}
|
||||
}
|
||||
result += "\n";
|
||||
|
||||
k++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Assign registers which are constrained. If constraints are inconsistent, won't succeed in
|
||||
* satisfying them (of course), and won't error either. Use check_constrained_alloc to confirm
|
||||
@@ -794,6 +589,12 @@ bool try_spill_coloring(int var, RegAllocCache* cache, const AllocationInput& in
|
||||
|
||||
if (bonus.load || bonus.store) {
|
||||
cache->stack_ops.at(instr).ops.push_back(bonus);
|
||||
if (bonus.load) {
|
||||
cache->stats.num_spill_ops++;
|
||||
}
|
||||
if (bonus.store) {
|
||||
cache->stats.num_spill_ops++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -910,3 +711,137 @@ bool run_allocator(RegAllocCache* cache, const AllocationInput& in, int debug_tr
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
/*!
|
||||
* Print out the state of the RegAllocCache after doing analysis.
|
||||
*/
|
||||
void print_analysis(const AllocationInput& in, RegAllocCache* cache) {
|
||||
fmt::print("[RegAlloc] Basic Blocks\n");
|
||||
fmt::print("-----------------------------------------------------------------\n");
|
||||
for (auto& b : cache->control_flow.basic_blocks) {
|
||||
fmt::print("{}\n", b.print(in.instructions));
|
||||
}
|
||||
|
||||
printf("[RegAlloc] Alive Info\n");
|
||||
printf("-----------------------------------------------------------------\n");
|
||||
// align to where we start putting live stuff
|
||||
printf(" %30s ", "");
|
||||
for (int i = 0; i < cache->max_var; i++) {
|
||||
printf("%2d ", i);
|
||||
}
|
||||
printf("\n");
|
||||
printf("_________________________________________________________________\n");
|
||||
for (uint32_t i = 0; i < in.instructions.size(); i++) {
|
||||
std::vector<bool> ids_live;
|
||||
std::string lives;
|
||||
|
||||
ids_live.resize(cache->max_var, false);
|
||||
|
||||
for (int j = 0; j < cache->max_var; j++) {
|
||||
if (cache->live_ranges.at(j).is_live_at_instr(i)) {
|
||||
ids_live.at(j) = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t j = 0; j < ids_live.size(); j++) {
|
||||
if (ids_live[j]) {
|
||||
char buff[256];
|
||||
sprintf(buff, "%2d ", (int)j);
|
||||
lives.append(buff);
|
||||
} else {
|
||||
lives.append(".. ");
|
||||
}
|
||||
}
|
||||
|
||||
if (in.debug_instruction_names.size() == in.instructions.size()) {
|
||||
std::string code_str = in.debug_instruction_names.at(i);
|
||||
if (code_str.length() >= 50) {
|
||||
code_str = code_str.substr(0, 48);
|
||||
code_str.push_back('~');
|
||||
}
|
||||
printf("[%03d] %30s -> %s\n", (int)i, code_str.c_str(), lives.c_str());
|
||||
} else {
|
||||
printf("[%03d] %30s -> %s\n", (int)i, "???", lives.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/*!
|
||||
* The top-level register allocation algorithm!
|
||||
*/
|
||||
AllocationResult allocate_registers(const AllocationInput& input) {
|
||||
AllocationResult result;
|
||||
RegAllocCache cache;
|
||||
cache.is_asm_func = input.is_asm_function;
|
||||
|
||||
// if desired, print input for debugging.
|
||||
if (input.debug_settings.print_input) {
|
||||
print_allocate_input(input);
|
||||
}
|
||||
|
||||
// first step is analysis
|
||||
find_basic_blocks(&cache.control_flow, input);
|
||||
analyze_liveliness(&cache, input);
|
||||
if (input.debug_settings.print_analysis) {
|
||||
print_analysis(input, &cache);
|
||||
}
|
||||
|
||||
// do constraints first, to get them out of the way
|
||||
do_constrained_alloc(&cache, input, input.debug_settings.trace_debug_constraints);
|
||||
// the user may have specified invalid constraints, so we should attempt to find conflicts now
|
||||
// rather than having the register allocation mysteriously fail later on or silently ignore a
|
||||
// constraint.
|
||||
if (!check_constrained_alloc(&cache, input)) {
|
||||
result.ok = false;
|
||||
fmt::print("[RegAlloc Error] Register allocation has failed due to bad constraints.\n");
|
||||
return result;
|
||||
}
|
||||
|
||||
// do the allocations!
|
||||
if (!run_allocator(&cache, input, input.debug_settings.allocate_log_level)) {
|
||||
result.ok = false;
|
||||
fmt::print("[RegAlloc Error] Register allocation has failed.\n");
|
||||
return result;
|
||||
}
|
||||
|
||||
// prepare the result
|
||||
result.ok = true;
|
||||
result.needs_aligned_stack_for_spills = cache.used_stack;
|
||||
result.stack_slots_for_spills = cache.current_stack_slot;
|
||||
result.stack_slots_for_vars = input.stack_slots_for_stack_vars;
|
||||
|
||||
// check for use of saved registers
|
||||
for (auto sr : emitter::gRegInfo.get_all_saved()) {
|
||||
bool uses_sr = false;
|
||||
for (auto& lr : cache.live_ranges) {
|
||||
for (int instr_idx = lr.min; instr_idx <= lr.max; instr_idx++) {
|
||||
if (lr.get(instr_idx).reg == sr) {
|
||||
uses_sr = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (uses_sr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (uses_sr) {
|
||||
result.used_saved_regs.push_back(sr);
|
||||
}
|
||||
}
|
||||
// result.ass_as_ranges = std::move(cache.live_ranges);
|
||||
for (auto& lr : cache.live_ranges) {
|
||||
result.ass_as_ranges.push_back(AssignmentRange(lr.min, lr.is_alive, lr.assignment));
|
||||
}
|
||||
result.stack_ops = std::move(cache.stack_ops);
|
||||
|
||||
// final result print
|
||||
if (input.debug_settings.print_result) {
|
||||
print_result(input, result);
|
||||
}
|
||||
|
||||
result.num_spills = cache.stats.num_spill_ops;
|
||||
|
||||
return result;
|
||||
}
|
||||
+201
-24
@@ -1,34 +1,208 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef JAK_ALLOCATOR_H
|
||||
#define JAK_ALLOCATOR_H
|
||||
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include "IRegSet.h"
|
||||
#include <unordered_map>
|
||||
#include "IRegister.h"
|
||||
#include "allocate.h"
|
||||
#include "allocator_interface.h"
|
||||
|
||||
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();
|
||||
// with this on, gaps in usage of registers allow other variables to steal registers.
|
||||
// this reduces stack spills/moves, but may make register allocation slower.
|
||||
constexpr bool enable_fancy_coloring = true;
|
||||
|
||||
// will attempt to allocate in a way to reduce the number of moves.
|
||||
constexpr bool move_eliminator = true;
|
||||
|
||||
constexpr bool allow_read_write_same_reg = true;
|
||||
|
||||
// Indication of where a variable is live and what assignment it has at each point in the range.
|
||||
struct LiveInfo {
|
||||
public:
|
||||
LiveInfo(int start, int end) : min(start), max(end) {}
|
||||
// min, max are inclusive.
|
||||
// meaning the variable written for the first time at min, and read for the last time at max.
|
||||
int min, max;
|
||||
|
||||
std::vector<bool> is_alive;
|
||||
std::vector<int> indices_of_alive;
|
||||
|
||||
// which variable is this?
|
||||
int var = -1;
|
||||
|
||||
// have we actually seen this variable in the code?
|
||||
bool seen = false;
|
||||
|
||||
// does this variable have a constraint?
|
||||
bool has_constraint = false;
|
||||
|
||||
// the assignment of this variable at each instruction in [min, max]
|
||||
std::vector<Assignment> assignment;
|
||||
|
||||
// a hint on where to put this variable.
|
||||
Assignment best_hint;
|
||||
|
||||
/*!
|
||||
* Add an instruction id where this variable is live.
|
||||
*/
|
||||
void add_live_instruction(int value) {
|
||||
assert(value >= 0);
|
||||
if (value > max)
|
||||
max = value;
|
||||
if (value < min)
|
||||
min = value;
|
||||
indices_of_alive.push_back(value);
|
||||
// remember that this variable is actually used
|
||||
seen = true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Is the given instruction contained in the live range?
|
||||
*/
|
||||
bool is_live_at_instr(int value) const {
|
||||
if (value >= min && value <= max) {
|
||||
if (enable_fancy_coloring) {
|
||||
return is_alive.at(value - min);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Are we alive at idx, but not alive at idx - 1 (or idx - 1 doesn't exist)
|
||||
*/
|
||||
bool becomes_live_at_instr(int idx) {
|
||||
if (enable_fancy_coloring) {
|
||||
if (idx == min)
|
||||
return true;
|
||||
if (idx < min || idx > max)
|
||||
return false;
|
||||
assert(idx > min);
|
||||
return is_alive.at(idx - min) && !is_alive.at(idx - min - 1);
|
||||
} else {
|
||||
return idx == min;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Are we alive at idx, but not alive at idx + 1 (or idx + 1 doesn't exist)
|
||||
*/
|
||||
bool dies_next_at_instr(int idx) {
|
||||
if (enable_fancy_coloring) {
|
||||
if (idx == max)
|
||||
return true;
|
||||
if (idx < min || idx > max)
|
||||
return false;
|
||||
assert(idx < max);
|
||||
return is_alive.at(idx - min) && !is_alive.at(idx - min + 1);
|
||||
} else {
|
||||
return idx == max;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Resize Live Range after instructions have been added. Do this before assigning.
|
||||
*/
|
||||
void prepare_for_allocation(int id) {
|
||||
var = id;
|
||||
if (!seen)
|
||||
return; // don't do any prep for a variable which isn't used.
|
||||
assert(max - min >= 0);
|
||||
assignment.resize(max - min + 1);
|
||||
is_alive.resize(max - min + 1);
|
||||
for (auto& x : indices_of_alive) {
|
||||
is_alive.at(x - min) = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Lock an assignment at a given instruction.
|
||||
* Will overwrite any previous assignment here
|
||||
* Will set best_hint to this assignment.
|
||||
*/
|
||||
void constrain_at_one(int id, emitter::Register reg) {
|
||||
assert(id >= min && id <= max);
|
||||
Assignment ass;
|
||||
ass.reg = reg;
|
||||
ass.kind = Assignment::Kind::REGISTER;
|
||||
assignment.at(id - min) = ass;
|
||||
has_constraint = true;
|
||||
best_hint = ass;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Lock an assignment everywhere.
|
||||
*/
|
||||
void constrain_everywhere(emitter::Register reg) {
|
||||
Assignment ass;
|
||||
ass.reg = reg;
|
||||
ass.kind = Assignment::Kind::REGISTER;
|
||||
for (int i = min; i <= max; i++) {
|
||||
assignment.at(i - min) = ass;
|
||||
}
|
||||
has_constraint = true;
|
||||
best_hint = ass;
|
||||
}
|
||||
|
||||
/*!
|
||||
* At the given instruction, does the given assignment conflict with this one?
|
||||
*/
|
||||
bool conflicts_at(int id, Assignment ass) {
|
||||
assert(id >= min && id <= max);
|
||||
return assignment.at(id - min).occupies_same_reg(ass);
|
||||
}
|
||||
|
||||
/*!
|
||||
* At the given instruction, does the given assignment conflict with this one?
|
||||
*/
|
||||
bool conflicts_at(int id, emitter::Register reg) {
|
||||
assert(id >= min && id <= max);
|
||||
Assignment ass;
|
||||
ass.reg = reg;
|
||||
ass.kind = Assignment::Kind::REGISTER;
|
||||
return assignment.at(id - min).occupies_same_reg(ass);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Assign variable to the given assignment at all instructions
|
||||
* Throws if this would require modifying a currently set assignment.
|
||||
*/
|
||||
void assign_no_overwrite(Assignment ass) {
|
||||
assert(seen);
|
||||
assert(ass.is_assigned());
|
||||
for (int i = min; i <= max; i++) {
|
||||
auto& a = assignment.at(i - min);
|
||||
if (a.is_assigned() && !(a.occupies_same_reg(ass))) {
|
||||
throw std::runtime_error("assign_no_overwrite failed!");
|
||||
} else {
|
||||
a = ass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the assignment at the given instruction.
|
||||
*/
|
||||
const Assignment& get(int id) const {
|
||||
assert(id >= min && id <= max);
|
||||
return assignment.at(id - min);
|
||||
}
|
||||
|
||||
int size() const { return 1 + max - min; }
|
||||
|
||||
bool overlaps(const LiveInfo& other) const {
|
||||
auto overlap_min = std::max(min, other.min);
|
||||
auto overlap_max = std::min(max, other.max);
|
||||
return overlap_min <= overlap_max;
|
||||
}
|
||||
|
||||
std::string print_assignment();
|
||||
};
|
||||
|
||||
struct RegAllocCache {
|
||||
std::vector<RegAllocBasicBlock> basic_blocks;
|
||||
ControlFlowAnalysisCache control_flow;
|
||||
std::vector<LiveInfo> live_ranges;
|
||||
int max_var = -1;
|
||||
std::vector<bool> was_colored;
|
||||
@@ -39,13 +213,16 @@ struct RegAllocCache {
|
||||
bool used_stack = false;
|
||||
bool is_asm_func = false;
|
||||
|
||||
struct Stats {
|
||||
int num_spill_ops = 0;
|
||||
} stats;
|
||||
|
||||
std::vector<std::vector<int>> live_ranges_by_instr;
|
||||
};
|
||||
|
||||
void find_basic_blocks(RegAllocCache* cache, const AllocationInput& in);
|
||||
void find_basic_blocks(ControlFlowAnalysisCache* cache, const AllocationInput& in);
|
||||
void analyze_liveliness(RegAllocCache* cache, const AllocationInput& in);
|
||||
void do_constrained_alloc(RegAllocCache* cache, const AllocationInput& in, bool trace_debug);
|
||||
bool check_constrained_alloc(RegAllocCache* cache, const AllocationInput& in);
|
||||
bool run_allocator(RegAllocCache* cache, const AllocationInput& in, int debug_trace);
|
||||
|
||||
#endif // JAK_ALLOCATOR_H
|
||||
AllocationResult allocate_registers(const AllocationInput& input);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "goalc/regalloc/allocator_interface.h"
|
||||
|
||||
// Allocator v2's interface
|
||||
AllocationResult allocate_registers_v2(const AllocationInput& input);
|
||||
@@ -1,260 +0,0 @@
|
||||
/*!
|
||||
* @file allocate.cpp
|
||||
* Runs the register allocator.
|
||||
*/
|
||||
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "allocate.h"
|
||||
#include "Allocator.h"
|
||||
|
||||
namespace {
|
||||
/*!
|
||||
* Print out the input data for debugging.
|
||||
*/
|
||||
void print_allocate_input(const AllocationInput& in) {
|
||||
fmt::print("[RegAlloc] Debug Input Program:\n");
|
||||
if (in.instructions.size() == in.debug_instruction_names.size()) {
|
||||
for (size_t i = 0; i < in.instructions.size(); i++) {
|
||||
// fmt::print(" [{}] {} -> {}\n", in.debug_instruction_names.at(i),
|
||||
// in.instructions.at(i).print());
|
||||
fmt::print(" [{:3d}] {:30} -> {:30}\n", i, in.debug_instruction_names.at(i),
|
||||
in.instructions.at(i).print());
|
||||
}
|
||||
} else {
|
||||
for (const auto& instruction : in.instructions) {
|
||||
fmt::print(" [{:3d}] {}\n", instruction.print());
|
||||
}
|
||||
}
|
||||
fmt::print("[RegAlloc] Debug Input Constraints:\n");
|
||||
for (const auto& c : in.constraints) {
|
||||
fmt::print(" {}\n", c.to_string());
|
||||
}
|
||||
|
||||
fmt::print("\n");
|
||||
}
|
||||
|
||||
/*!
|
||||
* Print out the state of the RegAllocCache after doing analysis.
|
||||
*/
|
||||
void print_analysis(const AllocationInput& in, RegAllocCache* cache) {
|
||||
fmt::print("[RegAlloc] Basic Blocks\n");
|
||||
fmt::print("-----------------------------------------------------------------\n");
|
||||
for (auto& b : cache->basic_blocks) {
|
||||
fmt::print("{}\n", b.print(in.instructions));
|
||||
}
|
||||
|
||||
printf("[RegAlloc] Alive Info\n");
|
||||
printf("-----------------------------------------------------------------\n");
|
||||
// align to where we start putting live stuff
|
||||
printf(" %30s ", "");
|
||||
for (int i = 0; i < cache->max_var; i++) {
|
||||
printf("%2d ", i);
|
||||
}
|
||||
printf("\n");
|
||||
printf("_________________________________________________________________\n");
|
||||
for (uint32_t i = 0; i < in.instructions.size(); i++) {
|
||||
std::vector<bool> ids_live;
|
||||
std::string lives;
|
||||
|
||||
ids_live.resize(cache->max_var, false);
|
||||
|
||||
for (int j = 0; j < cache->max_var; j++) {
|
||||
if (cache->live_ranges.at(j).is_live_at_instr(i)) {
|
||||
ids_live.at(j) = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t j = 0; j < ids_live.size(); j++) {
|
||||
if (ids_live[j]) {
|
||||
char buff[256];
|
||||
sprintf(buff, "%2d ", j);
|
||||
lives.append(buff);
|
||||
} else {
|
||||
lives.append(".. ");
|
||||
}
|
||||
}
|
||||
|
||||
if (in.debug_instruction_names.size() == in.instructions.size()) {
|
||||
std::string code_str = in.debug_instruction_names.at(i);
|
||||
if (code_str.length() >= 50) {
|
||||
code_str = code_str.substr(0, 48);
|
||||
code_str.push_back('~');
|
||||
}
|
||||
printf("[%03d] %30s -> %s\n", i, code_str.c_str(), lives.c_str());
|
||||
} else {
|
||||
printf("[%03d] %30s -> %s\n", i, "???", lives.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Print the result of register allocation for debugging.
|
||||
*/
|
||||
void print_result(const AllocationInput& in, const AllocationResult& result) {
|
||||
printf("[RegAlloc] result:\n");
|
||||
printf("-----------------------------------------------------------------\n");
|
||||
for (uint32_t i = 0; i < in.instructions.size(); i++) {
|
||||
std::vector<bool> ids_live;
|
||||
std::string lives;
|
||||
|
||||
ids_live.resize(in.max_vars, false);
|
||||
|
||||
for (int j = 0; j < in.max_vars; j++) {
|
||||
if (result.ass_as_ranges.at(j).is_live_at_instr(i)) {
|
||||
lives += std::to_string(j) + " " + result.ass_as_ranges.at(j).get(i).to_string() + " ";
|
||||
}
|
||||
}
|
||||
|
||||
std::string code_str;
|
||||
if (in.debug_instruction_names.size() == in.instructions.size()) {
|
||||
code_str = in.debug_instruction_names.at(i);
|
||||
}
|
||||
|
||||
if (code_str.length() >= 50) {
|
||||
code_str = code_str.substr(0, 48);
|
||||
code_str.push_back('~');
|
||||
}
|
||||
printf("[%03d] %30s | %30s | %30s\n", i, code_str.c_str(), lives.c_str(),
|
||||
result.stack_ops.at(i).print().c_str());
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/*!
|
||||
* The top-level register allocation algorithm!
|
||||
*/
|
||||
AllocationResult allocate_registers(const AllocationInput& input) {
|
||||
AllocationResult result;
|
||||
RegAllocCache cache;
|
||||
cache.is_asm_func = input.is_asm_function;
|
||||
|
||||
// if desired, print input for debugging.
|
||||
if (input.debug_settings.print_input) {
|
||||
print_allocate_input(input);
|
||||
}
|
||||
|
||||
// first step is analysis
|
||||
find_basic_blocks(&cache, input);
|
||||
analyze_liveliness(&cache, input);
|
||||
if (input.debug_settings.print_analysis) {
|
||||
print_analysis(input, &cache);
|
||||
}
|
||||
|
||||
// do constraints first, to get them out of the way
|
||||
do_constrained_alloc(&cache, input, input.debug_settings.trace_debug_constraints);
|
||||
// the user may have specified invalid constraints, so we should attempt to find conflicts now
|
||||
// rather than having the register allocation mysteriously fail later on or silently ignore a
|
||||
// constraint.
|
||||
if (!check_constrained_alloc(&cache, input)) {
|
||||
result.ok = false;
|
||||
fmt::print("[RegAlloc Error] Register allocation has failed due to bad constraints.\n");
|
||||
return result;
|
||||
}
|
||||
|
||||
// do the allocations!
|
||||
if (!run_allocator(&cache, input, input.debug_settings.allocate_log_level)) {
|
||||
result.ok = false;
|
||||
fmt::print("[RegAlloc Error] Register allocation has failed.\n");
|
||||
return result;
|
||||
}
|
||||
|
||||
// prepare the result
|
||||
result.ok = true;
|
||||
result.needs_aligned_stack_for_spills = cache.used_stack;
|
||||
result.stack_slots_for_spills = cache.current_stack_slot;
|
||||
result.stack_slots_for_vars = input.stack_slots_for_stack_vars;
|
||||
|
||||
// copy over the assignment result
|
||||
result.assignment.resize(cache.max_var);
|
||||
for (size_t i = 0; i < result.assignment.size(); i++) {
|
||||
auto& x = result.assignment[i];
|
||||
x.resize(input.instructions.size());
|
||||
const auto& lr = cache.live_ranges.at(i);
|
||||
for (int j = lr.min; j <= lr.max; j++) {
|
||||
x.at(j) = lr.get(j);
|
||||
}
|
||||
}
|
||||
|
||||
// check for use of saved registers
|
||||
for (auto sr : emitter::gRegInfo.get_all_saved()) {
|
||||
bool uses_sr = false;
|
||||
for (auto& lr : cache.live_ranges) {
|
||||
for (int instr_idx = lr.min; instr_idx <= lr.max; instr_idx++) {
|
||||
if (lr.get(instr_idx).reg == sr) {
|
||||
uses_sr = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (uses_sr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (uses_sr) {
|
||||
result.used_saved_regs.push_back(sr);
|
||||
}
|
||||
}
|
||||
result.ass_as_ranges = std::move(cache.live_ranges);
|
||||
result.stack_ops = std::move(cache.stack_ops);
|
||||
|
||||
// final result print
|
||||
if (input.debug_settings.print_result) {
|
||||
print_result(input, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Print for debugging
|
||||
*/
|
||||
std::string RegAllocInstr::print() const {
|
||||
bool first = true;
|
||||
std::string result = "(";
|
||||
|
||||
if (!write.empty()) {
|
||||
first = false;
|
||||
result += "(write";
|
||||
for (auto& i : write) {
|
||||
result += " " + i.to_string();
|
||||
}
|
||||
result += ")";
|
||||
}
|
||||
|
||||
if (!read.empty()) {
|
||||
if (!first) {
|
||||
result += " ";
|
||||
}
|
||||
first = false;
|
||||
result += "(read";
|
||||
for (auto& i : read) {
|
||||
result += " " + i.to_string();
|
||||
}
|
||||
result += ")";
|
||||
}
|
||||
|
||||
if (!clobber.empty()) {
|
||||
if (!first) {
|
||||
result += " ";
|
||||
}
|
||||
first = false;
|
||||
result += "(clobber";
|
||||
for (auto& i : clobber) {
|
||||
result += " " + i.print();
|
||||
}
|
||||
result += ")";
|
||||
}
|
||||
|
||||
if (!jumps.empty()) {
|
||||
if (!first) {
|
||||
result += " ";
|
||||
}
|
||||
first = false;
|
||||
result += "(jumps";
|
||||
for (auto& i : jumps) {
|
||||
result += " " + std::to_string(i);
|
||||
}
|
||||
result += ")";
|
||||
}
|
||||
result += ")";
|
||||
return result;
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
#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 <vector>
|
||||
#include <unordered_set>
|
||||
#include "goalc/emitter/Register.h"
|
||||
#include "IRegister.h"
|
||||
#include "allocate_common.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;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Result of the allocate_registers algorithm
|
||||
*/
|
||||
struct AllocationResult {
|
||||
bool ok = false; // did it work?
|
||||
std::vector<std::vector<Assignment>> assignment; // variable, instruction
|
||||
std::vector<LiveInfo> 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;
|
||||
|
||||
// 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;
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
AllocationResult allocate_registers(const AllocationInput& input);
|
||||
@@ -1,54 +0,0 @@
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "allocate_common.h"
|
||||
|
||||
std::string StackOp::print() const {
|
||||
std::string result;
|
||||
bool added = false;
|
||||
for (const auto& op : ops) {
|
||||
if (op.load) {
|
||||
result += fmt::format("{} <- [{:2d}], ", emitter::gRegInfo.get_info(op.reg).name, op.slot);
|
||||
added = true;
|
||||
}
|
||||
if (op.store) {
|
||||
result += fmt::format("{} -> [{:2d}], ", emitter::gRegInfo.get_info(op.reg).name, op.slot);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (added) {
|
||||
result.pop_back();
|
||||
result.pop_back();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string Assignment::to_string() const {
|
||||
std::string result;
|
||||
if (spilled) {
|
||||
result += "*";
|
||||
}
|
||||
switch (kind) {
|
||||
case Kind::STACK:
|
||||
result += fmt::format("s[{:2d}]", stack_slot);
|
||||
break;
|
||||
case Kind::REGISTER:
|
||||
result += emitter::gRegInfo.get_info(reg).name;
|
||||
break;
|
||||
case Kind::UNASSIGNED:
|
||||
result += "unassigned";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string LiveInfo::print_assignment() {
|
||||
std::string result = "Assignment for var " + std::to_string(var) + "\n";
|
||||
for (uint32_t i = 0; i < assignment.size(); i++) {
|
||||
result += fmt::format("i[{:3d}] {}\n", i + min, assignment.at(i).to_string());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
#ifndef JAK_ALLOCATE_COMMON_H
|
||||
#define JAK_ALLOCATE_COMMON_H
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include "goalc/emitter/Register.h"
|
||||
|
||||
/*!
|
||||
* 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 {
|
||||
enum class Kind { STACK, REGISTER, UNASSIGNED } kind = Kind::UNASSIGNED;
|
||||
emitter::Register reg = -1; //! where the IRegister is now
|
||||
int stack_slot = -1; //! index of the slot, if we are ever spilled
|
||||
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; }
|
||||
};
|
||||
|
||||
// with this on, gaps in usage of registers allow other variables to steal registers.
|
||||
// this reduces stack spills/moves, but may make register allocation slower.
|
||||
constexpr bool enable_fancy_coloring = true;
|
||||
|
||||
// will attempt to allocate in a way to reduce the number of moves.
|
||||
constexpr bool move_eliminator = true;
|
||||
|
||||
constexpr bool allow_read_write_same_reg = true;
|
||||
|
||||
// Indication of where a variable is live and what assignment it has at each point in the range.
|
||||
struct LiveInfo {
|
||||
public:
|
||||
LiveInfo(int start, int end) : min(start), max(end) {}
|
||||
// min, max are inclusive.
|
||||
// meaning the variable written for the first time at min, and read for the last time at max.
|
||||
int min, max;
|
||||
|
||||
std::vector<bool> is_alive;
|
||||
std::vector<int> indices_of_alive;
|
||||
|
||||
// which variable is this?
|
||||
int var = -1;
|
||||
|
||||
// have we actually seen this variable in the code?
|
||||
bool seen = false;
|
||||
|
||||
// does this variable have a constraint?
|
||||
bool has_constraint = false;
|
||||
|
||||
// the assignment of this variable at each instruction in [min, max]
|
||||
std::vector<Assignment> assignment;
|
||||
|
||||
// a hint on where to put this variable.
|
||||
Assignment best_hint;
|
||||
|
||||
/*!
|
||||
* Add an instruction id where this variable is live.
|
||||
*/
|
||||
void add_live_instruction(int value) {
|
||||
assert(value >= 0);
|
||||
if (value > max)
|
||||
max = value;
|
||||
if (value < min)
|
||||
min = value;
|
||||
indices_of_alive.push_back(value);
|
||||
// remember that this variable is actually used
|
||||
seen = true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Is the given instruction contained in the live range?
|
||||
*/
|
||||
bool is_live_at_instr(int value) const {
|
||||
if (value >= min && value <= max) {
|
||||
if (enable_fancy_coloring) {
|
||||
return is_alive.at(value - min);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Are we alive at idx, but not alive at idx - 1 (or idx - 1 doesn't exist)
|
||||
*/
|
||||
bool becomes_live_at_instr(int idx) {
|
||||
if (enable_fancy_coloring) {
|
||||
if (idx == min)
|
||||
return true;
|
||||
if (idx < min || idx > max)
|
||||
return false;
|
||||
assert(idx > min);
|
||||
return is_alive.at(idx - min) && !is_alive.at(idx - min - 1);
|
||||
} else {
|
||||
return idx == min;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Are we alive at idx, but not alive at idx + 1 (or idx + 1 doesn't exist)
|
||||
*/
|
||||
bool dies_next_at_instr(int idx) {
|
||||
if (enable_fancy_coloring) {
|
||||
if (idx == max)
|
||||
return true;
|
||||
if (idx < min || idx > max)
|
||||
return false;
|
||||
assert(idx < max);
|
||||
return is_alive.at(idx - min) && !is_alive.at(idx - min + 1);
|
||||
} else {
|
||||
return idx == max;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Resize Live Range after instructions have been added. Do this before assigning.
|
||||
*/
|
||||
void prepare_for_allocation(int id) {
|
||||
var = id;
|
||||
if (!seen)
|
||||
return; // don't do any prep for a variable which isn't used.
|
||||
assert(max - min >= 0);
|
||||
assignment.resize(max - min + 1);
|
||||
is_alive.resize(max - min + 1);
|
||||
for (auto& x : indices_of_alive) {
|
||||
is_alive.at(x - min) = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Lock an assignment at a given instruction.
|
||||
* Will overwrite any previous assignment here
|
||||
* Will set best_hint to this assignment.
|
||||
*/
|
||||
void constrain_at_one(int id, emitter::Register reg) {
|
||||
assert(id >= min && id <= max);
|
||||
Assignment ass;
|
||||
ass.reg = reg;
|
||||
ass.kind = Assignment::Kind::REGISTER;
|
||||
assignment.at(id - min) = ass;
|
||||
has_constraint = true;
|
||||
best_hint = ass;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Lock an assignment everywhere.
|
||||
*/
|
||||
void constrain_everywhere(emitter::Register reg) {
|
||||
Assignment ass;
|
||||
ass.reg = reg;
|
||||
ass.kind = Assignment::Kind::REGISTER;
|
||||
for (int i = min; i <= max; i++) {
|
||||
assignment.at(i - min) = ass;
|
||||
}
|
||||
has_constraint = true;
|
||||
best_hint = ass;
|
||||
}
|
||||
|
||||
/*!
|
||||
* At the given instruction, does the given assignment conflict with this one?
|
||||
*/
|
||||
bool conflicts_at(int id, Assignment ass) {
|
||||
assert(id >= min && id <= max);
|
||||
return assignment.at(id - min).occupies_same_reg(ass);
|
||||
}
|
||||
|
||||
/*!
|
||||
* At the given instruction, does the given assignment conflict with this one?
|
||||
*/
|
||||
bool conflicts_at(int id, emitter::Register reg) {
|
||||
assert(id >= min && id <= max);
|
||||
Assignment ass;
|
||||
ass.reg = reg;
|
||||
ass.kind = Assignment::Kind::REGISTER;
|
||||
return assignment.at(id - min).occupies_same_reg(ass);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Assign variable to the given assignment at all instructions
|
||||
* Throws if this would require modifying a currently set assignment.
|
||||
*/
|
||||
void assign_no_overwrite(Assignment ass) {
|
||||
assert(seen);
|
||||
assert(ass.is_assigned());
|
||||
for (int i = min; i <= max; i++) {
|
||||
auto& a = assignment.at(i - min);
|
||||
if (a.is_assigned() && !(a.occupies_same_reg(ass))) {
|
||||
throw std::runtime_error("assign_no_overwrite failed!");
|
||||
} else {
|
||||
a = ass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the assignment at the given instruction.
|
||||
*/
|
||||
const Assignment& get(int id) const {
|
||||
assert(id >= min && id <= max);
|
||||
return assignment.at(id - min);
|
||||
}
|
||||
|
||||
int size() const { return 1 + max - min; }
|
||||
|
||||
bool overlaps(const LiveInfo& other) const {
|
||||
auto overlap_min = std::max(min, other.min);
|
||||
auto overlap_max = std::min(max, other.max);
|
||||
return overlap_min <= overlap_max;
|
||||
}
|
||||
|
||||
std::string print_assignment();
|
||||
};
|
||||
#endif // JAK_ALLOCATE_COMMON_H
|
||||
@@ -0,0 +1,379 @@
|
||||
/*!
|
||||
* @file allocate.cpp
|
||||
* Runs the register allocator.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "allocator_interface.h"
|
||||
|
||||
/*!
|
||||
* Print out the input data for debugging.
|
||||
*/
|
||||
void print_allocate_input(const AllocationInput& in) {
|
||||
fmt::print("[RegAlloc] Debug Input Program:\n");
|
||||
if (in.instructions.size() == in.debug_instruction_names.size()) {
|
||||
for (size_t i = 0; i < in.instructions.size(); i++) {
|
||||
// fmt::print(" [{}] {} -> {}\n", in.debug_instruction_names.at(i),
|
||||
// in.instructions.at(i).print());
|
||||
fmt::print(" [{:3d}] {:30} -> {:30}\n", i, in.debug_instruction_names.at(i),
|
||||
in.instructions.at(i).print());
|
||||
}
|
||||
} else {
|
||||
for (const auto& instruction : in.instructions) {
|
||||
fmt::print(" [{:3d}] {}\n", instruction.print());
|
||||
}
|
||||
}
|
||||
fmt::print("[RegAlloc] Debug Input Constraints:\n");
|
||||
for (const auto& c : in.constraints) {
|
||||
fmt::print(" {}\n", c.to_string());
|
||||
}
|
||||
|
||||
fmt::print("\n");
|
||||
}
|
||||
|
||||
/*!
|
||||
* Print the result of register allocation for debugging.
|
||||
*/
|
||||
void print_result(const AllocationInput& in, const AllocationResult& result) {
|
||||
printf("[RegAlloc] result:\n");
|
||||
printf("-----------------------------------------------------------------\n");
|
||||
for (uint32_t i = 0; i < in.instructions.size(); i++) {
|
||||
std::vector<bool> ids_live;
|
||||
std::string lives;
|
||||
|
||||
ids_live.resize(in.max_vars, false);
|
||||
|
||||
for (int j = 0; j < in.max_vars; j++) {
|
||||
if (result.ass_as_ranges.at(j).is_live_at_instr(i)) {
|
||||
lives += std::to_string(j) + " " + result.ass_as_ranges.at(j).get(i).to_string() + " ";
|
||||
}
|
||||
}
|
||||
|
||||
std::string code_str;
|
||||
if (in.debug_instruction_names.size() == in.instructions.size()) {
|
||||
code_str = in.debug_instruction_names.at(i);
|
||||
}
|
||||
|
||||
if (code_str.length() >= 50) {
|
||||
code_str = code_str.substr(0, 48);
|
||||
code_str.push_back('~');
|
||||
}
|
||||
printf("[%03d] %30s | %30s | %30s\n", (int)i, code_str.c_str(), lives.c_str(),
|
||||
result.stack_ops.at(i).print().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::string Assignment::to_string() const {
|
||||
std::string result;
|
||||
if (spilled) {
|
||||
result += "*";
|
||||
}
|
||||
switch (kind) {
|
||||
case Kind::STACK:
|
||||
result += fmt::format("s[{:2d}]", stack_slot);
|
||||
break;
|
||||
case Kind::REGISTER:
|
||||
result += emitter::gRegInfo.get_info(reg).name;
|
||||
break;
|
||||
case Kind::UNASSIGNED:
|
||||
result += "unassigned";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Find basic blocks and add block link info.
|
||||
*/
|
||||
void find_basic_blocks(ControlFlowAnalysisCache* cache, const AllocationInput& in) {
|
||||
std::vector<int> dividers;
|
||||
|
||||
dividers.push_back(0);
|
||||
dividers.push_back(in.instructions.size());
|
||||
|
||||
// loop over instructions, finding jump targets
|
||||
for (uint32_t i = 0; i < in.instructions.size(); i++) {
|
||||
const auto& instr = in.instructions[i];
|
||||
if (!instr.jumps.empty()) {
|
||||
dividers.push_back(i + 1);
|
||||
for (auto dest : instr.jumps) {
|
||||
dividers.push_back(dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sort dividers, and make blocks
|
||||
std::sort(dividers.begin(), dividers.end(), [](int a, int b) { return a < b; });
|
||||
|
||||
for (uint32_t i = 0; i < dividers.size() - 1; i++) {
|
||||
if (dividers[i] != dividers[i + 1]) {
|
||||
// new basic block!
|
||||
RegAllocBasicBlock block;
|
||||
for (int j = dividers[i]; j < dividers[i + 1]; j++) {
|
||||
block.instr_idx.push_back(j);
|
||||
}
|
||||
|
||||
block.idx = cache->basic_blocks.size();
|
||||
cache->basic_blocks.push_back(block);
|
||||
}
|
||||
}
|
||||
|
||||
if (!cache->basic_blocks.empty()) {
|
||||
cache->basic_blocks.front().is_entry = true;
|
||||
cache->basic_blocks.back().is_exit = true;
|
||||
}
|
||||
|
||||
auto find_basic_block_to_target = [&](int instr) {
|
||||
bool found = false;
|
||||
uint32_t result = -1;
|
||||
for (uint32_t i = 0; i < cache->basic_blocks.size(); i++) {
|
||||
if (!cache->basic_blocks[i].instr_idx.empty() &&
|
||||
cache->basic_blocks[i].instr_idx.front() == instr) {
|
||||
assert(!found);
|
||||
found = true;
|
||||
result = i;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
printf("[RegAlloc Error] couldn't find basic block beginning with instr %d of %d\n", instr,
|
||||
int(in.instructions.size()));
|
||||
}
|
||||
assert(found);
|
||||
return result;
|
||||
};
|
||||
|
||||
// link blocks
|
||||
for (auto& block : cache->basic_blocks) {
|
||||
assert(!block.instr_idx.empty());
|
||||
auto& last_instr = in.instructions.at(block.instr_idx.back());
|
||||
if (last_instr.fallthrough) {
|
||||
// try to link to next block:
|
||||
int next_idx = block.idx + 1;
|
||||
if (next_idx < (int)cache->basic_blocks.size()) {
|
||||
cache->basic_blocks.at(next_idx).pred.push_back(block.idx);
|
||||
block.succ.push_back(next_idx);
|
||||
}
|
||||
}
|
||||
for (auto target : last_instr.jumps) {
|
||||
cache->basic_blocks.at(find_basic_block_to_target(target)).pred.push_back(block.idx);
|
||||
block.succ.push_back(find_basic_block_to_target(target));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RegAllocBasicBlock::analyze_liveliness_phase1(const std::vector<RegAllocInstr>& instructions) {
|
||||
for (int i = instr_idx.size(); i-- > 0;) {
|
||||
auto ii = instr_idx.at(i);
|
||||
auto& instr = instructions.at(ii);
|
||||
auto& lv = live.at(i);
|
||||
auto& dd = dead.at(i);
|
||||
|
||||
// make all read live out
|
||||
lv.clear();
|
||||
for (auto& x : instr.read) {
|
||||
lv.insert(x.id);
|
||||
}
|
||||
|
||||
// kill things which are overwritten
|
||||
dd.clear();
|
||||
for (auto& x : instr.write) {
|
||||
if (!lv[x.id]) {
|
||||
dd.insert(x.id);
|
||||
}
|
||||
}
|
||||
|
||||
use.bitwise_and_not(dd);
|
||||
use.bitwise_or(lv);
|
||||
|
||||
defs.bitwise_and_not(lv);
|
||||
defs.bitwise_or(dd);
|
||||
}
|
||||
}
|
||||
|
||||
bool RegAllocBasicBlock::analyze_liveliness_phase2(std::vector<RegAllocBasicBlock>& blocks,
|
||||
const std::vector<RegAllocInstr>& instructions) {
|
||||
(void)instructions;
|
||||
bool changed = false;
|
||||
auto out = defs;
|
||||
|
||||
for (auto s : succ) {
|
||||
out.bitwise_or(blocks.at(s).input);
|
||||
}
|
||||
|
||||
IRegSet in = use;
|
||||
IRegSet temp = out;
|
||||
temp.bitwise_and_not(defs);
|
||||
in.bitwise_or(temp);
|
||||
|
||||
if (in != input || out != output) {
|
||||
changed = true;
|
||||
input = in;
|
||||
output = out;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
void RegAllocBasicBlock::analyze_liveliness_phase3(std::vector<RegAllocBasicBlock>& blocks,
|
||||
const std::vector<RegAllocInstr>& instructions) {
|
||||
(void)instructions;
|
||||
IRegSet live_local;
|
||||
for (auto s : succ) {
|
||||
live_local.bitwise_or(blocks.at(s).input);
|
||||
}
|
||||
|
||||
for (int i = instr_idx.size(); i-- > 0;) {
|
||||
auto& lv = live.at(i);
|
||||
auto& dd = dead.at(i);
|
||||
|
||||
IRegSet new_live = live_local;
|
||||
new_live.bitwise_and_not(dd);
|
||||
new_live.bitwise_or(lv);
|
||||
|
||||
lv = live_local;
|
||||
live_local = new_live;
|
||||
}
|
||||
}
|
||||
|
||||
std::string RegAllocBasicBlock::print_summary() {
|
||||
std::string result = "block " + std::to_string(idx) + "\nsucc: ";
|
||||
for (auto s : succ) {
|
||||
result += std::to_string(s) + " ";
|
||||
}
|
||||
result += "\npred: ";
|
||||
for (auto p : pred) {
|
||||
result += std::to_string(p) + " ";
|
||||
}
|
||||
result += "\nuse: ";
|
||||
for (int x = 0; x < use.size(); x++) {
|
||||
if (use[x]) {
|
||||
result += std::to_string(x) + " ";
|
||||
}
|
||||
}
|
||||
result += "\ndef: ";
|
||||
for (int x = 0; x < defs.size(); x++) {
|
||||
if (defs[x]) {
|
||||
result += std::to_string(x) + " ";
|
||||
}
|
||||
}
|
||||
result += "\ninput: ";
|
||||
for (int x = 0; x < input.size(); x++) {
|
||||
if (input[x]) {
|
||||
result += std::to_string(x) + " ";
|
||||
}
|
||||
}
|
||||
result += "\noutput: ";
|
||||
for (int x = 0; x < output.size(); x++) {
|
||||
if (output[x]) {
|
||||
result += std::to_string(x) + " ";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string RegAllocBasicBlock::print(const std::vector<RegAllocInstr>& insts) {
|
||||
std::string result = print_summary() + "\n";
|
||||
int k = 0;
|
||||
for (auto instr : instr_idx) {
|
||||
std::string line = insts.at(instr).print();
|
||||
constexpr int pad_len = 30;
|
||||
if (line.length() < pad_len) {
|
||||
// line.insert(line.begin(), pad_len - line.length(), ' ');
|
||||
line.append(pad_len - line.length(), ' ');
|
||||
}
|
||||
|
||||
result += " " + line + " live: ";
|
||||
for (int x = 0; x < live.at(k).size(); x++) {
|
||||
if (live.at(k)[x]) {
|
||||
result += std::to_string(x) + " ";
|
||||
}
|
||||
}
|
||||
result += "\n";
|
||||
|
||||
k++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Print for debugging
|
||||
*/
|
||||
std::string RegAllocInstr::print() const {
|
||||
bool first = true;
|
||||
std::string result = "(";
|
||||
|
||||
if (!write.empty()) {
|
||||
first = false;
|
||||
result += "(write";
|
||||
for (auto& i : write) {
|
||||
result += " " + i.to_string();
|
||||
}
|
||||
result += ")";
|
||||
}
|
||||
|
||||
if (!read.empty()) {
|
||||
if (!first) {
|
||||
result += " ";
|
||||
}
|
||||
first = false;
|
||||
result += "(read";
|
||||
for (auto& i : read) {
|
||||
result += " " + i.to_string();
|
||||
}
|
||||
result += ")";
|
||||
}
|
||||
|
||||
if (!clobber.empty()) {
|
||||
if (!first) {
|
||||
result += " ";
|
||||
}
|
||||
first = false;
|
||||
result += "(clobber";
|
||||
for (auto& i : clobber) {
|
||||
result += " " + i.print();
|
||||
}
|
||||
result += ")";
|
||||
}
|
||||
|
||||
if (!jumps.empty()) {
|
||||
if (!first) {
|
||||
result += " ";
|
||||
}
|
||||
first = false;
|
||||
result += "(jumps";
|
||||
for (auto& i : jumps) {
|
||||
result += " " + std::to_string(i);
|
||||
}
|
||||
result += ")";
|
||||
}
|
||||
result += ")";
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string StackOp::print() const {
|
||||
std::string result;
|
||||
bool added = false;
|
||||
for (const auto& op : ops) {
|
||||
if (op.load) {
|
||||
result += fmt::format("{} <- [{:2d}], ", emitter::gRegInfo.get_info(op.reg).name, op.slot);
|
||||
added = true;
|
||||
}
|
||||
if (op.store) {
|
||||
result += fmt::format("{} -> [{:2d}], ", emitter::gRegInfo.get_info(op.reg).name, op.slot);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (added) {
|
||||
result.pop_back();
|
||||
result.pop_back();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
#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 <vector>
|
||||
#include <unordered_set>
|
||||
#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 {
|
||||
enum class Kind { STACK, REGISTER, UNASSIGNED } kind = Kind::UNASSIGNED;
|
||||
emitter::Register reg = -1; //! where the IRegister is now
|
||||
int stack_slot = -1; //! index of the slot, if we are ever spilled
|
||||
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);
|
||||
@@ -12,4 +12,5 @@
|
||||
|
||||
(let ((obj (new 'static 'type-with-weird-relocate :foo 123)))
|
||||
(relocate obj (the kheap 1) (the (pointer uint8) 2))
|
||||
0
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user