mirror of
https://github.com/open-goal/jak-project.git
synced 2024-11-28 00:40:42 +00:00
Improve Register Allocator (#154)
* before adding IRegSet stuff * use bitsets for live analysis * speed up * add stack structures * organize new better
This commit is contained in:
parent
06918e1fea
commit
d86964985a
@ -189,6 +189,7 @@
|
||||
(declare-type process basic)
|
||||
(declare-type stack-frame basic)
|
||||
(declare-type cpu-thread basic)
|
||||
(declare-type state basic)
|
||||
|
||||
;; gkernel-h
|
||||
(deftype thread (basic)
|
||||
|
@ -73,4 +73,10 @@
|
||||
- Reworked type checking for `set!`. You may now use `#f` for non-numeric types.
|
||||
- Fixed a bug where arguments to a method were unmodifiable.
|
||||
- Fixed a bug where multiple anonymous lambda functions in the same file would throw a compiler error related to function name uniqueness.
|
||||
- Method declarations can now use compound types. Previously they could only use simple types due to a mistake in deftype parser.
|
||||
- Method declarations can now use compound types. Previously they could only use simple types due to a mistake in deftype parser.
|
||||
- Added a declare option for `allow-saved-regs` to let `asm-func`s use saved registers in special cases
|
||||
- Improved register allocation for the above case to avoid inserting extra moves to and from temp registers.
|
||||
- Fixed a bug where early returns out of methods would not change the return type of the method.
|
||||
- Fixed a bug where the return instruction was still emitted and the overridden return type of `asm-func` was ignored for methods
|
||||
- Rearranged function stack frames so spilled register variable slots come after stack structures.
|
||||
- Added `stack` allocated and constructed basic/structure types.
|
@ -890,7 +890,8 @@ Example:
|
||||
- `inline` means "inline whenever possible". See function inlining section for why inlining may be impossible in some cases.
|
||||
- `allow-inline` or `disallow-inline`. You can control if inlining is allowed, though it is not clear why I thought this would be useful. Currently the default is to allow always.
|
||||
- `print-asm` if codegen runs on this function (`:color #t`), disassemble the result and print it. This is intended for compiler debugging.
|
||||
- `asm-func` will disable the prologue and epilogue from being generated. You need to include your own `ret` instruction or similar. The compiler will error if it needs to use the stack for a stack variable or a spilled register. The coloring system will not use callee saved registers. As a result, complicated GOAL expression may fail inside an `asm-func` function. The intent is to use it for context switching routines inside in the kernel, where you may not be able to use the stack, or may not want to return with `ret`. The return type of an `asm-func` must manually be specified as the compiler doesn't automatically put the result in the return register and cannot do type analysis to figure out the real return type.
|
||||
- `asm-func` will disable the prologue and epilogue from being generated. You need to include your own `ret` instruction or similar. The compiler will error if it needs to use the stack for a stack variable or a spilled register. The coloring system will not use callee saved registers and will error if you force it to use one. As a result, complicated GOAL expression may fail inside an `asm-func` function. The intent is to use it for context switching routines inside in the kernel, where you may not be able to use the stack, or may not want to return with `ret`. The return type of an `asm-func` must manually be specified as the compiler doesn't automatically put the result in the return register and cannot do type analysis to figure out the real return type.
|
||||
- `allow-saved-regs` allows an `asm-func`'s coloring to use saved registers without an error. Stacks spills are still an error. The compiler will not automatically put things in a saved register, you must do this yourself. The move eliminator may still be used on your variables which use saved registers, so you should be careful if you really care about where saved variables are used.
|
||||
|
||||
This form will probably get more options in the future.
|
||||
|
||||
@ -1437,10 +1438,13 @@ Fields which aren't explicitly initialized are zeroed, except for the type field
|
||||
|
||||
This does not work on dynamically sized structures.
|
||||
|
||||
### Stack Allocated Objects
|
||||
### Stack Allocated Arrays
|
||||
Currently only arrays of integers, floats, or pointers can be stack allocated.
|
||||
For example, use `(new 'array 'int32 1)` to get a `(pointer int32)`. Unlike heap allocated arrays, these stack arrays
|
||||
must have a size that can be determined at compile time.
|
||||
For example, use `(new 'stack ''array 'int32 1)` to get a `(pointer int32)`. Unlike heap allocated arrays, these stack arrays
|
||||
must have a size that can be determined at compile time. The objects are uninitialized.
|
||||
|
||||
### Stack Allocated Structures
|
||||
Works like heap allocated, the objects are initialized with the constructor. The constructor must support "stack mode". Using `object-new` supports stack mode so usually you don't have to worry about this. The structure's memory will be memset to 0 with `object-new` automatically.
|
||||
|
||||
## Defining a `new` Method
|
||||
|
||||
|
@ -51,7 +51,7 @@ std::vector<u8> CodeGenerator::run() {
|
||||
|
||||
void CodeGenerator::do_function(FunctionEnv* env, int f_idx) {
|
||||
if (env->is_asm_func) {
|
||||
do_asm_function(env, f_idx);
|
||||
do_asm_function(env, f_idx, env->asm_func_saved_regs);
|
||||
} else {
|
||||
do_goal_function(env, f_idx);
|
||||
}
|
||||
@ -71,7 +71,7 @@ void CodeGenerator::do_goal_function(FunctionEnv* env, int f_idx) {
|
||||
// compute how much stack we will use
|
||||
int stack_offset = 0;
|
||||
|
||||
// back up xmms
|
||||
// back up xmms (currently not aligned)
|
||||
for (auto& saved_reg : allocs.used_saved_regs) {
|
||||
if (saved_reg.is_xmm()) {
|
||||
m_gen.add_instr_no_ir(f_rec, IGen::sub_gpr64_imm8s(RSP, XMM_SIZE), InstructionInfo::PROLOGUE);
|
||||
@ -133,7 +133,9 @@ void CodeGenerator::do_goal_function(FunctionEnv* env, int f_idx) {
|
||||
for (auto& op : bonus.ops) {
|
||||
if (op.load) {
|
||||
if (op.reg.is_gpr()) {
|
||||
m_gen.add_instr(IGen::load64_gpr64_plus_s32(op.reg, op.slot * GPR_SIZE, RSP), i_rec);
|
||||
m_gen.add_instr(IGen::load64_gpr64_plus_s32(
|
||||
op.reg, allocs.get_slot_for_spill(op.slot) * GPR_SIZE, RSP),
|
||||
i_rec);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
@ -147,7 +149,9 @@ void CodeGenerator::do_goal_function(FunctionEnv* env, int f_idx) {
|
||||
for (auto& op : bonus.ops) {
|
||||
if (op.store) {
|
||||
if (op.reg.is_gpr()) {
|
||||
m_gen.add_instr(IGen::store64_gpr64_plus_s32(RSP, op.slot * GPR_SIZE, op.reg), i_rec);
|
||||
m_gen.add_instr(IGen::store64_gpr64_plus_s32(
|
||||
RSP, allocs.get_slot_for_spill(op.slot) * GPR_SIZE, op.reg),
|
||||
i_rec);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
@ -188,11 +192,11 @@ void CodeGenerator::do_goal_function(FunctionEnv* env, int f_idx) {
|
||||
m_gen.add_instr_no_ir(f_rec, IGen::ret(), InstructionInfo::EPILOGUE);
|
||||
}
|
||||
|
||||
void CodeGenerator::do_asm_function(FunctionEnv* env, int f_idx) {
|
||||
void CodeGenerator::do_asm_function(FunctionEnv* env, int f_idx, bool allow_saved_regs) {
|
||||
auto f_rec = m_gen.get_existing_function_record(f_idx);
|
||||
const auto& allocs = env->alloc_result();
|
||||
|
||||
if (!allocs.used_saved_regs.empty()) {
|
||||
if (!allow_saved_regs && !allocs.used_saved_regs.empty()) {
|
||||
std::string err = fmt::format(
|
||||
"ASM Function {}'s coloring using the following callee-saved registers: ", env->name());
|
||||
for (auto& x : allocs.used_saved_regs) {
|
||||
|
@ -23,7 +23,7 @@ class CodeGenerator {
|
||||
private:
|
||||
void do_function(FunctionEnv* env, int f_idx);
|
||||
void do_goal_function(FunctionEnv* env, int f_idx);
|
||||
void do_asm_function(FunctionEnv* env, int f_idx);
|
||||
void do_asm_function(FunctionEnv* env, int f_idx, bool allow_saved_regs);
|
||||
emitter::ObjectGenerator m_gen;
|
||||
FileEnv* m_fe = nullptr;
|
||||
DebugInfo* m_debug_info = nullptr;
|
||||
|
@ -88,6 +88,7 @@ class Compiler {
|
||||
std::string as_string(const goos::Object& o);
|
||||
std::string symbol_string(const goos::Object& o);
|
||||
std::string quoted_sym_as_string(const goos::Object& o);
|
||||
goos::Object unquote(const goos::Object& o);
|
||||
bool is_quoted_sym(const goos::Object& o);
|
||||
bool is_basic(const TypeSpec& ts);
|
||||
bool is_structure(const TypeSpec& ts);
|
||||
@ -115,6 +116,19 @@ class Compiler {
|
||||
|
||||
bool try_getting_constant_integer(const goos::Object& in, int64_t* out, Env* env);
|
||||
float try_getting_constant_float(const goos::Object& in, float* out, Env* env);
|
||||
Val* compile_heap_new(const goos::Object& form,
|
||||
const std::string& allocation,
|
||||
const goos::Object& type,
|
||||
const goos::Object* rest,
|
||||
Env* env);
|
||||
Val* compile_static_new(const goos::Object& form,
|
||||
const goos::Object& type,
|
||||
const goos::Object* rest,
|
||||
Env* env);
|
||||
Val* compile_stack_new(const goos::Object& form,
|
||||
const goos::Object& type,
|
||||
const goos::Object* rest,
|
||||
Env* env);
|
||||
|
||||
TypeSystem m_ts;
|
||||
std::unique_ptr<GlobalEnv> m_global_env = nullptr;
|
||||
|
@ -275,6 +275,29 @@ StackVarAddrVal* FunctionEnv::allocate_stack_variable(const TypeSpec& ts, int si
|
||||
return result;
|
||||
}
|
||||
|
||||
StackVarAddrVal* FunctionEnv::allocate_aligned_stack_variable(const TypeSpec& ts,
|
||||
int size_bytes,
|
||||
int align_bytes) {
|
||||
require_aligned_stack();
|
||||
assert(align_bytes <= 16);
|
||||
int align_slots = (align_bytes + emitter::GPR_SIZE - 1) / emitter::GPR_SIZE;
|
||||
while (m_stack_var_slots_used % align_slots) {
|
||||
m_stack_var_slots_used++;
|
||||
}
|
||||
|
||||
// we align our size too. The stack versions of the default new methods in kscheme.cpp round up
|
||||
// to 16 bytes and memset this size, which can cause issues if we make this size only 8 byte
|
||||
// aligned.
|
||||
while (size_bytes % align_bytes) {
|
||||
size_bytes++;
|
||||
}
|
||||
|
||||
int slots_used = (size_bytes + emitter::GPR_SIZE - 1) / emitter::GPR_SIZE;
|
||||
auto result = alloc_val<StackVarAddrVal>(ts, m_stack_var_slots_used, slots_used);
|
||||
m_stack_var_slots_used += slots_used;
|
||||
return result;
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// LexicalEnv
|
||||
///////////////////
|
||||
|
@ -176,6 +176,9 @@ class FunctionEnv : public DeclareEnv {
|
||||
const std::string& name() const { return m_name; }
|
||||
|
||||
StackVarAddrVal* allocate_stack_variable(const TypeSpec& ts, int size_bytes);
|
||||
StackVarAddrVal* allocate_aligned_stack_variable(const TypeSpec& ts,
|
||||
int size_bytes,
|
||||
int align_bytes);
|
||||
int stack_slots_used_for_stack_vars() const { return m_stack_var_slots_used; }
|
||||
|
||||
int idx_in_file = -1;
|
||||
@ -197,6 +200,7 @@ class FunctionEnv : public DeclareEnv {
|
||||
int segment = -1;
|
||||
std::string method_of_type_name = "#f";
|
||||
bool is_asm_func = false;
|
||||
bool asm_func_saved_regs = false;
|
||||
TypeSpec asm_func_return_type;
|
||||
std::vector<UnresolvedGoto> unresolved_gotos;
|
||||
std::vector<UnresolvedConditionalGoto> unresolved_cond_gotos;
|
||||
|
@ -949,7 +949,7 @@ void IR_GetStackAddr::do_codegen(emitter::ObjectGenerator* gen,
|
||||
const AllocationResult& allocs,
|
||||
emitter::IR_Record irec) {
|
||||
auto dest_reg = get_reg(m_dest, allocs, irec);
|
||||
int offset = GPR_SIZE * (m_slot + allocs.stack_slots_for_spills);
|
||||
int offset = GPR_SIZE * allocs.get_slot_for_var(m_slot);
|
||||
|
||||
// dest = offset
|
||||
load_constant(offset, gen, irec, dest_reg);
|
||||
|
@ -55,6 +55,15 @@ std::string Compiler::quoted_sym_as_string(const goos::Object& o) {
|
||||
return symbol_string(args.unnamed.at(1));
|
||||
}
|
||||
|
||||
goos::Object Compiler::unquote(const goos::Object& o) {
|
||||
auto args = get_va(o, o);
|
||||
va_check(o, args, {{goos::ObjectType::SYMBOL}, {}}, {});
|
||||
if (symbol_string(args.unnamed.at(0)) != "quote") {
|
||||
throw_compiler_error(o, "Invalid quoted symbol: {}.", o.print());
|
||||
}
|
||||
return args.unnamed.at(1);
|
||||
}
|
||||
|
||||
bool Compiler::is_quoted_sym(const goos::Object& o) {
|
||||
if (o.is_pair()) {
|
||||
auto car = pair_car(o);
|
||||
|
@ -576,6 +576,14 @@ Val* Compiler::compile_declare(const goos::Object& form, const goos::Object& res
|
||||
throw_compiler_error(first, "Invalid print-asm declare");
|
||||
}
|
||||
settings.print_asm = true;
|
||||
|
||||
} else if (first.as_symbol()->name == "allow-saved-regs") {
|
||||
if (!rrest->is_empty_list()) {
|
||||
throw_compiler_error(first, "Invalid allow-saved-regs declare");
|
||||
}
|
||||
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
||||
fe->asm_func_saved_regs = true;
|
||||
|
||||
} else {
|
||||
throw_compiler_error(first, "Unrecognized declare option {}.", first.print());
|
||||
}
|
||||
|
@ -349,6 +349,9 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _
|
||||
bool first_thing = true;
|
||||
for_each_in_list(lambda.body, [&](const goos::Object& o) {
|
||||
result = compile_error_guard(o, func_block_env);
|
||||
if (!dynamic_cast<None*>(result)) {
|
||||
result = result->to_reg(func_block_env);
|
||||
}
|
||||
if (first_thing) {
|
||||
first_thing = false;
|
||||
// you could probably cheat and do a (begin (blorp) (declare ...)) to get around this.
|
||||
@ -356,10 +359,15 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _
|
||||
}
|
||||
});
|
||||
|
||||
if (result && !dynamic_cast<None*>(result)) {
|
||||
if (new_func_env->is_asm_func) {
|
||||
// don't add return automatically!
|
||||
lambda_ts.add_arg(new_func_env->asm_func_return_type);
|
||||
} else if (result && !dynamic_cast<None*>(result)) {
|
||||
auto final_result = result->to_gpr(new_func_env.get());
|
||||
new_func_env->emit(std::make_unique<IR_Return>(return_reg, final_result));
|
||||
lambda_ts.add_arg(final_result->type());
|
||||
func_block_env->return_types.push_back(final_result->type());
|
||||
auto return_type = m_ts.lowest_common_ancestor(func_block_env->return_types);
|
||||
lambda_ts.add_arg(return_type);
|
||||
} else {
|
||||
lambda_ts.add_arg(m_ts.make_typespec("none"));
|
||||
}
|
||||
@ -588,127 +596,185 @@ Val* Compiler::compile_print_type(const goos::Object& form, const goos::Object&
|
||||
return get_none();
|
||||
}
|
||||
|
||||
Val* Compiler::compile_new(const goos::Object& form, const goos::Object& _rest, Env* env) {
|
||||
// todo - support compound types.
|
||||
// todo - stack arrays?
|
||||
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
||||
/*!
|
||||
* New on global or debug heap.
|
||||
*/
|
||||
Val* Compiler::compile_heap_new(const goos::Object& form,
|
||||
const std::string& allocation,
|
||||
const goos::Object& type,
|
||||
const goos::Object* rest,
|
||||
Env* env) {
|
||||
auto main_type = parse_typespec(unquote(type));
|
||||
if (main_type == TypeSpec("inline-array") || main_type == TypeSpec("array")) {
|
||||
bool is_inline = main_type == TypeSpec("inline-array");
|
||||
auto elt_type = quoted_sym_as_string(pair_car(*rest));
|
||||
rest = &pair_cdr(*rest);
|
||||
|
||||
auto count_obj = pair_car(*rest);
|
||||
rest = &pair_cdr(*rest);
|
||||
// try to get the size as a compile time constant.
|
||||
int64_t constant_count = 0;
|
||||
bool is_constant_size = try_getting_constant_integer(count_obj, &constant_count, env);
|
||||
|
||||
if (!rest->is_empty_list()) {
|
||||
// got extra arguments
|
||||
throw_compiler_error(form, "new array form got more arguments than expected");
|
||||
}
|
||||
|
||||
auto ts = is_inline ? m_ts.make_inline_array_typespec(elt_type)
|
||||
: m_ts.make_pointer_typespec(elt_type);
|
||||
auto info = m_ts.get_deref_info(ts);
|
||||
if (!info.can_deref) {
|
||||
throw_compiler_error(form, "Cannot make an {} of {}\n", main_type.print(), ts.print());
|
||||
}
|
||||
|
||||
auto malloc_func = compile_get_symbol_value(form, "malloc", env)->to_reg(env);
|
||||
std::vector<RegVal*> args;
|
||||
args.push_back(compile_get_sym_obj(allocation, env)->to_reg(env));
|
||||
|
||||
if (is_constant_size) {
|
||||
auto array_size = constant_count * info.stride;
|
||||
args.push_back(compile_integer(array_size, env)->to_reg(env));
|
||||
} else {
|
||||
auto array_size = compile_integer(info.stride, env)->to_reg(env);
|
||||
env->emit(std::make_unique<IR_IntegerMath>(IntegerMathKind::IMUL_32, array_size,
|
||||
compile_error_guard(count_obj, env)->to_gpr(env)));
|
||||
args.push_back(array_size);
|
||||
}
|
||||
|
||||
auto array = compile_real_function_call(form, malloc_func, args, env);
|
||||
array->set_type(ts);
|
||||
return array;
|
||||
} else {
|
||||
if (!m_ts.lookup_type(main_type)->is_reference()) {
|
||||
throw_compiler_error(form, "Cannot heap allocate the value type {}.", main_type.print());
|
||||
}
|
||||
std::vector<RegVal*> args;
|
||||
// allocation
|
||||
args.push_back(compile_get_sym_obj(allocation, env)->to_reg(env));
|
||||
// type
|
||||
args.push_back(compile_get_symbol_value(form, main_type.base_type(), env)->to_reg(env));
|
||||
// the other arguments
|
||||
for_each_in_list(*rest, [&](const goos::Object& o) {
|
||||
args.push_back(compile_error_guard(o, env)->to_reg(env));
|
||||
});
|
||||
|
||||
auto new_method = compile_get_method_of_type(form, main_type, "new", env);
|
||||
auto new_obj = compile_real_function_call(form, new_method, args, env);
|
||||
new_obj->set_type(main_type);
|
||||
return new_obj;
|
||||
}
|
||||
}
|
||||
|
||||
Val* Compiler::compile_static_new(const goos::Object& form,
|
||||
const goos::Object& type,
|
||||
const goos::Object* rest,
|
||||
Env* env) {
|
||||
auto type_of_object = parse_typespec(unquote(type));
|
||||
if (is_structure(type_of_object)) {
|
||||
return compile_new_static_structure_or_basic(form, type_of_object, *rest, env);
|
||||
}
|
||||
|
||||
if (is_bitfield(type_of_object)) {
|
||||
return compile_new_static_bitfield(form, type_of_object, *rest, env);
|
||||
}
|
||||
|
||||
throw_compiler_error(form,
|
||||
"Cannot do a static new of a {} because it is not a bitfield or structure.");
|
||||
return get_none();
|
||||
}
|
||||
|
||||
Val* Compiler::compile_stack_new(const goos::Object& form,
|
||||
const goos::Object& type,
|
||||
const goos::Object* rest,
|
||||
Env* env) {
|
||||
auto type_of_object = parse_typespec(unquote(type));
|
||||
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
||||
if (type_of_object == TypeSpec("inline-array") || type_of_object == TypeSpec("array")) {
|
||||
bool is_inline = type_of_object == TypeSpec("inline-array");
|
||||
auto elt_type = quoted_sym_as_string(pair_car(*rest));
|
||||
rest = &pair_cdr(*rest);
|
||||
|
||||
auto count_obj = pair_car(*rest);
|
||||
rest = &pair_cdr(*rest);
|
||||
// try to get the size as a compile time constant.
|
||||
int64_t constant_count = 0;
|
||||
bool is_constant_size = try_getting_constant_integer(count_obj, &constant_count, env);
|
||||
if (!is_constant_size) {
|
||||
throw_compiler_error(form, "Cannot create a dynamically sized stack array");
|
||||
}
|
||||
|
||||
if (!rest->is_empty_list()) {
|
||||
// got extra arguments
|
||||
throw_compiler_error(form, "New array form got more arguments than expected");
|
||||
}
|
||||
|
||||
auto ts = is_inline ? m_ts.make_inline_array_typespec(elt_type)
|
||||
: m_ts.make_pointer_typespec(elt_type);
|
||||
auto info = m_ts.get_deref_info(ts);
|
||||
if (!info.can_deref) {
|
||||
throw_compiler_error(form, "Cannot make an {} of {}\n", type_of_object.print(), ts.print());
|
||||
}
|
||||
|
||||
if (!m_ts.lookup_type(elt_type)->is_reference()) {
|
||||
// not a reference type
|
||||
int size_in_bytes = info.stride * constant_count;
|
||||
auto addr = fe->allocate_stack_variable(ts, size_in_bytes);
|
||||
return addr;
|
||||
}
|
||||
// todo
|
||||
throw_compiler_error(form, "Static array of type {} is not yet supported.", ts.print());
|
||||
return get_none();
|
||||
} else {
|
||||
auto ti = m_ts.lookup_type(type_of_object);
|
||||
|
||||
if (!ti->is_reference()) {
|
||||
throw_compiler_error(form, "Cannot stack allocate the value type {}.",
|
||||
type_of_object.print());
|
||||
}
|
||||
auto ti_as_struct = dynamic_cast<StructureType*>(ti);
|
||||
assert(ti_as_struct);
|
||||
|
||||
if (ti_as_struct->is_dynamic()) {
|
||||
throw_compiler_error(form, "Cannot stack allocate the dynamic type {}.",
|
||||
type_of_object.print());
|
||||
}
|
||||
std::vector<RegVal*> args;
|
||||
// allocation
|
||||
auto mem = fe->allocate_aligned_stack_variable(type_of_object, ti->get_size_in_memory(), 16)
|
||||
->to_gpr(env);
|
||||
// the new method actual takes a "symbol" according the type system. So we have to cheat it.
|
||||
mem->set_type(TypeSpec("symbol"));
|
||||
args.push_back(mem);
|
||||
// type
|
||||
args.push_back(compile_get_symbol_value(form, type_of_object.base_type(), env)->to_reg(env));
|
||||
// the other arguments
|
||||
for_each_in_list(*rest, [&](const goos::Object& o) {
|
||||
args.push_back(compile_error_guard(o, env)->to_reg(env));
|
||||
});
|
||||
|
||||
auto new_method = compile_get_method_of_type(form, type_of_object, "new", env);
|
||||
auto new_obj = compile_real_function_call(form, new_method, args, env);
|
||||
new_obj->set_type(type_of_object);
|
||||
return new_obj;
|
||||
}
|
||||
}
|
||||
|
||||
Val* Compiler::compile_new(const goos::Object& form, const goos::Object& _rest, Env* env) {
|
||||
auto allocation = quoted_sym_as_string(pair_car(_rest));
|
||||
auto rest = &pair_cdr(_rest);
|
||||
|
||||
// auto type_of_obj = get_base_typespec(quoted_sym_as_string(pair_car(rest)));
|
||||
auto type_as_string = quoted_sym_as_string(pair_car(*rest));
|
||||
auto type = pair_car(*rest);
|
||||
rest = &pair_cdr(*rest);
|
||||
|
||||
if (allocation == "global" || allocation == "debug") {
|
||||
// allocate on a named heap
|
||||
|
||||
if (type_as_string == "inline-array" || type_as_string == "array") {
|
||||
bool is_inline = type_as_string == "inline-array";
|
||||
auto elt_type = quoted_sym_as_string(pair_car(*rest));
|
||||
rest = &pair_cdr(*rest);
|
||||
|
||||
auto count_obj = pair_car(*rest);
|
||||
rest = &pair_cdr(*rest);
|
||||
// try to get the size as a compile time constant.
|
||||
int64_t constant_count = 0;
|
||||
bool is_constant_size = try_getting_constant_integer(count_obj, &constant_count, env);
|
||||
|
||||
if (!rest->is_empty_list()) {
|
||||
// got extra arguments
|
||||
throw_compiler_error(form, "new array form got more arguments than expected");
|
||||
}
|
||||
|
||||
auto ts = is_inline ? m_ts.make_inline_array_typespec(elt_type)
|
||||
: m_ts.make_pointer_typespec(elt_type);
|
||||
auto info = m_ts.get_deref_info(ts);
|
||||
if (!info.can_deref) {
|
||||
throw_compiler_error(form, "Cannot make an {} of {}\n", type_as_string, ts.print());
|
||||
}
|
||||
|
||||
auto malloc_func = compile_get_symbol_value(form, "malloc", env)->to_reg(env);
|
||||
std::vector<RegVal*> args;
|
||||
args.push_back(compile_get_sym_obj(allocation, env)->to_reg(env));
|
||||
|
||||
if (is_constant_size) {
|
||||
auto array_size = constant_count * info.stride;
|
||||
args.push_back(compile_integer(array_size, env)->to_reg(env));
|
||||
} else {
|
||||
auto array_size = compile_integer(info.stride, env)->to_reg(env);
|
||||
env->emit(
|
||||
std::make_unique<IR_IntegerMath>(IntegerMathKind::IMUL_32, array_size,
|
||||
compile_error_guard(count_obj, env)->to_gpr(env)));
|
||||
args.push_back(array_size);
|
||||
}
|
||||
|
||||
auto array = compile_real_function_call(form, malloc_func, args, env);
|
||||
array->set_type(ts);
|
||||
return array;
|
||||
} else {
|
||||
auto type_of_obj = m_ts.make_typespec(type_as_string);
|
||||
std::vector<RegVal*> args;
|
||||
// allocation
|
||||
args.push_back(compile_get_sym_obj(allocation, env)->to_reg(env));
|
||||
// type
|
||||
args.push_back(compile_get_symbol_value(form, type_of_obj.base_type(), env)->to_reg(env));
|
||||
// the other arguments
|
||||
for_each_in_list(*rest, [&](const goos::Object& o) {
|
||||
args.push_back(compile_error_guard(o, env)->to_reg(env));
|
||||
});
|
||||
|
||||
auto new_method = compile_get_method_of_type(form, type_of_obj, "new", env);
|
||||
auto new_obj = compile_real_function_call(form, new_method, args, env);
|
||||
new_obj->set_type(type_of_obj);
|
||||
return new_obj;
|
||||
}
|
||||
return compile_heap_new(form, allocation, type, rest, env);
|
||||
} else if (allocation == "static") {
|
||||
auto type_of_object = m_ts.make_typespec(type_as_string);
|
||||
if (is_structure(type_of_object)) {
|
||||
return compile_new_static_structure_or_basic(form, type_of_object, *rest, env);
|
||||
}
|
||||
|
||||
if (is_bitfield(type_of_object)) {
|
||||
return compile_new_static_bitfield(form, type_of_object, *rest, env);
|
||||
}
|
||||
|
||||
// put in code.
|
||||
return compile_static_new(form, type, rest, env);
|
||||
} else if (allocation == "stack") {
|
||||
auto type_of_object = m_ts.make_typespec(type_as_string);
|
||||
|
||||
if (type_as_string == "inline-array" || type_as_string == "array") {
|
||||
bool is_inline = type_as_string == "inline-array";
|
||||
auto elt_type = quoted_sym_as_string(pair_car(*rest));
|
||||
rest = &pair_cdr(*rest);
|
||||
|
||||
auto count_obj = pair_car(*rest);
|
||||
rest = &pair_cdr(*rest);
|
||||
// try to get the size as a compile time constant.
|
||||
int64_t constant_count = 0;
|
||||
bool is_constant_size = try_getting_constant_integer(count_obj, &constant_count, env);
|
||||
if (!is_constant_size) {
|
||||
throw_compiler_error(form, "Cannot create a dynamically sized stack array");
|
||||
}
|
||||
|
||||
if (!rest->is_empty_list()) {
|
||||
// got extra arguments
|
||||
throw_compiler_error(form, "New array form got more arguments than expected");
|
||||
}
|
||||
|
||||
auto ts = is_inline ? m_ts.make_inline_array_typespec(elt_type)
|
||||
: m_ts.make_pointer_typespec(elt_type);
|
||||
auto info = m_ts.get_deref_info(ts);
|
||||
if (!info.can_deref) {
|
||||
throw_compiler_error(form, "Cannot make an {} of {}\n", type_as_string, ts.print());
|
||||
}
|
||||
|
||||
if (!m_ts.lookup_type(elt_type)->is_reference()) {
|
||||
// not a reference type
|
||||
int size_in_bytes = info.stride * constant_count;
|
||||
auto addr = fe->allocate_stack_variable(ts, size_in_bytes);
|
||||
return addr;
|
||||
}
|
||||
// todo, stack structures.
|
||||
}
|
||||
// todo, stack not-arrays
|
||||
return compile_stack_new(form, type, rest, env);
|
||||
}
|
||||
|
||||
throw_compiler_error(form, "Unsupported new form");
|
||||
|
@ -111,8 +111,10 @@ void compute_live_ranges(RegAllocCache* cache, const AllocationInput& in) {
|
||||
// and liveliness analysis
|
||||
assert(block.live.size() == block.instr_idx.size());
|
||||
for (uint32_t i = 0; i < block.live.size(); i++) {
|
||||
for (auto& x : block.live[i]) {
|
||||
cache->live_ranges.at(x).add_live_instruction(block.instr_idx.at(i));
|
||||
for (int j = 0; j < block.live[i].size(); j++) {
|
||||
if (block.live[i][j]) {
|
||||
cache->live_ranges.at(j).add_live_instruction(block.instr_idx.at(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -175,14 +177,22 @@ void analyze_liveliness(RegAllocCache* cache, const AllocationInput& in) {
|
||||
cache->live_ranges.at(i).prepare_for_allocation(i);
|
||||
}
|
||||
cache->stack_ops.resize(in.instructions.size());
|
||||
}
|
||||
|
||||
namespace {
|
||||
template <typename T>
|
||||
bool in_set(std::set<T>& set, const T& obj) {
|
||||
return set.find(obj) != set.end();
|
||||
// cache a list of live ranges which are live at each instruction.
|
||||
// filters out unseen lr's as well.
|
||||
// this makes instr * lr1 * lr2 loop much faster!
|
||||
cache->live_ranges_by_instr.resize(in.instructions.size());
|
||||
for (u32 lr_idx = 0; lr_idx < cache->live_ranges.size(); lr_idx++) {
|
||||
auto& lr = cache->live_ranges.at(lr_idx);
|
||||
if (lr.seen) {
|
||||
for (int i = lr.min; i <= lr.max; i++) {
|
||||
if (lr.is_live_at_instr(i)) {
|
||||
cache->live_ranges_by_instr.at(i).push_back(lr_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void RegAllocBasicBlock::analyze_liveliness_phase1(const std::vector<RegAllocInstr>& instructions) {
|
||||
for (int i = instr_idx.size(); i-- > 0;) {
|
||||
@ -200,36 +210,16 @@ void RegAllocBasicBlock::analyze_liveliness_phase1(const std::vector<RegAllocIns
|
||||
// kill things which are overwritten
|
||||
dd.clear();
|
||||
for (auto& x : instr.write) {
|
||||
if (!in_set(lv, x.id)) {
|
||||
if (!lv[x.id]) {
|
||||
dd.insert(x.id);
|
||||
}
|
||||
}
|
||||
|
||||
// b.use = i.liveout
|
||||
std::set<int> use_old = use;
|
||||
use.clear();
|
||||
for (auto& x : lv) {
|
||||
use.insert(x);
|
||||
}
|
||||
// | (bu.use & !i.dead)
|
||||
for (auto& x : use_old) {
|
||||
if (!in_set(dd, x)) {
|
||||
use.insert(x);
|
||||
}
|
||||
}
|
||||
use.bitwise_and_not(dd);
|
||||
use.bitwise_or(lv);
|
||||
|
||||
// b.defs = i.dead
|
||||
std::set<int> defs_old = defs;
|
||||
defs.clear();
|
||||
for (auto& x : dd) {
|
||||
defs.insert(x);
|
||||
}
|
||||
// | b.defs & !i.lv
|
||||
for (auto& x : defs_old) {
|
||||
if (!in_set(lv, x)) {
|
||||
defs.insert(x);
|
||||
}
|
||||
}
|
||||
defs.bitwise_and_not(lv);
|
||||
defs.bitwise_or(dd);
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,17 +230,13 @@ bool RegAllocBasicBlock::analyze_liveliness_phase2(std::vector<RegAllocBasicBloc
|
||||
auto out = defs;
|
||||
|
||||
for (auto s : succ) {
|
||||
for (auto in : blocks.at(s).input) {
|
||||
out.insert(in);
|
||||
}
|
||||
out.bitwise_or(blocks.at(s).input);
|
||||
}
|
||||
|
||||
std::set<int> in = use;
|
||||
for (auto x : out) {
|
||||
if (!in_set(defs, x)) {
|
||||
in.insert(x);
|
||||
}
|
||||
}
|
||||
IRegSet in = use;
|
||||
IRegSet temp = out;
|
||||
temp.bitwise_and_not(defs);
|
||||
in.bitwise_or(temp);
|
||||
|
||||
if (in != input || out != output) {
|
||||
changed = true;
|
||||
@ -264,29 +250,25 @@ bool RegAllocBasicBlock::analyze_liveliness_phase2(std::vector<RegAllocBasicBloc
|
||||
void RegAllocBasicBlock::analyze_liveliness_phase3(std::vector<RegAllocBasicBlock>& blocks,
|
||||
const std::vector<RegAllocInstr>& instructions) {
|
||||
(void)instructions;
|
||||
std::set<int> live_local;
|
||||
IRegSet live_local;
|
||||
for (auto s : succ) {
|
||||
for (auto i : blocks.at(s).input) {
|
||||
live_local.insert(i);
|
||||
}
|
||||
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);
|
||||
|
||||
std::set<int> new_live = lv;
|
||||
for (auto x : live_local) {
|
||||
if (!in_set(dd, x)) {
|
||||
new_live.insert(x);
|
||||
}
|
||||
}
|
||||
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() const {
|
||||
std::string RegAllocBasicBlock::print_summary() {
|
||||
std::string result = "block " + std::to_string(idx) + "\nsucc: ";
|
||||
for (auto s : succ) {
|
||||
result += std::to_string(s) + " ";
|
||||
@ -296,26 +278,34 @@ std::string RegAllocBasicBlock::print_summary() const {
|
||||
result += std::to_string(p) + " ";
|
||||
}
|
||||
result += "\nuse: ";
|
||||
for (auto x : use) {
|
||||
result += std::to_string(x) + " ";
|
||||
for (int x = 0; x < use.size(); x++) {
|
||||
if (use[x]) {
|
||||
result += std::to_string(x) + " ";
|
||||
}
|
||||
}
|
||||
result += "\ndef: ";
|
||||
for (auto x : defs) {
|
||||
result += std::to_string(x) + " ";
|
||||
for (int x = 0; x < defs.size(); x++) {
|
||||
if (defs[x]) {
|
||||
result += std::to_string(x) + " ";
|
||||
}
|
||||
}
|
||||
result += "\ninput: ";
|
||||
for (auto x : input) {
|
||||
result += std::to_string(x) + " ";
|
||||
for (int x = 0; x < input.size(); x++) {
|
||||
if (input[x]) {
|
||||
result += std::to_string(x) + " ";
|
||||
}
|
||||
}
|
||||
result += "\noutput: ";
|
||||
for (auto x : output) {
|
||||
result += std::to_string(x) + " ";
|
||||
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) const {
|
||||
std::string RegAllocBasicBlock::print(const std::vector<RegAllocInstr>& insts) {
|
||||
std::string result = print_summary() + "\n";
|
||||
int k = 0;
|
||||
for (auto instr : instr_idx) {
|
||||
@ -327,8 +317,10 @@ std::string RegAllocBasicBlock::print(const std::vector<RegAllocInstr>& insts) c
|
||||
}
|
||||
|
||||
result += " " + line + " live: ";
|
||||
for (auto j : live.at(k)) {
|
||||
result += std::to_string(j) + " ";
|
||||
for (int x = 0; x < live.at(k).size(); x++) {
|
||||
if (live.at(k)[x]) {
|
||||
result += std::to_string(x) + " ";
|
||||
}
|
||||
}
|
||||
result += "\n";
|
||||
|
||||
@ -384,12 +376,13 @@ bool check_constrained_alloc(RegAllocCache* cache, const AllocationInput& in) {
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < in.instructions.size(); i++) {
|
||||
for (auto& lr1 : cache->live_ranges) {
|
||||
if (!lr1.seen || !lr1.is_live_at_instr(i))
|
||||
continue;
|
||||
for (auto& lr2 : cache->live_ranges) {
|
||||
if (!lr2.seen || !lr2.is_live_at_instr(i) || (&lr1 == &lr2))
|
||||
for (auto idx1 : cache->live_ranges_by_instr.at(i)) {
|
||||
auto& lr1 = cache->live_ranges.at(idx1);
|
||||
for (auto idx2 : cache->live_ranges_by_instr.at(i)) {
|
||||
if (idx1 == idx2) {
|
||||
continue;
|
||||
}
|
||||
auto& lr2 = cache->live_ranges.at(idx2);
|
||||
// if lr1 is assigned...
|
||||
auto& ass1 = lr1.get(i);
|
||||
if (ass1.kind != Assignment::Kind::UNASSIGNED) {
|
||||
@ -430,45 +423,45 @@ bool can_var_be_assigned(int var,
|
||||
// our live range:
|
||||
auto& lr = cache->live_ranges.at(var);
|
||||
// check against all other live ranges:
|
||||
for (auto& other_lr : cache->live_ranges) {
|
||||
if (other_lr.var == var /*|| !other_lr.seen*/)
|
||||
continue; // but not us!
|
||||
for (int instr = lr.min; instr <= lr.max; instr++) {
|
||||
if (other_lr.is_live_at_instr(instr)) {
|
||||
// LR's overlap
|
||||
if (/*(instr != other_lr.max) && */ other_lr.conflicts_at(instr, ass)) {
|
||||
bool allowed_by_move_eliminator = false;
|
||||
if (move_eliminator) {
|
||||
if (enable_fancy_coloring) {
|
||||
if (lr.dies_next_at_instr(instr) && other_lr.becomes_live_at_instr(instr) &&
|
||||
in.instructions.at(instr).is_move) {
|
||||
allowed_by_move_eliminator = true;
|
||||
}
|
||||
|
||||
if (lr.becomes_live_at_instr(instr) && other_lr.dies_next_at_instr(instr) &&
|
||||
in.instructions.at(instr).is_move) {
|
||||
allowed_by_move_eliminator = true;
|
||||
}
|
||||
} else {
|
||||
// case to allow rename (from us to them)
|
||||
if (instr == lr.max && instr == other_lr.min && in.instructions.at(instr).is_move) {
|
||||
allowed_by_move_eliminator = true;
|
||||
}
|
||||
|
||||
if (instr == lr.min && instr == other_lr.min && in.instructions.at(instr).is_move) {
|
||||
allowed_by_move_eliminator = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!allowed_by_move_eliminator) {
|
||||
if (debug_trace >= 2) {
|
||||
printf("at idx %d, %s conflicts\n", instr, other_lr.print_assignment().c_str());
|
||||
for (int instr = lr.min; instr <= lr.max; instr++) {
|
||||
for (int other_idx : cache->live_ranges_by_instr.at(instr)) {
|
||||
auto& other_lr = cache->live_ranges.at(other_idx);
|
||||
if (other_lr.var == var) {
|
||||
continue;
|
||||
}
|
||||
// LR's overlap
|
||||
if (/*(instr != other_lr.max) && */ other_lr.conflicts_at(instr, ass)) {
|
||||
bool allowed_by_move_eliminator = false;
|
||||
if (move_eliminator) {
|
||||
if (enable_fancy_coloring) {
|
||||
if (lr.dies_next_at_instr(instr) && other_lr.becomes_live_at_instr(instr) &&
|
||||
in.instructions.at(instr).is_move) {
|
||||
allowed_by_move_eliminator = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (lr.becomes_live_at_instr(instr) && other_lr.dies_next_at_instr(instr) &&
|
||||
in.instructions.at(instr).is_move) {
|
||||
allowed_by_move_eliminator = true;
|
||||
}
|
||||
} else {
|
||||
// case to allow rename (from us to them)
|
||||
if (instr == lr.max && instr == other_lr.min && in.instructions.at(instr).is_move) {
|
||||
allowed_by_move_eliminator = true;
|
||||
}
|
||||
|
||||
if (instr == lr.min && instr == other_lr.min && in.instructions.at(instr).is_move) {
|
||||
allowed_by_move_eliminator = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!allowed_by_move_eliminator) {
|
||||
if (debug_trace >= 2) {
|
||||
printf("at idx %d, %s conflicts\n", instr, other_lr.print_assignment().c_str());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -522,41 +515,42 @@ bool assignment_ok_at(int var,
|
||||
const AllocationInput& in,
|
||||
int debug_trace) {
|
||||
auto& lr = cache->live_ranges.at(var);
|
||||
for (auto& other_lr : cache->live_ranges) {
|
||||
if (other_lr.var == var /*|| !other_lr.seen*/)
|
||||
for (auto other_idx : cache->live_ranges_by_instr.at(idx)) {
|
||||
auto& other_lr = cache->live_ranges.at(other_idx);
|
||||
if (other_lr.var == var) {
|
||||
continue;
|
||||
if (other_lr.is_live_at_instr(idx)) {
|
||||
if (/*(idx != other_lr.max) &&*/ other_lr.conflicts_at(idx, ass)) {
|
||||
bool allowed_by_move_eliminator = false;
|
||||
if (move_eliminator) {
|
||||
if (enable_fancy_coloring) {
|
||||
if (lr.dies_next_at_instr(idx) && other_lr.becomes_live_at_instr(idx) &&
|
||||
in.instructions.at(idx).is_move) {
|
||||
allowed_by_move_eliminator = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (lr.becomes_live_at_instr(idx) && other_lr.dies_next_at_instr(idx) &&
|
||||
in.instructions.at(idx).is_move) {
|
||||
allowed_by_move_eliminator = true;
|
||||
}
|
||||
} else {
|
||||
// case to allow rename (from us to them)
|
||||
if (idx == lr.max && idx == other_lr.min && in.instructions.at(idx).is_move) {
|
||||
allowed_by_move_eliminator = true;
|
||||
}
|
||||
if (/*(idx != other_lr.max) &&*/ other_lr.conflicts_at(idx, ass)) {
|
||||
bool allowed_by_move_eliminator = false;
|
||||
if (move_eliminator) {
|
||||
if (enable_fancy_coloring) {
|
||||
if (lr.dies_next_at_instr(idx) && other_lr.becomes_live_at_instr(idx) &&
|
||||
in.instructions.at(idx).is_move) {
|
||||
allowed_by_move_eliminator = true;
|
||||
}
|
||||
|
||||
if (idx == lr.min && idx == other_lr.min && in.instructions.at(idx).is_move) {
|
||||
allowed_by_move_eliminator = true;
|
||||
}
|
||||
if (lr.becomes_live_at_instr(idx) && other_lr.dies_next_at_instr(idx) &&
|
||||
in.instructions.at(idx).is_move) {
|
||||
allowed_by_move_eliminator = true;
|
||||
}
|
||||
} else {
|
||||
// case to allow rename (from us to them)
|
||||
if (idx == lr.max && idx == other_lr.min && in.instructions.at(idx).is_move) {
|
||||
allowed_by_move_eliminator = true;
|
||||
}
|
||||
|
||||
if (idx == lr.min && idx == other_lr.min && in.instructions.at(idx).is_move) {
|
||||
allowed_by_move_eliminator = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!allowed_by_move_eliminator) {
|
||||
if (debug_trace >= 2) {
|
||||
printf("at idx %d, %s conflicts\n", idx, other_lr.print_assignment().c_str());
|
||||
}
|
||||
return false;
|
||||
if (!allowed_by_move_eliminator) {
|
||||
if (debug_trace >= 2) {
|
||||
printf("at idx %d, %s conflicts\n", idx, other_lr.print_assignment().c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -634,18 +628,20 @@ const std::vector<emitter::Register>& get_default_alloc_order_for_var_spill(int
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<emitter::Register>& get_default_alloc_order_for_var(int v, RegAllocCache* cache) {
|
||||
const std::vector<emitter::Register>& get_default_alloc_order_for_var(int v,
|
||||
RegAllocCache* cache,
|
||||
bool get_all) {
|
||||
auto& info = cache->iregs.at(v);
|
||||
// todo fix this.
|
||||
// assert(info.kind != emitter::RegKind::INVALID);
|
||||
if (info.kind == emitter::RegKind::GPR || info.kind == emitter::RegKind::INVALID) {
|
||||
if (cache->is_asm_func) {
|
||||
if (!get_all && cache->is_asm_func) {
|
||||
return emitter::gRegInfo.get_gpr_temp_alloc_order();
|
||||
} else {
|
||||
return emitter::gRegInfo.get_gpr_alloc_order();
|
||||
}
|
||||
} else if (info.kind == emitter::RegKind::XMM) {
|
||||
if (cache->is_asm_func) {
|
||||
if (!get_all && cache->is_asm_func) {
|
||||
return emitter::gRegInfo.get_xmm_temp_alloc_order();
|
||||
} else {
|
||||
return emitter::gRegInfo.get_xmm_alloc_order();
|
||||
@ -816,7 +812,8 @@ bool do_allocation_for_var(int var,
|
||||
}
|
||||
}
|
||||
|
||||
auto reg_order = get_default_alloc_order_for_var(var, cache);
|
||||
auto reg_order = get_default_alloc_order_for_var(var, cache, false);
|
||||
auto& all_reg_order = get_default_alloc_order_for_var(var, cache, true);
|
||||
|
||||
// todo, try other regs..
|
||||
if (!colored && move_eliminator) {
|
||||
@ -825,14 +822,14 @@ bool do_allocation_for_var(int var,
|
||||
|
||||
if (first_instr.is_move) {
|
||||
auto& possible_coloring = cache->live_ranges.at(first_instr.read.front().id).get(lr.min);
|
||||
if (possible_coloring.is_assigned() && in_vec(reg_order, possible_coloring.reg)) {
|
||||
if (possible_coloring.is_assigned() && in_vec(all_reg_order, possible_coloring.reg)) {
|
||||
colored = try_assignment_for_var(var, possible_coloring, cache, in, debug_trace);
|
||||
}
|
||||
}
|
||||
|
||||
if (!colored && last_instr.is_move) {
|
||||
auto& possible_coloring = cache->live_ranges.at(last_instr.write.front().id).get(lr.max);
|
||||
if (possible_coloring.is_assigned() && in_vec(reg_order, possible_coloring.reg)) {
|
||||
if (possible_coloring.is_assigned() && in_vec(all_reg_order, possible_coloring.reg)) {
|
||||
colored = try_assignment_for_var(var, possible_coloring, cache, in, debug_trace);
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
#define JAK_ALLOCATOR_H
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include "IRegSet.h"
|
||||
#include <unordered_map>
|
||||
#include "IRegister.h"
|
||||
#include "allocate.h"
|
||||
@ -13,8 +13,8 @@ struct RegAllocBasicBlock {
|
||||
std::vector<int> instr_idx;
|
||||
std::vector<int> succ;
|
||||
std::vector<int> pred;
|
||||
std::vector<std::set<int>> live, dead;
|
||||
std::set<int> use, defs, input, output;
|
||||
std::vector<IRegSet> live, dead;
|
||||
IRegSet use, defs, input, output;
|
||||
bool is_entry = false;
|
||||
bool is_exit = false;
|
||||
int idx = -1;
|
||||
@ -23,8 +23,8 @@ struct RegAllocBasicBlock {
|
||||
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) const;
|
||||
std::string print_summary() const;
|
||||
std::string print(const std::vector<RegAllocInstr>& insts);
|
||||
std::string print_summary();
|
||||
};
|
||||
|
||||
struct RegAllocCache {
|
||||
@ -38,6 +38,8 @@ struct RegAllocCache {
|
||||
int current_stack_slot = 0;
|
||||
bool used_stack = false;
|
||||
bool is_asm_func = false;
|
||||
|
||||
std::vector<std::vector<int>> live_ranges_by_instr;
|
||||
};
|
||||
|
||||
void find_basic_blocks(RegAllocCache* cache, const AllocationInput& in);
|
||||
|
134
goalc/regalloc/IRegSet.h
Normal file
134
goalc/regalloc/IRegSet.h
Normal file
@ -0,0 +1,134 @@
|
||||
#pragma once
|
||||
/*!
|
||||
* @file IRegSet.h
|
||||
* An efficient implementation of a set of IRegs.
|
||||
* This takes advantage of the fact that:
|
||||
* - IReg id values are pretty densely packed (ie no huge gaps in ids)
|
||||
* - Generally there are ~hundreds of IRegs in complicated functions
|
||||
* and has a bit per ireg.
|
||||
*
|
||||
* The container dynamically increases size as needed. Uninitialized values are 0.
|
||||
* Doing operations on pairs of sets will resize both to be the max size.
|
||||
* This was found to be more efficient in the end.
|
||||
*
|
||||
* The space used is (highest_set_reg + 63) / 64 + constant overhead (vector + int)
|
||||
*/
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
#include "common/common_types.h"
|
||||
|
||||
class IRegSet {
|
||||
public:
|
||||
IRegSet() = default;
|
||||
|
||||
/*!
|
||||
* Add the given ireg to the set.
|
||||
* Resizes if needed.
|
||||
*/
|
||||
void insert(int x) {
|
||||
resize(x + 1);
|
||||
assert(m_bits > x);
|
||||
auto word = x / 64;
|
||||
auto bit = x % 64;
|
||||
m_data.at(word) |= (1ll << bit);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Remove everything from the set.
|
||||
*/
|
||||
void clear() {
|
||||
for (auto& x : m_data) {
|
||||
x = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Is the given register in the set?
|
||||
* Will resize if needed. (todo - is this better than not resizing?)
|
||||
*/
|
||||
bool operator[](int x) {
|
||||
resize(x + 1);
|
||||
auto word = x / 64;
|
||||
auto bit = x % 64;
|
||||
return m_data.at(word) & (1ll << bit);
|
||||
}
|
||||
|
||||
/*!
|
||||
* The size is (maximum value we can access with operator[] without resizing) - 1
|
||||
*/
|
||||
int size() const { return m_bits; }
|
||||
|
||||
/*!
|
||||
* Is this the same set?
|
||||
* Doesn't resize either.
|
||||
*/
|
||||
bool operator==(const IRegSet& other) const {
|
||||
auto compare_size = std::min(m_data.size(), other.m_data.size());
|
||||
size_t i;
|
||||
for (i = 0; i < compare_size; i++) {
|
||||
if (m_data[i] != other.m_data[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t j = i; j < m_data.size(); j++) {
|
||||
if (m_data[j]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t j = i; j < other.m_data.size(); j++) {
|
||||
if (other.m_data[j]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator!=(const IRegSet& other) const { return !((*this) == other); }
|
||||
|
||||
void resize(int bits) {
|
||||
if (bits > m_bits) {
|
||||
auto new_vector_size = (bits + 63) / 64;
|
||||
m_data.resize(new_vector_size);
|
||||
m_bits = new_vector_size * 64;
|
||||
}
|
||||
}
|
||||
|
||||
void make_max_size(IRegSet& other) {
|
||||
if (other.size() < size()) {
|
||||
other.resize(size());
|
||||
} else {
|
||||
resize(other.size());
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* this = (this & !other).
|
||||
* Resizes the smaller one.
|
||||
*/
|
||||
void bitwise_and_not(IRegSet& other) {
|
||||
// make same size
|
||||
make_max_size(other);
|
||||
|
||||
for (size_t i = 0; i < m_data.size(); i++) {
|
||||
m_data.at(i) &= ~other.m_data.at(i);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* this = (this | other)
|
||||
* Resizes the smaller one
|
||||
*/
|
||||
void bitwise_or(IRegSet& other) {
|
||||
make_max_size(other);
|
||||
|
||||
for (size_t i = 0; i < m_data.size(); i++) {
|
||||
m_data.at(i) |= other.m_data.at(i);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<u64> m_data;
|
||||
int m_bits = 0;
|
||||
};
|
@ -72,6 +72,20 @@ struct AllocationResult {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -0,0 +1,7 @@
|
||||
(defun stack-test ()
|
||||
(let ((arr (new 'stack 'array 'uint8 12)))
|
||||
(logand #b1111 (the uint (&-> arr 3)))
|
||||
)
|
||||
)
|
||||
|
||||
(stack-test)
|
@ -0,0 +1,32 @@
|
||||
(deftype test-stack-type (basic)
|
||||
((name string)
|
||||
(count uint32)
|
||||
(pad uint8 12))
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (init-count int)) _type_ 0)
|
||||
)
|
||||
)
|
||||
|
||||
(defmethod new test-stack-type ((allocation symbol) (type-to-make type) (init-count int))
|
||||
(let ((obj (object-new)))
|
||||
(set! (-> obj name) "this is my name")
|
||||
(set! (-> obj count) init-count)
|
||||
obj
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
(defun stack-test-2 ()
|
||||
(let ((obj (new 'stack 'test-stack-type 12))
|
||||
(obj2 (new 'stack 'test-stack-type 13)))
|
||||
(if (and (= 4 (logand 15 (the uint obj)))
|
||||
(= 4 (logand 15 (the uint obj2)))
|
||||
(= 12 (-> obj count))
|
||||
)
|
||||
1234
|
||||
0
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(stack-test-2)
|
@ -85,4 +85,12 @@ TEST_F(VariableTests, InlineAsm) {
|
||||
|
||||
TEST_F(VariableTests, StaticBitfieldField) {
|
||||
runner.run_static_test(env, testCategory, "static-bitfield-field.gc", {"22\n"});
|
||||
}
|
||||
|
||||
TEST_F(VariableTests, StackArrayAlignment) {
|
||||
runner.run_static_test(env, testCategory, "stack-array-align.gc", {"3\n"});
|
||||
}
|
||||
|
||||
TEST_F(VariableTests, StackStructureAlignment) {
|
||||
runner.run_static_test(env, testCategory, "stack-structure-align.gc", {"1234\n"});
|
||||
}
|
Loading…
Reference in New Issue
Block a user