mirror of
https://github.com/open-goal/jak-project
synced 2026-07-09 06:53:45 -04:00
start docs
This commit is contained in:
@@ -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
|
||||
@@ -1,3 +1,3 @@
|
||||
add_library(emitter
|
||||
CodeTester.cpp
|
||||
registers.cpp)
|
||||
Register.cpp
|
||||
CodeTester.cpp)
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
#include "Register.h"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
Reference in New Issue
Block a user