diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 05e0d99744..b928900c41 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -7,7 +7,6 @@ set(CMAKE_CXX_FLAGS "-O0 -ggdb -Wall \ enable_language(ASM_NASM) set(RUNTIME_SOURCE - main.cpp runtime.cpp system/SystemThread.cpp system/IOP_Kernel.cpp @@ -49,7 +48,7 @@ set(RUNTIME_SOURCE overlord/stream.cpp) # the runtime should be built without any static/dynamic libraries. -add_executable(gk ${RUNTIME_SOURCE}) +add_executable(gk ${RUNTIME_SOURCE} main.cpp) # we also build a runtime library for testing. This version is likely unable to call GOAL code correctly, but # can be used to test other things. diff --git a/game/kernel/fileio.cpp b/game/kernel/fileio.cpp index 585748bf0d..c5ccbb3e43 100644 --- a/game/kernel/fileio.cpp +++ b/game/kernel/fileio.cpp @@ -199,6 +199,7 @@ char* basename_goal(char* s) { } } + /* Original code, has memory bug. // back up... for (;;) { if (pt < input) { @@ -211,6 +212,20 @@ char* basename_goal(char* s) { return pt + 1; // and return one past } } + */ + + // back up... + for (;;) { + if (pt <= input) { + return input; + } + pt--; + char c = *pt; + // until we hit a slash. + if (c == '\\' || c == '/') { // slashes + return pt + 1; // and return one past + } + } } /*! diff --git a/game/system/Deci2Server.h b/game/system/Deci2Server.h index 1bdb8c2d06..d3db7d96ca 100644 --- a/game/system/Deci2Server.h +++ b/game/system/Deci2Server.h @@ -34,9 +34,9 @@ class Deci2Server { void accept_thread_func(); bool kill_accept_thread = false; char* buffer = nullptr; - int server_fd; + int server_fd = -1; sockaddr_in addr; - int new_sock; + int new_sock = -1; bool server_initialized = false; bool accept_thread_running = false; bool server_connected = false; diff --git a/goalc/emitter/CodeTester.cpp b/goalc/emitter/CodeTester.cpp index 3d5c4b69bf..f64cb4e60b 100644 --- a/goalc/emitter/CodeTester.cpp +++ b/goalc/emitter/CodeTester.cpp @@ -1 +1,106 @@ +#include #include "CodeTester.h" +#include "IGen.h" + +namespace emitter { + +CodeTester::CodeTester() : m_info(RegisterInfo::make_register_info()) { + +} + +std::string CodeTester::dump_to_hex_string(bool nospace) { + std::string result; + char buff[32]; + for (int i = 0; i < code_buffer_size; i++) { + if(nospace) { + sprintf(buff, "%02X", code_buffer[i]); + } else { + sprintf(buff, "%02x ", code_buffer[i]); + } + + result += buff; + } + + // remove trailing space + if (!nospace && !result.empty()) { + result.pop_back(); + } + return result; +} + +void CodeTester::emit(const Instruction& instr) { + code_buffer_size += instr.emit(code_buffer + code_buffer_size); + assert(code_buffer_size <= code_buffer_capacity); +} + +void CodeTester::emit_set_gpr_as_return(Register gpr) { + assert(gpr.is_gpr()); + emit(IGen::mov_gpr64_gpr64(RAX, gpr)); +} + +void CodeTester::emit_return() { + emit(IGen::ret()); +} + +void CodeTester::emit_pop_all_gprs(bool exclude_rax) { + for (int i = 16; i-- > 0;) { + if (i != RAX || !exclude_rax) { + emit(IGen::pop_gpr64(i)); + } + } +} + +void CodeTester::emit_push_all_gprs(bool exclude_rax) { + for (int i = 0; i < 16; i++) { + if (i != RAX || !exclude_rax) { + emit(IGen::push_gpr64(i)); + } + } +} + +void CodeTester::emit_push_all_xmms() { + emit(IGen::sub_gpr64_imm8s(RSP, 8)); + for(int i = 0; i < 16; i++) { + emit(IGen::sub_gpr64_imm8s(RSP, 16)); + emit(IGen::store128_gpr64_xmm128(RSP, XMM0 + i)); + } +} + +void CodeTester::emit_pop_all_xmms() { + for(int i = 0; i < 16; i++) { + emit(IGen::load128_xmm128_gpr64(XMM0 + i, RSP)); + emit(IGen::add_gpr64_imm8s(RSP, 16)); + } + emit(IGen::add_gpr64_imm8s(RSP, 8)); +} + +void CodeTester::clear() { + code_buffer_size = 0; +} + +u64 CodeTester::execute() { + return ((u64(*)())code_buffer)(); +} + +u64 CodeTester::execute(u64 in0, u64 in1, u64 in2, u64 in3) { + return ((u64(*)(u64, u64, u64, u64))code_buffer)(in0, in1, in2, in3); +} + +void CodeTester::init_code_buffer(int capacity) { + code_buffer = (u8*)mmap(nullptr, capacity, PROT_EXEC | PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); + if (code_buffer == (u8*)(-1)) { + printf("[CodeTester] Failed to map memory!\n"); + assert(false); + } + + code_buffer_capacity = capacity; + code_buffer_size = 0; +} + +CodeTester::~CodeTester() { + if (code_buffer_capacity) { + munmap(code_buffer, code_buffer_capacity); + } +} +} \ No newline at end of file diff --git a/goalc/emitter/CodeTester.h b/goalc/emitter/CodeTester.h index ae961f49f4..626ff8e4e2 100644 --- a/goalc/emitter/CodeTester.h +++ b/goalc/emitter/CodeTester.h @@ -1,6 +1,54 @@ #ifndef JAK_CODETESTER_H #define JAK_CODETESTER_H -class CodeTester {}; +#include +#include "common/common_types.h" +#include "Register.h" +#include "Instruction.h" +namespace emitter { +class CodeTester { + public: + CodeTester(); + std::string dump_to_hex_string(bool nospace = false); + void init_code_buffer(int capacity); + void emit_push_all_gprs(bool exclude_rax = false); + void emit_pop_all_gprs(bool exclude_rax = false); + void emit_push_all_xmms(); + void emit_pop_all_xmms(); + void emit_return(); + void emit_set_gpr_as_return(Register gpr); + void emit(const Instruction& instr); + u64 execute(); + u64 execute(u64 in0, u64 in1, u64 in2, u64 in3); + void clear(); + ~CodeTester(); + + Register get_c_abi_arg_reg(int i) { + // todo - this should be different for windows. + switch(i) { + case 0: + return RDI; + case 1: + return RSI; + case 2: + return RDX; + case 3: + return RCX; + default: + assert(false); + } + } + + std::string reg_name(Register x) { + return m_info.get_info(x).name; + } + + private: + int code_buffer_size = 0; + int code_buffer_capacity = 0; + u8* code_buffer = nullptr; + RegisterInfo m_info; +}; +} // namespace goal #endif // JAK_CODETESTER_H diff --git a/goalc/emitter/IGen.h b/goalc/emitter/IGen.h new file mode 100644 index 0000000000..8e2719923f --- /dev/null +++ b/goalc/emitter/IGen.h @@ -0,0 +1,346 @@ +#ifndef JAK_IGEN_H +#define JAK_IGEN_H + +#include +#include "Register.h" +#include "Instruction.h" + +namespace emitter { +class IGen { + public: + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + // MOVES + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + /*! + * Move data from src to dst. Moves all 64-bits of the GPR. + */ + static Instruction mov_gpr64_gpr64(Register dst, Register src) { + assert(dst.is_gpr()); + assert(src.is_gpr()); + Instruction instr(0x89); + instr.set_modrm_and_rex(src.hw_id(), dst.hw_id(), 3, true); + return instr; + } + + /*! + * Move a 64-bit constant into a register. + */ + static Instruction mov_gpr64_u64(Register dst, uint64_t val) { + assert(dst.is_gpr()); + bool rex_b = false; + auto dst_hw_id = dst.hw_id(); + if (dst_hw_id >= 8) { + dst_hw_id -= 8; + rex_b = true; + } + Instruction instr(0xb8 + dst_hw_id); + instr.set(REX(true, false, false, rex_b)); + instr.set(Imm(8, val)); + return instr; + } + + /*! + * Move a 32-bit constant into a register. Zeros the upper 32 bits. + */ + static Instruction mov_gpr64_u32(Register dst, uint64_t val) { + assert(val <= UINT32_MAX); + assert(dst.is_gpr()); + auto dst_hw_id = dst.hw_id(); + bool rex_b = false; + if (dst_hw_id >= 8) { + dst_hw_id -= 8; + rex_b = true; + } + + Instruction instr(0xb8 + dst_hw_id); + if (rex_b) { + instr.set(REX(false, false, false, rex_b)); + } + instr.set(Imm(4, val)); + return instr; + } + + /*! + * Move a signed 32-bit constant into a register. Sign extends for the upper 32 bits. + * When possible prefer mov_gpr64_u32. (use this only for negative values...) + * This is always bigger than mov_gpr64_u32, but smaller than a mov_gpr_u64. + */ + static Instruction mov_gpr64_s32(Register dst, int64_t val) { + assert(val >= INT32_MIN && val <= INT32_MAX); + assert(dst.is_gpr()); + Instruction instr(0xc7); + instr.set_modrm_and_rex(0, dst.hw_id(), 3, true); + instr.set(Imm(4, val)); + return instr; + } + + /*! + * Move 32-bits of xmm to 32 bits of gpr (no sign extension). + */ + static Instruction movd_gpr32_xmm32(Register dst, Register src) { + assert(dst.is_gpr()); + assert(src.is_xmm()); + Instruction instr(0x66); + instr.set_op2(0x0f); + instr.set_op3(0x7e); + instr.set_modrm_and_rex(src.hw_id(), dst.hw_id(), 3, false); + instr.swap_op0_rex(); + return instr; + } + + /*! + * Move 32-bits of gpr to 32-bits of xmm (no sign extension) + */ + static Instruction movd_xmm32_gpr32(Register dst, Register src) { + assert(dst.is_xmm()); + assert(src.is_gpr()); + Instruction instr(0x66); + instr.set_op2(0x0f); + instr.set_op3(0x6e); + instr.set_modrm_and_rex(dst.hw_id(), src.hw_id(), 3, false); + instr.swap_op0_rex(); + return instr; + } + + /*! + * Move 32-bits between xmm's + */ + static Instruction mov_xmm32_xmm32(Register dst, Register src) { + assert(dst.is_xmm()); + assert(src.is_xmm()); + Instruction instr(0xf3); + instr.set_op2(0x0f); + instr.set_op3(0x10); + instr.set_modrm_and_rex(dst.hw_id(), src.hw_id(), 3, false); + return instr; + } + + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + // LOADS n' STORES - reg + reg addr + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + /*! + * movsx dst, BYTE PTR [addr1 + addr2] + * addr1 and addr2 have to be different registers. + * Cannot use rsp. + */ + static Instruction load8s_gpr64_gpr64_plus_gpr64(Register dst, Register addr1, Register addr2) { + assert(dst.is_gpr()); + assert(addr1.is_gpr()); + assert(addr2.is_gpr()); + assert(addr1 != addr2); + assert(addr1 != RSP); + assert(addr2 != RSP); + Instruction instr(0xf); + instr.set_op2(0xbe); + instr.set_modrm_and_rex_for_reg_plus_reg_addr(dst.hw_id(), addr1.hw_id(), addr2.hw_id(), true, + false); + return instr; + } + + /*! + * movzx dst, BYTE PTR [addr1 + addr2] + * addr1 and addr2 have to be different registers. + * Cannot use rsp. + */ + static Instruction load8u_gpr64_gpr64_plus_gpr64(Register dst, Register addr1, Register addr2) { + assert(dst.is_gpr()); + assert(addr1.is_gpr()); + assert(addr2.is_gpr()); + assert(addr1 != addr2); + assert(addr1 != RSP); + assert(addr2 != RSP); + Instruction instr(0xf); + instr.set_op2(0xb6); + instr.set_modrm_and_rex_for_reg_plus_reg_addr(dst.hw_id(), addr1.hw_id(), addr2.hw_id(), true, + false); + return instr; + } + + /*! + * movsx dst, WORD PTR [addr1 + addr2] + * addr1 and addr2 have to be different registers. + * Cannot use rsp. + */ + static Instruction load16s_gpr64_gpr64_plus_gpr64(Register dst, Register addr1, Register addr2) { + assert(dst.is_gpr()); + assert(addr1.is_gpr()); + assert(addr2.is_gpr()); + assert(addr1 != addr2); + assert(addr1 != RSP); + assert(addr2 != RSP); + Instruction instr(0xf); + instr.set_op2(0xbf); + instr.set_modrm_and_rex_for_reg_plus_reg_addr(dst.hw_id(), addr1.hw_id(), addr2.hw_id(), true, + false); + return instr; + } + + + /*! + * movzx dst, WORD PTR [addr1 + addr2] + * addr1 and addr2 have to be different registers. + * Cannot use rsp. + */ + static Instruction load16u_gpr64_gpr64_plus_gpr64(Register dst, Register addr1, Register addr2) { + assert(dst.is_gpr()); + assert(addr1.is_gpr()); + assert(addr2.is_gpr()); + assert(addr1 != addr2); + assert(addr1 != RSP); + assert(addr2 != RSP); + Instruction instr(0xf); + instr.set_op2(0xb7); + instr.set_modrm_and_rex_for_reg_plus_reg_addr(dst.hw_id(), addr1.hw_id(), addr2.hw_id(), true, + false); + return instr; + } + + /*! + * movsxd dst, DWORD PTR [addr1 + addr2] + * addr1 and addr2 have to be different registers. + * Cannot use rsp. + */ + static Instruction load32s_gpr64_gpr64_plus_gpr64(Register dst, Register addr1, Register addr2) { + assert(dst.is_gpr()); + assert(addr1.is_gpr()); + assert(addr2.is_gpr()); + assert(addr1 != addr2); + assert(addr1 != RSP); + assert(addr2 != RSP); + Instruction instr(0x63); + instr.set_modrm_and_rex_for_reg_plus_reg_addr(dst.hw_id(), addr1.hw_id(), addr2.hw_id(), true); + return instr; + } + + /*! + * movzxd dst, DWORD PTR [addr1 + addr2] + * addr1 and addr2 have to be different registers. + * Cannot use rsp. + */ + static Instruction load32u_gpr64_gpr64_plus_gpr64(Register dst, Register addr1, Register addr2) { + assert(dst.is_gpr()); + assert(addr1.is_gpr()); + assert(addr2.is_gpr()); + assert(addr1 != addr2); + assert(addr1 != RSP); + assert(addr2 != RSP); + Instruction instr(0x8b); + instr.set_modrm_and_rex_for_reg_plus_reg_addr(dst.hw_id(), addr1.hw_id(), addr2.hw_id()); + return instr; + } + + /*! + * mov dst, QWORD PTR [addr1 + addr2] + * addr1 and addr2 have to be different registers. + * Cannot use rsp. + */ + static Instruction load64_gpr64_gpr64_plus_gpr64(Register dst, Register addr1, Register addr2) { + assert(dst.is_gpr()); + assert(addr1.is_gpr()); + assert(addr2.is_gpr()); + assert(addr1 != addr2); + assert(addr1 != RSP); + assert(addr2 != RSP); + Instruction instr(0x8b); + instr.set_modrm_and_rex_for_reg_plus_reg_addr(dst.hw_id(), addr1.hw_id(), addr2.hw_id(), true); + return instr; + } + + /*! + * Store a 128-bit xmm into a register, no offset + */ + static Instruction store128_gpr64_xmm128(Register gpr_addr, Register xmm_value) { + assert(gpr_addr.is_gpr()); + assert(xmm_value.is_xmm()); + Instruction instr(0x66); + instr.set_op2(0x0f); + instr.set_op3(0x7f); + instr.set_modrm_and_rex_for_reg_addr(xmm_value.hw_id(), gpr_addr.hw_id(), false); + instr.swap_op0_rex(); + return instr; + } + + static Instruction load128_xmm128_gpr64(Register xmm_dest, Register gpr_addr) { + assert(gpr_addr.is_gpr()); + assert(xmm_dest.is_xmm()); + Instruction instr(0x66); + instr.set_op2(0x0f); + instr.set_op3(0x6f); + instr.set_modrm_and_rex_for_reg_addr(xmm_dest.hw_id(), gpr_addr.hw_id(), false); + instr.swap_op0_rex(); + return instr; + } + + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + // FUNCTION STUFF + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + /*! + * Function return. Pops the 64-bit return address (real) off the stack and jumps to it. + */ + static Instruction ret() { return Instruction(0xc3); } + + /*! + * Instruction to push gpr (64-bits) onto the stack + */ + static Instruction push_gpr64(Register reg) { + assert(reg.is_gpr()); + if (reg.hw_id() >= 8) { + auto i = Instruction(0x50 + reg.hw_id() - 8); + i.set(REX(false, false, false, true)); + return i; + } + return Instruction(0x50 + reg.hw_id()); + } + + /*! + * Instruction to pop 64 bit gpr from the stack + */ + static Instruction pop_gpr64(Register reg) { + if (reg.hw_id() >= 8) { + auto i = Instruction(0x58 + reg.hw_id() - 8); + i.set(REX(false, false, false, true)); + return i; + } + return Instruction(0x58 + reg.hw_id()); + } + + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + // INTEGER MATH + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + static Instruction sub_gpr64_imm8s(Register reg, int64_t imm) { + assert(reg.is_gpr()); + assert(imm >= INT8_MIN && imm <= INT8_MAX); + // SUB r/m64, imm8 : REX.W + 83 /5 ib + Instruction instr(0x83); + instr.set_modrm_and_rex(5, reg.hw_id(), 3, true); + instr.set(Imm(1, imm)); + return instr; + } + + static Instruction add_gpr64_imm8s(Register reg, int8_t v) { + Instruction instr(0x83); + instr.set_modrm_and_rex(0, reg.hw_id(), 3, true); + instr.set(Imm(1, v)); + return instr; + } + + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + // BIT STUFF + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + // CONTROL FLOW + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + // FLOAT MATH + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + // UTILITIES + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +}; +} // namespace emitter + +#endif // JAK_IGEN_H diff --git a/goalc/old_emitter/Instruction.h b/goalc/emitter/Instruction.h similarity index 78% rename from goalc/old_emitter/Instruction.h rename to goalc/emitter/Instruction.h index cdf74dfe6b..4383bca475 100644 --- a/goalc/old_emitter/Instruction.h +++ b/goalc/emitter/Instruction.h @@ -1,15 +1,10 @@ -/*! - * @file Instruction.h - * x86-64 instruction encoding - */ -#ifndef JAK1_INSTRUCTION_H -#define JAK1_INSTRUCTION_H +#ifndef JAK_INSTRUCTION_H +#define JAK_INSTRUCTION_H #include #include "common/common_types.h" -namespace goal { - +namespace emitter { /*! * The ModRM byte */ @@ -174,11 +169,76 @@ struct Instruction { } } + void set_modrm_and_rex_for_reg_plus_reg_addr(uint8_t reg, uint8_t addr1, uint8_t addr2, bool rex_w = false, bool rex_always = false) { + bool rex_b = false, rex_r = false, rex_x = false; + bool addr1_ext = false; + bool addr2_ext = false; + + if (addr1 >= 8) { + addr1 -= 8; + addr1_ext = true; + } + + if (addr2 >= 8) { + addr2 -= 8; + addr2_ext = true; + } + + if (reg >= 8) { + reg -= 8; + rex_r = true; + } + + ModRM modrm; + modrm.mod = 0; // no disp + modrm.rm = 4; // sib! + modrm.reg_op = reg; + + SIB sib; + sib.scale = 0; + + if(addr1 == 5 && addr2 == 5) { + sib.index = addr1; + sib.base = addr2; + rex_x = addr1_ext; + rex_b = addr2_ext; + modrm.mod = 1; + set(Imm(1, 0)); + + } else { + // default addr1 in index + bool flipped = (addr1 == 4) || (addr2 == 5); + + if(flipped) { + sib.index = addr2; + sib.base = addr1; + rex_x = addr2_ext; + rex_b = addr1_ext; + } else { + // addr1 in index + sib.index = addr1; + sib.base = addr2; + rex_x = addr1_ext; + rex_b = addr2_ext; + } + assert(sib.base != 5); + assert(sib.index != 4); + } + + + if(rex_b || rex_w || rex_r || rex_x || rex_always) { + set(REX(rex_w, rex_r, rex_x, rex_b)); + } + + set(modrm); + set(sib); + } + /*! * Set modrm and rex as needed for two regs for an addressing mode. * Will set SIB if R12 or RSP indexing is used. */ - void set_modrm_and_rex_for_addr(uint8_t reg, uint8_t rm, uint8_t mod, bool rex_w = false) { + void set_modrm_and_rex_for_reg_addr(uint8_t reg, uint8_t rm, bool rex_w = false) { bool rex_b = false, rex_r = false; if (rm >= 8) { @@ -192,12 +252,10 @@ struct Instruction { } ModRM modrm; - modrm.mod = mod; + modrm.mod = 0; modrm.reg_op = reg; modrm.rm = rm; - set(modrm); - if (rm == 4) { SIB sib; sib.scale = 0; @@ -207,6 +265,12 @@ struct Instruction { set(sib); } + if (rm == 5) { + modrm.mod = 1; // 1 byte imm + set(Imm(1, 0)); + } + + set(modrm); if (rex_b || rex_w || rex_r) { set(REX(rex_w, rex_r, false, rex_b)); } @@ -343,6 +407,6 @@ struct Instruction { return count; } }; -} // namespace goal +} // namespace emitter -#endif // JAK1_INSTRUCTION_H +#endif // JAK_INSTRUCTION_H diff --git a/goalc/emitter/Register.cpp b/goalc/emitter/Register.cpp index e83d953d9c..8f692cdcff 100644 --- a/goalc/emitter/Register.cpp +++ b/goalc/emitter/Register.cpp @@ -1 +1,36 @@ -#include "Register.h" \ No newline at end of file +#include "Register.h" + +namespace emitter { +RegisterInfo RegisterInfo::make_register_info() { + RegisterInfo info; + + info.m_info[RAX] = {-1, false, false, "rax"}; + info.m_info[RCX] = {3, false, false, "rcx"}; + info.m_info[RDX] = {2, false, false, "rdx"}; + 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[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_arg_regs = std::array({RDI, RSI, RDX, RCX, R8, R9, R10, R11}); + 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}); + + return info; +} + + + + + +} \ No newline at end of file diff --git a/goalc/emitter/Register.h b/goalc/emitter/Register.h index 4760d01c98..91acd4ada0 100644 --- a/goalc/emitter/Register.h +++ b/goalc/emitter/Register.h @@ -20,8 +20,8 @@ enum X86_REG : u8 { RDX, // arg 2 RBX, // X saved - RSP, // stack pointer - RBP, // X base pointer (like fp) + RSP, // stack pointer !!!! + RBP, // saved !!!! RSI, // arg 1 RDI, // arg 0 @@ -29,8 +29,8 @@ enum X86_REG : u8 { R9, // arg 5, saved R10, // arg 6, saved (arg in GOAL only) R11, // arg 7, saved (arg in GOAL only) - R12, // X saved - pp register (like s6) - R13, // X saved - function call register (like t9) + R12, // X saved - pp register (like s6) !! + R13, // X saved - function call register (like t9) !! R14, // X saved - offset (added in GOAL x86) R15, // X saved - st (like s7) XMM0, @@ -51,8 +51,7 @@ enum X86_REG : u8 { XMM15 }; -constexpr int N_REGS = 32; -static_assert(N_REGS - 1 == XMM15, "bad register count"); + class Register { public: @@ -61,11 +60,11 @@ class Register { // intentionally not explicit so we can use X86_REGs in place of Registers Register(int id) : m_id(id) {} - bool is_xmm() { return m_id >= XMM0 && m_id <= XMM15; } + bool is_xmm() const { return m_id >= XMM0 && m_id <= XMM15; } - bool is_gpr() { return m_id >= RAX && m_id <= R15; } + bool is_gpr() const { return m_id >= RAX && m_id <= R15; } - int hw_id() { + int hw_id() const { if (is_xmm()) { return m_id - XMM0; } else if (is_gpr()) { @@ -76,47 +75,80 @@ class Register { return 0xff; } + int id() const { + return m_id; + } + struct hash { auto operator()(const Register& x) const { return std::hash()(x.m_id); } }; - bool operator==(const Register& x) const { - return m_id == x.m_id; - } + bool operator==(const Register& x) const { return m_id == x.m_id; } - bool operator!=(const Register& x) const { - return m_id != x.m_id; - } + bool operator!=(const Register& x) const { return m_id != x.m_id; } private: u8 m_id = 0xff; }; - - class RegisterInfo { + public: + static constexpr int N_ARGS = 8; + static constexpr int N_REGS = 32; + static constexpr int N_SAVED_GPRS = 5; + static constexpr int N_SAVED_XMMS = 8; + + static_assert(N_REGS - 1 == XMM15, "bad register count"); + + 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? + 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; }; - const Info& get_info(Register r); + const Info& get_info(Register r) const { + return m_info.at(r.id()); + } - int get_arg_reg_count(); - Register get_arg_reg(int id); + Register get_arg_reg(int id) const { + return m_arg_regs.at(id); + } - int get_saved_reg_count(); - Register get_saved_reg(int id); + Register get_saved_gpr(int id) const { + return m_saved_gprs.at(id); + } - Register get_process_reg(); - Register get_st_reg(); - Register get_offset_reg(); - Register get_ret_reg(); + 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; + } + + private: + RegisterInfo() = default; + std::array m_info; + std::array m_arg_regs; + std::array m_saved_gprs; + std::array m_saved_xmms; }; - } // namespace emitter #endif // JAK_REGISTER_H diff --git a/goalc/goos/Interpreter.cpp b/goalc/goos/Interpreter.cpp index 26bbc94852..937525e050 100644 --- a/goalc/goos/Interpreter.cpp +++ b/goalc/goos/Interpreter.cpp @@ -85,6 +85,14 @@ Interpreter::Interpreter() { load_goos_library(); } + +Interpreter::~Interpreter() { + // There are some circular references that prevent shared_ptrs from cleaning up if we + // don't do this. + global_environment.as_env()->vars.clear(); + goal_env.as_env()->vars.clear(); +} + /*! * Disable printfs on errors, to make test output look less messy. */ diff --git a/goalc/goos/Interpreter.h b/goalc/goos/Interpreter.h index 7d37dbe210..b19903e68e 100644 --- a/goalc/goos/Interpreter.h +++ b/goalc/goos/Interpreter.h @@ -15,6 +15,7 @@ namespace goos { class Interpreter { public: Interpreter(); + ~Interpreter(); void execute_repl(); void throw_eval_error(const Object& o, const std::string& err); Object eval_with_rewind(const Object& obj, const std::shared_ptr& env); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3cff0d2440..38374ded9c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -5,8 +5,11 @@ add_executable(goalc-test test_goos.cpp test_listener_deci2.cpp test_kernel.cpp - test_CodeTester.cpp all_jak1_symbols.cpp - test_type_system.cpp) + test_type_system.cpp + test_CodeTester.cpp + test_emitter_slow.cpp + test_emitter_fast.cpp + ) target_link_libraries(goalc-test goos util listener runtime emitter type_system gtest) \ No newline at end of file diff --git a/test/test_CodeTester.cpp b/test/test_CodeTester.cpp index 9f3d2e36b8..77e3e41832 100644 --- a/test/test_CodeTester.cpp +++ b/test/test_CodeTester.cpp @@ -2,166 +2,169 @@ * @file test_CodeTester.cpp * Tests for the CodeTester, a tool for testing the emitter by emitting code and running it * from within the test application. + * + * These tests should just make sure the basic functionality of CodeTester works, and that it + * can generate prologues/epilogues, and execute them without crashing. */ #include "gtest/gtest.h" #include "goalc/emitter/CodeTester.h" -//#include "goalc/emitter/IGen.h" -// -//using namespace goal; -// -//TEST(CodeTester, prologue) { -// CodeTester tester; -// tester.init_code_buffer(256); -// tester.emit_push_all_gprs(); -// // check we generate the right code for pushing all gpr's -// EXPECT_EQ(tester.dump_to_hex_string(), -// "50 51 52 53 54 55 56 57 41 50 41 51 41 52 41 53 41 54 41 55 41 56 41 57"); -//} -// -//TEST(CodeTester, epilogue) { -// CodeTester tester; -// tester.init_code_buffer(256); -// tester.emit_pop_all_gprs(); -// // check we generate the right code for popping all gpr's -// EXPECT_EQ(tester.dump_to_hex_string(), -// "41 5f 41 5e 41 5d 41 5c 41 5b 41 5a 41 59 41 58 5f 5e 5d 5c 5b 5a 59 58"); -//} -// -//TEST(CodeTester, execute_return) { -// CodeTester tester; -// tester.init_code_buffer(256); -// // test creating a function which simply returns -// tester.emit_return(); -// // and execute it! -// tester.execute(); -//} -// -//TEST(CodeTester, execute_push_pop_gprs) { -// CodeTester tester; -// tester.init_code_buffer(256); -// // test we can push/pop gprs without crashing. -// tester.emit_push_all_gprs(); -// tester.emit_pop_all_gprs(); -// tester.emit_return(); -// tester.execute(); -//} -// -//TEST(CodeTester, load_constant_64_and_move_gpr_gpr_64) { -// std::vector u64_constants = {0, UINT64_MAX, INT64_MAX, 7, 12}; -// -// // test we can load a 64-bit constant into all gprs, move it to any other gpr, and return it. -// // rsp is skipping because that's the stack pointer and would prevent us from popping gprs after -// -// CodeTester tester; -// tester.init_code_buffer(256); -// -// for (auto constant : u64_constants) { -// for (int r1 = 0; r1 < 16; r1++) { -// if (r1 == RSP) { -// continue; -// } -// -// for (int r2 = 0; r2 < 16; r2++) { -// if (r2 == RSP) { -// continue; -// } -// tester.clear(); -// tester.emit_push_all_gprs(true); -// tester.emit(IGen::mov_gpr64_u64(r1, constant)); -// tester.emit(IGen::mov_gpr64_gpr64(r2, r1)); -// tester.emit(IGen::mov_gpr64_gpr64(RAX, r2)); -// tester.emit_pop_all_gprs(true); -// tester.emit_return(); -// EXPECT_EQ(tester.execute(), constant); -// } -// } -// } -//} -// -//TEST(CodeTester, load_constant_32_unsigned) { -// std::vector u64_constants = {0, UINT32_MAX, INT32_MAX, 7, 12}; -// -// // test loading 32-bit constants, with all upper 32-bits zero. -// // this uses a different opcode than 64-bit loads. -// CodeTester tester; -// tester.init_code_buffer(256); -// -// for (auto constant : u64_constants) { -// for (int r1 = 0; r1 < 16; r1++) { -// if (r1 == RSP) { -// continue; -// } -// -// tester.clear(); -// tester.emit_push_all_gprs(true); -// tester.emit(IGen::mov_gpr64_u32(r1, constant)); -// tester.emit(IGen::mov_gpr64_gpr64(RAX, r1)); -// tester.emit_pop_all_gprs(true); -// tester.emit_return(); -// EXPECT_EQ(tester.execute(), constant); -// } -// } -//} -// -//TEST(CodeTester, load_constant_32_signed) { -// std::vector s32_constants = {0, 1, INT32_MAX, INT32_MIN, 12, -1}; -// -// // test loading signed 32-bit constants. for values < 0 this will sign extend. -// CodeTester tester; -// tester.init_code_buffer(256); -// -// for (auto constant : s32_constants) { -// for (int r1 = 0; r1 < 16; r1++) { -// if (r1 == RSP) { -// continue; -// } -// -// tester.clear(); -// tester.emit_push_all_gprs(true); -// tester.emit(IGen::mov_gpr64_s32(r1, constant)); -// tester.emit(IGen::mov_gpr64_gpr64(RAX, r1)); -// tester.emit_pop_all_gprs(true); -// tester.emit_return(); -// EXPECT_EQ(tester.execute(), constant); -// } -// } -//} -// -//TEST(CodeTester, xmm_move) { -// std::vector u32_constants = {0, INT32_MAX, UINT32_MAX, 17}; -// -// // test moving between xmms (32-bit) and gprs. -// CodeTester tester; -// tester.init_code_buffer(256); -// -// for (auto constant : u32_constants) { -// for (int r1 = 0; r1 < 16; r1++) { -// if (r1 == RSP) { -// continue; -// } -// for (int r2 = 0; r2 < 16; r2++) { -// if (r2 == RSP) { -// continue; -// } -// for (int r3 = 0; r3 < 16; r3++) { -// for (int r4 = 0; r4 < 16; r4++) { -// tester.clear(); -// tester.emit_push_all_gprs(true); -// // move constant to gpr -// tester.emit(IGen::mov_gpr64_u32(r1, constant)); -// // move gpr to xmm -// tester.emit(IGen::movd_xmm32_gpr32(get_nth_xmm(r3), r1)); -// // move xmm to xmm -// tester.emit(IGen::mov_xmm32_xmm32(get_nth_xmm(r4), get_nth_xmm(r3))); -// // move xmm to gpr -// tester.emit(IGen::movd_gpr32_xmm32(r2, get_nth_xmm(r4))); -// // return! -// tester.emit(IGen::mov_gpr64_gpr64(RAX, r2)); -// tester.emit_return(); -// } -// } -// } -// } -// } -//} \ No newline at end of file +#include "goalc/emitter/IGen.h" + +using namespace emitter; + +TEST(CodeTester, prologue) { + CodeTester tester; + tester.init_code_buffer(256); + tester.emit_push_all_gprs(); + // check we generate the right code for pushing all gpr's + EXPECT_EQ(tester.dump_to_hex_string(), + "50 51 52 53 54 55 56 57 41 50 41 51 41 52 41 53 41 54 41 55 41 56 41 57"); +} + +TEST(CodeTester, epilogue) { + CodeTester tester; + tester.init_code_buffer(256); + tester.emit_pop_all_gprs(); + // check we generate the right code for popping all gpr's + EXPECT_EQ(tester.dump_to_hex_string(), + "41 5f 41 5e 41 5d 41 5c 41 5b 41 5a 41 59 41 58 5f 5e 5d 5c 5b 5a 59 58"); +} + +TEST(CodeTester, execute_return) { + CodeTester tester; + tester.init_code_buffer(256); + // test creating a function which simply returns + tester.emit_return(); + // and execute it! + tester.execute(); +} + +TEST(CodeTester, execute_push_pop_gprs) { + CodeTester tester; + tester.init_code_buffer(256); + // test we can push/pop gprs without crashing. + tester.emit_push_all_gprs(); + tester.emit_pop_all_gprs(); + tester.emit_return(); + tester.execute(); +} + +TEST(CodeTester, xmm_store_128) { + CodeTester tester; + tester.init_code_buffer(256); + // movdqa [rbx], xmm3 + // movdqa [r14], xmm3 + // movdqa [rbx], xmm14 + // movdqa [r14], xmm13 + tester.emit(IGen::store128_gpr64_xmm128(RBX, XMM3)); + tester.emit(IGen::store128_gpr64_xmm128(R14, XMM3)); + tester.emit(IGen::store128_gpr64_xmm128(RBX, XMM14)); + tester.emit(IGen::store128_gpr64_xmm128(R14, XMM13)); + EXPECT_EQ(tester.dump_to_hex_string(), + "66 0f 7f 1b 66 41 0f 7f 1e 66 44 0f 7f 33 66 45 0f 7f 2e"); + + tester.clear(); + tester.emit(IGen::store128_gpr64_xmm128(RSP, XMM1)); + EXPECT_EQ(tester.dump_to_hex_string(), "66 0f 7f 0c 24"); // requires SIB byte. + + tester.clear(); + tester.emit(IGen::store128_gpr64_xmm128(R12, XMM13)); + EXPECT_EQ(tester.dump_to_hex_string(), "66 45 0f 7f 2c 24"); // requires SIB byte and REX byte + + tester.clear(); + tester.emit(IGen::store128_gpr64_xmm128(RBP, XMM1)); + EXPECT_EQ(tester.dump_to_hex_string(), "66 0f 7f 4d 00"); + + tester.clear(); + tester.emit(IGen::store128_gpr64_xmm128(RBP, XMM11)); + EXPECT_EQ(tester.dump_to_hex_string(), "66 44 0f 7f 5d 00"); + + tester.clear(); + tester.emit(IGen::store128_gpr64_xmm128(R13, XMM2)); + EXPECT_EQ(tester.dump_to_hex_string(), "66 41 0f 7f 55 00"); + + tester.clear(); + tester.emit(IGen::store128_gpr64_xmm128(R13, XMM12)); + EXPECT_EQ(tester.dump_to_hex_string(), "66 45 0f 7f 65 00"); +} + +TEST(CodeTester, sub_gpr64_imm8) { + CodeTester tester; + tester.init_code_buffer(256); + for (int i = 0; i < 16; i++) { + tester.emit(IGen::sub_gpr64_imm8s(i, -1)); + } + EXPECT_EQ(tester.dump_to_hex_string(true), + "4883E8FF4883E9FF4883EAFF4883EBFF4883ECFF4883EDFF4883EEFF4883EFFF4983E8FF4983E9FF4983EA" + "FF4983EBFF4983ECFF4983EDFF4983EEFF4983EFFF"); +} + +TEST(CodeTester, add_gpr64_imm8) { + CodeTester tester; + tester.init_code_buffer(256); + for (int i = 0; i < 16; i++) { + tester.emit(IGen::add_gpr64_imm8s(i, -1)); + } + EXPECT_EQ(tester.dump_to_hex_string(true), + "4883C0FF4883C1FF4883C2FF4883C3FF4883C4FF4883C5FF4883C6FF4883C7FF4983C0FF4983C1FF4983C2" + "FF4983C3FF4983C4FF4983C5FF4983C6FF4983C7FF"); +} + +TEST(CodeTester, xmm_load_128) { + CodeTester tester; + tester.init_code_buffer(256); + tester.emit(IGen::load128_xmm128_gpr64(XMM3, RBX)); + tester.emit(IGen::load128_xmm128_gpr64(XMM3, R14)); + tester.emit(IGen::load128_xmm128_gpr64(XMM14, RBX)); + tester.emit(IGen::load128_xmm128_gpr64(XMM13, R14)); + EXPECT_EQ(tester.dump_to_hex_string(), + "66 0f 6f 1b 66 41 0f 6f 1e 66 44 0f 6f 33 66 45 0f 6f 2e"); + + tester.clear(); + tester.emit(IGen::load128_xmm128_gpr64(XMM1, RSP)); + EXPECT_EQ(tester.dump_to_hex_string(), "66 0f 6f 0c 24"); // requires SIB byte. + + tester.clear(); + tester.emit(IGen::load128_xmm128_gpr64(XMM13, R12)); + EXPECT_EQ(tester.dump_to_hex_string(), "66 45 0f 6f 2c 24"); // requires SIB byte and REX byte + + tester.clear(); + tester.emit(IGen::load128_xmm128_gpr64(XMM1, RBP)); + EXPECT_EQ(tester.dump_to_hex_string(), "66 0f 6f 4d 00"); + + tester.clear(); + tester.emit(IGen::load128_xmm128_gpr64(XMM11, RBP)); + EXPECT_EQ(tester.dump_to_hex_string(), "66 44 0f 6f 5d 00"); + + tester.clear(); + tester.emit(IGen::load128_xmm128_gpr64(XMM2, R13)); + EXPECT_EQ(tester.dump_to_hex_string(), "66 41 0f 6f 55 00"); + + tester.clear(); + tester.emit(IGen::load128_xmm128_gpr64(XMM12, R13)); + EXPECT_EQ(tester.dump_to_hex_string(), "66 45 0f 6f 65 00"); +} + +TEST(CodeTester, push_pop_xmms) { + CodeTester tester; + tester.init_code_buffer(512); + tester.emit_push_all_xmms(); + tester.emit_pop_all_xmms(); + tester.emit_return(); + tester.execute(); +} + +TEST(CodeTester, push_pop_all_the_things) { + CodeTester tester; + tester.init_code_buffer(512); + tester.emit_push_all_xmms(); + tester.emit_push_all_gprs(); + + // ... + tester.emit_pop_all_gprs(); + tester.emit_pop_all_xmms(); + tester.emit_return(); + tester.execute(); +} + diff --git a/test/test_emitter_fast.cpp b/test/test_emitter_fast.cpp new file mode 100644 index 0000000000..a5e70ff80c --- /dev/null +++ b/test/test_emitter_fast.cpp @@ -0,0 +1,585 @@ +/*! + * @file test_emitter_slow.cpp + * Tests for the emitter which are fast (checking 100's of functions) + */ + +#include "gtest/gtest.h" +#include "goalc/emitter/CodeTester.h" +#include "goalc/emitter/IGen.h" +#include "third-party/fmt/core.h" +// +using namespace emitter; + +TEST(Emitter, load_constant_64_and_move_gpr_gpr_64) { + std::vector u64_constants = {0, UINT64_MAX, INT64_MAX, 7, 12}; + + // test we can load a 64-bit constant into all gprs, move it to any other gpr, and return it. + // rsp is skipping because that's the stack pointer and would prevent us from popping gprs after + + CodeTester tester; + tester.init_code_buffer(256); + + for (auto constant : u64_constants) { + for (int r1 = 0; r1 < 16; r1++) { + if (r1 == RSP) { + continue; + } + + for (int r2 = 0; r2 < 16; r2++) { + if (r2 == RSP) { + continue; + } + tester.clear(); + tester.emit_push_all_gprs(true); + tester.emit(IGen::mov_gpr64_u64(r1, constant)); + tester.emit(IGen::mov_gpr64_gpr64(r2, r1)); + tester.emit(IGen::mov_gpr64_gpr64(RAX, r2)); + tester.emit_pop_all_gprs(true); + tester.emit_return(); + EXPECT_EQ(tester.execute(), constant); + } + } + } +} + +TEST(Emitter, load_constant_32_unsigned) { + std::vector u64_constants = {0, UINT32_MAX, INT32_MAX, 7, 12}; + + // test loading 32-bit constants, with all upper 32-bits zero. + // this uses a different opcode than 64-bit loads. + CodeTester tester; + tester.init_code_buffer(256); + + for (auto constant : u64_constants) { + for (int r1 = 0; r1 < 16; r1++) { + if (r1 == RSP) { + continue; + } + + tester.clear(); + tester.emit_push_all_gprs(true); + tester.emit(IGen::mov_gpr64_u64(r1, UINT64_MAX)); + tester.emit(IGen::mov_gpr64_u32(r1, constant)); + tester.emit(IGen::mov_gpr64_gpr64(RAX, r1)); + tester.emit_pop_all_gprs(true); + tester.emit_return(); + EXPECT_EQ(tester.execute(), constant); + } + } +} + +TEST(Emitter, load_constant_32_signed) { + std::vector s32_constants = {0, 1, INT32_MAX, INT32_MIN, 12, -1}; + + // test loading signed 32-bit constants. for values < 0 this will sign extend. + CodeTester tester; + tester.init_code_buffer(256); + + for (auto constant : s32_constants) { + for (int r1 = 0; r1 < 16; r1++) { + if (r1 == RSP) { + continue; + } + + tester.clear(); + tester.emit_push_all_gprs(true); + tester.emit(IGen::mov_gpr64_s32(r1, constant)); + tester.emit(IGen::mov_gpr64_gpr64(RAX, r1)); + tester.emit_pop_all_gprs(true); + tester.emit_return(); + EXPECT_EQ(tester.execute(), constant); + } + } +} + +TEST(Emitter, load8s_gpr64_goal_ptr_gpr64) { + CodeTester tester; + tester.init_code_buffer(512); + + tester.clear(); + tester.emit(IGen::load8s_gpr64_gpr64_plus_gpr64(RAX, RBX, RSI)); + EXPECT_EQ(tester.dump_to_hex_string(), "48 0f be 04 1e"); + + tester.clear(); + tester.emit(IGen::load8s_gpr64_gpr64_plus_gpr64(R12, RBX, RSI)); + EXPECT_EQ(tester.dump_to_hex_string(), "4c 0f be 24 1e"); + + tester.clear(); + tester.emit(IGen::load8s_gpr64_gpr64_plus_gpr64(R12, R15, RSI)); + EXPECT_EQ(tester.dump_to_hex_string(), "4e 0f be 24 3e"); + + tester.clear(); + tester.emit(IGen::load8s_gpr64_gpr64_plus_gpr64(R12, R15, R14)); + EXPECT_EQ(tester.dump_to_hex_string(), "4f 0f be 24 3e"); + + int iter = 0; + for (int i = 0; i < 16; i++) { + if (i == RSP) { + continue; + } + + for (int j = 0; j < 16; j++) { + if (j == RSP || j == i) { + continue; + } + + for (int k = 0; k < 16; k++) { + if (k == RSP) { + continue; + } + + tester.clear(); + tester.emit_push_all_gprs(true); + // push args to the stack + tester.emit(IGen::push_gpr64(tester.get_c_abi_arg_reg(1))); + tester.emit(IGen::push_gpr64(tester.get_c_abi_arg_reg(0))); + + // pop args into appropriate register + tester.emit(IGen::pop_gpr64(i)); // i will have offset 0 + tester.emit(IGen::pop_gpr64(j)); // j will have offset 1 + + // fill k with junk + if(k != i && k != j) { + tester.emit(IGen::mov_gpr64_u64(k, (iter&1)?0:UINT64_MAX)); + } + + // load into k + tester.emit(IGen::load8s_gpr64_gpr64_plus_gpr64(k, i, j)); + + // move k to return register + tester.emit(IGen::mov_gpr64_gpr64(RAX, k)); + + // return! + tester.emit_pop_all_gprs(true); + tester.emit_return(); + + // prepare the memory: + u8 memory[8] = {0, 0, 0xfd, 0xfe, 0xff, 0, 0, 0}; + + // run! + EXPECT_EQ(s64(tester.execute((u64)memory, 3, 0, 0)), -2); + EXPECT_EQ(s64(tester.execute((u64)memory, 2, 0, 0)), -3); + EXPECT_EQ(s64(tester.execute((u64)memory, 4, 0, 0)), -1); + EXPECT_EQ(s64(tester.execute((u64)memory, 5, 0, 0)), 0); + iter++; + } + } + } +} + +TEST(Emitter, load8u_gpr64_goal_ptr_gpr64) { + CodeTester tester; + tester.init_code_buffer(512); + + tester.clear(); + tester.emit(IGen::load8u_gpr64_gpr64_plus_gpr64(RAX, RBX, RSI)); + EXPECT_EQ(tester.dump_to_hex_string(), "48 0f b6 04 1e"); + + tester.clear(); + tester.emit(IGen::load8u_gpr64_gpr64_plus_gpr64(R12, RBX, RSI)); + EXPECT_EQ(tester.dump_to_hex_string(), "4c 0f b6 24 1e"); + + tester.clear(); + tester.emit(IGen::load8u_gpr64_gpr64_plus_gpr64(R12, R15, RSI)); + EXPECT_EQ(tester.dump_to_hex_string(), "4e 0f b6 24 3e"); + + tester.clear(); + tester.emit(IGen::load8u_gpr64_gpr64_plus_gpr64(R12, R15, R14)); + EXPECT_EQ(tester.dump_to_hex_string(), "4f 0f b6 24 3e"); + + int iter = 0; + for (int i = 0; i < 16; i++) { + if (i == RSP) { + continue; + } + + for (int j = 0; j < 16; j++) { + if (j == RSP || j == i) { + continue; + } + + for (int k = 0; k < 16; k++) { + if (k == RSP) { + continue; + } + + tester.clear(); + tester.emit_push_all_gprs(true); + // push args to the stack + tester.emit(IGen::push_gpr64(tester.get_c_abi_arg_reg(1))); + tester.emit(IGen::push_gpr64(tester.get_c_abi_arg_reg(0))); + + // pop args into appropriate register + tester.emit(IGen::pop_gpr64(i)); // i will have offset 0 + tester.emit(IGen::pop_gpr64(j)); // j will have offset 1 + + // fill k with junk + if(k != i && k != j) { + tester.emit(IGen::mov_gpr64_u64(k, (iter&1)?0:UINT64_MAX)); + } + + // load into k + tester.emit(IGen::load8s_gpr64_gpr64_plus_gpr64(k, i, j)); + + // move k to return register + tester.emit(IGen::mov_gpr64_gpr64(RAX, k)); + + // return! + tester.emit_pop_all_gprs(true); + tester.emit_return(); + + // prepare the memory: + u8 memory[8] = {0, 0, 0xfd, 0xfe, 0xff, 0, 0, 0}; + + // run! + EXPECT_EQ(s64(tester.execute((u64)memory, 3, 0, 0)), -2); + EXPECT_EQ(s64(tester.execute((u64)memory, 2, 0, 0)), -3); + EXPECT_EQ(s64(tester.execute((u64)memory, 4, 0, 0)), -1); + EXPECT_EQ(s64(tester.execute((u64)memory, 5, 0, 0)), 0); + iter++; + } + } + } +} + +TEST(Emitter, load16s_gpr64_goal_ptr_gpr64) { + CodeTester tester; + tester.init_code_buffer(512); + + tester.clear(); + tester.emit(IGen::load16s_gpr64_gpr64_plus_gpr64(RAX, RBX, RSI)); + EXPECT_EQ(tester.dump_to_hex_string(), "48 0f bf 04 1e"); + + tester.clear(); + tester.emit(IGen::load16s_gpr64_gpr64_plus_gpr64(R12, RBX, RSI)); + EXPECT_EQ(tester.dump_to_hex_string(), "4c 0f bf 24 1e"); + + tester.clear(); + tester.emit(IGen::load16s_gpr64_gpr64_plus_gpr64(R12, R15, RSI)); + EXPECT_EQ(tester.dump_to_hex_string(), "4e 0f bf 24 3e"); + + tester.clear(); + tester.emit(IGen::load16s_gpr64_gpr64_plus_gpr64(R12, R15, R14)); + EXPECT_EQ(tester.dump_to_hex_string(), "4f 0f bf 24 3e"); + + int iter = 0; + for (int i = 0; i < 16; i++) { + if (i == RSP) { + continue; + } + + for (int j = 0; j < 16; j++) { + if (j == RSP || j == i) { + continue; + } + + for (int k = 0; k < 16; k++) { + if (k == RSP) { + continue; + } + + tester.clear(); + tester.emit_push_all_gprs(true); + // push args to the stack + tester.emit(IGen::push_gpr64(tester.get_c_abi_arg_reg(1))); + tester.emit(IGen::push_gpr64(tester.get_c_abi_arg_reg(0))); + + // pop args into appropriate register + tester.emit(IGen::pop_gpr64(i)); // i will have offset 0 + tester.emit(IGen::pop_gpr64(j)); // j will have offset 1 + + // fill k with junk + if(k != i && k != j) { + tester.emit(IGen::mov_gpr64_u64(k, (iter&1)?0:UINT64_MAX)); + } + + // load into k + tester.emit(IGen::load16s_gpr64_gpr64_plus_gpr64(k, i, j)); + + // move k to return register + tester.emit(IGen::mov_gpr64_gpr64(RAX, k)); + + // return! + tester.emit_pop_all_gprs(true); + tester.emit_return(); + + // prepare the memory: + s16 memory[8] = {0, 0, -3, -2, -1, 0, 0, 0}; + + // run! + EXPECT_EQ(s64(tester.execute((u64)memory, 6, 0, 0)), -2); + EXPECT_EQ(s64(tester.execute((u64)memory, 4, 0, 0)), -3); + EXPECT_EQ(s64(tester.execute((u64)memory, 8, 0, 0)), -1); + EXPECT_EQ(s64(tester.execute((u64)memory, 10, 0, 0)), 0); + iter++; + } + } + } +} + +TEST(Emitter, load16u_gpr64_goal_ptr_gpr64) { + CodeTester tester; + tester.init_code_buffer(512); + + tester.clear(); + tester.emit(IGen::load16u_gpr64_gpr64_plus_gpr64(RAX, RBX, RSI)); + EXPECT_EQ(tester.dump_to_hex_string(), "48 0f b7 04 1e"); + + tester.clear(); + tester.emit(IGen::load16u_gpr64_gpr64_plus_gpr64(R12, RBX, RSI)); + EXPECT_EQ(tester.dump_to_hex_string(), "4c 0f b7 24 1e"); + + tester.clear(); + tester.emit(IGen::load16u_gpr64_gpr64_plus_gpr64(R12, R15, RSI)); + EXPECT_EQ(tester.dump_to_hex_string(), "4e 0f b7 24 3e"); + + tester.clear(); + tester.emit(IGen::load16u_gpr64_gpr64_plus_gpr64(R12, R15, R14)); + EXPECT_EQ(tester.dump_to_hex_string(), "4f 0f b7 24 3e"); + + int iter = 0; + for (int i = 0; i < 16; i++) { + if (i == RSP) { + continue; + } + + for (int j = 0; j < 16; j++) { + if (j == RSP || j == i) { + continue; + } + + for (int k = 0; k < 16; k++) { + if (k == RSP) { + continue; + } + + tester.clear(); + tester.emit_push_all_gprs(true); + // push args to the stack + tester.emit(IGen::push_gpr64(tester.get_c_abi_arg_reg(1))); + tester.emit(IGen::push_gpr64(tester.get_c_abi_arg_reg(0))); + + // pop args into appropriate register + tester.emit(IGen::pop_gpr64(i)); // i will have offset 0 + tester.emit(IGen::pop_gpr64(j)); // j will have offset 1 + + // fill k with junk + if(k != i && k != j) { + tester.emit(IGen::mov_gpr64_u64(k, (iter&1)?0:UINT64_MAX)); + } + + // load into k + tester.emit(IGen::load16u_gpr64_gpr64_plus_gpr64(k, i, j)); + + // move k to return register + tester.emit(IGen::mov_gpr64_gpr64(RAX, k)); + + // return! + tester.emit_pop_all_gprs(true); + tester.emit_return(); + + // prepare the memory: + s16 memory[8] = {0, 0, -3, -2, -1, 0, 0, 0}; + + // run! + EXPECT_EQ(s64(tester.execute((u64)memory, 6, 0, 0)), 0xfffe); + EXPECT_EQ(s64(tester.execute((u64)memory, 4, 0, 0)), 0xfffd); + EXPECT_EQ(s64(tester.execute((u64)memory, 8, 0, 0)), 0xffff); + EXPECT_EQ(s64(tester.execute((u64)memory, 10, 0, 0)), 0); + iter++; + } + } + } +} + +TEST(Emitter, load32s_gpr64_goal_ptr_gpr64) { + CodeTester tester; + tester.init_code_buffer(512); + + tester.clear(); + tester.emit(IGen::load32s_gpr64_gpr64_plus_gpr64(RAX, RBX, RSI)); + EXPECT_EQ(tester.dump_to_hex_string(), "48 63 04 1e"); + + + int iter = 0; + for (int i = 0; i < 16; i++) { + if (i == RSP) { + continue; + } + + for (int j = 0; j < 16; j++) { + if (j == RSP || j == i) { + continue; + } + + for (int k = 0; k < 16; k++) { + if (k == RSP) { + continue; + } + + tester.clear(); + tester.emit_push_all_gprs(true); + // push args to the stack + tester.emit(IGen::push_gpr64(tester.get_c_abi_arg_reg(1))); + tester.emit(IGen::push_gpr64(tester.get_c_abi_arg_reg(0))); + + // pop args into appropriate register + tester.emit(IGen::pop_gpr64(i)); // i will have offset 0 + tester.emit(IGen::pop_gpr64(j)); // j will have offset 1 + + // fill k with junk + if(k != i && k != j) { + tester.emit(IGen::mov_gpr64_u64(k, (iter&1)?0:UINT64_MAX)); + } + + // load into k + tester.emit(IGen::load32s_gpr64_gpr64_plus_gpr64(k, i, j)); + + // move k to return register + tester.emit(IGen::mov_gpr64_gpr64(RAX, k)); + + // return! + tester.emit_pop_all_gprs(true); + tester.emit_return(); + + // prepare the memory: + s32 memory[8] = {0, 0, -3, -2, -1, 0, 0, 0}; + + // run! + EXPECT_EQ(s64(tester.execute((u64)memory, 12, 0, 0)), -2); + EXPECT_EQ(s64(tester.execute((u64)memory, 8, 0, 0)), -3); + EXPECT_EQ(s64(tester.execute((u64)memory, 16, 0, 0)), -1); + EXPECT_EQ(s64(tester.execute((u64)memory, 20, 0, 0)), 0); + iter++; + } + } + } +} + +TEST(Emitter, load32u_gpr64_goal_ptr_gpr64) { + CodeTester tester; + tester.init_code_buffer(512); + + tester.clear(); + tester.emit(IGen::load32u_gpr64_gpr64_plus_gpr64(RAX, RBX, RSI)); + EXPECT_EQ(tester.dump_to_hex_string(), "8b 04 1e"); + + + int iter = 0; + for (int i = 0; i < 16; i++) { + if (i == RSP) { + continue; + } + + for (int j = 0; j < 16; j++) { + if (j == RSP || j == i) { + continue; + } + + for (int k = 0; k < 16; k++) { + if (k == RSP) { + continue; + } + + tester.clear(); + tester.emit_push_all_gprs(true); + // push args to the stack + tester.emit(IGen::push_gpr64(tester.get_c_abi_arg_reg(1))); + tester.emit(IGen::push_gpr64(tester.get_c_abi_arg_reg(0))); + + // pop args into appropriate register + tester.emit(IGen::pop_gpr64(i)); // i will have offset 0 + tester.emit(IGen::pop_gpr64(j)); // j will have offset 1 + + // fill k with junk + if(k != i && k != j) { + tester.emit(IGen::mov_gpr64_u64(k, (iter&1)?0:UINT64_MAX)); + } + + // load into k + tester.emit(IGen::load32u_gpr64_gpr64_plus_gpr64(k, i, j)); + + // move k to return register + tester.emit(IGen::mov_gpr64_gpr64(RAX, k)); + + // return! + tester.emit_pop_all_gprs(true); + tester.emit_return(); + + // prepare the memory: + s32 memory[8] = {0, 0, -3, -2, -1, 0, 0, 0}; + + // run! + EXPECT_EQ(s64(tester.execute((u64)memory, 12, 0, 0)), 0xfffffffe); + EXPECT_EQ(s64(tester.execute((u64)memory, 8, 0, 0)), 0xfffffffd); + EXPECT_EQ(s64(tester.execute((u64)memory, 16, 0, 0)), 0xffffffff); + EXPECT_EQ(s64(tester.execute((u64)memory, 20, 0, 0)), 0); + iter++; + } + } + } +} + +TEST(Emitter, load64_gpr64_goal_ptr_gpr64) { + CodeTester tester; + tester.init_code_buffer(512); + + tester.clear(); + tester.emit(IGen::load64_gpr64_gpr64_plus_gpr64(RAX, RBX, RSI)); + EXPECT_EQ(tester.dump_to_hex_string(), "48 8b 04 1e"); + + + int iter = 0; + for (int i = 0; i < 16; i++) { + if (i == RSP) { + continue; + } + + for (int j = 0; j < 16; j++) { + if (j == RSP || j == i) { + continue; + } + + for (int k = 0; k < 16; k++) { + if (k == RSP) { + continue; + } + + tester.clear(); + tester.emit_push_all_gprs(true); + // push args to the stack + tester.emit(IGen::push_gpr64(tester.get_c_abi_arg_reg(1))); + tester.emit(IGen::push_gpr64(tester.get_c_abi_arg_reg(0))); + + // pop args into appropriate register + tester.emit(IGen::pop_gpr64(i)); // i will have offset 0 + tester.emit(IGen::pop_gpr64(j)); // j will have offset 1 + + // fill k with junk + if(k != i && k != j) { + tester.emit(IGen::mov_gpr64_u64(k, (iter&1)?0:UINT64_MAX)); + } + + // load into k + tester.emit(IGen::load64_gpr64_gpr64_plus_gpr64(k, i, j)); + + // move k to return register + tester.emit(IGen::mov_gpr64_gpr64(RAX, k)); + + // return! + tester.emit_pop_all_gprs(true); + tester.emit_return(); + + // prepare the memory: + s64 memory[8] = {0, 0, -3, -2, -1, 0, 0, 0}; + + // run! + EXPECT_EQ(s64(tester.execute((u64)memory, 24, 0, 0)), -2); + EXPECT_EQ(s64(tester.execute((u64)memory, 16, 0, 0)), -3); + EXPECT_EQ(s64(tester.execute((u64)memory, 32, 0, 0)), -1); + EXPECT_EQ(s64(tester.execute((u64)memory, 40, 0, 0)), 0); + iter++; + } + } + } +} diff --git a/test/test_emitter_slow.cpp b/test/test_emitter_slow.cpp new file mode 100644 index 0000000000..fc878cb578 --- /dev/null +++ b/test/test_emitter_slow.cpp @@ -0,0 +1,55 @@ +/*! + * @file test_emitter_slow.cpp + * Tests for the emitter which take over 1 second. (Checking 10,000's of functions). + * + * It may make sense to exclude these tests when developing to save time. + */ + +#include "gtest/gtest.h" +#include "goalc/emitter/CodeTester.h" +#include "goalc/emitter/IGen.h" +// +using namespace emitter; + + + +TEST(EmitterSlow, xmm32_move) { + std::vector u32_constants = {0, INT32_MAX, UINT32_MAX, 17}; + + // test moving between xmms (32-bit) and gprs. + CodeTester tester; + tester.init_code_buffer(512); + + for (auto constant : u32_constants) { + for (int r1 = 0; r1 < 16; r1++) { + if (r1 == RSP) { + continue; + } + for (int r2 = 0; r2 < 16; r2++) { + if (r2 == RSP) { + continue; + } + for (int r3 = 0; r3 < 16; r3++) { + for (int r4 = 0; r4 < 16; r4++) { + tester.clear(); + tester.emit_push_all_xmms(); + tester.emit_push_all_gprs(true); + // move constant to gpr + tester.emit(IGen::mov_gpr64_u32(r1, constant)); + // move gpr to xmm + tester.emit(IGen::movd_xmm32_gpr32(XMM0 + r3, r1)); + // move xmm to xmm + tester.emit(IGen::mov_xmm32_xmm32(XMM0 + r4, XMM0 + r3)); + // move xmm to gpr + tester.emit(IGen::movd_gpr32_xmm32(r2, XMM0 + r4)); + // return! + tester.emit(IGen::mov_gpr64_gpr64(RAX, r2)); + tester.emit_pop_all_gprs(true); + tester.emit_pop_all_xmms(); + tester.emit_return(); + } + } + } + } + } +}