Implement Thumb PUSH instruction

This commit is contained in:
MerryMage 2016-07-18 15:11:16 +01:00
parent 9109b226af
commit f7e3d7b8d2
10 changed files with 191 additions and 54 deletions

View file

@ -106,7 +106,7 @@ Gen::X64Reg RegAlloc::UseRegister(IR::Value* use_value, std::initializer_list<Ho
} else if (HostLocIsRegister(current_location)) {
ASSERT(hostloc_state[current_location] == HostLocState::Idle);
code->XCHG(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<Ho
return hostloc_to_x64.at(new_location);
}
Gen::X64Reg RegAlloc::UseScratchRegister(IR::Value* use_value, std::initializer_list<HostLoc> 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<HostLoc> 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]});
}

View file

@ -71,6 +71,8 @@ public:
Gen::X64Reg UseDefRegister(IR::Value* use_value, IR::Value* def_value, std::initializer_list<HostLoc> desired_locations = hostloc_any_register);
/// Early-use
Gen::X64Reg UseRegister(IR::Value* use_value, std::initializer_list<HostLoc> desired_locations = hostloc_any_register);
/// Early-use, Destroyed
Gen::X64Reg UseScratchRegister(IR::Value* use_value, std::initializer_list<HostLoc> desired_locations = hostloc_any_register);
/// Early-def, Late-use, single-use
Gen::X64Reg ScratchRegister(std::initializer_list<HostLoc> desired_locations = hostloc_any_register);

View file

@ -8,11 +8,11 @@
#include <climits>
#include "common/assert.h"
#include "common/common_types.h"
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4554)
#include <intrin.h>
#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<size_t bit_position, typename T>
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<typename T>
constexpr bool Bit(size_t bit_position, const T value) {
ASSERT_MSG(bit_position < BitSize<T>(), "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<size_t bit_count, typename T>
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

View file

@ -124,7 +124,7 @@ boost::optional<const Thumb16Matcher<V>&> 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

View file

@ -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<Reg>(i));
first_reg = false;
}
}
return ret;
}
std::string thumb16_REV(Reg m, Reg d) {
return Common::StringFromFormat("rev %s, %s", RegStr(d), RegStr(m));
}

View file

@ -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});
}

View file

@ -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);

View file

@ -7,6 +7,7 @@
#include <tuple>
#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>
// reg_list cannot encode for R15.
u32 num_bytes_to_push = static_cast<u32>(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<Reg>(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>, <Rm>
// Rd cannot encode R15.

View file

@ -115,9 +115,9 @@ static Dynarmic::UserCallbacks GetUserCallbacks() {
return user_callbacks;
}
struct InstructionGenerator final {
struct ThumbInstGen final {
public:
InstructionGenerator(const char* format, std::function<bool(u16)> is_valid = [](u16){ return true; }) : is_valid(is_valid) {
ThumbInstGen(const char* format, std::function<bool(u16)> 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<u16>(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<InstructionGenerator, 23> instructions = {{
InstructionGenerator("00000xxxxxxxxxxx"), // LSL <Rd>, <Rm>, #<imm5>
InstructionGenerator("00001xxxxxxxxxxx"), // LSR <Rd>, <Rm>, #<imm5>
InstructionGenerator("00010xxxxxxxxxxx"), // ASR <Rd>, <Rm>, #<imm5>
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)
const std::array<ThumbInstGen, 24> instructions = {{
ThumbInstGen("00000xxxxxxxxxxx"), // LSL <Rd>, <Rm>, #<imm5>
ThumbInstGen("00001xxxxxxxxxxx"), // LSR <Rd>, <Rm>, #<imm5>
ThumbInstGen("00010xxxxxxxxxxx"), // ASR <Rd>, <Rm>, #<imm5>
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
InstructionGenerator("0100010110xxxxxx", // CMP (high registers)
ThumbInstGen("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
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<InstructionGenerator, 7> instructions = {{
InstructionGenerator("01000111xmmmm000", // BLX/BX
const std::array<ThumbInstGen, 7> instructions = {{
ThumbInstGen("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<cond>
ThumbInstGen("1010oxxxxxxxxxxx"), // add to pc/sp
ThumbInstGen("11100xxxxxxxxxxx"), // B
ThumbInstGen("01000100h0xxxxxx"), // ADD (high registers)
ThumbInstGen("01000110h0xxxxxx"), // MOV (high registers)
ThumbInstGen("1101ccccxxxxxxxx", // B<cond>
[](u16 inst){
u32 c = Dynarmic::Common::Bits<9, 12>(inst);
return c < 0b1110; // Don't want SWI or undefined instructions.
}),
InstructionGenerator("10110110011x0xxx"), // CPS
ThumbInstGen("10110110011x0xxx"), // CPS
}};
auto instruction_select = [&]() -> u16 {

View file

@ -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
}