But for normal msvc builds i would put it at around 30-50% Added per-xexmodule caching of information per instruction, can be used to remember what code needs compiling at start up Record what guest addresses wrote mmio and backpropagate that to future runs, eliminating dependence on exception trapping. this makes many games like h3 actually tolerable to run under a debugger fixed a number of errors where temporaries were being passed by reference/pointer Can now be compiled with clang-cl 14.0.1, requires -Werror off though and some other solution/project changes. Added macros wrapping compiler extensions like noinline, forceinline, __expect, and cold. Removed the "global lock" in guest code completely. It does not properly emulate the behavior of mfmsrd/mtmsr and it seriously cripples amd cpus. Removing this yielded around a 3x speedup in Halo Reach for me. Disabled the microprofiler for now. The microprofiler has a huge performance cost associated with it. Developers can re-enable it in the base/profiling header if they really need it Disable the trace writer in release builds. despite just returning after checking if the file was open the trace functions were consuming about 0.60% cpu time total Add IsValidReg, GetRegisterInfo is a huge (about 45k) branching function and using that to check if a register was valid consumed a significant chunk of time Optimized RingBuffer::ReadAndSwap and RingBuffer::read_count. This gave us the largest overall boost in performance. The memcpies were unnecessary and one of them was always a no-op Added simplification rules for multiplicative patterns like (x+x), (x<<1)+x For the most frequently called win32 functions i added code to call their underlying NT implementations, which lets us skip a lot of MS code we don't care about/isnt relevant to our usecases ^this can be toggled off in the platform_win header handle indirect call true with constant function pointer, was occurring in h3 lookup host format swizzle in denser array by default, don't check if a gpu register is unknown, instead just check if its out of range. controlled by a cvar ^looking up whether its known or not took approx 0.3% cpu time Changed some things in /cpu to make the project UNITYBUILD friendly The timer thread was spinning way too much and consuming a ton of cpu, changed it to use a blocking wait instead tagged some conditions as XE_UNLIKELY/LIKELY based on profiler feedback (will only affect clang builds) Shifted around some code in CommandProcessor::WriteRegister based on how frequently it was executed added support for docdecaduple precision floating point so that we can represent our performance gains numerically tons of other stuff im probably forgetting
373 lines
12 KiB
C++
373 lines
12 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2020 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <vector>
|
|
|
|
#include "third_party/fmt/include/fmt/format.h"
|
|
#include "xenia/base/logging.h"
|
|
#include "xenia/base/mapped_memory.h"
|
|
#include "xenia/base/math.h"
|
|
#include "xenia/base/memory.h"
|
|
#include "xenia/base/platform_win.h"
|
|
|
|
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | \
|
|
WINAPI_PARTITION_SYSTEM | WINAPI_PARTITION_GAMES)
|
|
#define XE_BASE_MAPPED_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
|
#endif
|
|
|
|
namespace xe {
|
|
|
|
class Win32MappedMemory : public MappedMemory {
|
|
public:
|
|
// CreateFile returns INVALID_HANDLE_VALUE in case of failure.
|
|
// chrispy: made inline const to get around clang error
|
|
static inline const HANDLE kFileHandleInvalid = INVALID_HANDLE_VALUE;
|
|
// CreateFileMapping returns nullptr in case of failure.
|
|
static constexpr HANDLE kMappingHandleInvalid = nullptr;
|
|
|
|
~Win32MappedMemory() override {
|
|
if (data_) {
|
|
UnmapViewOfFile(data_);
|
|
}
|
|
if (mapping_handle != kMappingHandleInvalid) {
|
|
CloseHandle(mapping_handle);
|
|
}
|
|
if (file_handle != kFileHandleInvalid) {
|
|
CloseHandle(file_handle);
|
|
}
|
|
}
|
|
|
|
void Close(uint64_t truncate_size) override {
|
|
if (data_) {
|
|
UnmapViewOfFile(data_);
|
|
data_ = nullptr;
|
|
}
|
|
if (mapping_handle != kMappingHandleInvalid) {
|
|
CloseHandle(mapping_handle);
|
|
mapping_handle = kMappingHandleInvalid;
|
|
}
|
|
if (file_handle != kFileHandleInvalid) {
|
|
if (truncate_size) {
|
|
LONG distance_high = truncate_size >> 32;
|
|
SetFilePointer(file_handle, truncate_size & 0xFFFFFFFF, &distance_high,
|
|
FILE_BEGIN);
|
|
SetEndOfFile(file_handle);
|
|
}
|
|
|
|
CloseHandle(file_handle);
|
|
file_handle = kFileHandleInvalid;
|
|
}
|
|
}
|
|
|
|
void Flush() override { FlushViewOfFile(data(), size()); }
|
|
bool Remap(size_t offset, size_t length) override {
|
|
size_t aligned_offset = offset & ~(memory::allocation_granularity() - 1);
|
|
size_t aligned_length = length + (offset - aligned_offset);
|
|
|
|
UnmapViewOfFile(data_);
|
|
#ifdef XE_BASE_MAPPED_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
|
data_ = MapViewOfFile(mapping_handle, view_access_, aligned_offset >> 32,
|
|
aligned_offset & 0xFFFFFFFF, aligned_length);
|
|
#else
|
|
data_ = MapViewOfFileFromApp(mapping_handle, ULONG(view_access_),
|
|
ULONG64(aligned_offset), aligned_length);
|
|
#endif
|
|
if (!data_) {
|
|
return false;
|
|
}
|
|
|
|
if (length) {
|
|
size_ = aligned_length;
|
|
} else {
|
|
DWORD length_high;
|
|
size_t map_length = GetFileSize(file_handle, &length_high);
|
|
map_length |= static_cast<uint64_t>(length_high) << 32;
|
|
size_ = map_length - aligned_offset;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
HANDLE file_handle = kFileHandleInvalid;
|
|
HANDLE mapping_handle = kMappingHandleInvalid;
|
|
DWORD view_access_ = 0;
|
|
};
|
|
|
|
std::unique_ptr<MappedMemory> MappedMemory::Open(
|
|
const std::filesystem::path& path, Mode mode, size_t offset,
|
|
size_t length) {
|
|
DWORD file_access = 0;
|
|
DWORD file_share = 0;
|
|
DWORD create_mode = 0;
|
|
DWORD mapping_protect = 0;
|
|
DWORD view_access = 0;
|
|
switch (mode) {
|
|
case Mode::kRead:
|
|
file_access |= GENERIC_READ;
|
|
file_share |= FILE_SHARE_READ;
|
|
create_mode |= OPEN_EXISTING;
|
|
mapping_protect |= PAGE_READONLY;
|
|
view_access |= FILE_MAP_READ;
|
|
break;
|
|
case Mode::kReadWrite:
|
|
file_access |= GENERIC_READ | GENERIC_WRITE;
|
|
file_share |= 0;
|
|
create_mode |= OPEN_EXISTING;
|
|
mapping_protect |= PAGE_READWRITE;
|
|
view_access |= FILE_MAP_READ | FILE_MAP_WRITE;
|
|
break;
|
|
}
|
|
|
|
SYSTEM_INFO system_info;
|
|
GetSystemInfo(&system_info);
|
|
|
|
const size_t aligned_offset =
|
|
offset & ~static_cast<size_t>(system_info.dwAllocationGranularity - 1);
|
|
const size_t aligned_length = length + (offset - aligned_offset);
|
|
|
|
auto mm = std::make_unique<Win32MappedMemory>();
|
|
mm->view_access_ = view_access;
|
|
|
|
mm->file_handle = CreateFile(path.c_str(), file_access, file_share, nullptr,
|
|
create_mode, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
if (mm->file_handle == Win32MappedMemory::kFileHandleInvalid) {
|
|
return nullptr;
|
|
}
|
|
|
|
#ifdef XE_BASE_MAPPED_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
|
mm->mapping_handle = CreateFileMapping(
|
|
mm->file_handle, nullptr, mapping_protect, DWORD(aligned_length >> 32),
|
|
DWORD(aligned_length), nullptr);
|
|
#else
|
|
mm->mapping_handle =
|
|
CreateFileMappingFromApp(mm->file_handle, nullptr, ULONG(mapping_protect),
|
|
ULONG64(aligned_length), nullptr);
|
|
#endif
|
|
if (mm->mapping_handle == Win32MappedMemory::kMappingHandleInvalid) {
|
|
return nullptr;
|
|
}
|
|
|
|
#ifdef XE_BASE_MAPPED_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
|
mm->data_ = reinterpret_cast<uint8_t*>(MapViewOfFile(
|
|
mm->mapping_handle, view_access, DWORD(aligned_offset >> 32),
|
|
DWORD(aligned_offset), aligned_length));
|
|
#else
|
|
mm->data_ = reinterpret_cast<uint8_t*>(
|
|
MapViewOfFileFromApp(mm->mapping_handle, ULONG(view_access),
|
|
ULONG64(aligned_offset), aligned_length));
|
|
#endif
|
|
if (!mm->data_) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (length) {
|
|
mm->size_ = aligned_length;
|
|
} else {
|
|
DWORD length_high;
|
|
size_t map_length = GetFileSize(mm->file_handle, &length_high);
|
|
map_length |= static_cast<uint64_t>(length_high) << 32;
|
|
mm->size_ = map_length - aligned_offset;
|
|
}
|
|
|
|
return std::move(mm);
|
|
}
|
|
|
|
class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
|
|
public:
|
|
Win32ChunkedMappedMemoryWriter(const std::filesystem::path& path,
|
|
size_t chunk_size, bool low_address_space)
|
|
: ChunkedMappedMemoryWriter(path, chunk_size, low_address_space) {}
|
|
|
|
~Win32ChunkedMappedMemoryWriter() override {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
chunks_.clear();
|
|
}
|
|
|
|
uint8_t* Allocate(size_t length) override {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
if (!chunks_.empty()) {
|
|
uint8_t* result = chunks_.back()->Allocate(length);
|
|
if (result != nullptr) {
|
|
return result;
|
|
}
|
|
}
|
|
auto chunk = std::make_unique<Chunk>(chunk_size_);
|
|
auto chunk_path =
|
|
path_.replace_extension(fmt::format(".{}", chunks_.size()));
|
|
if (!chunk->Open(chunk_path, low_address_space_)) {
|
|
return nullptr;
|
|
}
|
|
uint8_t* result = chunk->Allocate(length);
|
|
chunks_.push_back(std::move(chunk));
|
|
return result;
|
|
}
|
|
|
|
void Flush() override {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
for (auto& chunk : chunks_) {
|
|
chunk->Flush();
|
|
}
|
|
}
|
|
|
|
void FlushNew() override {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
for (auto& chunk : chunks_) {
|
|
chunk->FlushNew();
|
|
}
|
|
}
|
|
|
|
private:
|
|
class Chunk {
|
|
public:
|
|
explicit Chunk(size_t capacity)
|
|
: file_handle_(Win32MappedMemory::kFileHandleInvalid),
|
|
mapping_handle_(Win32MappedMemory::kMappingHandleInvalid),
|
|
data_(nullptr),
|
|
offset_(0),
|
|
capacity_(capacity),
|
|
last_flush_offset_(0) {}
|
|
|
|
~Chunk() {
|
|
if (data_) {
|
|
UnmapViewOfFile(data_);
|
|
}
|
|
if (mapping_handle_ != Win32MappedMemory::kMappingHandleInvalid) {
|
|
CloseHandle(mapping_handle_);
|
|
}
|
|
if (file_handle_ != Win32MappedMemory::kFileHandleInvalid) {
|
|
CloseHandle(file_handle_);
|
|
}
|
|
}
|
|
|
|
bool Open(const std::filesystem::path& path, bool low_address_space) {
|
|
DWORD file_access = GENERIC_READ | GENERIC_WRITE;
|
|
DWORD file_share = FILE_SHARE_READ;
|
|
DWORD create_mode = CREATE_ALWAYS;
|
|
DWORD mapping_protect = PAGE_READWRITE;
|
|
DWORD view_access = FILE_MAP_READ | FILE_MAP_WRITE;
|
|
|
|
file_handle_ = CreateFile(path.c_str(), file_access, file_share, nullptr,
|
|
create_mode, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
if (file_handle_ == Win32MappedMemory::kFileHandleInvalid) {
|
|
return false;
|
|
}
|
|
|
|
#ifdef XE_BASE_MAPPED_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
|
mapping_handle_ =
|
|
CreateFileMapping(file_handle_, nullptr, mapping_protect,
|
|
DWORD(capacity_ >> 32), DWORD(capacity_), nullptr);
|
|
#else
|
|
mapping_handle_ = CreateFileMappingFromApp(file_handle_, nullptr,
|
|
ULONG(mapping_protect),
|
|
ULONG64(capacity_), nullptr);
|
|
#endif
|
|
if (mapping_handle_ == Win32MappedMemory::kMappingHandleInvalid) {
|
|
return false;
|
|
}
|
|
|
|
// If specified, ensure the allocation occurs in the lower 32-bit address
|
|
// space.
|
|
if (low_address_space) {
|
|
bool successful = false;
|
|
data_ = reinterpret_cast<uint8_t*>(0x10000000);
|
|
#ifndef XE_BASE_MAPPED_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
|
HANDLE process = GetCurrentProcess();
|
|
#endif
|
|
for (int i = 0; i < 1000; ++i) {
|
|
#ifdef XE_BASE_MAPPED_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
|
if (MapViewOfFileEx(mapping_handle_, view_access, 0, 0, capacity_,
|
|
data_)) {
|
|
successful = true;
|
|
}
|
|
#else
|
|
// VirtualAlloc2FromApp and MapViewOfFile3FromApp were added in
|
|
// 10.0.17134.0.
|
|
// https://docs.microsoft.com/en-us/uwp/win32-and-com/win32-apis
|
|
if (VirtualAlloc2FromApp(process, data_, capacity_,
|
|
MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
|
|
PAGE_NOACCESS, nullptr, 0)) {
|
|
if (MapViewOfFile3FromApp(mapping_handle_, process, data_, 0,
|
|
capacity_, MEM_REPLACE_PLACEHOLDER,
|
|
ULONG(mapping_protect), nullptr, 0)) {
|
|
successful = true;
|
|
} else {
|
|
VirtualFree(data_, capacity_, MEM_RELEASE);
|
|
}
|
|
}
|
|
#endif
|
|
if (successful) {
|
|
break;
|
|
}
|
|
data_ += capacity_;
|
|
if (!successful) {
|
|
XELOGE("Unable to find space for mapping");
|
|
data_ = nullptr;
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
#ifdef XE_BASE_MAPPED_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
|
data_ = reinterpret_cast<uint8_t*>(
|
|
MapViewOfFile(mapping_handle_, view_access, 0, 0, capacity_));
|
|
#else
|
|
data_ = reinterpret_cast<uint8_t*>(MapViewOfFileFromApp(
|
|
mapping_handle_, ULONG(view_access), 0, capacity_));
|
|
#endif
|
|
}
|
|
if (!data_) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
uint8_t* Allocate(size_t length) {
|
|
if (capacity_ - offset_ < length) {
|
|
return nullptr;
|
|
}
|
|
uint8_t* result = data_ + offset_;
|
|
offset_ += length;
|
|
return result;
|
|
}
|
|
|
|
void Flush() { FlushViewOfFile(data_, offset_); }
|
|
|
|
void FlushNew() {
|
|
FlushViewOfFile(data_ + last_flush_offset_, offset_ - last_flush_offset_);
|
|
last_flush_offset_ = offset_;
|
|
}
|
|
|
|
private:
|
|
HANDLE file_handle_;
|
|
HANDLE mapping_handle_;
|
|
uint8_t* data_;
|
|
size_t offset_;
|
|
size_t capacity_;
|
|
size_t last_flush_offset_;
|
|
};
|
|
|
|
std::mutex mutex_;
|
|
std::vector<std::unique_ptr<Chunk>> chunks_;
|
|
};
|
|
|
|
std::unique_ptr<ChunkedMappedMemoryWriter> ChunkedMappedMemoryWriter::Open(
|
|
const std::filesystem::path& path, size_t chunk_size,
|
|
bool low_address_space) {
|
|
SYSTEM_INFO system_info;
|
|
GetSystemInfo(&system_info);
|
|
size_t aligned_chunk_size =
|
|
xe::round_up(chunk_size, system_info.dwAllocationGranularity);
|
|
return std::make_unique<Win32ChunkedMappedMemoryWriter>(
|
|
path, aligned_chunk_size, low_address_space);
|
|
}
|
|
|
|
} // namespace xe
|