mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 10:00:16 +02:00
Windows: Introduce an initial ARM64EC frontend
This allows for running x64 applications under wine without having to run all of wine under FEX. The JIT is invoked when ARM64EC code performs an indirect branch to x64 code, and left whenever the x64 code calls into ARM64EC code.
This commit is contained in:
1 parent
549e06aade
commit
5dc85307a6
7 files changed
+422
-1
No files matched your search
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include <windef.h>
|
||||
#include <ntstatus.h>
|
||||
#include <winternl.h>
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -0,0 +1,273 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
/*
|
||||
$info$
|
||||
tags: Bin|ARM64EC
|
||||
desc: Implements the ARM64EC BT module API using FEXCore
|
||||
$end_info$
|
||||
*/
|
||||
|
||||
#include <FEXCore/fextl/fmt.h>
|
||||
#include <FEXCore/Core/X86Enums.h>
|
||||
#include <FEXCore/Core/SignalDelegator.h>
|
||||
#include <FEXCore/Core/Context.h>
|
||||
#include <FEXCore/Core/CoreState.h>
|
||||
#include <FEXCore/Debug/InternalThreadState.h>
|
||||
#include <FEXCore/HLE/SyscallHandler.h>
|
||||
#include <FEXCore/Config/Config.h>
|
||||
#include <FEXCore/Utils/Allocator.h>
|
||||
#include <FEXCore/Utils/LogManager.h>
|
||||
#include <FEXCore/Utils/Threads.h>
|
||||
#include <FEXCore/Utils/EnumOperators.h>
|
||||
#include <FEXCore/Utils/EnumUtils.h>
|
||||
#include <FEXCore/Utils/FPState.h>
|
||||
#include <FEXCore/Utils/ArchHelpers/Arm64.h>
|
||||
#include <FEXCore/Utils/MathUtils.h>
|
||||
#include <FEXCore/Utils/TypeDefines.h>
|
||||
|
||||
#include "Common/Config.h"
|
||||
#include "Common/InvalidationTracker.h"
|
||||
#include "Common/CPUFeatures.h"
|
||||
#include "DummyHandlers.h"
|
||||
#include "BTInterface.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <type_traits>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <ntstatus.h>
|
||||
#include <windef.h>
|
||||
#include <winternl.h>
|
||||
#include <wine/debug.h>
|
||||
|
||||
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<CHPE_V2_CPU_AREA_INFO**>(reinterpret_cast<uintptr_t>(TEB) + TEBCPUAreaOffset)) {}
|
||||
|
||||
uint64_t EmulatorStackLimit() const {
|
||||
return Area->EmulatorStackLimit;
|
||||
}
|
||||
|
||||
uint64_t EmulatorStackBase() const {
|
||||
return Area->EmulatorStackBase;
|
||||
}
|
||||
|
||||
FEXCore::Core::CpuStateFrame*& StateFrame() const {
|
||||
return reinterpret_cast<FEXCore::Core::CpuStateFrame*&>(Area->EmulatorData[0]);
|
||||
}
|
||||
|
||||
FEXCore::Core::InternalThreadState*& ThreadState() const {
|
||||
return reinterpret_cast<FEXCore::Core::InternalThreadState*&>(Area->EmulatorData[1]);
|
||||
}
|
||||
|
||||
uint64_t& DispatcherLoopTopEnterEC() const {
|
||||
return reinterpret_cast<uint64_t&>(Area->EmulatorData[2]);
|
||||
}
|
||||
|
||||
uint64_t& DispatcherLoopTopEnterECFillSRA() const {
|
||||
return reinterpret_cast<uint64_t&>(Area->EmulatorData[3]);
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
fextl::unique_ptr<FEXCore::Context::Context> CTX;
|
||||
fextl::unique_ptr<FEX::DummyHandlers::DummySignalDelegator> SignalDelegator;
|
||||
fextl::unique_ptr<ECSyscallHandler> SyscallHandler;
|
||||
std::optional<FEX::Windows::InvalidationTracker> InvalidationTracker;
|
||||
std::optional<FEX::Windows::CPUFeatures> CPUFeatures;
|
||||
|
||||
std::recursive_mutex ThreadCreationMutex;
|
||||
// Map of TIDs to their FEX thread state, `ThreadCreationMutex` must be locked when accessing
|
||||
std::unordered_map<DWORD, FEXCore::Core::InternalThreadState*> Threads;
|
||||
|
||||
|
||||
std::pair<NTSTATUS, ThreadCPUArea> 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<FEX::DummyHandlers::DummySignalDelegator>();
|
||||
SyscallHandler = fextl::make_unique<ECSyscallHandler>();
|
||||
|
||||
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<uint64_t>(Address), static_cast<uint64_t>(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<uint64_t>(Address), true);
|
||||
} else if (FreeType & MEM_DECOMMIT) {
|
||||
InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(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<uint64_t>(Address), static_cast<uint64_t>(Size), NewProt);
|
||||
}
|
||||
|
||||
void NotifyUnmapViewOfSection(void* Address) {
|
||||
if (!InvalidationTracker || !GetCPUArea().ThreadState()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock Lock(ThreadCreationMutex);
|
||||
InvalidationTracker->InvalidateContainingSection(reinterpret_cast<uint64_t>(Address), true);
|
||||
}
|
||||
|
||||
void BTCpu64FlushInstructionCache(const void* Address, SIZE_T Size) {
|
||||
if (!InvalidationTracker || !GetCPUArea().ThreadState()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock Lock(ThreadCreationMutex);
|
||||
InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size), false);
|
||||
}
|
||||
|
||||
NTSTATUS ThreadInit() {
|
||||
const auto CPUArea = GetCPUArea();
|
||||
|
||||
auto* Thread = CTX->CreateThread(0, 0);
|
||||
Thread->CurrentFrame->Pointers.Common.ExitFunctionEC = reinterpret_cast<uintptr_t>(&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<uint64_t>(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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user