more progress on emitter integers

This commit is contained in:
water 2020-09-02 16:24:13 -04:00
parent bea7167fbb
commit 4723153a4b
8 changed files with 796 additions and 196 deletions

View File

@ -30,11 +30,37 @@ class CodeTester {
return result_T;
}
template <typename T>
int emit_data(T x) {
auto ret = code_buffer_size;
assert(int(sizeof(T)) + code_buffer_size <= code_buffer_capacity);
memcpy(code_buffer + code_buffer_size, &x, sizeof(T));
code_buffer_size += sizeof(T);
return ret;
}
void clear();
~CodeTester();
/*!
* Should allow emitter tests which run code to do the right thing on windows.
* Assumes RAX is return and RSP is stack pointer.
*/
Register get_c_abi_arg_reg(int i) {
// todo - this should be different for windows.
#ifdef _WIN32
switch (i) {
case 0:
return RCX;
case 1:
return RDX;
case 2:
return R8;
case 3:
return R9;
default:
assert(false);
}
#else
switch (i) {
case 0:
return RDI;
@ -47,10 +73,31 @@ class CodeTester {
default:
assert(false);
}
#endif
}
std::string reg_name(Register x) { return m_info.get_info(x).name; }
int size() const { return code_buffer_size; }
template <typename T>
void write(T x, int at) {
assert(at >= 0);
assert(int(sizeof(T)) + at <= code_buffer_capacity);
memcpy(code_buffer + at, &x, sizeof(T));
}
template <typename T>
T read(int at) {
assert(at >= 0);
assert(int(sizeof(T)) + at <= code_buffer_capacity);
T result;
memcpy(&result, code_buffer + at, sizeof(T));
return result;
}
const u8* data() const { return code_buffer; }
private:
int code_buffer_size = 0;
int code_buffer_capacity = 0;

View File

@ -1147,9 +1147,56 @@ class IGen {
return instr;
}
// todo - lea rip
// todo - static load and store floating points? (this is probably big for loading float
// constants)
static Instruction static_store(Register value, s64 offset, int size) {
switch (size) {
case 1:
return store8_rip_s32(value, offset);
case 2:
return store16_rip_s32(value, offset);
case 4:
return store32_rip_s32(value, offset);
case 8:
return store64_rip_s32(value, offset);
default:
assert(false);
}
}
static Instruction static_addr(Register dst, s64 offset) {
assert(dst.is_gpr());
assert(offset >= INT32_MIN && offset <= INT32_MAX);
Instruction instr(0x8d);
instr.set_modrm_and_rex_for_rip_plus_s32(dst.hw_id(), offset, true);
return instr;
}
static Instruction static_load_xmm32(Register xmm_dest, s64 offset) {
assert(xmm_dest.is_xmm());
assert(offset >= INT32_MIN && offset <= INT32_MAX);
Instruction instr(0xf3);
instr.set_op2(0x0f);
instr.set_op3(0x10);
instr.set_modrm_and_rex_for_rip_plus_s32(xmm_dest.hw_id(), offset, false);
instr.swap_op0_rex();
return instr;
}
static Instruction static_store_xmm32(Register xmm_value, s64 offset) {
assert(xmm_value.is_xmm());
assert(offset >= INT32_MIN && offset <= INT32_MAX);
Instruction instr(0xf3);
instr.set_op2(0x0f);
instr.set_op3(0x11);
instr.set_modrm_and_rex_for_rip_plus_s32(xmm_value.hw_id(), offset, false);
instr.swap_op0_rex();
return instr;
}
// TODO, special load/stores of 128 bit values.
// TODO, consider specialized stack loads and stores?
@ -1186,10 +1233,45 @@ class IGen {
return Instruction(0x58 + reg.hw_id());
}
/*!
* Call a function stored in a 64-bit gpr
*/
static Instruction call_r64(uint8_t reg) {
Instruction instr(0xff);
if (reg >= 8) {
instr.set(REX(false, false, false, true));
reg -= 8;
}
assert(reg < 8);
ModRM mrm;
mrm.rm = reg;
mrm.reg_op = 2;
mrm.mod = 3;
instr.set(mrm);
return instr;
}
/*!
* Call a function stored in a 64-bit gpr
*/
static Instruction jmp_r64(uint8_t reg) {
Instruction instr(0xff);
if (reg >= 8) {
instr.set(REX(false, false, false, true));
reg -= 8;
}
assert(reg < 8);
ModRM mrm;
mrm.rm = reg;
mrm.reg_op = 4;
mrm.mod = 3;
instr.set(mrm);
return instr;
}
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
// INTEGER MATH
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
// todo - add test
static Instruction sub_gpr64_imm8s(Register reg, int64_t imm) {
assert(reg.is_gpr());
assert(imm >= INT8_MIN && imm <= INT8_MAX);
@ -1200,33 +1282,170 @@ class IGen {
return instr;
}
// sub imm32, imm64, reg
static Instruction sub_gpr64_imm32s(Register reg, int64_t imm) {
assert(reg.is_gpr());
assert(imm >= INT32_MIN && imm <= INT32_MAX);
Instruction instr(0x81);
instr.set_modrm_and_rex(5, reg.hw_id(), 3, true);
instr.set(Imm(4, imm));
return instr;
}
// todo - add test
static Instruction add_gpr64_imm8s(Register reg, int8_t v) {
static Instruction add_gpr64_imm8s(Register reg, int64_t v) {
assert(v >= INT8_MIN && v <= INT8_MAX);
Instruction instr(0x83);
instr.set_modrm_and_rex(0, reg.hw_id(), 3, true);
instr.set(Imm(1, v));
return instr;
}
// add imm32, imm64, reg
static Instruction add_gpr64_imm32s(Register reg, int64_t v) {
assert(v >= INT32_MIN && v <= INT32_MAX);
Instruction instr(0x81);
instr.set_modrm_and_rex(0, reg.hw_id(), 3, true);
instr.set(Imm(4, v));
return instr;
}
// mul imm8, imm32, imm64, reg
static Instruction add_gpr64_imm(Register reg, int64_t imm) {
if (imm >= INT8_MIN && imm <= INT8_MAX) {
return add_gpr64_imm8s(reg, imm);
} else if (imm >= INT32_MIN && imm <= INT32_MAX) {
return add_gpr64_imm32s(reg, imm);
} else {
assert(false);
}
}
// idiv, cdq, movsx for div
static Instruction sub_gpr64_imm(Register reg, int64_t imm) {
if (imm >= INT8_MIN && imm <= INT8_MAX) {
return sub_gpr64_imm8s(reg, imm);
} else if (imm >= INT32_MIN && imm <= INT32_MAX) {
return sub_gpr64_imm32s(reg, imm);
} else {
assert(false);
}
}
// cmp imm8, imm32, imm64, reg
static Instruction add_gpr64_gpr64(Register dst, Register src) {
Instruction instr(0x01);
assert(dst.is_gpr());
assert(src.is_gpr());
instr.set_modrm_and_rex(src.hw_id(), dst.hw_id(), 3, true);
return instr;
}
static Instruction sub_gpr64_gpr64(Register dst, Register src) {
Instruction instr(0x29);
assert(dst.is_gpr());
assert(src.is_gpr());
instr.set_modrm_and_rex(src.hw_id(), dst.hw_id(), 3, true);
return instr;
}
/*!
* Multiply gprs (32-bit, signed).
* (Note - probably worth doing imul on gpr64's to implement the EE's unsigned multiply)
*/
static Instruction imul_gpr32_gpr32(Register dst, Register src) {
Instruction instr(0xf);
instr.set_op2(0xaf);
assert(dst.is_gpr());
assert(src.is_gpr());
instr.set_modrm_and_rex(dst.hw_id(), src.hw_id(), 3, false);
return instr;
}
/*!
* Divide (idiv, 32 bit)
* todo UNTESTED
*/
static Instruction idiv_gpr32(Register reg) {
Instruction instr(0xf7);
assert(reg.is_gpr());
instr.set_modrm_and_rex(7, reg.hw_id(), 3, false);
return instr;
}
/*!
* Convert doubleword to quadword for division.
* todo UNTESTED
*/
static Instruction cdq() {
Instruction instr(0x99);
return instr;
}
/*!
* Move from gpr32 to gpr64, with sign extension.
* Needed for multiplication/divsion madness.
*/
static Instruction movsx_r64_r32(Register dst, Register src) {
Instruction instr(0x63);
assert(dst.is_gpr());
assert(src.is_gpr());
instr.set_modrm_and_rex(dst.hw_id(), src.hw_id(), 3, true);
return instr;
}
/*!
* Compare gpr64. This sets the flags for the jumps.
* todo UNTESTED
*/
static Instruction cmp_gpr64_gpr64(Register a, Register b) {
Instruction instr(0x3b);
assert(a.is_gpr());
assert(b.is_gpr());
instr.set_modrm_and_rex(a.hw_id(), b.hw_id(), 3, true);
return instr;
}
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
// BIT STUFF
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
// and imm8, imm32, imm64, reg
// or imm8, imm32, imm64, reg
// xor imm8, imm32, imm64, reg
// "fancy register zero xor"
// not
/*!
* Or of two gprs
*/
static Instruction or_gpr64_gpr64(Register dst, Register src) {
Instruction instr(0x0b);
assert(dst.is_gpr());
assert(src.is_gpr());
instr.set_modrm_and_rex(dst.hw_id(), src.hw_id(), 3, true);
return instr;
}
/*!
* And of two gprs
*/
static Instruction and_gpr64_gpr64(Register dst, Register src) {
Instruction instr(0x23);
assert(dst.is_gpr());
assert(src.is_gpr());
instr.set_modrm_and_rex(dst.hw_id(), src.hw_id(), 3, true);
return instr;
}
/*!
* Xor of two gprs
*/
static Instruction xor_gpr64_gpr64(Register dst, Register src) {
Instruction instr(0x33);
assert(dst.is_gpr());
assert(src.is_gpr());
instr.set_modrm_and_rex(dst.hw_id(), src.hw_id(), 3, true);
return instr;
}
/*!
* Bitwise not a gpr
*/
static Instruction not_gpr64(Register reg) {
Instruction instr(0xf7);
assert(reg.is_gpr());
instr.set_modrm_and_rex(2, reg.hw_id(), 3, true);
return instr;
}
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
// SHIFTS

View File

@ -546,6 +546,46 @@ struct Instruction {
}
return count;
}
uint8_t length() const {
if (is_null)
return 0;
uint8_t count = 0;
if (set_rex) {
count++;
}
count++;
if (op2_set) {
count++;
}
if (op3_set) {
count++;
}
if (set_modrm) {
count++;
}
if (set_sib) {
count++;
}
if (set_disp_imm) {
for (int i = 0; i < disp.size; i++) {
count++;
}
}
if (set_imm) {
for (int i = 0; i < imm.size; i++) {
count++;
}
}
return count;
}
};
} // namespace emitter

