mirror of
https://github.com/open-goal/jak-project
synced 2026-08-05 01:30:22 -04:00
[OpenGOAL] Support for 128-bit arguments and return values (WIP) (#510)
* xmm call and return working, needs some careful double checking and tests * clean up and test
This commit is contained in:
+1
-1
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -437,7 +437,7 @@
|
||||
],
|
||||
|
||||
"string->sound-name": [
|
||||
[[6, 8], "a1", "(pointer uint8)"]
|
||||
[[2, 18], "a1", "(pointer uint8)"]
|
||||
],
|
||||
|
||||
"ramdisk-load": [
|
||||
|
||||
@@ -156,3 +156,6 @@
|
||||
- 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
|
||||
- 128-bit values may now be used in function arguments and return values.
|
||||
@@ -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
|
||||
|
||||
@@ -354,3 +354,5 @@
|
||||
|
||||
(define *sound-bank-1* #f)
|
||||
(define *sound-bank-2* #f)
|
||||
|
||||
(defun-extern string->sound-name string sound-name)
|
||||
|
||||
@@ -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))
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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))
|
||||
(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 "#<vector ~F ~F ~F ~F @ #x~X>"
|
||||
(-> obj x)
|
||||
(-> obj y)
|
||||
(-> obj z)
|
||||
(-> obj w)
|
||||
obj)
|
||||
obj
|
||||
)
|
||||
|
||||
(defmacro print128 (value &key (stream #t))
|
||||
"Print a 128-bit value"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
add_library(compiler
|
||||
SHARED
|
||||
emitter/CallingConvention.cpp
|
||||
emitter/CodeTester.cpp
|
||||
emitter/ObjectFileData.cpp
|
||||
emitter/ObjectGenerator.cpp
|
||||
|
||||
@@ -139,7 +139,8 @@ std::unique_ptr<FunctionEnv> Compiler::compile_top_level_function(const std::str
|
||||
|
||||
// only move to return register if we actually got a result
|
||||
if (!dynamic_cast<const None*>(result)) {
|
||||
fe->emit(std::make_unique<IR_Return>(fe->make_gpr(result->type()), result->to_gpr(fe.get())));
|
||||
fe->emit(std::make_unique<IR_Return>(fe->make_gpr(result->type()), result->to_gpr(fe.get()),
|
||||
emitter::gRegInfo.get_gpr_ret_reg()));
|
||||
}
|
||||
|
||||
fe->finish();
|
||||
|
||||
+23
-12
@@ -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<IRegConstraint>* 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<RegVal*> args)
|
||||
: m_func(func), m_ret(ret), m_args(std::move(args)) {}
|
||||
IR_FunctionCall::IR_FunctionCall(const RegVal* func,
|
||||
const RegVal* ret,
|
||||
std::vector<RegVal*> args,
|
||||
std::vector<emitter::Register> arg_regs,
|
||||
std::optional<emitter::Register> 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<IRegConstraint>* 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,
|
||||
|
||||
+9
-2
@@ -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<IRegConstraint>* 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<RegVal*> args);
|
||||
IR_FunctionCall(const RegVal* func,
|
||||
const RegVal* ret,
|
||||
std::vector<RegVal*> args,
|
||||
std::vector<emitter::Register> arg_regs,
|
||||
std::optional<emitter::Register> 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<RegVal*> m_args;
|
||||
std::vector<emitter::Register> m_arg_regs;
|
||||
std::optional<emitter::Register> m_ret_reg;
|
||||
};
|
||||
|
||||
class IR_StaticVarAddr : public IR {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<RegVal*> args_for_coloring;
|
||||
std::vector<TypeSpec> 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<BlockEnv>(new_func_env.get(), "#f");
|
||||
func_block_env->return_value = return_reg;
|
||||
@@ -239,9 +248,29 @@ 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<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));
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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<IR_Return>(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 {
|
||||
@@ -364,7 +393,6 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en
|
||||
std::vector<RegVal*> 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));
|
||||
}
|
||||
|
||||
@@ -420,7 +448,14 @@ 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<BlockEnv>(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,10 +482,19 @@ 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<None*>(result)) {
|
||||
auto final_result = result->to_gpr(inlined_compile_env);
|
||||
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<IR_RegSet>(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 {
|
||||
@@ -526,7 +570,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 +602,11 @@ 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<RegVal*> 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);
|
||||
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<IR_RegSet>(arg_outs.back(), arg));
|
||||
}
|
||||
@@ -561,10 +614,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<IR_RegSet>(temp_function, function));
|
||||
env->emit(std::make_unique<IR_FunctionCall>(temp_function, return_reg, arg_outs));
|
||||
env->emit(std::make_unique<IR_FunctionCall>(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<IR_RegSet>(result_reg, return_reg));
|
||||
return result_reg;
|
||||
} else {
|
||||
|
||||
@@ -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<FunctionEnv>(
|
||||
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<IR_ValueReset>(std::vector<RegVal*>{input}));
|
||||
|
||||
RegVal* type_name = nullptr;
|
||||
if (dynamic_cast<BasicType*>(structured_type)) {
|
||||
type_name = get_field_of_structure(structured_type, input, "type", method_env.get())
|
||||
if (dynamic_cast<BasicType*>(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<IR_Return>(method_env->make_gpr(input->type()), input));
|
||||
method_env->emit_ir<IR_Return>(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<FunctionEnv>(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<FunctionEnv>(
|
||||
@@ -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<IR_ValueReset>(std::vector<RegVal*>{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<IR_Return>(method_env->make_gpr(input->type()), input));
|
||||
if (bitfield_128) {
|
||||
method_env->emit(
|
||||
std::make_unique<IR_Return>(method_env->make_ireg(input->type(), RegClass::INT_128), input,
|
||||
emitter::gRegInfo.get_gpr_ret_reg()));
|
||||
} else {
|
||||
method_env->emit(std::make_unique<IR_Return>(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<FunctionEnv>(env);
|
||||
@@ -394,13 +409,20 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _
|
||||
throw_compiler_error(form, "Methods cannot have more than 8 arguments");
|
||||
}
|
||||
std::vector<RegVal*> args_for_coloring;
|
||||
std::vector<TypeSpec> 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);
|
||||
@@ -434,9 +456,28 @@ 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<None*>(result)) {
|
||||
auto final_result = result->to_gpr(new_func_env.get());
|
||||
new_func_env->emit(std::make_unique<IR_Return>(return_reg, final_result));
|
||||
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());
|
||||
}
|
||||
|
||||
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<IR_Return>(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 {
|
||||
|
||||
@@ -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<const ValueType*>(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<emitter::Register> get_arg_registers(const TypeSystem& type_system,
|
||||
const std::vector<TypeSpec>& arg_types) {
|
||||
std::vector<emitter::Register> 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;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
#include "common/type_system/TypeSystem.h"
|
||||
#include "goalc/emitter/Register.h"
|
||||
|
||||
struct CallingConvention {
|
||||
std::vector<emitter::Register> arg_regs;
|
||||
std::optional<emitter::Register> return_reg;
|
||||
};
|
||||
|
||||
std::vector<emitter::Register> get_arg_registers(const TypeSystem& type_system,
|
||||
const std::vector<TypeSpec>& arg_types);
|
||||
CallingConvention get_function_calling_convention(const TypeSpec& function_type,
|
||||
const TypeSystem& type_system);
|
||||
+36
-33
@@ -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<Register, N_ARGS>({RDI, RSI, RDX, RCX, R8, R9, R10, R11});
|
||||
info.m_gpr_arg_regs = std::array<Register, N_ARGS>({RDI, RSI, RDX, RCX, R8, R9, R10, R11});
|
||||
// skip xmm0 so it can be used for return.
|
||||
info.m_xmm_arg_regs =
|
||||
std::array<Register, N_ARGS>({XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7, XMM8});
|
||||
info.m_saved_gprs = std::array<Register, N_SAVED_GPRS>({RBX, RBP, R10, R11, R12});
|
||||
info.m_saved_xmms =
|
||||
std::array<Register, N_SAVED_XMMS>({XMM8, XMM9, XMM10, XMM11, XMM12, XMM13, XMM14, XMM15});
|
||||
|
||||
@@ -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<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; }
|
||||
@@ -148,7 +149,8 @@ class RegisterInfo {
|
||||
private:
|
||||
RegisterInfo() = default;
|
||||
std::array<Info, N_REGS> m_info;
|
||||
std::array<Register, N_ARGS> m_arg_regs;
|
||||
std::array<Register, N_ARGS> m_gpr_arg_regs;
|
||||
std::array<Register, N_ARGS> m_xmm_arg_regs;
|
||||
std::array<Register, N_SAVED_GPRS> m_saved_gprs;
|
||||
std::array<Register, N_SAVED_XMMS> m_saved_xmms;
|
||||
std::array<Register, N_SAVED_XMMS + N_SAVED_GPRS> m_saved_all;
|
||||
|
||||
@@ -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)
|
||||
@@ -735,6 +735,17 @@ TEST_F(WithGameTests, WeirdMultiply) {
|
||||
"0\n"});
|
||||
}
|
||||
|
||||
TEST_F(WithGameTests, Function128) {
|
||||
runner.run_static_test(
|
||||
env, testCategory, "test-function128.gc",
|
||||
{"#<vector 1.0000 2.0000 3.0000 4.0000 @ #x400000003f800000>\n"
|
||||
"#<vector 1.0000 20.0000 3.0000 4.0000 @ #x41a000003f800000>\n"
|
||||
"#<vector 10.0000 2.0000 3.0000 4.0000 @ #x4000000041200000>\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();
|
||||
|
||||
Reference in New Issue
Block a user