Skip to content

Commit

Permalink
Merge pull request #3367 from bylaws/prepwow
Browse files Browse the repository at this point in the history
Windows: Commonise WOW64 logic that can be shared with ARM64EC
  • Loading branch information
Sonicadvance1 committed Jan 15, 2024
2 parents 8320723 + 9f311cd commit 8ff4b52
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 1,574 deletions.
17 changes: 10 additions & 7 deletions Source/Windows/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
function(build_implib name)
add_custom_target(${name}lib ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/lib${name}.a)
set(name_ex ${name}_ex)
add_custom_target(${name_ex}lib ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/lib${name_ex}.a)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/lib${name}.a
COMMAND ${CMAKE_DLLTOOL} -d ${CMAKE_CURRENT_SOURCE_DIR}/Defs/${name}.def -k -l lib${name}.a
COMMENT "Building lib${name}.a"
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/lib${name_ex}.a
COMMAND ${CMAKE_DLLTOOL} -d ${CMAKE_CURRENT_SOURCE_DIR}/Defs/${name}.def -k -l lib${name_ex}.a
COMMENT "Building lib${name_ex}.a"
)

add_library(${name} SHARED IMPORTED)
set_property(TARGET ${name} PROPERTY IMPORTED_IMPLIB ${CMAKE_CURRENT_BINARY_DIR}/lib${name}.a)
add_dependencies(${name} ${name}lib)
add_library(${name_ex} SHARED IMPORTED)
set_property(TARGET ${name_ex} PROPERTY IMPORTED_IMPLIB ${CMAKE_CURRENT_BINARY_DIR}/lib${name_ex}.a)
add_dependencies(${name_ex} ${name_ex}lib)
endfunction()

build_implib(ntdll)
build_implib(wow64)

add_subdirectory(Common)

if (_M_ARM_64)
add_subdirectory(WOW64)
endif()
6 changes: 6 additions & 0 deletions Source/Windows/Common/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
add_library(CommonWindows STATIC CPUFeatures.cpp InvalidationTracker.cpp)
target_link_libraries(CommonWindows FEXCore_Base)

target_include_directories(CommonWindows PRIVATE
"${CMAKE_SOURCE_DIR}/Source/Windows/include/"
)
102 changes: 102 additions & 0 deletions Source/Windows/Common/CPUFeatures.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: MIT

#include <FEXCore/Core/Context.h>
#include "CPUFeatures.h"

namespace FEX::Windows {
CPUFeatures::CPUFeatures(FEXCore::Context::Context &CTX) {
CpuInfo.ProcessorArchitecture = PROCESSOR_ARCHITECTURE_INTEL;

// Baseline FEX feature-set
CpuInfo.ProcessorFeatureBits = CPU_FEATURE_VME | CPU_FEATURE_TSC | CPU_FEATURE_CMOV | CPU_FEATURE_PGE |
CPU_FEATURE_PSE | CPU_FEATURE_MTRR | CPU_FEATURE_CX8 | CPU_FEATURE_MMX |
CPU_FEATURE_X86 | CPU_FEATURE_PAT | CPU_FEATURE_FXSR | CPU_FEATURE_SEP |
CPU_FEATURE_SSE | CPU_FEATURE_3DNOW | CPU_FEATURE_SSE2 | CPU_FEATURE_SSE3 |
CPU_FEATURE_CX128 | CPU_FEATURE_NX | CPU_FEATURE_SSSE3 | CPU_FEATURE_SSE41 |
CPU_FEATURE_PAE | CPU_FEATURE_DAZ;

// Features that require specific host CPU support
const auto CPUIDResult01 = CTX.RunCPUIDFunction(0x01, 0);
if (CPUIDResult01.ecx & (1 << 20)) {
CpuInfo.ProcessorFeatureBits |= CPU_FEATURE_SSE42;
}
if (CPUIDResult01.ecx & (1 << 27)) {
CpuInfo.ProcessorFeatureBits |= CPU_FEATURE_XSAVE;
}
if (CPUIDResult01.ecx & (1 << 28)) {
CpuInfo.ProcessorFeatureBits |= CPU_FEATURE_AVX;
}

const auto CPUIDResult07 = CTX.RunCPUIDFunction(0x07, 0);
if (CPUIDResult07.ebx & (1 << 5)) {
CpuInfo.ProcessorFeatureBits |= CPU_FEATURE_AVX2;
}

const auto FamilyIdentifier = CPUIDResult01.eax;
CpuInfo.ProcessorLevel = ((FamilyIdentifier >> 8) & 0xf) + ((FamilyIdentifier >> 20) & 0xff); // Family
CpuInfo.ProcessorRevision = (FamilyIdentifier & 0xf0000) >> 4; // Extended Model
CpuInfo.ProcessorRevision |= (FamilyIdentifier & 0xf0) << 4; // Model
CpuInfo.ProcessorRevision |= FamilyIdentifier & 0xf; // Stepping
}

bool CPUFeatures::IsFeaturePresent(uint32_t Feature) {
switch (Feature) {
case PF_FLOATING_POINT_PRECISION_ERRATA:
return FALSE;
case PF_FLOATING_POINT_EMULATED:
return FALSE;
case PF_COMPARE_EXCHANGE_DOUBLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_CX8);
case PF_MMX_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_MMX);
case PF_XMMI_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSE);
case PF_3DNOW_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_3DNOW);
case PF_RDTSC_INSTRUCTION_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_TSC);
case PF_PAE_ENABLED:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_PAE);
case PF_XMMI64_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSE2);
case PF_SSE3_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSE3);
case PF_SSSE3_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSSE3);
case PF_XSAVE_ENABLED:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_XSAVE);
case PF_COMPARE_EXCHANGE128:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_CX128);
case PF_SSE_DAZ_MODE_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_DAZ);
case PF_NX_ENABLED:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_NX);
case PF_SECOND_LEVEL_ADDRESS_TRANSLATION:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_2NDLEV);
case PF_VIRT_FIRMWARE_ENABLED:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_VIRT);
case PF_RDWRFSGSBASE_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_RDFS);
case PF_FASTFAIL_AVAILABLE:
return TRUE;
case PF_SSE4_1_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSE41);
case PF_SSE4_2_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSE42);
case PF_AVX_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_AVX);
case PF_AVX2_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_AVX2);
default:
LogMan::Msg::DFmt("Unknown CPU feature: {:X}", Feature);
return false;
}
}

