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;