View File

@ -8,185 +8,6 @@
namespace goal {
class IGen {
public:
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
// FUNCTION STUFF
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
// /*!
// * Call a function stored in a 64-bit gpr
// */
// static Instruction call_r64(uint8_t reg) {
// Instruction instr(0xff);
// if (reg >= 8) {
// instr.set(REX(false, false, false, true));
// reg -= 8;
// }
// assert(reg < 8);
// ModRM mrm;
// mrm.rm = reg;
// mrm.reg_op = 2;
// mrm.mod = 3;
// instr.set(mrm);
// return instr;
// }
//
// /*!
// * Call a function stored in a 64-bit gpr
// */
// static Instruction jmp_r64(uint8_t reg) {
// Instruction instr(0xff);
// if (reg >= 8) {
// instr.set(REX(false, false, false, true));
// reg -= 8;
// }
// assert(reg < 8);
// ModRM mrm;
// mrm.rm = reg;
// mrm.reg_op = 4;
// mrm.mod = 3;
// instr.set(mrm);
// return instr;
// }
//
// //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
// // INTEGER MATH
// //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
//
// /*!
// * Add 64-bit registers.
// */
// static Instruction add_gpr64_gpr64(uint8_t dst, uint8_t src) {
// Instruction instr(0x01);
// instr.set_modrm_and_rex(src, dst, 3, true);
// return instr;
// }
//
// /*!
// * Add a signed 32 bit immediate to a 64 bit register
// * TODO: determine if we can decrease to imm16?
// */
// static Instruction add_gpr64_imm32s(uint8_t dst, int32_t offset) {
// Instruction instr(0x81);
// instr.set_modrm_and_rex(0, dst, 3, true);
// instr.set(Imm(4, offset));
// return instr;
// }
//
// /*!
// * Add a signed 32 bit immediate to a 64 bit register
// * TODO: determine if we can decrease to imm16?
// */
// static Instruction add_gpr64_imm8s(uint8_t dst, int8_t v) {
// Instruction instr(0x83);
// instr.set_modrm_and_rex(0, dst, 3, true);
// instr.set(Imm(1, v));
// return instr;
// }
//
// /*!
// * Subtract 64-bit registers
// */
// static Instruction sub_gpr64_gpr64(uint8_t dst, uint8_t src) {
// Instruction instr(0x29);
// instr.set_modrm_and_rex(src, dst, 3, true);
// return instr;
// }
//
// /*!
// * Multiply gprs (32-bit, signed).
// */
// static Instruction imul_gpr32_gpr32(uint8_t dst, uint8_t src) {
// Instruction instr(0xf);
// instr.set_op2(0xaf);
// instr.set_modrm_and_rex(dst, src, 3, false);
// return instr;
// }
//
// /*!
// * Divide (idiv, 32 bit)
// */
// static Instruction idiv_gpr32(uint8_t reg) {
// Instruction instr(0xf7);
// instr.set_modrm_and_rex(7, reg, 3, false);
// return instr;
// }
//
// /*!
// * Convert doubleword to quadword for division.
// * Blame Intel for this disaster.
// */
// static Instruction cdq() {
// Instruction instr(0x99);
// return instr;
// }
//
// /*!
// * Move from gpr32 to gpr64, with sign extension.
// * Needed for division madness.
// */
// static Instruction movsx_r64_r32(uint8_t dst, uint8_t src) {
// Instruction instr(0x63);
// instr.set_modrm_and_rex(dst, src, 3, true);
// return instr;
// }
//
// /*!
// * Compare gpr64. This sets the flags for the jumps.
// */
// static Instruction cmp_gpr64_gpr64(uint8_t a, uint8_t b) {
// Instruction instr(0x3b);
// instr.set_modrm_and_rex(a, b, 3, true);
// return instr;
// }
//
// //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
// // BIT STUFF
// //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
//
// /*!
// * Or of two gprs
// */
// static Instruction or_gpr64_gpr64(uint8_t dst, uint8_t src) {
// Instruction instr(0x0b);
// instr.set_modrm_and_rex(dst, src, 3, true);
// return instr;
// }
//
// /*!
// * And of two gprs
// */
// static Instruction and_gpr64_gpr64(uint8_t dst, uint8_t src) {
// Instruction instr(0x23);
// instr.set_modrm_and_rex(dst, src, 3, true);
// return instr;
// }
//
// /*!
// * Xor of two gprs
// */
// static Instruction xor_gpr64_gpr64(uint8_t dst, uint8_t src) {
// Instruction instr(0x33);
// instr.set_modrm_and_rex(dst, src, 3, true);
// return instr;
// }
//
// /*!
// * This is the way "real" compilers zero registers, so we should do it too.
// */
// static Instruction xor_zero_gpr(uint8_t reg) {
// Instruction instr(0x31);
// instr.set_modrm_and_rex(reg, reg, 3, false);
// return instr;
// }
//
// /*!
// * Bitwise not a gpr
// */
// static Instruction not_gpr64(uint8_t reg) {
// Instruction instr(0xf7);
// instr.set_modrm_and_rex(2, reg, 3, true);
// return instr;
// }
//
// /*!
// * Shift 64-bit gpr left by CL register

View File

@ -11,6 +11,7 @@ add_executable(goalc-test
test_emitter_slow.cpp
test_emitter_loads_and_store.cpp
test_emitter_xmm32.cpp
test_emitter_integer_math.cpp
)
target_link_libraries(goalc-test goos util listener runtime emitter type_system gtest)

View File

@ -0,0 +1,393 @@
#include "third-party/fmt/core.h"
#include "gtest/gtest.h"
#include "goalc/emitter/CodeTester.h"
#include "goalc/emitter/IGen.h"
using namespace emitter;
TEST(EmitterIntegerMath, add_gpr64_imm8s) {
CodeTester tester;
tester.init_code_buffer(256);
std::vector<s64> vals = {0, 1, -1, INT32_MIN, INT32_MAX, INT64_MIN, INT64_MAX};
std::vector<s64> imms = {0, 1, -1, INT8_MIN, INT8_MAX};
// test the ones that aren't rsp
for (int i = 0; i < 16; i++) {
if (i == RSP) {
continue;
}
for (auto val : vals) {
for (auto imm : imms) {
auto expected = val + imm;
tester.clear();
tester.emit_push_all_gprs(true);
// move initial value to register
tester.emit(IGen::mov_gpr64_gpr64(i, tester.get_c_abi_arg_reg(0)));
// do the add
tester.emit(IGen::add_gpr64_imm8s(i, imm));
// move for return
tester.emit(IGen::mov_gpr64_gpr64(RAX, i));
tester.emit_pop_all_gprs(true);
tester.emit_return();
auto result = tester.execute_ret<s64>(val, 0, 0, 0);
EXPECT_EQ(result, expected);
}
}
}
tester.clear();
tester.emit(IGen::add_gpr64_imm8s(RSP, 12));
EXPECT_EQ(tester.dump_to_hex_string(), "48 83 c4 0c");
}
TEST(EmitterIntegerMath, add_gpr64_imm32s) {
CodeTester tester;
tester.init_code_buffer(256);
std::vector<s64> vals = {0, 1, -1, INT32_MIN, INT32_MAX, INT64_MIN, INT64_MAX};
std::vector<s64> imms = {0, 1, -1, INT8_MIN, INT8_MAX, INT32_MIN, INT32_MAX};
// test the ones that aren't rsp
for (int i = 0; i < 16; i++) {
if (i == RSP) {
continue;
}
for (auto val : vals) {
for (auto imm : imms) {
auto expected = val + imm;
tester.clear();
tester.emit_push_all_gprs(true);
// move initial value to register
tester.emit(IGen::mov_gpr64_gpr64(i, tester.get_c_abi_arg_reg(0)));
// do the add
tester.emit(IGen::add_gpr64_imm32s(i, imm));
// move for return
tester.emit(IGen::mov_gpr64_gpr64(RAX, i));
tester.emit_pop_all_gprs(true);
tester.emit_return();
auto result = tester.execute_ret<s64>(val, 0, 0, 0);
EXPECT_EQ(result, expected);
}
}
}
tester.clear();
tester.emit(IGen::add_gpr64_imm32s(RSP, 12));
EXPECT_EQ(tester.dump_to_hex_string(), "48 81 c4 0c 00 00 00");
}
TEST(EmitterIntegerMath, sub_gpr64_imm8s) {
CodeTester tester;
tester.init_code_buffer(256);
std::vector<s64> vals = {0, 1, -1, INT32_MIN, INT32_MAX, INT64_MIN, INT64_MAX};
std::vector<s64> imms = {0, 1, -1, INT8_MIN, INT8_MAX};
// test the ones that aren't rsp
for (int i = 0; i < 16; i++) {
if (i == RSP) {
continue;
}
for (auto val : vals) {
for (auto imm : imms) {
auto expected = val - imm;
tester.clear();
tester.emit_push_all_gprs(true);
// move initial value to register
tester.emit(IGen::mov_gpr64_gpr64(i, tester.get_c_abi_arg_reg(0)));
// do the add
tester.emit(IGen::sub_gpr64_imm8s(i, imm));
// move for return
tester.emit(IGen::mov_gpr64_gpr64(RAX, i));
tester.emit_pop_all_gprs(true);
tester.emit_return();
auto result = tester.execute_ret<s64>(val, 0, 0, 0);
EXPECT_EQ(result, expected);
}
}
}
tester.clear();
tester.emit(IGen::sub_gpr64_imm8s(RSP, 12));
EXPECT_EQ(tester.dump_to_hex_string(), "48 83 ec 0c");
}
TEST(EmitterIntegerMath, sub_gpr64_imm32s) {
CodeTester tester;
tester.init_code_buffer(256);
std::vector<s64> vals = {0, 1, -1, INT32_MIN, INT32_MAX, INT64_MIN, INT64_MAX};
std::vector<s64> imms = {0, 1, -1, INT8_MIN, INT8_MAX, INT32_MIN, INT32_MAX};
// test the ones that aren't rsp
for (int i = 0; i < 16; i++) {
if (i == RSP) {
continue;
}
for (auto val : vals) {
for (auto imm : imms) {
auto expected = val - imm;
tester.clear();
tester.emit_push_all_gprs(true);
// move initial value to register
tester.emit(IGen::mov_gpr64_gpr64(i, tester.get_c_abi_arg_reg(0)));
// do the add
tester.emit(IGen::sub_gpr64_imm32s(i, imm));
// move for return
tester.emit(IGen::mov_gpr64_gpr64(RAX, i));
tester.emit_pop_all_gprs(true);
tester.emit_return();
auto result = tester.execute_ret<s64>(val, 0, 0, 0);
EXPECT_EQ(result, expected);
}
}
}
tester.clear();
tester.emit(IGen::sub_gpr64_imm32s(RSP, 12));
EXPECT_EQ(tester.dump_to_hex_string(), "48 81 ec 0c 00 00 00");
}
TEST(EmitterIntegerMath, add_gpr64_gpr64) {
CodeTester tester;
tester.init_code_buffer(256);
std::vector<s64> vals = {0, 1, -2, INT32_MIN, INT32_MAX, INT64_MIN,
INT64_MAX, 117, 32, -348473, 83747382};
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 (auto v1 : vals) {
for (auto v2 : vals) {
auto expected = v1 + v2;
tester.clear();
tester.emit_push_all_gprs(true);
tester.emit(IGen::mov_gpr64_u64(i, v1));
tester.emit(IGen::mov_gpr64_u64(j, v2));
tester.emit(IGen::add_gpr64_gpr64(i, j));
tester.emit(IGen::mov_gpr64_gpr64(RAX, i));
tester.emit_pop_all_gprs(true);
tester.emit_return();
auto result = tester.execute_ret<s64>(0, 0, 0, 0);
EXPECT_EQ(result, expected);
}
}
}
}
}
TEST(EmitterIntegerMath, sub_gpr64_gpr64) {
CodeTester tester;
tester.init_code_buffer(256);
std::vector<s64> vals = {0, 1, -2, INT32_MIN, INT32_MAX, INT64_MIN,
INT64_MAX, 117, 32, -348473, 83747382};
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 (auto v1 : vals) {
for (auto v2 : vals) {
auto expected = v1 - v2;
tester.clear();
tester.emit_push_all_gprs(true);
tester.emit(IGen::mov_gpr64_u64(i, v1));
tester.emit(IGen::mov_gpr64_u64(j, v2));
tester.emit(IGen::sub_gpr64_gpr64(i, j));
tester.emit(IGen::mov_gpr64_gpr64(RAX, i));
tester.emit_pop_all_gprs(true);
tester.emit_return();
auto result = tester.execute_ret<s64>(0, 0, 0, 0);
EXPECT_EQ(result, expected);
}
}
}
}
}
TEST(EmitterIntegerMath, mul_gpr32_gpr32) {
CodeTester tester;
tester.init_code_buffer(256);
std::vector<s32> vals = {
0, 1, -2, -20, 123123, INT32_MIN, INT32_MAX, INT32_MIN + 1, INT32_MAX - 1};
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 (auto v1 : vals) {
for (auto v2 : vals) {
// this is kind of weird behavior, but it's what the PS2 CPU does, I think.
// the lower 32-bits of the result are sign extended, even if this sign doesn't match
// the sign of the real product. This is true for both signed and unsigned multiply.
auto expected = ((s64(v1) * s64(v2)) << 32) >> 32;
tester.clear();
tester.emit_push_all_gprs(true);
tester.emit(IGen::mov_gpr64_u64(i, (s64)v1));
tester.emit(IGen::mov_gpr64_u64(j, (s64)v2));
tester.emit(IGen::imul_gpr32_gpr32(i, j));
tester.emit(IGen::movsx_r64_r32(RAX, i)); // weird PS2 sign extend.
tester.emit_pop_all_gprs(true);
tester.emit_return();
auto result = tester.execute_ret<s64>(0, 0, 0, 0);
EXPECT_EQ(result, expected);
if (result != expected) {
fmt::print("fail {} x {}: {}\n", v1, v2, tester.dump_to_hex_string());
}
}
}
}
}
}
TEST(EmitterIntegerMath, or_gpr64_gpr64) {
CodeTester tester;
tester.init_code_buffer(256);
std::vector<s64> vals = {0, 1, -2, INT32_MIN, INT32_MAX, INT64_MIN,
INT64_MAX, 117, 32, -348473, 83747382};
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 (auto v1 : vals) {
for (auto v2 : vals) {
auto expected = v1 | v2;
tester.clear();
tester.emit_push_all_gprs(true);
tester.emit(IGen::mov_gpr64_u64(i, v1));
tester.emit(IGen::mov_gpr64_u64(j, v2));
tester.emit(IGen::or_gpr64_gpr64(i, j));
tester.emit(IGen::mov_gpr64_gpr64(RAX, i));
tester.emit_pop_all_gprs(true);
tester.emit_return();
auto result = tester.execute_ret<s64>(0, 0, 0, 0);
EXPECT_EQ(result, expected);
}
}
}
}
}
TEST(EmitterIntegerMath, and_gpr64_gpr64) {
CodeTester tester;
tester.init_code_buffer(256);
std::vector<s64> vals = {0, 1, -2, INT32_MIN, INT32_MAX, INT64_MIN,
INT64_MAX, 117, 32, -348473, 83747382};
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 (auto v1 : vals) {
for (auto v2 : vals) {
auto expected = v1 & v2;
tester.clear();
tester.emit_push_all_gprs(true);
tester.emit(IGen::mov_gpr64_u64(i, v1));
tester.emit(IGen::mov_gpr64_u64(j, v2));
tester.emit(IGen::and_gpr64_gpr64(i, j));
tester.emit(IGen::mov_gpr64_gpr64(RAX, i));
tester.emit_pop_all_gprs(true);
tester.emit_return();
auto result = tester.execute_ret<s64>(0, 0, 0, 0);
EXPECT_EQ(result, expected);
}
}
}
}
}
TEST(EmitterIntegerMath, xor_gpr64_gpr64) {
CodeTester tester;
tester.init_code_buffer(256);
std::vector<s64> vals = {0, 1, -2, INT32_MIN, INT32_MAX, INT64_MIN,
INT64_MAX, 117, 32, -348473, 83747382};
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 (auto v1 : vals) {
for (auto v2 : vals) {
auto expected = v1 ^ v2;
tester.clear();
tester.emit_push_all_gprs(true);
tester.emit(IGen::mov_gpr64_u64(i, v1));
tester.emit(IGen::mov_gpr64_u64(j, v2));
tester.emit(IGen::xor_gpr64_gpr64(i, j));
tester.emit(IGen::mov_gpr64_gpr64(RAX, i));
tester.emit_pop_all_gprs(true);
tester.emit_return();
auto result = tester.execute_ret<s64>(0, 0, 0, 0);
EXPECT_EQ(result, expected);
}
}
}
}
}
TEST(EmitterIntegerMath, not_gpr64) {
CodeTester tester;
tester.init_code_buffer(256);
std::vector<s64> vals = {0, 1, -2, INT32_MIN, INT32_MAX, INT64_MIN,
INT64_MAX, 117, 32, -348473, 83747382};
for (int i = 0; i < 16; i++) {
if (i == RSP) {
continue;
}
for (auto v1 : vals) {
auto expected = ~v1;
tester.clear();
tester.emit_push_all_gprs(true);
tester.emit(IGen::mov_gpr64_u64(i, v1));
tester.emit(IGen::not_gpr64(i));
tester.emit(IGen::mov_gpr64_gpr64(RAX, i));
tester.emit_pop_all_gprs(true);
tester.emit_return();
auto result = tester.execute_ret<s64>(0, 0, 0, 0);
EXPECT_EQ(result, expected);
}
}
}

