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} "")
endif()
if (ARCHITECTURE_arm64 AND (ANDROID OR PLATFORM_LINUX))
if (ARCHITECTURE_arm64 AND (ANDROID OR PLATFORM_LINUX OR WIN32))
set(HAS_NCE 1)
add_compile_definitions(HAS_NCE=1)
endif()

2
src/common/vector_math.h

@ -654,7 +654,7 @@ template <>
float32x4_t va = vld1q_f32(&a.x);
float32x4_t vb = vld1q_f32(&b.x);
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);
#else // Use manual addition for older architectures
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/visitor_base.h)
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()
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-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
// 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\"") \
__attribute__((naked))
#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)
#else
#error Unsupported compiler
#endif
#if defined(__GNUC__)
#if defined(__clang__) || defined(__GNUC__)
#define YUZU_NAKED_END _Pragma("GCC diagnostic pop")
#else
#define YUZU_NAKED_END
@ -40,18 +37,23 @@
#include "core/hle/kernel/k_process.h"
#ifndef __WIN32
#include <unistd.h>
#include <sys/syscall.h>
#include <signal.h>
#else
#include "core/arm/nce/win/exceptions.h"
#endif
namespace Core {
namespace {
#ifndef __WIN32
struct sigaction g_orig_bus_action;
struct sigaction g_orig_segv_action;
#endif
// Verify assembly offsets.
using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
using namespace Common::Literals;
@ -62,15 +64,22 @@ constexpr u32 StackSize = 128_KiB;
YUZU_ALWAYS_INLINE
void* ArmNce::GetGuestParameters() {
void* nep; /* NativeExecutionParameters* */
#ifdef __APPLE__
#if defined(__APPLE__)
// https://github.com/apple-oss-distributions/xnu/blob/f6217f891ac0bb64f3d375211650a4c1ff8ca1ea/libsyscall/os/tsd.h#L156-L189
asm volatile(
"mrs %[out], TPIDRRO_EL0\n" // load pthreads TLS storage
"ldr %[out], [ %[out], #%[off] ]\n" // accessed like an array, so i * sizeof(u64)
: [out] "=&r"(nep)
: [off] "i"(CONTEXT_KEY * 8)
: [off] "i"((ContextKey - 1) * 8)
: "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(
"mrs %0, TPIDR_EL0\n"
: "=r"(nep));
@ -137,7 +146,7 @@ void ArmNce::ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void *i
auto tpidr = static_cast<NativeExecutionParameters*>(RestoreGuestContext(raw_context));
RestoreGuestContext(raw_context);
#ifndef __APPLE__
#if !defined(__APPLE__) && !defined(__WIN32)
// Save old value of TPIDR_EL0, load guest one
u64 tpidr_el0;
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"
"add x5, x2, #%[host_ctx] \n"
#ifndef __APPLE__
#if !defined(__APPLE__) && !defined(__WIN32)
// Load guest tpidr_el0
"mrs x4, TPIDR_EL0\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)),
[sp_off] "i"(offsetof(GuestContext, sp)),
[host_ctx] "i"(offsetof(GuestContext, host_ctx))
#ifdef __APPLE__
#if defined(__APPLE__) || defined(__WIN32)
,[is_running_off] "i"(offsetof(NativeExecutionParameters, is_actually_running))
#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) {
NativeExecutionParameters* tpidr = reinterpret_cast<NativeExecutionParameters *>(GetGuestParameters());
#ifdef __APPLE__
#if defined(__APPLE__) || defined(__WIN32)
if (tpidr->is_actually_running) {
tpidr->is_actually_running = false;
#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) {
DEBUG_ASSERT(sig == SIGSEGV);
DEBUG_ASSERT(sig == SIGSEGV || sig == SIGBUS);
NativeExecutionParameters* nep = static_cast<NativeExecutionParameters*>(GetGuestParameters());
#ifdef __APPLE__
#if defined(__APPLE__) || defined(__WIN32)
if (nep->is_actually_running) {
nep->is_actually_running = false;
#else
@ -250,22 +259,26 @@ void ArmNce::GuestMemoryFaultSignalHandler(int sig, void* raw_info, void* raw_co
:: [host_tpidr] "r"(host_tpidr));
#endif
auto* info = static_cast<siginfo_t*>(raw_info);
auto* guest_ctx = static_cast<GuestContext*>(nep->native_context);
auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory();
if (sig == SIGSEGV) {
// Try to handle an invalid access.
// 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 =
(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)) {
// We handled the access successfully and are returning to guest code.
goto ret;
}
} else if (sig == SIGBUS) {
// 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);
if (next_pc) {
// 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
nep->is_actually_running = true;
#endif
} else {
}
#ifndef __WIN32
else {
// Host fault, call original handler
if (sig == SIGSEGV) {
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);
}
}
#elif
is_host_fault = true;
#endif
}
void* ArmNce::RestoreGuestContext(void* raw_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.
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) {
// 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.
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) {
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.
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.
// 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();
#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
// Move non-critical operations outside the locked section
@ -502,11 +521,16 @@ void ArmNce::Initialize() {
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__)
if (m_thread_id == -1) {
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
// Configure signal stack.
@ -522,6 +546,7 @@ void ArmNce::Initialize() {
// Set up signals.
static std::once_flag flag;
std::call_once(flag, [] {
#ifndef __WIN32
using HandlerType = decltype(sigaction::sa_sigaction);
sigset_t signal_mask;
@ -559,6 +584,9 @@ void ArmNce::Initialize() {
reinterpret_cast<HandlerType>(&ArmNce::GuestMemoryFaultSignalHandler);
access_fault_action.sa_mask = signal_mask;
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"
:: "r"(static_cast<u64>(m_thread_id)), "r"(static_cast<u64>(SIGURG))
: "x0", "x1", "x16", "memory", "cc");
#elif
SuspendThread(m_thread_id);
UnlockThreadParameters(params);
#endif
} else {
// 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
#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,
// so we can manually initialize and use it.
// 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
@ -91,7 +97,11 @@ public:
// Members set on initialization.
std::size_t m_core_index{};
#ifndef __WIN32
pid_t m_thread_id{-1};
#else
void* m_thread_id{};
#endif
// Core context.
GuestContext m_guest_ctx{};

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

@ -49,7 +49,7 @@ struct GuestContext {
class KernelContext {
public:
#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 (unsigned long) does not equal unsigned long long
@ -84,40 +84,74 @@ public:
}
#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() {
return &(*ptr)->__ss.__pc;
return &ptr->__ss.__pc;
}
u64* sp() {
return &(*ptr)->__ss.__sp;
return &ptr->__ss.__sp;
}
u64* regs() {
return (*ptr)->__ss.__x;
return ptr->__ss.__x;
}
u128* vregs() {
// .__v returns __uint128, u128 is an std::array
return reinterpret_cast<u128 *>((*ptr)->__ns.__v);
return reinterpret_cast<u128 *>(ptr->__ns.__v);
}
u32* fpcr() {
return &(*ptr)->__ns.__fpcr;
return &ptr->__ns.__fpcr;
}
u32* fpsr() {
return &(*ptr)->__ns.__fpsr;
return &ptr->__ns.__fpsr;
}
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
private:
#if defined(__APPLE__)
mcontext_t ptr;
#elif defined(__linux__)
mcontext_t* ptr;
#ifdef __linux__
fpsimd_context* fpsimd;
fpsimd_context* GetFloatingPointState(mcontext_t* host_ctx) {
@ -127,6 +161,8 @@ private:
}
return reinterpret_cast<fpsimd_context*>(header);
}
#elif defined(__WIN32)
ARM64_NT_CONTEXT* ptr;
#endif
};

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

@ -17,6 +17,7 @@
#include "core/hle/kernel/svc.h"
#include "core/memory.h"
#include "core/hle/kernel/k_thread.h"
#include "win/platform_visitor.h"
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()) {
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
@ -365,7 +379,7 @@ size_t Patcher::GetPreSectionSize() const noexcept {
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) {
#ifdef __APPLE__
// 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
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
cg.MRS(out, oaknut::SystemReg::TPIDR_EL0);
#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) {
// 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
@ -495,7 +537,7 @@ void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id, oaknut
// Reload host TPIDR_EL0 and SP.
cg.LDP(X2, X3, X1, offsetof(HostContext, host_sp));
cg.MOV(SP, X2);
#ifndef __APPLE__
#if !defined(__APPLE__) && !defined(__WIN32)
static_assert(offsetof(HostContext, host_sp) + 8 == offsetof(HostContext, host_tpidr_el0));
cg.MSR(oaknut::SystemReg::TPIDR_EL0, X3);
#endif
@ -563,12 +605,28 @@ void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id, oaknut
// Retrieve emulated TLS register from GuestContext.
void Patcher::WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg,
oaknut::SystemReg src_reg, oaknut::VectorCodeGenerator& cg) {
#ifdef __WIN32
if (dest_reg != X18) {
#endif
LoadTLS(cg, dest_reg);
if (src_reg == oaknut::SystemReg::TPIDRRO_EL0) {
cg.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidrro_el0));
} else {
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.
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) {
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.
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));
// 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.
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 WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_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
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:
// TODO: This shouldn't be defined in kernel namespace
struct NativeExecutionParameters {
#if defined(__APPLE__) && HAS_NCE
#if (defined(__APPLE__) || defined(__WIN32)) && HAS_NCE
// Are we in actual guest code?
bool is_actually_running{};
#endif

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

@ -11,7 +11,7 @@ namespace Dynarmic {
/// @brief Smallest valid page
///
// 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;
#else
constexpr inline uint64_t CODE_PAGE_SIZE = 0x1000;

Loading…
Cancel
Save