backend/rv64: Add minimal toy implementation enough to execute LSLS

This commit is contained in:
Yang Liu 2024-01-19 14:24:10 +08:00 committed by Merry
parent 62ff78d527
commit f856ac9f33
7 changed files with 251 additions and 38 deletions

View file

@ -405,6 +405,7 @@ if ("riscv" IN_LIST ARCHITECTURE)
target_sources(dynarmic PRIVATE target_sources(dynarmic PRIVATE
backend/riscv64/abi.h backend/riscv64/abi.h
backend/riscv64/emit_context.h backend/riscv64/emit_context.h
backend/riscv64/emit_riscv64_data_processing.cpp
backend/riscv64/emit_riscv64.cpp backend/riscv64/emit_riscv64.cpp
backend/riscv64/emit_riscv64.h backend/riscv64/emit_riscv64.h
backend/riscv64/reg_alloc.cpp backend/riscv64/reg_alloc.cpp
@ -421,6 +422,7 @@ if ("riscv" IN_LIST ARCHITECTURE)
backend/riscv64/a32_jitstate.cpp backend/riscv64/a32_jitstate.cpp
backend/riscv64/a32_jitstate.h backend/riscv64/a32_jitstate.h
backend/riscv64/code_block.h backend/riscv64/code_block.h
backend/riscv64/emit_riscv64_a32.cpp
) )
endif() endif()

View file

