Browse Source
[dynarmic, loongarch64] Add minimal toy implementation enough to execute LSLS (#4054)
[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>remotes/1780728215256966520/master
committed by
crueter
No known key found for this signature in database
GPG Key ID: 425ACD2D4830EBC6
18 changed files with 1379 additions and 35 deletions
-
18src/dynarmic/src/dynarmic/CMakeLists.txt
-
123src/dynarmic/src/dynarmic/backend/loongarch64/a32_address_space.cpp
-
68src/dynarmic/src/dynarmic/backend/loongarch64/a32_address_space.h
-
99src/dynarmic/src/dynarmic/backend/loongarch64/a32_interface.cpp
-
57src/dynarmic/src/dynarmic/backend/loongarch64/a32_jitstate.cpp
-
43src/dynarmic/src/dynarmic/backend/loongarch64/a32_jitstate.h
-
39src/dynarmic/src/dynarmic/backend/loongarch64/abi.h
-
24src/dynarmic/src/dynarmic/backend/loongarch64/code_block.h
-
24src/dynarmic/src/dynarmic/backend/loongarch64/emit_context.h
-
113src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.cpp
-
55src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.h
-
59src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64_a32.cpp
-
52src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64_data_processing.cpp
-
8src/dynarmic/src/dynarmic/backend/loongarch64/lagoon_cpp.h
-
364src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.cpp
-
232src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.h
-
28src/dynarmic/src/dynarmic/backend/loongarch64/stack_layout.h
-
8src/dynarmic/src/dynarmic/common/atomic.h
@ -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
|
||||
@ -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 |
||||
@ -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
|
||||
@ -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 |
||||
@ -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 |
||||
@ -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 |
||||
@ -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
|
||||
@ -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 |
||||
@ -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
|
||||
@ -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
|
||||
@ -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> |
||||
|
} |
||||
@ -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
|
||||
@ -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 |
||||
@ -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 |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue