fixed wine crash from use of NtSetEventPriorityBoost

add xe::clear_lowest_bit, use it in place of shift-andnot in some bit iteration code
make is_allocated_ and is_enabled_ volatile in xma_context
preallocate avpacket buffer in XMAContext::Setup, the reallocations of the buffer in ffmpeg were showing up on profiles
check is_enabled and is_allocated BEFORE locking an xmacontext. XMA worker was spending most of its time locking and unlocking contexts
Removed XeDMAC, dma:: namespace. It was a bad idea and I couldn't make it work in the end. Kept vastcpy and moved it to the memory namespace instead
Made the rest of global_critical_region's members static. They never needed an instance.
Removed ifdef'ed out code from ring_buffer.h
Added EventInfo struct to threading, added Event::Query to aid with implementing NtQueryEvent.
Removed vector from WaitMultiple, instead use a fixed array of 64 handles that we populate. WaitForMultipleObjects cannot handle more than 64 objects.
Remove XE_MSVC_OPTIMIZE_SMALL() use in x64_sequences, x64 backend is now always size optimized because of premake
Make global_critical_region_ static constexpr in shared_memory.h to get rid of wasteage of 8 bytes (empty class=1byte, +alignment for next member=8)
Move trace-related data to the tail of SharedMemory to keep more important data together
In IssueDraw build an array of fetch constant addresses/sizes, then pre-lock the global lock before doing requestrange for each instead of individually locking within requestrange for each of them
Consistent access specifier protected for pm4_command_processor_declare
Devirtualize WriteOneRegisterFromRing.
Move ExecutePacket and ExecutePrimaryBuffer to pm4_command_buffer_x
Remove many redundant header inclusions access xenia-gpu
Minor microoptimization of ExecutePacketType0

Add TextureCache::RequestTextures for batch invocation of LoadTexturesData

Add TextureCache::LoadTexturesData for reducing the number of times we release and reacquire the global lock.
Ideally you should hold the global lock for as little time as possible, but if you are constantly acquiring and releasing it you are actually more likely to have contention
Add already_locked param to ObjectTable::LookupObject to help with reducing lock acquire/release pairs
Add missing checks to XAudioRegisterRenderDriverClient_entry. this is unlikely to fix anything, it was just an easy thing to do
Add NtQueryEvent system call implementation. I don't actually know of any games that need it.
Instead of using std::vector + push_back in KeWaitForMultipleObjects and xeNtWaitForMultipleObjectsEx use a fixed size array of 64 and track the count. More than 64 objects is not permitted by the kernel. The repeated reallocations from push_back were appearing unusually high on the profiler, but were masked until now by waitformultipleobjects natural overhead
Pre-lock the global lock before looking up each handle for xeNtWaitForMultipleObjectsEx and KeWaitForMultipleObjects.
Pre-lock before looking up the signal and waiter in NtSignalAndWaitForSingleObjectEx
add missing checks to NtWaitForMultipleObjectsEx
Support pre-locking in XObject::GetNativeObject
This commit is contained in:
chss95cs@gmail.com
2022-10-08 09:55:17 -07:00
parent bae63b95c5
commit 8f7f7dc6ad
50 changed files with 722 additions and 788 deletions

View File

@@ -1,348 +0,0 @@
#include "dma.h"
#include "logging.h"
#include "mutex.h"
#include "platform_win.h"
XE_NTDLL_IMPORT(NtDelayExecution, cls_NtDelayExecution,
NtDelayExecutionPointer);
XE_NTDLL_IMPORT(NtAlertThread, cls_NtAlertThread, NtAlertThreadPointer);
XE_NTDLL_IMPORT(NtAlertThreadByThreadId, cls_NtAlertThreadByThreadId,
NtAlertThreadByThreadId);
template <size_t N, typename... Ts>
static void xedmaloghelper(const char (&fmt)[N], Ts... args) {
char buffer[1024];
sprintf_s(buffer, fmt, args...);
XELOGI("%s", buffer);
}
//#define XEDMALOG(...) XELOGI("XeDma: " __VA_ARGS__)
//#define XEDMALOG(...) xedmaloghelper("XeDma: " __VA_ARGS__)
#define XEDMALOG(...) static_cast<void>(0)
using xe::swcache::CacheLine;
static constexpr unsigned NUM_CACHELINES_IN_PAGE = 4096 / sizeof(CacheLine);
#if defined(__clang__)
XE_FORCEINLINE
static void mvdir64b(void* to, const void* from) {
__asm__("movdir64b %1, %0" : : "r"(to), "m"(*(char*)from) : "memory");
}
#define _movdir64b mvdir64b
#endif
XE_FORCEINLINE
static void XeCopy16384StreamingAVX(CacheLine* XE_RESTRICT to,
CacheLine* XE_RESTRICT from) {
uint32_t num_lines_for_8k = 4096 / XE_HOST_CACHE_LINE_SIZE;
CacheLine* dest1 = to;
CacheLine* src1 = from;
CacheLine* dest2 = to + NUM_CACHELINES_IN_PAGE;
CacheLine* src2 = from + NUM_CACHELINES_IN_PAGE;
CacheLine* dest3 = to + (NUM_CACHELINES_IN_PAGE * 2);
CacheLine* src3 = from + (NUM_CACHELINES_IN_PAGE * 2);
CacheLine* dest4 = to + (NUM_CACHELINES_IN_PAGE * 3);
CacheLine* src4 = from + (NUM_CACHELINES_IN_PAGE * 3);
#pragma loop(no_vector)
for (uint32_t i = 0; i < num_lines_for_8k; ++i) {
xe::swcache::CacheLine line0, line1, line2, line3;
xe::swcache::ReadLine(&line0, src1 + i);
xe::swcache::ReadLine(&line1, src2 + i);
xe::swcache::ReadLine(&line2, src3 + i);
xe::swcache::ReadLine(&line3, src4 + i);
XE_MSVC_REORDER_BARRIER();
xe::swcache::WriteLineNT(dest1 + i, &line0);
xe::swcache::WriteLineNT(dest2 + i, &line1);
xe::swcache::WriteLineNT(dest3 + i, &line2);
xe::swcache::WriteLineNT(dest4 + i, &line3);
}
XE_MSVC_REORDER_BARRIER();
}
XE_FORCEINLINE
static void XeCopy16384Movdir64M(CacheLine* XE_RESTRICT to,
CacheLine* XE_RESTRICT from) {
uint32_t num_lines_for_8k = 4096 / XE_HOST_CACHE_LINE_SIZE;
CacheLine* dest1 = to;
CacheLine* src1 = from;
CacheLine* dest2 = to + NUM_CACHELINES_IN_PAGE;
CacheLine* src2 = from + NUM_CACHELINES_IN_PAGE;
CacheLine* dest3 = to + (NUM_CACHELINES_IN_PAGE * 2);
CacheLine* src3 = from + (NUM_CACHELINES_IN_PAGE * 2);
CacheLine* dest4 = to + (NUM_CACHELINES_IN_PAGE * 3);
CacheLine* src4 = from + (NUM_CACHELINES_IN_PAGE * 3);
#pragma loop(no_vector)
for (uint32_t i = 0; i < num_lines_for_8k; ++i) {
#if 0
xe::swcache::CacheLine line0, line1, line2, line3;
xe::swcache::ReadLine(&line0, src1 + i);
xe::swcache::ReadLine(&line1, src2 + i);
xe::swcache::ReadLine(&line2, src3 + i);
xe::swcache::ReadLine(&line3, src4 + i);
XE_MSVC_REORDER_BARRIER();
xe::swcache::WriteLineNT(dest1 + i, &line0);
xe::swcache::WriteLineNT(dest2 + i, &line1);
xe::swcache::WriteLineNT(dest3 + i, &line2);
xe::swcache::WriteLineNT(dest4 + i, &line3);
#else
_movdir64b(dest1 + i, src1 + i);
_movdir64b(dest2 + i, src2 + i);
_movdir64b(dest3 + i, src3 + i);
_movdir64b(dest4 + i, src4 + i);
#endif
}
XE_MSVC_REORDER_BARRIER();
}
namespace xe::dma {
using VastCpyDispatch = void (*)(CacheLine* XE_RESTRICT physaddr,
CacheLine* XE_RESTRICT rdmapping,
uint32_t written_length);
static void vastcpy_impl_avx(CacheLine* XE_RESTRICT physaddr,
CacheLine* XE_RESTRICT rdmapping,
uint32_t written_length) {
static constexpr unsigned NUM_LINES_FOR_16K = 16384 / XE_HOST_CACHE_LINE_SIZE;
while (written_length >= 16384) {
XeCopy16384StreamingAVX(physaddr, rdmapping);
physaddr += NUM_LINES_FOR_16K;
rdmapping += NUM_LINES_FOR_16K;
written_length -= 16384;
}
if (!written_length) {
return;
}
uint32_t num_written_lines = written_length / XE_HOST_CACHE_LINE_SIZE;
uint32_t i = 0;
for (; i + 1 < num_written_lines; i += 2) {
xe::swcache::CacheLine line0, line1;
xe::swcache::ReadLine(&line0, rdmapping + i);
xe::swcache::ReadLine(&line1, rdmapping + i + 1);
XE_MSVC_REORDER_BARRIER();
xe::swcache::WriteLineNT(physaddr + i, &line0);
xe::swcache::WriteLineNT(physaddr + i + 1, &line1);
}
if (i < num_written_lines) {
xe::swcache::CacheLine line0;
xe::swcache::ReadLine(&line0, rdmapping + i);
xe::swcache::WriteLineNT(physaddr + i, &line0);
}
}
static void vastcpy_impl_movdir64m(CacheLine* XE_RESTRICT physaddr,
CacheLine* XE_RESTRICT rdmapping,
uint32_t written_length) {
static constexpr unsigned NUM_LINES_FOR_16K = 16384 / XE_HOST_CACHE_LINE_SIZE;
while (written_length >= 16384) {
XeCopy16384Movdir64M(physaddr, rdmapping);
physaddr += NUM_LINES_FOR_16K;
rdmapping += NUM_LINES_FOR_16K;
written_length -= 16384;
}
if (!written_length) {
return;
}
uint32_t num_written_lines = written_length / XE_HOST_CACHE_LINE_SIZE;
uint32_t i = 0;
for (; i + 1 < num_written_lines; i += 2) {
_movdir64b(physaddr + i, rdmapping + i);
_movdir64b(physaddr + i + 1, rdmapping + i + 1);
}
if (i < num_written_lines) {
_movdir64b(physaddr + i, rdmapping + i);
}
}
XE_COLD
static void first_vastcpy(CacheLine* XE_RESTRICT physaddr,
CacheLine* XE_RESTRICT rdmapping,
uint32_t written_length);
static VastCpyDispatch vastcpy_dispatch = first_vastcpy;
XE_COLD
static void first_vastcpy(CacheLine* XE_RESTRICT physaddr,
CacheLine* XE_RESTRICT rdmapping,
uint32_t written_length) {
VastCpyDispatch dispatch_to_use = nullptr;
if (amd64::GetFeatureFlags() & amd64::kX64EmitMovdir64M) {
XELOGI("Selecting MOVDIR64M vastcpy.");
dispatch_to_use = vastcpy_impl_movdir64m;
} else {
XELOGI("Selecting generic AVX vastcpy.");
dispatch_to_use = vastcpy_impl_avx;
}
vastcpy_dispatch =
dispatch_to_use; // all future calls will go through our selected path
return vastcpy_dispatch(physaddr, rdmapping, written_length);
}
XE_NOINLINE
void vastcpy(uint8_t* XE_RESTRICT physaddr, uint8_t* XE_RESTRICT rdmapping,
uint32_t written_length) {
return vastcpy_dispatch((CacheLine*)physaddr, (CacheLine*)rdmapping,
written_length);
}
#define MAX_INFLIGHT_DMAJOBS 65536
#define INFLICT_DMAJOB_MASK (MAX_INFLIGHT_DMAJOBS - 1)
class XeDMACGeneric : public XeDMAC {
std::unique_ptr<xe::threading::Thread> thrd_;
XeDMAJob* jobs_ring_;
volatile std::atomic<uintptr_t> write_ptr_;
struct alignas(XE_HOST_CACHE_LINE_SIZE) {
volatile std::atomic<uintptr_t> read_ptr_;
xe_mutex push_into_ring_lock_;
};
HANDLE gotjob_event;
void WorkerWait();
public:
virtual ~XeDMACGeneric() {}
void WorkerThreadMain();
XeDMACGeneric() {
threading::Thread::CreationParameters crparams;
crparams.create_suspended = true;
crparams.initial_priority = threading::ThreadPriority::kNormal;
crparams.stack_size = 65536;
gotjob_event = CreateEventA(nullptr, false, false, nullptr);
thrd_ = std::move(threading::Thread::Create(
crparams, [this]() { this->WorkerThreadMain(); }));
jobs_ring_ = (XeDMAJob*)_aligned_malloc(
MAX_INFLIGHT_DMAJOBS * sizeof(XeDMAJob), XE_HOST_CACHE_LINE_SIZE);
write_ptr_ = 0;
read_ptr_ = 0;
thrd_->Resume();
}
virtual DMACJobHandle PushDMAJob(XeDMAJob* job) override {
// std::unique_lock<xe_mutex> pushlock{push_into_ring_lock_};
HANDLE dmacevent = CreateEventA(nullptr, true, false, nullptr);
{
job->dmac_specific_ = (uintptr_t)dmacevent;
jobs_ring_[write_ptr_ % MAX_INFLIGHT_DMAJOBS] = *job;
write_ptr_++;
SetEvent(gotjob_event);
}
return (DMACJobHandle)dmacevent;
}
virtual void WaitJobDone(DMACJobHandle handle) override {
while (WaitForSingleObject((HANDLE)handle, 2) == WAIT_TIMEOUT) {
// NtAlertThreadByThreadId.invoke<void>(thrd_->system_id());
// while (SignalObjectAndWait(gotjob_event, (HANDLE)handle, 2, false) ==
// WAIT_TIMEOUT) {
// ;
}
//}
// SignalObjectAndWait(gotjob_event, (HANDLE)handle, INFINITE, false);
CloseHandle((HANDLE)handle);
}
virtual void WaitForIdle() override {
while (write_ptr_ != read_ptr_) {
threading::MaybeYield();
}
}
};
void XeDMACGeneric::WorkerWait() {
constexpr unsigned NUM_PAUSE_SPINS = 2048;
constexpr unsigned NUM_YIELD_SPINS = 8;
#if 0
for (unsigned i = 0; i < NUM_PAUSE_SPINS; ++i) {
if (write_ptr_ == read_ptr_) {
_mm_pause();
} else {
break;
}
}
for (unsigned i = 0; i < NUM_YIELD_SPINS; ++i) {
if (write_ptr_ == read_ptr_) {
threading::MaybeYield();
} else {
break;
}
}
LARGE_INTEGER yield_execution_delay{};
yield_execution_delay.QuadPart =
-2000; //-10000 == 1 ms, so -2000 means delay for 0.2 milliseconds
while (write_ptr_ == read_ptr_) {
NtDelayExecutionPointer.invoke<void>(0, &yield_execution_delay);
}
#else
do {
if (WaitForSingleObjectEx(gotjob_event, 1, TRUE) == WAIT_OBJECT_0) {
while (write_ptr_ == read_ptr_) {
_mm_pause();
}
}
} while (write_ptr_ == read_ptr_);
#endif
}
void XeDMACGeneric::WorkerThreadMain() {
while (true) {
this->WorkerWait();
XeDMAJob current_job = jobs_ring_[read_ptr_ % MAX_INFLIGHT_DMAJOBS];
swcache::ReadFence();
if (current_job.precall) {
current_job.precall(&current_job);
}
size_t num_lines = current_job.size / XE_HOST_CACHE_LINE_SIZE;
size_t line_rounded = num_lines * XE_HOST_CACHE_LINE_SIZE;
size_t line_rem = current_job.size - line_rounded;
vastcpy(current_job.destination, current_job.source,
static_cast<uint32_t>(line_rounded));
if (line_rem) {
__movsb(current_job.destination + line_rounded,
current_job.source + line_rounded, line_rem);
}
if (current_job.postcall) {
current_job.postcall(&current_job);
}
read_ptr_++;
swcache::WriteFence();
SetEvent((HANDLE)current_job.dmac_specific_);
}
}
XeDMAC* CreateDMAC() { return new XeDMACGeneric(); }
} // namespace xe::dma

