emit_arm64_a32_coprocessor: Implement coprocessor IR instructions

This commit is contained in:
Merry 2022-08-07 21:14:59 +01:00 committed by merry
parent 0f0744cb78
commit 94f5ae4f37
5 changed files with 264 additions and 40 deletions

View file

@ -150,6 +150,7 @@ EmittedBlockInfo A32AddressSpace::Emit(IR::Block block) {
.descriptor_to_fpcr = [](const IR::LocationDescriptor& location) { return FP::FPCR{A32::LocationDescriptor{location}.FPSCR().Value()}; }, .descriptor_to_fpcr = [](const IR::LocationDescriptor& location) { return FP::FPCR{A32::LocationDescriptor{location}.FPSCR().Value()}; },
.state_nzcv_offset = offsetof(A32JitState, cpsr_nzcv), .state_nzcv_offset = offsetof(A32JitState, cpsr_nzcv),
.state_fpsr_offset = offsetof(A32JitState, fpsr), .state_fpsr_offset = offsetof(A32JitState, fpsr),
.coprocessors = conf.coprocessors,
}; };
EmittedBlockInfo block_info = EmitArm64(code, std::move(block), emit_conf); EmittedBlockInfo block_info = EmitArm64(code, std::move(block), emit_conf);

View file

@ -5,11 +5,15 @@
#pragma once #pragma once
#include <array>
#include <cstddef> #include <cstddef>
#include <memory>
#include <vector> #include <vector>
#include <mcl/stdint.hpp> #include <mcl/stdint.hpp>
#include "dynarmic/interface/A32/coprocessor.h"
namespace oaknut { namespace oaknut {
struct PointerCodeGeneratorPolicy; struct PointerCodeGeneratorPolicy;
template<typename> template<typename>
@ -71,6 +75,8 @@ struct EmitConfig {
size_t state_nzcv_offset; size_t state_nzcv_offset;
size_t state_fpsr_offset; size_t state_fpsr_offset;
std::array<std::shared_ptr<A32::Coprocessor>, 16> coprocessors{};
}; };
struct EmitContext; struct EmitContext;

View file

@ -10,6 +10,7 @@
#include "dynarmic/backend/arm64/emit_arm64.h" #include "dynarmic/backend/arm64/emit_arm64.h"
#include "dynarmic/backend/arm64/emit_context.h" #include "dynarmic/backend/arm64/emit_context.h"
#include "dynarmic/backend/arm64/reg_alloc.h" #include "dynarmic/backend/arm64/reg_alloc.h"
#include "dynarmic/interface/A32/coprocessor.h"
#include "dynarmic/ir/basic_block.h" #include "dynarmic/ir/basic_block.h"
#include "dynarmic/ir/microinstruction.h" #include "dynarmic/ir/microinstruction.h"
#include "dynarmic/ir/opcodes.h" #include "dynarmic/ir/opcodes.h"
@ -18,60 +19,277 @@ namespace Dynarmic::Backend::Arm64 {
using namespace oaknut::util; using namespace oaknut::util;
static void EmitCoprocessorException() {
ASSERT_FALSE("Should raise coproc exception here");
}
static void CallCoprocCallback(oaknut::CodeGenerator& code, EmitContext& ctx, A32::Coprocessor::Callback callback, IR::Inst* inst = nullptr, std::optional<Argument::copyable_reference> arg0 = {}, std::optional<Argument::copyable_reference> arg1 = {}) {
ctx.reg_alloc.PrepareForCall(inst, {}, arg0, arg1);
if (callback.user_arg) {
code.MOV(X0, reinterpret_cast<u64>(*callback.user_arg));
}
code.MOV(Xscratch0, reinterpret_cast<u64>(callback.function));
code.BLR(Xscratch0);
}
template<> template<>
void EmitIR<IR::Opcode::A32CoprocInternalOperation>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) { void EmitIR<IR::Opcode::A32CoprocInternalOperation>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
(void)code; const auto coproc_info = inst->GetArg(0).GetCoprocInfo();
(void)ctx; const size_t coproc_num = coproc_info[0];
(void)inst; const bool two = coproc_info[1] != 0;
ASSERT_FALSE("Unimplemented"); const auto opc1 = static_cast<unsigned>(coproc_info[2]);
const auto CRd = static_cast<A32::CoprocReg>(coproc_info[3]);
const auto CRn = static_cast<A32::CoprocReg>(coproc_info[4]);
const auto CRm = static_cast<A32::CoprocReg>(coproc_info[5]);
const auto opc2 = static_cast<unsigned>(coproc_info[6]);
std::shared_ptr<A32::Coprocessor> coproc = ctx.conf.coprocessors[coproc_num];
if (!coproc) {
EmitCoprocessorException();
return;
}
const auto action = coproc->CompileInternalOperation(two, opc1, CRd, CRn, CRm, opc2);
if (!action) {
EmitCoprocessorException();
return;
}
CallCoprocCallback(code, ctx, *action);
} }
template<> template<>
void EmitIR<IR::Opcode::A32CoprocSendOneWord>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) { void EmitIR<IR::Opcode::A32CoprocSendOneWord>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
(void)code; auto args = ctx.reg_alloc.GetArgumentInfo(inst);
(void)ctx; const auto coproc_info = inst->GetArg(0).GetCoprocInfo();
(void)inst; const size_t coproc_num = coproc_info[0];
ASSERT_FALSE("Unimplemented"); const bool two = coproc_info[1] != 0;
const auto opc1 = static_cast<unsigned>(coproc_info[2]);
const auto CRn = static_cast<A32::CoprocReg>(coproc_info[3]);
const auto CRm = static_cast<A32::CoprocReg>(coproc_info[4]);
const auto opc2 = static_cast<unsigned>(coproc_info[5]);
std::shared_ptr<A32::Coprocessor> coproc = ctx.conf.coprocessors[coproc_num];
if (!coproc) {
EmitCoprocessorException();
return;
}
const auto action = coproc->CompileSendOneWord(two, opc1, CRn, CRm, opc2);
if (std::holds_alternative<std::monostate>(action)) {
EmitCoprocessorException();
return;
}
if (const auto cb = std::get_if<A32::Coprocessor::Callback>(&action)) {
CallCoprocCallback(code, ctx, *cb, nullptr, args[1]);
return;
}
if (const auto destination_ptr = std::get_if<u32*>(&action)) {
auto Wvalue = ctx.reg_alloc.ReadW(args[1]);
RegAlloc::Realize(Wvalue);
code.MOV(Xscratch0, reinterpret_cast<u64>(*destination_ptr));
code.STR(Wvalue, Xscratch0);
return;
}
UNREACHABLE();
} }
template<> template<>
void EmitIR<IR::Opcode::A32CoprocSendTwoWords>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) { void EmitIR<IR::Opcode::A32CoprocSendTwoWords>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
(void)code; auto args = ctx.reg_alloc.GetArgumentInfo(inst);
(void)ctx;
(void)inst; const auto coproc_info = inst->GetArg(0).GetCoprocInfo();
ASSERT_FALSE("Unimplemented"); const size_t coproc_num = coproc_info[0];
const bool two = coproc_info[1] != 0;
const auto opc = static_cast<unsigned>(coproc_info[2]);
const auto CRm = static_cast<A32::CoprocReg>(coproc_info[3]);
std::shared_ptr<A32::Coprocessor> coproc = ctx.conf.coprocessors[coproc_num];
if (!coproc) {
EmitCoprocessorException();
return;
}
const auto action = coproc->CompileSendTwoWords(two, opc, CRm);
if (std::holds_alternative<std::monostate>(action)) {
EmitCoprocessorException();
return;
}
if (const auto cb = std::get_if<A32::Coprocessor::Callback>(&action)) {
CallCoprocCallback(code, ctx, *cb, nullptr, args[1], args[2]);
return;
}
if (const auto destination_ptrs = std::get_if<std::array<u32*, 2>>(&action)) {
auto Wvalue1 = ctx.reg_alloc.ReadW(args[1]);
auto Wvalue2 = ctx.reg_alloc.ReadW(args[2]);
RegAlloc::Realize(Wvalue1, Wvalue2);
code.MOV(Xscratch0, reinterpret_cast<u64>((*destination_ptrs)[0]));
code.MOV(Xscratch1, reinterpret_cast<u64>((*destination_ptrs)[1]));
code.STR(Wvalue1, Xscratch0);
code.STR(Wvalue2, Xscratch1);
return;
}
UNREACHABLE();
} }
template<> template<>
void EmitIR<IR::Opcode::A32CoprocGetOneWord>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) { void EmitIR<IR::Opcode::A32CoprocGetOneWord>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
(void)code; const auto coproc_info = inst->GetArg(0).GetCoprocInfo();
(void)ctx;
(void)inst; const size_t coproc_num = coproc_info[0];
ASSERT_FALSE("Unimplemented"); const bool two = coproc_info[1] != 0;
const auto opc1 = static_cast<unsigned>(coproc_info[2]);
const auto CRn = static_cast<A32::CoprocReg>(coproc_info[3]);
const auto CRm = static_cast<A32::CoprocReg>(coproc_info[4]);
const auto opc2 = static_cast<unsigned>(coproc_info[5]);
std::shared_ptr<A32::Coprocessor> coproc = ctx.conf.coprocessors[coproc_num];
if (!coproc) {
EmitCoprocessorException();
return;
}
const auto action = coproc->CompileGetOneWord(two, opc1, CRn, CRm, opc2);
if (std::holds_alternative<std::monostate>(action)) {
EmitCoprocessorException();
return;
}
if (const auto cb = std::get_if<A32::Coprocessor::Callback>(&action)) {
CallCoprocCallback(code, ctx, *cb, inst);
return;
}
if (const auto source_ptr = std::get_if<u32*>(&action)) {
auto Wvalue = ctx.reg_alloc.WriteW(inst);
RegAlloc::Realize(Wvalue);
code.MOV(Xscratch0, reinterpret_cast<u64>(*source_ptr));
code.LDR(Wvalue, Xscratch0);
return;
}
UNREACHABLE();
} }
template<> template<>
void EmitIR<IR::Opcode::A32CoprocGetTwoWords>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) { void EmitIR<IR::Opcode::A32CoprocGetTwoWords>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
(void)code; const auto coproc_info = inst->GetArg(0).GetCoprocInfo();
(void)ctx; const size_t coproc_num = coproc_info[0];
(void)inst; const bool two = coproc_info[1] != 0;
ASSERT_FALSE("Unimplemented"); const unsigned opc = coproc_info[2];
const auto CRm = static_cast<A32::CoprocReg>(coproc_info[3]);
std::shared_ptr<A32::Coprocessor> coproc = ctx.conf.coprocessors[coproc_num];
if (!coproc) {
EmitCoprocessorException();
return;
}
auto action = coproc->CompileGetTwoWords(two, opc, CRm);
if (std::holds_alternative<std::monostate>(action)) {
EmitCoprocessorException();
return;
}
if (const auto cb = std::get_if<A32::Coprocessor::Callback>(&action)) {
CallCoprocCallback(code, ctx, *cb, inst);
return;
}
if (const auto source_ptrs = std::get_if<std::array<u32*, 2>>(&action)) {
auto Xvalue = ctx.reg_alloc.WriteX(inst);
RegAlloc::Realize(Xvalue);
code.MOV(Xscratch0, reinterpret_cast<u64>((*source_ptrs)[0]));
code.MOV(Xscratch1, reinterpret_cast<u64>((*source_ptrs)[1]));
code.LDR(Xvalue, Xscratch0);
code.LDR(Wscratch1, Xscratch1);
code.BFI(Xvalue, Xscratch1, 32, 32);
return;
}
UNREACHABLE();
} }
template<> template<>
void EmitIR<IR::Opcode::A32CoprocLoadWords>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) { void EmitIR<IR::Opcode::A32CoprocLoadWords>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
(void)code; auto args = ctx.reg_alloc.GetArgumentInfo(inst);
(void)ctx;
(void)inst; const auto coproc_info = inst->GetArg(0).GetCoprocInfo();
ASSERT_FALSE("Unimplemented"); const size_t coproc_num = coproc_info[0];
const bool two = coproc_info[1] != 0;
const bool long_transfer = coproc_info[2] != 0;
const auto CRd = static_cast<A32::CoprocReg>(coproc_info[3]);
const bool has_option = coproc_info[4] != 0;
std::optional<u8> option = std::nullopt;
if (has_option) {
option = coproc_info[5];
}
std::shared_ptr<A32::Coprocessor> coproc = ctx.conf.coprocessors[coproc_num];
if (!coproc) {
EmitCoprocessorException();
return;
}
const auto action = coproc->CompileLoadWords(two, long_transfer, CRd, option);
if (!action) {
EmitCoprocessorException();
return;
}
CallCoprocCallback(code, ctx, *action, nullptr, args[1]);
} }
template<> template<>
void EmitIR<IR::Opcode::A32CoprocStoreWords>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) { void EmitIR<IR::Opcode::A32CoprocStoreWords>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
(void)code; auto args = ctx.reg_alloc.GetArgumentInfo(inst);
(void)ctx;
(void)inst; const auto coproc_info = inst->GetArg(0).GetCoprocInfo();
ASSERT_FALSE("Unimplemented"); const size_t coproc_num = coproc_info[0];
const bool two = coproc_info[1] != 0;
const bool long_transfer = coproc_info[2] != 0;
const auto CRd = static_cast<A32::CoprocReg>(coproc_info[3]);
const bool has_option = coproc_info[4] != 0;
std::optional<u8> option = std::nullopt;
if (has_option) {
option = coproc_info[5];
}
std::shared_ptr<A32::Coprocessor> coproc = ctx.conf.coprocessors[coproc_num];
if (!coproc) {
EmitCoprocessorException();
return;
}
const auto action = coproc->CompileStoreWords(two, long_transfer, CRd, option);
if (!action) {
EmitCoprocessorException();
return;
}
CallCoprocCallback(code, ctx, *action, nullptr, args[1]);
} }
} // namespace Dynarmic::Backend::Arm64 } // namespace Dynarmic::Backend::Arm64