void CPUFeatures::UpdateInformation(SYSTEM_CPU_INFORMATION *Info) {
Info->ProcessorArchitecture = CpuInfo.ProcessorArchitecture;
Info->ProcessorLevel = CpuInfo.ProcessorLevel;
Info->ProcessorRevision = CpuInfo.ProcessorRevision;
Info->ProcessorFeatureBits = CpuInfo.ProcessorFeatureBits;
}
}
32 changes: 32 additions & 0 deletions Source/Windows/Common/CPUFeatures.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
#pragma once

#include <windef.h>
#include <winternl.h>

namespace FEXCore::Context {
class Context;
}

/**
* @brief Maps CPUID results to Windows CPU info structures
*/
namespace FEX::Windows {
class CPUFeatures {
public:
CPUFeatures(FEXCore::Context::Context &CTX);

/**
* @brief If the given PF_* feature is supported
*/
bool IsFeaturePresent(uint32_t Feature);

/**
* @brief Fills in `Info` according to the detected CPU features
*/
void UpdateInformation(SYSTEM_CPU_INFORMATION *Info);

private:
SYSTEM_CPU_INFORMATION CpuInfo{};
};
}
File renamed without changes.
98 changes: 98 additions & 0 deletions Source/Windows/Common/InvalidationTracker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: MIT

#include <FEXCore/Utils/LogManager.h>
#include <FEXCore/Core/Context.h>
#include <FEXCore/Debug/InternalThreadState.h>
#include "InvalidationTracker.h"
#include <windef.h>
#include <winternl.h>

namespace FEX::Windows {
void InvalidationTracker::HandleMemoryProtectionNotification(FEXCore::Core::InternalThreadState *Thread, uint64_t Address, uint64_t Size, ULONG Prot) {
const auto AlignedBase = Address & FHU::FEX_PAGE_MASK;
const auto AlignedSize = (Address - AlignedBase + Size + FHU::FEX_PAGE_SIZE - 1) & FHU::FEX_PAGE_MASK;

if (Prot & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE)) {
Thread->CTX->InvalidateGuestCodeRange(Thread, AlignedBase, AlignedSize);
}

if (Prot & PAGE_EXECUTE_READWRITE) {
LogMan::Msg::DFmt("Add SMC interval: {:X} - {:X}", AlignedBase, AlignedBase + AlignedSize);
std::scoped_lock Lock(RWXIntervalsLock);
RWXIntervals.Insert({AlignedBase, AlignedBase + AlignedSize});
} else {
std::scoped_lock Lock(RWXIntervalsLock);
RWXIntervals.Remove({AlignedBase, AlignedBase + AlignedSize});
}
}

