diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 16b9075d1..e59db183e 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -120,6 +120,13 @@ if(${CPU_ARCH} STREQUAL "x64")
cpu_recompiler_code_generator_x64.cpp
)
message("Building x64 recompiler")
+elseif(${CPU_ARCH} STREQUAL "aarch32")
+ target_compile_definitions(core PRIVATE "WITH_RECOMPILER=1")
+ target_sources(core PRIVATE ${RECOMPILER_SRCS}
+ cpu_recompiler_code_generator_aarch32.cpp
+ )
+ target_link_libraries(core PRIVATE vixl)
+ message("Building AArch32 recompiler")
elseif(${CPU_ARCH} STREQUAL "aarch64")
target_compile_definitions(core PRIVATE "WITH_RECOMPILER=1 WITH_FASTMEM=1")
target_sources(core PRIVATE ${RECOMPILER_SRCS}
diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj
index b74e3bc33..d386bbcf8 100644
--- a/src/core/core.vcxproj
+++ b/src/core/core.vcxproj
@@ -67,6 +67,20 @@
true
true
+
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+
true
true
@@ -825,4 +839,4 @@
true
core
-
\ No newline at end of file
+
diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters
index 7793d8dd5..d01f8c308 100644
--- a/src/core/core.vcxproj.filters
+++ b/src/core/core.vcxproj.filters
@@ -50,6 +50,7 @@
+
diff --git a/src/core/cpu_recompiler_code_generator_aarch32.cpp b/src/core/cpu_recompiler_code_generator_aarch32.cpp
new file mode 100644
index 000000000..1e5512edb
--- /dev/null
+++ b/src/core/cpu_recompiler_code_generator_aarch32.cpp
@@ -0,0 +1,1794 @@
+#include "common/align.h"
+#include "common/assert.h"
+#include "common/log.h"
+#include "cpu_core.h"
+#include "cpu_core_private.h"
+#include "cpu_recompiler_code_generator.h"
+#include "cpu_recompiler_thunks.h"
+#include "settings.h"
+#include "timing_event.h"
+Log_SetChannel(CPU::Recompiler);
+
+// #include "vixl/aarch32/disasm-aarch32.h"
+// #include
+
+namespace a32 = vixl::aarch32;
+
+namespace CPU::Recompiler {
+
+constexpr HostReg RCPUPTR = 4;
+constexpr HostReg RRETURN = 0;
+constexpr HostReg RARG1 = 0;
+constexpr HostReg RARG2 = 1;
+constexpr HostReg RARG3 = 2;
+constexpr HostReg RARG4 = 3;
+constexpr HostReg RSCRATCH = 12;
+constexpr u32 FUNCTION_CALL_STACK_ALIGNMENT = 16;
+constexpr u32 FUNCTION_CALL_SHADOW_SPACE = 32;
+constexpr u32 FUNCTION_CALLEE_SAVED_SPACE_RESERVE = 80; // 8 registers
+constexpr u32 FUNCTION_CALLER_SAVED_SPACE_RESERVE = 144; // 18 registers -> 224 bytes
+constexpr u32 FUNCTION_STACK_SIZE =
+ FUNCTION_CALLEE_SAVED_SPACE_RESERVE + FUNCTION_CALLER_SAVED_SPACE_RESERVE + FUNCTION_CALL_SHADOW_SPACE;
+
+// PC we return to after the end of the block
+static void* s_dispatcher_return_address;
+
+static s32 GetPCDisplacement(const void* current, const void* target)
+{
+ Assert(Common::IsAlignedPow2(reinterpret_cast(current), 4));
+ Assert(Common::IsAlignedPow2(reinterpret_cast(target), 4));
+ return static_cast((reinterpret_cast(target) - reinterpret_cast(current)));
+}
+
+static bool IsPCDisplacementInImmediateRange(s32 displacement)
+{
+ return (displacement >= -33554432 && displacement <= 33554428);
+}
+
+static const a32::Register GetHostReg8(HostReg reg)
+{
+ return a32::Register(reg);
+}
+
+static const a32::Register GetHostReg8(const Value& value)
+{
+ DebugAssert(value.size == RegSize_8 && value.IsInHostRegister());
+ return a32::Register(value.host_reg);
+}
+
+static const a32::Register GetHostReg16(HostReg reg)
+{
+ return a32::Register(reg);
+}
+
+static const a32::Register GetHostReg16(const Value& value)
+{
+ DebugAssert(value.size == RegSize_16 && value.IsInHostRegister());
+ return a32::Register(value.host_reg);
+}
+
+static const a32::Register GetHostReg32(HostReg reg)
+{
+ return a32::Register(reg);
+}
+
+static const a32::Register GetHostReg32(const Value& value)
+{
+ DebugAssert(value.size == RegSize_32 && value.IsInHostRegister());
+ return a32::Register(value.host_reg);
+}
+
+static const a32::Register GetCPUPtrReg()
+{
+ return GetHostReg32(RCPUPTR);
+}
+
+CodeGenerator::CodeGenerator(JitCodeBuffer* code_buffer)
+ : m_code_buffer(code_buffer), m_register_cache(*this),
+ m_near_emitter(static_cast(code_buffer->GetFreeCodePointer()), code_buffer->GetFreeCodeSpace(),
+ a32::A32),
+ m_far_emitter(static_cast(code_buffer->GetFreeFarCodePointer()), code_buffer->GetFreeFarCodeSpace(),
+ a32::A32),
+ m_emit(&m_near_emitter)
+{
+ InitHostRegs();
+}
+
+CodeGenerator::~CodeGenerator() = default;
+
+const char* CodeGenerator::GetHostRegName(HostReg reg, RegSize size /*= HostPointerSize*/)
+{
+ static constexpr std::array reg_names = {
+ {"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"}};
+ if (reg >= static_cast(HostReg_Count))
+ return "";
+
+ switch (size)
+ {
+ case RegSize_32:
+ return reg_names[reg];
+ default:
+ return "";
+ }
+}
+
+void CodeGenerator::AlignCodeBuffer(JitCodeBuffer* code_buffer)
+{
+ code_buffer->Align(16, 0x90);
+}
+
+void CodeGenerator::InitHostRegs()
+{
+ // allocate nonvolatile before volatile
+ // NOTE: vixl also uses r12 for the macro assembler
+ m_register_cache.SetHostRegAllocationOrder({4, 5, 6, 7, 8, 9, 10, 11});
+ m_register_cache.SetCallerSavedHostRegs({0, 1, 2, 3, 12});
+ m_register_cache.SetCalleeSavedHostRegs({4, 5, 6, 7, 8, 9, 10, 11, 13, 14});
+ m_register_cache.SetCPUPtrHostReg(RCPUPTR);
+}
+
+void CodeGenerator::SwitchToFarCode()
+{
+ m_emit = &m_far_emitter;
+}
+
+void CodeGenerator::SwitchToNearCode()
+{
+ m_emit = &m_near_emitter;
+}
+
+void* CodeGenerator::GetCurrentNearCodePointer() const
+{
+ return static_cast(m_code_buffer->GetFreeCodePointer()) + m_near_emitter.GetCursorOffset();
+}
+
+void* CodeGenerator::GetCurrentFarCodePointer() const
+{
+ return static_cast(m_code_buffer->GetFreeFarCodePointer()) + m_far_emitter.GetCursorOffset();
+}
+
+Value CodeGenerator::GetValueInHostRegister(const Value& value, bool allow_zero_register /* = true */)
+{
+ if (value.IsInHostRegister())
+ return Value::FromHostReg(&m_register_cache, value.host_reg, value.size);
+
+ Value new_value = m_register_cache.AllocateScratch(value.size);
+ EmitCopyValue(new_value.host_reg, value);
+ return new_value;
+}
+
+Value CodeGenerator::GetValueInHostOrScratchRegister(const Value& value, bool allow_zero_register /* = true */)
+{
+ if (value.IsInHostRegister())
+ return Value::FromHostReg(&m_register_cache, value.host_reg, value.size);
+
+ Value new_value = Value::FromHostReg(&m_register_cache, RSCRATCH, value.size);
+ EmitCopyValue(new_value.host_reg, value);
+ return new_value;
+}
+
+void CodeGenerator::EmitBeginBlock()
+{
+ m_emit->sub(a32::sp, a32::sp, FUNCTION_STACK_SIZE);
+
+ // Save the link register, since we'll be calling functions.
+ const bool link_reg_allocated = m_register_cache.AllocateHostReg(14);
+ DebugAssert(link_reg_allocated);
+ m_register_cache.AssumeCalleeSavedRegistersAreSaved();
+
+ // Store the CPU struct pointer. TODO: make this better.
+ const bool cpu_reg_allocated = m_register_cache.AllocateHostReg(RCPUPTR);
+ // m_emit->Mov(GetCPUPtrReg(), reinterpret_cast(&g_state));
+ DebugAssert(cpu_reg_allocated);
+}
+
+void CodeGenerator::EmitEndBlock()
+{
+ m_register_cache.FreeHostReg(RCPUPTR);
+ m_register_cache.PopCalleeSavedRegisters(true);
+
+ m_emit->add(a32::sp, a32::sp, FUNCTION_STACK_SIZE);
+ // m_emit->b(GetPCDisplacement(GetCurrentCodePointer(), s_dispatcher_return_address));
+ m_emit->bx(a32::lr);
+}
+
+void CodeGenerator::EmitExceptionExit()
+{
+ // ensure all unflushed registers are written back
+ m_register_cache.FlushAllGuestRegisters(false, false);
+
+ // the interpreter load delay might have its own value, but we'll overwrite it here anyway
+ // technically RaiseException() and FlushPipeline() have already been called, but that should be okay
+ m_register_cache.FlushLoadDelay(false);
+
+ m_register_cache.PopCalleeSavedRegisters(false);
+
+ m_emit->add(a32::sp, a32::sp, FUNCTION_STACK_SIZE);
+ // m_emit->b(GetPCDisplacement(GetCurrentCodePointer(), s_dispatcher_return_address));
+ m_emit->bx(a32::lr);
+}
+
+void CodeGenerator::EmitExceptionExitOnBool(const Value& value)
+{
+ Assert(!value.IsConstant() && value.IsInHostRegister());
+
+ m_register_cache.PushState();
+
+ // TODO: This is... not great.
+ a32::Label skip_branch;
+ m_emit->tst(GetHostReg32(value.host_reg), 1);
+ m_emit->b(a32::eq, &skip_branch);
+ EmitBranch(GetCurrentFarCodePointer());
+ m_emit->Bind(&skip_branch);
+
+ SwitchToFarCode();
+ EmitExceptionExit();
+ SwitchToNearCode();
+
+ m_register_cache.PopState();
+}
+
+void CodeGenerator::FinalizeBlock(CodeBlock::HostCodePointer* out_host_code, u32* out_host_code_size)
+{
+ m_near_emitter.FinalizeCode();
+ m_far_emitter.FinalizeCode();
+
+ *out_host_code = reinterpret_cast(m_code_buffer->GetFreeCodePointer());
+ *out_host_code_size = static_cast(m_near_emitter.GetSizeOfCodeGenerated());
+
+ m_code_buffer->CommitCode(static_cast(m_near_emitter.GetSizeOfCodeGenerated()));
+ m_code_buffer->CommitFarCode(static_cast(m_far_emitter.GetSizeOfCodeGenerated()));
+
+ m_near_emitter = CodeEmitter(static_cast(m_code_buffer->GetFreeCodePointer()),
+ m_code_buffer->GetFreeCodeSpace(), a32::A32);
+ m_far_emitter = CodeEmitter(static_cast(m_code_buffer->GetFreeFarCodePointer()),
+ m_code_buffer->GetFreeFarCodeSpace(), a32::A32);
+
+#if 0
+ a32::PrintDisassembler dis(std::cout, 0);
+ dis.SetCodeAddress(reinterpret_cast(*out_host_code));
+ dis.DisassembleA32Buffer(reinterpret_cast(*out_host_code), *out_host_code_size);
+#endif
+}
+
+void CodeGenerator::EmitSignExtend(HostReg to_reg, RegSize to_size, HostReg from_reg, RegSize from_size)
+{
+ switch (to_size)
+ {
+ case RegSize_16:
+ {
+ switch (from_size)
+ {
+ case RegSize_8:
+ m_emit->sxtb(GetHostReg16(to_reg), GetHostReg8(from_reg));
+ m_emit->and_(GetHostReg16(to_reg), GetHostReg16(to_reg), 0xFFFF);
+ return;
+ }
+ }
+ break;
+
+ case RegSize_32:
+ {
+ switch (from_size)
+ {
+ case RegSize_8:
+ m_emit->sxtb(GetHostReg32(to_reg), GetHostReg8(from_reg));
+ return;
+ case RegSize_16:
+ m_emit->sxth(GetHostReg32(to_reg), GetHostReg16(from_reg));
+ return;
+ }
+ }
+ break;
+ }
+
+ Panic("Unknown sign-extend combination");
+}
+
+void CodeGenerator::EmitZeroExtend(HostReg to_reg, RegSize to_size, HostReg from_reg, RegSize from_size)
+{
+ switch (to_size)
+ {
+ case RegSize_16:
+ {
+ switch (from_size)
+ {
+ case RegSize_8:
+ m_emit->and_(GetHostReg16(to_reg), GetHostReg8(from_reg), 0xFF);
+ return;
+ }
+ }
+ break;
+
+ case RegSize_32:
+ {
+ switch (from_size)
+ {
+ case RegSize_8:
+ m_emit->and_(GetHostReg32(to_reg), GetHostReg8(from_reg), 0xFF);
+ return;
+ case RegSize_16:
+ m_emit->and_(GetHostReg32(to_reg), GetHostReg16(from_reg), 0xFFFF);
+ return;
+ }
+ }
+ break;
+ }
+
+ Panic("Unknown sign-extend combination");
+}
+
+void CodeGenerator::EmitCopyValue(HostReg to_reg, const Value& value)
+{
+ // TODO: mov x, 0 -> xor x, x
+ DebugAssert(value.IsConstant() || value.IsInHostRegister());
+
+ switch (value.size)
+ {
+ case RegSize_8:
+ case RegSize_16:
+ case RegSize_32:
+ {
+ if (value.IsConstant())
+ m_emit->Mov(GetHostReg32(to_reg), value.GetS32ConstantValue());
+ else
+ m_emit->Mov(GetHostReg32(to_reg), GetHostReg32(value.host_reg));
+ }
+ break;
+
+ default:
+ UnreachableCode();
+ break;
+ }
+}
+
+void CodeGenerator::EmitAdd(HostReg to_reg, HostReg from_reg, const Value& value, bool set_flags)
+{
+ Assert(value.IsConstant() || value.IsInHostRegister());
+
+ // if it's in a host register already, this is easy
+ if (value.IsInHostRegister())
+ {
+ if (set_flags)
+ m_emit->adds(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(value.host_reg));
+ else
+ m_emit->add(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(value.host_reg));
+
+ return;
+ }
+
+ // do we need temporary storage for the constant, if it won't fit in an immediate?
+ const s32 constant_value = value.GetS32ConstantValue();
+ if (a32::ImmediateA32::IsImmediateA32(static_cast(constant_value)))
+ {
+ if (set_flags)
+ m_emit->adds(GetHostReg32(to_reg), GetHostReg32(from_reg), constant_value);
+ else
+ m_emit->add(GetHostReg32(to_reg), GetHostReg32(from_reg), constant_value);
+
+ return;
+ }
+
+ // need a temporary
+ m_emit->Mov(GetHostReg32(RSCRATCH), constant_value);
+ if (set_flags)
+ m_emit->adds(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(RSCRATCH));
+ else
+ m_emit->add(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(RSCRATCH));
+}
+
+void CodeGenerator::EmitSub(HostReg to_reg, HostReg from_reg, const Value& value, bool set_flags)
+{
+ Assert(value.IsConstant() || value.IsInHostRegister());
+
+ // if it's in a host register already, this is easy
+ if (value.IsInHostRegister())
+ {
+ if (set_flags)
+ m_emit->subs(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(value.host_reg));
+ else
+ m_emit->sub(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(value.host_reg));
+
+ return;
+ }
+
+ // do we need temporary storage for the constant, if it won't fit in an immediate?
+ const s32 constant_value = value.GetS32ConstantValue();
+ if (a32::ImmediateA32::IsImmediateA32(static_cast(constant_value)))
+ {
+ if (set_flags)
+ m_emit->subs(GetHostReg32(to_reg), GetHostReg32(from_reg), constant_value);
+ else
+ m_emit->sub(GetHostReg32(to_reg), GetHostReg32(from_reg), constant_value);
+
+ return;
+ }
+
+ // need a temporary
+ m_emit->Mov(GetHostReg32(RSCRATCH), constant_value);
+ if (set_flags)
+ m_emit->subs(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(RSCRATCH));
+ else
+ m_emit->sub(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(RSCRATCH));
+}
+
+void CodeGenerator::EmitCmp(HostReg to_reg, const Value& value)
+{
+ Assert(value.IsConstant() || value.IsInHostRegister());
+
+ // if it's in a host register already, this is easy
+ if (value.IsInHostRegister())
+ {
+ m_emit->cmp(GetHostReg32(to_reg), GetHostReg32(value.host_reg));
+ return;
+ }
+
+ // do we need temporary storage for the constant, if it won't fit in an immediate?
+ const s32 constant_value = value.GetS32ConstantValue();
+ if (constant_value >= 0)
+ {
+ if (a32::ImmediateA32::IsImmediateA32(static_cast(constant_value)))
+ {
+ m_emit->cmp(GetHostReg32(to_reg), constant_value);
+ return;
+ }
+ }
+ else
+ {
+ if (a32::ImmediateA32::IsImmediateA32(static_cast(-constant_value)))
+ {
+ m_emit->cmn(GetHostReg32(to_reg), -constant_value);
+ return;
+ }
+ }
+
+ // need a temporary
+ m_emit->Mov(GetHostReg32(RSCRATCH), constant_value);
+ m_emit->cmp(GetHostReg32(to_reg), GetHostReg32(RSCRATCH));
+}
+
+void CodeGenerator::EmitMul(HostReg to_reg_hi, HostReg to_reg_lo, const Value& lhs, const Value& rhs,
+ bool signed_multiply)
+{
+ Value lhs_in_reg = GetValueInHostRegister(lhs);
+ Value rhs_in_reg = GetValueInHostRegister(rhs);
+
+ if (lhs.size < RegSize_64)
+ {
+ if (signed_multiply)
+ {
+ m_emit->smull(GetHostReg32(to_reg_lo), GetHostReg32(to_reg_hi), GetHostReg32(lhs_in_reg.host_reg),
+ GetHostReg32(rhs_in_reg.host_reg));
+ }
+ else
+ {
+ m_emit->umull(GetHostReg32(to_reg_lo), GetHostReg32(to_reg_hi), GetHostReg32(lhs_in_reg.host_reg),
+ GetHostReg32(rhs_in_reg.host_reg));
+ }
+ }
+ else
+ {
+ // TODO: Use mul + smulh
+ Panic("Not implemented");
+ }
+}
+
+void CodeGenerator::EmitDiv(HostReg to_reg_quotient, HostReg to_reg_remainder, HostReg num, HostReg denom, RegSize size,
+ bool signed_divide)
+{
+ // only 32-bit supported for now..
+ Assert(size == RegSize_32);
+
+ Value quotient_value;
+ if (to_reg_quotient == HostReg_Count)
+ quotient_value.SetHostReg(&m_register_cache, RSCRATCH, size);
+ else
+ quotient_value.SetHostReg(&m_register_cache, to_reg_quotient, size);
+
+ if (signed_divide)
+ {
+ m_emit->sdiv(GetHostReg32(quotient_value), GetHostReg32(num), GetHostReg32(denom));
+ if (to_reg_remainder != HostReg_Count)
+ {
+ m_emit->mul(GetHostReg32(to_reg_remainder), GetHostReg32(quotient_value), GetHostReg32(denom));
+ m_emit->sub(GetHostReg32(to_reg_remainder), GetHostReg32(num), GetHostReg32(to_reg_remainder));
+ }
+ }
+ else
+ {
+ m_emit->udiv(GetHostReg32(quotient_value), GetHostReg32(num), GetHostReg32(denom));
+ if (to_reg_remainder != HostReg_Count)
+ {
+ m_emit->mul(GetHostReg32(to_reg_remainder), GetHostReg32(quotient_value), GetHostReg32(denom));
+ m_emit->sub(GetHostReg32(to_reg_remainder), GetHostReg32(num), GetHostReg32(to_reg_remainder));
+ }
+ }
+}
+
+void CodeGenerator::EmitInc(HostReg to_reg, RegSize size)
+{
+ Panic("Not implemented");
+#if 0
+ switch (size)
+ {
+ case RegSize_8:
+ m_emit->inc(GetHostReg8(to_reg));
+ break;
+ case RegSize_16:
+ m_emit->inc(GetHostReg16(to_reg));
+ break;
+ case RegSize_32:
+ m_emit->inc(GetHostReg32(to_reg));
+ break;
+ default:
+ UnreachableCode();
+ break;
+ }
+#endif
+}
+
+void CodeGenerator::EmitDec(HostReg to_reg, RegSize size)
+{
+ Panic("Not implemented");
+#if 0
+ switch (size)
+ {
+ case RegSize_8:
+ m_emit->dec(GetHostReg8(to_reg));
+ break;
+ case RegSize_16:
+ m_emit->dec(GetHostReg16(to_reg));
+ break;
+ case RegSize_32:
+ m_emit->dec(GetHostReg32(to_reg));
+ break;
+ default:
+ UnreachableCode();
+ break;
+ }
+#endif
+}
+
+void CodeGenerator::EmitShl(HostReg to_reg, HostReg from_reg, RegSize size, const Value& amount_value,
+ bool assume_amount_masked)
+{
+ switch (size)
+ {
+ case RegSize_8:
+ case RegSize_16:
+ case RegSize_32:
+ {
+ if (amount_value.IsConstant())
+ {
+ m_emit->lsl(GetHostReg32(to_reg), GetHostReg32(from_reg), static_cast(amount_value.constant_value & 0x1F));
+ }
+ else if (assume_amount_masked)
+ {
+ m_emit->lsl(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(amount_value));
+ }
+ else
+ {
+ m_emit->and_(GetHostReg32(RSCRATCH), GetHostReg32(amount_value), 0x1F);
+ m_emit->lsl(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(RSCRATCH));
+ }
+
+ if (size == RegSize_8)
+ m_emit->and_(GetHostReg32(to_reg), GetHostReg32(from_reg), 0xFF);
+ else if (size == RegSize_16)
+ m_emit->and_(GetHostReg32(to_reg), GetHostReg32(from_reg), 0xFFFF);
+ }
+ break;
+ }
+}
+
+void CodeGenerator::EmitShr(HostReg to_reg, HostReg from_reg, RegSize size, const Value& amount_value,
+ bool assume_amount_masked)
+{
+ switch (size)
+ {
+ case RegSize_8:
+ case RegSize_16:
+ case RegSize_32:
+ {
+ if (amount_value.IsConstant())
+ {
+ m_emit->lsr(GetHostReg32(to_reg), GetHostReg32(from_reg), static_cast(amount_value.constant_value & 0x1F));
+ }
+ else if (assume_amount_masked)
+ {
+ m_emit->lsr(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(amount_value));
+ }
+ else
+ {
+ m_emit->and_(GetHostReg32(RSCRATCH), GetHostReg32(amount_value), 0x1F);
+ m_emit->lsr(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(RSCRATCH));
+ }
+
+ if (size == RegSize_8)
+ m_emit->and_(GetHostReg32(to_reg), GetHostReg32(from_reg), 0xFF);
+ else if (size == RegSize_16)
+ m_emit->and_(GetHostReg32(to_reg), GetHostReg32(from_reg), 0xFFFF);
+ }
+ break;
+ }
+}
+
+void CodeGenerator::EmitSar(HostReg to_reg, HostReg from_reg, RegSize size, const Value& amount_value,
+ bool assume_amount_masked)
+{
+ switch (size)
+ {
+ case RegSize_8:
+ case RegSize_16:
+ case RegSize_32:
+ {
+ if (amount_value.IsConstant())
+ {
+ m_emit->asr(GetHostReg32(to_reg), GetHostReg32(from_reg), static_cast(amount_value.constant_value & 0x1F));
+ }
+ else if (assume_amount_masked)
+ {
+ m_emit->asr(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(amount_value));
+ }
+ else
+ {
+ m_emit->and_(GetHostReg32(RSCRATCH), GetHostReg32(amount_value), 0x1F);
+ m_emit->asr(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(RSCRATCH));
+ }
+
+ if (size == RegSize_8)
+ m_emit->and_(GetHostReg32(to_reg), GetHostReg32(from_reg), 0xFF);
+ else if (size == RegSize_16)
+ m_emit->and_(GetHostReg32(to_reg), GetHostReg32(from_reg), 0xFFFF);
+ }
+ break;
+ }
+}
+
+static bool CanFitInBitwiseImmediate(const Value& value)
+{
+ return a32::ImmediateA32::IsImmediateA32(static_cast(value.constant_value));
+}
+
+void CodeGenerator::EmitAnd(HostReg to_reg, HostReg from_reg, const Value& value)
+{
+ Assert(value.IsConstant() || value.IsInHostRegister());
+
+ // if it's in a host register already, this is easy
+ if (value.IsInHostRegister())
+ {
+ m_emit->and_(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(value.host_reg));
+ return;
+ }
+
+ // do we need temporary storage for the constant, if it won't fit in an immediate?
+ if (CanFitInBitwiseImmediate(value))
+ {
+ m_emit->and_(GetHostReg32(to_reg), GetHostReg32(from_reg), s32(value.constant_value));
+ return;
+ }
+
+ // need a temporary
+ m_emit->Mov(GetHostReg32(RSCRATCH), s32(value.constant_value));
+ m_emit->and_(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(RSCRATCH));
+}
+
+void CodeGenerator::EmitOr(HostReg to_reg, HostReg from_reg, const Value& value)
+{
+ Assert(value.IsConstant() || value.IsInHostRegister());
+
+ // if it's in a host register already, this is easy
+ if (value.IsInHostRegister())
+ {
+ m_emit->orr(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(value.host_reg));
+ return;
+ }
+
+ // do we need temporary storage for the constant, if it won't fit in an immediate?
+ if (CanFitInBitwiseImmediate(value))
+ {
+ m_emit->orr(GetHostReg32(to_reg), GetHostReg32(from_reg), s32(value.constant_value));
+ return;
+ }
+
+ // need a temporary
+ m_emit->Mov(GetHostReg32(RSCRATCH), s32(value.constant_value));
+ m_emit->orr(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(RSCRATCH));
+}
+
+void CodeGenerator::EmitXor(HostReg to_reg, HostReg from_reg, const Value& value)
+{
+ Assert(value.IsConstant() || value.IsInHostRegister());
+
+ // if it's in a host register already, this is easy
+ if (value.IsInHostRegister())
+ {
+ m_emit->eor(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(value.host_reg));
+ return;
+ }
+
+ // do we need temporary storage for the constant, if it won't fit in an immediate?
+ if (CanFitInBitwiseImmediate(value))
+ {
+ m_emit->eor(GetHostReg32(to_reg), GetHostReg32(from_reg), s32(value.constant_value));
+ return;
+ }
+
+ // need a temporary
+ m_emit->Mov(GetHostReg32(RSCRATCH), s32(value.constant_value));
+ m_emit->eor(GetHostReg32(to_reg), GetHostReg32(from_reg), GetHostReg32(RSCRATCH));
+}
+
+void CodeGenerator::EmitTest(HostReg to_reg, const Value& value)
+{
+ Assert(value.IsConstant() || value.IsInHostRegister());
+
+ // if it's in a host register already, this is easy
+ if (value.IsInHostRegister())
+ {
+ m_emit->tst(GetHostReg32(to_reg), GetHostReg32(value.host_reg));
+ return;
+ }
+
+ // do we need temporary storage for the constant, if it won't fit in an immediate?
+ if (CanFitInBitwiseImmediate(value))
+ {
+ m_emit->tst(GetHostReg32(to_reg), s32(value.constant_value));
+ return;
+ }
+
+ // need a temporary
+ m_emit->Mov(GetHostReg32(RSCRATCH), s32(value.constant_value));
+ m_emit->tst(GetHostReg32(to_reg), GetHostReg32(RSCRATCH));
+}
+
+void CodeGenerator::EmitNot(HostReg to_reg, RegSize size)
+{
+ switch (size)
+ {
+ case RegSize_8:
+ m_emit->mvn(GetHostReg8(to_reg), GetHostReg8(to_reg));
+ m_emit->and_(GetHostReg8(to_reg), GetHostReg8(to_reg), 0xFF);
+ break;
+
+ case RegSize_16:
+ m_emit->mvn(GetHostReg16(to_reg), GetHostReg16(to_reg));
+ m_emit->and_(GetHostReg16(to_reg), GetHostReg16(to_reg), 0xFFFF);
+ break;
+
+ case RegSize_32:
+ m_emit->mvn(GetHostReg32(to_reg), GetHostReg32(to_reg));
+ break;
+
+ default:
+ break;
+ }
+}
+
+void CodeGenerator::EmitSetConditionResult(HostReg to_reg, RegSize to_size, Condition condition)
+{
+ if (condition == Condition::Always)
+ {
+ m_emit->Mov(GetHostReg32(to_reg), 1);
+ return;
+ }
+
+ a32::Condition acond(a32::Condition::Never());
+ switch (condition)
+ {
+ case Condition::NotEqual:
+ acond = a32::ne;
+ break;
+
+ case Condition::Equal:
+ acond = a32::eq;
+ break;
+
+ case Condition::Overflow:
+ acond = a32::vs;
+ break;
+
+ case Condition::Greater:
+ acond = a32::gt;
+ break;
+
+ case Condition::GreaterEqual:
+ acond = a32::ge;
+ break;
+
+ case Condition::Less:
+ acond = a32::lt;
+ break;
+
+ case Condition::LessEqual:
+ acond = a32::le;
+ break;
+
+ case Condition::Negative:
+ acond = a32::mi;
+ break;
+
+ case Condition::PositiveOrZero:
+ acond = a32::pl;
+ break;
+
+ case Condition::Above:
+ acond = a32::hi;
+ break;
+
+ case Condition::AboveEqual:
+ acond = a32::cs;
+ break;
+
+ case Condition::Below:
+ acond = a32::cc;
+ break;
+
+ case Condition::BelowEqual:
+ acond = a32::ls;
+ break;
+
+ default:
+ UnreachableCode();
+ return;
+ }
+
+ m_emit->mov(GetHostReg32(to_reg), 0);
+ m_emit->mov(acond, GetHostReg32(to_reg), 1);
+}
+
+u32 CodeGenerator::PrepareStackForCall()
+{
+ m_register_cache.PushCallerSavedRegisters();
+ return 0;
+}
+
+void CodeGenerator::RestoreStackAfterCall(u32 adjust_size)
+{
+ m_register_cache.PopCallerSavedRegisters();
+}
+
+void CodeGenerator::EmitCall(const void* ptr)
+{
+ const s32 displacement = GetPCDisplacement(GetCurrentCodePointer(), ptr);
+ if (!IsPCDisplacementInImmediateRange(displacement))
+ {
+ m_emit->Mov(GetHostReg32(RSCRATCH), reinterpret_cast(ptr));
+ m_emit->blx(GetHostReg32(RSCRATCH));
+ }
+ else
+ {
+ a32::Label label(displacement + m_emit->GetCursorOffset());
+ m_emit->bl(&label);
+ }
+}
+
+void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr)
+{
+ if (return_value)
+ return_value->Discard();
+
+ // shadow space allocate
+ const u32 adjust_size = PrepareStackForCall();
+
+ // actually call the function
+ EmitCall(ptr);
+
+ // shadow space release
+ RestoreStackAfterCall(adjust_size);
+
+ // copy out return value if requested
+ if (return_value)
+ {
+ return_value->Undiscard();
+ EmitCopyValue(return_value->GetHostRegister(), Value::FromHostReg(&m_register_cache, RRETURN, return_value->size));
+ }
+}
+
+void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, const Value& arg1)
+{
+ if (return_value)
+ return_value->Discard();
+
+ // shadow space allocate
+ const u32 adjust_size = PrepareStackForCall();
+
+ // push arguments
+ EmitCopyValue(RARG1, arg1);
+
+ // actually call the function
+ EmitCall(ptr);
+
+ // shadow space release
+ RestoreStackAfterCall(adjust_size);
+
+ // copy out return value if requested
+ if (return_value)
+ {
+ return_value->Undiscard();
+ EmitCopyValue(return_value->GetHostRegister(), Value::FromHostReg(&m_register_cache, RRETURN, return_value->size));
+ }
+}
+
+void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, const Value& arg1, const Value& arg2)
+{
+ if (return_value)
+ return_value->Discard();
+
+ // shadow space allocate
+ const u32 adjust_size = PrepareStackForCall();
+
+ // push arguments
+ EmitCopyValue(RARG1, arg1);
+ EmitCopyValue(RARG2, arg2);
+
+ // actually call the function
+ EmitCall(ptr);
+
+ // shadow space release
+ RestoreStackAfterCall(adjust_size);
+
+ // copy out return value if requested
+ if (return_value)
+ {
+ return_value->Undiscard();
+ EmitCopyValue(return_value->GetHostRegister(), Value::FromHostReg(&m_register_cache, RRETURN, return_value->size));
+ }
+}
+
+void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, const Value& arg1, const Value& arg2,
+ const Value& arg3)
+{
+ if (return_value)
+ m_register_cache.DiscardHostReg(return_value->GetHostRegister());
+
+ // shadow space allocate
+ const u32 adjust_size = PrepareStackForCall();
+
+ // push arguments
+ EmitCopyValue(RARG1, arg1);
+ EmitCopyValue(RARG2, arg2);
+ EmitCopyValue(RARG3, arg3);
+
+ // actually call the function
+ EmitCall(ptr);
+
+ // shadow space release
+ RestoreStackAfterCall(adjust_size);
+
+ // copy out return value if requested
+ if (return_value)
+ {
+ return_value->Undiscard();
+ EmitCopyValue(return_value->GetHostRegister(), Value::FromHostReg(&m_register_cache, RRETURN, return_value->size));
+ }
+}
+
+void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, const Value& arg1, const Value& arg2,
+ const Value& arg3, const Value& arg4)
+{
+ if (return_value)
+ return_value->Discard();
+
+ // shadow space allocate
+ const u32 adjust_size = PrepareStackForCall();
+
+ // push arguments
+ EmitCopyValue(RARG1, arg1);
+ EmitCopyValue(RARG2, arg2);
+ EmitCopyValue(RARG3, arg3);
+ EmitCopyValue(RARG4, arg4);
+
+ // actually call the function
+ EmitCall(ptr);
+
+ // shadow space release
+ RestoreStackAfterCall(adjust_size);
+
+ // copy out return value if requested
+ if (return_value)
+ {
+ return_value->Undiscard();
+ EmitCopyValue(return_value->GetHostRegister(), Value::FromHostReg(&m_register_cache, RRETURN, return_value->size));
+ }
+}
+
+void CodeGenerator::EmitPushHostReg(HostReg reg, u32 position)
+{
+ const a32::MemOperand addr(a32::sp, FUNCTION_STACK_SIZE - FUNCTION_CALL_SHADOW_SPACE - (position * 4));
+ m_emit->str(GetHostReg32(reg), addr);
+}
+
+void CodeGenerator::EmitPushHostRegPair(HostReg reg, HostReg reg2, u32 position)
+{
+ // TODO: Use stm?
+ EmitPushHostReg(reg, position);
+ EmitPushHostReg(reg2, position + 1);
+}
+
+void CodeGenerator::EmitPopHostReg(HostReg reg, u32 position)
+{
+ const a32::MemOperand addr(a32::sp, FUNCTION_STACK_SIZE - FUNCTION_CALL_SHADOW_SPACE - (position * 4));
+ m_emit->ldr(GetHostReg32(reg), addr);
+}
+
+void CodeGenerator::EmitPopHostRegPair(HostReg reg, HostReg reg2, u32 position)
+{
+ // TODO: Use ldm?
+ Assert(position > 0);
+ EmitPopHostReg(reg2, position);
+ EmitPopHostReg(reg, position - 1);
+}
+
+void CodeGenerator::EmitLoadCPUStructField(HostReg host_reg, RegSize guest_size, u32 offset)
+{
+ const s32 s_offset = static_cast(offset);
+
+ switch (guest_size)
+ {
+ case RegSize_8:
+ m_emit->ldrb(GetHostReg8(host_reg), a32::MemOperand(GetCPUPtrReg(), s_offset));
+ break;
+
+ case RegSize_16:
+ m_emit->ldrh(GetHostReg16(host_reg), a32::MemOperand(GetCPUPtrReg(), s_offset));
+ break;
+
+ case RegSize_32:
+ m_emit->ldr(GetHostReg32(host_reg), a32::MemOperand(GetCPUPtrReg(), s_offset));
+ break;
+
+ default:
+ {
+ UnreachableCode();
+ }
+ break;
+ }
+}
+
+void CodeGenerator::EmitStoreCPUStructField(u32 offset, const Value& value)
+{
+ const Value hr_value = GetValueInHostOrScratchRegister(value);
+ const s32 s_offset = static_cast(offset);
+
+ switch (value.size)
+ {
+ case RegSize_8:
+ m_emit->strb(GetHostReg8(hr_value), a32::MemOperand(GetCPUPtrReg(), s_offset));
+ break;
+
+ case RegSize_16:
+ m_emit->strh(GetHostReg16(hr_value), a32::MemOperand(GetCPUPtrReg(), s_offset));
+ break;
+
+ case RegSize_32:
+ m_emit->str(GetHostReg32(hr_value), a32::MemOperand(GetCPUPtrReg(), s_offset));
+ break;
+
+ default:
+ {
+ UnreachableCode();
+ }
+ break;
+ }
+}
+
+void CodeGenerator::EmitAddCPUStructField(u32 offset, const Value& value)
+{
+ DebugAssert(value.IsInHostRegister() || value.IsConstant());
+
+ const s32 s_offset = static_cast(offset);
+ const a32::MemOperand o_offset(GetCPUPtrReg(), s_offset);
+
+ // Don't need to mask here because we're storing back to memory.
+ switch (value.size)
+ {
+ case RegSize_8:
+ {
+ m_emit->Ldrb(GetHostReg8(RARG1), o_offset);
+ if (value.IsConstant())
+ m_emit->Add(GetHostReg8(RARG1), GetHostReg8(RARG1), value.GetS32ConstantValue());
+ else
+ m_emit->Add(GetHostReg8(RARG1), GetHostReg8(RARG1), GetHostReg8(value));
+ m_emit->Strb(GetHostReg8(RARG1), o_offset);
+ }
+ break;
+
+ case RegSize_16:
+ {
+ m_emit->Ldrh(GetHostReg16(RARG1), o_offset);
+ if (value.IsConstant())
+ m_emit->Add(GetHostReg16(RARG1), GetHostReg16(RARG1), value.GetS32ConstantValue());
+ else
+ m_emit->Add(GetHostReg16(RARG1), GetHostReg16(RARG1), GetHostReg16(value));
+ m_emit->Strh(GetHostReg16(RARG1), o_offset);
+ }
+ break;
+
+ case RegSize_32:
+ {
+ m_emit->Ldr(GetHostReg32(RARG1), o_offset);
+ if (value.IsConstant())
+ m_emit->Add(GetHostReg32(RARG1), GetHostReg32(RARG1), value.GetS32ConstantValue());
+ else
+ m_emit->Add(GetHostReg32(RARG1), GetHostReg32(RARG1), GetHostReg32(value));
+ m_emit->Str(GetHostReg32(RARG1), o_offset);
+ }
+ break;
+
+ default:
+ {
+ UnreachableCode();
+ }
+ break;
+ }
+}
+
+void CodeGenerator::EmitLoadGuestMemorySlowmem(const CodeBlockInstruction& cbi, const Value& address, RegSize size,
+ Value& result, bool in_far_code)
+{
+ if (g_settings.cpu_recompiler_memory_exceptions)
+ {
+ // NOTE: This can leave junk in the upper bits
+ switch (size)
+ {
+ case RegSize_8:
+ EmitFunctionCall(&result, &Thunks::ReadMemoryByte, address);
+ break;
+
+ case RegSize_16:
+ EmitFunctionCall(&result, &Thunks::ReadMemoryHalfWord, address);
+ break;
+
+ case RegSize_32:
+ EmitFunctionCall(&result, &Thunks::ReadMemoryWord, address);
+ break;
+
+ default:
+ UnreachableCode();
+ break;
+ }
+
+ m_register_cache.PushState();
+
+ a32::Label load_okay;
+ m_emit->tst(GetHostReg32(1), 1);
+ m_emit->b(a32::ne, &load_okay);
+ EmitBranch(GetCurrentFarCodePointer());
+ m_emit->Bind(&load_okay);
+
+ // load exception path
+ if (!in_far_code)
+ SwitchToFarCode();
+
+ // cause_bits = (-result << 2) | BD | cop_n
+ m_emit->rsb(GetHostReg32(result.host_reg), GetHostReg32(result.host_reg), 0);
+ m_emit->lsl(GetHostReg32(result.host_reg), GetHostReg32(result.host_reg), 2);
+ EmitOr(result.host_reg, result.host_reg,
+ Value::FromConstantU32(Cop0Registers::CAUSE::MakeValueForException(
+ static_cast(0), cbi.is_branch_delay_slot, false, cbi.instruction.cop.cop_n)));
+ EmitFunctionCall(nullptr, static_cast(&CPU::RaiseException), result, GetCurrentInstructionPC());
+
+ EmitExceptionExit();
+
+ if (!in_far_code)
+ SwitchToNearCode();
+
+ m_register_cache.PopState();
+ }
+ else
+ {
+ switch (size)
+ {
+ case RegSize_8:
+ EmitFunctionCall(&result, &Thunks::UncheckedReadMemoryByte, address);
+ break;
+
+ case RegSize_16:
+ EmitFunctionCall(&result, &Thunks::UncheckedReadMemoryHalfWord, address);
+ break;
+
+ case RegSize_32:
+ EmitFunctionCall(&result, &Thunks::UncheckedReadMemoryWord, address);
+ break;
+
+ default:
+ UnreachableCode();
+ break;
+ }
+ }
+}
+
+void CodeGenerator::EmitStoreGuestMemorySlowmem(const CodeBlockInstruction& cbi, const Value& address,
+ const Value& value, bool in_far_code)
+{
+ AddPendingCycles(true);
+
+ Value value_in_hr = GetValueInHostRegister(value);
+
+ if (g_settings.cpu_recompiler_memory_exceptions)
+ {
+ Assert(!in_far_code);
+
+ Value result = m_register_cache.AllocateScratch(RegSize_32);
+ switch (value.size)
+ {
+ case RegSize_8:
+ EmitFunctionCall(&result, &Thunks::WriteMemoryByte, address, value_in_hr);
+ break;
+
+ case RegSize_16:
+ EmitFunctionCall(&result, &Thunks::WriteMemoryHalfWord, address, value_in_hr);
+ break;
+
+ case RegSize_32:
+ EmitFunctionCall(&result, &Thunks::WriteMemoryWord, address, value_in_hr);
+ break;
+
+ default:
+ UnreachableCode();
+ break;
+ }
+
+ m_register_cache.PushState();
+
+ a32::Label store_okay;
+ m_emit->tst(GetHostReg32(result.host_reg), 1);
+ m_emit->b(a32::eq, &store_okay);
+ EmitBranch(GetCurrentFarCodePointer());
+ m_emit->Bind(&store_okay);
+
+ // store exception path
+ if (!in_far_code)
+ SwitchToFarCode();
+
+ // cause_bits = (result << 2) | BD | cop_n
+ m_emit->lsl(GetHostReg32(result.host_reg), GetHostReg32(result.host_reg), 2);
+ EmitOr(result.host_reg, result.host_reg,
+ Value::FromConstantU32(Cop0Registers::CAUSE::MakeValueForException(
+ static_cast(0), cbi.is_branch_delay_slot, false, cbi.instruction.cop.cop_n)));
+ EmitFunctionCall(nullptr, static_cast(&CPU::RaiseException), result, GetCurrentInstructionPC());
+
+ if (!in_far_code)
+ EmitExceptionExit();
+ SwitchToNearCode();
+
+ m_register_cache.PopState();
+ }
+ else
+ {
+ switch (value.size)
+ {
+ case RegSize_8:
+ EmitFunctionCall(nullptr, &Thunks::UncheckedWriteMemoryByte, address, value_in_hr);
+ break;
+
+ case RegSize_16:
+ EmitFunctionCall(nullptr, &Thunks::UncheckedWriteMemoryHalfWord, address, value_in_hr);
+ break;
+
+ case RegSize_32:
+ EmitFunctionCall(nullptr, &Thunks::UncheckedWriteMemoryWord, address, value_in_hr);
+ break;
+
+ default:
+ UnreachableCode();
+ break;
+ }
+ }
+}
+
+void CodeGenerator::EmitLoadGlobal(HostReg host_reg, RegSize size, const void* ptr)
+{
+ EmitLoadGlobalAddress(RSCRATCH, ptr);
+ switch (size)
+ {
+ case RegSize_8:
+ m_emit->Ldrb(GetHostReg8(host_reg), a32::MemOperand(GetHostReg32(RSCRATCH)));
+ break;
+
+ case RegSize_16:
+ m_emit->Ldrh(GetHostReg16(host_reg), a32::MemOperand(GetHostReg32(RSCRATCH)));
+ break;
+
+ case RegSize_32:
+ m_emit->Ldr(GetHostReg32(host_reg), a32::MemOperand(GetHostReg32(RSCRATCH)));
+ break;
+
+ default:
+ UnreachableCode();
+ break;
+ }
+}
+
+void CodeGenerator::EmitStoreGlobal(void* ptr, const Value& value)
+{
+ Value value_in_hr = GetValueInHostRegister(value);
+
+ EmitLoadGlobalAddress(RSCRATCH, ptr);
+ switch (value.size)
+ {
+ case RegSize_8:
+ m_emit->Strb(GetHostReg8(value_in_hr), a32::MemOperand(GetHostReg32(RSCRATCH)));
+ break;
+
+ case RegSize_16:
+ m_emit->Strh(GetHostReg16(value_in_hr), a32::MemOperand(GetHostReg32(RSCRATCH)));
+ break;
+
+ case RegSize_32:
+ m_emit->Str(GetHostReg32(value_in_hr), a32::MemOperand(GetHostReg32(RSCRATCH)));
+ break;
+
+ default:
+ UnreachableCode();
+ break;
+ }
+}
+
+void CodeGenerator::EmitFlushInterpreterLoadDelay()
+{
+ Value reg = Value::FromHostReg(&m_register_cache, 0, RegSize_32);
+ Value value = Value::FromHostReg(&m_register_cache, 1, RegSize_32);
+
+ const a32::MemOperand load_delay_reg(GetCPUPtrReg(), offsetof(State, load_delay_reg));
+ const a32::MemOperand load_delay_value(GetCPUPtrReg(), offsetof(State, load_delay_value));
+ const a32::MemOperand regs_base(GetCPUPtrReg(), offsetof(State, regs.r[0]));
+
+ a32::Label skip_flush;
+
+ // reg = load_delay_reg
+ m_emit->Ldrb(GetHostReg32(reg), load_delay_reg);
+
+ // if load_delay_reg == Reg::count goto skip_flush
+ m_emit->Cmp(GetHostReg32(reg), static_cast(Reg::count));
+ m_emit->B(a32::eq, &skip_flush);
+
+ // value = load_delay_value
+ m_emit->Ldr(GetHostReg32(value), load_delay_value);
+
+ // reg = offset(r[0] + reg << 2)
+ m_emit->Lsl(GetHostReg32(reg), GetHostReg32(reg), 2);
+ m_emit->Add(GetHostReg32(reg), GetHostReg32(reg), offsetof(State, regs.r[0]));
+
+ // r[reg] = value
+ m_emit->Str(GetHostReg32(value), a32::MemOperand(GetCPUPtrReg(), GetHostReg32(reg)));
+
+ // load_delay_reg = Reg::count
+ m_emit->Mov(GetHostReg32(reg), static_cast(Reg::count));
+ m_emit->Strb(GetHostReg32(reg), load_delay_reg);
+
+ m_emit->Bind(&skip_flush);
+}
+
+void CodeGenerator::EmitMoveNextInterpreterLoadDelay()
+{
+ Value reg = Value::FromHostReg(&m_register_cache, 0, RegSize_32);
+ Value value = Value::FromHostReg(&m_register_cache, 1, RegSize_32);
+
+ const a32::MemOperand load_delay_reg(GetCPUPtrReg(), offsetof(State, load_delay_reg));
+ const a32::MemOperand load_delay_value(GetCPUPtrReg(), offsetof(State, load_delay_value));
+ const a32::MemOperand next_load_delay_reg(GetCPUPtrReg(), offsetof(State, next_load_delay_reg));
+ const a32::MemOperand next_load_delay_value(GetCPUPtrReg(), offsetof(State, next_load_delay_value));
+
+ m_emit->ldrb(GetHostReg32(reg), next_load_delay_reg);
+ m_emit->ldr(GetHostReg32(value), next_load_delay_value);
+ m_emit->strb(GetHostReg32(reg), load_delay_reg);
+ m_emit->str(GetHostReg32(value), load_delay_value);
+ m_emit->Mov(GetHostReg32(reg), static_cast(Reg::count));
+ m_emit->strb(GetHostReg32(reg), next_load_delay_reg);
+}
+
+void CodeGenerator::EmitCancelInterpreterLoadDelayForReg(Reg reg)
+{
+ if (!m_load_delay_dirty)
+ return;
+
+ const a32::MemOperand load_delay_reg(GetCPUPtrReg(), offsetof(State, load_delay_reg));
+ Value temp = Value::FromHostReg(&m_register_cache, RSCRATCH, RegSize_8);
+
+ a32::Label skip_cancel;
+
+ // if load_delay_reg != reg goto skip_cancel
+ m_emit->ldrb(GetHostReg8(temp), load_delay_reg);
+ m_emit->cmp(GetHostReg8(temp), static_cast(reg));
+ m_emit->B(a32::ne, &skip_cancel);
+
+ // load_delay_reg = Reg::count
+ m_emit->Mov(GetHostReg8(temp), static_cast(Reg::count));
+ m_emit->strb(GetHostReg8(temp), load_delay_reg);
+
+ m_emit->Bind(&skip_cancel);
+}
+
+void CodeGenerator::EmitBranch(const void* address, bool allow_scratch)
+{
+ const s32 displacement = GetPCDisplacement(GetCurrentCodePointer(), address);
+ if (IsPCDisplacementInImmediateRange(displacement))
+ {
+ a32::Label label(displacement + m_emit->GetCursorOffset());
+ m_emit->b(&label);
+ return;
+ }
+
+ m_emit->Mov(GetHostReg32(RSCRATCH), reinterpret_cast(address));
+ m_emit->bx(GetHostReg32(RSCRATCH));
+}
+
+void CodeGenerator::EmitBranch(LabelType* label)
+{
+ m_emit->b(label);
+}
+
+static a32::Condition TranslateCondition(Condition condition, bool invert)
+{
+ switch (condition)
+ {
+ case Condition::Always:
+ return a32::Condition::None();
+
+ case Condition::NotEqual:
+ case Condition::NotZero:
+ return invert ? a32::eq : a32::ne;
+
+ case Condition::Equal:
+ case Condition::Zero:
+ return invert ? a32::ne : a32::eq;
+
+ case Condition::Overflow:
+ return invert ? a32::vc : a32::vs;
+
+ case Condition::Greater:
+ return invert ? a32::le : a32::gt;
+
+ case Condition::GreaterEqual:
+ return invert ? a32::lt : a32::ge;
+
+ case Condition::Less:
+ return invert ? a32::ge : a32::lt;
+
+ case Condition::LessEqual:
+ return invert ? a32::gt : a32::le;
+
+ case Condition::Negative:
+ return invert ? a32::pl : a32::mi;
+
+ case Condition::PositiveOrZero:
+ return invert ? a32::mi : a32::pl;
+
+ case Condition::Above:
+ return invert ? a32::ls : a32::hi;
+
+ case Condition::AboveEqual:
+ return invert ? a32::cc : a32::cs;
+
+ case Condition::Below:
+ return invert ? a32::cs : a32::cc;
+
+ case Condition::BelowEqual:
+ return invert ? a32::hi : a32::ls;
+
+ default:
+ UnreachableCode();
+ return a32::Condition::Never();
+ }
+}
+
+void CodeGenerator::EmitConditionalBranch(Condition condition, bool invert, HostReg value, RegSize size,
+ LabelType* label)
+{
+ switch (condition)
+ {
+ case Condition::NotEqual:
+ case Condition::Equal:
+ case Condition::Overflow:
+ case Condition::Greater:
+ case Condition::GreaterEqual:
+ case Condition::LessEqual:
+ case Condition::Less:
+ case Condition::Above:
+ case Condition::AboveEqual:
+ case Condition::Below:
+ case Condition::BelowEqual:
+ Panic("Needs a comparison value");
+ return;
+
+ case Condition::Negative:
+ case Condition::PositiveOrZero:
+ {
+ switch (size)
+ {
+ case RegSize_8:
+ m_emit->tst(GetHostReg8(value), GetHostReg8(value));
+ break;
+ case RegSize_16:
+ m_emit->tst(GetHostReg16(value), GetHostReg16(value));
+ break;
+ case RegSize_32:
+ m_emit->tst(GetHostReg32(value), GetHostReg32(value));
+ break;
+ default:
+ UnreachableCode();
+ break;
+ }
+
+ EmitConditionalBranch(condition, invert, label);
+ return;
+ }
+
+ case Condition::NotZero:
+ {
+ switch (size)
+ {
+ case RegSize_8:
+ m_emit->tst(GetHostReg8(value), GetHostReg8(value));
+ m_emit->b(a32::ne, label);
+ break;
+ case RegSize_16:
+ m_emit->tst(GetHostReg8(value), GetHostReg8(value));
+ m_emit->b(a32::ne, label);
+ break;
+ case RegSize_32:
+ m_emit->tst(GetHostReg8(value), GetHostReg8(value));
+ m_emit->b(a32::ne, label);
+ break;
+ default:
+ UnreachableCode();
+ break;
+ }
+
+ return;
+ }
+
+ case Condition::Zero:
+ {
+ switch (size)
+ {
+ case RegSize_8:
+ m_emit->tst(GetHostReg8(value), GetHostReg8(value));
+ m_emit->b(a32::eq, label);
+ break;
+ case RegSize_16:
+ m_emit->tst(GetHostReg8(value), GetHostReg8(value));
+ m_emit->b(a32::eq, label);
+ break;
+ case RegSize_32:
+ m_emit->tst(GetHostReg8(value), GetHostReg8(value));
+ m_emit->b(a32::eq, label);
+ break;
+ default:
+ UnreachableCode();
+ break;
+ }
+
+ return;
+ }
+
+ case Condition::Always:
+ m_emit->b(label);
+ return;
+
+ default:
+ UnreachableCode();
+ return;
+ }
+}
+
+void CodeGenerator::EmitConditionalBranch(Condition condition, bool invert, HostReg lhs, const Value& rhs,
+ LabelType* label)
+{
+ switch (condition)
+ {
+ case Condition::NotEqual:
+ case Condition::Equal:
+ case Condition::Overflow:
+ case Condition::Greater:
+ case Condition::GreaterEqual:
+ case Condition::LessEqual:
+ case Condition::Less:
+ case Condition::Above:
+ case Condition::AboveEqual:
+ case Condition::Below:
+ case Condition::BelowEqual:
+ {
+ EmitCmp(lhs, rhs);
+ EmitConditionalBranch(condition, invert, label);
+ return;
+ }
+
+ case Condition::Negative:
+ case Condition::PositiveOrZero:
+ case Condition::NotZero:
+ case Condition::Zero:
+ {
+ Assert(!rhs.IsValid() || (rhs.IsConstant() && rhs.GetS64ConstantValue() == 0));
+ EmitConditionalBranch(condition, invert, lhs, rhs.size, label);
+ return;
+ }
+
+ case Condition::Always:
+ m_emit->b(label);
+ return;
+
+ default:
+ UnreachableCode();
+ return;
+ }
+}
+
+void CodeGenerator::EmitConditionalBranch(Condition condition, bool invert, LabelType* label)
+{
+ if (condition == Condition::Always)
+ m_emit->b(label);
+ else
+ m_emit->b(TranslateCondition(condition, invert), label);
+}
+
+void CodeGenerator::EmitBranchIfBitClear(HostReg reg, RegSize size, u8 bit, LabelType* label)
+{
+ switch (size)
+ {
+ case RegSize_8:
+ case RegSize_16:
+ case RegSize_32:
+ m_emit->tst(GetHostReg32(reg), static_cast(1u << bit));
+ m_emit->b(a32::eq, label);
+ break;
+
+ default:
+ UnreachableCode();
+ break;
+ }
+}
+
+void CodeGenerator::EmitBindLabel(LabelType* label)
+{
+ m_emit->Bind(label);
+}
+
+void CodeGenerator::EmitLoadGlobalAddress(HostReg host_reg, const void* ptr)
+{
+ m_emit->Mov(GetHostReg32(host_reg), reinterpret_cast(ptr));
+}
+
+CodeCache::DispatcherFunction CodeGenerator::CompileDispatcher()
+{
+ m_emit->sub(a32::sp, a32::sp, FUNCTION_STACK_SIZE);
+ m_register_cache.ReserveCalleeSavedRegisters();
+ const u32 stack_adjust = PrepareStackForCall();
+
+ EmitLoadGlobalAddress(RCPUPTR, &g_state);
+
+ a32::Label frame_done_loop;
+ a32::Label exit_dispatcher;
+ m_emit->Bind(&frame_done_loop);
+
+ // if frame_done goto exit_dispatcher
+ m_emit->ldrb(a32::r0, a32::MemOperand(GetHostReg32(RCPUPTR), offsetof(State, frame_done)));
+ m_emit->tst(a32::r0, 1);
+ m_emit->b(a32::ne, &exit_dispatcher);
+
+ // r0 <- sr
+ a32::Label no_interrupt;
+ m_emit->ldr(a32::r0, a32::MemOperand(GetHostReg32(RCPUPTR), offsetof(State, cop0_regs.sr.bits)));
+
+ // if Iec == 0 then goto no_interrupt
+ m_emit->tst(a32::r0, 1);
+ m_emit->b(a32::eq, &no_interrupt);
+
+ // r1 <- cause
+ // r0 (sr) & cause
+ m_emit->ldr(a32::r1, a32::MemOperand(GetHostReg32(RCPUPTR), offsetof(State, cop0_regs.cause.bits)));
+ m_emit->and_(a32::r0, a32::r0, a32::r1);
+
+ // ((sr & cause) & 0xff00) == 0 goto no_interrupt
+ m_emit->tst(a32::r0, 0xFF00);
+ m_emit->b(a32::eq, &no_interrupt);
+
+ // we have an interrupt
+ EmitCall(reinterpret_cast(&DispatchInterrupt));
+
+ // no interrupt or we just serviced it
+ m_emit->Bind(&no_interrupt);
+
+ // TimingEvents::UpdateCPUDowncount:
+ // r0 <- head event->downcount
+ // downcount <- r0
+ EmitLoadGlobalAddress(0, TimingEvents::GetHeadEventPtr());
+ m_emit->ldr(a32::r0, a32::MemOperand(a32::r0));
+ m_emit->ldr(a32::r0, a32::MemOperand(a32::r0, offsetof(TimingEvent, m_downcount)));
+ m_emit->str(a32::r0, a32::MemOperand(GetHostReg32(RCPUPTR), offsetof(State, downcount)));
+
+ // main dispatch loop
+ a32::Label main_loop;
+ m_emit->Bind(&main_loop);
+ s_dispatcher_return_address = GetCurrentCodePointer();
+
+ // r0 <- pending_ticks
+ // r1 <- downcount
+ m_emit->ldr(a32::r0, a32::MemOperand(GetHostReg32(RCPUPTR), offsetof(State, pending_ticks)));
+ m_emit->ldr(a32::r1, a32::MemOperand(GetHostReg32(RCPUPTR), offsetof(State, downcount)));
+
+ // while downcount < pending_ticks
+ a32::Label downcount_hit;
+ m_emit->cmp(a32::r0, a32::r1);
+ m_emit->b(a32::ge, &downcount_hit);
+
+ // time to lookup the block
+ // r0 <- pc
+ m_emit->Mov(a32::r3, Bus::BIOS_BASE);
+ m_emit->ldr(a32::r0, a32::MemOperand(GetHostReg32(RCPUPTR), offsetof(State, regs.pc)));
+
+ // current_instruction_pc <- pc (eax)
+ m_emit->str(a32::r0, a32::MemOperand(GetHostReg32(RCPUPTR), offsetof(State, current_instruction_pc)));
+
+ // r1 <- (pc & RAM_MASK) >> 2
+ m_emit->and_(a32::r1, a32::r0, Bus::RAM_MASK);
+ m_emit->lsr(a32::r1, a32::r1, 2);
+
+ // r2 <- ((pc & BIOS_MASK) >> 2) + FAST_MAP_RAM_SLOT_COUNT
+ m_emit->and_(a32::r2, a32::r0, Bus::BIOS_MASK);
+ m_emit->lsr(a32::r2, a32::r2, 2);
+ m_emit->add(a32::r2, a32::r2, FAST_MAP_RAM_SLOT_COUNT);
+
+ // if ((r0 (pc) & PHYSICAL_MEMORY_ADDRESS_MASK) >= BIOS_BASE) { use r2 as index }
+ m_emit->and_(a32::r0, a32::r0, PHYSICAL_MEMORY_ADDRESS_MASK);
+ m_emit->cmp(a32::r0, a32::r3);
+ m_emit->mov(a32::ge, a32::r1, a32::r2);
+
+ // ebx contains our index, rax <- fast_map[ebx * 8], rax(), continue
+ EmitLoadGlobalAddress(0, CodeCache::GetFastMapPointer());
+ m_emit->ldr(a32::r0, a32::MemOperand(a32::r0, a32::r1, a32::LSL, 2));
+ m_emit->blx(a32::r0);
+
+ // end while
+ m_emit->Bind(&downcount_hit);
+
+ // check events then for frame done
+ m_emit->ldr(a32::r0, a32::MemOperand(GetHostReg32(RCPUPTR), offsetof(State, pending_ticks)));
+ EmitLoadGlobalAddress(1, TimingEvents::GetHeadEventPtr());
+ m_emit->ldr(a32::r1, a32::MemOperand(a32::r1));
+ m_emit->ldr(a32::r1, a32::MemOperand(a32::r1, offsetof(TimingEvent, m_downcount)));
+ m_emit->cmp(a32::r0, a32::r1);
+ m_emit->b(a32::lt, &frame_done_loop);
+ EmitCall(reinterpret_cast(&TimingEvents::RunEvents));
+ m_emit->b(&frame_done_loop);
+
+ // all done
+ m_emit->Bind(&exit_dispatcher);
+
+ RestoreStackAfterCall(stack_adjust);
+ m_register_cache.PopCalleeSavedRegisters(true);
+ m_emit->add(a32::sp, a32::sp, FUNCTION_STACK_SIZE);
+ m_emit->bx(a32::lr);
+
+ CodeBlock::HostCodePointer ptr;
+ u32 code_size;
+ FinalizeBlock(&ptr, &code_size);
+ Log_DevPrintf("Dispatcher is %u bytes at %p", code_size, ptr);
+ return reinterpret_cast(ptr);
+}
+
+CodeCache::SingleBlockDispatcherFunction CodeGenerator::CompileSingleBlockDispatcher()
+{
+ m_emit->sub(a32::sp, a32::sp, FUNCTION_STACK_SIZE);
+ m_register_cache.ReserveCalleeSavedRegisters();
+ const u32 stack_adjust = PrepareStackForCall();
+
+ EmitLoadGlobalAddress(RCPUPTR, &g_state);
+
+ m_emit->blx(GetHostReg32(RARG1));
+
+ RestoreStackAfterCall(stack_adjust);
+ m_register_cache.PopCalleeSavedRegisters(true);
+ m_emit->add(a32::sp, a32::sp, FUNCTION_STACK_SIZE);
+ m_emit->bx(a32::lr);
+
+ CodeBlock::HostCodePointer ptr;
+ u32 code_size;
+ FinalizeBlock(&ptr, &code_size);
+ Log_DevPrintf("Single block dispatcher is %u bytes at %p", code_size, ptr);
+ return reinterpret_cast(ptr);
+}
+
+} // namespace CPU::Recompiler
diff --git a/src/core/cpu_recompiler_types.h b/src/core/cpu_recompiler_types.h
index 44f8d5dcf..633e28b26 100644
--- a/src/core/cpu_recompiler_types.h
+++ b/src/core/cpu_recompiler_types.h
@@ -12,10 +12,16 @@
#define XBYAK_NO_OP_NAMES 1
#include "xbyak.h"
+#elif defined(CPU_AARCH32)
+
+#include "vixl/aarch32/constants-aarch32.h"
+#include "vixl/aarch32/instructions-aarch32.h"
+#include "vixl/aarch32/macro-assembler-aarch32.h"
+
#elif defined(CPU_AARCH64)
-#include
-#include
+#include "vixl/aarch64/constants-aarch64.h"
+#include "vixl/aarch64/macro-assembler-aarch64.h"
#endif
@@ -83,6 +89,25 @@ constexpr u32 CODE_STORAGE_ALIGNMENT = 4096;
#error Unknown ABI.
#endif
+#elif defined(CPU_AARCH32)
+
+using HostReg = unsigned;
+using CodeEmitter = vixl::aarch32::MacroAssembler;
+using LabelType = vixl::aarch32::Label;
+enum : u32
+{
+ HostReg_Count = vixl::aarch32::kNumberOfRegisters
+};
+constexpr HostReg HostReg_Invalid = static_cast(HostReg_Count);
+constexpr RegSize HostPointerSize = RegSize_32;
+
+// A reasonable "maximum" number of bytes per instruction.
+constexpr u32 MAX_NEAR_HOST_BYTES_PER_INSTRUCTION = 64;
+constexpr u32 MAX_FAR_HOST_BYTES_PER_INSTRUCTION = 128;
+
+// Alignment of code stoarge.
+constexpr u32 CODE_STORAGE_ALIGNMENT = 4096;
+
#elif defined(CPU_AARCH64)
using HostReg = unsigned;