View file

@ -835,12 +835,11 @@ static void EmitCoprocessorException() {
ASSERT_FALSE("Should raise coproc exception here"); ASSERT_FALSE("Should raise coproc exception here");
} }
static void CallCoprocCallback(BlockOfCode& code, RegAlloc& reg_alloc, A32::Jit* jit_interface, A32::Coprocessor::Callback callback, IR::Inst* inst = nullptr, std::optional<Argument::copyable_reference> arg0 = {}, std::optional<Argument::copyable_reference> arg1 = {}) { static void CallCoprocCallback(BlockOfCode& code, RegAlloc& reg_alloc, A32::Coprocessor::Callback callback, IR::Inst* inst = nullptr, std::optional<Argument::copyable_reference> arg0 = {}, std::optional<Argument::copyable_reference> arg1 = {}) {
reg_alloc.HostCall(inst, {}, {}, arg0, arg1); reg_alloc.HostCall(inst, {}, arg0, arg1);
code.mov(code.ABI_PARAM1, reinterpret_cast<u64>(jit_interface));
if (callback.user_arg) { if (callback.user_arg) {
code.mov(code.ABI_PARAM2, reinterpret_cast<u64>(*callback.user_arg)); code.mov(code.ABI_PARAM1, reinterpret_cast<u64>(*callback.user_arg));
} }
code.CallFunction(callback.function); code.CallFunction(callback.function);
@ -868,7 +867,7 @@ void A32EmitX64::EmitA32CoprocInternalOperation(A32EmitContext& ctx, IR::Inst* i
return; return;
} }
CallCoprocCallback(code, ctx.reg_alloc, jit_interface, *action); CallCoprocCallback(code, ctx.reg_alloc, *action);
} }
void A32EmitX64::EmitA32CoprocSendOneWord(A32EmitContext& ctx, IR::Inst* inst) { void A32EmitX64::EmitA32CoprocSendOneWord(A32EmitContext& ctx, IR::Inst* inst) {
@ -895,7 +894,7 @@ void A32EmitX64::EmitA32CoprocSendOneWord(A32EmitContext& ctx, IR::Inst* inst) {
} }
if (const auto cb = std::get_if<A32::Coprocessor::Callback>(&action)) { if (const auto cb = std::get_if<A32::Coprocessor::Callback>(&action)) {
CallCoprocCallback(code, ctx.reg_alloc, jit_interface, *cb, nullptr, args[1]); CallCoprocCallback(code, ctx.reg_alloc, *cb, nullptr, args[1]);
return; return;
} }
@ -935,7 +934,7 @@ void A32EmitX64::EmitA32CoprocSendTwoWords(A32EmitContext& ctx, IR::Inst* inst)
} }
if (const auto cb = std::get_if<A32::Coprocessor::Callback>(&action)) { if (const auto cb = std::get_if<A32::Coprocessor::Callback>(&action)) {
CallCoprocCallback(code, ctx.reg_alloc, jit_interface, *cb, nullptr, args[1], args[2]); CallCoprocCallback(code, ctx.reg_alloc, *cb, nullptr, args[1], args[2]);
return; return;
} }
@ -979,7 +978,7 @@ void A32EmitX64::EmitA32CoprocGetOneWord(A32EmitContext& ctx, IR::Inst* inst) {
} }
if (const auto cb = std::get_if<A32::Coprocessor::Callback>(&action)) { if (const auto cb = std::get_if<A32::Coprocessor::Callback>(&action)) {
CallCoprocCallback(code, ctx.reg_alloc, jit_interface, *cb, inst); CallCoprocCallback(code, ctx.reg_alloc, *cb, inst);
return; return;
} }
@ -1019,7 +1018,7 @@ void A32EmitX64::EmitA32CoprocGetTwoWords(A32EmitContext& ctx, IR::Inst* inst) {
} }
if (const auto cb = std::get_if<A32::Coprocessor::Callback>(&action)) { if (const auto cb = std::get_if<A32::Coprocessor::Callback>(&action)) {
CallCoprocCallback(code, ctx.reg_alloc, jit_interface, *cb, inst); CallCoprocCallback(code, ctx.reg_alloc, *cb, inst);
return; return;
} }
@ -1070,7 +1069,7 @@ void A32EmitX64::EmitA32CoprocLoadWords(A32EmitContext& ctx, IR::Inst* inst) {
return; return;
} }
CallCoprocCallback(code, ctx.reg_alloc, jit_interface, *action, nullptr, args[1]); CallCoprocCallback(code, ctx.reg_alloc, *action, nullptr, args[1]);
} }
void A32EmitX64::EmitA32CoprocStoreWords(A32EmitContext& ctx, IR::Inst* inst) { void A32EmitX64::EmitA32CoprocStoreWords(A32EmitContext& ctx, IR::Inst* inst) {
@ -1100,7 +1099,7 @@ void A32EmitX64::EmitA32CoprocStoreWords(A32EmitContext& ctx, IR::Inst* inst) {
return; return;
} }
CallCoprocCallback(code, ctx.reg_alloc, jit_interface, *action, nullptr, args[1]); CallCoprocCallback(code, ctx.reg_alloc, *action, nullptr, args[1]);
} }
std::string A32EmitX64::LocationDescriptorToFriendlyName(const IR::LocationDescriptor& ir_descriptor) const { std::string A32EmitX64::LocationDescriptorToFriendlyName(const IR::LocationDescriptor& ir_descriptor) const {

View file

@ -28,7 +28,7 @@ public:
* @param arg1 Purpose of this argument depends on type of callback. * @param arg1 Purpose of this argument depends on type of callback.
* @return Purpose of return value depends on type of callback. * @return Purpose of return value depends on type of callback.
*/ */
std::uint64_t (*function)(Jit* jit, void* user_arg, std::uint32_t arg0, std::uint32_t arg1); std::uint64_t (*function)(void* user_arg, std::uint32_t arg0, std::uint32_t arg1);
/// If std::nullopt, function will be called with a user_arg parameter containing garbage. /// If std::nullopt, function will be called with a user_arg parameter containing garbage.
std::optional<void*> user_arg; std::optional<void*> user_arg;
}; };