[dynarmic,loongarch64] Add initial loongarch64 framework

This commit is contained in:
Yang Liu 2026-05-26 16:23:28 +08:00 committed by crueter
parent aadcc24aac
commit ffc7910cd3
11 changed files with 475 additions and 34 deletions

View file

@ -299,10 +299,18 @@ if ("loongarch64" IN_LIST ARCHITECTURE)
target_link_libraries(dynarmic PRIVATE lagoon::lagoon)
target_sources(dynarmic PRIVATE
backend/loongarch64/exclusive_monitor.cpp
backend/loongarch64/a32_jitstate.cpp
backend/loongarch64/a32_jitstate.h
backend/loongarch64/code_block.h
backend/loongarch64/emit_loongarch64.cpp
backend/loongarch64/emit_loongarch64.h
backend/loongarch64/a32_address_space.cpp
backend/loongarch64/a32_address_space.h
backend/loongarch64/a32_interface.cpp
backend/loongarch64/a64_interface.cpp
backend/loongarch64/exclusive_monitor.cpp
backend/loongarch64/code_block.h
backend/loongarch64/lagoon_cpp.h
common/spin_lock_loongarch64.cpp
)

View file

@ -0,0 +1,119 @@
// 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/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));
}
// Jump to block entry (a0)
la_jr(&cb.as, LA_A0);
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();
}
EmittedBlockInfo block_info = EmitLoongArch64(cb.as, std::move(block));
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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 != nullptr);
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

View file

@ -0,0 +1,33 @@
// 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/ir/basic_block.h"
namespace Dynarmic::Backend::LoongArch64 {
EmittedBlockInfo EmitLoongArch64(lagoon_assembler_t& as, [[maybe_unused]] IR::Block block) {
EmittedBlockInfo ebi;
ebi.entry_point = reinterpret_cast<CodePtr>(as.cursor);
// Dummy code: set regs[0] = 8, regs[1] = 2, regs[15] = 2
la_addi_d(&as, LA_A0, LA_ZERO, 8);
la_st_w(&as, LA_A0, LA_A1, static_cast<int32_t>(offsetof(A32JitState, regs) + 0 * sizeof(u32)));
la_addi_d(&as, LA_A0, LA_ZERO, 2);
la_st_w(&as, LA_A0, LA_A1, static_cast<int32_t>(offsetof(A32JitState, regs) + 1 * sizeof(u32)));
la_st_w(&as, LA_A0, LA_A1, static_cast<int32_t>(offsetof(A32JitState, regs) + 15 * sizeof(u32)));
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

View file

@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <cstddef>
#include <vector>
#include "dynarmic/backend/loongarch64/lagoon_cpp.h"
#include "common/common_types.h"
namespace Dynarmic::IR {
class Block;
class LocationDescriptor;
} // namespace Dynarmic::IR
namespace Dynarmic::Backend::LoongArch64 {
using CodePtr = std::byte*;
enum class LinkTarget {
ReturnFromRunCode,
};
struct Relocation {
std::ptrdiff_t code_offset;
LinkTarget target;
};
struct EmittedBlockInfo {
CodePtr entry_point;
size_t size;
size_t cycle_count;
std::vector<Relocation> relocations;
};
EmittedBlockInfo EmitLoongArch64(lagoon_assembler_t& as, IR::Block block);
} // namespace Dynarmic::Backend::LoongArch64

View file

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

View file

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