@ -78,7 +78,7 @@ void A32AddressSpace::EmitPrelude() {
as.SD(GPR{i}, i * 8, sp); as.SD(GPR{i}, i * 8, sp);
} }
for (u32 i = 0; i < 32; i += 1) { for (u32 i = 0; i < 32; i += 1) {
as.FSD(FPR{i}, 32 + i * 8, sp); as.FSD(FPR{i}, (32 + i) * 8, sp);
} }
as.ADDI(Xstate, a1, 0); as.ADDI(Xstate, a1, 0);
@ -92,7 +92,7 @@ void A32AddressSpace::EmitPrelude() {
as.LD(GPR{i}, i * 8, sp); as.LD(GPR{i}, i * 8, sp);
} }
for (u32 i = 0; i < 32; i += 1) { for (u32 i = 0; i < 32; i += 1) {
as.FLD(FPR{i}, 32 + i * 8, sp); as.FLD(FPR{i}, (32 + i) * 8, sp);
} }
as.ADDI(sp, sp, 64 * 8); as.ADDI(sp, sp, 64 * 8);
as.JALR(ra); as.JALR(ra);
@ -128,7 +128,7 @@ void A32AddressSpace::Link(EmittedBlockInfo& block_info) {
switch (target) { switch (target) {
case LinkTarget::ReturnFromRunCode: { case LinkTarget::ReturnFromRunCode: {
std::ptrdiff_t off = prelude_info.return_from_run_code - GetCursorPtr<CodePtr>(); std::ptrdiff_t off = prelude_info.return_from_run_code - reinterpret_cast<CodePtr>(a.GetCursorPointer());
a.JAL(x0, off); a.JAL(x0, off);
break; break;
} }

View file

@ -22,7 +22,7 @@
namespace Dynarmic::Backend::RV64 { namespace Dynarmic::Backend::RV64 {
// TODO: We should really move this to biscuit. // TODO: We should really move this to biscuit.
static void Mov64(biscuit::Assembler& as, biscuit::GPR rd, u64 imm) { void Mov64(biscuit::Assembler& as, biscuit::GPR rd, u64 imm) {
if (mcl::bit::sign_extend<32>(imm) == imm) { if (mcl::bit::sign_extend<32>(imm) == imm) {
as.LI(rd, static_cast<u32>(imm)); as.LI(rd, static_cast<u32>(imm));
return; return;
@ -38,7 +38,7 @@ static void Mov64(biscuit::Assembler& as, biscuit::GPR rd, u64 imm) {
int shift = 12 + std::countr_zero(hi52); int shift = 12 + std::countr_zero(hi52);
hi52 = mcl::bit::sign_extend(shift, hi52 >> (shift - 12)); hi52 = mcl::bit::sign_extend(shift, hi52 >> (shift - 12));
Mov64(as, rd, hi52); Mov64(as, rd, hi52);
as.SLLI(rd, rd, shift); as.SLLI64(rd, rd, shift);
if (lo12 != 0) { if (lo12 != 0) {
as.ADDI(rd, rd, lo12); as.ADDI(rd, rd, lo12);
} }
@ -49,11 +49,38 @@ void EmitIR(biscuit::Assembler&, EmitContext&, IR::Inst*) {
ASSERT_FALSE("Unimplemented opcode {}", op); ASSERT_FALSE("Unimplemented opcode {}", op);
} }
template<>
void EmitIR<IR::Opcode::Void>(biscuit::Assembler&, EmitContext&, IR::Inst*) {}
template<>
void EmitIR<IR::Opcode::A32GetRegister>(biscuit::Assembler& as, EmitContext& ctx, IR::Inst* inst);
template<>
void EmitIR<IR::Opcode::A32SetRegister>(biscuit::Assembler& as, EmitContext& ctx, IR::Inst* inst);
template<>
void EmitIR<IR::Opcode::A32SetCpsrNZC>(biscuit::Assembler& as, EmitContext& ctx, IR::Inst* inst);
template<>
void EmitIR<IR::Opcode::LogicalShiftLeft32>(biscuit::Assembler& as, EmitContext& ctx, IR::Inst* inst);
template<> template<>
void EmitIR<IR::Opcode::GetCarryFromOp>(biscuit::Assembler&, EmitContext& ctx, IR::Inst* inst) { void EmitIR<IR::Opcode::GetCarryFromOp>(biscuit::Assembler&, EmitContext& ctx, IR::Inst* inst) {
ASSERT(ctx.reg_alloc.IsValueLive(inst)); ASSERT(ctx.reg_alloc.IsValueLive(inst));
} }
template<>
void EmitIR<IR::Opcode::GetNZFromOp>(biscuit::Assembler& 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);
as.SEQZ(Xnz, Xvalue);
as.SLLI(Xnz, Xnz, 30);
as.SLT(Xscratch0, Xvalue, biscuit::zero);
as.SLLI(Xscratch0, Xscratch0, 31);
as.OR(Xnz, Xnz, Xscratch0);
}
EmittedBlockInfo EmitRV64(biscuit::Assembler& as, IR::Block block, const EmitConfig& emit_conf) { EmittedBlockInfo EmitRV64(biscuit::Assembler& as, IR::Block block, const EmitConfig& emit_conf) {
using namespace biscuit; using namespace biscuit;

View file

@ -0,0 +1,63 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2024 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <biscuit/assembler.hpp>
#include <fmt/ostream.h>
#include "dynarmic/backend/riscv64/a32_jitstate.h"
#include "dynarmic/backend/riscv64/abi.h"
#include "dynarmic/backend/riscv64/emit_context.h"
#include "dynarmic/backend/riscv64/emit_riscv64.h"
#include "dynarmic/backend/riscv64/reg_alloc.h"
#include "dynarmic/ir/basic_block.h"
#include "dynarmic/ir/microinstruction.h"
#include "dynarmic/ir/opcodes.h"
namespace Dynarmic::Backend::RV64 {
template<>
void EmitIR<IR::Opcode::A32GetRegister>(biscuit::Assembler& as, EmitContext& ctx, IR::Inst* inst) {
const A32::Reg reg = inst->GetArg(0).GetA32RegRef();
auto Xresult = ctx.reg_alloc.WriteX(inst);
RegAlloc::Realize(Xresult);
as.LWU(Xresult, offsetof(A32JitState, regs) + sizeof(u32) * static_cast<size_t>(reg), Xstate);
}
template<>
void EmitIR<IR::Opcode::A32SetRegister>(biscuit::Assembler& 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);
// TODO: Detect if Gpr vs Fpr is more appropriate
as.SW(Xvalue, offsetof(A32JitState, regs) + sizeof(u32) * static_cast<size_t>(reg), Xstate);
}
template<>
void EmitIR<IR::Opcode::A32SetCpsrNZC>(biscuit::Assembler& as, EmitContext& ctx, IR::Inst* inst) {
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
// TODO: Add full implementation
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);
as.LWU(Xscratch0, offsetof(A32JitState, cpsr_nzcv), Xstate);
as.LUI(Xscratch1, 0x10000);
as.AND(Xscratch0, Xscratch0, Xscratch1);
as.OR(Xscratch0, Xscratch0, Xnz);
as.OR(Xscratch0, Xscratch0, Xc);
as.SW(Xscratch0, offsetof(A32JitState, cpsr_nzcv), Xstate);
}
} // namespace Dynarmic::Backend::RV64

View file

@ -0,0 +1,57 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2024 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <biscuit/assembler.hpp>
#include <fmt/ostream.h>
#include "dynarmic/backend/riscv64/a32_jitstate.h"
#include "dynarmic/backend/riscv64/abi.h"
#include "dynarmic/backend/riscv64/emit_context.h"
#include "dynarmic/backend/riscv64/emit_riscv64.h"
#include "dynarmic/backend/riscv64/reg_alloc.h"
#include "dynarmic/ir/basic_block.h"
#include "dynarmic/ir/microinstruction.h"
#include "dynarmic/ir/opcodes.h"
namespace Dynarmic::Backend::RV64 {
template<>
void EmitIR<IR::Opcode::LogicalShiftLeft32>(biscuit::Assembler& 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];
// TODO: Add full implementation
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) {
as.ADDW(Xresult, Xoperand, biscuit::zero);
as.ADDW(Xcarry_out, Xcarry_in, biscuit::zero);
} else if (shift < 32) {
as.SRLIW(Xcarry_out, Xoperand, 32 - shift);
as.ANDI(Xcarry_out, Xcarry_out, 1);
as.SLLIW(Xresult, Xoperand, shift);
} else if (shift > 32) {
as.MV(Xresult, biscuit::zero);
as.MV(Xcarry_out, biscuit::zero);
} else {
as.ANDI(Xcarry_out, Xresult, 1);
as.MV(Xresult, biscuit::zero);
}
}
} // namespace Dynarmic::Backend::RV64

View file

@ -9,10 +9,16 @@
#include <array> #include <array>
#include <mcl/assert.hpp> #include <mcl/assert.hpp>
#include <mcl/mp/metavalue/lift_value.hpp>
#include <mcl/stdint.hpp> #include <mcl/stdint.hpp>
#include "dynarmic/common/always_false.h"
namespace Dynarmic::Backend::RV64 { namespace Dynarmic::Backend::RV64 {
// TODO: We should really move this to biscuit.
void Mov64(biscuit::Assembler& as, biscuit::GPR rd, u64 imm);
constexpr size_t spill_offset = offsetof(StackLayout, spill); constexpr size_t spill_offset = offsetof(StackLayout, spill);
constexpr size_t spill_slot_size = sizeof(decltype(StackLayout::spill)::value_type); constexpr size_t spill_slot_size = sizeof(decltype(StackLayout::spill)::value_type);
@ -73,6 +79,15 @@ bool HostLocInfo::Contains(const IR::Inst* value) const {
return std::find(values.begin(), values.end(), value) != values.end(); return std::find(values.begin(), values.end(), value) != values.end();
} }
void HostLocInfo::SetupScratchLocation() {
ASSERT(IsCompletelyEmpty());
realized = true;
}
bool HostLocInfo::IsCompletelyEmpty() const {
return values.empty() && !locked && !realized && !accumulated_uses && !expected_uses;
}
RegAlloc::ArgumentInfo RegAlloc::GetArgumentInfo(IR::Inst* inst) { RegAlloc::ArgumentInfo RegAlloc::GetArgumentInfo(IR::Inst* inst) {
ArgumentInfo ret = {Argument{*this}, Argument{*this}, Argument{*this}, Argument{*this}}; ArgumentInfo ret = {Argument{*this}, Argument{*this}, Argument{*this}, Argument{*this}};
for (size_t i = 0; i < inst->NumArgs(); i++) { for (size_t i = 0; i < inst->NumArgs(); i++) {
@ -90,11 +105,35 @@ bool RegAlloc::IsValueLive(IR::Inst* inst) const {
return !!ValueLocation(inst); return !!ValueLocation(inst);
} }
template<bool is_fpr> template<HostLoc::Kind kind>
u32 RegAlloc::RealizeReadImpl(const IR::Inst* value) { u32 RegAlloc::GenerateImmediate(const IR::Value& value) {
constexpr HostLoc::Kind required_kind = is_fpr ? HostLoc::Kind::Fpr : HostLoc::Kind::Gpr; // TODO
// ASSERT(value.GetType() != IR::Type::U1);
const auto current_location = ValueLocation(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();
Mov64(as, biscuit::GPR{new_location_index}, value.GetImmediateAsU64());
return new_location_index;
} else if constexpr (kind == HostLoc::Kind::Fpr) {
ASSERT_FALSE("Unimplemented");
} else {
static_assert(Common::always_false_v<mcl::mp::lift_value<kind>>);
}
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); ASSERT(current_location);
if (current_location->kind == required_kind) { if (current_location->kind == required_kind) {
@ -105,7 +144,7 @@ u32 RegAlloc::RealizeReadImpl(const IR::Inst* value) {
ASSERT(!ValueInfo(*current_location).realized); ASSERT(!ValueInfo(*current_location).realized);
ASSERT(!ValueInfo(*current_location).locked); ASSERT(!ValueInfo(*current_location).locked);
if constexpr (is_fpr) { if constexpr (required_kind == HostLoc::Kind::Fpr) {
const u32 new_location_index = AllocateRegister(fprs, fpr_order); const u32 new_location_index = AllocateRegister(fprs, fpr_order);
SpillFpr(new_location_index); SpillFpr(new_location_index);
@ -124,7 +163,7 @@ u32 RegAlloc::RealizeReadImpl(const IR::Inst* value) {
fprs[new_location_index] = std::exchange(ValueInfo(*current_location), {}); fprs[new_location_index] = std::exchange(ValueInfo(*current_location), {});
fprs[new_location_index].realized = true; fprs[new_location_index].realized = true;
return new_location_index; return new_location_index;
} else { } else if constexpr (required_kind == HostLoc::Kind::Gpr) {
const u32 new_location_index = AllocateRegister(gprs, gpr_order); const u32 new_location_index = AllocateRegister(gprs, gpr_order);
SpillGpr(new_location_index); SpillGpr(new_location_index);
@ -144,10 +183,12 @@ u32 RegAlloc::RealizeReadImpl(const IR::Inst* value) {
gprs[new_location_index] = std::exchange(ValueInfo(*current_location), {}); gprs[new_location_index] = std::exchange(ValueInfo(*current_location), {});
gprs[new_location_index].realized = true; gprs[new_location_index].realized = true;
return new_location_index; return new_location_index;
} else {
static_assert(Common::always_false_v<mcl::mp::lift_value<required_kind>>);
} }
} }
template<bool is_fpr> template<HostLoc::Kind required_kind>
u32 RegAlloc::RealizeWriteImpl(const IR::Inst* value) { u32 RegAlloc::RealizeWriteImpl(const IR::Inst* value) {
ASSERT(!ValueLocation(value)); ASSERT(!ValueLocation(value));
@ -159,23 +200,25 @@ u32 RegAlloc::RealizeWriteImpl(const IR::Inst* value) {
info.expected_uses += value->UseCount(); info.expected_uses += value->UseCount();
}; };
if constexpr (is_fpr) { if constexpr (required_kind == HostLoc::Kind::Fpr) {
const u32 new_location_index = AllocateRegister(fprs, fpr_order); const u32 new_location_index = AllocateRegister(fprs, fpr_order);
SpillFpr(new_location_index); SpillFpr(new_location_index);
setup_location(fprs[new_location_index]); setup_location(fprs[new_location_index]);
return new_location_index; return new_location_index;
} else { } else if constexpr (required_kind == HostLoc::Kind::Gpr) {
const u32 new_location_index = AllocateRegister(gprs, gpr_order); const u32 new_location_index = AllocateRegister(gprs, gpr_order);
SpillGpr(new_location_index); SpillGpr(new_location_index);
setup_location(gprs[new_location_index]); setup_location(gprs[new_location_index]);
return new_location_index; return new_location_index;
} else {
static_assert(Common::always_false_v<mcl::mp::lift_value<required_kind>>);
} }
} }
template u32 RegAlloc::RealizeReadImpl<true>(const IR::Inst* value); template u32 RegAlloc::RealizeReadImpl<HostLoc::Kind::Gpr>(const IR::Value& value);
template u32 RegAlloc::RealizeReadImpl<false>(const IR::Inst* value); template u32 RegAlloc::RealizeReadImpl<HostLoc::Kind::Fpr>(const IR::Value& value);
template u32 RegAlloc::RealizeWriteImpl<true>(const IR::Inst* value); template u32 RegAlloc::RealizeWriteImpl<HostLoc::Kind::Gpr>(const IR::Inst* value);
template u32 RegAlloc::RealizeWriteImpl<false>(const IR::Inst* value); template u32 RegAlloc::RealizeWriteImpl<HostLoc::Kind::Fpr>(const IR::Inst* value);
void RegAlloc::Unlock(HostLoc host_loc) { void RegAlloc::Unlock(HostLoc host_loc) {
HostLocInfo& info = ValueInfo(host_loc); HostLocInfo& info = ValueInfo(host_loc);

View file

@ -64,35 +64,40 @@ private:
template<typename T> template<typename T>
struct RAReg { struct RAReg {
public: public:
static constexpr bool is_fpr = std::is_base_of_v<biscuit::FPR, T>; static constexpr HostLoc::Kind kind = std::is_base_of_v<biscuit::FPR, T>
? HostLoc::Kind::Fpr
: HostLoc::Kind::Gpr;
operator T() const { return *reg; } operator T() const { return *reg; }
T operator*() const { return *reg; } T operator*() const { return *reg; }
const T* operator->() const { return &*reg; }
~RAReg(); ~RAReg();
private: private:
friend class RegAlloc; friend class RegAlloc;
explicit RAReg(RegAlloc& reg_alloc, bool write, const IR::Inst* value) explicit RAReg(RegAlloc& reg_alloc, bool write, const IR::Value& value);
: reg_alloc{reg_alloc}, write{write}, value{value} {}
void Realize(); void Realize();
RegAlloc& reg_alloc; RegAlloc& reg_alloc;
bool write; bool write;
const IR::Inst* value; const IR::Value value;
std::optional<T> reg; std::optional<T> reg;
}; };
struct HostLocInfo final { struct HostLocInfo final {
std::vector<const IR::Inst*> values; std::vector<const IR::Inst*> values;
bool locked = false; size_t locked = 0;
bool realized = false; bool realized = false;
size_t accumulated_uses = 0; size_t accumulated_uses = 0;
size_t expected_uses = 0; size_t expected_uses = 0;
bool Contains(const IR::Inst*) const; bool Contains(const IR::Inst*) const;
void SetupScratchLocation();
bool IsCompletelyEmpty() const;
}; };
class RegAlloc { class RegAlloc {
@ -105,11 +110,11 @@ public:
ArgumentInfo GetArgumentInfo(IR::Inst* inst); ArgumentInfo GetArgumentInfo(IR::Inst* inst);
bool IsValueLive(IR::Inst* inst) const; bool IsValueLive(IR::Inst* inst) const;
auto ReadX(Argument& arg) { return RAReg<biscuit::GPR>{*this, false, PreReadImpl(arg.value)}; } auto ReadX(Argument& arg) { return RAReg<biscuit::GPR>{*this, false, arg.value}; }
auto ReadD(Argument& arg) { return RAReg<biscuit::FPR>{*this, false, PreReadImpl(arg.value)}; } auto ReadD(Argument& arg) { return RAReg<biscuit::FPR>{*this, false, arg.value}; }
auto WriteX(IR::Inst* inst) { return RAReg<biscuit::GPR>{*this, true, inst}; } auto WriteX(IR::Inst* inst) { return RAReg<biscuit::GPR>{*this, true, IR::Value{inst}}; }
auto WriteD(IR::Inst* inst) { return RAReg<biscuit::FPR>{*this, true, inst}; } auto WriteD(IR::Inst* inst) { return RAReg<biscuit::FPR>{*this, true, IR::Value{inst}}; }
void SpillAll(); void SpillAll();
@ -123,14 +128,11 @@ private:
template<typename> template<typename>
friend struct RAReg; friend struct RAReg;
const IR::Inst* PreReadImpl(const IR::Value& value) { template<HostLoc::Kind kind>
ValueInfo(value.GetInst()).locked = true; u32 GenerateImmediate(const IR::Value& value);
return value.GetInst(); template<HostLoc::Kind kind>
} u32 RealizeReadImpl(const IR::Value& value);
template<HostLoc::Kind kind>
template<bool is_fpr>
u32 RealizeReadImpl(const IR::Inst* value);
template<bool is_fpr>
u32 RealizeWriteImpl(const IR::Inst* value); u32 RealizeWriteImpl(const IR::Inst* value);
void Unlock(HostLoc host_loc); void Unlock(HostLoc host_loc);
@ -154,16 +156,35 @@ private:
mutable std::mt19937 rand_gen; 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> template<typename T>
RAReg<T>::~RAReg() { RAReg<T>::~RAReg() {
if (value.IsImmediate()) {
if (reg) { if (reg) {
reg_alloc.Unlock(HostLoc{is_fpr ? HostLoc::Kind::Fpr : HostLoc::Kind::Gpr, reg->Index()}); // Immediate in scratch register
HostLocInfo& info = reg_alloc.ValueInfo(HostLoc{kind, reg->Index()});
info.locked--;
info.realized = false;
}
} else {
HostLocInfo& info = reg_alloc.ValueInfo(value.GetInst());
info.locked--;
if (reg) {
info.realized = false;
}
} }
} }
template<typename T> template<typename T>
void RAReg<T>::Realize() { void RAReg<T>::Realize() {
reg = T{write ? reg_alloc.RealizeWriteImpl<is_fpr>(value) : reg_alloc.RealizeReadImpl<is_fpr>(value)}; reg = T{write ? reg_alloc.RealizeWriteImpl<kind>(value.GetInst()) : reg_alloc.RealizeReadImpl<kind>(value)};
} }
} // namespace Dynarmic::Backend::RV64 } // namespace Dynarmic::Backend::RV64