View File

@ -2483,4 +2483,31 @@ TEST(EmitterLoadsAndStores, store8_rip_s32) {
"88050C000000880D0C00000088150C000000881D0C0000004088250C00000040882D0C0000004088350C00"
"000040883D0C0000004488050C00000044880D0C0000004488150C00000044881D0C0000004488250C0000"
"0044882D0C0000004488350C00000044883D0C000000");
}
TEST(EmitterLoadsAndStores, static_addr) {
CodeTester tester;
tester.init_code_buffer(512);
for (int i = 0; i < 16; i++) {
if (i == RSP) {
continue;
}
tester.clear();
tester.emit_push_all_gprs(true);
tester.emit(IGen::mov_gpr64_u64(i, 12345)); // load test reg with junk
int start_of_lea = tester.size();
auto lea_instr = IGen::static_addr(i, INT32_MAX);
tester.emit(lea_instr);
// patch instruction to lea the start of this code + 1.
tester.write<s32>(-start_of_lea - lea_instr.length() + 1,
start_of_lea + lea_instr.offset_of_disp());
tester.emit(IGen::mov_gpr64_gpr64(RAX, i));
tester.emit_pop_all_gprs(true);
tester.emit_return();
auto result = tester.execute();
EXPECT_EQ(result, (u64)(tester.data()) + 1);
}
}

