aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorYang Liu <numbksco@gmail.com>2026-06-05 02:32:06 +0200
committercrueter <crueter@eden-emu.dev>2026-06-05 02:32:06 +0200
commit48219f348c8558ce3ecd3a8ac0f1b69185ae844b (patch)
treef9c5224225559a88f1f6db58c8a87bb872044854 /src
parent661346503b73d69bbeb3d65702f3ab423ec67e02 (diff)
[dynarmic, loongarch64] Add minimal toy implementation enough to execute LSLS (#4054)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4054 Reviewed-by: Lizzie <lizzie@eden-emu.dev> Reviewed-by: crueter <crueter@eden-emu.dev>
Diffstat (limited to 'src')
-rw-r--r--src/dynarmic/src/dynarmic/CMakeLists.txt18
-rw-r--r--src/dynarmic/src/dynarmic/backend/loongarch64/a32_address_space.cpp123
-rw-r--r--src/dynarmic/src/dynarmic/backend/loongarch64/a32_address_space.h68
-rw-r--r--src/dynarmic/src/dynarmic/backend/loongarch64/a32_interface.cpp99
-rw-r--r--src/dynarmic/src/dynarmic/backend/loongarch64/a32_jitstate.cpp57
-rw-r--r--src/dynarmic/src/dynarmic/backend/loongarch64/a32_jitstate.h43
-rw-r--r--src/dynarmic/src/dynarmic/backend/loongarch64/abi.h39
-rw-r--r--src/dynarmic/src/dynarmic/backend/loongarch64/code_block.h24
-rw-r--r--src/dynarmic/src/dynarmic/backend/loongarch64/emit_context.h24
-rw-r--r--src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.cpp113
-rw-r--r--src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.h55
-rw-r--r--src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64_a32.cpp59
-rw-r--r--src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64_data_processing.cpp52
-rw-r--r--src/dynarmic/src/dynarmic/backend/loongarch64/lagoon_cpp.h8
-rw-r--r--src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.cpp364
-rw-r--r--src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.h232
-rw-r--r--src/dynarmic/src/dynarmic/backend/loongarch64/stack_layout.h28
-rw-r--r--src/dynarmic/src/dynarmic/common/atomic.h8
18 files changed, 1379 insertions, 35 deletions
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<PreludeInfo::RunCodeFuncType>();
+
+ // 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<la_gpr_t>(i), LA_SP, static_cast<int32_t>(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<CodePtr>();
+
+ // 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<la_gpr_t>(i), LA_SP, static_cast<int32_t>(i * 8));
+ }
+ la_addi_d(&cb.as, LA_SP, LA_SP, 64 * 8);
+ la_ret(&cb.as);
+
+ prelude_info.end_of_prelude = GetCursorPtr<CodePtr>();
+}
+
+void A32AddressSpace::SetCursorPtr(CodePtr ptr) {
+ cb.as.cursor = reinterpret_cast<uint8_t*>(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<uint8_t*>(block_info.entry_point) + reloc.code_offset;
+
+ switch (reloc.target) {
+ case LinkTarget::ReturnFromRunCode: {
+ std::ptrdiff_t off = reinterpret_cast<uint8_t*>(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<int32_t>(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 <ankerl/unordered_dense.h>
+
+#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<typename T>
+ T GetCursorPtr() {
+ return reinterpret_cast<T>(cb.as.cursor);
+ }
+
+ template<typename T>
+ T GetCursorPtr() const {
+ return reinterpret_cast<T>(cb.as.cursor);
+ }
+
+ template<typename T>
+ T GetMemPtr() const {
+ return cb.ptr<T>();
+ }
+
+ 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<u64, CodePtr> block_entries;
+ ankerl::unordered_dense::map<u64, EmittedBlockInfo> 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 <array>
#include <memory>
-#include <string>
-#include <utility>
+#include <mutex>
+#include <boost/icl/interval_set.hpp>
#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, &current_state, &halt_reason);
+
+ HaltReason hr = static_cast<HaltReason>(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, &current_state, &halt_reason);
+
+ HaltReason hr = static_cast<HaltReason>(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<u32>::closed(start_address, static_cast<u32>(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<u32>(hr));
}
void ClearHalt(HaltReason hr) {
- halt_reason &= ~hr;
+ Atomic::And(&halt_reason, ~static_cast<u32>(hr));
}
std::array<u32, 16>& Regs() {
- return regs;
+ return current_state.regs;
}
const std::array<u32, 16>& Regs() const {
- return regs;
+ return current_state.regs;
}
std::array<u32, 64>& ExtRegs() {
- return ext_regs;
+ return current_state.ext_regs;
}
const std::array<u32, 64>& 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<u32, 16> regs{};
- std::array<u32, 64> 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<u32> invalid_cache_ranges;
+ bool invalidate_entire_cache = false;
};
-Jit::Jit(UserConfig conf) : impl(std::make_unique<Impl>(std::move(conf))) {}
+Jit::Jit(UserConfig conf)
+ : impl(std::make_unique<Impl>(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<u32>(upper_location_descriptor & 0b11111100'00000000);
+ cpsr |= static_cast<u32>(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 <array>
+
+#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<u32, 16> regs{};
+
+ u32 upper_location_descriptor;
+
+ alignas(16) std::array<u32, 64> 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<u64>(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 <initializer_list>
+
+#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<u32> 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<u32> 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 <cstdint>
+#include <new>
+#include <type_traits>
+#include <sys/mman.h>
+#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<u8*>(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<typename T>
T ptr() const noexcept {
+ static_assert(std::is_pointer_v<T> || std::is_same_v<T, std::uintptr_t> || std::is_same_v<T, std::intptr_t>);
return reinterpret_cast<T>(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<IR::Opcode op>
+void EmitIR(lagoon_assembler_t&, EmitContext&, IR::Inst*) {
+ ASSERT(false && "Unimplemented opcode");
+}
+
+template<>
+void EmitIR<IR::Opcode::Void>(lagoon_assembler_t&, EmitContext&, IR::Inst*) {}
+
+template<>
+void EmitIR<IR::Opcode::A32GetRegister>(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst);
+
+template<>
+void EmitIR<IR::Opcode::A32SetRegister>(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst);
+
+template<>
+void EmitIR<IR::Opcode::A32SetCpsrNZC>(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst);
+
+template<>
+void EmitIR<IR::Opcode::LogicalShiftLeft32>(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst);
+
+template<>
+void EmitIR<IR::Opcode::GetCarryFromOp>(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst) {
+ ASSERT(ctx.reg_alloc.IsValueLive(inst));
+}
+
+template<>
+void EmitIR<IR::Opcode::GetNZFromOp>(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<u32>(GPR_ORDER), std::vector<u32>(FPR_ORDER)};
+ EmitContext ctx{block, reg_alloc, emit_conf, ebi};
+
+ ebi.entry_point = reinterpret_cast<CodePtr>(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<IR::Opcode::name>(as, ctx, inst); \
+ break;
+#define A32OPC(name, type, ...) \
+ case IR::Opcode::A32##name: \
+ EmitIR<IR::Opcode::A32##name>(as, ctx, inst); \
+ break;
+#define A64OPC(name, type, ...) \
+ case IR::Opcode::A64##name: \
+ EmitIR<IR::Opcode::A64##name>(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<IR::Term::LinkBlock>(&term);
+ ASSERT(link_block_term);
+ la_load_immediate64(&as, Xscratch0, link_block_term->next.Value());
+ la_st_w(&as, Xscratch0, Xstate, static_cast<int32_t>(offsetof(A32JitState, regs) + sizeof(u32) * 15));
+
+ ebi.relocations.push_back(Relocation{
+ reinterpret_cast<CodePtr>(as.cursor) - ebi.entry_point,
+ LinkTarget::ReturnFromRunCode
+ });
+ la_nop(&as);
+
+ ebi.size = reinterpret_cast<CodePtr>(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 <array>
+#include <cstddef>
+#include <vector>
+
+#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<Relocation> relocations;
+ CodePtr entry_point;
+ size_t size;
+ size_t cycle_count;
+};
+
+struct EmitConfig {};
+
+struct EmitContext;
+
+template<IR::Opcode op>
+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<IR::Opcode::A32GetRegister>(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<int32_t>(offsetof(A32JitState, regs) + sizeof(u32) * static_cast<size_t>(reg)));
+}
+
+template<>
+void EmitIR<IR::Opcode::A32SetRegister>(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<int32_t>(offsetof(A32JitState, regs) + sizeof(u32) * static_cast<size_t>(reg)));
+}
+
+template<>
+void EmitIR<IR::Opcode::A32SetCpsrNZC>(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<int32_t>(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<int32_t>(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<IR::Opcode::LogicalShiftLeft32>(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 <lagoon.h>
+}
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 <algorithm>
+#include <array>
+#include <limits>
+
+#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<HostLoc::Kind kind>
+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<la_gpr_t>(new_location_index),
+ static_cast<int64_t>(value.GetImmediateAsU64()));
+
+ return new_location_index;
+ } else if constexpr (kind == HostLoc::Kind::Fpr) {
+ ASSERT(false && "Unimplemented instruction");
+ } else {
+ UNREACHABLE();
+ }
+ return 0;
+}
+
+template<HostLoc::Kind required_kind>
+u32 RegAlloc::RealizeReadImpl(const IR::Value& value) {
+ if (value.IsImmediate()) {
+ return GenerateImmediate<required_kind>(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<la_gpr_t>(new_location_index),
+ static_cast<la_fpr_t>(current_location->index));
+ break;
+ case HostLoc::Kind::Spill:
+ la_ld_d(&as, static_cast<la_gpr_t>(new_location_index), LA_SP,
+ static_cast<int32_t>(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<la_fpr_t>(new_location_index),
+ static_cast<la_gpr_t>(current_location->index));
+ break;
+ case HostLoc::Kind::Fpr:
+ UNREACHABLE();
+ case HostLoc::Kind::Spill:
+ la_fld_d(&as, static_cast<la_fpr_t>(new_location_index), LA_SP,
+ static_cast<int32_t>(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<HostLoc::Kind::Gpr>(const IR::Value& value);
+template u32 RegAlloc::RealizeReadImpl<HostLoc::Kind::Fpr>(const IR::Value& value);
+
+u32 RegAlloc::AllocateRegister(const std::vector<u32>& 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<u32> 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<la_gpr_t>(index), LA_SP,
+ static_cast<int32_t>(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<la_fpr_t>(index), LA_SP,
+ static_cast<int32_t>(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<u32>(i);
+ }
+ }
+ UNREACHABLE();
+}
+
+std::optional<HostLoc> 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<u32>(i)};
+ } else if (i < GprCount + FprCount) {
+ return HostLoc{HostLoc::Kind::Fpr, static_cast<u32>(i - GprCount)};
+ } else {
+ return HostLoc{HostLoc::Kind::Spill, static_cast<u32>(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 <array>
+#include <optional>
+#include <utility>
+#include <vector>
+
+#include "dynarmic/backend/loongarch64/lagoon_cpp.h"
+#include <ankerl/unordered_dense.h>
+
+#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<la_gpr_t>(i)} {}
+ uint32_t Index() const { return static_cast<uint32_t>(index); }
+};
+
+struct FPR {
+ la_fpr_t index = LA_F0;
+ FPR() = default;
+ explicit FPR(u32 i) : index{static_cast<la_fpr_t>(i)} {}
+ uint32_t Index() const { return static_cast<uint32_t>(index); }
+};
+
+struct VPR {
+ la_vpr_t index;
+ VPR() = default;
+ explicit VPR(u32 i) : index{static_cast<la_vpr_t>(i)} {}
+ uint32_t Index() const { return static_cast<uint32_t>(index); }
+};
+
+struct HostLoc {
+ enum class Kind {
+ Gpr,
+ Fpr,
+ Spill,
+ } kind;
+ u32 index;
+};
+
+struct Argument {
+public:
+ using copyable_reference = std::reference_wrapper<Argument>;
+
+ 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<typename T>
+struct RAReg {
+public:
+ static constexpr HostLoc::Kind kind = std::is_same_v<T, FPR> || std::is_same_v<T, VPR>
+ ? 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<T> reg;
+};
+
+struct HostLocInfo final {
+ std::vector<const IR::Inst*> 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<Argument, IR::max_arg_count>;
+
+ explicit RegAlloc(lagoon_assembler_t& as, std::vector<u32> gpr_order, std::vector<u32> 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<GPR>{*this, false, arg.value}; }
+ auto ReadD(Argument& arg) { return RAReg<FPR>{*this, false, arg.value}; }
+ auto ReadV(Argument& arg) { return RAReg<VPR>{*this, false, arg.value}; }
+
+ auto WriteX(IR::Inst* inst) { return RAReg<GPR>{*this, true, inst ? IR::Value{inst} : IR::Value{}}; }
+ auto WriteD(IR::Inst* inst) { return RAReg<FPR>{*this, true, inst ? IR::Value{inst} : IR::Value{}}; }
+ auto WriteV(IR::Inst* inst) { return RAReg<VPR>{*this, true, inst ? IR::Value{inst} : IR::Value{}}; }
+
+ auto ScratchGpr() { return RAReg<GPR>{*this, true, IR::Value{}}; }
+ auto ScratchFpr() { return RAReg<FPR>{*this, true, IR::Value{}}; }
+ auto ScratchVec() { return RAReg<VPR>{*this, true, IR::Value{}}; }
+
+ void DefineAsExisting(IR::Inst* inst, Argument& arg);
+
+ template<typename... Ts>
+ static void Realize(Ts&... rs) {
+ static_assert((mcl::is_instance_of_template<RAReg, Ts>() && ...));
+ (rs.Realize(), ...);
+ }
+
+ void UpdateAllUses();
+ void AssertNoMoreUses() const;
+
+private:
+ template<typename>
+ friend struct RAReg;
+
+ template<HostLoc::Kind kind>
+ u32 GenerateImmediate(const IR::Value& value);
+ template<HostLoc::Kind kind>
+ u32 RealizeReadImpl(const IR::Value& value);
+ u32 RealizeWriteImpl(const IR::Inst* value, HostLoc::Kind required_kind);
+
+ u32 AllocateRegister(const std::vector<u32>& order, size_t base_offset);
+ void SpillGpr(u32 index);
+ void SpillFpr(u32 index);
+ u32 FindFreeSpill() const;
+
+ std::optional<HostLoc> ValueLocation(const IR::Inst* value) const;
+ HostLocInfo& ValueInfo(HostLoc host_loc);
+ HostLocInfo& ValueInfo(const IR::Inst* value);
+
+ lagoon_assembler_t& as;
+ std::vector<u32> gpr_order;
+ std::vector<u32> 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<HostLocInfo, GprCount + FprCount + SpillCount> hostloc_info;
+};
+
+template<typename T>
+RAReg<T>::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<typename T>
+RAReg<T>::~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<typename T>
+void RAReg<T>::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<kind>(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 <array>
+
+#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<u64, SpillCount> 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<u32>(_InterlockedExchange(reinterpret_cast<volatile long*>(ptr), value));
+#else
+ return __atomic_exchange_n(ptr, value, __ATOMIC_SEQ_CST);
+#endif
+}
+
inline void Barrier() {
#ifdef _MSC_VER
_ReadWriteBarrier();