start docs

This commit is contained in:
water
2020-08-28 20:16:30 -04:00
parent 90060dbc21
commit 2ac07ae6bd
13 changed files with 515 additions and 274 deletions
+111
View File
@@ -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<T>` 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
+2 -2
View File
@@ -1,3 +1,3 @@
add_library(emitter
CodeTester.cpp
registers.cpp)
Register.cpp
CodeTester.cpp)
-79
View File
@@ -1,80 +1 @@
#include <sys/mman.h>
#include <cstdio>
#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
+4 -34
View File
@@ -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 <string>
#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
+1
View File
@@ -0,0 +1 @@
#include "Register.h"
+122
View File
@@ -0,0 +1,122 @@
/*!
* @file Register.h
* Representation of an x86-64 Register.
*/
#ifndef JAK_REGISTER_H
#define JAK_REGISTER_H
#include <cassert>
#include <functional>
#include <array>
#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<u8>()(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
+80
View File
@@ -0,0 +1,80 @@
#include <sys/mman.h>
#include <cstdio>
#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
+36
View File
@@ -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 <string>
#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
+159 -159
View File
@@ -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> 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> 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> 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> 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();
}
}
}
}
}
}
//#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> 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> 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> 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> 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();
// }
// }
// }
// }
// }
//}