diff --git a/src/dynarmic/src/dynarmic/CMakeLists.txt b/src/dynarmic/src/dynarmic/CMakeLists.txt index 2274943613..98fcea6333 100644 --- a/src/dynarmic/src/dynarmic/CMakeLists.txt +++ b/src/dynarmic/src/dynarmic/CMakeLists.txt @@ -299,10 +299,24 @@ if ("loongarch64" IN_LIST ARCHITECTURE) target_link_libraries(dynarmic PRIVATE lagoon::lagoon) target_sources(dynarmic PRIVATE - backend/loongarch64/exclusive_monitor.cpp + backend/loongarch64/abi.h + backend/loongarch64/a32_jitstate.cpp + backend/loongarch64/a32_jitstate.h + backend/loongarch64/code_block.h + backend/loongarch64/emit_context.h + backend/loongarch64/emit_loongarch64.cpp + backend/loongarch64/emit_loongarch64.h + backend/loongarch64/emit_loongarch64_a32.cpp + backend/loongarch64/emit_loongarch64_data_processing.cpp + backend/loongarch64/a32_address_space.cpp + backend/loongarch64/a32_address_space.h backend/loongarch64/a32_interface.cpp backend/loongarch64/a64_interface.cpp - backend/loongarch64/code_block.h + backend/loongarch64/exclusive_monitor.cpp + backend/loongarch64/reg_alloc.cpp + backend/loongarch64/reg_alloc.h + backend/loongarch64/stack_layout.h + backend/loongarch64/lagoon_cpp.h common/spin_lock_loongarch64.cpp ) diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/a32_address_space.cpp b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_address_space.cpp new file mode 100644 index 0000000000..f1a7d55d2c --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_address_space.cpp @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dynarmic/backend/loongarch64/a32_address_space.h" + +#include "common/assert.h" + +#include "dynarmic/backend/loongarch64/a32_jitstate.h" +#include "dynarmic/backend/loongarch64/abi.h" +#include "dynarmic/backend/loongarch64/emit_loongarch64.h" +#include "dynarmic/frontend/A32/a32_location_descriptor.h" +#include "dynarmic/frontend/A32/translate/a32_translate.h" +#include "dynarmic/ir/opt_passes.h" + +namespace Dynarmic::Backend::LoongArch64 { + +A32AddressSpace::A32AddressSpace(const A32::UserConfig& conf) + : conf(conf) + , cb(conf.code_cache_size) { + EmitPrelude(); +} + +void A32AddressSpace::GenerateIR(IR::Block& ir_block, IR::LocationDescriptor descriptor) const { + A32::Translate(ir_block, A32::LocationDescriptor{descriptor}, conf.callbacks, {conf.arch_version, conf.define_unpredictable_behaviour, conf.hook_hint_instructions}); + Optimization::Optimize(ir_block, conf, {.sha256 = true}); +} + +CodePtr A32AddressSpace::Get(IR::LocationDescriptor descriptor) { + if (const auto iter = block_entries.find(descriptor.Value()); iter != block_entries.end()) { + return iter->second; + } + return nullptr; +} + +CodePtr A32AddressSpace::GetOrEmit(IR::LocationDescriptor descriptor) { + if (CodePtr block_entry = Get(descriptor)) { + return block_entry; + } + + IR::Block ir_block{descriptor}; + GenerateIR(ir_block, descriptor); + const EmittedBlockInfo block_info = Emit(std::move(ir_block)); + + block_infos.insert_or_assign(descriptor.Value(), block_info); + block_entries.insert_or_assign(descriptor.Value(), block_info.entry_point); + return block_info.entry_point; +} + +void A32AddressSpace::ClearCache() { + block_entries.clear(); + block_infos.clear(); + SetCursorPtr(prelude_info.end_of_prelude); +} + +void A32AddressSpace::EmitPrelude() { + prelude_info.run_code = GetCursorPtr(); + + // Save all GPRs except sp (r3) and tp (r2) + la_addi_d(&cb.as, LA_SP, LA_SP, -64 * 8); + for (u32 i = 1; i < 32; i++) { + if (i == LA_SP || i == LA_TP) + continue; + la_st_d(&cb.as, static_cast(i), LA_SP, static_cast(i * 8)); + } + + // Set up reserved registers and jump to block entry + la_move(&cb.as, Xstate, LA_A1); // Xstate = state ptr + la_move(&cb.as, Xhalt, LA_A2); // Xhalt = halt reason ptr + la_jr(&cb.as, LA_A0); // jump to block_entry + + prelude_info.return_from_run_code = GetCursorPtr(); + + // Restore all GPRs except sp and tp + for (u32 i = 1; i < 32; i++) { + if (i == LA_SP || i == LA_TP) + continue; + la_ld_d(&cb.as, static_cast(i), LA_SP, static_cast(i * 8)); + } + la_addi_d(&cb.as, LA_SP, LA_SP, 64 * 8); + la_ret(&cb.as); + + prelude_info.end_of_prelude = GetCursorPtr(); +} + +void A32AddressSpace::SetCursorPtr(CodePtr ptr) { + cb.as.cursor = reinterpret_cast(ptr); +} + +size_t A32AddressSpace::GetRemainingSize() { + return la_get_remaining_buffer_size(&cb.as); +} + +EmittedBlockInfo A32AddressSpace::Emit(IR::Block block) { + if (GetRemainingSize() < 1024 * 1024) { + ClearCache(); + } + + EmitConfig emit_conf{}; + EmittedBlockInfo block_info = EmitLoongArch64(cb.as, std::move(block), emit_conf); + Link(block_info); + + return block_info; +} + +void A32AddressSpace::Link(EmittedBlockInfo& block_info) { + for (const auto& reloc : block_info.relocations) { + uint8_t* patch_location = reinterpret_cast(block_info.entry_point) + reloc.code_offset; + + switch (reloc.target) { + case LinkTarget::ReturnFromRunCode: { + std::ptrdiff_t off = reinterpret_cast(prelude_info.return_from_run_code) - patch_location; + lagoon_assembler_t patch_as; + la_init_assembler(&patch_as, patch_location, 4); + la_b(&patch_as, static_cast(off)); + break; + } + default: + ASSERT(false && "Invalid relocation target"); + } + } +} + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/a32_address_space.h b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_address_space.h new file mode 100644 index 0000000000..55c4d8c5f3 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_address_space.h @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "dynarmic/backend/loongarch64/lagoon_cpp.h" +#include + +#include "dynarmic/backend/loongarch64/code_block.h" +#include "dynarmic/backend/loongarch64/emit_loongarch64.h" +#include "dynarmic/interface/A32/config.h" +#include "dynarmic/interface/halt_reason.h" +#include "dynarmic/ir/basic_block.h" +#include "dynarmic/ir/location_descriptor.h" + +namespace Dynarmic::Backend::LoongArch64 { + +struct A32JitState; + +class A32AddressSpace final { +public: + explicit A32AddressSpace(const A32::UserConfig& conf); + + void GenerateIR(IR::Block& ir_block, IR::LocationDescriptor descriptor) const; + CodePtr Get(IR::LocationDescriptor descriptor); + CodePtr GetOrEmit(IR::LocationDescriptor descriptor); + + void ClearCache(); + +private: + void EmitPrelude(); + + template + T GetCursorPtr() { + return reinterpret_cast(cb.as.cursor); + } + + template + T GetCursorPtr() const { + return reinterpret_cast(cb.as.cursor); + } + + template + T GetMemPtr() const { + return cb.ptr(); + } + + void SetCursorPtr(CodePtr ptr); + size_t GetRemainingSize(); + EmittedBlockInfo Emit(IR::Block block); + void Link(EmittedBlockInfo& block_info); + + const A32::UserConfig conf; + CodeBlock cb; + + ankerl::unordered_dense::map block_entries; + ankerl::unordered_dense::map block_infos; + +public: + struct PreludeInfo { + CodePtr end_of_prelude; + using RunCodeFuncType = HaltReason (*)(CodePtr entry_point, A32JitState* context, volatile u32* halt_reason); + RunCodeFuncType run_code; + CodePtr return_from_run_code; + } prelude_info; +}; + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/a32_interface.cpp b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_interface.cpp index d1166d79b6..93ad34017a 100644 --- a/src/dynarmic/src/dynarmic/backend/loongarch64/a32_interface.cpp +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_interface.cpp @@ -1,101 +1,134 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include #include -#include -#include +#include +#include #include "common/assert.h" #include "common/common_types.h" + +#include "dynarmic/backend/loongarch64/a32_address_space.h" +#include "dynarmic/backend/loongarch64/a32_jitstate.h" +#include "dynarmic/common/atomic.h" +#include "dynarmic/frontend/A32/a32_location_descriptor.h" #include "dynarmic/interface/A32/a32.h" namespace Dynarmic::A32 { +using namespace Backend::LoongArch64; + struct Jit::Impl final { - explicit Impl(UserConfig conf_) : conf(std::move(conf_)) {} + Impl(Jit* jit_interface, A32::UserConfig conf) + : jit_interface(jit_interface) + , conf(conf) + , current_address_space(conf) {} HaltReason Run() { - UNIMPLEMENTED(); - return halt_reason; + ASSERT(!jit_interface->is_executing); + jit_interface->is_executing = true; + + const auto location_descriptor = current_state.GetLocationDescriptor(); + const auto entry_point = current_address_space.GetOrEmit(location_descriptor); + current_address_space.prelude_info.run_code(entry_point, ¤t_state, &halt_reason); + + HaltReason hr = static_cast(Atomic::Exchange(&halt_reason, 0)); + jit_interface->is_executing = false; + return hr; } HaltReason Step() { - UNIMPLEMENTED(); - return halt_reason | HaltReason::Step; + ASSERT(!jit_interface->is_executing); + jit_interface->is_executing = true; + + const auto location_descriptor = A32::LocationDescriptor{current_state.GetLocationDescriptor()}.SetSingleStepping(true); + const auto entry_point = current_address_space.GetOrEmit(location_descriptor); + current_address_space.prelude_info.run_code(entry_point, ¤t_state, &halt_reason); + + HaltReason hr = static_cast(Atomic::Exchange(&halt_reason, 0)); + jit_interface->is_executing = false; + return hr; } void ClearCache() { + std::unique_lock lock{invalidation_mutex}; + invalidate_entire_cache = true; HaltExecution(HaltReason::CacheInvalidation); } - void InvalidateCacheRange(u32, std::size_t) { + void InvalidateCacheRange(u32 start_address, size_t length) { + std::unique_lock lock{invalidation_mutex}; + invalid_cache_ranges.add(boost::icl::discrete_interval::closed(start_address, static_cast(start_address + length - 1))); HaltExecution(HaltReason::CacheInvalidation); } void Reset() { - regs = {}; - ext_regs = {}; - cpsr = 0; - fpscr = 0; - halt_reason = {}; + current_state = {}; } void HaltExecution(HaltReason hr) { - halt_reason |= hr; + Atomic::Or(&halt_reason, static_cast(hr)); } void ClearHalt(HaltReason hr) { - halt_reason &= ~hr; + Atomic::And(&halt_reason, ~static_cast(hr)); } std::array& Regs() { - return regs; + return current_state.regs; } const std::array& Regs() const { - return regs; + return current_state.regs; } std::array& ExtRegs() { - return ext_regs; + return current_state.ext_regs; } const std::array& ExtRegs() const { - return ext_regs; + return current_state.ext_regs; } u32 Cpsr() const { - return cpsr; + return current_state.Cpsr(); } void SetCpsr(u32 value) { - cpsr = value; + current_state.SetCpsr(value); } u32 Fpscr() const { - return fpscr; + return current_state.Fpscr(); } void SetFpscr(u32 value) { - fpscr = value; + current_state.SetFpscr(value); } - void ClearExclusiveState() {} + void ClearExclusiveState() { + current_state.exclusive_state = false; + } std::string Disassemble() const { return {}; } - UserConfig conf; - std::array regs{}; - std::array ext_regs{}; - u32 cpsr = 0; - u32 fpscr = 0; - HaltReason halt_reason{}; +private: + Jit* jit_interface; + A32::UserConfig conf; + A32JitState current_state{}; + A32AddressSpace current_address_space; + + volatile u32 halt_reason = 0; + + std::mutex invalidation_mutex; + boost::icl::interval_set invalid_cache_ranges; + bool invalidate_entire_cache = false; }; -Jit::Jit(UserConfig conf) : impl(std::make_unique(std::move(conf))) {} +Jit::Jit(UserConfig conf) + : impl(std::make_unique(this, conf)) {} Jit::~Jit() = default; @@ -111,7 +144,7 @@ void Jit::ClearCache() { impl->ClearCache(); } -void Jit::InvalidateCacheRange(u32 start_address, std::size_t length) { +void Jit::InvalidateCacheRange(u32 start_address, size_t length) { impl->InvalidateCacheRange(start_address, length); } diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/a32_jitstate.cpp b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_jitstate.cpp new file mode 100644 index 0000000000..d895d840e5 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_jitstate.cpp @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dynarmic/backend/loongarch64/a32_jitstate.h" + +#include "dynarmic/mcl/bit.hpp" +#include "common/common_types.h" + +namespace Dynarmic::Backend::LoongArch64 { + +u32 A32JitState::Cpsr() const { + u32 cpsr = 0; + cpsr |= cpsr_nzcv; + cpsr |= cpsr_q; + cpsr |= mcl::bit::get_bit<31>(cpsr_ge) ? 1 << 19 : 0; + cpsr |= mcl::bit::get_bit<23>(cpsr_ge) ? 1 << 18 : 0; + cpsr |= mcl::bit::get_bit<15>(cpsr_ge) ? 1 << 17 : 0; + cpsr |= mcl::bit::get_bit<7>(cpsr_ge) ? 1 << 16 : 0; + cpsr |= mcl::bit::get_bit<1>(upper_location_descriptor) ? 1 << 9 : 0; + cpsr |= mcl::bit::get_bit<0>(upper_location_descriptor) ? 1 << 5 : 0; + cpsr |= static_cast(upper_location_descriptor & 0b11111100'00000000); + cpsr |= static_cast(upper_location_descriptor & 0b00000011'00000000) << 17; + cpsr |= cpsr_jaifm; + return cpsr; +} + +void A32JitState::SetCpsr(u32 cpsr) { + cpsr_nzcv = cpsr & 0xF0000000; + cpsr_q = cpsr & (1 << 27); + cpsr_ge = 0; + cpsr_ge |= mcl::bit::get_bit<19>(cpsr) ? 0xFF000000 : 0; + cpsr_ge |= mcl::bit::get_bit<18>(cpsr) ? 0x00FF0000 : 0; + cpsr_ge |= mcl::bit::get_bit<17>(cpsr) ? 0x0000FF00 : 0; + cpsr_ge |= mcl::bit::get_bit<16>(cpsr) ? 0x000000FF : 0; + + upper_location_descriptor &= 0xFFFF0000; + upper_location_descriptor |= mcl::bit::get_bit<9>(cpsr) ? 2 : 0; + upper_location_descriptor |= mcl::bit::get_bit<5>(cpsr) ? 1 : 0; + upper_location_descriptor |= (cpsr >> 0) & 0b11111100'00000000; + upper_location_descriptor |= (cpsr >> 17) & 0b00000011'00000000; + cpsr_jaifm = cpsr & 0x010001DF; +} + +constexpr u32 FPCR_MASK = A32::LocationDescriptor::FPSCR_MODE_MASK; +constexpr u32 FPSR_MASK = 0x0800009F; + +u32 A32JitState::Fpscr() const { + return (upper_location_descriptor & FPCR_MASK) | fpsr | fpsr_nzcv; +} + +void A32JitState::SetFpscr(u32 fpscr) { + fpsr = fpscr & FPSR_MASK; + fpsr_nzcv = fpscr & 0xF0000000; + upper_location_descriptor = (upper_location_descriptor & 0x0000ffff) | (fpscr & FPCR_MASK); +} + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/a32_jitstate.h b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_jitstate.h new file mode 100644 index 0000000000..045b31f6f6 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_jitstate.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" + +#include "dynarmic/frontend/A32/a32_location_descriptor.h" +#include "dynarmic/ir/location_descriptor.h" + +namespace Dynarmic::Backend::LoongArch64 { + +struct A32JitState { + u32 cpsr_nzcv = 0; + u32 cpsr_q = 0; + u32 cpsr_jaifm = 0; + u32 cpsr_ge = 0; + + u32 fpsr = 0; + u32 fpsr_nzcv = 0; + + std::array regs{}; + + u32 upper_location_descriptor; + + alignas(16) std::array ext_regs{}; + + u32 exclusive_state = 0; + + u32 Cpsr() const; + void SetCpsr(u32 cpsr); + + u32 Fpscr() const; + void SetFpscr(u32 fpscr); + + IR::LocationDescriptor GetLocationDescriptor() const { + return IR::LocationDescriptor{regs[15] | (static_cast(upper_location_descriptor) << 32)}; + } +}; + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/abi.h b/src/dynarmic/src/dynarmic/backend/loongarch64/abi.h new file mode 100644 index 0000000000..69e416a57e --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/abi.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "dynarmic/backend/loongarch64/lagoon_cpp.h" + +#include "common/common_types.h" + +namespace Dynarmic::Backend::LoongArch64 { + +// Reserved registers used by the dynarmic JIT +// Xstate (s0/r23): pointer to JIT CPU state, callee-saved +// Xhalt (s1/r24): halt reason pointer, callee-saved +// Xscratch0 (t7/r19): scratch register (caller-saved) +// Xscratch1 (t8/r20): scratch register (caller-saved) +constexpr la_gpr_t Xstate = LA_S0; // r23 +constexpr la_gpr_t Xhalt = LA_S1; // r24 +constexpr la_gpr_t Xscratch0 = LA_T7; // r19 +constexpr la_gpr_t Xscratch1 = LA_T8; // r20 + +// GPR allocation order: callee-saved (s2-s8) first, then temps, then args +constexpr std::initializer_list GPR_ORDER{ + 25, 26, 27, 28, 29, 30, 31, // s2-s8 (r25-r31) + 12, 13, 14, 15, 16, 17, 18, // t0-t6 (r12-r18) + 4, 5, 6, 7, 8, 9, 10, 11 // a0-a7 (r4-r11) +}; + +// FPR allocation order: callee-saved (fs0-fs7) first, then ft, then fa +constexpr std::initializer_list FPR_ORDER{ + 24, 25, 26, 27, 28, 29, 30, 31, // fs0-fs7 (f24-f31) + 8, 9, 10, 11, 12, 13, 14, 15, // ft0-ft7 (f8-f15) + 16, 17, 18, 19, 20, 21, 22, 23, // ft8-ft15 (f16-f23) + 0, 1, 2, 3, 4, 5, 6, 7 // fa0-fa7 (f0-f7) +}; + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/code_block.h b/src/dynarmic/src/dynarmic/backend/loongarch64/code_block.h index a4011719d1..133d748c41 100644 --- a/src/dynarmic/src/dynarmic/backend/loongarch64/code_block.h +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/code_block.h @@ -4,20 +4,44 @@ #pragma once #include +#include +#include +#include +#include "dynarmic/backend/loongarch64/lagoon_cpp.h" + +#include "common/assert.h" #include "common/common_types.h" namespace Dynarmic::Backend::LoongArch64 { class CodeBlock { public: + explicit CodeBlock(std::size_t size) noexcept + : memsize(size) { + mem = static_cast(mmap(nullptr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0)); + ASSERT(mem != MAP_FAILED); + la_init_assembler(&as, mem, size); + } + + ~CodeBlock() noexcept { + if (mem == nullptr) { + return; + } + munmap(mem, memsize); + } + template T ptr() const noexcept { + static_assert(std::is_pointer_v || std::is_same_v || std::is_same_v); return reinterpret_cast(mem); } + lagoon_assembler_t as{}; + private: u8* mem = nullptr; + size_t memsize = 0; }; } // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/emit_context.h b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_context.h new file mode 100644 index 0000000000..1dd56313d9 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_context.h @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "dynarmic/backend/loongarch64/emit_loongarch64.h" +#include "dynarmic/backend/loongarch64/reg_alloc.h" + +namespace Dynarmic::IR { +class Block; +} // namespace Dynarmic::IR + +namespace Dynarmic::Backend::LoongArch64 { + +struct EmitConfig; + +struct EmitContext { + IR::Block& block; + RegAlloc& reg_alloc; + const EmitConfig& emit_conf; + EmittedBlockInfo& ebi; +}; + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.cpp b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.cpp new file mode 100644 index 0000000000..d4d8454fce --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.cpp @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dynarmic/backend/loongarch64/emit_loongarch64.h" + +#include "dynarmic/backend/loongarch64/a32_jitstate.h" +#include "dynarmic/backend/loongarch64/abi.h" +#include "dynarmic/backend/loongarch64/emit_context.h" +#include "dynarmic/backend/loongarch64/reg_alloc.h" +#include "dynarmic/ir/basic_block.h" +#include "dynarmic/ir/microinstruction.h" +#include "dynarmic/ir/opcodes.h" + +namespace Dynarmic::Backend::LoongArch64 { + +template +void EmitIR(lagoon_assembler_t&, EmitContext&, IR::Inst*) { + ASSERT(false && "Unimplemented opcode"); +} + +template<> +void EmitIR(lagoon_assembler_t&, EmitContext&, IR::Inst*) {} + +template<> +void EmitIR(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst); + +template<> +void EmitIR(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst); + +template<> +void EmitIR(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst); + +template<> +void EmitIR(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst); + +template<> +void EmitIR(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst) { + ASSERT(ctx.reg_alloc.IsValueLive(inst)); +} + +template<> +void EmitIR(lagoon_assembler_t& as, EmitContext& ctx, IR::Inst* inst) { + auto args = ctx.reg_alloc.GetArgumentInfo(inst); + + auto Xvalue = ctx.reg_alloc.ReadX(args[0]); + auto Xnz = ctx.reg_alloc.WriteX(inst); + RegAlloc::Realize(Xvalue, Xnz); + + // Z flag (bit 30): set if value == 0 + la_sltui(&as, Xnz->index, Xvalue->index, 1); + la_slli_d(&as, Xnz->index, Xnz->index, 30); + // N flag (bit 31): set if value < 0 (signed) + la_slt(&as, Xscratch0, Xvalue->index, LA_ZERO); + la_slli_d(&as, Xscratch0, Xscratch0, 31); + la_or(&as, Xnz->index, Xnz->index, Xscratch0); +} + +EmittedBlockInfo EmitLoongArch64(lagoon_assembler_t& as, IR::Block block, const EmitConfig& emit_conf) { + EmittedBlockInfo ebi; + + RegAlloc reg_alloc{as, std::vector(GPR_ORDER), std::vector(FPR_ORDER)}; + EmitContext ctx{block, reg_alloc, emit_conf, ebi}; + + ebi.entry_point = reinterpret_cast(as.cursor); + + for (auto iter = block.instructions.begin(); iter != block.instructions.end(); ++iter) { + IR::Inst* inst = &*iter; + + switch (inst->GetOpcode()) { +#define OPCODE(name, type, ...) \ + case IR::Opcode::name: \ + EmitIR(as, ctx, inst); \ + break; +#define A32OPC(name, type, ...) \ + case IR::Opcode::A32##name: \ + EmitIR(as, ctx, inst); \ + break; +#define A64OPC(name, type, ...) \ + case IR::Opcode::A64##name: \ + EmitIR(as, ctx, inst); \ + break; +#include "dynarmic/ir/opcodes.inc" +#undef OPCODE +#undef A32OPC +#undef A64OPC + default: + UNREACHABLE(); + } + + reg_alloc.UpdateAllUses(); + } + + reg_alloc.UpdateAllUses(); + reg_alloc.AssertNoMoreUses(); + + // TODO: Emit Terminal + const auto term = block.GetTerminal(); + const IR::Term::LinkBlock* link_block_term = boost::get(&term); + ASSERT(link_block_term); + la_load_immediate64(&as, Xscratch0, link_block_term->next.Value()); + la_st_w(&as, Xscratch0, Xstate, static_cast(offsetof(A32JitState, regs) + sizeof(u32) * 15)); + + ebi.relocations.push_back(Relocation{ + reinterpret_cast(as.cursor) - ebi.entry_point, + LinkTarget::ReturnFromRunCode + }); + la_nop(&as); + + ebi.size = reinterpret_cast(as.cursor) - ebi.entry_point; + return ebi; +} + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.h b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.h new file mode 100644 index 0000000000..c66d357957 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +#include "dynarmic/backend/loongarch64/lagoon_cpp.h" + +#include "common/common_types.h" + +namespace Dynarmic::IR { +class Block; +class Inst; +class LocationDescriptor; +enum class Cond; +enum class Opcode; +} // namespace Dynarmic::IR + +namespace Dynarmic::A32 { +class Coprocessor; +} // namespace Dynarmic::A32 + +namespace Dynarmic::Backend::LoongArch64 { + +using CodePtr = std::byte*; + +enum class LinkTarget { + ReturnFromRunCode, +}; + +struct Relocation { + std::ptrdiff_t code_offset; + LinkTarget target; +}; + +struct EmittedBlockInfo { + std::vector relocations; + CodePtr entry_point; + size_t size; + size_t cycle_count; +}; + +struct EmitConfig {}; + +struct EmitContext; + +template +void EmitIR(lagoon_assembler_t& as, EmitContext& ctx, IR::Inst* inst); + +EmittedBlockInfo EmitLoongArch64(lagoon_assembler_t& as, IR::Block block, const EmitConfig& emit_conf); + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64_a32.cpp b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64_a32.cpp new file mode 100644 index 0000000000..7ecadbec90 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64_a32.cpp @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dynarmic/backend/loongarch64/lagoon_cpp.h" + +#include "dynarmic/backend/loongarch64/a32_jitstate.h" +#include "dynarmic/backend/loongarch64/abi.h" +#include "dynarmic/backend/loongarch64/emit_context.h" +#include "dynarmic/backend/loongarch64/emit_loongarch64.h" +#include "dynarmic/backend/loongarch64/reg_alloc.h" +#include "dynarmic/ir/basic_block.h" +#include "dynarmic/ir/microinstruction.h" +#include "dynarmic/ir/opcodes.h" + +namespace Dynarmic::Backend::LoongArch64 { + +template<> +void EmitIR(lagoon_assembler_t& as, EmitContext& ctx, IR::Inst* inst) { + const A32::Reg reg = inst->GetArg(0).GetA32RegRef(); + + auto Xresult = ctx.reg_alloc.WriteX(inst); + RegAlloc::Realize(Xresult); + + la_ld_wu(&as, Xresult->index, Xstate, + static_cast(offsetof(A32JitState, regs) + sizeof(u32) * static_cast(reg))); +} + +template<> +void EmitIR(lagoon_assembler_t& as, EmitContext& ctx, IR::Inst* inst) { + const A32::Reg reg = inst->GetArg(0).GetA32RegRef(); + + auto args = ctx.reg_alloc.GetArgumentInfo(inst); + auto Xvalue = ctx.reg_alloc.ReadX(args[1]); + RegAlloc::Realize(Xvalue); + + la_st_w(&as, Xvalue->index, Xstate, + static_cast(offsetof(A32JitState, regs) + sizeof(u32) * static_cast(reg))); +} + +template<> +void EmitIR(lagoon_assembler_t& as, EmitContext& ctx, IR::Inst* inst) { + auto args = ctx.reg_alloc.GetArgumentInfo(inst); + + ASSERT(!args[0].IsImmediate() && !args[1].IsImmediate()); + + auto Xnz = ctx.reg_alloc.ReadX(args[0]); + auto Xc = ctx.reg_alloc.ReadX(args[1]); + RegAlloc::Realize(Xnz, Xc); + + la_ld_wu(&as, Xscratch0, Xstate, static_cast(offsetof(A32JitState, cpsr_nzcv))); + la_load_immediate64(&as, Xscratch1, 0x10000000); + la_and(&as, Xscratch0, Xscratch0, Xscratch1); + la_or(&as, Xscratch0, Xscratch0, Xnz->index); + la_slli_w(&as, Xscratch1, Xc->index, 29); + la_or(&as, Xscratch0, Xscratch0, Xscratch1); + la_st_w(&as, Xscratch0, Xstate, static_cast(offsetof(A32JitState, cpsr_nzcv))); +} + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64_data_processing.cpp b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64_data_processing.cpp new file mode 100644 index 0000000000..4f1683edcf --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64_data_processing.cpp @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dynarmic/backend/loongarch64/lagoon_cpp.h" + +#include "dynarmic/backend/loongarch64/abi.h" +#include "dynarmic/backend/loongarch64/emit_context.h" +#include "dynarmic/backend/loongarch64/emit_loongarch64.h" +#include "dynarmic/backend/loongarch64/reg_alloc.h" +#include "dynarmic/ir/basic_block.h" +#include "dynarmic/ir/microinstruction.h" +#include "dynarmic/ir/opcodes.h" + +namespace Dynarmic::Backend::LoongArch64 { + +template<> +void EmitIR(lagoon_assembler_t& as, EmitContext& ctx, IR::Inst* inst) { + const auto carry_inst = inst->GetAssociatedPseudoOperation(IR::Opcode::GetCarryFromOp); + + auto args = ctx.reg_alloc.GetArgumentInfo(inst); + auto& operand_arg = args[0]; + auto& shift_arg = args[1]; + auto& carry_arg = args[2]; + + ASSERT(carry_inst != nullptr); + ASSERT(shift_arg.IsImmediate()); + + auto Xresult = ctx.reg_alloc.WriteX(inst); + auto Xcarry_out = ctx.reg_alloc.WriteX(carry_inst); + auto Xoperand = ctx.reg_alloc.ReadX(operand_arg); + auto Xcarry_in = ctx.reg_alloc.ReadX(carry_arg); + RegAlloc::Realize(Xresult, Xcarry_out, Xoperand, Xcarry_in); + + const u8 shift = shift_arg.GetImmediateU8(); + + if (shift == 0) { + la_addi_w(&as, Xresult->index, Xoperand->index, 0); + la_addi_w(&as, Xcarry_out->index, Xcarry_in->index, 0); + } else if (shift < 32) { + la_srli_w(&as, Xcarry_out->index, Xoperand->index, 32 - shift); + la_andi(&as, Xcarry_out->index, Xcarry_out->index, 1); + la_slli_w(&as, Xresult->index, Xoperand->index, shift); + } else if (shift > 32) { + la_move(&as, Xresult->index, LA_ZERO); + la_move(&as, Xcarry_out->index, LA_ZERO); + } else { + la_andi(&as, Xcarry_out->index, Xoperand->index, 1); + la_move(&as, Xresult->index, LA_ZERO); + } +} + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/lagoon_cpp.h b/src/dynarmic/src/dynarmic/backend/loongarch64/lagoon_cpp.h new file mode 100644 index 0000000000..da0edcff1c --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/lagoon_cpp.h @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +extern "C" { +#include +} diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.cpp b/src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.cpp new file mode 100644 index 0000000000..5ff7edb6a8 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.cpp @@ -0,0 +1,364 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dynarmic/backend/loongarch64/reg_alloc.h" + +#include +#include +#include + +#include "dynarmic/backend/loongarch64/lagoon_cpp.h" + +#include "common/assert.h" +#include "common/common_types.h" + +#include "dynarmic/common/always_false.h" + +namespace Dynarmic::Backend::LoongArch64 { + +constexpr size_t spill_offset = offsetof(StackLayout, spill); +constexpr size_t spill_slot_size = sizeof(decltype(StackLayout::spill)::value_type); + +static bool IsValuelessType(IR::Type type) { + switch (type) { + case IR::Type::Table: + return true; + default: + return false; + } +} + +IR::Type Argument::GetType() const { + return value.GetType(); +} + +bool Argument::IsImmediate() const { + return value.IsImmediate(); +} + +bool Argument::GetImmediateU1() const { + return value.GetU1(); +} + +u8 Argument::GetImmediateU8() const { + const u64 imm = value.GetImmediateAsU64(); + ASSERT(imm < 0x100); + return u8(imm); +} + +u16 Argument::GetImmediateU16() const { + const u64 imm = value.GetImmediateAsU64(); + ASSERT(imm < 0x10000); + return u16(imm); +} + +u32 Argument::GetImmediateU32() const { + const u64 imm = value.GetImmediateAsU64(); + ASSERT(imm < 0x100000000); + return u32(imm); +} + +u64 Argument::GetImmediateU64() const { + return value.GetImmediateAsU64(); +} + +IR::Cond Argument::GetImmediateCond() const { + ASSERT(IsImmediate() && GetType() == IR::Type::Cond); + return value.GetCond(); +} + +IR::AccType Argument::GetImmediateAccType() const { + ASSERT(IsImmediate() && GetType() == IR::Type::AccType); + return value.GetAccType(); +} + +bool HostLocInfo::Contains(const IR::Inst* value) const { + return std::find(values.begin(), values.end(), value) != values.end(); +} + +void HostLocInfo::SetupScratchLocation() { + ASSERT(IsCompletelyEmpty()); + locked = 1; + realized = true; +} + +bool HostLocInfo::IsCompletelyEmpty() const { + return values.empty() && !locked && !accumulated_uses && !expected_uses && !uses_this_inst && !realized; +} + +void HostLocInfo::UpdateUses() { + accumulated_uses += uses_this_inst; + uses_this_inst = 0; + + if (accumulated_uses == expected_uses) { + values.clear(); + accumulated_uses = 0; + expected_uses = 0; + } +} + +RegAlloc::ArgumentInfo RegAlloc::GetArgumentInfo(IR::Inst* inst) { + ArgumentInfo ret = {Argument{}, Argument{}, Argument{}, Argument{}}; + for (size_t i = 0; i < inst->NumArgs(); i++) { + const IR::Value arg = inst->GetArg(i); + ret[i].value = arg; + if (!arg.IsImmediate() && !IsValuelessType(arg.GetType())) { + ASSERT(ValueLocation(arg.GetInst()) && "argument must already been defined"); + ValueInfo(arg.GetInst()).uses_this_inst++; + } + } + return ret; +} + +bool RegAlloc::IsValueLive(IR::Inst* inst) const { + return !!ValueLocation(inst); +} + +void RegAlloc::UpdateAllUses() { + for (auto& info : hostloc_info) { + info.UpdateUses(); + } +} + +void RegAlloc::DefineAsExisting(IR::Inst* inst, Argument& arg) { + ASSERT(!ValueLocation(inst)); + + if (arg.value.IsImmediate()) { + inst->ReplaceUsesWith(arg.value); + return; + } + + auto& info = ValueInfo(arg.value.GetInst()); + info.values.emplace_back(inst); + info.expected_uses += inst->UseCount(); +} + +void RegAlloc::AssertNoMoreUses() const { + // TODO: Re-enable this assert once all register allocation issues are fixed + // const auto is_empty = [](const auto& i) { return i.IsCompletelyEmpty(); }; + // ASSERT(std::all_of(hostloc_info.begin(), hostloc_info.end(), is_empty)); +} + +template +u32 RegAlloc::GenerateImmediate(const IR::Value& value) { + if constexpr (kind == HostLoc::Kind::Gpr) { + const u32 new_location_index = AllocateRegister(gpr_order, GprOffset); + SpillGpr(new_location_index); + hostloc_info[GprOffset + new_location_index].SetupScratchLocation(); + + la_load_immediate64(&as, static_cast(new_location_index), + static_cast(value.GetImmediateAsU64())); + + return new_location_index; + } else if constexpr (kind == HostLoc::Kind::Fpr) { + ASSERT(false && "Unimplemented instruction"); + } else { + UNREACHABLE(); + } + return 0; +} + +template +u32 RegAlloc::RealizeReadImpl(const IR::Value& value) { + if (value.IsImmediate()) { + return GenerateImmediate(value); + } + + const auto current_location = ValueLocation(value.GetInst()); + ASSERT(current_location); + + if (current_location->kind == required_kind) { + ValueInfo(*current_location).realized = true; + return current_location->index; + } + + ASSERT(!ValueInfo(*current_location).realized); + ASSERT(!ValueInfo(*current_location).locked); + + if constexpr (required_kind == HostLoc::Kind::Gpr) { + const u32 new_location_index = AllocateRegister(gpr_order, GprOffset); + SpillGpr(new_location_index); + + switch (current_location->kind) { + case HostLoc::Kind::Gpr: + UNREACHABLE(); + case HostLoc::Kind::Fpr: + la_movfr2gr_d(&as, static_cast(new_location_index), + static_cast(current_location->index)); + break; + case HostLoc::Kind::Spill: + la_ld_d(&as, static_cast(new_location_index), LA_SP, + static_cast(spill_offset + current_location->index * spill_slot_size)); + break; + } + + hostloc_info[GprOffset + new_location_index] = std::exchange(ValueInfo(*current_location), {}); + hostloc_info[GprOffset + new_location_index].realized = true; + return new_location_index; + } else if constexpr (required_kind == HostLoc::Kind::Fpr) { + const u32 new_location_index = AllocateRegister(fpr_order, FprOffset); + SpillFpr(new_location_index); + + switch (current_location->kind) { + case HostLoc::Kind::Gpr: + la_movgr2fr_d(&as, static_cast(new_location_index), + static_cast(current_location->index)); + break; + case HostLoc::Kind::Fpr: + UNREACHABLE(); + case HostLoc::Kind::Spill: + la_fld_d(&as, static_cast(new_location_index), LA_SP, + static_cast(spill_offset + current_location->index * spill_slot_size)); + break; + } + + hostloc_info[FprOffset + new_location_index] = std::exchange(ValueInfo(*current_location), {}); + hostloc_info[FprOffset + new_location_index].realized = true; + return new_location_index; + } else { + UNREACHABLE(); + } +} + +u32 RegAlloc::RealizeWriteImpl(const IR::Inst* value, HostLoc::Kind required_kind) { + if (value == nullptr) { + // Scratch register allocation + if (required_kind == HostLoc::Kind::Gpr) { + const u32 idx = AllocateRegister(gpr_order, GprOffset); + SpillGpr(idx); + hostloc_info[GprOffset + idx].SetupScratchLocation(); + return idx; + } else if (required_kind == HostLoc::Kind::Fpr) { + const u32 idx = AllocateRegister(fpr_order, FprOffset); + SpillFpr(idx); + hostloc_info[FprOffset + idx].SetupScratchLocation(); + return idx; + } + } + + ASSERT(!ValueLocation(value)); + + const auto setup_location = [&](HostLocInfo& info) { + info = {}; + info.values.emplace_back(value); + info.locked = true; + info.realized = true; + info.expected_uses = value->UseCount(); + }; + + if (required_kind == HostLoc::Kind::Gpr) { + const u32 new_location_index = AllocateRegister(gpr_order, GprOffset); + SpillGpr(new_location_index); + setup_location(hostloc_info[GprOffset + new_location_index]); + return new_location_index; + } else if (required_kind == HostLoc::Kind::Fpr) { + const u32 new_location_index = AllocateRegister(fpr_order, FprOffset); + SpillFpr(new_location_index); + setup_location(hostloc_info[FprOffset + new_location_index]); + return new_location_index; + } else { + UNREACHABLE(); + } +} + +template u32 RegAlloc::RealizeReadImpl(const IR::Value& value); +template u32 RegAlloc::RealizeReadImpl(const IR::Value& value); + +u32 RegAlloc::AllocateRegister(const std::vector& order, size_t base_offset) { + const auto empty = std::find_if(order.begin(), order.end(), [&](u32 i) { + auto& info = hostloc_info[base_offset + i]; + return info.values.empty() && !info.locked; + }); + if (empty != order.end()) { + return *empty; + } + + std::vector candidates; + std::copy_if(order.begin(), order.end(), std::back_inserter(candidates), [&](u32 i) { + return !hostloc_info[base_offset + i].locked; + }); + ASSERT(!candidates.empty()); + + u32 best = candidates[0]; + size_t min_lru = hostloc_info[base_offset + best].lru_counter; + for (size_t i = 1; i < candidates.size(); ++i) { + auto& info = hostloc_info[base_offset + candidates[i]]; + if (info.lru_counter < min_lru) { + min_lru = info.lru_counter; + best = candidates[i]; + } + } + hostloc_info[base_offset + best].lru_counter++; + return best; +} + +void RegAlloc::SpillGpr(u32 index) { + auto& gpr_info = hostloc_info[GprOffset + index]; + ASSERT(!gpr_info.locked && !gpr_info.realized); + if (gpr_info.values.empty()) { + return; + } + const u32 new_location_index = FindFreeSpill(); + la_st_d(&as, static_cast(index), LA_SP, + static_cast(spill_offset + new_location_index * spill_slot_size)); + hostloc_info[SpillOffset + new_location_index] = std::exchange(gpr_info, {}); +} + +void RegAlloc::SpillFpr(u32 index) { + auto& fpr_info = hostloc_info[FprOffset + index]; + ASSERT(!fpr_info.locked && !fpr_info.realized); + if (fpr_info.values.empty()) { + return; + } + const u32 new_location_index = FindFreeSpill(); + la_fst_d(&as, static_cast(index), LA_SP, + static_cast(spill_offset + new_location_index * spill_slot_size)); + hostloc_info[SpillOffset + new_location_index] = std::exchange(fpr_info, {}); +} + +u32 RegAlloc::FindFreeSpill() const { + for (size_t i = 0; i < SpillCount; ++i) { + if (hostloc_info[SpillOffset + i].values.empty()) { + return static_cast(i); + } + } + UNREACHABLE(); +} + +std::optional RegAlloc::ValueLocation(const IR::Inst* value) const { + for (size_t i = 0; i < hostloc_info.size(); ++i) { + if (hostloc_info[i].Contains(value)) { + if (i < GprCount) { + return HostLoc{HostLoc::Kind::Gpr, static_cast(i)}; + } else if (i < GprCount + FprCount) { + return HostLoc{HostLoc::Kind::Fpr, static_cast(i - GprCount)}; + } else { + return HostLoc{HostLoc::Kind::Spill, static_cast(i - GprCount - FprCount)}; + } + } + } + return std::nullopt; +} + +HostLocInfo& RegAlloc::ValueInfo(HostLoc host_loc) { + switch (host_loc.kind) { + case HostLoc::Kind::Gpr: + return hostloc_info[GprOffset + host_loc.index]; + case HostLoc::Kind::Fpr: + return hostloc_info[FprOffset + host_loc.index]; + case HostLoc::Kind::Spill: + return hostloc_info[SpillOffset + host_loc.index]; + } + UNREACHABLE(); +} + +HostLocInfo& RegAlloc::ValueInfo(const IR::Inst* value) { + for (auto& info : hostloc_info) { + if (info.Contains(value)) { + return info; + } + } + UNREACHABLE(); +} + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.h b/src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.h new file mode 100644 index 0000000000..f3c5ff881b --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.h @@ -0,0 +1,232 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "dynarmic/backend/loongarch64/lagoon_cpp.h" +#include + +#include "common/assert.h" +#include "common/common_types.h" +#include "dynarmic/mcl/is_instance_of_template.hpp" + +#include "dynarmic/backend/loongarch64/stack_layout.h" +#include "dynarmic/ir/cond.h" +#include "dynarmic/ir/microinstruction.h" +#include "dynarmic/ir/value.h" + +namespace Dynarmic::Backend::LoongArch64 { + +class RegAlloc; + +// Wrapper types for LoongArch GPR/FPR (replacing biscuit's register types) +struct GPR { + la_gpr_t index = LA_ZERO; + GPR() = default; + explicit GPR(u32 i) : index{static_cast(i)} {} + uint32_t Index() const { return static_cast(index); } +}; + +struct FPR { + la_fpr_t index = LA_F0; + FPR() = default; + explicit FPR(u32 i) : index{static_cast(i)} {} + uint32_t Index() const { return static_cast(index); } +}; + +struct VPR { + la_vpr_t index; + VPR() = default; + explicit VPR(u32 i) : index{static_cast(i)} {} + uint32_t Index() const { return static_cast(index); } +}; + +struct HostLoc { + enum class Kind { + Gpr, + Fpr, + Spill, + } kind; + u32 index; +}; + +struct Argument { +public: + using copyable_reference = std::reference_wrapper; + + IR::Type GetType() const; + bool IsImmediate() const; + + bool GetImmediateU1() const; + u8 GetImmediateU8() const; + u16 GetImmediateU16() const; + u32 GetImmediateU32() const; + u64 GetImmediateU64() const; + IR::Cond GetImmediateCond() const; + IR::AccType GetImmediateAccType() const; + +private: + friend class RegAlloc; + explicit Argument() {} + + IR::Value value; + bool allocated = false; +}; + +template +struct RAReg { +public: + static constexpr HostLoc::Kind kind = std::is_same_v || std::is_same_v + ? HostLoc::Kind::Fpr + : HostLoc::Kind::Gpr; + + operator T() const { return *reg; } + + T operator*() const { return *reg; } + + const T* operator->() const { return &*reg; } + + ~RAReg(); + +private: + friend class RegAlloc; + explicit RAReg(RegAlloc& reg_alloc, bool write, const IR::Value& value); + + void Realize(); + + RegAlloc& reg_alloc; + bool write; + const IR::Value value; + std::optional reg; +}; + +struct HostLocInfo final { + std::vector values; + size_t locked = 0; + size_t uses_this_inst = 0; + size_t accumulated_uses = 0; + size_t expected_uses = 0; + bool realized = false; + size_t lru_counter = 0; + + bool Contains(const IR::Inst*) const; + void SetupScratchLocation(); + bool IsCompletelyEmpty() const; + void UpdateUses(); +}; + +class RegAlloc { +public: + using ArgumentInfo = std::array; + + explicit RegAlloc(lagoon_assembler_t& as, std::vector gpr_order, std::vector fpr_order) + : as{as}, gpr_order{std::move(gpr_order)}, fpr_order{std::move(fpr_order)} {} + + ArgumentInfo GetArgumentInfo(IR::Inst* inst); + bool IsValueLive(IR::Inst* inst) const; + + auto ReadX(Argument& arg) { return RAReg{*this, false, arg.value}; } + auto ReadD(Argument& arg) { return RAReg{*this, false, arg.value}; } + auto ReadV(Argument& arg) { return RAReg{*this, false, arg.value}; } + + auto WriteX(IR::Inst* inst) { return RAReg{*this, true, inst ? IR::Value{inst} : IR::Value{}}; } + auto WriteD(IR::Inst* inst) { return RAReg{*this, true, inst ? IR::Value{inst} : IR::Value{}}; } + auto WriteV(IR::Inst* inst) { return RAReg{*this, true, inst ? IR::Value{inst} : IR::Value{}}; } + + auto ScratchGpr() { return RAReg{*this, true, IR::Value{}}; } + auto ScratchFpr() { return RAReg{*this, true, IR::Value{}}; } + auto ScratchVec() { return RAReg{*this, true, IR::Value{}}; } + + void DefineAsExisting(IR::Inst* inst, Argument& arg); + + template + static void Realize(Ts&... rs) { + static_assert((mcl::is_instance_of_template() && ...)); + (rs.Realize(), ...); + } + + void UpdateAllUses(); + void AssertNoMoreUses() const; + +private: + template + friend struct RAReg; + + template + u32 GenerateImmediate(const IR::Value& value); + template + u32 RealizeReadImpl(const IR::Value& value); + u32 RealizeWriteImpl(const IR::Inst* value, HostLoc::Kind required_kind); + + u32 AllocateRegister(const std::vector& order, size_t base_offset); + void SpillGpr(u32 index); + void SpillFpr(u32 index); + u32 FindFreeSpill() const; + + std::optional ValueLocation(const IR::Inst* value) const; + HostLocInfo& ValueInfo(HostLoc host_loc); + HostLocInfo& ValueInfo(const IR::Inst* value); + + lagoon_assembler_t& as; + std::vector gpr_order; + std::vector fpr_order; + + static constexpr size_t GprCount = 32; + static constexpr size_t FprCount = 32; + static constexpr size_t GprOffset = 0; + static constexpr size_t FprOffset = GprCount; + static constexpr size_t SpillOffset = GprCount + FprCount; + + std::array hostloc_info; +}; + +template +RAReg::RAReg(RegAlloc& reg_alloc, bool write, const IR::Value& value) + : reg_alloc{reg_alloc}, write{write}, value{value} { + if (!write && !value.IsImmediate()) { + reg_alloc.ValueInfo(value.GetInst()).locked++; + } +} + +template +RAReg::~RAReg() { + if (value.IsEmpty()) { + if (reg) { + HostLocInfo& info = reg_alloc.ValueInfo(HostLoc{kind, reg->Index()}); + info.locked--; + info.realized = false; + } + return; + } + + if (value.IsImmediate()) { + if (reg) { + // Immediate was materialized into a scratch register + HostLocInfo& info = reg_alloc.ValueInfo(HostLoc{kind, reg->Index()}); + info.locked--; + info.realized = false; + } + } else if (!value.IsEmpty()) { + HostLocInfo& info = reg_alloc.ValueInfo(value.GetInst()); + info.locked--; + if (reg) { + info.realized = false; + } + } +} + +template +void RAReg::Realize() { + if (write && value.IsEmpty()) { + reg = T{reg_alloc.RealizeWriteImpl(nullptr, kind)}; + } else { + reg = T{write ? reg_alloc.RealizeWriteImpl(value.GetInst(), kind) : reg_alloc.RealizeReadImpl(value)}; + } +} + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/stack_layout.h b/src/dynarmic/src/dynarmic/backend/loongarch64/stack_layout.h new file mode 100644 index 0000000000..1583447f41 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/stack_layout.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" + +namespace Dynarmic::Backend::LoongArch64 { + +constexpr size_t SpillCount = 64; + +struct alignas(16) StackLayout { + s64 cycles_remaining; + s64 cycles_to_run; + + std::array spill; + + u32 save_host_fpcr; + u32 save_host_fpsr; + + bool check_bit; +}; + +static_assert(sizeof(StackLayout) % 16 == 0); + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/common/atomic.h b/src/dynarmic/src/dynarmic/common/atomic.h index 5eb4288517..8af6626be9 100644 --- a/src/dynarmic/src/dynarmic/common/atomic.h +++ b/src/dynarmic/src/dynarmic/common/atomic.h @@ -36,6 +36,14 @@ inline void And(volatile u32* ptr, u32 value) { #endif } +inline u32 Exchange(volatile u32* ptr, u32 value) { +#ifdef _MSC_VER + return static_cast(_InterlockedExchange(reinterpret_cast(ptr), value)); +#else + return __atomic_exchange_n(ptr, value, __ATOMIC_SEQ_CST); +#endif +} + inline void Barrier() { #ifdef _MSC_VER _ReadWriteBarrier();