Basic Inline Assembly (#149)

* basic inline assembly support

* fix rlet

* clean up detail in IR and update documentation
This commit is contained in:
water111 2020-12-04 12:57:10 -05:00 committed by GitHub
parent ea479bee98
commit 90e5c023f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 761 additions and 53 deletions

View file

@ -61,4 +61,8 @@
- Added a `:disassemble` option to `asm-file` to disassemble functions for debugging purposes.
## V0.3
- Added typechecking when setting fields of a type.
- Added typechecking when setting fields of a type.
- Added inline assembly `.ret`, `.sub`, `.push`, and `.pop`.
- Added `rlet` to declare register variables.
- Added `:color #f` option to inline assembly forms to exclude them from the coloring system.
- Added `asm-func` to declare for purely assembly functions.

View file

@ -142,6 +142,11 @@ Documented forms are crossed out.
- &+
- ~~build-dgos~~
- ~~set-config!~~
- rlet
- .ret
- .sub
- .push
- .pop
- set-config!
# Language Basics
@ -871,7 +876,7 @@ The other two cases are handled by `let` and `defun` macros, and shouldn't show
## `declare`
Set options for a function or method
```lisp
(declare [(inline)] [(allow-inline)] [(disallow-inline)] [(asm-func)])
(declare [(inline)] [(allow-inline)] [(disallow-inline)] [(asm-func return-typespec)] [(print-asm)])
```
If used, this should be the first thing inside of a `defun`/`defmethod`. Don't use it anywhere else.
Example:
@ -884,7 +889,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.
- `asm-func` currently does nothing. Eventually should disable generating prologues/epilogues. Use if you want an entirely asm function. Used very rarely and probably only in the GOAL kernel.
- `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.
This form will probably get more options in the future.
@ -1154,6 +1160,86 @@ None of the edge cases of `the` apply to `the-as`.
## Pointer Math
Not implemented well yet.
# Compiler Forms - Assembly
## `rlet`
```lisp
(rlet ((var-name [:reg reg-name] [:class reg-class] [:type typespec])...)
body...
)
```
Create register variables. You can optionally specify a register with the `:reg` option and a register name like `rax` or `xmm3`. The initial value of the register is not set. If you don't specify a register, a GPR will be chosen for you by the coloring system and it will behave like a `let`. If you don't specify a register, you can specify a register class (`gpr` or `xmm`) and the compiler will pick a GPR or XMM for you.
If you pick a callee-saved register and use it within the coloring system, the compiler will back it up for you.
If you pick a special register like `rsp`, it won't be backed up.
Inside the `rlet`, all uses of `var-name` will always be in the given register. If the variable goes dead (or is never live), the compiler may reuse the register as it wants. The compiler may also spill the variable onto the stack. Of course, if you are in an `asm-func`, the stack will never be used.
Here is an example of using an `rlet` to access registers:
```lisp
(defun get-goal-rsp-2 ()
"Get the stack pointer as a GOAL pointer"
(rlet ((rsp :reg rsp :type uint)
(off :reg r15 :type uint))
(the pointer (- rsp off))
)
)
```
## General assembly forms
In general, assembly forms have a name that begins with a `.`. They all evaluate to `none` and copy the form of an x86-64 instruction. For example `(.sub dst src)`. A destination must be a settable register (ok if it's spilled). So you can't do something like `(.sub (-> obj field) x)`. Instead, do `(set! temp (-> obj field))`, `(.sub temp x)`, `(set! (-> obj field) temp)`. The sources can be any expression.
By default, assembly forms work with the coloring system. This means that assembly and high level expression can be mixed together without clobbering each other. It also means use of callee-saved registers will cause them to be backed up/restored in the function prologue and epilogue. Use of weird registers like `r15`, `r14`, and `rsp` works as you would expect with the coloring system.
But you can also request to skip this with `:color #f` option, like `(.push my-reg-var :color #f)`. Be very careful with this. The `:color #f` option will only work with register variables from `rlet` which have a manually specified register. It will entirely bypass the coloring system and use this register. Use of this with other GOAL code is extremely dangerous and should be done very carefully or avoided.
## `.sub`
```lisp
(.sub dest src [:color #t|#f])
```
x86-64 subtraction. If coloring is on (the default), the `dest` must be a settable register (`rlet` var, `let` var, function argument, ...). It can't be a place like a symbol, field, stack variable, etc. If coloring is off, both `src` and `dest` must be registers defined and constrained in an enclosing `rlet`.
Example:
```
(defun get-goal-rsp ()
(declare (asm-func uint))
(rlet ((rsp :reg rsp :type uint)
(off :reg r15 :type uint)
(ret :reg rax :type uint)
)
;; mov rax, rsp
(set! ret rsp)
;; sub rax, r15
(.sub ret off)
;; ret
(.ret)
)
)
```
## `.push`
```lisp
(.push src [:color #t|#f])
```
The x86-64 push instruction. Does a 64-bit GPR. The `src` can be any expression if color is on. Otherwise it must be a register defined and constrained in an enclosing `rlet`.
## `.pop`
```lisp
(.pop dst [:color #t|#f])
```
The x86-64 pop instruction. Does a 64-bit GPR. The `dst` can be any settable register if color is on. Otherwise it must be a register defined and constrained in an enclosing `rlet`.
## `.ret`
```lisp
(.ret [:color #t|#f])
```
The x86-64 ret instruction. The color option does nothing. This is not recognized as a control flow instruction by the coloring system.
# Compiler Forms - Unsorted
## `let`

View file

@ -453,4 +453,4 @@
(defmacro finish-test ()
`(format #t "Test ~A: ~D Passes~%" *test-name* *test-count*)
)
)

View file

@ -35,7 +35,8 @@ add_library(compiler
regalloc/Allocator.cpp
regalloc/allocate.cpp
regalloc/allocate_common.cpp
compiler/Compiler.cpp)
compiler/Compiler.cpp
compiler/compilation/Asm.cpp)
add_executable(goalc main.cpp)

View file

@ -6,7 +6,8 @@
*/
#include <unordered_set>
#include <goalc/debugger/DebugInfo.h>
#include "goalc/debugger/DebugInfo.h"
#include "third-party/fmt/core.h"
#include "CodeGenerator.h"
#include "goalc/emitter/IGen.h"
#include "IR.h"
@ -48,11 +49,19 @@ std::vector<u8> CodeGenerator::run() {
return m_gen.generate_data_v3().to_vector();
}
void CodeGenerator::do_function(FunctionEnv* env, int f_idx) {
if (env->is_asm_func) {
do_asm_function(env, f_idx);
} else {
do_goal_function(env, f_idx);
}
}
/*!
* Add instructions to the function, specified by index.
* Generates prologues / epilogues.
*/
void CodeGenerator::do_function(FunctionEnv* env, int f_idx) {
void CodeGenerator::do_goal_function(FunctionEnv* env, int f_idx) {
auto f_rec = m_gen.get_existing_function_record(f_idx);
// todo, extra alignment settings
@ -177,4 +186,44 @@ void CodeGenerator::do_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) {
auto f_rec = m_gen.get_existing_function_record(f_idx);
const auto& allocs = env->alloc_result();
if (!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) {
err += x.print();
err += " ";
}
err.pop_back();
err.push_back('.');
throw std::runtime_error(err);
}
if (allocs.stack_slots_for_spills) {
throw std::runtime_error("ASM Function has used the stack for spills.");
}
if (allocs.stack_slots_for_vars) {
throw std::runtime_error("ASM Function has variables on the stack.");
}
// emit each IR into x86 instructions.
for (int ir_idx = 0; ir_idx < int(env->code().size()); ir_idx++) {
auto& ir = env->code().at(ir_idx);
// start of IR
auto i_rec = m_gen.add_ir(f_rec, ir->print());
// Make sure we aren't automatically accessing the stack.
if (!allocs.stack_ops.at(ir_idx).ops.empty()) {
throw std::runtime_error("ASM Function used a bonus op.");
}
// do the actual op
ir->do_codegen(&m_gen, allocs, i_rec);
}
}

View file

@ -22,6 +22,8 @@ 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);
emitter::ObjectGenerator m_gen;
FileEnv* m_fe = nullptr;
DebugInfo* m_debug_info = nullptr;

View file

@ -64,7 +64,7 @@ void Compiler::execute_repl() {
}
} catch (std::exception& e) {
print_compiler_warning("REPL Error: %s\n", e.what());
print_compiler_warning("REPL Error: {}\n", e.what());
}
}
@ -152,6 +152,7 @@ Val* Compiler::compile_error_guard(const goos::Object& code, Env* env) {
void Compiler::color_object_file(FileEnv* env) {
for (auto& f : env->functions()) {
AllocationInput input;
input.is_asm_function = f->is_asm_func;
for (auto& i : f->code()) {
input.instructions.push_back(i->to_rai());
input.debug_instruction_names.push_back(i->print());
@ -173,10 +174,22 @@ void Compiler::color_object_file(FileEnv* env) {
}
std::vector<u8> Compiler::codegen_object_file(FileEnv* env) {
auto debug_info = &m_debugger.get_debug_info_for_object(env->name());
debug_info->clear();
CodeGenerator gen(env, debug_info);
return gen.run();
try {
auto debug_info = &m_debugger.get_debug_info_for_object(env->name());
debug_info->clear();
CodeGenerator gen(env, debug_info);
bool ok = true;
auto result = gen.run();
for (auto& f : env->functions()) {
if (f->settings.print_asm) {
fmt::print("{}\n", debug_info->disassemble_function_by_name(f->name(), &ok));
}
}
return result;
} catch (std::exception& e) {
throw_compiler_error_no_code("Error during codegen: {}", e.what());
}
return {};
}
bool Compiler::codegen_and_disassemble_object_file(FileEnv* env,
@ -186,7 +199,7 @@ bool Compiler::codegen_and_disassemble_object_file(FileEnv* env,
debug_info->clear();
CodeGenerator gen(env, debug_info);
*data_out = gen.run();
bool ok = false;
bool ok = true;
*asm_out = debug_info->disassemble_all_functions(&ok);
return ok;
}

View file

@ -44,6 +44,7 @@ class Compiler {
bool connect_to_target();
private:
bool get_true_or_false(const goos::Object& form, const goos::Object& boolean);
bool try_getting_macro_from_goos(const goos::Object& macro_name, goos::Object* dest);
void set_bitfield(const goos::Object& form, BitFieldVal* dst, RegVal* src, Env* env);
Val* do_set(const goos::Object& form, Val* dst, RegVal* src, Env* env);
@ -135,6 +136,7 @@ class Compiler {
Val* number_to_binteger(const goos::Object& form, Val* in, Env* env);
Val* to_math_type(const goos::Object& form, Val* in, MathMode mode, Env* env);
bool is_none(Val* in);
emitter::Register parse_register(const goos::Object& code);
Val* compile_enum_lookup(const goos::Object& form,
const GoalEnum& e,
const goos::Object& rest,
@ -199,6 +201,19 @@ class Compiler {
throw CompilerException("Compilation Error");
}
template <typename... Args>
void throw_compiler_error_no_code(const std::string& str, Args&&... args) {
fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, "-- Compilation Error! --\n");
if (!str.empty() && str.back() == '\n') {
fmt::print(str, std::forward<Args>(args)...);
} else {
fmt::print(str + '\n', std::forward<Args>(args)...);
}
fmt::print(fg(fmt::color::yellow) | fmt::emphasis::bold, "Form:\n");
throw CompilerException("Compilation Error");
}
template <typename... Args>
void print_compiler_warning(const std::string& str, Args&&... args) {
fmt::print(fg(fmt::color::yellow) | fmt::emphasis::bold, "[Warning] ");
@ -210,6 +225,13 @@ class Compiler {
}
public:
// Asm
Val* compile_rlet(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_asm_ret(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_asm_push(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_asm_pop(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_asm_sub(const goos::Object& form, const goos::Object& rest, Env* env);
// Atoms
// Block

View file

@ -39,9 +39,13 @@ class Env {
RegVal* make_gpr(const TypeSpec& ts);
RegVal* make_xmm(const TypeSpec& ts);
virtual ~Env() = default;
Env* parent() { return m_parent; }
template <typename IR_Type, typename... Args>
void emit_ir(Args&&... args) {
emit(std::make_unique<IR_Type>(std::forward<Args>(args)...));
}
protected:
Env* m_parent = nullptr;
};
@ -125,6 +129,7 @@ class DeclareEnv : public Env {
bool inline_by_default = false; // if a function, inline when possible?
bool save_code = true; // if a function, should we save the code?
bool allow_inline = false; // should we allow the user to use this an inline function
bool print_asm = false; // should we print out the asm for this function?
} settings;
};
@ -188,6 +193,7 @@ class FunctionEnv : public DeclareEnv {
int segment = -1;
std::string method_of_type_name = "#f";
bool is_asm_func = false;
TypeSpec asm_func_return_type;
std::vector<UnresolvedGoto> unresolved_gotos;
std::vector<UnresolvedConditionalGoto> unresolved_cond_gotos;
std::unordered_map<std::string, RegVal*> params;

View file

@ -7,9 +7,38 @@ using namespace emitter;
namespace {
Register get_reg(const RegVal* rv, const AllocationResult& allocs, emitter::IR_Record irec) {
auto& ass = allocs.ass_as_ranges.at(rv->ireg().id).get(irec.ir_id);
assert(ass.kind == Assignment::Kind::REGISTER);
return ass.reg;
if (rv->rlet_constraint().has_value()) {
auto& range = allocs.ass_as_ranges;
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) {
auto ass_reg = range.at(rv->ireg().id).get(irec.ir_id);
if (ass_reg.kind == Assignment::Kind::REGISTER) {
assert(ass_reg.reg == reg);
} else {
assert(false);
}
} else {
assert(false);
}
} else {
assert(false);
}
return reg;
} else {
auto& ass = allocs.ass_as_ranges.at(rv->ireg().id).get(irec.ir_id);
assert(ass.kind == Assignment::Kind::REGISTER);
return ass.reg;
}
}
Register get_no_color_reg(const RegVal* rv) {
if (!rv->rlet_constraint().has_value()) {
throw std::runtime_error(
"Accessed a non-rlet constrained variable without the coloring system.");
}
return rv->rlet_constraint().value();
}
void load_constant(u64 value,
@ -928,4 +957,127 @@ void IR_GetStackAddr::do_codegen(emitter::ObjectGenerator* gen,
gen->add_instr(IGen::add_gpr64_gpr64(dest_reg, RSP), irec);
// dest = offset + RSP - offset
gen->add_instr(IGen::sub_gpr64_gpr64(dest_reg, gRegInfo.get_offset_reg()), irec);
}
///////////////////////
// Asm
///////////////////////
IR_Asm::IR_Asm(bool use_coloring) : m_use_coloring(use_coloring) {}
std::string IR_Asm::get_color_suffix_string() {
if (m_use_coloring) {
return "";
} else {
return " :no-color";
}
}
///////////////////////
// AsmRet
///////////////////////
IR_AsmRet::IR_AsmRet(bool use_coloring) : IR_Asm(use_coloring) {}
std::string IR_AsmRet::print() {
return fmt::format(".ret{}", get_color_suffix_string());
}
RegAllocInstr IR_AsmRet::to_rai() {
return {};
}
void IR_AsmRet::do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) {
(void)allocs;
gen->add_instr(IGen::ret(), irec);
}
///////////////////////
// AsmPush
///////////////////////
IR_AsmPush::IR_AsmPush(bool use_coloring, const RegVal* src) : IR_Asm(use_coloring), m_src(src) {}
std::string IR_AsmPush::print() {
return fmt::format(".push{} {}", get_color_suffix_string(), m_src->print());
}
RegAllocInstr IR_AsmPush::to_rai() {
RegAllocInstr rai;
if (m_use_coloring) {
rai.read.push_back(m_src->ireg());
}
return rai;
}
void IR_AsmPush::do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) {
if (m_use_coloring) {
gen->add_instr(IGen::push_gpr64(get_reg(m_src, allocs, irec)), irec);
} else {
gen->add_instr(IGen::push_gpr64(get_no_color_reg(m_src)), irec);
}
}
///////////////////////
// AsmPop
///////////////////////
IR_AsmPop::IR_AsmPop(bool use_coloring, const RegVal* dst) : IR_Asm(use_coloring), m_dst(dst) {}
std::string IR_AsmPop::print() {
return fmt::format(".pop{} {}", get_color_suffix_string(), m_dst->print());
}
RegAllocInstr IR_AsmPop::to_rai() {
RegAllocInstr rai;
if (m_use_coloring) {
rai.write.push_back(m_dst->ireg());
}
return rai;
}
void IR_AsmPop::do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) {
if (m_use_coloring) {
gen->add_instr(IGen::pop_gpr64(get_reg(m_dst, allocs, irec)), irec);
} else {
gen->add_instr(IGen::pop_gpr64(get_no_color_reg(m_dst)), irec);
}
}
///////////////////////
// AsmSub
///////////////////////
IR_AsmSub::IR_AsmSub(bool use_coloring, const RegVal* dst, const RegVal* src)
: IR_Asm(use_coloring), m_dst(dst), m_src(src) {}
std::string IR_AsmSub::print() {
return fmt::format(".sub{} {}, {}", get_color_suffix_string(), m_dst->print(), m_src->print());
}
RegAllocInstr IR_AsmSub::to_rai() {
RegAllocInstr rai;
if (m_use_coloring) {
rai.write.push_back(m_dst->ireg());
rai.read.push_back(m_dst->ireg());
rai.read.push_back(m_src->ireg());
}
return rai;
}
void IR_AsmSub::do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) {
if (m_use_coloring) {
gen->add_instr(
IGen::sub_gpr64_gpr64(get_reg(m_dst, allocs, irec), get_reg(m_src, allocs, irec)), irec);
} else {
gen->add_instr(IGen::sub_gpr64_gpr64(get_no_color_reg(m_dst), get_no_color_reg(m_src)), irec);
}
}

View file

@ -371,4 +371,63 @@ class IR_GetStackAddr : public IR {
int m_slot = -1;
};
class IR_Asm : public IR {
public:
explicit IR_Asm(bool use_coloring);
std::string get_color_suffix_string();
protected:
bool m_use_coloring;
};
class IR_AsmRet : public IR_Asm {
public:
IR_AsmRet(bool use_coloring);
std::string print() override;
RegAllocInstr to_rai() override;
void do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) override;
};
class IR_AsmPush : public IR_Asm {
public:
IR_AsmPush(bool use_coloring, const RegVal* src);
std::string print() override;
RegAllocInstr to_rai() override;
void do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) override;
private:
const RegVal* m_src = nullptr;
};
class IR_AsmPop : public IR_Asm {
public:
IR_AsmPop(bool use_coloring, const RegVal* dst);
std::string print() override;
RegAllocInstr to_rai() override;
void do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) override;
private:
const RegVal* m_dst = nullptr;
};
class IR_AsmSub : public IR_Asm {
public:
IR_AsmSub(bool use_coloring, const RegVal* dst, const RegVal* src);
std::string print() override;
RegAllocInstr to_rai() override;
void do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) override;
private:
const RegVal* m_dst = nullptr;
const RegVal* m_src = nullptr;
};
#endif // JAK_IR_H

View file

@ -174,4 +174,18 @@ float Compiler::try_getting_constant_float(const goos::Object& in, float* out, E
// todo, try more things like constants before giving up.
return false;
}
bool Compiler::get_true_or_false(const goos::Object& form, const goos::Object& boolean) {
// todo try other things.
if (boolean.is_symbol()) {
if (boolean.as_symbol()->name == "#t") {
return true;
}
if (boolean.as_symbol()->name == "#f") {
return false;
}
}
throw_compiler_error(form, "The value {} cannot be used as a boolean.", boolean.print());
return false;
}

View file

@ -59,6 +59,14 @@ RegVal* RegVal::to_xmm(Env* fe) {
}
}
void RegVal::set_rlet_constraint(emitter::Register reg) {
m_rlet_constraint = reg;
}
const std::optional<emitter::Register>& RegVal::rlet_constraint() const {
return m_rlet_constraint;
}
RegVal* IntegerConstantVal::to_reg(Env* fe) {
auto rv = fe->make_gpr(coerce_to_reg_type(m_ts));
fe->emit(std::make_unique<IR_LoadConstant64>(rv, m_value));

View file

@ -11,6 +11,7 @@
#include <utility>
#include <string>
#include <stdexcept>
#include <optional>
#include "common/type_system/TypeSystem.h"
#include "goalc/regalloc/IRegister.h"
#include "Lambda.h"
@ -74,9 +75,12 @@ class RegVal : public Val {
RegVal* to_reg(Env* fe) override;
RegVal* to_gpr(Env* fe) override;
RegVal* to_xmm(Env* fe) override;
void set_rlet_constraint(emitter::Register reg);
const std::optional<emitter::Register>& rlet_constraint() const;
protected:
IRegister m_ireg;
std::optional<emitter::Register> m_rlet_constraint = std::nullopt;
};
/*!

View file

@ -0,0 +1,154 @@
#include "goalc/compiler/Compiler.h"
namespace {
const char* reg_names[] = {
"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", "r8", "r9", "r10",
"r11", "r12", "r13", "r14", "r15", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5",
"xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15",
};
}
emitter::Register Compiler::parse_register(const goos::Object& code) {
if (!code.is_symbol()) {
throw_compiler_error(code, "Could not parse {} as a register name", code.print());
}
auto nas = code.as_symbol();
for (int i = 0; i < 32; i++) {
if (nas->name == reg_names[i]) {
return emitter::Register(i);
}
}
throw_compiler_error(code, "Could not parse {} as a register name", code.print());
return {};
}
Val* Compiler::compile_rlet(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
if (args.unnamed.size() < 1 || !args.named.empty()) {
throw_compiler_error(form, "Must have an rlet body.");
}
auto defs = args.unnamed.front();
auto fenv = get_parent_env_of_type<FunctionEnv>(env);
auto lenv = fenv->alloc_env<LexicalEnv>(env);
std::vector<IRegConstraint> constraints;
for_each_in_list(defs, [&](const goos::Object& o) {
// (new-place [:reg old-place] [:type type-spec] [:class reg-type] [:bind #f|lexical|lambda])
auto def_args = get_va(o, o);
va_check(o, def_args, {goos::ObjectType::SYMBOL},
{{"reg", {false, goos::ObjectType::SYMBOL}},
{"type", {false, {}}},
{"class", {false, goos::ObjectType::SYMBOL}}});
// get the name of the new place
auto new_place_name = def_args.unnamed.at(0);
// get the type of the new place
TypeSpec ts = m_ts.make_typespec("object");
if (def_args.has_named("type")) {
ts = parse_typespec(def_args.named.at("type"));
}
// figure out the class
emitter::RegKind register_kind = emitter::RegKind::GPR;
if (def_args.has_named("class")) {
auto& class_name = def_args.named.at("class").as_symbol()->name;
if (class_name == "gpr") {
register_kind = emitter::RegKind::GPR;
} else if (class_name == "xmm") {
register_kind = emitter::RegKind::XMM;
} else {
throw_compiler_error(o, "Register class {} is unknown.", class_name);
}
}
// alloc a register:
auto new_place_reg = env->make_ireg(ts, register_kind);
new_place_reg->mark_as_settable();
if (def_args.has_named("reg")) {
IRegConstraint constraint;
constraint.ireg = new_place_reg->ireg();
constraint.contrain_everywhere = true;
constraint.desired_register = parse_register(def_args.named.at("reg"));
new_place_reg->set_rlet_constraint(constraint.desired_register);
constraints.push_back(constraint);
}
lenv->vars[new_place_name.as_symbol()->name] = new_place_reg;
});
Val* result = get_none();
for (u64 i = 1; i < args.unnamed.size(); i++) {
auto& o = args.unnamed.at(i);
result = compile_error_guard(o, lenv);
if (!dynamic_cast<None*>(result)) {
result = result->to_reg(lenv);
}
}
for (auto c : constraints) {
fenv->constrain(c);
}
return result;
}
Val* Compiler::compile_asm_ret(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {}, {{"color", {false, goos::ObjectType::SYMBOL}}});
bool color = true;
if (args.has_named("color")) {
color = get_true_or_false(form, args.named.at("color"));
}
env->emit_ir<IR_AsmRet>(color);
return get_none();
}
Val* Compiler::compile_asm_pop(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {{}}, {{"color", {false, goos::ObjectType::SYMBOL}}});
bool color = true;
if (args.has_named("color")) {
color = get_true_or_false(form, args.named.at("color"));
}
auto pop_dest = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env);
if (!pop_dest->settable()) {
throw_compiler_error(form, "Cannot pop into this destination. Got a {}.", pop_dest->print());
}
env->emit_ir<IR_AsmPop>(color, pop_dest);
return get_none();
}
Val* Compiler::compile_asm_push(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {{}}, {{"color", {false, goos::ObjectType::SYMBOL}}});
bool color = true;
if (args.has_named("color")) {
color = get_true_or_false(form, args.named.at("color"));
}
env->emit_ir<IR_AsmPush>(color, compile_error_guard(args.unnamed.at(0), env)->to_gpr(env));
return get_none();
}
Val* Compiler::compile_asm_sub(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {{}, {}}, {{"color", {false, goos::ObjectType::SYMBOL}}});
bool color = true;
if (args.has_named("color")) {
color = get_true_or_false(form, args.named.at("color"));
}
auto dest = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env);
if (!dest->settable()) {
throw_compiler_error(form, "Cannot .sub this. Got a {}.", dest->print());
}
auto src = compile_error_guard(args.unnamed.at(1), env)->to_gpr(env);
env->emit_ir<IR_AsmSub>(color, dest, src);
return get_none();
}

View file

@ -13,12 +13,13 @@ static const std::unordered_map<
std::string,
Val* (Compiler::*)(const goos::Object& form, const goos::Object& rest, Env* env)>
goal_forms = {
// // inline asm
// {".ret", &Compiler::compile_asm},
// {".push", &Compiler::compile_asm},
// {".pop", &Compiler::compile_asm},
// inline asm
{".ret", &Compiler::compile_asm_ret},
{".push", &Compiler::compile_asm_push},
{".pop", &Compiler::compile_asm_pop},
{"rlet", &Compiler::compile_rlet},
// {".jmp", &Compiler::compile_asm},
// {".sub", &Compiler::compile_asm},
{".sub", &Compiler::compile_asm_sub},
// {".ret-reg", &Compiler::compile_asm},
// BLOCK FORMS
@ -86,7 +87,6 @@ static const std::unordered_map<
{"declare", &Compiler::compile_declare},
{"inline", &Compiler::compile_inline},
// {"with-inline", &Compiler::compile_with_inline},
// {"rlet", &Compiler::compile_rlet},
// {"get-ra-ptr", &Compiler::compile_get_ra_ptr},
// MACRO

View file

@ -187,14 +187,17 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
new_func_env->settings.is_set = true;
}
});
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)) {
// got a result, so to_gpr it and return it.
auto final_result = result->to_gpr(new_func_env.get());
new_func_env->emit(std::make_unique<IR_Return>(return_reg, final_result));
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);
// lambda_ts.add_arg(final_result->type());
} else {
// empty body or returning none, return none
lambda_ts.add_arg(m_ts.make_typespec("none"));
@ -535,7 +538,7 @@ Val* Compiler::compile_declare(const goos::Object& form, const goos::Object& res
}
auto first = o.as_pair()->car;
auto rrest = o.as_pair()->cdr;
auto rrest = &o.as_pair()->cdr;
if (!first.is_symbol()) {
throw_compiler_error(
@ -544,21 +547,35 @@ Val* Compiler::compile_declare(const goos::Object& form, const goos::Object& res
}
if (first.as_symbol()->name == "inline") {
if (!rrest.is_empty_list()) {
if (!rrest->is_empty_list()) {
throw_compiler_error(first, "Invalid inline declare, no options were expected.");
}
settings.allow_inline = true;
settings.inline_by_default = true;
settings.save_code = true;
} else if (first.as_symbol()->name == "allow-inline") {
if (!rrest.is_empty_list()) {
if (!rrest->is_empty_list()) {
throw_compiler_error(first, "Invalid allow-inline declare");
}
settings.allow_inline = true;
settings.inline_by_default = false;
settings.save_code = true;
} else if (first.as_symbol()->name == "asm-func") {
get_parent_env_of_type<FunctionEnv>(env)->is_asm_func = true;
auto fe = get_parent_env_of_type<FunctionEnv>(env);
fe->is_asm_func = true;
if (!rrest->is_pair()) {
throw_compiler_error(
form, "Declare asm-func must provide the function's return type as an argument.");
}
fe->asm_func_return_type = parse_typespec(rrest->as_pair()->car);
if (!rrest->as_pair()->cdr.is_empty_list()) {
throw_compiler_error(first, "Invalid asm-func declare");
}
} else if (first.as_symbol()->name == "print-asm") {
if (!rrest->is_empty_list()) {
throw_compiler_error(first, "Invalid print-asm declare");
}
settings.print_asm = true;
} else {
throw_compiler_error(first, "Unrecognized declare option {}.", first.print());
}

View file

@ -28,4 +28,14 @@ std::string DebugInfo::disassemble_all_functions(bool* had_failure) {
result += kv.second.disassemble_debug_info(had_failure) + "\n\n";
}
return result;
}
std::string DebugInfo::disassemble_function_by_name(const std::string& name, bool* had_failure) {
std::string result;
for (auto& kv : m_functions) {
if (kv.second.name == name) {
result += kv.second.disassemble_debug_info(had_failure) + "\n\n";
}
}
return result;
}

View file

@ -49,6 +49,7 @@ class DebugInfo {
void clear() { m_functions.clear(); }
std::string disassemble_all_functions(bool* had_failure);
std::string disassemble_function_by_name(const std::string& name, bool* had_failure);
private:
std::string m_obj_name;

View file

@ -57,6 +57,10 @@ RegisterInfo RegisterInfo::make_register_info() {
info.m_xmm_alloc_order = {XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7,
XMM8, XMM9, XMM10, XMM11, XMM12, XMM13, XMM14};
// these should only be temp registers!
info.m_gpr_temp_only_alloc_order = {RAX, RCX, RDX, RSI, RDI, R8, R9};
info.m_xmm_temp_only_alloc_order = {XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7};
info.m_gpr_spill_temp_alloc_order = {RAX, RCX, RDX, RBX, RBP, RSI,
RDI, R8, R9, R10, R11, R12}; // arbitrary
info.m_xmm_spill_temp_alloc_order = {XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7,

View file

@ -122,29 +122,19 @@ class RegisterInfo {
};
const Info& get_info(Register r) const { return m_info.at(r.id()); }
Register get_arg_reg(int id) const { return m_arg_regs.at(id); }
Register get_saved_gpr(int id) const { return m_saved_gprs.at(id); }
Register get_saved_xmm(int id) const { return m_saved_xmms.at(id); }
Register get_process_reg() const { return R13; }
Register get_st_reg() const { return R14; }
Register get_offset_reg() const { return R15; }
Register get_ret_reg() const { return RAX; }
const std::vector<Register>& get_gpr_alloc_order() { return m_gpr_alloc_order; }
const std::vector<Register>& get_xmm_alloc_order() { return m_xmm_alloc_order; }
const std::vector<Register>& get_gpr_temp_alloc_order() { return m_gpr_temp_only_alloc_order; }
const std::vector<Register>& get_xmm_temp_alloc_order() { return m_xmm_temp_only_alloc_order; }
const std::vector<Register>& get_gpr_spill_alloc_order() { return m_gpr_spill_temp_alloc_order; }
const std::vector<Register>& get_xmm_spill_alloc_order() { return m_xmm_spill_temp_alloc_order; }
const std::array<Register, N_SAVED_XMMS + N_SAVED_GPRS>& get_all_saved() { return m_saved_all; }
private:
@ -156,6 +146,8 @@ class RegisterInfo {
std::array<Register, N_SAVED_XMMS + N_SAVED_GPRS> m_saved_all;
std::vector<Register> m_gpr_alloc_order;
std::vector<Register> m_xmm_alloc_order;
std::vector<Register> m_gpr_temp_only_alloc_order;
std::vector<Register> m_xmm_temp_only_alloc_order;
std::vector<Register> m_gpr_spill_temp_alloc_order;
std::vector<Register> m_xmm_spill_temp_alloc_order;
};

View file

@ -119,7 +119,9 @@ void compute_live_ranges(RegAllocCache* cache, const AllocationInput& in) {
// make us alive at any constrained instruction. todo, if this happens is this a sign of an issue
for (auto& con : in.constraints) {
cache->live_ranges.at(con.ireg.id).add_live_instruction(con.instr_idx);
if (!con.contrain_everywhere) {
cache->live_ranges.at(con.ireg.id).add_live_instruction(con.instr_idx);
}
}
}
} // namespace
@ -346,7 +348,11 @@ void do_constrained_alloc(RegAllocCache* cache, const AllocationInput& in, bool
if (trace_debug) {
fmt::print("[RA] Apply constraint {}\n", constr.to_string());
}
cache->live_ranges.at(var_id).constrain_at_one(constr.instr_idx, constr.desired_register);
if (constr.contrain_everywhere) {
cache->live_ranges.at(var_id).constrain_everywhere(constr.desired_register);
} else {
cache->live_ranges.at(var_id).constrain_at_one(constr.instr_idx, constr.desired_register);
}
}
}
@ -356,12 +362,24 @@ void do_constrained_alloc(RegAllocCache* cache, const AllocationInput& in, bool
bool check_constrained_alloc(RegAllocCache* cache, const AllocationInput& in) {
bool ok = true;
for (auto& constr : in.constraints) {
if (!cache->live_ranges.at(constr.ireg.id)
.conflicts_at(constr.instr_idx, constr.desired_register)) {
fmt::print("[RegAlloc Error] There are conflicting constraints on {}: {} and {}\n",
constr.ireg.to_string(), constr.desired_register.print(),
cache->live_ranges.at(constr.ireg.id).get(constr.instr_idx).to_string());
ok = false;
if (constr.contrain_everywhere) {
auto& lr = cache->live_ranges.at(constr.ireg.id);
for (int i = lr.min; i <= lr.max; i++) {
if (!lr.conflicts_at(i, constr.desired_register)) {
fmt::print("[RegAlloc Error] There are conflicting constraints on {}: {} and {}\n",
constr.ireg.to_string(), constr.desired_register.print(),
cache->live_ranges.at(constr.ireg.id).get(i).to_string());
ok = false;
}
}
} else {
if (!cache->live_ranges.at(constr.ireg.id)
.conflicts_at(constr.instr_idx, constr.desired_register)) {
fmt::print("[RegAlloc Error] There are conflicting constraints on {}: {} and {}\n",
constr.ireg.to_string(), constr.desired_register.print(),
cache->live_ranges.at(constr.ireg.id).get(constr.instr_idx).to_string());
ok = false;
}
}
}
@ -618,11 +636,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) {
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) {
return emitter::gRegInfo.get_gpr_alloc_order();
if (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) {
return emitter::gRegInfo.get_xmm_alloc_order();
if (cache->is_asm_func) {
return emitter::gRegInfo.get_xmm_temp_alloc_order();
} else {
return emitter::gRegInfo.get_xmm_alloc_order();
}
} else {
throw std::runtime_error("Unsupported RegKind");
}
@ -758,7 +785,10 @@ bool try_spill_coloring(int var, RegAllocCache* cache, const AllocationInput& in
bonus.slot = get_stack_slot_for_var(var, cache);
bonus.load = is_read;
bonus.store = is_written;
cache->stack_ops.at(instr).ops.push_back(bonus);
if (bonus.load || bonus.store) {
cache->stack_ops.at(instr).ops.push_back(bonus);
}
}
return true;
}

View file

@ -37,6 +37,7 @@ struct RegAllocCache {
std::unordered_map<int, int> var_to_stack_slot;
int current_stack_slot = 0;
bool used_stack = false;
bool is_asm_func = false;
};
void find_basic_blocks(RegAllocCache* cache, const AllocationInput& in);

View file

@ -15,5 +15,9 @@ std::string IRegister::to_string() const {
}
std::string IRegConstraint::to_string() const {
return fmt::format("[{:3d}] {} in {}", instr_idx, ireg.to_string(), desired_register.print());
if (contrain_everywhere) {
return fmt::format("[all] {} in {}", ireg.to_string(), desired_register.print());
} else {
return fmt::format("[{:3d}] {} in {}", instr_idx, ireg.to_string(), desired_register.print());
}
}

View file

@ -23,6 +23,7 @@ struct IRegister {
struct IRegConstraint {
IRegister ireg;
int instr_idx = -1;
bool contrain_everywhere = false;
emitter::Register desired_register;
std::string to_string() const;
};

View file

@ -126,6 +126,7 @@ void print_result(const AllocationInput& in, const AllocationResult& result) {
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) {

View file

@ -83,6 +83,7 @@ struct AllocationInput {
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;

View file

@ -77,6 +77,7 @@ struct LiveInfo {
* 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)
@ -162,6 +163,20 @@ struct LiveInfo {
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?
*/

View file

@ -0,0 +1,53 @@
(defun test-asm-func ()
(declare (asm-func uint64))
(rlet ((x :reg rbx :type int)
(ret :reg rax :type int))
(.push x :color #f)
(.push 3)
(.pop x :color #f)
(.push x :color #f)
(.pop ret)
(.pop x :color #f)
)
(.ret)
)
(defun get-goal-rsp ()
(declare (asm-func uint)
(print-asm))
(rlet ((rsp :reg rsp :type uint)
(off :reg r15 :type uint)
(ret :reg rax :type uint)
(unused :reg r11) ;; just to test that this isn't a compiler error.
)
;; just something weird to make the regalloc more interesting
(.push off)
(set! off (the uint 12))
(.pop off)
;; do the actual computation
(set! ret rsp)
(.sub ret off)
;; return!
(.ret)
)
)
(defun get-goal-rsp-2 ()
(rlet ((rsp :reg rsp :type uint)
(off :reg r15 :type uint))
(- rsp off)
)
)
(if (and
(= (get-goal-rsp) (get-goal-rsp-2))
(< #x7ffff00 (get-goal-rsp))
(> #x7ffffff (get-goal-rsp))
(= 3 (test-asm-func))
)
1
0)

View file

@ -77,4 +77,8 @@ TEST_F(VariableTests, StackVars) {
TEST_F(VariableTests, Bitfields) {
runner.run_static_test(env, testCategory, "bitfield-enums.gc", {"5\n"});
runner.run_static_test(env, testCategory, "integer-enums.gc", {"11\n"});
}
TEST_F(VariableTests, InlineAsm) {
runner.run_static_test(env, testCategory, "inline-asm.static.gc", {"1\n"});
}