From 16ba4cb9970a4e5f32924b2c57250f750b68d93a Mon Sep 17 00:00:00 2001 From: Yang Liu Date: Tue, 26 May 2026 16:28:53 +0800 Subject: [PATCH] [dynarmic,loongarch64] Initial implementation of register allocator --- src/dynarmic/src/dynarmic/CMakeLists.txt | 6 +- .../backend/loongarch64/a32_address_space.cpp | 3 +- .../src/dynarmic/backend/loongarch64/abi.h | 39 ++ .../backend/loongarch64/emit_context.h | 24 ++ .../backend/loongarch64/emit_loongarch64.cpp | 62 ++- .../backend/loongarch64/emit_loongarch64.h | 17 +- .../backend/loongarch64/reg_alloc.cpp | 359 ++++++++++++++++++ .../dynarmic/backend/loongarch64/reg_alloc.h | 235 ++++++++++++ .../backend/loongarch64/stack_layout.h | 28 ++ 9 files changed, 763 insertions(+), 10 deletions(-) create mode 100644 src/dynarmic/src/dynarmic/backend/loongarch64/abi.h create mode 100644 src/dynarmic/src/dynarmic/backend/loongarch64/emit_context.h create mode 100644 src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.cpp create mode 100644 src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.h create mode 100644 src/dynarmic/src/dynarmic/backend/loongarch64/stack_layout.h diff --git a/src/dynarmic/src/dynarmic/CMakeLists.txt b/src/dynarmic/src/dynarmic/CMakeLists.txt index 37c24109c7..20924d35b9 100644 --- a/src/dynarmic/src/dynarmic/CMakeLists.txt +++ b/src/dynarmic/src/dynarmic/CMakeLists.txt @@ -299,9 +299,11 @@ if ("loongarch64" IN_LIST ARCHITECTURE) target_link_libraries(dynarmic PRIVATE lagoon::lagoon) target_sources(dynarmic PRIVATE + 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/a32_address_space.cpp @@ -309,7 +311,9 @@ if ("loongarch64" IN_LIST ARCHITECTURE) backend/loongarch64/a32_interface.cpp backend/loongarch64/a64_interface.cpp backend/loongarch64/exclusive_monitor.cpp - backend/loongarch64/code_block.h + 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 index 45b0a17727..5f35695311 100644 --- a/src/dynarmic/src/dynarmic/backend/loongarch64/a32_address_space.cpp +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_address_space.cpp @@ -92,7 +92,8 @@ EmittedBlockInfo A32AddressSpace::Emit(IR::Block block) { ClearCache(); } - EmittedBlockInfo block_info = EmitLoongArch64(cb.as, std::move(block)); + EmitConfig emit_conf{}; + EmittedBlockInfo block_info = EmitLoongArch64(cb.as, std::move(block), emit_conf); Link(block_info); return block_info; 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/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 index ca7a47c33d..b37fc3d2b0 100644 --- a/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.cpp +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.cpp @@ -4,21 +4,69 @@ #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 { -EmittedBlockInfo EmitLoongArch64(lagoon_assembler_t& as, [[maybe_unused]] IR::Block block) { +template +void EmitIR(lagoon_assembler_t&, EmitContext&, IR::Inst*) { + ASSERT(false && "Unimplemented opcode"); +} + +template<> +void EmitIR(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst) { + ASSERT(ctx.reg_alloc.IsValueLive(inst)); +} + +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); - // 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(offsetof(A32JitState, regs) + 0 * sizeof(u32))); + for (auto iter = block.instructions.begin(); iter != block.instructions.end(); ++iter) { + IR::Inst* inst = &*iter; - la_addi_d(&as, LA_A0, LA_ZERO, 2); - la_st_w(&as, LA_A0, LA_A1, static_cast(offsetof(A32JitState, regs) + 1 * sizeof(u32))); - la_st_w(&as, LA_A0, LA_A1, static_cast(offsetof(A32JitState, regs) + 15 * sizeof(u32))); + 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, diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.h b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.h index 6a4398eee2..5e8b9192e0 100644 --- a/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.h +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -12,9 +13,16 @@ 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*; @@ -35,6 +43,13 @@ struct EmittedBlockInfo { std::vector relocations; }; -EmittedBlockInfo EmitLoongArch64(lagoon_assembler_t& as, IR::Block block); +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/reg_alloc.cpp b/src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.cpp new file mode 100644 index 0000000000..64af8503ee --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.cpp @@ -0,0 +1,359 @@ +// 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 "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 && !realized && !accumulated_uses && !expected_uses && !uses_this_inst; +} + +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{*this}, Argument{*this}, Argument{*this}, Argument{*this}}; + 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& gpr : gprs) { + gpr.UpdateUses(); + } + for (auto& fpr : fprs) { + fpr.UpdateUses(); + } + for (auto& spill : spills) { + spill.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(gprs.begin(), gprs.end(), is_empty)); + // ASSERT(std::all_of(fprs.begin(), fprs.end(), is_empty)); + // ASSERT(std::all_of(spills.begin(), spills.end(), is_empty)); +} + +template +u32 RegAlloc::GenerateImmediate(const IR::Value& value) { + if constexpr (kind == HostLoc::Kind::Gpr) { + const u32 new_location_index = AllocateRegister(gprs, gpr_order); + SpillGpr(new_location_index); + gprs[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(gprs, gpr_order); + 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; + } + + gprs[new_location_index] = std::exchange(ValueInfo(*current_location), {}); + gprs[new_location_index].realized = true; + return new_location_index; + } else if constexpr (required_kind == HostLoc::Kind::Fpr) { + const u32 new_location_index = AllocateRegister(fprs, fpr_order); + 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; + } + + fprs[new_location_index] = std::exchange(ValueInfo(*current_location), {}); + fprs[new_location_index].realized = true; + return new_location_index; + } else { + UNREACHABLE(); + } +} + +template +u32 RegAlloc::RealizeWriteImpl(const IR::Inst* value) { + if (value == nullptr) { + // Scratch register allocation + if constexpr (required_kind == HostLoc::Kind::Gpr) { + const u32 idx = AllocateRegister(gprs, gpr_order); + SpillGpr(idx); + gprs[idx].SetupScratchLocation(); + return idx; + } else if constexpr (required_kind == HostLoc::Kind::Fpr) { + const u32 idx = AllocateRegister(fprs, fpr_order); + SpillFpr(idx); + fprs[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 constexpr (required_kind == HostLoc::Kind::Gpr) { + const u32 new_location_index = AllocateRegister(gprs, gpr_order); + SpillGpr(new_location_index); + setup_location(gprs[new_location_index]); + return new_location_index; + } else if constexpr (required_kind == HostLoc::Kind::Fpr) { + const u32 new_location_index = AllocateRegister(fprs, fpr_order); + SpillFpr(new_location_index); + setup_location(fprs[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); +template u32 RegAlloc::RealizeWriteImpl(const IR::Inst* value); +template u32 RegAlloc::RealizeWriteImpl(const IR::Inst* value); + +u32 RegAlloc::AllocateRegister(const std::array& regs, const std::vector& order) const { + const auto empty = std::find_if(order.begin(), order.end(), [&](u32 i) { return regs[i].values.empty() && !regs[i].locked; }); + if (empty != order.end()) { + return *empty; + } + + std::vector candidates; + std::copy_if(order.begin(), order.end(), std::back_inserter(candidates), [&](u32 i) { return !regs[i].locked; }); + + // TODO: LRU + std::uniform_int_distribution dis{0, candidates.size() - 1}; + return candidates[dis(rand_gen)]; +} + +void RegAlloc::SpillGpr(u32 index) { + ASSERT(!gprs[index].locked && !gprs[index].realized); + if (gprs[index].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)); + spills[new_location_index] = std::exchange(gprs[index], {}); +} + +void RegAlloc::SpillFpr(u32 index) { + ASSERT(!fprs[index].locked && !fprs[index].realized); + if (fprs[index].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)); + spills[new_location_index] = std::exchange(fprs[index], {}); +} + +u32 RegAlloc::FindFreeSpill() const { + const auto iter = std::find_if(spills.begin(), spills.end(), [](const HostLocInfo& info) { return info.values.empty(); }); + ASSERT(iter != spills.end() && "All spill locations are full"); + return static_cast(iter - spills.begin()); +} + +std::optional RegAlloc::ValueLocation(const IR::Inst* value) const { + const auto contains_value = [value](const HostLocInfo& info) { + return info.Contains(value); + }; + if (const auto iter = std::find_if(gprs.begin(), gprs.end(), contains_value); iter != gprs.end()) { + return HostLoc{HostLoc::Kind::Gpr, static_cast(iter - gprs.begin())}; + } else if (const auto iter = std::find_if(fprs.begin(), fprs.end(), contains_value); iter != fprs.end()) { + return HostLoc{HostLoc::Kind::Fpr, static_cast(iter - fprs.begin())}; + } else if (const auto iter = std::find_if(spills.begin(), spills.end(), contains_value); iter != spills.end()) { + return HostLoc{HostLoc::Kind::Spill, static_cast(iter - spills.begin())}; + } + return std::nullopt; +} + +HostLocInfo& RegAlloc::ValueInfo(HostLoc host_loc) { + switch (host_loc.kind) { + case HostLoc::Kind::Gpr: + return gprs[size_t(host_loc.index)]; + case HostLoc::Kind::Fpr: + return fprs[size_t(host_loc.index)]; + case HostLoc::Kind::Spill: + return spills[size_t(host_loc.index)]; + } + UNREACHABLE(); +} + +HostLocInfo& RegAlloc::ValueInfo(const IR::Inst* value) { + const auto contains_value = [value](const HostLocInfo& info) { + return info.Contains(value); + }; + if (const auto iter = std::find_if(gprs.begin(), gprs.end(), contains_value); iter != gprs.end()) { + return *iter; + } else if (const auto iter = std::find_if(fprs.begin(), fprs.end(), contains_value); iter != fprs.end()) { + return *iter; + } else if (const auto iter = std::find_if(spills.begin(), spills.end(), contains_value); iter != spills.end()) { + return *iter; + } + 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..57a7d72b0f --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.h @@ -0,0 +1,235 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#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(RegAlloc& reg_alloc) + : reg_alloc{reg_alloc} {} + + bool allocated = false; + RegAlloc& reg_alloc; + IR::Value value; +}; + +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; + bool realized = false; + size_t uses_this_inst = 0; + size_t accumulated_uses = 0; + size_t expected_uses = 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{gpr_order}, fpr_order{fpr_order}, rand_gen{std::random_device{}()} {} + + 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); + + void SpillAll(); + + 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); + template + u32 RealizeWriteImpl(const IR::Inst* value); + + u32 AllocateRegister(const std::array& regs, const std::vector& order) const; + 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; + + std::array gprs; + std::array fprs; + std::array spills; + + mutable std::mt19937 rand_gen; +}; + +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)}; + } else { + reg = T{write ? reg_alloc.RealizeWriteImpl(value.GetInst()) : 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