Browse Source

[core] Windows NCE because why the fuck not

This implementation is mostly a recommendation for now; MSVC hates inline assembly and disallows it completely it on arm64
remotes/1785372757367212240/tmp_refs/heads/variable-page-size
Exverge 1 month ago
parent
commit
301c030234
No known key found for this signature in database GPG Key ID: DAD399BCC5FB77E4
  1. 2
      CMakeLists.txt
  2. 2
      src/common/vector_math.h
  3. 8
      src/core/CMakeLists.txt
  4. 85
      src/core/arm/nce/arm_nce.cpp
  5. 12
      src/core/arm/nce/arm_nce.h
  6. 56
      src/core/arm/nce/guest_context.h
  7. 79
      src/core/arm/nce/patcher.cpp
  8. 3
      src/core/arm/nce/patcher.h
  9. 29
      src/core/arm/nce/win/exceptions.cpp
  10. 19
      src/core/arm/nce/win/exceptions.h
  11. 17
      src/core/arm/nce/win/platform_visitor.cpp
  12. 1361
      src/core/arm/nce/win/platform_visitor.h
  13. 2
      src/core/hle/kernel/k_thread.h
  14. 2
      src/dynarmic/src/dynarmic/interface/code_page.h

2
CMakeLists.txt

@ -300,7 +300,7 @@ if (NOT EXISTS ${PROJECT_BINARY_DIR}/${compat_json})
file(WRITE ${PROJECT_BINARY_DIR}/${compat_json} "") file(WRITE ${PROJECT_BINARY_DIR}/${compat_json} "")
endif() endif()
if (ARCHITECTURE_arm64 AND (ANDROID OR PLATFORM_LINUX))
if (ARCHITECTURE_arm64 AND (ANDROID OR PLATFORM_LINUX OR WIN32))
set(HAS_NCE 1) set(HAS_NCE 1)
add_compile_definitions(HAS_NCE=1) add_compile_definitions(HAS_NCE=1)
endif() endif()

2
src/common/vector_math.h

@ -654,7 +654,7 @@ template <>
float32x4_t va = vld1q_f32(&a.x); float32x4_t va = vld1q_f32(&a.x);
float32x4_t vb = vld1q_f32(&b.x); float32x4_t vb = vld1q_f32(&b.x);
float32x4_t result = vmulq_f32(va, vb); float32x4_t result = vmulq_f32(va, vb);
#if defined(__aarch64__) // Use vaddvq_f32 in ARMv8 architectures
#if defined(ARCHITECTURE_arm64) // Use vaddvq_f32 in ARMv8 architectures
return vaddvq_f32(result); return vaddvq_f32(result);
#else // Use manual addition for older architectures #else // Use manual addition for older architectures
float32x2_t sum2 = vadd_f32(vget_high_f32(result), vget_low_f32(result)); float32x2_t sum2 = vadd_f32(vget_high_f32(result), vget_low_f32(result));

8
src/core/CMakeLists.txt

@ -1247,6 +1247,14 @@ if (HAS_NCE)
arm/nce/patcher.h arm/nce/patcher.h
arm/nce/visitor_base.h) arm/nce/visitor_base.h)
target_link_libraries(core PRIVATE merry::oaknut) target_link_libraries(core PRIVATE merry::oaknut)
if (WIN32)
target_sources(core PRIVATE
arm/nce/win/platform_visitor.h
arm/nce/win/platform_visitor.cpp
arm/nce/win/exceptions.h
arm/nce/win/exceptions.cpp)
endif ()
endif() endif()
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64 OR ARCHITECTURE_loongarch64) if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64 OR ARCHITECTURE_loongarch64)

85
src/core/arm/nce/arm_nce.cpp

