Merge pull request #12237 from liamwhite/nce-sigtrap
nce: implement instruction emulation for misaligned memory accesses
This commit is contained in:
commit
4f600f746a
|
@ -174,7 +174,8 @@ android {
|
||||||
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
|
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
|
||||||
"-DYUZU_USE_BUNDLED_VCPKG=ON",
|
"-DYUZU_USE_BUNDLED_VCPKG=ON",
|
||||||
"-DYUZU_USE_BUNDLED_FFMPEG=ON",
|
"-DYUZU_USE_BUNDLED_FFMPEG=ON",
|
||||||
"-DYUZU_ENABLE_LTO=ON"
|
"-DYUZU_ENABLE_LTO=ON",
|
||||||
|
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
|
||||||
)
|
)
|
||||||
|
|
||||||
abiFilters("arm64-v8a", "x86_64")
|
abiFilters("arm64-v8a", "x86_64")
|
||||||
|
|
|
@ -953,15 +953,19 @@ if (HAS_NCE)
|
||||||
set(CMAKE_ASM_FLAGS "${CFLAGS} -x assembler-with-cpp")
|
set(CMAKE_ASM_FLAGS "${CFLAGS} -x assembler-with-cpp")
|
||||||
|
|
||||||
target_sources(core PRIVATE
|
target_sources(core PRIVATE
|
||||||
|
arm/nce/arm_nce_asm_definitions.h
|
||||||
arm/nce/arm_nce.cpp
|
arm/nce/arm_nce.cpp
|
||||||
arm/nce/arm_nce.h
|
arm/nce/arm_nce.h
|
||||||
arm/nce/arm_nce.s
|
arm/nce/arm_nce.s
|
||||||
arm/nce/guest_context.h
|
arm/nce/guest_context.h
|
||||||
|
arm/nce/instructions.h
|
||||||
|
arm/nce/interpreter_visitor.cpp
|
||||||
|
arm/nce/interpreter_visitor.h
|
||||||
arm/nce/patcher.cpp
|
arm/nce/patcher.cpp
|
||||||
arm/nce/patcher.h
|
arm/nce/patcher.h
|
||||||
arm/nce/instructions.h
|
arm/nce/visitor_base.h
|
||||||
)
|
)
|
||||||
target_link_libraries(core PRIVATE merry::oaknut)
|
target_link_libraries(core PRIVATE merry::mcl merry::oaknut)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
|
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
#include "common/signal_chain.h"
|
#include "common/signal_chain.h"
|
||||||
#include "core/arm/nce/arm_nce.h"
|
#include "core/arm/nce/arm_nce.h"
|
||||||
#include "core/arm/nce/guest_context.h"
|
#include "core/arm/nce/interpreter_visitor.h"
|
||||||
#include "core/arm/nce/patcher.h"
|
#include "core/arm/nce/patcher.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
|
@ -21,7 +21,8 @@ namespace Core {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
struct sigaction g_orig_action;
|
struct sigaction g_orig_bus_action;
|
||||||
|
struct sigaction g_orig_segv_action;
|
||||||
|
|
||||||
// Verify assembly offsets.
|
// Verify assembly offsets.
|
||||||
using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
|
using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
|
||||||
|
@ -37,6 +38,9 @@ fpsimd_context* GetFloatingPointState(mcontext_t& host_ctx) {
|
||||||
return reinterpret_cast<fpsimd_context*>(header);
|
return reinterpret_cast<fpsimd_context*>(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using namespace Common::Literals;
|
||||||
|
constexpr u32 StackSize = 32_KiB;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void* ArmNce::RestoreGuestContext(void* raw_context) {
|
void* ArmNce::RestoreGuestContext(void* raw_context) {
|
||||||
|
@ -104,19 +108,10 @@ void ArmNce::SaveGuestContext(GuestContext* guest_ctx, void* raw_context) {
|
||||||
host_ctx.regs[0] = guest_ctx->esr_el1.exchange(0);
|
host_ctx.regs[0] = guest_ctx->esr_el1.exchange(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ArmNce::HandleGuestFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
bool ArmNce::HandleFailedGuestFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
||||||
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
|
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
|
||||||
auto* info = static_cast<siginfo_t*>(raw_info);
|
auto* info = static_cast<siginfo_t*>(raw_info);
|
||||||
|
|
||||||
// Try to handle an invalid access.
|
|
||||||
// TODO: handle accesses which split a page?
|
|
||||||
const Common::ProcessAddress addr =
|
|
||||||
(reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
|
|
||||||
if (guest_ctx->system->ApplicationMemory().InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) {
|
|
||||||
// We handled the access successfully and are returning to guest code.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can't handle the access, so determine why we crashed.
|
// We can't handle the access, so determine why we crashed.
|
||||||
const bool is_prefetch_abort = host_ctx.pc == reinterpret_cast<u64>(info->si_addr);
|
const bool is_prefetch_abort = host_ctx.pc == reinterpret_cast<u64>(info->si_addr);
|
||||||
|
|
||||||
|
@ -143,8 +138,44 @@ bool ArmNce::HandleGuestFault(GuestContext* guest_ctx, void* raw_info, void* raw
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArmNce::HandleHostFault(int sig, void* raw_info, void* raw_context) {
|
bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
||||||
return g_orig_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
|
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
|
||||||
|
auto* fpctx = GetFloatingPointState(host_ctx);
|
||||||
|
auto& memory = guest_ctx->system->ApplicationMemory();
|
||||||
|
|
||||||
|
// Match and execute an instruction.
|
||||||
|
auto next_pc = MatchAndExecuteOneInstruction(memory, &host_ctx, fpctx);
|
||||||
|
if (next_pc) {
|
||||||
|
host_ctx.pc = *next_pc;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We couldn't handle the access.
|
||||||
|
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
||||||
|
auto* info = static_cast<siginfo_t*>(raw_info);
|
||||||
|
|
||||||
|
// Try to handle an invalid access.
|
||||||
|
// TODO: handle accesses which split a page?
|
||||||
|
const Common::ProcessAddress addr =
|
||||||
|
(reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
|
||||||
|
if (guest_ctx->system->ApplicationMemory().InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) {
|
||||||
|
// We handled the access successfully and are returning to guest code.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We couldn't handle the access.
|
||||||
|
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArmNce::HandleHostAlignmentFault(int sig, void* raw_info, void* raw_context) {
|
||||||
|
return g_orig_bus_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArmNce::HandleHostAccessFault(int sig, void* raw_info, void* raw_context) {
|
||||||
|
return g_orig_segv_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArmNce::LockThread(Kernel::KThread* thread) {
|
void ArmNce::LockThread(Kernel::KThread* thread) {
|
||||||
|
@ -225,18 +256,31 @@ ArmNce::ArmNce(System& system, bool uses_wall_clock, std::size_t core_index)
|
||||||
ArmNce::~ArmNce() = default;
|
ArmNce::~ArmNce() = default;
|
||||||
|
|
||||||
void ArmNce::Initialize() {
|
void ArmNce::Initialize() {
|
||||||
m_thread_id = gettid();
|
if (m_thread_id == -1) {
|
||||||
|
m_thread_id = gettid();
|
||||||
|
}
|
||||||
|
|
||||||
// Setup our signals
|
// Configure signal stack.
|
||||||
static std::once_flag signals;
|
if (!m_stack) {
|
||||||
std::call_once(signals, [] {
|
m_stack = std::make_unique<u8[]>(StackSize);
|
||||||
|
|
||||||
|
stack_t ss{};
|
||||||
|
ss.ss_sp = m_stack.get();
|
||||||
|
ss.ss_size = StackSize;
|
||||||
|
sigaltstack(&ss, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up signals.
|
||||||
|
static std::once_flag flag;
|
||||||
|
std::call_once(flag, [] {
|
||||||
using HandlerType = decltype(sigaction::sa_sigaction);
|
using HandlerType = decltype(sigaction::sa_sigaction);
|
||||||
|
|
||||||
sigset_t signal_mask;
|
sigset_t signal_mask;
|
||||||
sigemptyset(&signal_mask);
|
sigemptyset(&signal_mask);
|
||||||
sigaddset(&signal_mask, ReturnToRunCodeByExceptionLevelChangeSignal);
|
sigaddset(&signal_mask, ReturnToRunCodeByExceptionLevelChangeSignal);
|
||||||
sigaddset(&signal_mask, BreakFromRunCodeSignal);
|
sigaddset(&signal_mask, BreakFromRunCodeSignal);
|
||||||
sigaddset(&signal_mask, GuestFaultSignal);
|
sigaddset(&signal_mask, GuestAlignmentFaultSignal);
|
||||||
|
sigaddset(&signal_mask, GuestAccessFaultSignal);
|
||||||
|
|
||||||
struct sigaction return_to_run_code_action {};
|
struct sigaction return_to_run_code_action {};
|
||||||
return_to_run_code_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
return_to_run_code_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
||||||
|
@ -253,18 +297,19 @@ void ArmNce::Initialize() {
|
||||||
break_from_run_code_action.sa_mask = signal_mask;
|
break_from_run_code_action.sa_mask = signal_mask;
|
||||||
Common::SigAction(BreakFromRunCodeSignal, &break_from_run_code_action, nullptr);
|
Common::SigAction(BreakFromRunCodeSignal, &break_from_run_code_action, nullptr);
|
||||||
|
|
||||||
struct sigaction fault_action {};
|
struct sigaction alignment_fault_action {};
|
||||||
fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART;
|
alignment_fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
||||||
fault_action.sa_sigaction = reinterpret_cast<HandlerType>(&ArmNce::GuestFaultSignalHandler);
|
alignment_fault_action.sa_sigaction =
|
||||||
fault_action.sa_mask = signal_mask;
|
reinterpret_cast<HandlerType>(&ArmNce::GuestAlignmentFaultSignalHandler);
|
||||||
Common::SigAction(GuestFaultSignal, &fault_action, &g_orig_action);
|
alignment_fault_action.sa_mask = signal_mask;
|
||||||
|
Common::SigAction(GuestAlignmentFaultSignal, &alignment_fault_action, nullptr);
|
||||||
|
|
||||||
// Simplify call for g_orig_action.
|
struct sigaction access_fault_action {};
|
||||||
// These fields occupy the same space in memory, so this should be a no-op in practice.
|
access_fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART;
|
||||||
if (!(g_orig_action.sa_flags & SA_SIGINFO)) {
|
access_fault_action.sa_sigaction =
|
||||||
g_orig_action.sa_sigaction =
|
reinterpret_cast<HandlerType>(&ArmNce::GuestAccessFaultSignalHandler);
|
||||||
reinterpret_cast<decltype(g_orig_action.sa_sigaction)>(g_orig_action.sa_handler);
|
access_fault_action.sa_mask = signal_mask;
|
||||||
}
|
Common::SigAction(GuestAccessFaultSignal, &access_fault_action, &g_orig_segv_action);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,8 @@ private:
|
||||||
static void ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void* info,
|
static void ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void* info,
|
||||||
void* raw_context);
|
void* raw_context);
|
||||||
static void BreakFromRunCodeSignalHandler(int sig, void* info, void* raw_context);
|
static void BreakFromRunCodeSignalHandler(int sig, void* info, void* raw_context);
|
||||||
static void GuestFaultSignalHandler(int sig, void* info, void* raw_context);
|
static void GuestAlignmentFaultSignalHandler(int sig, void* info, void* raw_context);
|
||||||
|
static void GuestAccessFaultSignalHandler(int sig, void* info, void* raw_context);
|
||||||
|
|
||||||
static void LockThreadParameters(void* tpidr);
|
static void LockThreadParameters(void* tpidr);
|
||||||
static void UnlockThreadParameters(void* tpidr);
|
static void UnlockThreadParameters(void* tpidr);
|
||||||
|
@ -70,8 +71,11 @@ private:
|
||||||
// C++ implementation functions for assembly definitions.
|
// C++ implementation functions for assembly definitions.
|
||||||
static void* RestoreGuestContext(void* raw_context);
|
static void* RestoreGuestContext(void* raw_context);
|
||||||
static void SaveGuestContext(GuestContext* ctx, void* raw_context);
|
static void SaveGuestContext(GuestContext* ctx, void* raw_context);
|
||||||
static bool HandleGuestFault(GuestContext* ctx, void* info, void* raw_context);
|
static bool HandleFailedGuestFault(GuestContext* ctx, void* info, void* raw_context);
|
||||||
static void HandleHostFault(int sig, void* info, void* raw_context);
|
static bool HandleGuestAlignmentFault(GuestContext* ctx, void* info, void* raw_context);
|
||||||
|
static bool HandleGuestAccessFault(GuestContext* ctx, void* info, void* raw_context);
|
||||||
|
static void HandleHostAlignmentFault(int sig, void* info, void* raw_context);
|
||||||
|
static void HandleHostAccessFault(int sig, void* info, void* raw_context);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Core::System& m_system;
|
Core::System& m_system;
|
||||||
|
@ -83,6 +87,9 @@ public:
|
||||||
// Core context.
|
// Core context.
|
||||||
GuestContext m_guest_ctx{};
|
GuestContext m_guest_ctx{};
|
||||||
Kernel::KThread* m_running_thread{};
|
Kernel::KThread* m_running_thread{};
|
||||||
|
|
||||||
|
// Stack for signal processing.
|
||||||
|
std::unique_ptr<u8[]> m_stack{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -130,11 +130,11 @@ _ZN4Core6ArmNce29BreakFromRunCodeSignalHandlerEiPvS1_:
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
|
||||||
/* static void Core::ArmNce::GuestFaultSignalHandler(int sig, void* info, void* raw_context) */
|
/* static void Core::ArmNce::GuestAlignmentFaultSignalHandler(int sig, void* info, void* raw_context) */
|
||||||
.section .text._ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_, "ax", %progbits
|
.section .text._ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_, "ax", %progbits
|
||||||
.global _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_
|
.global _ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_
|
||||||
.type _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_, %function
|
.type _ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_, %function
|
||||||
_ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_:
|
_ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_:
|
||||||
/* Check to see if we have the correct TLS magic. */
|
/* Check to see if we have the correct TLS magic. */
|
||||||
mrs x8, tpidr_el0
|
mrs x8, tpidr_el0
|
||||||
ldr w9, [x8, #(TpidrEl0TlsMagic)]
|
ldr w9, [x8, #(TpidrEl0TlsMagic)]
|
||||||
|
@ -146,7 +146,7 @@ _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_:
|
||||||
|
|
||||||
/* Incorrect TLS magic, so this is a host fault. */
|
/* Incorrect TLS magic, so this is a host fault. */
|
||||||
/* Tail call the handler. */
|
/* Tail call the handler. */
|
||||||
b _ZN4Core6ArmNce15HandleHostFaultEiPvS1_
|
b _ZN4Core6ArmNce24HandleHostAlignmentFaultEiPvS1_
|
||||||
|
|
||||||
1:
|
1:
|
||||||
/* Correct TLS magic, so this is a guest fault. */
|
/* Correct TLS magic, so this is a guest fault. */
|
||||||
|
@ -163,7 +163,53 @@ _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_:
|
||||||
msr tpidr_el0, x3
|
msr tpidr_el0, x3
|
||||||
|
|
||||||
/* Call the handler. */
|
/* Call the handler. */
|
||||||
bl _ZN4Core6ArmNce16HandleGuestFaultEPNS_12GuestContextEPvS3_
|
bl _ZN4Core6ArmNce25HandleGuestAlignmentFaultEPNS_12GuestContextEPvS3_
|
||||||
|
|
||||||
|
/* If the handler returned false, we want to preserve the host tpidr_el0. */
|
||||||
|
cbz x0, 2f
|
||||||
|
|
||||||
|
/* Otherwise, restore guest tpidr_el0. */
|
||||||
|
msr tpidr_el0, x19
|
||||||
|
|
||||||
|
2:
|
||||||
|
ldr x19, [sp, #0x10]
|
||||||
|
ldp x29, x30, [sp], #0x20
|
||||||
|
ret
|
||||||
|
|
||||||
|
/* static void Core::ArmNce::GuestAccessFaultSignalHandler(int sig, void* info, void* raw_context) */
|
||||||
|
.section .text._ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_, "ax", %progbits
|
||||||
|
.global _ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_
|
||||||
|
.type _ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_, %function
|
||||||
|
_ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_:
|
||||||
|
/* Check to see if we have the correct TLS magic. */
|
||||||
|
mrs x8, tpidr_el0
|
||||||
|
ldr w9, [x8, #(TpidrEl0TlsMagic)]
|
||||||
|
|
||||||
|
LOAD_IMMEDIATE_32(w10, TlsMagic)
|
||||||
|
|
||||||
|
cmp w9, w10
|
||||||
|
b.eq 1f
|
||||||
|
|
||||||
|
/* Incorrect TLS magic, so this is a host fault. */
|
||||||
|
/* Tail call the handler. */
|
||||||
|
b _ZN4Core6ArmNce21HandleHostAccessFaultEiPvS1_
|
||||||
|
|
||||||
|
1:
|
||||||
|
/* Correct TLS magic, so this is a guest fault. */
|
||||||
|
stp x29, x30, [sp, #-0x20]!
|
||||||
|
str x19, [sp, #0x10]
|
||||||
|
mov x29, sp
|
||||||
|
|
||||||
|
/* Save the old tpidr_el0. */
|
||||||
|
mov x19, x8
|
||||||
|
|
||||||
|
/* Restore host tpidr_el0. */
|
||||||
|
ldr x0, [x8, #(TpidrEl0NativeContext)]
|
||||||
|
ldr x3, [x0, #(GuestContextHostContext + HostContextTpidrEl0)]
|
||||||
|
msr tpidr_el0, x3
|
||||||
|
|
||||||
|
/* Call the handler. */
|
||||||
|
bl _ZN4Core6ArmNce22HandleGuestAccessFaultEPNS_12GuestContextEPvS3_
|
||||||
|
|
||||||
/* If the handler returned false, we want to preserve the host tpidr_el0. */
|
/* If the handler returned false, we want to preserve the host tpidr_el0. */
|
||||||
cbz x0, 2f
|
cbz x0, 2f
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
|
|
||||||
#define ReturnToRunCodeByExceptionLevelChangeSignal SIGUSR2
|
#define ReturnToRunCodeByExceptionLevelChangeSignal SIGUSR2
|
||||||
#define BreakFromRunCodeSignal SIGURG
|
#define BreakFromRunCodeSignal SIGURG
|
||||||
#define GuestFaultSignal SIGSEGV
|
#define GuestAccessFaultSignal SIGSEGV
|
||||||
|
#define GuestAlignmentFaultSignal SIGBUS
|
||||||
|
|
||||||
#define GuestContextSp 0xF8
|
#define GuestContextSp 0xF8
|
||||||
#define GuestContextHostContext 0x320
|
#define GuestContextHostContext 0x320
|
||||||
|
|
825
src/core/arm/nce/interpreter_visitor.cpp
Normal file
825
src/core/arm/nce/interpreter_visitor.cpp
Normal file
|
@ -0,0 +1,825 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2023 merryhime <https://mary.rs>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/bit_cast.h"
|
||||||
|
#include "core/arm/nce/interpreter_visitor.h"
|
||||||
|
|
||||||
|
#include <dynarmic/frontend/A64/decoder/a64.h>
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
template <u32 BitSize>
|
||||||
|
u64 SignExtendToLong(u64 value) {
|
||||||
|
u64 mask = 1ULL << (BitSize - 1);
|
||||||
|
value &= (1ULL << BitSize) - 1;
|
||||||
|
return (value ^ mask) - mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 SignExtendToLong(u64 value, u64 bitsize) {
|
||||||
|
switch (bitsize) {
|
||||||
|
case 8:
|
||||||
|
return SignExtendToLong<8>(value);
|
||||||
|
case 16:
|
||||||
|
return SignExtendToLong<16>(value);
|
||||||
|
case 32:
|
||||||
|
return SignExtendToLong<32>(value);
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <u64 BitSize>
|
||||||
|
u32 SignExtendToWord(u32 value) {
|
||||||
|
u32 mask = 1ULL << (BitSize - 1);
|
||||||
|
value &= (1ULL << BitSize) - 1;
|
||||||
|
return (value ^ mask) - mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 SignExtendToWord(u32 value, u64 bitsize) {
|
||||||
|
switch (bitsize) {
|
||||||
|
case 8:
|
||||||
|
return SignExtendToWord<8>(value);
|
||||||
|
case 16:
|
||||||
|
return SignExtendToWord<16>(value);
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 SignExtend(u64 value, u64 bitsize, u64 regsize) {
|
||||||
|
if (regsize == 64) {
|
||||||
|
return SignExtendToLong(value, bitsize);
|
||||||
|
} else {
|
||||||
|
return SignExtendToWord(static_cast<u32>(value), bitsize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static u128 VectorGetElement(u128 value, u64 bitsize) {
|
||||||
|
switch (bitsize) {
|
||||||
|
case 8:
|
||||||
|
return {value[0] & ((1ULL << 8) - 1), 0};
|
||||||
|
case 16:
|
||||||
|
return {value[0] & ((1ULL << 16) - 1), 0};
|
||||||
|
case 32:
|
||||||
|
return {value[0] & ((1ULL << 32) - 1), 0};
|
||||||
|
case 64:
|
||||||
|
return {value[0], 0};
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 InterpreterVisitor::ExtendReg(size_t bitsize, Reg reg, Imm<3> option, u8 shift) {
|
||||||
|
ASSERT(shift <= 4);
|
||||||
|
ASSERT(bitsize == 32 || bitsize == 64);
|
||||||
|
u64 val = this->GetReg(reg);
|
||||||
|
size_t len;
|
||||||
|
u64 extended;
|
||||||
|
bool signed_extend;
|
||||||
|
|
||||||
|
switch (option.ZeroExtend()) {
|
||||||
|
case 0b000: { // UXTB
|
||||||
|
val &= ((1ULL << 8) - 1);
|
||||||
|
len = 8;
|
||||||
|
signed_extend = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0b001: { // UXTH
|
||||||
|
val &= ((1ULL << 16) - 1);
|
||||||
|
len = 16;
|
||||||
|
signed_extend = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0b010: { // UXTW
|
||||||
|
val &= ((1ULL << 32) - 1);
|
||||||
|
len = 32;
|
||||||
|
signed_extend = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0b011: { // UXTX
|
||||||
|
len = 64;
|
||||||
|
signed_extend = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0b100: { // SXTB
|
||||||
|
val &= ((1ULL << 8) - 1);
|
||||||
|
len = 8;
|
||||||
|
signed_extend = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0b101: { // SXTH
|
||||||
|
val &= ((1ULL << 16) - 1);
|
||||||
|
len = 16;
|
||||||
|
signed_extend = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0b110: { // SXTW
|
||||||
|
val &= ((1ULL << 32) - 1);
|
||||||
|
len = 32;
|
||||||
|
signed_extend = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0b111: { // SXTX
|
||||||
|
len = 64;
|
||||||
|
signed_extend = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len < bitsize && signed_extend) {
|
||||||
|
extended = SignExtend(val, len, bitsize);
|
||||||
|
} else {
|
||||||
|
extended = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
return extended << shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
u128 InterpreterVisitor::GetVec(Vec v) {
|
||||||
|
return m_fpsimd_regs[static_cast<u32>(v)];
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 InterpreterVisitor::GetReg(Reg r) {
|
||||||
|
return m_regs[static_cast<u32>(r)];
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 InterpreterVisitor::GetSp() {
|
||||||
|
return m_sp;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 InterpreterVisitor::GetPc() {
|
||||||
|
return m_pc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpreterVisitor::SetVec(Vec v, u128 value) {
|
||||||
|
m_fpsimd_regs[static_cast<u32>(v)] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpreterVisitor::SetReg(Reg r, u64 value) {
|
||||||
|
m_regs[static_cast<u32>(r)] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpreterVisitor::SetSp(u64 value) {
|
||||||
|
m_sp = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::Ordered(size_t size, bool L, bool o0, Reg Rn, Reg Rt) {
|
||||||
|
const auto memop = L ? MemOp::Load : MemOp::Store;
|
||||||
|
const size_t elsize = 8 << size;
|
||||||
|
const size_t datasize = elsize;
|
||||||
|
|
||||||
|
// Operation
|
||||||
|
const size_t dbytes = datasize / 8;
|
||||||
|
|
||||||
|
u64 address;
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
address = this->GetSp();
|
||||||
|
} else {
|
||||||
|
address = this->GetReg(Rn);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (memop) {
|
||||||
|
case MemOp::Store: {
|
||||||
|
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||||
|
u64 value = this->GetReg(Rt);
|
||||||
|
m_memory.WriteBlock(address, &value, dbytes);
|
||||||
|
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MemOp::Load: {
|
||||||
|
u64 value = 0;
|
||||||
|
m_memory.ReadBlock(address, &value, dbytes);
|
||||||
|
this->SetReg(Rt, value);
|
||||||
|
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STLLR(Imm<2> sz, Reg Rn, Reg Rt) {
|
||||||
|
const size_t size = sz.ZeroExtend<size_t>();
|
||||||
|
const bool L = 0;
|
||||||
|
const bool o0 = 0;
|
||||||
|
return this->Ordered(size, L, o0, Rn, Rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STLR(Imm<2> sz, Reg Rn, Reg Rt) {
|
||||||
|
const size_t size = sz.ZeroExtend<size_t>();
|
||||||
|
const bool L = 0;
|
||||||
|
const bool o0 = 1;
|
||||||
|
return this->Ordered(size, L, o0, Rn, Rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::LDLAR(Imm<2> sz, Reg Rn, Reg Rt) {
|
||||||
|
const size_t size = sz.ZeroExtend<size_t>();
|
||||||
|
const bool L = 1;
|
||||||
|
const bool o0 = 0;
|
||||||
|
return this->Ordered(size, L, o0, Rn, Rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::LDAR(Imm<2> sz, Reg Rn, Reg Rt) {
|
||||||
|
const size_t size = sz.ZeroExtend<size_t>();
|
||||||
|
const bool L = 1;
|
||||||
|
const bool o0 = 1;
|
||||||
|
return this->Ordered(size, L, o0, Rn, Rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::LDR_lit_gen(bool opc_0, Imm<19> imm19, Reg Rt) {
|
||||||
|
const size_t size = opc_0 == 0 ? 4 : 8;
|
||||||
|
const s64 offset = Dynarmic::concatenate(imm19, Imm<2>{0}).SignExtend<s64>();
|
||||||
|
const u64 address = this->GetPc() + offset;
|
||||||
|
|
||||||
|
u64 data = 0;
|
||||||
|
m_memory.ReadBlock(address, &data, size);
|
||||||
|
|
||||||
|
this->SetReg(Rt, data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::LDR_lit_fpsimd(Imm<2> opc, Imm<19> imm19, Vec Vt) {
|
||||||
|
if (opc == 0b11) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u64 size = 4 << opc.ZeroExtend();
|
||||||
|
const u64 offset = imm19.SignExtend<u64>() << 2;
|
||||||
|
const u64 address = this->GetPc() + offset;
|
||||||
|
|
||||||
|
u128 data{};
|
||||||
|
m_memory.ReadBlock(address, &data, size);
|
||||||
|
this->SetVec(Vt, data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STP_LDP_gen(Imm<2> opc, bool not_postindex, bool wback, Imm<1> L,
|
||||||
|
Imm<7> imm7, Reg Rt2, Reg Rn, Reg Rt) {
|
||||||
|
if ((L == 0 && opc.Bit<0>() == 1) || opc == 0b11) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto memop = L == 1 ? MemOp::Load : MemOp::Store;
|
||||||
|
if (memop == MemOp::Load && wback && (Rt == Rn || Rt2 == Rn) && Rn != Reg::R31) {
|
||||||
|
// Unpredictable instruction
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (memop == MemOp::Store && wback && (Rt == Rn || Rt2 == Rn) && Rn != Reg::R31) {
|
||||||
|
// Unpredictable instruction
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (memop == MemOp::Load && Rt == Rt2) {
|
||||||
|
// Unpredictable instruction
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 address;
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
address = this->GetSp();
|
||||||
|
} else {
|
||||||
|
address = this->GetReg(Rn);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool postindex = !not_postindex;
|
||||||
|
const bool signed_ = opc.Bit<0>() != 0;
|
||||||
|
const size_t scale = 2 + opc.Bit<1>();
|
||||||
|
const size_t datasize = 8 << scale;
|
||||||
|
const u64 offset = imm7.SignExtend<u64>() << scale;
|
||||||
|
|
||||||
|
if (!postindex) {
|
||||||
|
address += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t dbytes = datasize / 8;
|
||||||
|
switch (memop) {
|
||||||
|
case MemOp::Store: {
|
||||||
|
u64 data1 = this->GetReg(Rt);
|
||||||
|
u64 data2 = this->GetReg(Rt2);
|
||||||
|
m_memory.WriteBlock(address, &data1, dbytes);
|
||||||
|
m_memory.WriteBlock(address + dbytes, &data2, dbytes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MemOp::Load: {
|
||||||
|
u64 data1 = 0, data2 = 0;
|
||||||
|
m_memory.ReadBlock(address, &data1, dbytes);
|
||||||
|
m_memory.ReadBlock(address + dbytes, &data2, dbytes);
|
||||||
|
if (signed_) {
|
||||||
|
this->SetReg(Rt, SignExtend(data1, datasize, 64));
|
||||||
|
this->SetReg(Rt2, SignExtend(data2, datasize, 64));
|
||||||
|
} else {
|
||||||
|
this->SetReg(Rt, data1);
|
||||||
|
this->SetReg(Rt2, data2);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wback) {
|
||||||
|
if (postindex) {
|
||||||
|
address += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
this->SetSp(address);
|
||||||
|
} else {
|
||||||
|
this->SetReg(Rn, address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STP_LDP_fpsimd(Imm<2> opc, bool not_postindex, bool wback, Imm<1> L,
|
||||||
|
Imm<7> imm7, Vec Vt2, Reg Rn, Vec Vt) {
|
||||||
|
if (opc == 0b11) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto memop = L == 1 ? MemOp::Load : MemOp::Store;
|
||||||
|
if (memop == MemOp::Load && Vt == Vt2) {
|
||||||
|
// Unpredictable instruction
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 address;
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
address = this->GetSp();
|
||||||
|
} else {
|
||||||
|
address = this->GetReg(Rn);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool postindex = !not_postindex;
|
||||||
|
const size_t scale = 2 + opc.ZeroExtend<size_t>();
|
||||||
|
const size_t datasize = 8 << scale;
|
||||||
|
const u64 offset = imm7.SignExtend<u64>() << scale;
|
||||||
|
const size_t dbytes = datasize / 8;
|
||||||
|
|
||||||
|
if (!postindex) {
|
||||||
|
address += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (memop) {
|
||||||
|
case MemOp::Store: {
|
||||||
|
u128 data1 = VectorGetElement(this->GetVec(Vt), datasize);
|
||||||
|
u128 data2 = VectorGetElement(this->GetVec(Vt2), datasize);
|
||||||
|
m_memory.WriteBlock(address, &data1, dbytes);
|
||||||
|
m_memory.WriteBlock(address + dbytes, &data2, dbytes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MemOp::Load: {
|
||||||
|
u128 data1{}, data2{};
|
||||||
|
m_memory.ReadBlock(address, &data1, dbytes);
|
||||||
|
m_memory.ReadBlock(address + dbytes, &data2, dbytes);
|
||||||
|
this->SetVec(Vt, data1);
|
||||||
|
this->SetVec(Vt2, data2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wback) {
|
||||||
|
if (postindex) {
|
||||||
|
address += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
this->SetSp(address);
|
||||||
|
} else {
|
||||||
|
this->SetReg(Rn, address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::RegisterImmediate(bool wback, bool postindex, size_t scale, u64 offset,
|
||||||
|
Imm<2> size, Imm<2> opc, Reg Rn, Reg Rt) {
|
||||||
|
MemOp memop;
|
||||||
|
bool signed_ = false;
|
||||||
|
size_t regsize = 0;
|
||||||
|
|
||||||
|
if (opc.Bit<1>() == 0) {
|
||||||
|
memop = opc.Bit<0>() ? MemOp::Load : MemOp::Store;
|
||||||
|
regsize = size == 0b11 ? 64 : 32;
|
||||||
|
signed_ = false;
|
||||||
|
} else if (size == 0b11) {
|
||||||
|
memop = MemOp::Prefetch;
|
||||||
|
ASSERT(!opc.Bit<0>());
|
||||||
|
} else {
|
||||||
|
memop = MemOp::Load;
|
||||||
|
ASSERT(!(size == 0b10 && opc.Bit<0>() == 1));
|
||||||
|
regsize = opc.Bit<0>() ? 32 : 64;
|
||||||
|
signed_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memop == MemOp::Load && wback && Rn == Rt && Rn != Reg::R31) {
|
||||||
|
// Unpredictable instruction
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (memop == MemOp::Store && wback && Rn == Rt && Rn != Reg::R31) {
|
||||||
|
// Unpredictable instruction
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 address;
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
address = this->GetSp();
|
||||||
|
} else {
|
||||||
|
address = this->GetReg(Rn);
|
||||||
|
}
|
||||||
|
if (!postindex) {
|
||||||
|
address += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t datasize = 8 << scale;
|
||||||
|
switch (memop) {
|
||||||
|
case MemOp::Store: {
|
||||||
|
u64 data = this->GetReg(Rt);
|
||||||
|
m_memory.WriteBlock(address, &data, datasize / 8);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MemOp::Load: {
|
||||||
|
u64 data = 0;
|
||||||
|
m_memory.ReadBlock(address, &data, datasize / 8);
|
||||||
|
if (signed_) {
|
||||||
|
this->SetReg(Rt, SignExtend(data, datasize, regsize));
|
||||||
|
} else {
|
||||||
|
this->SetReg(Rt, data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MemOp::Prefetch:
|
||||||
|
// this->Prefetch(address, Rt)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wback) {
|
||||||
|
if (postindex) {
|
||||||
|
address += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
this->SetSp(address);
|
||||||
|
} else {
|
||||||
|
this->SetReg(Rn, address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STRx_LDRx_imm_1(Imm<2> size, Imm<2> opc, Imm<9> imm9, bool not_postindex,
|
||||||
|
Reg Rn, Reg Rt) {
|
||||||
|
const bool wback = true;
|
||||||
|
const bool postindex = !not_postindex;
|
||||||
|
const size_t scale = size.ZeroExtend<size_t>();
|
||||||
|
const u64 offset = imm9.SignExtend<u64>();
|
||||||
|
|
||||||
|
return this->RegisterImmediate(wback, postindex, scale, offset, size, opc, Rn, Rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STRx_LDRx_imm_2(Imm<2> size, Imm<2> opc, Imm<12> imm12, Reg Rn, Reg Rt) {
|
||||||
|
const bool wback = false;
|
||||||
|
const bool postindex = false;
|
||||||
|
const size_t scale = size.ZeroExtend<size_t>();
|
||||||
|
const u64 offset = imm12.ZeroExtend<u64>() << scale;
|
||||||
|
|
||||||
|
return this->RegisterImmediate(wback, postindex, scale, offset, size, opc, Rn, Rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STURx_LDURx(Imm<2> size, Imm<2> opc, Imm<9> imm9, Reg Rn, Reg Rt) {
|
||||||
|
const bool wback = false;
|
||||||
|
const bool postindex = false;
|
||||||
|
const size_t scale = size.ZeroExtend<size_t>();
|
||||||
|
const u64 offset = imm9.SignExtend<u64>();
|
||||||
|
|
||||||
|
return this->RegisterImmediate(wback, postindex, scale, offset, size, opc, Rn, Rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::SIMDImmediate(bool wback, bool postindex, size_t scale, u64 offset,
|
||||||
|
MemOp memop, Reg Rn, Vec Vt) {
|
||||||
|
const size_t datasize = 8 << scale;
|
||||||
|
|
||||||
|
u64 address;
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
address = this->GetSp();
|
||||||
|
} else {
|
||||||
|
address = this->GetReg(Rn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!postindex) {
|
||||||
|
address += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (memop) {
|
||||||
|
case MemOp::Store: {
|
||||||
|
u128 data = VectorGetElement(this->GetVec(Vt), datasize);
|
||||||
|
m_memory.WriteBlock(address, &data, datasize / 8);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MemOp::Load: {
|
||||||
|
u128 data{};
|
||||||
|
m_memory.ReadBlock(address, &data, datasize);
|
||||||
|
this->SetVec(Vt, data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wback) {
|
||||||
|
if (postindex) {
|
||||||
|
address += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
this->SetSp(address);
|
||||||
|
} else {
|
||||||
|
this->SetReg(Rn, address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STR_imm_fpsimd_1(Imm<2> size, Imm<1> opc_1, Imm<9> imm9,
|
||||||
|
bool not_postindex, Reg Rn, Vec Vt) {
|
||||||
|
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||||
|
if (scale > 4) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool wback = true;
|
||||||
|
const bool postindex = !not_postindex;
|
||||||
|
const u64 offset = imm9.SignExtend<u64>();
|
||||||
|
|
||||||
|
return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Store, Rn, Vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STR_imm_fpsimd_2(Imm<2> size, Imm<1> opc_1, Imm<12> imm12, Reg Rn,
|
||||||
|
Vec Vt) {
|
||||||
|
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||||
|
if (scale > 4) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool wback = false;
|
||||||
|
const bool postindex = false;
|
||||||
|
const u64 offset = imm12.ZeroExtend<u64>() << scale;
|
||||||
|
|
||||||
|
return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Store, Rn, Vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::LDR_imm_fpsimd_1(Imm<2> size, Imm<1> opc_1, Imm<9> imm9,
|
||||||
|
bool not_postindex, Reg Rn, Vec Vt) {
|
||||||
|
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||||
|
if (scale > 4) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool wback = true;
|
||||||
|
const bool postindex = !not_postindex;
|
||||||
|
const u64 offset = imm9.SignExtend<u64>();
|
||||||
|
|
||||||
|
return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Load, Rn, Vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::LDR_imm_fpsimd_2(Imm<2> size, Imm<1> opc_1, Imm<12> imm12, Reg Rn,
|
||||||
|
Vec Vt) {
|
||||||
|
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||||
|
if (scale > 4) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool wback = false;
|
||||||
|
const bool postindex = false;
|
||||||
|
const u64 offset = imm12.ZeroExtend<u64>() << scale;
|
||||||
|
|
||||||
|
return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Load, Rn, Vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STUR_fpsimd(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, Reg Rn, Vec Vt) {
|
||||||
|
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||||
|
if (scale > 4) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool wback = false;
|
||||||
|
const bool postindex = false;
|
||||||
|
const u64 offset = imm9.SignExtend<u64>();
|
||||||
|
|
||||||
|
return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Store, Rn, Vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::LDUR_fpsimd(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, Reg Rn, Vec Vt) {
|
||||||
|
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||||
|
if (scale > 4) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool wback = false;
|
||||||
|
const bool postindex = false;
|
||||||
|
const u64 offset = imm9.SignExtend<u64>();
|
||||||
|
|
||||||
|
return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Load, Rn, Vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::RegisterOffset(size_t scale, u8 shift, Imm<2> size, Imm<1> opc_1,
|
||||||
|
Imm<1> opc_0, Reg Rm, Imm<3> option, Reg Rn, Reg Rt) {
|
||||||
|
MemOp memop;
|
||||||
|
size_t regsize = 64;
|
||||||
|
bool signed_ = false;
|
||||||
|
|
||||||
|
if (opc_1 == 0) {
|
||||||
|
memop = opc_0 == 1 ? MemOp::Load : MemOp::Store;
|
||||||
|
regsize = size == 0b11 ? 64 : 32;
|
||||||
|
signed_ = false;
|
||||||
|
} else if (size == 0b11) {
|
||||||
|
memop = MemOp::Prefetch;
|
||||||
|
if (opc_0 == 1) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
memop = MemOp::Load;
|
||||||
|
if (size == 0b10 && opc_0 == 1) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
regsize = opc_0 == 1 ? 32 : 64;
|
||||||
|
signed_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t datasize = 8 << scale;
|
||||||
|
|
||||||
|
// Operation
|
||||||
|
const u64 offset = this->ExtendReg(64, Rm, option, shift);
|
||||||
|
|
||||||
|
u64 address;
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
address = this->GetSp();
|
||||||
|
} else {
|
||||||
|
address = this->GetReg(Rn);
|
||||||
|
}
|
||||||
|
address += offset;
|
||||||
|
|
||||||
|
switch (memop) {
|
||||||
|
case MemOp::Store: {
|
||||||
|
u64 data = this->GetReg(Rt);
|
||||||
|
m_memory.WriteBlock(address, &data, datasize / 8);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MemOp::Load: {
|
||||||
|
u64 data = 0;
|
||||||
|
m_memory.ReadBlock(address, &data, datasize / 8);
|
||||||
|
if (signed_) {
|
||||||
|
this->SetReg(Rt, SignExtend(data, datasize, regsize));
|
||||||
|
} else {
|
||||||
|
this->SetReg(Rt, data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MemOp::Prefetch:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn,
|
||||||
|
Reg Rt) {
|
||||||
|
const Imm<1> opc_0{0};
|
||||||
|
const size_t scale = size.ZeroExtend<size_t>();
|
||||||
|
const u8 shift = S ? static_cast<u8>(scale) : 0;
|
||||||
|
if (!option.Bit<1>()) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this->RegisterOffset(scale, shift, size, opc_1, opc_0, Rm, option, Rn, Rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::LDRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn,
|
||||||
|
Reg Rt) {
|
||||||
|
const Imm<1> opc_0{1};
|
||||||
|
const size_t scale = size.ZeroExtend<size_t>();
|
||||||
|
const u8 shift = S ? static_cast<u8>(scale) : 0;
|
||||||
|
if (!option.Bit<1>()) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this->RegisterOffset(scale, shift, size, opc_1, opc_0, Rm, option, Rn, Rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::SIMDOffset(size_t scale, u8 shift, Imm<1> opc_0, Reg Rm, Imm<3> option,
|
||||||
|
Reg Rn, Vec Vt) {
|
||||||
|
const auto memop = opc_0 == 1 ? MemOp::Load : MemOp::Store;
|
||||||
|
const size_t datasize = 8 << scale;
|
||||||
|
|
||||||
|
// Operation
|
||||||
|
const u64 offset = this->ExtendReg(64, Rm, option, shift);
|
||||||
|
|
||||||
|
u64 address;
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
address = this->GetSp();
|
||||||
|
} else {
|
||||||
|
address = this->GetReg(Rn);
|
||||||
|
}
|
||||||
|
address += offset;
|
||||||
|
|
||||||
|
switch (memop) {
|
||||||
|
case MemOp::Store: {
|
||||||
|
u128 data = VectorGetElement(this->GetVec(Vt), datasize);
|
||||||
|
m_memory.WriteBlock(address, &data, datasize / 8);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MemOp::Load: {
|
||||||
|
u128 data{};
|
||||||
|
m_memory.ReadBlock(address, &data, datasize / 8);
|
||||||
|
this->SetVec(Vt, data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S,
|
||||||
|
Reg Rn, Vec Vt) {
|
||||||
|
const Imm<1> opc_0{0};
|
||||||
|
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||||
|
if (scale > 4) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const u8 shift = S ? static_cast<u8>(scale) : 0;
|
||||||
|
if (!option.Bit<1>()) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this->SIMDOffset(scale, shift, opc_0, Rm, option, Rn, Vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::LDR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S,
|
||||||
|
Reg Rn, Vec Vt) {
|
||||||
|
const Imm<1> opc_0{1};
|
||||||
|
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||||
|
if (scale > 4) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const u8 shift = S ? static_cast<u8>(scale) : 0;
|
||||||
|
if (!option.Bit<1>()) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this->SIMDOffset(scale, shift, opc_0, Rm, option, Rn, Vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<u64> MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, mcontext_t* context,
|
||||||
|
fpsimd_context* fpsimd_context) {
|
||||||
|
// Construct the interpreter.
|
||||||
|
std::span<u64, 31> regs(reinterpret_cast<u64*>(context->regs), 31);
|
||||||
|
std::span<u128, 32> vregs(reinterpret_cast<u128*>(fpsimd_context->vregs), 32);
|
||||||
|
u64& sp = *reinterpret_cast<u64*>(&context->sp);
|
||||||
|
const u64& pc = *reinterpret_cast<u64*>(&context->pc);
|
||||||
|
|
||||||
|
InterpreterVisitor visitor(memory, regs, vregs, sp, pc);
|
||||||
|
|
||||||
|
// Read the instruction at the program counter.
|
||||||
|
u32 instruction = memory.Read32(pc);
|
||||||
|
bool was_executed = false;
|
||||||
|
|
||||||
|
// Interpret the instruction.
|
||||||
|
if (auto decoder = Dynarmic::A64::Decode<VisitorBase>(instruction)) {
|
||||||
|
was_executed = decoder->get().call(visitor, instruction);
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Core_ARM, "Unallocated encoding: {:#x}", instruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (was_executed) {
|
||||||
|
return pc + 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Core
|
103
src/core/arm/nce/interpreter_visitor.h
Normal file
103
src/core/arm/nce/interpreter_visitor.h
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2023 merryhime <https://mary.rs>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "core/arm/nce/visitor_base.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
namespace Memory {
|
||||||
|
class Memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
class InterpreterVisitor final : public VisitorBase {
|
||||||
|
public:
|
||||||
|
explicit InterpreterVisitor(Core::Memory::Memory& memory, std::span<u64, 31> regs,
|
||||||
|
std::span<u128, 32> fpsimd_regs, u64& sp, const u64& pc)
|
||||||
|
: m_memory(memory), m_regs(regs), m_fpsimd_regs(fpsimd_regs), m_sp(sp), m_pc(pc) {}
|
||||||
|
~InterpreterVisitor() override = default;
|
||||||
|
|
||||||
|
enum class MemOp {
|
||||||
|
Load,
|
||||||
|
Store,
|
||||||
|
Prefetch,
|
||||||
|
};
|
||||||
|
|
||||||
|
u128 GetVec(Vec v);
|
||||||
|
u64 GetReg(Reg r);
|
||||||
|
u64 GetSp();
|
||||||
|
u64 GetPc();
|
||||||
|
|
||||||
|
void SetVec(Vec v, u128 value);
|
||||||
|
void SetReg(Reg r, u64 value);
|
||||||
|
void SetSp(u64 value);
|
||||||
|
|
||||||
|
u64 ExtendReg(size_t bitsize, Reg reg, Imm<3> option, u8 shift);
|
||||||
|
|
||||||
|
// Loads and stores - Load/Store Exclusive
|
||||||
|
bool Ordered(size_t size, bool L, bool o0, Reg Rn, Reg Rt);
|
||||||
|
bool STLLR(Imm<2> size, Reg Rn, Reg Rt) override;
|
||||||
|
bool STLR(Imm<2> size, Reg Rn, Reg Rt) override;
|
||||||
|
bool LDLAR(Imm<2> size, Reg Rn, Reg Rt) override;
|
||||||
|
bool LDAR(Imm<2> size, Reg Rn, Reg Rt) override;
|
||||||
|
|
||||||
|
// Loads and stores - Load register (literal)
|
||||||
|
bool LDR_lit_gen(bool opc_0, Imm<19> imm19, Reg Rt) override;
|
||||||
|
bool LDR_lit_fpsimd(Imm<2> opc, Imm<19> imm19, Vec Vt) override;
|
||||||
|
|
||||||
|
// Loads and stores - Load/Store register pair
|
||||||
|
bool STP_LDP_gen(Imm<2> opc, bool not_postindex, bool wback, Imm<1> L, Imm<7> imm7, Reg Rt2,
|
||||||
|
Reg Rn, Reg Rt) override;
|
||||||
|
bool STP_LDP_fpsimd(Imm<2> opc, bool not_postindex, bool wback, Imm<1> L, Imm<7> imm7, Vec Vt2,
|
||||||
|
Reg Rn, Vec Vt) override;
|
||||||
|
|
||||||
|
// Loads and stores - Load/Store register (immediate)
|
||||||
|
bool RegisterImmediate(bool wback, bool postindex, size_t scale, u64 offset, Imm<2> size,
|
||||||
|
Imm<2> opc, Reg Rn, Reg Rt);
|
||||||
|
bool STRx_LDRx_imm_1(Imm<2> size, Imm<2> opc, Imm<9> imm9, bool not_postindex, Reg Rn,
|
||||||
|
Reg Rt) override;
|
||||||
|
bool STRx_LDRx_imm_2(Imm<2> size, Imm<2> opc, Imm<12> imm12, Reg Rn, Reg Rt) override;
|
||||||
|
bool STURx_LDURx(Imm<2> size, Imm<2> opc, Imm<9> imm9, Reg Rn, Reg Rt) override;
|
||||||
|
|
||||||
|
bool SIMDImmediate(bool wback, bool postindex, size_t scale, u64 offset, MemOp memop, Reg Rn,
|
||||||
|
Vec Vt);
|
||||||
|
bool STR_imm_fpsimd_1(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, bool not_postindex, Reg Rn,
|
||||||
|
Vec Vt) override;
|
||||||
|
bool STR_imm_fpsimd_2(Imm<2> size, Imm<1> opc_1, Imm<12> imm12, Reg Rn, Vec Vt) override;
|
||||||
|
bool LDR_imm_fpsimd_1(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, bool not_postindex, Reg Rn,
|
||||||
|
Vec Vt) override;
|
||||||
|
bool LDR_imm_fpsimd_2(Imm<2> size, Imm<1> opc_1, Imm<12> imm12, Reg Rn, Vec Vt) override;
|
||||||
|
bool STUR_fpsimd(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, Reg Rn, Vec Vt) override;
|
||||||
|
bool LDUR_fpsimd(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, Reg Rn, Vec Vt) override;
|
||||||
|
|
||||||
|
// Loads and stores - Load/Store register (register offset)
|
||||||
|
bool RegisterOffset(size_t scale, u8 shift, Imm<2> size, Imm<1> opc_1, Imm<1> opc_0, Reg Rm,
|
||||||
|
Imm<3> option, Reg Rn, Reg Rt);
|
||||||
|
bool STRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn,
|
||||||
|
Reg Rt) override;
|
||||||
|
bool LDRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn,
|
||||||
|
Reg Rt) override;
|
||||||
|
|
||||||
|
bool SIMDOffset(size_t scale, u8 shift, Imm<1> opc_0, Reg Rm, Imm<3> option, Reg Rn, Vec Vt);
|
||||||
|
bool STR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn,
|
||||||
|
Vec Vt) override;
|
||||||
|
bool LDR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn,
|
||||||
|
Vec Vt) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Core::Memory::Memory& m_memory;
|
||||||
|
std::span<u64, 31> m_regs;
|
||||||
|
std::span<u128, 32> m_fpsimd_regs;
|
||||||
|
u64& m_sp;
|
||||||
|
const u64& m_pc;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<u64> MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, mcontext_t* context,
|
||||||
|
fpsimd_context* fpsimd_context);
|
||||||
|
|
||||||
|
} // namespace Core
|
2777
src/core/arm/nce/visitor_base.h
Normal file
2777
src/core/arm/nce/visitor_base.h
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue