diff --git a/doc/registers.md b/doc/registers.md new file mode 100644 index 0000000000..97d5280b8d --- /dev/null +++ b/doc/registers.md @@ -0,0 +1,111 @@ +## Registers +Although modern computers are much faster than the PS2, and we could probably get away with a really inefficient register allocation scheme, I think it's worth it to get this right. + + +## Register differences between MIPS and x86-64 +The PS2's MIPS processor has these categories of register: +- General Purpose. They are 128-bit, but usually only lower 64 bits are used. 32 registers, each 128-bits. +- Floating point registers. 32 registers, each for a 32-bit float. +- Vector float registers. 32 registers, each for 4x 32-bit floats. Used only in inline assembly +- `vi` registers. 16 registers, each a 16-bit integer. Used very rarely in inline assembly + +There are also some control/special registers too (`Q`, `R`...), but code using these will be manually ported. + +In comparison, x86-64 has much fewer registers: +- 16 General Purpose. Each 64-bits +- 16 `xmm` registers. 128-bits, and can store either 128-bit integers or 4x 32-bit floats + +Here is the mapping: +- MIPS GPR (lower 64 bits only) - x86-64 GPR +- MIPS GPR (128-bits, only special cases) - x64-64 `xmm` +- MIPS floating point - x64-64 `xmm` (lower 32-bits) +- MIPS vector float - x64-64 `xmm` (packed single) +- MIPS `vi` - manually handled?? + +Here is the MIPS GPR map +- `r0` or `zero` : always zero +- `r1` or `at`: assembler temporary, not saved, not used by compiler +- `r2` or `v0`: return value, not saved +- `r3` or `v1`: not saved +- `r4` or `a0`: not saved, argument 0 +- `r5` or `a1`: not saved, argument 1 +- `r6` or `a2`: not saved, argument 2 +- `r7` or `a3`: not saved, argument 3 +- `r8` or `t0`: not saved, argument 4 +- `r9` or `t1`: not saved, argument 5 +- `r10` or `t2`: not saved, argument 6 +- `r11` or `t3`: not saved, argument 7 +- `r12` or `t4`: not saved +- `r13` or `t5`: not saved +- `r14` or `t6`: not saved +- `r15` or `t7`: not saved +- `r16` or `s0`: saved +- `r17` or `s1`: saved +- `r18` or `s2`: saved +- `r19` or `s3`: saved +- `r20` or `s4`: saved +- `r21` or `s5`: saved +- `r22` or `s6`: saved, process pointer +- `r23` or `s7`: saved, symbol pointer +- `r24` or `t8`: not saved +- `r25` or `t9`: function call pointer +- `r26` or `k0`: kernel reserved (unused) +- `r27` or `k1`: kernel reserved (unused) +- `r28` or `gp`: saved +- `r29` or `sp`: stack pointer +- `r30` or `fp`: current function pointer +- `r31` or `ra`: return address pointer + + +And the x86-64 GPR map +- `rax`: return value +- `rcx`: argument 3 +- `rdx`: argument 2 +- `rbx`: saved +- `rsp`: stack pointer +- `rbp`: saved +- `rsi`: argument 1 +- `rdi`: argument 0 +- `r8`: argument 4 +- `r9`: argument 5 +- `r10`: argument 6, saved if not argument +- `r11`: argument 7, saved if not argument +- `r12`: saved +- `r13`: process pointer +- `r14`: symbol table +- `r15`: offset pointer + + +### Plan for Memory Access +The PS2 uses 32-bit pointers, and changing the pointer size is likely to introduce bugs, so we will keep using 32-bit pointers. Also, GOAL has some hardcoded checks on the value for pointers, so we need to make sure the memory appears to the program at the correct address. + +To do this, we have separate "GOAL Pointers" and "real pointers". The "real pointers" are just normal x86-64 pointers, and the "GOAL Pointer" is an offset into a main memory array. A "real pointer" to the main memory array is stored in `r15` (offset pointer) when GOAL code is executing, and the GOAL compiler will automatically add this to all memory accesses. + +The overhead from doing this is not as bad as you might expect - x86 has nice addressing modes (Scale Index Base) which are quite fast, and don't require the use of temporary registers. If this does turn out to be much slower than I expect, we can introduce the concept of real pointers in GOAL code, and use them in places where we are limited in accessing memory. + +The main RAM is mapped at `0x0` on the PS2, with the first 1 MB reserved for the kernel. We should make sure that the first 1 MB of GOAL main memory will cause a segfault if read/written/executed, to catch null pointer bugs. + +In the C Kernel code, the `r15` pointer doesn't exist. Instead, `g_ee_main_memory` is a global which points to the beginning of GOAL main memory. The `Ptr` template class takes care of converting GOAL and C++ pointers in a convenient way, and catches null pointer access. + +The GOAL stack pointer should likely be a real pointer, for performance reasons. This makes pushing/popping/calling/returning/accessing stack variables much faster, with the only cost being getting a GOAL stack pointer requiring some extra work. The stack pointer's value is read/written extremely rarely, so this seems like a good tradeoff. + +The other registers are less clear. The process pointer can probably be a real pointer. But the symbol table could go a few ways: +1. Make it a real pointer. Symbol value access is fast, but comparison against false requires two extra operations. +2. Make it a GOAL pointer. Symbol value access requires more complicated addressing modes, but comparison against false is fast. + +Right now I'm leaning toward 1, but making it a configurable option in case I'm wrong. It should only be a change in a few places (emitter + where it's set up in the runtime). + +### Plan for Function Call and Arguments +In GOAL for MIPS, function calls are weird. Functions are always called by register using `t9`. There seems to be a different register allocator for function pointers, as nested function calls have really wacky register allocation. In GOAL-x86-64, this restriction will be removed, and a function can be called from any register. (see next section for why we can do this) + +Unfortunately, GOAL's 128-bit function arguments present a big challenge. When calling a function, we can't know if the function we're calling is expecting an integer, float, or 128-bit integer. In fact, the caller may not even know if it has an integer, float, or 128-bit integer. The easy and foolproof way to get this right is to use 128-bit `xmm` registers for all arguments and return values, but this will cause a massive performance hit and increase code size, as we'll have to move values between register types constantly. The current plan is this: + +- Floats go in GPRs for arguments/return values. GOAL does this too, and takes the hit of converting between registers as well. Probably the impact on a modern CPU is even worse, but we can live with it. +- We'll compromise + + +### Plan for Static Data + +### Plan for Memory + +### Other details diff --git a/goalc/emitter/CMakeLists.txt b/goalc/emitter/CMakeLists.txt index fde45f4cb7..1d6cc0dc8f 100644 --- a/goalc/emitter/CMakeLists.txt +++ b/goalc/emitter/CMakeLists.txt @@ -1,3 +1,3 @@ add_library(emitter - CodeTester.cpp - registers.cpp) \ No newline at end of file + Register.cpp + CodeTester.cpp) \ No newline at end of file diff --git a/goalc/emitter/CodeTester.cpp b/goalc/emitter/CodeTester.cpp index f722cadad8..3d5c4b69bf 100644 --- a/goalc/emitter/CodeTester.cpp +++ b/goalc/emitter/CodeTester.cpp @@ -1,80 +1 @@ -#include -#include #include "CodeTester.h" -#include "Instruction.h" -#include "IGen.h" - -namespace goal { - -std::string CodeTester::dump_to_hex_string() { - std::string result; - char buff[32]; - for (int i = 0; i < code_buffer_size; i++) { - sprintf(buff, "%02x ", code_buffer[i]); - result += buff; - } - - // remove trailing space - if (!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(X86R gpr) { - assert(is_gpr(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::clear() { - code_buffer_size = 0; -} - -u64 CodeTester::execute() { - return ((u64(*)())code_buffer)(); -} - -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); - } -} - -} // namespace goal \ No newline at end of file diff --git a/goalc/emitter/CodeTester.h b/goalc/emitter/CodeTester.h index 4bd7756c06..ae961f49f4 100644 --- a/goalc/emitter/CodeTester.h +++ b/goalc/emitter/CodeTester.h @@ -1,36 +1,6 @@ -/*! - * @file CodeTester - * CodeTester is a utility which allows small segments of x86 code to be run, for the purpose of - * testing the compiler's code emitter. It is not suitable for testing compiled GOAL code. - */ +#ifndef JAK_CODETESTER_H +#define JAK_CODETESTER_H -#ifndef JAK1_CODETESTER_H -#define JAK1_CODETESTER_H +class CodeTester {}; -#include -#include "common/common_types.h" -#include "registers.h" -#include "Instruction.h" - -namespace goal { -class CodeTester { - public: - std::string dump_to_hex_string(); - 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_return(); - void emit_set_gpr_as_return(X86R gpr); - void emit(const Instruction& instr); - u64 execute(); - void clear(); - ~CodeTester(); - - private: - int code_buffer_size = 0; - int code_buffer_capacity = 0; - u8* code_buffer = nullptr; -}; -} // namespace goal - -#endif // JAK1_CODETESTER_H +#endif // JAK_CODETESTER_H diff --git a/goalc/emitter/Register.cpp b/goalc/emitter/Register.cpp new file mode 100644 index 0000000000..e83d953d9c --- /dev/null +++ b/goalc/emitter/Register.cpp @@ -0,0 +1 @@ +#include "Register.h" \ No newline at end of file diff --git a/goalc/emitter/Register.h b/goalc/emitter/Register.h new file mode 100644 index 0000000000..4760d01c98 --- /dev/null +++ b/goalc/emitter/Register.h @@ -0,0 +1,122 @@ +/*! + * @file Register.h + * Representation of an x86-64 Register. + */ + +#ifndef JAK_REGISTER_H +#define JAK_REGISTER_H + +#include +#include +#include +#include "common/common_types.h" + +namespace emitter { + +// registers by name +enum X86_REG : u8 { + RAX, // return, temp + RCX, // arg 3 + RDX, // arg 2 + RBX, // X saved + + RSP, // stack pointer + RBP, // X base pointer (like fp) + RSI, // arg 1 + RDI, // arg 0 + + R8, // arg 4 + 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) + R14, // X saved - offset (added in GOAL x86) + R15, // X saved - st (like s7) + XMM0, + XMM1, + XMM2, + XMM3, + XMM4, + XMM5, + XMM6, + XMM7, + XMM8, + XMM9, + XMM10, + XMM11, + XMM12, + XMM13, + XMM14, + XMM15 +}; + +constexpr int N_REGS = 32; +static_assert(N_REGS - 1 == XMM15, "bad register count"); + +class Register { + public: + Register() = default; + + // 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_gpr() { return m_id >= RAX && m_id <= R15; } + + int hw_id() { + if (is_xmm()) { + return m_id - XMM0; + } else if (is_gpr()) { + return m_id - RAX; + } else { + assert(false); + } + return 0xff; + } + + 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; + } + + private: + u8 m_id = 0xff; +}; + + + +class RegisterInfo { + 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; + }; + + const Info& get_info(Register r); + + int get_arg_reg_count(); + Register get_arg_reg(int id); + + int get_saved_reg_count(); + Register get_saved_reg(int id); + + Register get_process_reg(); + Register get_st_reg(); + Register get_offset_reg(); + Register get_ret_reg(); +}; + + +} // namespace emitter + +#endif // JAK_REGISTER_H diff --git a/goalc/old_emitter/CodeTester.cpp b/goalc/old_emitter/CodeTester.cpp new file mode 100644 index 0000000000..f722cadad8 --- /dev/null +++ b/goalc/old_emitter/CodeTester.cpp @@ -0,0 +1,80 @@ +#include +#include +#include "CodeTester.h" +#include "Instruction.h" +#include "IGen.h" + +namespace goal { + +std::string CodeTester::dump_to_hex_string() { + std::string result; + char buff[32]; + for (int i = 0; i < code_buffer_size; i++) { + sprintf(buff, "%02x ", code_buffer[i]); + result += buff; + } + + // remove trailing space + if (!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(X86R gpr) { + assert(is_gpr(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::clear() { + code_buffer_size = 0; +} + +u64 CodeTester::execute() { + return ((u64(*)())code_buffer)(); +} + +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); + } +} + +} // namespace goal \ No newline at end of file diff --git a/goalc/old_emitter/CodeTester.h b/goalc/old_emitter/CodeTester.h new file mode 100644 index 0000000000..4bd7756c06 --- /dev/null +++ b/goalc/old_emitter/CodeTester.h @@ -0,0 +1,36 @@ +/*! + * @file CodeTester + * CodeTester is a utility which allows small segments of x86 code to be run, for the purpose of + * testing the compiler's code emitter. It is not suitable for testing compiled GOAL code. + */ + +#ifndef JAK1_CODETESTER_H +#define JAK1_CODETESTER_H + +#include +#include "common/common_types.h" +#include "registers.h" +#include "Instruction.h" + +namespace goal { +class CodeTester { + public: + std::string dump_to_hex_string(); + 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_return(); + void emit_set_gpr_as_return(X86R gpr); + void emit(const Instruction& instr); + u64 execute(); + void clear(); + ~CodeTester(); + + private: + int code_buffer_size = 0; + int code_buffer_capacity = 0; + u8* code_buffer = nullptr; +}; +} // namespace goal + +#endif // JAK1_CODETESTER_H diff --git a/goalc/emitter/IGen.h b/goalc/old_emitter/IGen.h similarity index 100% rename from goalc/emitter/IGen.h rename to goalc/old_emitter/IGen.h diff --git a/goalc/emitter/Instruction.h b/goalc/old_emitter/Instruction.h similarity index 100% rename from goalc/emitter/Instruction.h rename to goalc/old_emitter/Instruction.h diff --git a/goalc/emitter/registers.cpp b/goalc/old_emitter/registers.cpp similarity index 100% rename from goalc/emitter/registers.cpp rename to goalc/old_emitter/registers.cpp diff --git a/goalc/emitter/registers.h b/goalc/old_emitter/registers.h similarity index 100% rename from goalc/emitter/registers.h rename to goalc/old_emitter/registers.h diff --git a/test/test_CodeTester.cpp b/test/test_CodeTester.cpp index ede0aa79f4..9f3d2e36b8 100644 --- a/test/test_CodeTester.cpp +++ b/test/test_CodeTester.cpp @@ -6,162 +6,162 @@ #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 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