View File

@@ -1,47 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_BASE_DMA_H_
#define XENIA_BASE_DMA_H_
#include "memory.h"
#include "threading.h"
namespace xe::dma {
struct XeDMAJob;
using DmaPrecall = void (*)(XeDMAJob* job);
using DmaPostcall = void (*)(XeDMAJob* job);
struct XeDMAJob {
//threading::Event* signal_on_done;
uintptr_t dmac_specific_;
uint8_t* destination;
uint8_t* source;
size_t size;
DmaPrecall precall;
DmaPostcall postcall;
void* userdata1;
void* userdata2;
};
using DMACJobHandle = uint64_t;
class XeDMAC {
public:
virtual ~XeDMAC() {}
virtual DMACJobHandle PushDMAJob(XeDMAJob* job) = 0;
virtual void WaitJobDone(DMACJobHandle handle) = 0;
virtual void WaitForIdle() = 0;
};
XeDMAC* CreateDMAC();
// must be divisible by cache line size
XE_NOINLINE
void vastcpy(uint8_t* XE_RESTRICT physaddr, uint8_t* XE_RESTRICT rdmapping,
uint32_t written_length);
} // namespace xe::dma
#endif // XENIA_BASE_DMA_H_

View File

@@ -44,8 +44,18 @@ template <typename T>
constexpr bool is_pow2(T value) {
return (value & (value - 1)) == 0;
}
/*
Use this in place of the shift + and not sequence that is being used currently in bit iteration code. This is more efficient
because it does not introduce a dependency on to the previous bit scanning operation. The shift and not sequence does get translated to a single instruction (the bit test and reset instruction),
but this code can be executed alongside the scan
*/
template<typename T>
constexpr T clear_lowest_bit(T value) {
static_assert(std::is_integral_v<T>);
// Rounds up the given value to the given alignment.
return (value - static_cast<T>(1)) & value;
}
// Rounds up the given value to the given alignment.
template <typename T>
constexpr T align(T value, T alignment) {
return (value + alignment - 1) & ~(alignment - 1);

View File

@@ -10,6 +10,7 @@
#include "xenia/base/memory.h"
#include "xenia/base/cvar.h"
#include "xenia/base/platform.h"
#include "xenia/base/logging.h"
#if XE_ARCH_ARM64
#include <arm_neon.h>
@@ -31,6 +32,180 @@ bool IsWritableExecutableMemoryPreferred() {
cvars::writable_executable_memory;
}
using xe::swcache::CacheLine;
static constexpr unsigned NUM_CACHELINES_IN_PAGE = 4096 / sizeof(CacheLine);
#if defined(__clang__)
XE_FORCEINLINE
static void mvdir64b(void* to, const void* from) {
__asm__("movdir64b %1, %0" : : "r"(to), "m"(*(char*)from) : "memory");
}
#define _movdir64b mvdir64b
#endif
XE_FORCEINLINE
static void XeCopy16384StreamingAVX(CacheLine* XE_RESTRICT to,
CacheLine* XE_RESTRICT from) {
uint32_t num_lines_for_8k = 4096 / XE_HOST_CACHE_LINE_SIZE;
CacheLine* dest1 = to;
CacheLine* src1 = from;
CacheLine* dest2 = to + NUM_CACHELINES_IN_PAGE;
CacheLine* src2 = from + NUM_CACHELINES_IN_PAGE;
CacheLine* dest3 = to + (NUM_CACHELINES_IN_PAGE * 2);
CacheLine* src3 = from + (NUM_CACHELINES_IN_PAGE * 2);
CacheLine* dest4 = to + (NUM_CACHELINES_IN_PAGE * 3);
CacheLine* src4 = from + (NUM_CACHELINES_IN_PAGE * 3);
#pragma loop(no_vector)
for (uint32_t i = 0; i < num_lines_for_8k; ++i) {
xe::swcache::CacheLine line0, line1, line2, line3;
xe::swcache::ReadLine(&line0, src1 + i);
xe::swcache::ReadLine(&line1, src2 + i);
xe::swcache::ReadLine(&line2, src3 + i);
xe::swcache::ReadLine(&line3, src4 + i);
XE_MSVC_REORDER_BARRIER();
xe::swcache::WriteLineNT(dest1 + i, &line0);
xe::swcache::WriteLineNT(dest2 + i, &line1);
xe::swcache::WriteLineNT(dest3 + i, &line2);
xe::swcache::WriteLineNT(dest4 + i, &line3);
}
XE_MSVC_REORDER_BARRIER();
}
XE_FORCEINLINE
static void XeCopy16384Movdir64M(CacheLine* XE_RESTRICT to,
CacheLine* XE_RESTRICT from) {
uint32_t num_lines_for_8k = 4096 / XE_HOST_CACHE_LINE_SIZE;
CacheLine* dest1 = to;
CacheLine* src1 = from;
CacheLine* dest2 = to + NUM_CACHELINES_IN_PAGE;
CacheLine* src2 = from + NUM_CACHELINES_IN_PAGE;
CacheLine* dest3 = to + (NUM_CACHELINES_IN_PAGE * 2);
CacheLine* src3 = from + (NUM_CACHELINES_IN_PAGE * 2);
CacheLine* dest4 = to + (NUM_CACHELINES_IN_PAGE * 3);
CacheLine* src4 = from + (NUM_CACHELINES_IN_PAGE * 3);
#pragma loop(no_vector)
for (uint32_t i = 0; i < num_lines_for_8k; ++i) {
_movdir64b(dest1 + i, src1 + i);
_movdir64b(dest2 + i, src2 + i);
_movdir64b(dest3 + i, src3 + i);
_movdir64b(dest4 + i, src4 + i);
}
XE_MSVC_REORDER_BARRIER();
}
using VastCpyDispatch = void (*)(CacheLine* XE_RESTRICT physaddr,
CacheLine* XE_RESTRICT rdmapping,
uint32_t written_length);
static void vastcpy_impl_avx(CacheLine* XE_RESTRICT physaddr,
CacheLine* XE_RESTRICT rdmapping,
uint32_t written_length) {
static constexpr unsigned NUM_LINES_FOR_16K = 16384 / XE_HOST_CACHE_LINE_SIZE;
while (written_length >= 16384) {
XeCopy16384StreamingAVX(physaddr, rdmapping);
physaddr += NUM_LINES_FOR_16K;
rdmapping += NUM_LINES_FOR_16K;
written_length -= 16384;
}
if (!written_length) {
return;
}
uint32_t num_written_lines = written_length / XE_HOST_CACHE_LINE_SIZE;
uint32_t i = 0;
for (; i + 1 < num_written_lines; i += 2) {
xe::swcache::CacheLine line0, line1;
xe::swcache::ReadLine(&line0, rdmapping + i);
xe::swcache::ReadLine(&line1, rdmapping + i + 1);
XE_MSVC_REORDER_BARRIER();
xe::swcache::WriteLineNT(physaddr + i, &line0);
xe::swcache::WriteLineNT(physaddr + i + 1, &line1);
}
if (i < num_written_lines) {
xe::swcache::CacheLine line0;
xe::swcache::ReadLine(&line0, rdmapping + i);
xe::swcache::WriteLineNT(physaddr + i, &line0);
}
}
static void vastcpy_impl_movdir64m(CacheLine* XE_RESTRICT physaddr,
CacheLine* XE_RESTRICT rdmapping,
uint32_t written_length) {
static constexpr unsigned NUM_LINES_FOR_16K = 16384 / XE_HOST_CACHE_LINE_SIZE;
while (written_length >= 16384) {
XeCopy16384Movdir64M(physaddr, rdmapping);
physaddr += NUM_LINES_FOR_16K;
rdmapping += NUM_LINES_FOR_16K;
written_length -= 16384;
}
if (!written_length) {
return;
}
uint32_t num_written_lines = written_length / XE_HOST_CACHE_LINE_SIZE;
uint32_t i = 0;
for (; i + 1 < num_written_lines; i += 2) {
_movdir64b(physaddr + i, rdmapping + i);
_movdir64b(physaddr + i + 1, rdmapping + i + 1);
}
if (i < num_written_lines) {
_movdir64b(physaddr + i, rdmapping + i);
}
}
XE_COLD
static void first_vastcpy(CacheLine* XE_RESTRICT physaddr,
CacheLine* XE_RESTRICT rdmapping,
uint32_t written_length);
static VastCpyDispatch vastcpy_dispatch = first_vastcpy;
XE_COLD
static void first_vastcpy(CacheLine* XE_RESTRICT physaddr,
CacheLine* XE_RESTRICT rdmapping,
uint32_t written_length) {
VastCpyDispatch dispatch_to_use = nullptr;
if (amd64::GetFeatureFlags() & amd64::kX64EmitMovdir64M) {
XELOGI("Selecting MOVDIR64M vastcpy.");
dispatch_to_use = vastcpy_impl_movdir64m;
} else {
XELOGI("Selecting generic AVX vastcpy.");
dispatch_to_use = vastcpy_impl_avx;
}
vastcpy_dispatch =
dispatch_to_use; // all future calls will go through our selected path
return vastcpy_dispatch(physaddr, rdmapping, written_length);
}
XE_NOINLINE
void vastcpy(uint8_t* XE_RESTRICT physaddr, uint8_t* XE_RESTRICT rdmapping,
uint32_t written_length) {
return vastcpy_dispatch((CacheLine*)physaddr, (CacheLine*)rdmapping,
written_length);
}
} // namespace memory
// TODO(benvanik): fancy AVX versions.

