mirror of
				https://git.suyu.dev/suyu/suyu.git
				synced 2025-10-31 06:46:40 +08:00 
			
		
		
		
	nce: implement instruction emulation for misaligned memory accesses
This commit is contained in:
		
							parent
							
								
									11b123ba01
								
							
						
					
					
						commit
						bd59934350
					
				| @ -174,7 +174,8 @@ android { | ||||
|                     "-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work | ||||
|                     "-DYUZU_USE_BUNDLED_VCPKG=ON", | ||||
|                     "-DYUZU_USE_BUNDLED_FFMPEG=ON", | ||||
|                     "-DYUZU_ENABLE_LTO=ON" | ||||
|                     "-DYUZU_ENABLE_LTO=ON", | ||||
|                     "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON" | ||||
|                 ) | ||||
| 
 | ||||
|                 abiFilters("arm64-v8a", "x86_64") | ||||
|  | ||||
| @ -947,15 +947,19 @@ if (HAS_NCE) | ||||
|     set(CMAKE_ASM_FLAGS "${CFLAGS} -x assembler-with-cpp") | ||||
| 
 | ||||
|     target_sources(core PRIVATE | ||||
|         arm/nce/arm_nce_asm_definitions.h | ||||
|         arm/nce/arm_nce.cpp | ||||
|         arm/nce/arm_nce.h | ||||
|         arm/nce/arm_nce.s | ||||
|         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.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() | ||||
| 
 | ||||
| if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) | ||||
|  | ||||
| @ -6,7 +6,7 @@ | ||||
| 
 | ||||
| #include "common/signal_chain.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/core.h" | ||||
| #include "core/memory.h" | ||||
| @ -21,7 +21,8 @@ namespace Core { | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| struct sigaction g_orig_action; | ||||
| struct sigaction g_orig_bus_action; | ||||
| struct sigaction g_orig_segv_action; | ||||
| 
 | ||||
| // Verify assembly offsets.
 | ||||
| using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters; | ||||
| @ -37,6 +38,9 @@ fpsimd_context* GetFloatingPointState(mcontext_t& host_ctx) { | ||||
|     return reinterpret_cast<fpsimd_context*>(header); | ||||
| } | ||||
| 
 | ||||
| using namespace Common::Literals; | ||||
| constexpr u32 StackSize = 32_KiB; | ||||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
| 
 | ||||
| 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* 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.
 | ||||
|     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; | ||||
| } | ||||
| 
 | ||||
