mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-06-06 01:13:45 -04:00
[dynarmic,loongarch64] Initial implementation of register allocator
This commit is contained in:
parent
ffc7910cd3
commit
16ba4cb997
9 changed files with 763 additions and 10 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
39
src/dynarmic/src/dynarmic/backend/loongarch64/abi.h
Normal file
39
src/dynarmic/src/dynarmic/backend/loongarch64/abi.h
Normal file
|
|
@ -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
|
||||
24
src/dynarmic/src/dynarmic/backend/loongarch64/emit_context.h
Normal file
24
src/dynarmic/src/dynarmic/backend/loongarch64/emit_context.h
Normal file
|
|
@ -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
|
||||
|
|
@ -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<IR::Opcode op>
|
||||
void EmitIR(lagoon_assembler_t&, EmitContext&, IR::Inst*) {
|
||||
ASSERT(false && "Unimplemented opcode");
|
||||
}
|
||||
|
||||
template<>
|
||||
void EmitIR<IR::Opcode::GetCarryFromOp>(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<u32>(GPR_ORDER), std::vector<u32>(FPR_ORDER)};
|
||||
EmitContext ctx{block, reg_alloc, emit_conf, 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)));
|
||||
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<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)));
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -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<Relocation> relocations;
|
||||
};
|
||||
|
||||
EmittedBlockInfo EmitLoongArch64(lagoon_assembler_t& as, IR::Block block);
|
||||
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
|
||||
|
|
|
|||
359
src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.cpp
Normal file
359
src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.cpp
Normal file
|
|
@ -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 <algorithm>
|
||||
#include <array>
|
||||
|
||||
#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<HostLoc::Kind kind>
|
||||
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<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(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<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;
|
||||
}
|
||||
|
||||
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<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;
|
||||
}
|
||||
|
||||
fprs[new_location_index] = std::exchange(ValueInfo(*current_location), {});
|
||||
fprs[new_location_index].realized = true;
|
||||
return new_location_index;
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
template<HostLoc::Kind required_kind>
|
||||
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<HostLoc::Kind::Gpr>(const IR::Value& value);
|
||||
template u32 RegAlloc::RealizeReadImpl<HostLoc::Kind::Fpr>(const IR::Value& value);
|
||||
template u32 RegAlloc::RealizeWriteImpl<HostLoc::Kind::Gpr>(const IR::Inst* value);
|
||||
template u32 RegAlloc::RealizeWriteImpl<HostLoc::Kind::Fpr>(const IR::Inst* value);
|
||||
|
||||
u32 RegAlloc::AllocateRegister(const std::array<HostLocInfo, 32>& regs, const std::vector<u32>& 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<u32> candidates;
|
||||
std::copy_if(order.begin(), order.end(), std::back_inserter(candidates), [&](u32 i) { return !regs[i].locked; });
|
||||
|
||||
// TODO: LRU
|
||||
std::uniform_int_distribution<size_t> 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<la_gpr_t>(index), LA_SP,
|
||||
static_cast<int32_t>(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<la_fpr_t>(index), LA_SP,
|
||||
static_cast<int32_t>(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<u32>(iter - spills.begin());
|
||||
}
|
||||
|
||||
std::optional<HostLoc> 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<u32>(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<u32>(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<u32>(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
|
||||
235
src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.h
Normal file
235
src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.h
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#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(RegAlloc& reg_alloc)
|
||||
: reg_alloc{reg_alloc} {}
|
||||
|
||||
bool allocated = false;
|
||||
RegAlloc& reg_alloc;
|
||||
IR::Value value;
|
||||
};
|
||||
|
||||
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;
|
||||
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<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{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<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);
|
||||
|
||||
void SpillAll();
|
||||
|
||||
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);
|
||||
template<HostLoc::Kind kind>
|
||||
u32 RealizeWriteImpl(const IR::Inst* value);
|
||||
|
||||
u32 AllocateRegister(const std::array<HostLocInfo, 32>& regs, const std::vector<u32>& order) const;
|
||||
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;
|
||||
|
||||
std::array<HostLocInfo, 32> gprs;
|
||||
std::array<HostLocInfo, 32> fprs;
|
||||
std::array<HostLocInfo, SpillCount> spills;
|
||||
|
||||
mutable std::mt19937 rand_gen;
|
||||
};
|
||||
|
||||
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<kind>(nullptr)};
|
||||
} else {
|
||||
reg = T{write ? reg_alloc.RealizeWriteImpl<kind>(value.GetInst()) : reg_alloc.RealizeReadImpl<kind>(value)};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Dynarmic::Backend::LoongArch64
|
||||
28
src/dynarmic/src/dynarmic/backend/loongarch64/stack_layout.h
Normal file
28
src/dynarmic/src/dynarmic/backend/loongarch64/stack_layout.h
Normal file
|
|
@ -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
|
||||
Loading…
Add table
Reference in a new issue