void InvalidationTracker::InvalidateContainingSection(FEXCore::Core::InternalThreadState *Thread, uint64_t Address, bool Free) {
MEMORY_BASIC_INFORMATION Info;
if (NtQueryVirtualMemory(NtCurrentProcess(), reinterpret_cast<void *>(Address), MemoryBasicInformation, &Info, sizeof(Info), nullptr))
return;

const auto SectionBase = reinterpret_cast<uint64_t>(Info.AllocationBase);
const auto SectionSize = reinterpret_cast<uint64_t>(Info.BaseAddress) + Info.RegionSize
- reinterpret_cast<uint64_t>(Info.AllocationBase);
Thread->CTX->InvalidateGuestCodeRange(Thread, SectionBase, SectionSize);

if (Free) {
std::scoped_lock Lock(RWXIntervalsLock);
RWXIntervals.Remove({SectionBase, SectionBase + SectionSize});
}
}

void InvalidationTracker::InvalidateAlignedInterval(FEXCore::Core::InternalThreadState *Thread, uint64_t Address, uint64_t Size, bool Free) {
const auto AlignedBase = Address & FHU::FEX_PAGE_MASK;
const auto AlignedSize = (Address - AlignedBase + Size + FHU::FEX_PAGE_SIZE - 1) & FHU::FEX_PAGE_MASK;
Thread->CTX->InvalidateGuestCodeRange(Thread, AlignedBase, AlignedSize);

if (Free) {
std::scoped_lock Lock(RWXIntervalsLock);
RWXIntervals.Remove({AlignedBase, AlignedBase + AlignedSize});
}
}

void InvalidationTracker::ReprotectRWXIntervals(uint64_t Address, uint64_t Size) {
const auto End = Address + Size;
std::scoped_lock Lock(RWXIntervalsLock);

do {
const auto Query = RWXIntervals.Query(Address);
if (Query.Enclosed) {
void *TmpAddress = reinterpret_cast<void *>(Address);
SIZE_T TmpSize = static_cast<SIZE_T>(std::min(End, Address + Query.Size) - Address);
ULONG TmpProt;
NtProtectVirtualMemory(NtCurrentProcess(), &TmpAddress, &TmpSize, PAGE_EXECUTE_READ, &TmpProt);
} else if (!Query.Size) {
// No more regions past `Address` in the interval list
break;
}

Address += Query.Size;
} while (Address < End);
}

bool InvalidationTracker::HandleRWXAccessViolation(FEXCore::Core::InternalThreadState *Thread, uint64_t FaultAddress) {
const bool NeedsInvalidate = [&](uint64_t Address) {
std::unique_lock Lock(RWXIntervalsLock);
const bool Enclosed = RWXIntervals.Query(Address).Enclosed;
// Invalidate just the single faulting page
if (!Enclosed)
return false;

ULONG TmpProt;
void *TmpAddress = reinterpret_cast<void *>(Address);
SIZE_T TmpSize = 1;
NtProtectVirtualMemory(NtCurrentProcess(), &TmpAddress, &TmpSize, PAGE_EXECUTE_READWRITE, &TmpProt);
return true;
}(FaultAddress);

if (NeedsInvalidate) {
// RWXIntervalsLock cannot be held during invalidation
Thread->CTX->InvalidateGuestCodeRange(Thread, FaultAddress & FHU::FEX_PAGE_MASK, FHU::FEX_PAGE_SIZE);
return true;
}
return false;
}
}
28 changes: 28 additions & 0 deletions Source/Windows/Common/InvalidationTracker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT
// FIXME TODO put in cpp
#pragma once

#include "IntervalList.h"
#include <mutex>

namespace FEXCore::Core {
struct InternalThreadState;
}

namespace FEX::Windows {
/**
* @brief Handles SMC and regular code invalidation
*/
class InvalidationTracker {
public:
void HandleMemoryProtectionNotification(FEXCore::Core::InternalThreadState *Thread, uint64_t Address, uint64_t Size, ULONG Prot);
void InvalidateContainingSection(FEXCore::Core::InternalThreadState *Thread, uint64_t Address, bool Free);
void InvalidateAlignedInterval(FEXCore::Core::InternalThreadState *Thread, uint64_t Address, uint64_t Size, bool Free);
void ReprotectRWXIntervals(uint64_t Address, uint64_t Size);
bool HandleRWXAccessViolation(FEXCore::Core::InternalThreadState *Thread, uint64_t FaultAddress);

private:
IntervalList<uint64_t> RWXIntervals;
std::mutex RWXIntervalsLock;
};
}

0 comments on commit 8ff4b52

Please sign in to comment.