View File

@ -389,3 +389,55 @@ TEST(EmitterXmm32, store32_xmm32_gpr64_plus_gpr64_plus_s32) {
}
}
}
TEST(EmitterXmm32, static_load_xmm32) {
CodeTester tester;
tester.init_code_buffer(512);
for (int i = 0; i < 16; i++) {
tester.clear();
tester.emit_push_all_xmms();
tester.emit_push_all_gprs(true);
auto loc_of_load = tester.size();
auto load_instr = IGen::static_load_xmm32(XMM0 + i, INT32_MAX);
tester.emit(load_instr);
tester.emit(IGen::movd_gpr32_xmm32(RAX, XMM0 + i));
tester.emit_pop_all_gprs(true);
tester.emit_pop_all_xmms();
tester.emit_return();
auto loc_of_float = tester.emit_data(float(1.2345f));
// patch offset
tester.write<s32>(loc_of_float - loc_of_load - load_instr.length(),
loc_of_load + load_instr.offset_of_disp());
auto result = tester.execute_ret<float>(0, 0, 0, 0);
EXPECT_EQ(result, 1.2345f);
}
}
TEST(EmitterXmm32, static_store_xmm32) {
CodeTester tester;
tester.init_code_buffer(512);
for (int i = 0; i < 16; i++) {
tester.clear();
tester.emit_push_all_xmms();
tester.emit_push_all_gprs(true);
tester.emit(IGen::movd_xmm32_gpr32(XMM0 + i, tester.get_c_abi_arg_reg(0)));
auto loc_of_store = tester.size();
auto store_instr = IGen::static_store_xmm32(XMM0 + i, INT32_MAX);
tester.emit(store_instr);
tester.emit_pop_all_gprs(true);
tester.emit_pop_all_xmms();
tester.emit_return();
auto loc_of_float = tester.emit_data(float(1.2345f));
tester.write<s32>(loc_of_float - loc_of_store - store_instr.length(),
loc_of_store + store_instr.offset_of_disp());
tester.execute(as_u32(-44.567f), 0, 0, 0);
EXPECT_EQ(-44.567f, tester.read<float>(loc_of_float));
}
}