diff --git a/doc/changelog.md b/doc/changelog.md index 4f196f2d2..67c9c54ac 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -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. \ No newline at end of file +- 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. \ No newline at end of file diff --git a/doc/goal_doc.md b/doc/goal_doc.md index 2cf4ccda3..4dc017f38 100644 --- a/doc/goal_doc.md +++ b/doc/goal_doc.md @@ -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` diff --git a/goal_src/goal-lib.gc b/goal_src/goal-lib.gc index 6cf6aac6f..ede0d2205 100644 --- a/goal_src/goal-lib.gc +++ b/goal_src/goal-lib.gc @@ -453,4 +453,4 @@ (defmacro finish-test () `(format #t "Test ~A: ~D Passes~%" *test-name* *test-count*) - ) \ No newline at end of file + ) diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index 13a96f5ae..5ae068127 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -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) diff --git a/goalc/compiler/CodeGenerator.cpp b/goalc/compiler/CodeGenerator.cpp index 22d6f6ccd..c476024ae 100644 --- a/goalc/compiler/CodeGenerator.cpp +++ b/goalc/compiler/CodeGenerator.cpp @@ -6,7 +6,8 @@ */ #include -#include +#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 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); + } } \ No newline at end of file diff --git a/goalc/compiler/CodeGenerator.h b/goalc/compiler/CodeGenerator.h index 102f1781c..986ab211c 100644 --- a/goalc/compiler/CodeGenerator.h +++ b/goalc/compiler/CodeGenerator.h @@ -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; diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index c59cdff22..8ce60dfeb 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -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 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; } diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index 8674a8427..08ea8edfc 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -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 + 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)...); + } else { + fmt::print(str + '\n', std::forward(args)...); + } + + fmt::print(fg(fmt::color::yellow) | fmt::emphasis::bold, "Form:\n"); + throw CompilerException("Compilation Error"); + } + template 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 diff --git a/goalc/compiler/Env.h b/goalc/compiler/Env.h index 4cec30189..c3435a501 100644 --- a/goalc/compiler/Env.h +++ b/goalc/compiler/Env.h @@ -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 + void emit_ir(Args&&... args) { + emit(std::make_unique(std::forward(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 unresolved_gotos; std::vector unresolved_cond_gotos; std::unordered_map params; diff --git a/goalc/compiler/IR.cpp b/goalc/compiler/IR.cpp index b1ff333d5..dc494132b 100644 --- a/goalc/compiler/IR.cpp +++ b/goalc/compiler/IR.cpp @@ -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); + } } \ No newline at end of file diff --git a/goalc/compiler/IR.h b/goalc/compiler/IR.h index 55ac55a62..3b33427f2 100644 --- a/goalc/compiler/IR.h +++ b/goalc/compiler/IR.h @@ -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 diff --git a/goalc/compiler/Util.cpp b/goalc/compiler/Util.cpp index 9e9bea641..cf1dcb5ff 100644 --- a/goalc/compiler/Util.cpp +++ b/goalc/compiler/Util.cpp @@ -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; } \ No newline at end of file diff --git a/goalc/compiler/Val.cpp b/goalc/compiler/Val.cpp index ccd410587..d35a44d60 100644 --- a/goalc/compiler/Val.cpp +++ b/goalc/compiler/Val.cpp @@ -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& 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(rv, m_value)); diff --git a/goalc/compiler/Val.h b/goalc/compiler/Val.h index 74521132c..142e82ae6 100644 --- a/goalc/compiler/Val.h +++ b/goalc/compiler/Val.h @@ -11,6 +11,7 @@ #include #include #include +#include #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& rlet_constraint() const; protected: IRegister m_ireg; + std::optional m_rlet_constraint = std::nullopt; }; /*! diff --git a/goalc/compiler/compilation/Asm.cpp b/goalc/compiler/compilation/Asm.cpp new file mode 100644 index 000000000..4248c3b75 --- /dev/null +++ b/goalc/compiler/compilation/Asm.cpp @@ -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(env); + auto lenv = fenv->alloc_env(env); + + std::vector 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(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(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(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(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(color, dest, src); + return get_none(); +} \ No newline at end of file diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp index 72a330eaf..66c4c9350 100644 --- a/goalc/compiler/compilation/Atoms.cpp +++ b/goalc/compiler/compilation/Atoms.cpp @@ -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 diff --git a/goalc/compiler/compilation/Function.cpp b/goalc/compiler/compilation/Function.cpp index cdfd0c134..bf2b5a377 100644 --- a/goalc/compiler/compilation/Function.cpp +++ b/goalc/compiler/compilation/Function.cpp @@ -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(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(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(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(env)->is_asm_func = true; + auto fe = get_parent_env_of_type(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()); } diff --git a/goalc/debugger/DebugInfo.cpp b/goalc/debugger/DebugInfo.cpp index d88e3405b..cfc219f8b 100644 --- a/goalc/debugger/DebugInfo.cpp +++ b/goalc/debugger/DebugInfo.cpp @@ -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; } \ No newline at end of file diff --git a/goalc/debugger/DebugInfo.h b/goalc/debugger/DebugInfo.h index f0e70add8..951d0926a 100644 --- a/goalc/debugger/DebugInfo.h +++ b/goalc/debugger/DebugInfo.h @@ -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; diff --git a/goalc/emitter/Register.cpp b/goalc/emitter/Register.cpp index 1999622c7..2b6d06772 100644 --- a/goalc/emitter/Register.cpp +++ b/goalc/emitter/Register.cpp @@ -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, diff --git a/goalc/emitter/Register.h b/goalc/emitter/Register.h index b39cc713a..1a2866276 100644 --- a/goalc/emitter/Register.h +++ b/goalc/emitter/Register.h @@ -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& get_gpr_alloc_order() { return m_gpr_alloc_order; } - const std::vector& get_xmm_alloc_order() { return m_xmm_alloc_order; } - + const std::vector& get_gpr_temp_alloc_order() { return m_gpr_temp_only_alloc_order; } + const std::vector& get_xmm_temp_alloc_order() { return m_xmm_temp_only_alloc_order; } const std::vector& get_gpr_spill_alloc_order() { return m_gpr_spill_temp_alloc_order; } - const std::vector& get_xmm_spill_alloc_order() { return m_xmm_spill_temp_alloc_order; } - const std::array& get_all_saved() { return m_saved_all; } private: @@ -156,6 +146,8 @@ class RegisterInfo { std::array m_saved_all; std::vector m_gpr_alloc_order; std::vector m_xmm_alloc_order; + std::vector m_gpr_temp_only_alloc_order; + std::vector m_xmm_temp_only_alloc_order; std::vector m_gpr_spill_temp_alloc_order; std::vector m_xmm_spill_temp_alloc_order; }; diff --git a/goalc/regalloc/Allocator.cpp b/goalc/regalloc/Allocator.cpp index 41d52a8b3..8776e6b6e 100644 --- a/goalc/regalloc/Allocator.cpp +++ b/goalc/regalloc/Allocator.cpp @@ -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& get_default_alloc_order_for_var_spill(int const std::vector& 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; } diff --git a/goalc/regalloc/Allocator.h b/goalc/regalloc/Allocator.h index b1540de24..2982cb6fa 100644 --- a/goalc/regalloc/Allocator.h +++ b/goalc/regalloc/Allocator.h @@ -37,6 +37,7 @@ struct RegAllocCache { std::unordered_map 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); diff --git a/goalc/regalloc/IRegister.cpp b/goalc/regalloc/IRegister.cpp index 6e7a1c580..f78982d2f 100644 --- a/goalc/regalloc/IRegister.cpp +++ b/goalc/regalloc/IRegister.cpp @@ -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()); + } } \ No newline at end of file diff --git a/goalc/regalloc/IRegister.h b/goalc/regalloc/IRegister.h index 51d07decb..fb0aed566 100644 --- a/goalc/regalloc/IRegister.h +++ b/goalc/regalloc/IRegister.h @@ -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; }; diff --git a/goalc/regalloc/allocate.cpp b/goalc/regalloc/allocate.cpp index 17722bec3..5e6bb24aa 100644 --- a/goalc/regalloc/allocate.cpp +++ b/goalc/regalloc/allocate.cpp @@ -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) { diff --git a/goalc/regalloc/allocate.h b/goalc/regalloc/allocate.h index 01e2d08b6..298be2f21 100644 --- a/goalc/regalloc/allocate.h +++ b/goalc/regalloc/allocate.h @@ -83,6 +83,7 @@ struct AllocationInput { int max_vars = -1; // maximum register id. std::vector debug_instruction_names; // optional, for debug prints int stack_slots_for_stack_vars = 0; + bool is_asm_function = false; struct { bool print_input = false; diff --git a/goalc/regalloc/allocate_common.h b/goalc/regalloc/allocate_common.h index 8a2dfc7f0..9f5499dc3 100644 --- a/goalc/regalloc/allocate_common.h +++ b/goalc/regalloc/allocate_common.h @@ -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? */ diff --git a/test/goalc/source_templates/variables/inline-asm.static.gc b/test/goalc/source_templates/variables/inline-asm.static.gc new file mode 100644 index 000000000..6e313284a --- /dev/null +++ b/test/goalc/source_templates/variables/inline-asm.static.gc @@ -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) \ No newline at end of file diff --git a/test/goalc/test_variables.cpp b/test/goalc/test_variables.cpp index d1a44287e..07e99b378 100644 --- a/test/goalc/test_variables.cpp +++ b/test/goalc/test_variables.cpp @@ -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"}); } \ No newline at end of file