@ -4,7 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#ifdef __aarch64__
#ifdef ARCHITECTURE_arm64
// Certain functions have to be marked naked so that the compiler doesn't touch the stack // Certain functions have to be marked naked so that the compiler doesn't touch the stack
// or implement a return (we "artificially" return later by setting PC to the LR value) // or implement a return (we "artificially" return later by setting PC to the LR value)
@ -14,15 +14,12 @@
_Pragma("GCC diagnostic ignored \"-Wreturn-type\"") \ _Pragma("GCC diagnostic ignored \"-Wreturn-type\"") \
__attribute__((naked)) __attribute__((naked))
#elif defined(_MSC_VER) #elif defined(_MSC_VER)
// todo: windows support?? it supports native context switching and signal handling
// https://learn.microsoft.com/en-us/windows/win32/debug/using-a-vectored-exception-handler
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadcontext
#define YUZU_NAKED __declspec(naked) #define YUZU_NAKED __declspec(naked)
#else #else
#error Unsupported compiler #error Unsupported compiler
#endif #endif
#if defined(__GNUC__)
#if defined(__clang__) || defined(__GNUC__)
#define YUZU_NAKED_END _Pragma("GCC diagnostic pop") #define YUZU_NAKED_END _Pragma("GCC diagnostic pop")
#else #else
#define YUZU_NAKED_END #define YUZU_NAKED_END
@ -40,18 +37,23 @@
#include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_process.h"
#ifndef __WIN32
#include <unistd.h> #include <unistd.h>
#include <sys/syscall.h> #include <sys/syscall.h>
#include <signal.h> #include <signal.h>
#else
#include "core/arm/nce/win/exceptions.h"
#endif
namespace Core { namespace Core {
namespace { namespace {
#ifndef __WIN32
struct sigaction g_orig_bus_action; struct sigaction g_orig_bus_action;
struct sigaction g_orig_segv_action; struct sigaction g_orig_segv_action;
#endif
// Verify assembly offsets.
using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters; using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
using namespace Common::Literals; using namespace Common::Literals;
@ -62,15 +64,22 @@ constexpr u32 StackSize = 128_KiB;
YUZU_ALWAYS_INLINE YUZU_ALWAYS_INLINE
void* ArmNce::GetGuestParameters() { void* ArmNce::GetGuestParameters() {
void* nep; /* NativeExecutionParameters* */ void* nep; /* NativeExecutionParameters* */
#ifdef __APPLE__
#if defined(__APPLE__)
// https://github.com/apple-oss-distributions/xnu/blob/f6217f891ac0bb64f3d375211650a4c1ff8ca1ea/libsyscall/os/tsd.h#L156-L189 // https://github.com/apple-oss-distributions/xnu/blob/f6217f891ac0bb64f3d375211650a4c1ff8ca1ea/libsyscall/os/tsd.h#L156-L189
asm volatile( asm volatile(
"mrs %[out], TPIDRRO_EL0\n" // load pthreads TLS storage "mrs %[out], TPIDRRO_EL0\n" // load pthreads TLS storage
"ldr %[out], [ %[out], #%[off] ]\n" // accessed like an array, so i * sizeof(u64) "ldr %[out], [ %[out], #%[off] ]\n" // accessed like an array, so i * sizeof(u64)
: [out] "=&r"(nep) : [out] "=&r"(nep)
: [off] "i"(CONTEXT_KEY * 8)
: [off] "i"((ContextKey - 1) * 8)
: "memory"); : "memory");
#else
#elif defined(__WIN32)
asm volatile(
"mrs %[out], TPIDR_EL0\n" // load windows TLS storage
"ldr %[out], [ %[out], #%[off] ]\n"
: [out] "=&r"(nep)
: [off] "i"(TlsSlots + 8 * ContextKey)
: "memory");
#elif defined(__linux__)
asm volatile( asm volatile(
"mrs %0, TPIDR_EL0\n" "mrs %0, TPIDR_EL0\n"
: "=r"(nep)); : "=r"(nep));
@ -137,7 +146,7 @@ void ArmNce::ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void *i
auto tpidr = static_cast<NativeExecutionParameters*>(RestoreGuestContext(raw_context)); auto tpidr = static_cast<NativeExecutionParameters*>(RestoreGuestContext(raw_context));
RestoreGuestContext(raw_context); RestoreGuestContext(raw_context);
#ifndef __APPLE__
#if !defined(__APPLE__) && !defined(__WIN32)
// Save old value of TPIDR_EL0, load guest one // Save old value of TPIDR_EL0, load guest one
u64 tpidr_el0; u64 tpidr_el0;
asm volatile("mrs %0, TPIDR_EL0\n" asm volatile("mrs %0, TPIDR_EL0\n"
@ -169,7 +178,7 @@ HaltReason ArmNce::ReturnToRunCodeByTrampoline(void *tpidr, u64 trampoline_addr)
"ldr x2, [ x0, #%[ctx_off] ]\n" "ldr x2, [ x0, #%[ctx_off] ]\n"
"add x5, x2, #%[host_ctx] \n" "add x5, x2, #%[host_ctx] \n"
#ifndef __APPLE__
#if !defined(__APPLE__) && !defined(__WIN32)
// Load guest tpidr_el0 // Load guest tpidr_el0
"mrs x4, TPIDR_EL0\n" "mrs x4, TPIDR_EL0\n"
"msr TPIDR_EL0, x0\n" "msr TPIDR_EL0, x0\n"
@ -206,7 +215,7 @@ HaltReason ArmNce::ReturnToRunCodeByTrampoline(void *tpidr, u64 trampoline_addr)
:: [ctx_off] "i"(offsetof(NativeExecutionParameters, native_context)), :: [ctx_off] "i"(offsetof(NativeExecutionParameters, native_context)),
[sp_off] "i"(offsetof(GuestContext, sp)), [sp_off] "i"(offsetof(GuestContext, sp)),
[host_ctx] "i"(offsetof(GuestContext, host_ctx)) [host_ctx] "i"(offsetof(GuestContext, host_ctx))
#ifdef __APPLE__
#if defined(__APPLE__) || defined(__WIN32)
,[is_running_off] "i"(offsetof(NativeExecutionParameters, is_actually_running)) ,[is_running_off] "i"(offsetof(NativeExecutionParameters, is_actually_running))
#endif #endif
); );
@ -217,7 +226,7 @@ static_assert(offsetof(HostContext, host_sp) == 0xE0); // TODO: don't use magic
void ArmNce::BreakFromRunCodeSignalHandler(int sig, void *info, void *raw_context) { void ArmNce::BreakFromRunCodeSignalHandler(int sig, void *info, void *raw_context) {
NativeExecutionParameters* tpidr = reinterpret_cast<NativeExecutionParameters *>(GetGuestParameters()); NativeExecutionParameters* tpidr = reinterpret_cast<NativeExecutionParameters *>(GetGuestParameters());
#ifdef __APPLE__
#if defined(__APPLE__) || defined(__WIN32)
if (tpidr->is_actually_running) { if (tpidr->is_actually_running) {
tpidr->is_actually_running = false; tpidr->is_actually_running = false;
#else #else
@ -234,11 +243,11 @@ void ArmNce::BreakFromRunCodeSignalHandler(int sig, void *info, void *raw_contex
} }
void ArmNce::GuestMemoryFaultSignalHandler(int sig, void* raw_info, void* raw_context) { void ArmNce::GuestMemoryFaultSignalHandler(int sig, void* raw_info, void* raw_context) {
DEBUG_ASSERT(sig == SIGSEGV);
DEBUG_ASSERT(sig == SIGSEGV || sig == SIGBUS);
NativeExecutionParameters* nep = static_cast<NativeExecutionParameters*>(GetGuestParameters()); NativeExecutionParameters* nep = static_cast<NativeExecutionParameters*>(GetGuestParameters());
#ifdef __APPLE__
#if defined(__APPLE__) || defined(__WIN32)
if (nep->is_actually_running) { if (nep->is_actually_running) {
nep->is_actually_running = false; nep->is_actually_running = false;
#else #else
@ -250,22 +259,26 @@ void ArmNce::GuestMemoryFaultSignalHandler(int sig, void* raw_info, void* raw_co
:: [host_tpidr] "r"(host_tpidr)); :: [host_tpidr] "r"(host_tpidr));
#endif #endif
auto* info = static_cast<siginfo_t*>(raw_info);
auto* guest_ctx = static_cast<GuestContext*>(nep->native_context); auto* guest_ctx = static_cast<GuestContext*>(nep->native_context);
auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory(); auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory();
if (sig == SIGSEGV) { if (sig == SIGSEGV) {
// Try to handle an invalid access. // Try to handle an invalid access.
// TODO: handle accesses which split a page? // TODO: handle accesses which split a page?
#ifndef __WIN32
const Common::ProcessAddress addr =
(reinterpret_cast<u64>(static_cast<siginfo_t*>(raw_info)->si_addr) & ~Memory::YUZU_PAGEMASK);
#else
const Common::ProcessAddress addr = const Common::ProcessAddress addr =
(reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
(reinterpret_cast<u64>(*static_cast<u64*>(raw_info)) & ~Memory::YUZU_PAGEMASK);
#endif
if (memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) { if (memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) {
// We handled the access successfully and are returning to guest code. // We handled the access successfully and are returning to guest code.
goto ret; goto ret;
} }
} else if (sig == SIGBUS) { } else if (sig == SIGBUS) {
// Match and execute an instruction. // Match and execute an instruction.
auto ctx = KernelContext(&static_cast<ucontext_t*>(raw_context)->uc_mcontext);
auto ctx = KernelContext(raw_context);
auto next_pc = MatchAndExecuteOneInstruction(memory, &ctx); auto next_pc = MatchAndExecuteOneInstruction(memory, &ctx);
if (next_pc) { if (next_pc) {
// We handled the access successfully and are returning to guest code. // We handled the access successfully and are returning to guest code.
@ -292,7 +305,9 @@ void ArmNce::GuestMemoryFaultSignalHandler(int sig, void* raw_info, void* raw_co
#else #else
nep->is_actually_running = true; nep->is_actually_running = true;
#endif #endif
} else {
}
#ifndef __WIN32
else {
// Host fault, call original handler // Host fault, call original handler
if (sig == SIGSEGV) { if (sig == SIGSEGV) {
g_orig_segv_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context); g_orig_segv_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
@ -302,11 +317,14 @@ void ArmNce::GuestMemoryFaultSignalHandler(int sig, void* raw_info, void* raw_co
UNREACHABLE_MSG("unexpected signal {}", sig); UNREACHABLE_MSG("unexpected signal {}", sig);
} }
} }
#elif
is_host_fault = true;
#endif
} }
void* ArmNce::RestoreGuestContext(void* raw_context) { void* ArmNce::RestoreGuestContext(void* raw_context) {
// Retrieve the host context. // Retrieve the host context.
auto host_ctx = KernelContext(&static_cast<ucontext_t*>(raw_context)->uc_mcontext);
auto host_ctx = KernelContext(raw_context);
// Thread-local parameters will be located in x9. // Thread-local parameters will be located in x9.
auto* tpidr = reinterpret_cast<NativeExecutionParameters*>(host_ctx.regs()[9]); auto* tpidr = reinterpret_cast<NativeExecutionParameters*>(host_ctx.regs()[9]);
@ -336,7 +354,7 @@ void* ArmNce::RestoreGuestContext(void* raw_context) {
void ArmNce::SaveGuestContext(GuestContext* guest_ctx, void* raw_context) { void ArmNce::SaveGuestContext(GuestContext* guest_ctx, void* raw_context) {
// Retrieve the host context. // Retrieve the host context.
auto host_ctx = KernelContext(&static_cast<ucontext_t*>(raw_context)->uc_mcontext);
auto host_ctx = KernelContext(raw_context);
// Save all guest registers except tpidr_el0. // Save all guest registers except tpidr_el0.
std::memcpy(guest_ctx->cpu_registers.data(), host_ctx.regs(), sizeof(guest_ctx->cpu_registers)); std::memcpy(guest_ctx->cpu_registers.data(), host_ctx.regs(), sizeof(guest_ctx->cpu_registers));
@ -364,11 +382,10 @@ void ArmNce::SaveGuestContext(GuestContext* guest_ctx, void* raw_context) {
} }
bool ArmNce::HandleFailedGuestFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) { bool ArmNce::HandleFailedGuestFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
auto host_ctx = KernelContext(&static_cast<ucontext_t*>(raw_context)->uc_mcontext);
auto* info = static_cast<siginfo_t*>(raw_info);
auto host_ctx = KernelContext(raw_context);
// 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>(static_cast<siginfo_t*>(raw_info)->si_addr);
// For data aborts, skip the instruction and return to guest code. // For data aborts, skip the instruction and return to guest code.
// This will allow games to continue in many scenarios where they would otherwise crash. // This will allow games to continue in many scenarios where they would otherwise crash.
@ -419,7 +436,9 @@ HaltReason ArmNce::RunThread(Kernel::KThread* thread) {
auto* process = thread->GetOwnerProcess(); auto* process = thread->GetOwnerProcess();
#ifdef __APPLE__ #ifdef __APPLE__
ASSERT(pthread_setspecific(CONTEXT_KEY, &thread_params) == 0);
ASSERT(pthread_setspecific(ContextKey, &thread_params) == 0);
#elif
ASSERT(TlsSetValue(ContextKey, &thread_params) == 0);
#endif #endif
// Move non-critical operations outside the locked section // Move non-critical operations outside the locked section
@ -502,11 +521,16 @@ void ArmNce::Initialize() {
m_thread_id = pthread_mach_thread_np(pthread_self()); m_thread_id = pthread_mach_thread_np(pthread_self());
} }
ASSERT(pthread_key_init_np(CONTEXT_KEY, [](void*) -> void {}) == 0);
ASSERT(pthread_key_init_np(ContextKey, [](void*) -> void {}) == 0);
#elif defined(__linux__) #elif defined(__linux__)
if (m_thread_id == -1) { if (m_thread_id == -1) {
m_thread_id = gettid(); m_thread_id = gettid();
} }
#elif defined(__WIN32)
if (m_thread_id == nullptr) {
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(),
&m_thread_id, 0, false, DUPLICATE_SAME_ACCESS);
}
#endif #endif
// Configure signal stack. // Configure signal stack.
@ -522,6 +546,7 @@ void ArmNce::Initialize() {
// Set up signals. // Set up signals.
static std::once_flag flag; static std::once_flag flag;
std::call_once(flag, [] { std::call_once(flag, [] {
#ifndef __WIN32
using HandlerType = decltype(sigaction::sa_sigaction); using HandlerType = decltype(sigaction::sa_sigaction);
sigset_t signal_mask; sigset_t signal_mask;
@ -559,6 +584,9 @@ void ArmNce::Initialize() {
reinterpret_cast<HandlerType>(&ArmNce::GuestMemoryFaultSignalHandler); reinterpret_cast<HandlerType>(&ArmNce::GuestMemoryFaultSignalHandler);
access_fault_action.sa_mask = signal_mask; access_fault_action.sa_mask = signal_mask;
Common::SigAction(SIGSEGV, &access_fault_action, &g_orig_segv_action); Common::SigAction(SIGSEGV, &access_fault_action, &g_orig_segv_action);
#else
AddVectoredExceptionHandler(1, VectoredExceptionHandler);
#endif
}); });
} }
@ -619,6 +647,9 @@ void ArmNce::SignalInterrupt(Kernel::KThread* thread) {
"svc #0x80\n" "svc #0x80\n"
:: "r"(static_cast<u64>(m_thread_id)), "r"(static_cast<u64>(SIGURG)) :: "r"(static_cast<u64>(m_thread_id)), "r"(static_cast<u64>(SIGURG))
: "x0", "x1", "x16", "memory", "cc"); : "x0", "x1", "x16", "memory", "cc");
#elif
SuspendThread(m_thread_id);
UnlockThreadParameters(params);
#endif #endif
} else { } else {
// If the thread is no longer running, we have nothing to do. // If the thread is no longer running, we have nothing to do.
@ -639,4 +670,4 @@ void ArmNce::InvalidateCacheRange(u64 addr, std::size_t size) {
} // namespace Core } // namespace Core
#endif // #ifdef __aarch64__
#endif // #ifdef ARCHITECTURE_arm64

12
src/core/arm/nce/arm_nce.h

@ -27,7 +27,13 @@ class System;
// This value is actually reserved for old versions of iOSSimulator, however we aren't iOSSimulator, // This value is actually reserved for old versions of iOSSimulator, however we aren't iOSSimulator,
// so we can manually initialize and use it. // so we can manually initialize and use it.
// https://github.com/apple-oss-distributions/libpthread/blob/42d026df5b07825070f60134b980a1ec2552dfee/private/pthread/tsd_private.h#L241-L245 // https://github.com/apple-oss-distributions/libpthread/blob/42d026df5b07825070f60134b980a1ec2552dfee/private/pthread/tsd_private.h#L241-L245
constexpr pthread_key_t CONTEXT_KEY = 210;
constexpr pthread_key_t ContextKey = 210;
#else
#include <winternal.h>
static const u32 ContextKey = TlsAlloc();
static const u32 NCEStorage = TlsAlloc();
static const u64 TlsSlots = offsetof(TEB, TlsSlots);
#endif #endif
@ -91,7 +97,11 @@ public:
// Members set on initialization. // Members set on initialization.
std::size_t m_core_index{}; std::size_t m_core_index{};
#ifndef __WIN32
pid_t m_thread_id{-1}; pid_t m_thread_id{-1};
#else
void* m_thread_id{};
#endif
// Core context. // Core context.
GuestContext m_guest_ctx{}; GuestContext m_guest_ctx{};

56
src/core/arm/nce/guest_context.h

@ -49,7 +49,7 @@ struct GuestContext {
class KernelContext { class KernelContext {
public: public:
#if defined(__linux__) #if defined(__linux__)
KernelContext(void* ptr_) : ptr(static_cast<mcontext_t *>(ptr_)), fpsimd{GetFloatingPointState(ptr)} {}
KernelContext(void* ptr_) : ptr(&static_cast<ucontext_t *>(ptr_)->uc_mcontext), fpsimd{GetFloatingPointState(ptr)} {}
u64* pc() { u64* pc() {
// u64 (unsigned long) does not equal unsigned long long // u64 (unsigned long) does not equal unsigned long long
@ -84,40 +84,74 @@ public:
} }
#elif defined(__APPLE__) #elif defined(__APPLE__)
KernelContext(void* ptr) : ptr(*static_cast<mcontext_t **>(ptr)) {}
KernelContext(void* ptr) : ptr(static_cast<ucontext_t>(ptr).uc_mcontext) {}
u64* pc() { u64* pc() {
return &(*ptr)->__ss.__pc;
return &ptr->__ss.__pc;
} }
u64* sp() { u64* sp() {
return &(*ptr)->__ss.__sp;
return &ptr->__ss.__sp;
} }
u64* regs() { u64* regs() {
return (*ptr)->__ss.__x;
return ptr->__ss.__x;
} }
u128* vregs() { u128* vregs() {
// .__v returns __uint128, u128 is an std::array // .__v returns __uint128, u128 is an std::array
return reinterpret_cast<u128 *>((*ptr)->__ns.__v);
return reinterpret_cast<u128 *>(ptr->__ns.__v);
} }
u32* fpcr() { u32* fpcr() {
return &(*ptr)->__ns.__fpcr;
return &ptr->__ns.__fpcr;
} }
u32* fpsr() { u32* fpsr() {
return &(*ptr)->__ns.__fpsr;
return &ptr->__ns.__fpsr;
} }
u32* pstate() { u32* pstate() {
return &(*ptr)->__ss.__cpsr;
return &ptr->__ss.__cpsr;
}
#elif defined(__WIN32)
KernelContext(void* ptr) : ptr(static_cast<ARM64_NT_CONTEXT*>(ptr)) {}
u64* pc() {
return ptr->Pc;
}
u64* sp() {
return ptr->Sp;
}
u64* regs() {
return ptr->X;
}
u128* vregs() {
// V returns ARM64_NT_NEON128, u128 is an std::array
return reinterpret_cast<u128 *>(ptr->V);
}
u32* fpcr() {
return &ptr->Fpcr;
}
u32* fpsr() {
return &ptr->Fpsr;
}
u32* pstate() {
return &ptr->Cpsr;
} }
#endif #endif
private: private:
#if defined(__APPLE__)
mcontext_t ptr;
#elif defined(__linux__)
mcontext_t* ptr; mcontext_t* ptr;
#ifdef __linux__
fpsimd_context* fpsimd; fpsimd_context* fpsimd;
fpsimd_context* GetFloatingPointState(mcontext_t* host_ctx) { fpsimd_context* GetFloatingPointState(mcontext_t* host_ctx) {
@ -127,6 +161,8 @@ private:
} }
return reinterpret_cast<fpsimd_context*>(header); return reinterpret_cast<fpsimd_context*>(header);
} }
#elif defined(__WIN32)
ARM64_NT_CONTEXT* ptr;
#endif #endif
}; };

79
src/core/arm/nce/patcher.cpp

@ -17,6 +17,7 @@
#include "core/hle/kernel/svc.h" #include "core/hle/kernel/svc.h"
#include "core/memory.h" #include "core/memory.h"
#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/k_thread.h"
#include "win/platform_visitor.h"
namespace Core::NCE { namespace Core::NCE {
@ -165,6 +166,19 @@ bool Patcher::PatchText(std::span<const u8> program_image, const Kernel::CodeSet
if (auto exclusive = Exclusive{inst}; exclusive.Verify()) { if (auto exclusive = Exclusive{inst}; exclusive.Verify()) {
curr_patch->m_exclusives.push_back(i); curr_patch->m_exclusives.push_back(i);
} }
#ifdef __WIN32
// TODO: keep track of exclusives?
if (auto scratch = CheckForPlatformRegister(inst); scratch) {
bool pre_buffer = false;
auto ret = AddRelocations(pre_buffer);
if (pre_buffer) {
WritePlatformRegHandler(ret, inst, scratch, c_pre);
} else {
WritePlatformRegHandler(ret, inst, scratch, c);
}
}
#endif
} }
// Determine patching mode for the final relocation step // Determine patching mode for the final relocation step
@ -365,7 +379,7 @@ size_t Patcher::GetPreSectionSize() const noexcept {
return Common::AlignUp(m_patch_instructions_pre.size() * sizeof(u32), Common::HostPageSize); return Common::AlignUp(m_patch_instructions_pre.size() * sizeof(u32), Common::HostPageSize);
} }
__attribute__((always_inline))
YUZU_ALWAYS_INLINE
void Patcher::LoadTLS(oaknut::VectorCodeGenerator& cg, oaknut::XReg out) { void Patcher::LoadTLS(oaknut::VectorCodeGenerator& cg, oaknut::XReg out) {
#ifdef __APPLE__ #ifdef __APPLE__
// The kernel zeros out TPIDR_EL0 too unpredictably, so we use pthreads TLS instead // The kernel zeros out TPIDR_EL0 too unpredictably, so we use pthreads TLS instead
@ -373,12 +387,40 @@ void Patcher::LoadTLS(oaknut::VectorCodeGenerator& cg, oaknut::XReg out) {
// https://github.com/apple-oss-distributions/xnu/blob/f6217f891ac0bb64f3d375211650a4c1ff8ca1ea/libsyscall/os/tsd.h#L156-L189 // https://github.com/apple-oss-distributions/xnu/blob/f6217f891ac0bb64f3d375211650a4c1ff8ca1ea/libsyscall/os/tsd.h#L156-L189
cg.MRS(out, oaknut::SystemReg::TPIDRRO_EL0); cg.MRS(out, oaknut::SystemReg::TPIDRRO_EL0);
cg.LDR(out, out, CONTEXT_KEY * 8);
cg.LDR(out, out, ContextKey * 8);
#elif __WIN32
// x18 always points to TEB in Windows, we can just use that for TLS storage
ASSERT(out != X18);
cg.LDR(out, X18, TlsSlots + 8 * ContextKey);
#else #else
cg.MRS(out, oaknut::SystemReg::TPIDR_EL0); cg.MRS(out, oaknut::SystemReg::TPIDR_EL0);
#endif #endif
} }
#ifdef __WIN32
void Patcher::WritePlatformRegHandler(ModuleDestLabel module_dest, uint32 instruction, oaknut::XReg scratch, oaknut::VectorCodeGenerator& code) {
// Save X18 register and scratch register
cg.STP(X18, scratch, SP, PRE_INDEXED, -16);
// Load x18 register
cg.MOV(scratch, X18);
cg.LDR(X18, scratch, TlsSlots + 8 * NCEStorage);
// Perform operation
cg.append(instruction);
// Store x18 register and restore scratch register
cg.STR(X18, scratch, TlsSlots + 8 * NCEStorage);
cg.LDP(X18, scratch, SP, POST_INDEXED, 16);
// Jump back to the instruction after the "emulated" instruction.
if (&cg == &c_pre)
this->BranchToModulePre(module_dest);
else
this->BranchToModule(module_dest);
}
#endif
void Patcher::WriteLoadContext(oaknut::VectorCodeGenerator& cg) { void Patcher::WriteLoadContext(oaknut::VectorCodeGenerator& cg) {
// This function was called, which modifies X30, so use that as a scratch register. // This function was called, which modifies X30, so use that as a scratch register.
// SP contains the guest X30, so save our return X30 to SP + 8, since we have allocated 16 bytes // SP contains the guest X30, so save our return X30 to SP + 8, since we have allocated 16 bytes
@ -495,7 +537,7 @@ void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id, oaknut
// Reload host TPIDR_EL0 and SP. // Reload host TPIDR_EL0 and SP.
cg.LDP(X2, X3, X1, offsetof(HostContext, host_sp)); cg.LDP(X2, X3, X1, offsetof(HostContext, host_sp));
cg.MOV(SP, X2); cg.MOV(SP, X2);
#ifndef __APPLE__
#if !defined(__APPLE__) && !defined(__WIN32)
static_assert(offsetof(HostContext, host_sp) + 8 == offsetof(HostContext, host_tpidr_el0)); static_assert(offsetof(HostContext, host_sp) + 8 == offsetof(HostContext, host_tpidr_el0));
cg.MSR(oaknut::SystemReg::TPIDR_EL0, X3); cg.MSR(oaknut::SystemReg::TPIDR_EL0, X3);
#endif #endif
@ -563,12 +605,28 @@ void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id, oaknut
// Retrieve emulated TLS register from GuestContext. // Retrieve emulated TLS register from GuestContext.
void Patcher::WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, void Patcher::WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg,
oaknut::SystemReg src_reg, oaknut::VectorCodeGenerator& cg) { oaknut::SystemReg src_reg, oaknut::VectorCodeGenerator& cg) {
#ifdef __WIN32
if (dest_reg != X18) {
#endif
LoadTLS(cg, dest_reg); LoadTLS(cg, dest_reg);
if (src_reg == oaknut::SystemReg::TPIDRRO_EL0) { if (src_reg == oaknut::SystemReg::TPIDRRO_EL0) {
cg.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidrro_el0)); cg.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidrro_el0));
} else { } else {
cg.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidr_el0)); cg.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidr_el0));
} }
#ifdef __WIN32
} else {
const auto scratch = dest_reg.index() == 0 ? X1 : X0;
cg.STR(scratch, SP, PRE_INDEXED, -16);
LoadTLS(cg, scratch);
cg.LDR(scratch, scratch, offsetof(NativeExecutionParameters, native_context));
cg.STR(scratch, X18, TlsSlots + 8 * NCEStorage);
cg.LDR(scratch, SP, POST_INDEXED, 16);
}
#endif
// Jump back to the instruction after the emulated MRS. // Jump back to the instruction after the emulated MRS.
if (&cg == &c_pre) if (&cg == &c_pre)
@ -579,14 +637,23 @@ void Patcher::WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg
void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg, oaknut::VectorCodeGenerator& cg) { void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg, oaknut::VectorCodeGenerator& cg) {
const auto scratch_reg = src_reg.index() == 0 ? X1 : X0; const auto scratch_reg = src_reg.index() == 0 ? X1 : X0;
cg.STR(scratch_reg, SP, PRE_INDEXED, -16);
const auto scratch_reg2 = src_reg.index() == 2 ? X3 : X2;
cg.STP(scratch_reg, scratch_reg2, SP, PRE_INDEXED, -16);
// Save guest value to NativeExecutionParameters::tpidr_el0. // Save guest value to NativeExecutionParameters::tpidr_el0.
LoadTLS(cg, scratch_reg); LoadTLS(cg, scratch_reg);
#ifdef __WIN32
if (src_reg == X18) {
// Load real x18 value and use that
cg.LDR(scratch_reg2, X18, TlsSlots + 8 * NCEStorage);
src_reg = scratch_reg2;
} else
#endif
cg.STR(src_reg, scratch_reg, offsetof(NativeExecutionParameters, tpidr_el0)); cg.STR(src_reg, scratch_reg, offsetof(NativeExecutionParameters, tpidr_el0));
// Restore scratch register.
cg.LDR(scratch_reg, SP, POST_INDEXED, 16);
// Restore scratch registers.
cg.LDP(scratch_reg, scratch_reg2, SP, POST_INDEXED, 16);
// Jump back to the instruction after the emulated MSR. // Jump back to the instruction after the emulated MSR.
if (&cg == &c_pre) if (&cg == &c_pre)

3
src/core/arm/nce/patcher.h

@ -82,6 +82,9 @@ private:
void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::SystemReg src_reg, oaknut::VectorCodeGenerator& code); void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::SystemReg src_reg, oaknut::VectorCodeGenerator& code);
void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg, oaknut::VectorCodeGenerator& code); void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg, oaknut::VectorCodeGenerator& code);
void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& code); void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& code);
#ifdef __WIN32
void WritePlatformRegHandler(ModuleDestLabel module_dest, uint32 instruction, oaknut::XReg scratch, oaknut::VectorCodeGenerator& code);
#endif
// Convenience wrappers using default code generator // Convenience wrappers using default code generator
void WriteLoadContext() { WriteLoadContext(c); } void WriteLoadContext() { WriteLoadContext(c); }

29
src/core/arm/nce/win/exceptions.cpp

@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/arm/nce/win/exceptions.h"
#include "core/arm/nce/arm_nce.h"
namespace Core {
static s32 WINAPI VectoredExecptionHandler(EXCEPTION_POINTERS* info) {
u32 code = info->ExceptionRecord->ExceptionCode;
if (code == SIGSEGV || code == SIGBUS) {
GuestMemoryFaultSignalHandler(code, &info->ExceptionRecord->ExceptionAddress, info->ContextRecord);
if (is_host_fault) {
is_host_fault = false;
return EXCEPTION_CONTINUE_SEARCH;
}
return EXCEPTION_CONTINUE_EXECUTION;
} else if (code == SIGUSR2) {
ReturnToGuestByExceptionLevelChangeSignalHandler(code, &info->ExceptionRecord->ExceptionAddress, info->ContextRecord);
return EXCEPTION_CONTINUE_EXECUTION;
} else {
// other exception?? let it pass
return EXCEPTION_CONTINUE_SEARCH;
}
}
} // namespace Core

19
src/core/arm/nce/win/exceptions.h

@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <windows.h>
#define SIGBUS EXCEPTION_DATATYPE_MISALIGNMENT
#define SIGSEGV EXCEPTION_ACCESS_VIOLATION
#define SIGUSR2 0xE0000001
#define SIGURG 0xE0000002
namespace Core {
thread_local bool is_host_fault = false;
static s32 WINAPI VectoredExecptionHandler(EXCEPTION_POINTERS* info);
} // namespace Core

17
src/core/arm/nce/win/platform_visitor.cpp

@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/arm/nce/win/platform_visitor.h"
#include <oaknut/oaknut.hpp>
namespace Core {
std::optional<oaknut::XReg> CheckForPlatformRegister(u32 instruction) {
auto visitor = PlatformVisitor();
auto decoder = Dynarmic::A64::Decode<VisitorBase, bool>(visitor, instruction);
return decoder ? std::optional(oaknut::XReg(static_cast<int>(visitor.scratch))) : std::nullopt;
}
} // namespace Core

1361
src/core/arm/nce/win/platform_visitor.h
File diff suppressed because it is too large
View File

2
src/core/hle/kernel/k_thread.h

@ -668,7 +668,7 @@ public:
public: public:
// TODO: This shouldn't be defined in kernel namespace // TODO: This shouldn't be defined in kernel namespace
struct NativeExecutionParameters { struct NativeExecutionParameters {
#if defined(__APPLE__) && HAS_NCE
#if (defined(__APPLE__) || defined(__WIN32)) && HAS_NCE
// Are we in actual guest code? // Are we in actual guest code?
bool is_actually_running{}; bool is_actually_running{};
#endif #endif

2
src/dynarmic/src/dynarmic/interface/code_page.h

@ -11,7 +11,7 @@ namespace Dynarmic {
/// @brief Smallest valid page /// @brief Smallest valid page
/// ///
// TODO: can we base this off the system page size without using the heap? // TODO: can we base this off the system page size without using the heap?
#if defined(__APPLE__) && defined(__aarch64__)
#if defined(__APPLE__) && defined(ARCHITECTURE_arm64)
constexpr inline uint64_t CODE_PAGE_SIZE = 0x4000; constexpr inline uint64_t CODE_PAGE_SIZE = 0x4000;
#else #else
constexpr inline uint64_t CODE_PAGE_SIZE = 0x1000; constexpr inline uint64_t CODE_PAGE_SIZE = 0x1000;

Loading…
Cancel
Save