View File

@@ -17,9 +17,9 @@
#include <string>
#include <string_view>
#include "xenia/base/assert.h"
#include "xenia/base/byte_order.h"
#include "xenia/base/platform.h"
namespace xe {
namespace memory {
@@ -141,6 +141,10 @@ size_t hash_combine(size_t seed, const T& v, const Ts&... vs) {
seed ^= hasher(v) + 0x9E3779B9 + (seed << 6) + (seed >> 2);
return hash_combine(seed, vs...);
}
// must be divisible by cache line size
XE_NOINLINE
void vastcpy(uint8_t* XE_RESTRICT physaddr, uint8_t* XE_RESTRICT rdmapping,
uint32_t written_length);
} // namespace memory

View File

@@ -133,6 +133,7 @@ using global_unique_lock_type = std::unique_lock<global_mutex_type>;
// };
class global_critical_region {
public:
constexpr global_critical_region() {}
static global_mutex_type& mutex();
// Acquires a lock on the global critical section.
@@ -144,18 +145,18 @@ class global_critical_region {
}
// Acquires a lock on the global critical section.
inline global_unique_lock_type Acquire() {
static inline global_unique_lock_type Acquire() {
return global_unique_lock_type(mutex());
}
// Acquires a deferred lock on the global critical section.
inline global_unique_lock_type AcquireDeferred() {
static inline global_unique_lock_type AcquireDeferred() {
return global_unique_lock_type(mutex(), std::defer_lock);
}
// Tries to acquire a lock on the glboal critical section.
// Check owns_lock() to see if the lock was successfully acquired.
inline global_unique_lock_type TryAcquire() {
static inline global_unique_lock_type TryAcquire() {
return global_unique_lock_type(mutex(), std::try_to_lock);
}
};

View File

@@ -183,20 +183,13 @@ inline uint32_t RingBuffer::ReadAndSwap<uint32_t>() {
xenia_assert(this->capacity_ >= 4);
ring_size_t next_read_offset = read_offset + 4;
#if 0
size_t zerotest = next_read_offset - this->capacity_;
// unpredictable branch, use bit arith instead
// todo: it would be faster to use lzcnt, but we need to figure out if all
// machines we support support it
next_read_offset &= -static_cast<ptrdiff_t>(!!zerotest);
#else
if (XE_UNLIKELY(next_read_offset == this->capacity_)) {
next_read_offset = 0;
// todo: maybe prefetch next? or should that happen much earlier?
}
#endif
this->read_offset_ = next_read_offset;
unsigned int ring_value = *(uint32_t*)&this->buffer_[read_offset];
uint32_t ring_value = *(uint32_t*)&this->buffer_[read_offset];
return xe::byte_swap(ring_value);
}
} // namespace xe

View File

@@ -271,7 +271,10 @@ inline std::pair<WaitResult, size_t> WaitAny(
return WaitAny(wait_handles.data(), wait_handles.size(), is_alertable,
timeout);
}
struct EventInfo {
uint32_t type;
uint32_t state;
};
// Models a Win32-like event object.
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms682396(v=vs.85).aspx
class Event : public WaitHandle {
@@ -299,6 +302,8 @@ class Event : public WaitHandle {
// the nonsignaled state after releasing the appropriate number of waiting
// threads.
virtual void Pulse() = 0;
virtual EventInfo Query() = 0;
#if XE_PLATFORM_WIN32 ==1
//SetEvent, but if there is a waiter we immediately transfer execution to it
virtual void SetBoostPriority() = 0;

View File

@@ -55,7 +55,7 @@ XE_NTDLL_IMPORT(NtReleaseSemaphore, cls_NtReleaseSemaphore,
XE_NTDLL_IMPORT(NtDelayExecution, cls_NtDelayExecution,
NtDelayExecutionPointer);
XE_NTDLL_IMPORT(NtQueryEvent, cls_NtQueryEvent, NtQueryEventPointer);
namespace xe {
namespace threading {
@@ -255,20 +255,20 @@ std::pair<WaitResult, size_t> WaitMultiple(WaitHandle* wait_handles[],
size_t wait_handle_count,
bool wait_all, bool is_alertable,
std::chrono::milliseconds timeout) {
std::vector<HANDLE> handles(
wait_handle_count); // max handles is like 64, so it would make more
// sense to just do a fixed size array here
xenia_assert(wait_handle_count <= 64);
HANDLE handles[64];
for (size_t i = 0; i < wait_handle_count; ++i) {
handles[i] = wait_handles[i]->native_handle();
}
DWORD result = WaitForMultipleObjectsEx(
DWORD(handles.size()), handles.data(), wait_all ? TRUE : FALSE,
static_cast<DWORD>(wait_handle_count), handles, wait_all ? TRUE : FALSE,
DWORD(timeout.count()), is_alertable ? TRUE : FALSE);
if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + handles.size()) {
if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + wait_handle_count) {
return std::pair<WaitResult, size_t>(WaitResult::kSuccess,
result - WAIT_OBJECT_0);
} else if (result >= WAIT_ABANDONED_0 &&
result < WAIT_ABANDONED_0 + handles.size()) {
result < WAIT_ABANDONED_0 + wait_handle_count) {
return std::pair<WaitResult, size_t>(WaitResult::kAbandoned,
result - WAIT_ABANDONED_0);
}
@@ -293,7 +293,15 @@ class Win32Event : public Win32Handle<Event> {
void Pulse() override { NtPulseEventPointer.invoke(handle_, nullptr); }
void SetBoostPriority() override {
// no previous state for boostpriority
NtSetEventBoostPriorityPointer.invoke(handle_);
// Boost priority is unimplemented under wine probably because it's not used
// anywhere in user mode except by us. Maybe some Windows internals uses it
// see:
// https://discord.com/channels/308194948048486401/308207592482668545/1027178776599216228
if (NtSetEventBoostPriorityPointer) {
NtSetEventBoostPriorityPointer.invoke(handle_);
} else {
NtSetEventPointer.invoke(handle_, nullptr);
}
}
#else
void Set() override { SetEvent(handle_); }
@@ -305,6 +313,11 @@ class Win32Event : public Win32Handle<Event> {
SetEvent(handle_);
}
#endif
EventInfo Query() { EventInfo result{};
NtQueryEventPointer.invoke(handle_, 0, &result, sizeof(EventInfo), nullptr);
return result;
}
};
std::unique_ptr<Event> Event::CreateManualResetEvent(bool initial_state) {