diff --git a/Source/Windows/ARM64EC/BTInterface.h b/Source/Windows/ARM64EC/BTInterface.h new file mode 100644 index 000000000..7f14a5f2e --- /dev/null +++ b/Source/Windows/ARM64EC/BTInterface.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +#pragma once + +#include +#include +#include + +extern "C" { +void STDMETHODCALLTYPE ProcessInit(); +void STDMETHODCALLTYPE ProcessTerm(); +NTSTATUS STDMETHODCALLTYPE ThreadInit(); +NTSTATUS STDMETHODCALLTYPE ThreadTerm(HANDLE Thread); +NTSTATUS STDMETHODCALLTYPE ResetToConsistentState(EXCEPTION_POINTERS* Ptrs, ARM64_NT_CONTEXT* Context, BOOLEAN* Continue); +void STDMETHODCALLTYPE BTCpu64FlushInstructionCache(const void* Address, SIZE_T Size); +void STDMETHODCALLTYPE NotifyMemoryAlloc(void* Address, SIZE_T Size, ULONG Type, ULONG Prot); +void STDMETHODCALLTYPE NotifyMemoryFree(void* Address, SIZE_T Size, ULONG FreeType); +void STDMETHODCALLTYPE NotifyMemoryProtect(void* Address, SIZE_T Size, ULONG NewProt); +void STDMETHODCALLTYPE NotifyUnmapViewOfSection(void* Address); +BOOLEAN STDMETHODCALLTYPE BTCpu64IsProcessorFeaturePresent(UINT Feature); +void STDMETHODCALLTYPE UpdateProcessorInformation(SYSTEM_CPU_INFORMATION* Info); +} diff --git a/Source/Windows/ARM64EC/CMakeLists.txt b/Source/Windows/ARM64EC/CMakeLists.txt new file mode 100644 index 000000000..e2f26a7c3 --- /dev/null +++ b/Source/Windows/ARM64EC/CMakeLists.txt @@ -0,0 +1,28 @@ +add_library(arm64ecfex SHARED + Module.cpp + Module.S + libarm64ecfex.def +) +patch_library_wine(arm64ecfex) + +target_include_directories(arm64ecfex PRIVATE + "${CMAKE_SOURCE_DIR}/Source/Windows/include/" + "${CMAKE_SOURCE_DIR}/Source/" + "${CMAKE_SOURCE_DIR}/Source/Windows/" +) + +target_link_libraries(arm64ecfex + PRIVATE + FEXCore + FEXCore_Base + Common + CommonTools + CommonWindows + ntdll_ex + ntdll +) + +install(TARGETS arm64ecfex + RUNTIME + DESTINATION lib + COMPONENT runtime) diff --git a/Source/Windows/ARM64EC/Module.S b/Source/Windows/ARM64EC/Module.S new file mode 100644 index 000000000..2d24ecf4c --- /dev/null +++ b/Source/Windows/ARM64EC/Module.S @@ -0,0 +1,62 @@ +.text +.balign 16 + + // __os_arm64x_x64_jump in ARM64EC docs + // Expects target code address in x9 +.globl DispatchJump +DispatchJump: + str lr, [sp, #-8]! // Push return address to stack, this will be popped by the x86 RET instr. + b check_target_ec + + // __os_arm64x_dispatch_ret in ARM64EC docs + // Expects target code address in lr +.globl RetToEntryThunk +RetToEntryThunk: + mov x9, lr + +check_target_ec: + // Check if target is in fact x86 code + ldr x16, [x18, #0x60] // TEB->PEB + ldr x16, [x16, #0x368] // PEB->EcCodeBitMap + lsr x17, x9, #15 + and x17, x17, #0x1fffffffffff8 + ldr x16, [x16, x17] + lsr x17, x9, #12 + lsr x16, x16, x17 + tbnz x16, #0, ExitFunctionEC + b enter_jit + + // __os_arm64x_dispatch_call_no_redirect in ARM64EC docs + // Expects target code address in x9, and to be called using a 'blr x16' instruction. +.globl ExitToX64 +ExitToX64: + str lr, [sp, #-8]! // Push return address to stack, this will be popped by the x86 RET instr. + +enter_jit: + ldr x17, [x18, #0x1788] // TEB->ChpeV2CpuAreaInfo + ldr x16, [x17, #0x40] // ChpeV2CpuAreaInfo->EmulatorData[2] - DispatcherLoopTopEnterEC + br x16 // DispatcherLoopTopEnterEC(RIP:x9, CPUArea:x17) + + // Called into by FEXCore + // Expects the target code address in x9 +.global ExitFunctionEC +ExitFunctionEC: + // Either return to an exit thunk (return to ARM64EC function) or call an entry thunk (call to ARM64EC function). + // It is assumed that a 'blr x16' instruction is only ever used to call into x86 code from an exit thunk, and that all + // exported ARM64EC functions have a 4-byte offset to their entry thunk immediately before their first instruction. + mov x17, x9 + mov w16, #0x200 + movk w16, #0xd63f, lsl 16 // blr x16 + ldursw x23, [x17, #-0x4] // Load either the entry thunk offset or the calling instruction. + cmp w23, w16 + beq ret_sp_aligned + + and x23, x23, #-0x4 + add x17, x17, x23 // Resolve entry thunk address. + + mov x4, sp + ldr lr, [x4], #0x8 // Pop the return address into lr. + mov sp, x4 + +ret_sp_aligned: + br x17 diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp new file mode 100644 index 000000000..a31f4c06b --- /dev/null +++ b/Source/Windows/ARM64EC/Module.cpp @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: MIT +/* +$info$ +tags: Bin|ARM64EC +desc: Implements the ARM64EC BT module API using FEXCore +$end_info$ +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/Config.h" +#include "Common/InvalidationTracker.h" +#include "Common/CPUFeatures.h" +#include "DummyHandlers.h" +#include "BTInterface.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class ECSyscallHandler; +extern void* ExitFunctionEC; + +struct ThreadCPUArea { + static constexpr size_t TEBCPUAreaOffset = 0x1788; + CHPE_V2_CPU_AREA_INFO* Area; + + explicit ThreadCPUArea(_TEB* TEB) + : Area(*reinterpret_cast(reinterpret_cast(TEB) + TEBCPUAreaOffset)) {} + + uint64_t EmulatorStackLimit() const { + return Area->EmulatorStackLimit; + } + + uint64_t EmulatorStackBase() const { + return Area->EmulatorStackBase; + } + + FEXCore::Core::CpuStateFrame*& StateFrame() const { + return reinterpret_cast(Area->EmulatorData[0]); + } + + FEXCore::Core::InternalThreadState*& ThreadState() const { + return reinterpret_cast(Area->EmulatorData[1]); + } + + uint64_t& DispatcherLoopTopEnterEC() const { + return reinterpret_cast(Area->EmulatorData[2]); + } + + uint64_t& DispatcherLoopTopEnterECFillSRA() const { + return reinterpret_cast(Area->EmulatorData[3]); + } +}; + +namespace { +fextl::unique_ptr CTX; +fextl::unique_ptr SignalDelegator; +fextl::unique_ptr SyscallHandler; +std::optional InvalidationTracker; +std::optional CPUFeatures; + +std::recursive_mutex ThreadCreationMutex; +// Map of TIDs to their FEX thread state, `ThreadCreationMutex` must be locked when accessing +std::unordered_map Threads; + + +std::pair GetThreadCPUArea(HANDLE Thread) { + THREAD_BASIC_INFORMATION Info; + const NTSTATUS Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr); + return {Err, ThreadCPUArea(reinterpret_cast<_TEB*>(Info.TebBaseAddress))}; +} + +ThreadCPUArea GetCPUArea() { + return ThreadCPUArea(NtCurrentTeb()); +} + +} // namespace + +namespace Logging { +static void MsgHandler(LogMan::DebugLevels Level, const char* Message) { + const auto Output = fextl::fmt::format("[{}][{:X}] {}\n", LogMan::DebugLevelStr(Level), GetCurrentThreadId(), Message); + __wine_dbg_output(Output.c_str()); +} + +static void AssertHandler(const char* Message) { + const auto Output = fextl::fmt::format("[ASSERT] {}\n", Message); + __wine_dbg_output(Output.c_str()); +} + +static void Init() { + LogMan::Throw::InstallHandler(AssertHandler); + LogMan::Msg::InstallHandler(MsgHandler); +} +} // namespace Logging + +class ECSyscallHandler : public FEXCore::HLE::SyscallHandler, public FEXCore::Allocator::FEXAllocOperators { +public: + ECSyscallHandler() { + OSABI = FEXCore::HLE::SyscallOSABI::OS_WIN32; + } + + uint64_t HandleSyscall(FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args) override { + return 0; + } + + FEXCore::HLE::SyscallABI GetSyscallABI(uint64_t Syscall) override { + return {.NumArgs = 0, .HasReturn = false, .HostSyscallNumber = -1}; + } + + FEXCore::HLE::AOTIRCacheEntryLookupResult LookupAOTIRCacheEntry(FEXCore::Core::InternalThreadState* Thread, uint64_t GuestAddr) override { + return {0, 0}; + } + + void MarkGuestExecutableRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Start, uint64_t Length) override { + InvalidationTracker->ReprotectRWXIntervals(Start, Length); + } +}; + +void ProcessInit() { + Logging::Init(); + FEX::Config::InitializeConfigs(); + FEXCore::Config::Initialize(); + FEXCore::Config::AddLayer(FEX::Config::CreateGlobalMainLayer()); + FEXCore::Config::AddLayer(FEX::Config::CreateMainLayer()); + FEXCore::Config::Load(); + FEXCore::Config::ReloadMetaLayer(); + + FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_IS64BIT_MODE, "1"); + + // Not applicable to Windows + FEXCore::Config::EraseSet(FEXCore::Config::ConfigOption::CONFIG_TSOAUTOMIGRATION, "0"); + + FEXCore::Context::InitializeStaticTables(FEXCore::Context::MODE_64BIT); + + SignalDelegator = fextl::make_unique(); + SyscallHandler = fextl::make_unique(); + + CTX = FEXCore::Context::Context::CreateNewContext(); + CTX->SetSignalDelegator(SignalDelegator.get()); + CTX->SetSyscallHandler(SyscallHandler.get()); + CTX->InitCore(); + InvalidationTracker.emplace(*CTX, Threads); + CPUFeatures.emplace(*CTX); +} + +void ProcessTerm() {} + +void NotifyMemoryAlloc(void* Address, SIZE_T Size, ULONG Type, ULONG Prot) { + if (!InvalidationTracker || !GetCPUArea().ThreadState()) { + return; + } + + std::scoped_lock Lock(ThreadCreationMutex); + InvalidationTracker->HandleMemoryProtectionNotification(reinterpret_cast(Address), static_cast(Size), Prot); +} + +void NotifyMemoryFree(void* Address, SIZE_T Size, ULONG FreeType) { + if (!InvalidationTracker || !GetCPUArea().ThreadState()) { + return; + } + + std::scoped_lock Lock(ThreadCreationMutex); + if (!Size) { + InvalidationTracker->InvalidateContainingSection(reinterpret_cast(Address), true); + } else if (FreeType & MEM_DECOMMIT) { + InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast(Address), static_cast(Size), true); + } +} + +void NotifyMemoryProtect(void* Address, SIZE_T Size, ULONG NewProt) { + if (!InvalidationTracker || !GetCPUArea().ThreadState()) { + return; + } + + std::scoped_lock Lock(ThreadCreationMutex); + InvalidationTracker->HandleMemoryProtectionNotification(reinterpret_cast(Address), static_cast(Size), NewProt); +} + +void NotifyUnmapViewOfSection(void* Address) { + if (!InvalidationTracker || !GetCPUArea().ThreadState()) { + return; + } + + std::scoped_lock Lock(ThreadCreationMutex); + InvalidationTracker->InvalidateContainingSection(reinterpret_cast(Address), true); +} + +void BTCpu64FlushInstructionCache(const void* Address, SIZE_T Size) { + if (!InvalidationTracker || !GetCPUArea().ThreadState()) { + return; + } + + std::scoped_lock Lock(ThreadCreationMutex); + InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast(Address), static_cast(Size), false); +} + +NTSTATUS ThreadInit() { + const auto CPUArea = GetCPUArea(); + + auto* Thread = CTX->CreateThread(0, 0); + Thread->CurrentFrame->Pointers.Common.ExitFunctionEC = reinterpret_cast(&ExitFunctionEC); + CPUArea.StateFrame() = Thread->CurrentFrame; + + uint64_t EnterEC = Thread->CurrentFrame->Pointers.Common.DispatcherLoopTopEnterEC; + CPUArea.DispatcherLoopTopEnterEC() = EnterEC; + + uint64_t EnterECFillSRA = Thread->CurrentFrame->Pointers.Common.DispatcherLoopTopEnterECFillSRA; + CPUArea.DispatcherLoopTopEnterECFillSRA() = EnterECFillSRA; + + { + std::scoped_lock Lock(ThreadCreationMutex); + Threads.emplace(GetCurrentThreadId(), Thread); + } + + CPUArea.ThreadState() = Thread; + return STATUS_SUCCESS; +} + +NTSTATUS ThreadTerm(HANDLE Thread) { + const auto [Err, CPUArea] = GetThreadCPUArea(Thread); + if (Err) { + return Err; + } + auto* OldThreadState = CPUArea.ThreadState(); + CPUArea.ThreadState() = nullptr; + + { + THREAD_BASIC_INFORMATION Info; + if (NTSTATUS Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { + return Err; + } + + const auto ThreadTID = reinterpret_cast(Info.ClientId.UniqueThread); + std::scoped_lock Lock(ThreadCreationMutex); + Threads.erase(ThreadTID); + } + + CTX->DestroyThread(OldThreadState); + return STATUS_SUCCESS; +} + +BOOLEAN BTCpu64IsProcessorFeaturePresent(UINT Feature) { + return CPUFeatures->IsFeaturePresent(Feature) ? TRUE : FALSE; +} + +void UpdateProcessorInformation(SYSTEM_CPU_INFORMATION* Info) { + CPUFeatures->UpdateInformation(Info); +} diff --git a/Source/Windows/ARM64EC/libarm64ecfex.def b/Source/Windows/ARM64EC/libarm64ecfex.def new file mode 100644 index 000000000..e364baf2d --- /dev/null +++ b/Source/Windows/ARM64EC/libarm64ecfex.def @@ -0,0 +1,21 @@ +LIBRARY libarm64ecfex.dll + +EXPORTS + BTCpu64FlushInstructionCache + BTCpu64IsProcessorFeaturePresent + DispatchJump DATA + RetToEntryThunk DATA + ExitToX64 DATA + BeginSimulation DATA +; FlushInstructionCacheHeavy +; NotifyMapViewOfSection + NotifyMemoryAlloc + NotifyMemoryFree + NotifyMemoryProtect + NotifyUnmapViewOfSection + ProcessInit + ProcessTerm +; ResetToConsistentState + ThreadInit + ThreadTerm + UpdateProcessorInformation diff --git a/Source/Windows/CMakeLists.txt b/Source/Windows/CMakeLists.txt index 9b58ec9d0..431e37db7 100644 --- a/Source/Windows/CMakeLists.txt +++ b/Source/Windows/CMakeLists.txt @@ -24,6 +24,8 @@ build_implib(wow64) add_subdirectory(Common) -if (_M_ARM_64 AND (NOT _M_ARM_64EC)) +if (_M_ARM_64EC) + add_subdirectory(ARM64EC) +elseif (_M_ARM_64) add_subdirectory(WOW64) endif() diff --git a/Source/Windows/include/winternl.h b/Source/Windows/include/winternl.h index cf5a4d25a..365d9e0da 100644 --- a/Source/Windows/include/winternl.h +++ b/Source/Windows/include/winternl.h @@ -13,6 +13,20 @@ extern "C" { #define WOW64_TLS_MAX_NUMBER 19 +#ifdef _M_ARM_64EC +typedef struct _CHPE_V2_CPU_AREA_INFO { + BOOLEAN InSimulation; /* 000 */ + BOOLEAN InSyscallCallback; /* 001 */ + ULONG64 EmulatorStackBase; /* 008 */ + ULONG64 EmulatorStackLimit; /* 010 */ + ARM64EC_NT_CONTEXT* ContextAmd64; /* 018 */ + ULONG* SuspendDoorbell; /* 020 */ + ULONG64 LoadingModuleModflag; /* 028 */ + void* EmulatorData[4]; /* 030 */ + ULONG64 EmulatorDataInline; /* 050 */ +} CHPE_V2_CPU_AREA_INFO, *PCHPE_V2_CPU_AREA_INFO; +#endif + typedef struct _THREAD_BASIC_INFORMATION { NTSTATUS ExitStatus; PVOID TebBaseAddress;