From b2685d48fb5a880368c5492a91edefb386225fd7 Mon Sep 17 00:00:00 2001 From: water Date: Thu, 20 May 2021 18:13:06 -0400 Subject: [PATCH 1/2] xmm call and return working, needs some careful double checking and tests --- docs/markdown/progress-notes/changelog.md | 2 + goal_src/kernel/gcommon.gc | 25 ++++++-- goalc/CMakeLists.txt | 1 + goalc/compiler/Compiler.cpp | 3 +- goalc/compiler/IR.cpp | 35 +++++++---- goalc/compiler/IR.h | 11 +++- goalc/compiler/Val.h | 1 + goalc/compiler/compilation/Function.cpp | 62 +++++++++++++++---- goalc/compiler/compilation/Type.cpp | 72 +++++++++++++++++------ goalc/emitter/CallingConvention.cpp | 56 ++++++++++++++++++ goalc/emitter/CallingConvention.h | 17 ++++++ goalc/emitter/Register.cpp | 69 +++++++++++----------- goalc/emitter/Register.h | 10 ++-- 13 files changed, 275 insertions(+), 89 deletions(-) create mode 100644 goalc/emitter/CallingConvention.cpp create mode 100644 goalc/emitter/CallingConvention.h diff --git a/docs/markdown/progress-notes/changelog.md b/docs/markdown/progress-notes/changelog.md index 12957bff2e..99629166f4 100644 --- a/docs/markdown/progress-notes/changelog.md +++ b/docs/markdown/progress-notes/changelog.md @@ -156,3 +156,5 @@ - Support 128-bit bitfields inside of static structure - Support 128-bit bitfield constants - Support dynamic construction of 128-bit bitfield values + +## V0.8 New Calling Convention for 128-bit diff --git a/goal_src/kernel/gcommon.gc b/goal_src/kernel/gcommon.gc index 5c23dfa983..8c27138b6a 100644 --- a/goal_src/kernel/gcommon.gc +++ b/goal_src/kernel/gcommon.gc @@ -206,11 +206,7 @@ (define format _format) ;; vec4s - this is present in the game as a 128-bit integer with 4 packed floats. -;; 128-bit integers seem to be used almost never in GOAL and I suspect they were not -;; fully implemented in the compiler. Instead, 128-bit integer code used inline assembly. -;; OpenGOAL does not support 128-bit integer types, so this is a bit useless. -;; Note - the actually used vector type stores the vector in memory, not a register. -;; inline assembly code puts the register in vf registers, not integer registers. +;; this isn't used very much. (deftype vec4s (uint128) ((x float :offset 0) (y float :offset 32) @@ -221,7 +217,24 @@ :flag-assert #x900000010 ) -;; NOTE: there is a print/inspect for vec4s that is not implemented. +(defmethod inspect vec4s ((obj vec4s)) + (declare (print-asm)) + (format #t "[~8x] ~A~%" obj 'vec4s) + (format #t "~Tx: ~f~%" (-> obj x)) + (format #t "~Ty: ~f~%" (-> obj y)) + (format #t "~Tz: ~f~%" (-> obj z)) + (format #t "~Tw: ~f~%" (-> obj w)) + obj + ) + +(defmethod print vec4s ((obj vec4s)) + (format #t "#" + (-> obj x) + (-> obj y) + (-> obj z) + (-> obj w)) + obj + ) (defmacro print128 (value &key (stream #t)) "Print a 128-bit value" diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index e3890937dc..c2781ebdcb 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -1,5 +1,6 @@ add_library(compiler SHARED + emitter/CallingConvention.cpp emitter/CodeTester.cpp emitter/ObjectFileData.cpp emitter/ObjectGenerator.cpp diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index 37778ce5ee..726e26275d 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -139,7 +139,8 @@ std::unique_ptr Compiler::compile_top_level_function(const std::str // only move to return register if we actually got a result if (!dynamic_cast(result)) { - fe->emit(std::make_unique(fe->make_gpr(result->type()), result->to_gpr(fe.get()))); + fe->emit(std::make_unique(fe->make_gpr(result->type()), result->to_gpr(fe.get()), + emitter::gRegInfo.get_gpr_ret_reg())); } fe->finish(); diff --git a/goalc/compiler/IR.cpp b/goalc/compiler/IR.cpp index b834f788df..38f52ff029 100644 --- a/goalc/compiler/IR.cpp +++ b/goalc/compiler/IR.cpp @@ -130,8 +130,8 @@ void regset_common(emitter::ObjectGenerator* gen, /////////// // Return /////////// -IR_Return::IR_Return(const RegVal* return_reg, const RegVal* value) - : m_return_reg(return_reg), m_value(value) {} +IR_Return::IR_Return(const RegVal* return_reg, const RegVal* value, emitter::Register ret_reg) + : m_return_reg(return_reg), m_value(value), m_ret_reg(ret_reg) {} std::string IR_Return::print() { return fmt::format("ret {} {}", m_return_reg->print(), m_value->print()); } @@ -154,7 +154,7 @@ void IR_Return::add_constraints(std::vector* constraints, int my c.ireg = m_return_reg->ireg(); c.instr_idx = my_id; - c.desired_register = emitter::RAX; + c.desired_register = m_ret_reg; constraints->push_back(c); } @@ -167,7 +167,8 @@ void IR_Return::do_codegen(emitter::ObjectGenerator* gen, if (val_reg == dest_reg) { gen->add_instr(IGen::null(), irec); } else { - gen->add_instr(IGen::mov_gpr64_gpr64(dest_reg, val_reg), irec); + regset_common(gen, allocs, irec, m_return_reg, m_value, true); + // gen->add_instr(IGen::mov_gpr64_gpr64(dest_reg, val_reg), irec); } } @@ -361,8 +362,16 @@ void IR_GotoLabel::resolve(const Label* dest) { // FunctionCall ///////////////////// -IR_FunctionCall::IR_FunctionCall(const RegVal* func, const RegVal* ret, std::vector args) - : m_func(func), m_ret(ret), m_args(std::move(args)) {} +IR_FunctionCall::IR_FunctionCall(const RegVal* func, + const RegVal* ret, + std::vector args, + std::vector arg_regs, + std::optional ret_reg) + : m_func(func), + m_ret(ret), + m_args(std::move(args)), + m_arg_regs(std::move(arg_regs)), + m_ret_reg(ret_reg) {} std::string IR_FunctionCall::print() { std::string result = fmt::format("call {} (ret {}) (args ", m_func->print(), m_ret->print()); @@ -398,15 +407,17 @@ void IR_FunctionCall::add_constraints(std::vector* constraints, IRegConstraint c; c.ireg = m_args.at(i)->ireg(); c.instr_idx = my_id; - c.desired_register = emitter::gRegInfo.get_arg_reg(i); + c.desired_register = m_arg_regs.at(i); constraints->push_back(c); } - IRegConstraint c; - c.ireg = m_ret->ireg(); - c.desired_register = emitter::gRegInfo.get_ret_reg(); - c.instr_idx = my_id; - constraints->push_back(c); + if (m_ret_reg) { + IRegConstraint c; + c.ireg = m_ret->ireg(); + c.desired_register = *m_ret_reg; + c.instr_idx = my_id; + constraints->push_back(c); + } } void IR_FunctionCall::do_codegen(emitter::ObjectGenerator* gen, diff --git a/goalc/compiler/IR.h b/goalc/compiler/IR.h index 5607dd70c4..d6340e8ce4 100644 --- a/goalc/compiler/IR.h +++ b/goalc/compiler/IR.h @@ -32,7 +32,7 @@ class IR { class IR_Return : public IR { public: - IR_Return(const RegVal* return_reg, const RegVal* value); + IR_Return(const RegVal* return_reg, const RegVal* value, emitter::Register ret_reg); std::string print() override; RegAllocInstr to_rai() override; void add_constraints(std::vector* constraints, int my_id) override; @@ -44,6 +44,7 @@ class IR_Return : public IR { protected: const RegVal* m_return_reg = nullptr; const RegVal* m_value = nullptr; + emitter::Register m_ret_reg; }; class IR_LoadConstant64 : public IR { @@ -119,7 +120,11 @@ class IR_RegSet : public IR { class IR_FunctionCall : public IR { public: - IR_FunctionCall(const RegVal* func, const RegVal* ret, std::vector args); + IR_FunctionCall(const RegVal* func, + const RegVal* ret, + std::vector args, + std::vector arg_regs, + std::optional ret_reg); std::string print() override; RegAllocInstr to_rai() override; void do_codegen(emitter::ObjectGenerator* gen, @@ -131,6 +136,8 @@ class IR_FunctionCall : public IR { const RegVal* m_func = nullptr; const RegVal* m_ret = nullptr; std::vector m_args; + std::vector m_arg_regs; + std::optional m_ret_reg; }; class IR_StaticVarAddr : public IR { diff --git a/goalc/compiler/Val.h b/goalc/compiler/Val.h index 40bd97c5b4..69f9d486dc 100644 --- a/goalc/compiler/Val.h +++ b/goalc/compiler/Val.h @@ -70,6 +70,7 @@ class RegVal : public Val { RegVal(IRegister ireg, const TypeSpec& ts) : Val(coerce_to_reg_type(ts)), m_ireg(ireg) {} bool is_register() const override { return true; } IRegister ireg() const override { return m_ireg; } + void change_class(RegClass new_class) { m_ireg.reg_class = new_class; } std::string print() const override { return m_ireg.to_string(); }; RegVal* to_reg(Env* fe) override; RegVal* to_gpr(Env* fe) override; diff --git a/goalc/compiler/compilation/Function.cpp b/goalc/compiler/compilation/Function.cpp index a559440694..ebb7870b14 100644 --- a/goalc/compiler/compilation/Function.cpp +++ b/goalc/compiler/compilation/Function.cpp @@ -4,6 +4,7 @@ */ #include "goalc/compiler/Compiler.h" +#include "goalc/emitter/CallingConvention.h" #include "third-party/fmt/core.h" namespace { @@ -189,20 +190,27 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest // set up arguments if (lambda.params.size() > 8) { throw_compiler_error(form, - "Cannot generate an x86-64 function for a lambda with {} parameters. " + "Cannot generate a real function for a lambda with {} parameters. " "The current limit is 8.", lambda.params.size()); } // set up argument register constraints. std::vector args_for_coloring; + std::vector arg_types; + for (auto& parm : lambda.params) { + arg_types.push_back(parm.type); + } + auto arg_regs = get_arg_registers(m_ts, arg_types); + for (u32 i = 0; i < lambda.params.size(); i++) { IRegConstraint constr; constr.instr_idx = 0; // constraint at function start - auto ireg = new_func_env->make_gpr(lambda.params.at(i).type); + auto ireg = new_func_env->make_ireg( + lambda.params.at(i).type, arg_regs.at(i).is_gpr() ? RegClass::GPR_64 : RegClass::INT_128); ireg->mark_as_settable(); constr.ireg = ireg->ireg(); - constr.desired_register = emitter::gRegInfo.get_arg_reg(i); + constr.desired_register = arg_regs.at(i); new_func_env->params[lambda.params.at(i).name] = ireg; new_func_env->constrain(constr); args_for_coloring.push_back(ireg); @@ -211,6 +219,7 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest place->func = new_func_env.get(); // nasty function block env setup + // TODO use calling convention auto return_reg = new_func_env->make_gpr(get_none()->type()); auto func_block_env = new_func_env->alloc_env(new_func_env.get(), "#f"); func_block_env->return_value = return_reg; @@ -239,8 +248,19 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest 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)); + + RegVal* final_result; + emitter::Register ret_hw_reg = emitter::gRegInfo.get_gpr_ret_reg(); + if (result->type() != TypeSpec("none") && + m_ts.lookup_type(result->type())->get_load_size() == 16) { + ret_hw_reg = emitter::gRegInfo.get_xmm_ret_reg(); + final_result = result->to_xmm128(new_func_env.get()); + return_reg->change_class(RegClass::INT_128); + } else { + final_result = result->to_gpr(new_func_env.get()); + } + + new_func_env->emit(std::make_unique(return_reg, final_result, ret_hw_reg)); 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); @@ -364,7 +384,6 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en std::vector eval_args; for (uint32_t i = 1; i < args.unnamed.size(); i++) { auto intermediate = compile_error_guard(args.unnamed.at(i), env); - // todo, are the eval/to_reg'd in batches? eval_args.push_back(intermediate->to_reg(env)); } @@ -421,6 +440,14 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en if (auto_inline || got_inlined_lambda) { inlined_block_env = fe->alloc_env(inlined_compile_env, "#f"); result_reg_if_return_from = inlined_compile_env->make_gpr(get_none()->type()); + RegClass ret_class = RegClass::GPR_64; + if (head->type().last_arg() != TypeSpec("none") && + m_ts.lookup_type(head->type().last_arg())->get_load_size() == 16) { + ret_class = RegClass::INT_128; + } + result_reg_if_return_from = + inlined_compile_env->make_ireg(head->type().last_arg(), ret_class); + inlined_block_env->return_value = result_reg_if_return_from; inlined_block_env->end_label = Label(fe); inlined_compile_env = inlined_block_env; @@ -447,7 +474,7 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en // there were return froms used in the function, so we fall back to using the separate // return gpr. if (!dynamic_cast(result)) { - auto final_result = result->to_gpr(inlined_compile_env); + auto final_result = result->to_reg(inlined_compile_env); inlined_compile_env->emit( std::make_unique(result_reg_if_return_from, final_result)); inlined_block_env->return_types.push_back(final_result->type()); @@ -526,7 +553,13 @@ Val* Compiler::compile_real_function_call(const goos::Object& form, return_ts = function->type().last_arg(); } - auto return_reg = env->make_gpr(return_ts); + auto cc = get_function_calling_convention(function->type(), m_ts); + RegClass ret_reg_class = RegClass::GPR_64; + if (cc.return_reg && cc.return_reg->is_xmm()) { + ret_reg_class = RegClass::INT_128; + } + + auto return_reg = env->make_ireg(return_ts, ret_reg_class); // check arg count: if (function->type().arg_count() && !is_varargs_function(function->type())) { @@ -552,8 +585,12 @@ Val* Compiler::compile_real_function_call(const goos::Object& form, // set args (introducing a move here makes coloring more likely to be possible) std::vector arg_outs; - for (auto& arg : args) { - arg_outs.push_back(env->make_gpr(arg->type())); + for (int i = 0; i < (int)args.size(); i++) { + const auto& arg = args.at(i); + auto reg = cc.arg_regs.at(i); + // todo calling convention + arg_outs.push_back( + env->make_ireg(arg->type(), reg.is_xmm() ? RegClass::INT_128 : RegClass::GPR_64)); arg_outs.back()->mark_as_settable(); env->emit(std::make_unique(arg_outs.back(), arg)); } @@ -561,10 +598,11 @@ Val* Compiler::compile_real_function_call(const goos::Object& form, // todo, there's probably a more efficient way to do this. auto temp_function = fe->make_gpr(function->type()); env->emit(std::make_unique(temp_function, function)); - env->emit(std::make_unique(temp_function, return_reg, arg_outs)); + env->emit(std::make_unique(temp_function, return_reg, arg_outs, cc.arg_regs, + cc.return_reg)); if (m_settings.emit_move_after_return) { - auto result_reg = env->make_gpr(return_reg->type()); + auto result_reg = env->make_ireg(return_reg->type(), ret_reg_class); env->emit(std::make_unique(result_reg, return_reg)); return result_reg; } else { diff --git a/goalc/compiler/compilation/Type.cpp b/goalc/compiler/compilation/Type.cpp index 43b4654e40..cf94279569 100644 --- a/goalc/compiler/compilation/Type.cpp +++ b/goalc/compiler/compilation/Type.cpp @@ -2,6 +2,7 @@ #include "third-party/fmt/core.h" #include "common/type_system/defenum.h" #include "common/type_system/deftype.h" +#include "goalc/emitter/CallingConvention.h" namespace { @@ -171,40 +172,41 @@ void Compiler::generate_field_description(const goos::Object& form, Val* Compiler::generate_inspector_for_structured_type(const goos::Object& form, Env* env, - StructureType* structured_type) { + StructureType* structure_type) { // Create a function environment to hold the code for the inspect method. The name is just for // debugging. auto method_env = std::make_unique( - env, "autogenerated-inspect-method-of-" + structured_type->get_name()); + env, "autogenerated-inspect-method-of-" + structure_type->get_name()); // put the method in the debug segment. method_env->set_segment(DEBUG_SEGMENT); // Create a register which will hold the input to the inspect method - auto input = method_env->make_gpr(structured_type->get_name()); + auto input = method_env->make_gpr(structure_type->get_name()); // "Constrain" this register to be the register that the function argument is passed in IRegConstraint constraint; constraint.instr_idx = 0; // constraint at the start of the function constraint.ireg = input->ireg(); // constrain this register - constraint.desired_register = emitter::gRegInfo.get_arg_reg(0); // to the first argument + constraint.desired_register = emitter::gRegInfo.get_gpr_arg_reg(0); // to the first argument method_env->constrain(constraint); // Inform the compiler that `input`'s value will be written to `rdi` (first arg register) method_env->emit(std::make_unique(std::vector{input})); RegVal* type_name = nullptr; - if (dynamic_cast(structured_type)) { - type_name = get_field_of_structure(structured_type, input, "type", method_env.get()) + if (dynamic_cast(structure_type)) { + type_name = get_field_of_structure(structure_type, input, "type", method_env.get()) ->to_gpr(method_env.get()); } else { - type_name = compile_get_sym_obj(structured_type->get_name(), method_env.get()) - ->to_gpr(method_env.get()); + type_name = + compile_get_sym_obj(structure_type->get_name(), method_env.get())->to_gpr(method_env.get()); } compile_format_string(form, method_env.get(), "[~8x] ~A~%", {input, type_name}); - for (const Field& f : structured_type->fields()) { - generate_field_description(form, structured_type, method_env.get(), input, f); + for (const Field& f : structure_type->fields()) { + generate_field_description(form, structure_type, method_env.get(), input, f); } - method_env->emit(std::make_unique(method_env->make_gpr(input->type()), input)); + method_env->emit_ir(method_env->make_gpr(input->type()), input, + emitter::gRegInfo.get_gpr_ret_reg()); // add this function to the object file auto fe = get_parent_env_of_type(env); @@ -214,8 +216,8 @@ Val* Compiler::generate_inspector_for_structured_type(const goos::Object& form, obj_env_inspect->add_function(std::move(method_env)); // call method-set! - auto type_obj = compile_get_symbol_value(form, structured_type->get_name(), env)->to_gpr(env); - auto id_val = compile_integer(m_ts.lookup_method(structured_type->get_name(), "inspect").id, env) + auto type_obj = compile_get_symbol_value(form, structure_type->get_name(), env)->to_gpr(env); + auto id_val = compile_integer(m_ts.lookup_method(structure_type->get_name(), "inspect").id, env) ->to_gpr(env); auto method_set_val = compile_get_symbol_value(form, "method-set!", env)->to_gpr(env); return compile_real_function_call(form, method_set_val, {type_obj, id_val, method->to_gpr(env)}, @@ -225,6 +227,7 @@ Val* Compiler::generate_inspector_for_structured_type(const goos::Object& form, Val* Compiler::generate_inspector_for_bitfield_type(const goos::Object& form, Env* env, BitFieldType* bitfield_type) { + bool bitfield_128 = bitfield_type->get_load_size() == 16; // Create a function environment to hold the code for the inspect method. The name is just for // debugging. auto method_env = std::make_unique( @@ -238,7 +241,12 @@ Val* Compiler::generate_inspector_for_bitfield_type(const goos::Object& form, IRegConstraint constraint; constraint.instr_idx = 0; // constraint at the start of the function constraint.ireg = input->ireg(); // constrain this register - constraint.desired_register = emitter::gRegInfo.get_arg_reg(0); // to the first argument + if (bitfield_128) { + constraint.desired_register = emitter::gRegInfo.get_xmm_arg_reg(0); // to the first argument + } else { + constraint.desired_register = emitter::gRegInfo.get_gpr_arg_reg(0); // to the first argument + } + method_env->constrain(constraint); // Inform the compiler that `input`'s value will be written to `rdi` (first arg register) method_env->emit(std::make_unique(std::vector{input})); @@ -259,7 +267,14 @@ Val* Compiler::generate_inspector_for_bitfield_type(const goos::Object& form, compile_format_string(form, method_env.get(), str_template, format_args); } - method_env->emit(std::make_unique(method_env->make_gpr(input->type()), input)); + if (bitfield_128) { + method_env->emit( + std::make_unique(method_env->make_ireg(input->type(), RegClass::INT_128), input, + emitter::gRegInfo.get_gpr_ret_reg())); + } else { + method_env->emit(std::make_unique(method_env->make_gpr(input->type()), input, + emitter::gRegInfo.get_gpr_ret_reg())); + } // add this function to the object file auto fe = get_parent_env_of_type(env); @@ -394,13 +409,21 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _ throw_compiler_error(form, "Methods cannot have more than 8 arguments"); } std::vector args_for_coloring; + std::vector arg_types; + for (auto& parm : lambda.params) { + arg_types.push_back(parm.type); + } + auto arg_regs = get_arg_registers(m_ts, arg_types); + for (u32 i = 0; i < lambda.params.size(); i++) { IRegConstraint constr; constr.instr_idx = 0; // constraint at function start - auto ireg = new_func_env->make_gpr(lambda.params.at(i).type); + // todo calling convention + auto ireg = new_func_env->make_ireg( + lambda.params.at(i).type, arg_regs.at(i).is_gpr() ? RegClass::GPR_64 : RegClass::INT_128); ireg->mark_as_settable(); constr.ireg = ireg->ireg(); - constr.desired_register = emitter::gRegInfo.get_arg_reg(i); + constr.desired_register = arg_regs.at(i); new_func_env->params[lambda.params.at(i).name] = ireg; new_func_env->constrain(constr); args_for_coloring.push_back(ireg); @@ -409,6 +432,7 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _ place->func = new_func_env.get(); // nasty function block env setup + // todo calling convention auto return_reg = new_func_env->make_gpr(get_none()->type()); auto func_block_env = new_func_env->alloc_env(new_func_env.get(), "#f"); func_block_env->return_value = return_reg; @@ -434,8 +458,18 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _ // don't add return automatically! lambda_ts.add_arg(new_func_env->asm_func_return_type); } else if (result && !dynamic_cast(result)) { - auto final_result = result->to_gpr(new_func_env.get()); - new_func_env->emit(std::make_unique(return_reg, final_result)); + RegVal* final_result; + emitter::Register ret_hw_reg = emitter::gRegInfo.get_gpr_ret_reg(); + if (m_ts.lookup_type(result->type())->get_load_size() == 16) { + ret_hw_reg = emitter::gRegInfo.get_xmm_ret_reg(); + final_result = result->to_xmm128(new_func_env.get()); + return_reg->change_class(RegClass::INT_128); + } else { + final_result = result->to_gpr(new_func_env.get()); + } + + new_func_env->emit(std::make_unique(return_reg, final_result, ret_hw_reg)); + 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); diff --git a/goalc/emitter/CallingConvention.cpp b/goalc/emitter/CallingConvention.cpp new file mode 100644 index 0000000000..86bfb0c4eb --- /dev/null +++ b/goalc/emitter/CallingConvention.cpp @@ -0,0 +1,56 @@ +#include "common/util/assert.h" + +#include "CallingConvention.h" + +CallingConvention get_function_calling_convention(const TypeSpec& function_type, + const TypeSystem& type_system) { + assert(function_type.base_type() == "function"); + assert(function_type.arg_count() > 0); + assert(function_type.arg_count() <= 9); + + int gpr_idx = 0; + int xmm_idx = 0; + + CallingConvention cc; + + if (function_type.arg_count() == 2 && function_type.get_arg(0).print() == "_varargs_") { + for (int i = 0; i < 8; i++) { + cc.arg_regs.push_back(emitter::gRegInfo.get_gpr_arg_reg(gpr_idx++)); + } + } else { + for (int i = 0; i < (int)function_type.arg_count() - 1; i++) { + auto info = type_system.lookup_type(function_type.get_arg(i)); + if (dynamic_cast(info) && info->get_load_size() == 16) { + cc.arg_regs.push_back(emitter::gRegInfo.get_xmm_arg_reg(xmm_idx++)); + } else { + cc.arg_regs.push_back(emitter::gRegInfo.get_gpr_arg_reg(gpr_idx++)); + } + } + } + + if (function_type.last_arg() != TypeSpec("none")) { + if (type_system.lookup_type(function_type.last_arg())->get_load_size() == 16) { + cc.return_reg = emitter::gRegInfo.get_xmm_ret_reg(); + } else { + cc.return_reg = emitter::gRegInfo.get_gpr_ret_reg(); + } + } + + return cc; +} + +std::vector get_arg_registers(const TypeSystem& type_system, + const std::vector& arg_types) { + std::vector result; + int gpr_idx = 0; + int xmm_idx = 0; + for (auto& type : arg_types) { + auto info = type_system.lookup_type(type); + if (info->get_load_size() == 16) { + result.push_back(emitter::gRegInfo.get_xmm_arg_reg(xmm_idx++)); + } else { + result.push_back(emitter::gRegInfo.get_gpr_arg_reg(gpr_idx++)); + } + } + return result; +} \ No newline at end of file diff --git a/goalc/emitter/CallingConvention.h b/goalc/emitter/CallingConvention.h new file mode 100644 index 0000000000..a6338d4441 --- /dev/null +++ b/goalc/emitter/CallingConvention.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +#include "common/type_system/TypeSystem.h" +#include "goalc/emitter/Register.h" + +struct CallingConvention { + std::vector arg_regs; + std::optional return_reg; +}; + +std::vector get_arg_registers(const TypeSystem& type_system, + const std::vector& arg_types); +CallingConvention get_function_calling_convention(const TypeSpec& function_type, + const TypeSystem& type_system); \ No newline at end of file diff --git a/goalc/emitter/Register.cpp b/goalc/emitter/Register.cpp index 13ab94dd00..e2fa146fe3 100644 --- a/goalc/emitter/Register.cpp +++ b/goalc/emitter/Register.cpp @@ -5,42 +5,45 @@ namespace emitter { RegisterInfo RegisterInfo::make_register_info() { RegisterInfo info; - info.m_info[RAX] = {-1, false, false, "rax"}; // temp - info.m_info[RCX] = {3, false, false, "rcx"}; // temp - info.m_info[RDX] = {2, false, false, "rdx"}; // temp - info.m_info[RBX] = {-1, true, false, "rbx"}; // - info.m_info[RSP] = {-1, false, true, "rsp"}; - info.m_info[RBP] = {-1, true, false, "rbp"}; - info.m_info[RSI] = {1, false, false, "rsi"}; - info.m_info[RDI] = {0, false, false, "rdi"}; + info.m_info[RAX] = {false, false, "rax"}; // return, temp + info.m_info[RCX] = {false, false, "rcx"}; // gpr arg 3, temp + info.m_info[RDX] = {false, false, "rdx"}; // gpr arg 2, temp + info.m_info[RBX] = {true, false, "rbx"}; // saved + info.m_info[RSP] = {false, true, "rsp"}; // stack pointer + info.m_info[RBP] = {true, false, "rbp"}; // saved + info.m_info[RSI] = {false, false, "rsi"}; // gpr arg 1, temp + info.m_info[RDI] = {false, false, "rdi"}; // gpr arg 0, temp - info.m_info[R8] = {4, false, false, "r8"}; - info.m_info[R9] = {5, false, false, "r9"}; - info.m_info[R10] = {6, true, false, "r10"}; - info.m_info[R11] = {7, true, false, "r11"}; - info.m_info[R12] = {-1, true, false, "r12"}; - info.m_info[R13] = {-1, false, true, "r13"}; // pp? - info.m_info[R14] = {-1, false, true, "r14"}; // st? - info.m_info[R15] = {-1, false, true, "r15"}; // offset. + info.m_info[R8] = {false, false, "r8"}; // gpr arg 4, temp + info.m_info[R9] = {false, false, "r9"}; // gpr arg 5, temp + info.m_info[R10] = {true, false, "r10"}; // gpr arg 6, saved + info.m_info[R11] = {true, false, "r11"}; // gpr arg 7, saved + info.m_info[R12] = {true, false, "r12"}; // saved + info.m_info[R13] = {false, true, "r13"}; // pp + info.m_info[R14] = {false, true, "r14"}; // st + info.m_info[R15] = {false, true, "r15"}; // offset. - info.m_info[XMM0] = {-1, false, false, "xmm0"}; - info.m_info[XMM1] = {-1, false, false, "xmm1"}; - info.m_info[XMM2] = {-1, false, false, "xmm2"}; - info.m_info[XMM3] = {-1, false, false, "xmm3"}; - info.m_info[XMM4] = {-1, false, false, "xmm4"}; - info.m_info[XMM5] = {-1, false, false, "xmm5"}; - info.m_info[XMM6] = {-1, false, false, "xmm6"}; - info.m_info[XMM7] = {-1, false, false, "xmm7"}; - info.m_info[XMM8] = {-1, true, false, "xmm8"}; - info.m_info[XMM9] = {-1, true, false, "xmm9"}; - info.m_info[XMM10] = {-1, true, false, "xmm10"}; - info.m_info[XMM11] = {-1, true, false, "xmm11"}; - info.m_info[XMM12] = {-1, true, false, "xmm12"}; - info.m_info[XMM13] = {-1, true, false, "xmm13"}; - info.m_info[XMM14] = {-1, true, false, "xmm14"}; - info.m_info[XMM15] = {-1, true, false, "xmm15"}; + info.m_info[XMM0] = {false, false, "xmm0"}; + info.m_info[XMM1] = {false, false, "xmm1"}; + info.m_info[XMM2] = {false, false, "xmm2"}; + info.m_info[XMM3] = {false, false, "xmm3"}; + info.m_info[XMM4] = {false, false, "xmm4"}; + info.m_info[XMM5] = {false, false, "xmm5"}; + info.m_info[XMM6] = {false, false, "xmm6"}; + info.m_info[XMM7] = {false, false, "xmm7"}; + info.m_info[XMM8] = {true, false, "xmm8"}; + info.m_info[XMM9] = {true, false, "xmm9"}; + info.m_info[XMM10] = {true, false, "xmm10"}; + info.m_info[XMM11] = {true, false, "xmm11"}; + info.m_info[XMM12] = {true, false, "xmm12"}; + info.m_info[XMM13] = {true, false, "xmm13"}; + info.m_info[XMM14] = {true, false, "xmm14"}; + info.m_info[XMM15] = {true, false, "xmm15"}; - info.m_arg_regs = std::array({RDI, RSI, RDX, RCX, R8, R9, R10, R11}); + info.m_gpr_arg_regs = std::array({RDI, RSI, RDX, RCX, R8, R9, R10, R11}); + // skip xmm0 so it can be used for return. + info.m_xmm_arg_regs = + std::array({XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7, XMM8}); info.m_saved_gprs = std::array({RBX, RBP, R10, R11, R12}); info.m_saved_xmms = std::array({XMM8, XMM9, XMM10, XMM11, XMM12, XMM13, XMM14, XMM15}); diff --git a/goalc/emitter/Register.h b/goalc/emitter/Register.h index 6c33eb2dfa..9b86782e62 100644 --- a/goalc/emitter/Register.h +++ b/goalc/emitter/Register.h @@ -121,7 +121,6 @@ class RegisterInfo { static RegisterInfo make_register_info(); struct Info { - int argument_id = -1; // -1 if not argument bool saved = false; // does the callee save it? bool special = false; // is it a special GOAL register? std::string name; @@ -130,13 +129,15 @@ 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_gpr_arg_reg(int id) const { return m_gpr_arg_regs.at(id); } + Register get_xmm_arg_reg(int id) const { return m_xmm_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; } + Register get_gpr_ret_reg() const { return RAX; } + Register get_xmm_ret_reg() const { return XMM0; } 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; } @@ -148,7 +149,8 @@ class RegisterInfo { private: RegisterInfo() = default; std::array m_info; - std::array m_arg_regs; + std::array m_gpr_arg_regs; + std::array m_xmm_arg_regs; std::array m_saved_gprs; std::array m_saved_xmms; std::array m_saved_all; From 8fd4cac897197ef550e72fc0cce16dbbab690ca5 Mon Sep 17 00:00:00 2001 From: water Date: Thu, 20 May 2021 19:59:42 -0400 Subject: [PATCH 2/2] clean up and test --- common/versions.h | 2 +- decompiler/config/all-types.gc | 2 +- .../jak1_ntsc_black_label/type_casts.jsonc | 2 +- docs/markdown/progress-notes/changelog.md | 1 + goal_src/engine/load/load-dgo.gc | 18 +------------ goal_src/engine/sound/gsound-h.gc | 2 ++ goal_src/engine/sound/gsound.gc | 20 ++++++++++++++ goal_src/kernel/gcommon.gc | 4 +-- goalc/compiler/compilation/Function.cpp | 24 ++++++++++++++--- goalc/compiler/compilation/Type.cpp | 13 ++++++--- .../with_game/test-function128.gc | 27 +++++++++++++++++++ test/goalc/test_with_game.cpp | 11 ++++++++ 12 files changed, 97 insertions(+), 29 deletions(-) create mode 100644 test/goalc/source_templates/with_game/test-function128.gc diff --git a/common/versions.h b/common/versions.h index 638af3e087..d858d5738f 100644 --- a/common/versions.h +++ b/common/versions.h @@ -10,7 +10,7 @@ namespace versions { // language version (OpenGOAL) constexpr s32 GOAL_VERSION_MAJOR = 0; -constexpr s32 GOAL_VERSION_MINOR = 7; +constexpr s32 GOAL_VERSION_MINOR = 8; constexpr int DECOMPILER_VERSION = 4; diff --git a/decompiler/config/all-types.gc b/decompiler/config/all-types.gc index ed83867d1b..7d8705517d 100644 --- a/decompiler/config/all-types.gc +++ b/decompiler/config/all-types.gc @@ -13248,7 +13248,7 @@ (define-extern find-temp-buffer (function int pointer)) (define-extern dgo-load-link (function dgo-header kheap symbol symbol symbol)) (define-extern destroy-mem (function (pointer uint32) (pointer uint32) none)) -(define-extern string->sound-name (function string uint128)) +(define-extern string->sound-name (function string sound-name)) (define-extern str-play-kick (function none)) (define-extern *dgo-time* uint) diff --git a/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc b/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc index 27caefe7ea..eb88f4fc9f 100644 --- a/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc @@ -437,7 +437,7 @@ ], "string->sound-name": [ - [[6, 8], "a1", "(pointer uint8)"] + [[2, 18], "a1", "(pointer uint8)"] ], "ramdisk-load": [ diff --git a/docs/markdown/progress-notes/changelog.md b/docs/markdown/progress-notes/changelog.md index 99629166f4..4f07f25cd7 100644 --- a/docs/markdown/progress-notes/changelog.md +++ b/docs/markdown/progress-notes/changelog.md @@ -158,3 +158,4 @@ - Support dynamic construction of 128-bit bitfield values ## V0.8 New Calling Convention for 128-bit +- 128-bit values may now be used in function arguments and return values. \ No newline at end of file diff --git a/goal_src/engine/load/load-dgo.gc b/goal_src/engine/load/load-dgo.gc index faf1dd9f4c..634cb4e6b8 100644 --- a/goal_src/engine/load/load-dgo.gc +++ b/goal_src/engine/load/load-dgo.gc @@ -266,21 +266,6 @@ struct DgoHeader { ;; DGO LOAD and LINK ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defun string->sound-name! ((out (pointer uint128)) (in string)) - "This function was added as a temporary workaround for not having - uint128 return values in OpenGOAL yet." - (set! (-> out) (the uint128 0)) - (let ((out-ptr (the (pointer uint8) out)) - (in-ptr (-> in data))) - (while (and (nonzero? (-> in-ptr)) - (< (&- in-ptr (-> in data)) 15)) - (set! (-> out-ptr) (-> in-ptr)) - (&+! out-ptr 1) - (&+! in-ptr 1) - ) - ) - ) - (define *dgo-time* (the-as uint 0)) (defun dgo-load-begin ((name string) (buffer1 int) (buffer2 int) (current-heap int)) @@ -301,8 +286,7 @@ struct DgoHeader { (set! (-> cmd b2) (the-as uint buffer2)) (set! (-> cmd bt) (the-as uint current-heap)) ;; modified due to OpenGOAL not supporting uint128 return values yet - ;;(set! (-> cmd name) (string->sound-name name)) - (string->sound-name! (&-> cmd name) name) + (set! (-> cmd name) (string->sound-name name)) ;; call now! (call *load-dgo-rpc* (the-as uint 0) (the-as pointer cmd) (the-as uint 32)) cmd diff --git a/goal_src/engine/sound/gsound-h.gc b/goal_src/engine/sound/gsound-h.gc index 7f0c284885..46ddc160db 100644 --- a/goal_src/engine/sound/gsound-h.gc +++ b/goal_src/engine/sound/gsound-h.gc @@ -354,3 +354,5 @@ (define *sound-bank-1* #f) (define *sound-bank-2* #f) + +(defun-extern string->sound-name string sound-name) diff --git a/goal_src/engine/sound/gsound.gc b/goal_src/engine/sound/gsound.gc index 9113f9d4d3..0b9c3345d6 100644 --- a/goal_src/engine/sound/gsound.gc +++ b/goal_src/engine/sound/gsound.gc @@ -5,3 +5,23 @@ ;; name in dgo: gsound ;; dgos: GAME, ENGINE + +(defun string->sound-name ((str string)) + (let ((snd-name (new 'stack-no-clear 'qword))) + (set! (-> snd-name quad) (the-as uint128 0)) + (let ((out-ptr (the-as (pointer uint8) snd-name)) + (in-ptr (-> str data)) + ) + (while + (and + (nonzero? (-> in-ptr 0)) + (< (&- in-ptr (the-as uint (-> str data))) 15) + ) + (set! (-> out-ptr 0) (-> in-ptr 0)) + (set! out-ptr (&-> out-ptr 1)) + (set! in-ptr (&-> in-ptr 1)) + ) + ) + (the-as sound-name (-> snd-name quad)) + ) + ) diff --git a/goal_src/kernel/gcommon.gc b/goal_src/kernel/gcommon.gc index 8c27138b6a..e31ae57c8f 100644 --- a/goal_src/kernel/gcommon.gc +++ b/goal_src/kernel/gcommon.gc @@ -218,7 +218,6 @@ ) (defmethod inspect vec4s ((obj vec4s)) - (declare (print-asm)) (format #t "[~8x] ~A~%" obj 'vec4s) (format #t "~Tx: ~f~%" (-> obj x)) (format #t "~Ty: ~f~%" (-> obj y)) @@ -232,7 +231,8 @@ (-> obj x) (-> obj y) (-> obj z) - (-> obj w)) + (-> obj w) + obj) obj ) diff --git a/goalc/compiler/compilation/Function.cpp b/goalc/compiler/compilation/Function.cpp index ebb7870b14..c0571f81a3 100644 --- a/goalc/compiler/compilation/Function.cpp +++ b/goalc/compiler/compilation/Function.cpp @@ -260,8 +260,17 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest final_result = result->to_gpr(new_func_env.get()); } - new_func_env->emit(std::make_unique(return_reg, final_result, ret_hw_reg)); func_block_env->return_types.push_back(final_result->type()); + for (const auto& possible_type : func_block_env->return_types) { + if (possible_type != TypeSpec("none") && + m_ts.lookup_type(possible_type)->get_load_size() == 16) { + return_reg->change_class(RegClass::INT_128); + break; + } + } + + new_func_env->emit(std::make_unique(return_reg, final_result, ret_hw_reg)); + auto return_type = m_ts.lowest_common_ancestor(func_block_env->return_types); lambda_ts.add_arg(return_type); } else { @@ -439,7 +448,6 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en RegVal* result_reg_if_return_from = nullptr; if (auto_inline || got_inlined_lambda) { inlined_block_env = fe->alloc_env(inlined_compile_env, "#f"); - result_reg_if_return_from = inlined_compile_env->make_gpr(get_none()->type()); RegClass ret_class = RegClass::GPR_64; if (head->type().last_arg() != TypeSpec("none") && m_ts.lookup_type(head->type().last_arg())->get_load_size() == 16) { @@ -475,9 +483,18 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en // return gpr. if (!dynamic_cast(result)) { auto final_result = result->to_reg(inlined_compile_env); + inlined_block_env->return_types.push_back(final_result->type()); + + for (const auto& possible_type : inlined_block_env->return_types) { + if (possible_type != TypeSpec("none") && + m_ts.lookup_type(possible_type)->get_load_size() == 16) { + result_reg_if_return_from->change_class(RegClass::INT_128); + } + } + inlined_compile_env->emit( std::make_unique(result_reg_if_return_from, final_result)); - inlined_block_env->return_types.push_back(final_result->type()); + auto return_type = m_ts.lowest_common_ancestor(inlined_block_env->return_types); inlined_block_env->return_value->set_type(return_type); } else { @@ -588,7 +605,6 @@ Val* Compiler::compile_real_function_call(const goos::Object& form, for (int i = 0; i < (int)args.size(); i++) { const auto& arg = args.at(i); auto reg = cc.arg_regs.at(i); - // todo calling convention arg_outs.push_back( env->make_ireg(arg->type(), reg.is_xmm() ? RegClass::INT_128 : RegClass::GPR_64)); arg_outs.back()->mark_as_settable(); diff --git a/goalc/compiler/compilation/Type.cpp b/goalc/compiler/compilation/Type.cpp index cf94279569..3c2a793efb 100644 --- a/goalc/compiler/compilation/Type.cpp +++ b/goalc/compiler/compilation/Type.cpp @@ -418,7 +418,6 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _ for (u32 i = 0; i < lambda.params.size(); i++) { IRegConstraint constr; constr.instr_idx = 0; // constraint at function start - // todo calling convention auto ireg = new_func_env->make_ireg( lambda.params.at(i).type, arg_regs.at(i).is_gpr() ? RegClass::GPR_64 : RegClass::INT_128); ireg->mark_as_settable(); @@ -432,7 +431,6 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _ place->func = new_func_env.get(); // nasty function block env setup - // todo calling convention auto return_reg = new_func_env->make_gpr(get_none()->type()); auto func_block_env = new_func_env->alloc_env(new_func_env.get(), "#f"); func_block_env->return_value = return_reg; @@ -468,9 +466,18 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _ final_result = result->to_gpr(new_func_env.get()); } + func_block_env->return_types.push_back(final_result->type()); + + for (const auto& possible_type : func_block_env->return_types) { + if (possible_type != TypeSpec("none") && + m_ts.lookup_type(possible_type)->get_load_size() == 16) { + return_reg->change_class(RegClass::INT_128); + break; + } + } + new_func_env->emit(std::make_unique(return_reg, final_result, ret_hw_reg)); - 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 { diff --git a/test/goalc/source_templates/with_game/test-function128.gc b/test/goalc/source_templates/with_game/test-function128.gc new file mode 100644 index 0000000000..c78e5cf70d --- /dev/null +++ b/test/goalc/source_templates/with_game/test-function128.gc @@ -0,0 +1,27 @@ +(let ((tv (new 'static 'vec4s :x 1.0 :y 2.0 :z 3.0 :w 4.0))) + (let ((temp (print tv))) + (format #t "~%") + (set! (-> temp x) 10.0) + (set! (-> tv y) 20.0) + (print tv) + (format #t "~%") + (print temp) + (format #t "~%") + ) + ) + +(defun test-print-sound-name-chars ((arg0 int) (name sound-name) (arg2 int)) + (let ((mem (new 'stack 'array 'uint8 16))) + (set! (-> (the (pointer sound-name) mem)) name) + (let ((i 0)) + (while (and (< i 16) (nonzero? (-> mem i))) + (format #t " ~c" (-> mem i)) + (+! i 1) + ) + ) + (format #t "~%") + (format #t "arg0: ~d arg2: ~d~%" arg0 arg2) + ) + ) + +(test-print-sound-name-chars 1 (string->sound-name "0123456789abcdefghij") 2) diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index 2b09ca8100..c970e13324 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -735,6 +735,17 @@ TEST_F(WithGameTests, WeirdMultiply) { "0\n"}); } +TEST_F(WithGameTests, Function128) { + runner.run_static_test( + env, testCategory, "test-function128.gc", + {"#\n" + "#\n" + "#\n" + " 0 1 2 3 4 5 6 7 8 9 a b c d e\n" + "arg0: 1 arg2: 2\n" + "0\n"}); +} + TEST(TypeConsistency, TypeConsistency) { Compiler compiler; compiler.enable_throw_on_redefines();