diff --git a/src/backend_x64/reg_alloc.cpp b/src/backend_x64/reg_alloc.cpp index 134f9a4e..c6fe7443 100644 --- a/src/backend_x64/reg_alloc.cpp +++ b/src/backend_x64/reg_alloc.cpp @@ -106,7 +106,7 @@ Gen::X64Reg RegAlloc::UseRegister(IR::Value* use_value, std::initializer_listXCHG(32, Gen::R(hostloc_to_x64.at(current_location)), Gen::R(hostloc_to_x64.at(new_location))); + code->XCHG(32, Gen::R(hostloc_to_x64.at(new_location)), Gen::R(hostloc_to_x64.at(current_location))); hostloc_state[new_location] = HostLocState::Use; std::swap(hostloc_to_value[new_location], hostloc_to_value[current_location]); @@ -118,6 +118,43 @@ Gen::X64Reg RegAlloc::UseRegister(IR::Value* use_value, std::initializer_list desired_locations) { + ASSERT(std::all_of(desired_locations.begin(), desired_locations.end(), HostLocIsRegister)); + ASSERT_MSG(remaining_uses.find(use_value) != remaining_uses.end(), "use_value has not been defined"); + ASSERT_MSG(!ValueLocations(use_value).empty(), "use_value has not been defined"); + ASSERT_MSG(remaining_uses[use_value] != 0, "use_value ran out of uses. (Use-d an IR::Value* too many times)"); + + HostLoc current_location = ValueLocations(use_value).front(); + HostLoc new_location = SelectARegister(desired_locations); + + if (HostLocIsSpill(current_location)) { + if (IsRegisterOccupied(new_location)) { + SpillRegister(new_location); + } + + code->MOV(32, Gen::R(hostloc_to_x64.at(new_location)), SpillToOpArg(current_location)); + + hostloc_state[new_location] = HostLocState::Scratch; + remaining_uses[use_value]--; + } else if (HostLocIsRegister(current_location)) { + ASSERT(hostloc_state[current_location] == HostLocState::Idle); + + if (IsRegisterOccupied(new_location)) { + SpillRegister(new_location); + } + + code->MOV(32, Gen::R(hostloc_to_x64.at(new_location)), Gen::R(hostloc_to_x64.at(current_location))); + + hostloc_state[new_location] = HostLocState::Scratch; + remaining_uses[use_value]--; + } else { + ASSERT_MSG(0, "Invalid current_location"); + } + + return hostloc_to_x64.at(new_location); +} + + Gen::X64Reg RegAlloc::ScratchRegister(std::initializer_list desired_locations) { ASSERT(std::all_of(desired_locations.begin(), desired_locations.end(), HostLocIsRegister)); @@ -161,7 +198,7 @@ void RegAlloc::HostCall(IR::Value* result_def, IR::Value* arg0_use, IR::Value* a for (size_t i = 0; i < AbiArgs.size(); i++) { if (args[i]) { - UseRegister(args[i], {AbiArgs[i]}); + UseScratchRegister(args[i], {AbiArgs[i]}); } else { ScratchRegister({AbiArgs[i]}); } diff --git a/src/backend_x64/reg_alloc.h b/src/backend_x64/reg_alloc.h index 9e293390..8d9bed42 100644 --- a/src/backend_x64/reg_alloc.h +++ b/src/backend_x64/reg_alloc.h @@ -71,6 +71,8 @@ public: Gen::X64Reg UseDefRegister(IR::Value* use_value, IR::Value* def_value, std::initializer_list desired_locations = hostloc_any_register); /// Early-use Gen::X64Reg UseRegister(IR::Value* use_value, std::initializer_list desired_locations = hostloc_any_register); + /// Early-use, Destroyed + Gen::X64Reg UseScratchRegister(IR::Value* use_value, std::initializer_list desired_locations = hostloc_any_register); /// Early-def, Late-use, single-use Gen::X64Reg ScratchRegister(std::initializer_list desired_locations = hostloc_any_register); diff --git a/src/common/bit_util.h b/src/common/bit_util.h index 24d5a22c..ed66811e 100644 --- a/src/common/bit_util.h +++ b/src/common/bit_util.h @@ -8,11 +8,11 @@ #include +#include "common/assert.h" #include "common/common_types.h" #ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable:4554) +#include #endif namespace Dynarmic { @@ -35,6 +35,10 @@ constexpr T Bits(const T value) { return (value >> begin_bit) & ((1 << (end_bit - begin_bit + 1)) - 1); } +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4554) +#endif /// Extracts a single bit at bit_position from value of type T. template constexpr bool Bit(const T value) { @@ -43,6 +47,17 @@ constexpr bool Bit(const T value) { return ((value >> bit_position) & 1) != 0; } +/// Extracts a single bit at bit_position from value of type T. +template +constexpr bool Bit(size_t bit_position, const T value) { + ASSERT_MSG(bit_position < BitSize(), "bit_position must be smaller than size of T"); + + return ((value >> bit_position) & 1) != 0; +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + /// Sign-extends a value that has NBits bits to the full bitwidth of type T. template inline T SignExtend(const T value) { @@ -56,9 +71,13 @@ inline T SignExtend(const T value) { return value; } +inline size_t BitCount(u32 value) { +#ifdef _MSC_VER + return __popcnt(value); +#else + return __builtin_popcount(value); +#endif +} + } // namespace Common } // namespace Dynarmic - -#ifdef _MSC_VER -#pragma warning(pop) -#endif diff --git a/src/frontend/decoder/thumb16.h b/src/frontend/decoder/thumb16.h index d457944f..d59e1d86 100644 --- a/src/frontend/decoder/thumb16.h +++ b/src/frontend/decoder/thumb16.h @@ -124,7 +124,7 @@ boost::optional&> DecodeThumb16(u16 instruction) { INST(&V::thumb16_SXTB, "SXTB", "1011001001mmmddd"), // v6 INST(&V::thumb16_UXTH, "UXTH", "1011001010mmmddd"), // v6 INST(&V::thumb16_UXTB, "UXTB", "1011001011mmmddd"), // v6 - //INST(&V::thumb16_PUSH, "PUSH", "1011010rxxxxxxxx"), // v4T + INST(&V::thumb16_PUSH, "PUSH", "1011010Mxxxxxxxx"), // v4T //INST(&V::thumb16_POP, "POP", "1011110rxxxxxxxx"), // v4T //INST(&V::thumb16_SETEND, "SETEND", "101101100101x000"), // v6 //INST(&V::thumb16_CPS, "CPS", "10110110011m0aif"), // v6 diff --git a/src/frontend/disassembler/disassembler_thumb.cpp b/src/frontend/disassembler/disassembler_thumb.cpp index 162cc20f..25ab21f8 100644 --- a/src/frontend/disassembler/disassembler_thumb.cpp +++ b/src/frontend/disassembler/disassembler_thumb.cpp @@ -280,6 +280,24 @@ public: return Common::StringFromFormat("uxtb %s, %s", RegStr(d), RegStr(m)); } + std::string thumb16_PUSH(bool M, RegList reg_list) { + if (M) + reg_list |= 1 << 14; + + std::string ret = "PUSH "; + bool first_reg = true; + for (size_t i = 0; i < 16; i++) { + if (Common::Bit(i, reg_list)) { + if (!first_reg) + ret += ", "; + ret += RegStr(static_cast(i)); + first_reg = false; + } + } + + return ret; + } + std::string thumb16_REV(Reg m, Reg d) { return Common::StringFromFormat("rev %s, %s", RegStr(d), RegStr(m)); } diff --git a/src/frontend/ir/ir_emitter.cpp b/src/frontend/ir/ir_emitter.cpp index 1549ab31..7bf8edad 100644 --- a/src/frontend/ir/ir_emitter.cpp +++ b/src/frontend/ir/ir_emitter.cpp @@ -155,6 +155,10 @@ IREmitter::ResultAndCarryAndOverflow IREmitter::SubWithCarry(IR::ValuePtr a, IR: return {result, carry_out, overflow}; } +IR::ValuePtr IREmitter::Sub(IR::ValuePtr a, IR::ValuePtr b) { + return Inst(IR::Opcode::SubWithCarry, {a, b, Imm1(1)}); +} + IR::ValuePtr IREmitter::And(IR::ValuePtr a, IR::ValuePtr b) { return Inst(IR::Opcode::And, {a, b}); } diff --git a/src/frontend/ir/ir_emitter.h b/src/frontend/ir/ir_emitter.h index a3a7d244..7e3874c6 100644 --- a/src/frontend/ir/ir_emitter.h +++ b/src/frontend/ir/ir_emitter.h @@ -64,6 +64,7 @@ public: ResultAndCarryAndOverflow AddWithCarry(IR::ValuePtr a, IR::ValuePtr b, IR::ValuePtr carry_in); IR::ValuePtr Add(IR::ValuePtr a, IR::ValuePtr b); ResultAndCarryAndOverflow SubWithCarry(IR::ValuePtr a, IR::ValuePtr b, IR::ValuePtr carry_in); + IR::ValuePtr Sub(IR::ValuePtr a, IR::ValuePtr b); IR::ValuePtr And(IR::ValuePtr a, IR::ValuePtr b); IR::ValuePtr Eor(IR::ValuePtr a, IR::ValuePtr b); IR::ValuePtr Or(IR::ValuePtr a, IR::ValuePtr b); diff --git a/src/frontend/translate/translate_thumb.cpp b/src/frontend/translate/translate_thumb.cpp index 7e2852fb..6f1f5993 100644 --- a/src/frontend/translate/translate_thumb.cpp +++ b/src/frontend/translate/translate_thumb.cpp @@ -7,6 +7,7 @@ #include #include "common/assert.h" +#include "common/bit_util.h" #include "frontend/arm_types.h" #include "frontend/decoder/thumb16.h" #include "frontend/ir/ir_emitter.h" @@ -507,6 +508,28 @@ struct ThumbTranslatorVisitor final { return true; } + bool thumb16_PUSH(bool M, RegList reg_list) { + if (M) reg_list |= 1 << 14; + if (Common::BitCount(reg_list) < 1) { + return UnpredictableInstruction(); + } + // PUSH + // reg_list cannot encode for R15. + u32 num_bytes_to_push = static_cast(4 * Common::BitCount(reg_list)); + const auto final_address = ir.Sub(ir.GetRegister(Reg::SP), ir.Imm32(num_bytes_to_push)); + auto address = final_address; + for (size_t i = 0; i < 16; i++) { + if (Common::Bit(i, reg_list)) { + auto Ri = ir.GetRegister(static_cast(i)); + ir.WriteMemory32(address, Ri); + address = ir.Add(address, ir.Imm32(4)); + } + } + ir.SetRegister(Reg::SP, final_address); + // TODO(optimization): Possible location for an RSB push. + return true; + } + bool thumb16_REV(Reg m, Reg d) { // REV , // Rd cannot encode R15. diff --git a/tests/arm/fuzz_thumb.cpp b/tests/arm/fuzz_thumb.cpp index 23ccfcfd..608e6dc2 100644 --- a/tests/arm/fuzz_thumb.cpp +++ b/tests/arm/fuzz_thumb.cpp @@ -115,9 +115,9 @@ static Dynarmic::UserCallbacks GetUserCallbacks() { return user_callbacks; } -struct InstructionGenerator final { +struct ThumbInstGen final { public: - InstructionGenerator(const char* format, std::function is_valid = [](u16){ return true; }) : is_valid(is_valid) { + ThumbInstGen(const char* format, std::function is_valid = [](u16){ return true; }) : is_valid(is_valid) { REQUIRE(strlen(format) == 16); for (int i = 0; i < 16; i++) { @@ -138,10 +138,14 @@ public: } u16 Generate() const { u16 inst; + do { u16 random = RandInt(0, 0xFFFF); inst = bits | (random & ~mask); } while (!is_valid(inst)); + + ASSERT((inst & mask) == bits); + return inst; } private: @@ -223,6 +227,16 @@ void FuzzJitThumb(const size_t instruction_count, const size_t instructions_to_e } printf("CPSR: %08x %08x %s\n", interp.Cpsr, jit.Cpsr(), interp.Cpsr != jit.Cpsr() ? "*" : ""); + printf("\nInterp Write Records:\n"); + for (auto& record : interp_write_records) { + printf("%zu [%x] = %llx\n", record.size, record.address, record.data); + } + + printf("\nJIT Write Records:\n"); + for (auto& record : jit_write_records) { + printf("%zu [%x] = %llx\n", record.size, record.address, record.data); + } + #ifdef _MSC_VER __debugbreak(); #endif @@ -234,33 +248,35 @@ void FuzzJitThumb(const size_t instruction_count, const size_t instructions_to_e } TEST_CASE("Fuzz Thumb instructions set 1", "[JitX64][Thumb]") { - const std::array instructions = {{ - InstructionGenerator("00000xxxxxxxxxxx"), // LSL , , # - InstructionGenerator("00001xxxxxxxxxxx"), // LSR , , # - InstructionGenerator("00010xxxxxxxxxxx"), // ASR , , # - InstructionGenerator("000110oxxxxxxxxx"), // ADD/SUB_reg - InstructionGenerator("000111oxxxxxxxxx"), // ADD/SUB_imm - InstructionGenerator("001ooxxxxxxxxxxx"), // ADD/SUB/CMP/MOV_imm - InstructionGenerator("010000ooooxxxxxx"), // Data Processing - InstructionGenerator("010001000hxxxxxx"), // ADD (high registers) - InstructionGenerator("0100010101xxxxxx", // CMP (high registers) - [](u16 inst){ return Dynarmic::Common::Bits<3, 5>(inst) != 0b111; }), // R15 is UNPREDICTABLE - InstructionGenerator("0100010110xxxxxx", // CMP (high registers) - [](u16 inst){ return Dynarmic::Common::Bits<0, 2>(inst) != 0b111; }), // R15 is UNPREDICTABLE - InstructionGenerator("010001100hxxxxxx"), // MOV (high registers) - InstructionGenerator("10110000oxxxxxxx"), // Adjust stack pointer - InstructionGenerator("10110010ooxxxxxx"), // SXT/UXT - InstructionGenerator("1011101000xxxxxx"), // REV - InstructionGenerator("1011101001xxxxxx"), // REV16 - InstructionGenerator("1011101011xxxxxx"), // REVSH - InstructionGenerator("01001xxxxxxxxxxx"), // LDR Rd, [PC, #] - InstructionGenerator("0101oooxxxxxxxxx"), // LDR/STR Rd, [Rn, Rm] - InstructionGenerator("011xxxxxxxxxxxxx"), // LDR(B)/STR(B) Rd, [Rn, #] - InstructionGenerator("1000xxxxxxxxxxxx"), // LDRH/STRH Rd, [Rn, #offset] - InstructionGenerator("1001xxxxxxxxxxxx"), // LDR/STR Rd, [SP, #] - InstructionGenerator("1011x100xxxxxxxx"), // PUSH/POP (R = 0) - InstructionGenerator("1100xxxxxxxxxxxx"), // STMIA/LDMIA - //InstructionGenerator("101101100101x000"), // SETEND + const std::array instructions = {{ + ThumbInstGen("00000xxxxxxxxxxx"), // LSL , , # + ThumbInstGen("00001xxxxxxxxxxx"), // LSR , , # + ThumbInstGen("00010xxxxxxxxxxx"), // ASR , , # + ThumbInstGen("000110oxxxxxxxxx"), // ADD/SUB_reg + ThumbInstGen("000111oxxxxxxxxx"), // ADD/SUB_imm + ThumbInstGen("001ooxxxxxxxxxxx"), // ADD/SUB/CMP/MOV_imm + ThumbInstGen("010000ooooxxxxxx"), // Data Processing + ThumbInstGen("010001000hxxxxxx"), // ADD (high registers) + ThumbInstGen("0100010101xxxxxx", // CMP (high registers) + [](u16 inst){ return Dynarmic::Common::Bits<3, 5>(inst) != 0b111; }), // R15 is UNPREDICTABLE + ThumbInstGen("0100010110xxxxxx", // CMP (high registers) + [](u16 inst){ return Dynarmic::Common::Bits<0, 2>(inst) != 0b111; }), // R15 is UNPREDICTABLE + ThumbInstGen("010001100hxxxxxx"), // MOV (high registers) + ThumbInstGen("10110000oxxxxxxx"), // Adjust stack pointer + ThumbInstGen("10110010ooxxxxxx"), // SXT/UXT + ThumbInstGen("1011101000xxxxxx"), // REV + ThumbInstGen("1011101001xxxxxx"), // REV16 + ThumbInstGen("1011101011xxxxxx"), // REVSH + ThumbInstGen("01001xxxxxxxxxxx"), // LDR Rd, [PC, #] + ThumbInstGen("0101oooxxxxxxxxx"), // LDR/STR Rd, [Rn, Rm] + ThumbInstGen("011xxxxxxxxxxxxx"), // LDR(B)/STR(B) Rd, [Rn, #] + ThumbInstGen("1000xxxxxxxxxxxx"), // LDRH/STRH Rd, [Rn, #offset] + ThumbInstGen("1001xxxxxxxxxxxx"), // LDR/STR Rd, [SP, #] + ThumbInstGen("10110100xxxxxxxx", // PUSH (R = 0) + [](u16 inst){ return Dynarmic::Common::Bits<0, 7>(inst) != 0; }), // Empty reg_list is UNPREDICTABLE + ThumbInstGen("10111100xxxxxxxx"), // POP (R = 0) + ThumbInstGen("1100xxxxxxxxxxxx"), // STMIA/LDMIA + //ThumbInstGen("101101100101x000"), // SETEND }}; auto instruction_select = [&]() -> u16 { @@ -283,22 +299,22 @@ TEST_CASE("Fuzz Thumb instructions set 1", "[JitX64][Thumb]") { } TEST_CASE("Fuzz Thumb instructions set 2 (affects PC)", "[JitX64][Thumb]") { - const std::array instructions = {{ - InstructionGenerator("01000111xmmmm000", // BLX/BX - [](u16 inst){ - u32 Rm = Dynarmic::Common::Bits<3, 6>(inst); - return Rm != 15; - }), - InstructionGenerator("1010oxxxxxxxxxxx"), // add to pc/sp - InstructionGenerator("11100xxxxxxxxxxx"), // B - InstructionGenerator("01000100h0xxxxxx"), // ADD (high registers) - InstructionGenerator("01000110h0xxxxxx"), // MOV (high registers) - InstructionGenerator("1101ccccxxxxxxxx", // B - [](u16 inst){ - u32 c = Dynarmic::Common::Bits<9, 12>(inst); - return c < 0b1110; // Don't want SWI or undefined instructions. - }), - InstructionGenerator("10110110011x0xxx"), // CPS + const std::array instructions = {{ + ThumbInstGen("01000111xmmmm000", // BLX/BX + [](u16 inst){ + u32 Rm = Dynarmic::Common::Bits<3, 6>(inst); + return Rm != 15; + }), + ThumbInstGen("1010oxxxxxxxxxxx"), // add to pc/sp + ThumbInstGen("11100xxxxxxxxxxx"), // B + ThumbInstGen("01000100h0xxxxxx"), // ADD (high registers) + ThumbInstGen("01000110h0xxxxxx"), // MOV (high registers) + ThumbInstGen("1101ccccxxxxxxxx", // B + [](u16 inst){ + u32 c = Dynarmic::Common::Bits<9, 12>(inst); + return c < 0b1110; // Don't want SWI or undefined instructions. + }), + ThumbInstGen("10110110011x0xxx"), // CPS }}; auto instruction_select = [&]() -> u16 { diff --git a/tests/arm/test_thumb_instructions.cpp b/tests/arm/test_thumb_instructions.cpp index f425b103..d20ea0bd 100644 --- a/tests/arm/test_thumb_instructions.cpp +++ b/tests/arm/test_thumb_instructions.cpp @@ -103,3 +103,20 @@ TEST_CASE( "thumb: revsh r4, r3", "[thumb]" ) { REQUIRE( jit.Regs()[15] == 2 ); REQUIRE( jit.Cpsr() == 0x00000030 ); // Thumb, User-mode } + +TEST_CASE( "thumb: ldr r3, [r3, #28]", "[thumb]" ) { + Dynarmic::Jit jit{GetUserCallbacks()}; + code_mem.fill({}); + code_mem[0] = 0x69DB; // ldr r3, [r3, #28] + code_mem[1] = 0xE7FE; // b +#0 + + jit.Regs()[3] = 0x12345678; + jit.Regs()[15] = 0; // PC = 0 + jit.Cpsr() = 0x00000030; // Thumb, User-mode + + jit.Run(1); + + REQUIRE( jit.Regs()[3] == 0x12345694 ); + REQUIRE( jit.Regs()[15] == 2 ); + REQUIRE( jit.Cpsr() == 0x00000030 ); // Thumb, User-mode +}