From 4723153a4b3bf309c5f163c9b0c7804970fb7f29 Mon Sep 17 00:00:00 2001 From: water Date: Wed, 2 Sep 2020 16:24:13 -0400 Subject: [PATCH] more progress on emitter integers --- goalc/emitter/CodeTester.h | 49 +++- goalc/emitter/IGen.h | 251 ++++++++++++++-- goalc/emitter/Instruction.h | 40 +++ goalc/old_emitter/IGen.h | 179 ------------ test/CMakeLists.txt | 1 + test/test_emitter_integer_math.cpp | 393 ++++++++++++++++++++++++++ test/test_emitter_loads_and_store.cpp | 27 ++ test/test_emitter_xmm32.cpp | 52 ++++ 8 files changed, 796 insertions(+), 196 deletions(-) create mode 100644 test/test_emitter_integer_math.cpp diff --git a/goalc/emitter/CodeTester.h b/goalc/emitter/CodeTester.h index 3dd17d3ae1..dbab244d4a 100644 --- a/goalc/emitter/CodeTester.h +++ b/goalc/emitter/CodeTester.h @@ -30,11 +30,37 @@ class CodeTester { return result_T; } + template + 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 + 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 + 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; diff --git a/goalc/emitter/IGen.h b/goalc/emitter/IGen.h index 6a09cc9baf..1703295f37 100644 --- a/goalc/emitter/IGen.h +++ b/goalc/emitter/IGen.h @@ -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 diff --git a/goalc/emitter/Instruction.h b/goalc/emitter/Instruction.h index b46f7dc891..aab76e1a42 100644 --- a/goalc/emitter/Instruction.h +++ b/goalc/emitter/Instruction.h @@ -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 diff --git a/goalc/old_emitter/IGen.h b/goalc/old_emitter/IGen.h index 7f297993bf..ba95ca87ba 100644 --- a/goalc/old_emitter/IGen.h +++ b/goalc/old_emitter/IGen.h @@ -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 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index eabc23b71b..05be29603e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/test/test_emitter_integer_math.cpp b/test/test_emitter_integer_math.cpp new file mode 100644 index 0000000000..325e28f278 --- /dev/null +++ b/test/test_emitter_integer_math.cpp @@ -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 vals = {0, 1, -1, INT32_MIN, INT32_MAX, INT64_MIN, INT64_MAX}; + std::vector 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(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 vals = {0, 1, -1, INT32_MIN, INT32_MAX, INT64_MIN, INT64_MAX}; + std::vector 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(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 vals = {0, 1, -1, INT32_MIN, INT32_MAX, INT64_MIN, INT64_MAX}; + std::vector 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(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 vals = {0, 1, -1, INT32_MIN, INT32_MAX, INT64_MIN, INT64_MAX}; + std::vector 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(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 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(0, 0, 0, 0); + EXPECT_EQ(result, expected); + } + } + } + } +} + +TEST(EmitterIntegerMath, sub_gpr64_gpr64) { + CodeTester tester; + tester.init_code_buffer(256); + std::vector 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(0, 0, 0, 0); + EXPECT_EQ(result, expected); + } + } + } + } +} + +TEST(EmitterIntegerMath, mul_gpr32_gpr32) { + CodeTester tester; + tester.init_code_buffer(256); + std::vector 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(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 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(0, 0, 0, 0); + EXPECT_EQ(result, expected); + } + } + } + } +} + +TEST(EmitterIntegerMath, and_gpr64_gpr64) { + CodeTester tester; + tester.init_code_buffer(256); + std::vector 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(0, 0, 0, 0); + EXPECT_EQ(result, expected); + } + } + } + } +} + +TEST(EmitterIntegerMath, xor_gpr64_gpr64) { + CodeTester tester; + tester.init_code_buffer(256); + std::vector 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(0, 0, 0, 0); + EXPECT_EQ(result, expected); + } + } + } + } +} + +TEST(EmitterIntegerMath, not_gpr64) { + CodeTester tester; + tester.init_code_buffer(256); + std::vector 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(0, 0, 0, 0); + EXPECT_EQ(result, expected); + } + } +} \ No newline at end of file diff --git a/test/test_emitter_loads_and_store.cpp b/test/test_emitter_loads_and_store.cpp index 912d1364ff..02a9261800 100644 --- a/test/test_emitter_loads_and_store.cpp +++ b/test/test_emitter_loads_and_store.cpp @@ -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(-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); + } } \ No newline at end of file diff --git a/test/test_emitter_xmm32.cpp b/test/test_emitter_xmm32.cpp index 1f9e82e61e..fd19191e44 100644 --- a/test/test_emitter_xmm32.cpp +++ b/test/test_emitter_xmm32.cpp @@ -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(loc_of_float - loc_of_load - load_instr.length(), + loc_of_load + load_instr.offset_of_disp()); + + auto result = tester.execute_ret(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(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(loc_of_float)); + } +} \ No newline at end of file