| void ArmNce::HandleHostFault(int sig, void* raw_info, void* raw_context) { | ||||
|     return g_orig_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context); | ||||
| bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info, void* 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) { | ||||
| @ -220,6 +251,9 @@ void ArmNce::SetSvcArguments(std::span<const uint64_t, 8> args) { | ||||
| ArmNce::ArmNce(System& system, bool uses_wall_clock, std::size_t core_index) | ||||
|     : ArmInterface{uses_wall_clock}, m_system{system}, m_core_index{core_index} { | ||||
|     m_guest_ctx.system = &m_system; | ||||
| 
 | ||||
|     // Allocate signal stack.
 | ||||
|     m_stack = std::make_unique<u8[]>(StackSize); | ||||
| } | ||||
| 
 | ||||
| ArmNce::~ArmNce() = default; | ||||
| @ -227,16 +261,23 @@ ArmNce::~ArmNce() = default; | ||||
| void ArmNce::Initialize() { | ||||
|     m_thread_id = gettid(); | ||||
| 
 | ||||
|     // Setup our signals
 | ||||
|     static std::once_flag signals; | ||||
|     std::call_once(signals, [] { | ||||
|     // Configure signal stack.
 | ||||
|     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); | ||||
| 
 | ||||
|         sigset_t signal_mask; | ||||
|         sigemptyset(&signal_mask); | ||||
|         sigaddset(&signal_mask, ReturnToRunCodeByExceptionLevelChangeSignal); | ||||
|         sigaddset(&signal_mask, BreakFromRunCodeSignal); | ||||
|         sigaddset(&signal_mask, GuestFaultSignal); | ||||
|         sigaddset(&signal_mask, GuestAlignmentFaultSignal); | ||||
|         sigaddset(&signal_mask, GuestAccessFaultSignal); | ||||
| 
 | ||||
|         struct sigaction return_to_run_code_action {}; | ||||
|         return_to_run_code_action.sa_flags = SA_SIGINFO | SA_ONSTACK; | ||||
| @ -253,18 +294,19 @@ void ArmNce::Initialize() { | ||||
|         break_from_run_code_action.sa_mask = signal_mask; | ||||
|         Common::SigAction(BreakFromRunCodeSignal, &break_from_run_code_action, nullptr); | ||||
| 
 | ||||
|         struct sigaction fault_action {}; | ||||
|         fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART; | ||||
|         fault_action.sa_sigaction = reinterpret_cast<HandlerType>(&ArmNce::GuestFaultSignalHandler); | ||||
|         fault_action.sa_mask = signal_mask; | ||||
|         Common::SigAction(GuestFaultSignal, &fault_action, &g_orig_action); | ||||
|         struct sigaction alignment_fault_action {}; | ||||
|         alignment_fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK; | ||||
|         alignment_fault_action.sa_sigaction = | ||||
|             reinterpret_cast<HandlerType>(&ArmNce::GuestAlignmentFaultSignalHandler); | ||||
|         alignment_fault_action.sa_mask = signal_mask; | ||||
|         Common::SigAction(GuestAlignmentFaultSignal, &alignment_fault_action, nullptr); | ||||
| 
 | ||||
|         // Simplify call for g_orig_action.
 | ||||
|         // These fields occupy the same space in memory, so this should be a no-op in practice.
 | ||||
|         if (!(g_orig_action.sa_flags & SA_SIGINFO)) { | ||||
|             g_orig_action.sa_sigaction = | ||||
|                 reinterpret_cast<decltype(g_orig_action.sa_sigaction)>(g_orig_action.sa_handler); | ||||
|         } | ||||
|         struct sigaction access_fault_action {}; | ||||
|         access_fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART; | ||||
|         access_fault_action.sa_sigaction = | ||||
|             reinterpret_cast<HandlerType>(&ArmNce::GuestAccessFaultSignalHandler); | ||||
|         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, | ||||
|                                                                    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 UnlockThreadParameters(void* tpidr); | ||||
| @ -70,8 +71,11 @@ private: | ||||
|     // C++ implementation functions for assembly definitions.
 | ||||
|     static void* RestoreGuestContext(void* raw_context); | ||||
|     static void SaveGuestContext(GuestContext* ctx, void* raw_context); | ||||
|     static bool HandleGuestFault(GuestContext* ctx, void* info, void* raw_context); | ||||
|     static void HandleHostFault(int sig, void* info, void* raw_context); | ||||
|     static bool HandleFailedGuestFault(GuestContext* ctx, 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: | ||||
|     Core::System& m_system; | ||||
| @ -83,6 +87,9 @@ public: | ||||
|     // Core context.
 | ||||
|     GuestContext m_guest_ctx{}; | ||||
|     Kernel::KThread* m_running_thread{}; | ||||
| 
 | ||||
|     // Stack for signal processing.
 | ||||
|     std::unique_ptr<u8[]> m_stack{}; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Core
 | ||||
|  | ||||
| @ -130,11 +130,11 @@ _ZN4Core6ArmNce29BreakFromRunCodeSignalHandlerEiPvS1_: | ||||
|     ret | ||||
| 
 | ||||
| 
 | ||||
| /* static void Core::ArmNce::GuestFaultSignalHandler(int sig, void* info, void* raw_context) */ | ||||
| .section    .text._ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_, "ax", %progbits | ||||
| .global     _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_
 | ||||
| .type       _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_, %function | ||||
| _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_: | ||||
| /* static void Core::ArmNce::GuestAlignmentFaultSignalHandler(int sig, void* info, void* raw_context) */ | ||||
| .section    .text._ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_, "ax", %progbits | ||||
| .global     _ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_
 | ||||
| .type       _ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_, %function | ||||
| _ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_: | ||||
|     /* Check to see if we have the correct TLS magic. */ | ||||
|     mrs     x8, tpidr_el0 | ||||
|     ldr     w9, [x8, #(TpidrEl0TlsMagic)] | ||||
| @ -146,7 +146,7 @@ _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_: | ||||
| 
 | ||||
|     /* Incorrect TLS magic, so this is a host fault. */ | ||||
|     /* Tail call the handler. */ | ||||
|     b       _ZN4Core6ArmNce15HandleHostFaultEiPvS1_ | ||||
|     b       _ZN4Core6ArmNce24HandleHostAlignmentFaultEiPvS1_ | ||||
| 
 | ||||
| 1: | ||||
|     /* Correct TLS magic, so this is a guest fault. */ | ||||
| @ -163,7 +163,53 @@ _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_: | ||||
|     msr     tpidr_el0, x3 | ||||
| 
 | ||||
|     /* 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. */ | ||||
|     cbz     x0, 2f | ||||
|  | ||||
| @ -10,7 +10,8 @@ | ||||
| 
 | ||||
| #define ReturnToRunCodeByExceptionLevelChangeSignal SIGUSR2 | ||||
| #define BreakFromRunCodeSignal SIGURG | ||||
| #define GuestFaultSignal SIGSEGV | ||||
| #define GuestAccessFaultSignal SIGSEGV | ||||
| #define GuestAlignmentFaultSignal SIGBUS | ||||
| 
 | ||||
| #define GuestContextSp 0xF8 | ||||
| #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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user