mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-27 16:00:42 +01:00
Merge pull request #513 from Sonicadvance1/compile_service
Implements a threaded compile service routine
This commit is contained in:
commit
511812dfd9
6 files changed
+293
-14
No files matched your search
+1
@@ -120,6 +120,7 @@ set (SRCS
|
||||
Interface/Context/Context.cpp
|
||||
Interface/Core/BlockCache.cpp
|
||||
Interface/Core/BlockSamplingData.cpp
|
||||
Interface/Core/CompileService.cpp
|
||||
Interface/Core/Core.cpp
|
||||
Interface/Core/CPUID.cpp
|
||||
Interface/Core/Frontend.cpp
|
||||
|
||||
@@ -139,6 +139,7 @@ namespace FEXCore::Context {
|
||||
FEXCore::Core::ThreadState *GetThreadState();
|
||||
void LoadEntryList();
|
||||
|
||||
void *CompileCode(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestRIP);
|
||||
uintptr_t CompileBlock(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestRIP);
|
||||
uintptr_t CompileFallbackBlock(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestRIP);
|
||||
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
#include "Interface/Context/Context.h"
|
||||
#include "Interface/Core/BlockCache.h"
|
||||
#include "Interface/Core/CompileService.h"
|
||||
#include "Interface/Core/InternalThreadState.h"
|
||||
#include "Interface/Core/OpcodeDispatcher.h"
|
||||
#include "Interface/Core/SignalDelegator.h"
|
||||
|
||||
namespace FEXCore {
|
||||
CompileService::CompileService(FEXCore::Context::Context *ctx, FEXCore::Core::InternalThreadState *Thread)
|
||||
: CTX {ctx}
|
||||
, ParentThread {Thread} {
|
||||
|
||||
CompileThreadData = std::make_unique<FEXCore::Core::InternalThreadState>();
|
||||
CompileThreadData->IsCompileService = true;
|
||||
|
||||
// We need a compiler for this work thread
|
||||
CTX->InitializeCompiler(CompileThreadData.get(), true);
|
||||
CompileThreadData->CPUBackend->CopyNecessaryDataForCompileThread(ParentThread->CPUBackend.get());
|
||||
|
||||
WorkerThread = std::thread([this]() {
|
||||
ExecutionThread();
|
||||
});
|
||||
}
|
||||
|
||||
void CompileService::Initialize() {
|
||||
// Share CompileService which = this
|
||||
CompileThreadData->CompileService = ParentThread->CompileService;
|
||||
}
|
||||
|
||||
void CompileService::Shutdown() {
|
||||
ShuttingDown = true;
|
||||
// Kick the working thread
|
||||
StartWork.NotifyAll();
|
||||
WorkerThread.join();
|
||||
}
|
||||
|
||||
void CompileService::ClearCache(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestRIP) {
|
||||
// On cache clear we need to spin down the execution thread to ensure it isn't trying to give us more work items
|
||||
if (CompileMutex.try_lock()) {
|
||||
// We can only clear these things if we pulled the compile mutex
|
||||
|
||||
// Grab the work queue and clear it
|
||||
// We don't need to grab the queue mutex since this thread will no longer receive any work events
|
||||
// Threads are bounded 1:1
|
||||
while (WorkQueue.size()) {
|
||||
WorkItem *Item = WorkQueue.front();
|
||||
WorkQueue.pop();
|
||||
delete Item;
|
||||
}
|
||||
|
||||
// Go through the garbage collection array and clear it
|
||||
// It's safe to clear things that aren't marked safe since we are clearing cache
|
||||
if (GCArray.size()) {
|
||||
// Clean up our GC array
|
||||
for (auto it = GCArray.begin(); it != GCArray.end();) {
|
||||
delete *it;
|
||||
it = GCArray.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
if (GuestRIP == 0) {
|
||||
CompileThreadData->IRLists.clear();
|
||||
}
|
||||
else {
|
||||
auto IR = CompileThreadData->IRLists.find(GuestRIP)->second.release();
|
||||
CompileThreadData->IRLists.clear();
|
||||
CompileThreadData->IRLists.try_emplace(GuestRIP, IR);
|
||||
}
|
||||
|
||||
CompileMutex.unlock();
|
||||
}
|
||||
|
||||
// Clear the inverse cache of what is calling us from the Context ClearCache routine
|
||||
auto SelectedThread = Thread->IsCompileService ? ParentThread : Thread;
|
||||
SelectedThread->BlockCache->ClearCache();
|
||||
SelectedThread->CPUBackend->ClearCache();
|
||||
SelectedThread->IntBackend->ClearCache();
|
||||
}
|
||||
|
||||
void CompileService::RemoveCodeEntry(uint64_t GuestRIP) {
|
||||
CompileThreadData->IRLists.erase(GuestRIP);
|
||||
CompileThreadData->DebugData.erase(GuestRIP);
|
||||
CompileThreadData->BlockCache->Erase(GuestRIP);
|
||||
}
|
||||
|
||||
CompileService::WorkItem *CompileService::CompileCode(uint64_t RIP) {
|
||||
// Tell the worker thread to compile code for us
|
||||
WorkItem *Item = new WorkItem{};
|
||||
Item->RIP = RIP;
|
||||
|
||||
{
|
||||
// Fill the threads work queue
|
||||
std::scoped_lock<std::mutex> lk(QueueMutex);
|
||||
WorkQueue.emplace(Item);
|
||||
}
|
||||
|
||||
// Notify the thread that it has more work
|
||||
StartWork.NotifyAll();
|
||||
|
||||
return Item;
|
||||
}
|
||||
|
||||
void CompileService::ExecutionThread() {
|
||||
// Ignore signals coming from the guest
|
||||
CTX->SignalDelegation.MaskThreadSignals();
|
||||
|
||||
// Set our thread name so we can see its relation
|
||||
char ThreadName[16]{};
|
||||
snprintf(ThreadName, 16, "%ld-CS", ParentThread->State.ThreadManager.TID.load());
|
||||
pthread_setname_np(pthread_self(), ThreadName);
|
||||
|
||||
while (true) {
|
||||
// Wait for work
|
||||
StartWork.Wait();
|
||||
if (ShuttingDown.load()) {
|
||||
break;
|
||||
}
|
||||
std::scoped_lock<std::mutex> lk(CompileMutex);
|
||||
|
||||
size_t WorkItems{};
|
||||
|
||||
do {
|
||||
// Grab a work item
|
||||
WorkItem *Item{};
|
||||
{
|
||||
std::scoped_lock<std::mutex> lk(QueueMutex);
|
||||
WorkItems = WorkQueue.size();
|
||||
if (WorkItems) {
|
||||
Item = WorkQueue.front();
|
||||
WorkQueue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// If we had a work item then work on it
|
||||
if (Item) {
|
||||
// Does the block cache already contain this RIP?
|
||||
void *CompiledCode = reinterpret_cast<void*>(CompileThreadData->BlockCache->FindBlock(Item->RIP));
|
||||
if (!CompiledCode) {
|
||||
// Code isn't in cache, compile now
|
||||
// Set our thread state's RIP
|
||||
CompileThreadData->State.State.rip = Item->RIP;
|
||||
CompiledCode = CTX->CompileCode(CompileThreadData.get(), Item->RIP);
|
||||
}
|
||||
|
||||
if (!CompiledCode) {
|
||||
// XXX: We currently have the expectation that compile service code will be significantly smaller than regular thread's code
|
||||
ERROR_AND_DIE("Couldn't compile code for thread at RIP: 0x%lx", Item->RIP);
|
||||
}
|
||||
|
||||
auto BlockMapPtr = CompileThreadData->BlockCache->AddBlockMapping(Item->RIP, CompiledCode);
|
||||
if (BlockMapPtr == 0) {
|
||||
// XXX: We currently have the expectation that compiler service block cache will be significantly underutilized compared to regular thread
|
||||
ERROR_AND_DIE("Couldn't add code to block cache for thread at RIP: 0x%lx", Item->RIP);
|
||||
}
|
||||
|
||||
Item->CodePtr = CompiledCode;
|
||||
Item->IRList = CompileThreadData->IRLists.find(Item->RIP)->second.get();
|
||||
|
||||
GCArray.emplace_back(Item);
|
||||
Item->ServiceWorkDone.NotifyAll();
|
||||
}
|
||||
} while (WorkItems != 0);
|
||||
|
||||
if (GCArray.size()) {
|
||||
// Clean up our GC array
|
||||
for (auto it = GCArray.begin(); it != GCArray.end();) {
|
||||
if ((*it)->SafeToClear) {
|
||||
delete *it;
|
||||
it = GCArray.erase(it);
|
||||
}
|
||||
else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <FEXCore/Core/CPUBackend.h>
|
||||
#include <FEXCore/Utils/Event.h>
|
||||
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <queue>
|
||||
|
||||
namespace FEXCore {
|
||||
namespace Context {
|
||||
struct Context;
|
||||
}
|
||||
namespace Core {
|
||||
struct InternalThreadState;
|
||||
}
|
||||
|
||||
class CompileService final {
|
||||
public:
|
||||
CompileService(FEXCore::Context::Context *ctx, FEXCore::Core::InternalThreadState *Thread);
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
struct WorkItem {
|
||||
// Incoming
|
||||
uint64_t RIP{};
|
||||
|
||||
// Outgoing
|
||||
void *CodePtr{};
|
||||
FEXCore::IR::IRListView<true> *IRList{};
|
||||
FEXCore::Core::DebugData *DebugData{};
|
||||
|
||||
// Communication
|
||||
Event ServiceWorkDone{};
|
||||
std::atomic_bool SafeToClear{};
|
||||
};
|
||||
|
||||
WorkItem *CompileCode(uint64_t RIP);
|
||||
void ClearCache(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestRIP);
|
||||
void RemoveCodeEntry(uint64_t GuestRIP);
|
||||
|
||||
private:
|
||||
FEXCore::Context::Context *CTX;
|
||||
FEXCore::Core::InternalThreadState *ParentThread;
|
||||
|
||||
void ExecutionThread();
|
||||
std::thread WorkerThread;
|
||||
std::unique_ptr<FEXCore::Core::InternalThreadState> CompileThreadData;
|
||||
|
||||
std::mutex QueueMutex{};
|
||||
std::mutex CompileMutex{};
|
||||
std::queue<WorkItem*> WorkQueue{};
|
||||
std::vector<WorkItem*> GCArray{};
|
||||
Event StartWork{};
|
||||
std::atomic_bool ShuttingDown{false};
|
||||
};
|
||||
}
|
||||
+51
-13
@@ -4,6 +4,7 @@
|
||||
#include "Interface/Context/Context.h"
|
||||
#include "Interface/Core/BlockCache.h"
|
||||
#include "Interface/Core/BlockSamplingData.h"
|
||||
#include "Interface/Core/CompileService.h"
|
||||
#include "Interface/Core/Core.h"
|
||||
#include "Interface/Core/DebugData.h"
|
||||
#include "Interface/Core/OpcodeDispatcher.h"
|
||||
@@ -221,6 +222,10 @@ namespace FEXCore::Context {
|
||||
}
|
||||
|
||||
for (auto &Thread : Threads) {
|
||||
|
||||
if (Thread->CompileService) {
|
||||
Thread->CompileService->Shutdown();
|
||||
}
|
||||
delete Thread;
|
||||
}
|
||||
Threads.clear();
|
||||
@@ -587,9 +592,12 @@ namespace FEXCore::Context {
|
||||
|
||||
// Pull out the current IR we added and store it back after we cleared the rest of the list
|
||||
// Needed in the case the the block mapping has aliased
|
||||
auto IR = Thread->IRLists.find(Address)->second.release();
|
||||
Thread->IRLists.clear();
|
||||
Thread->IRLists.try_emplace(Address, IR);
|
||||
auto iter = Thread->IRLists.find(Address);
|
||||
if (iter != Thread->IRLists.end()) {
|
||||
auto IR = iter->second.release();
|
||||
Thread->IRLists.clear();
|
||||
Thread->IRLists.try_emplace(Address, IR);
|
||||
}
|
||||
BlockMapPtr = Thread->BlockCache->AddBlockMapping(Address, Ptr);
|
||||
LogMan::Throw::A(BlockMapPtr, "Couldn't add mapping after clearing mapping cache");
|
||||
}
|
||||
@@ -601,7 +609,11 @@ namespace FEXCore::Context {
|
||||
Thread->BlockCache->ClearCache();
|
||||
Thread->CPUBackend->ClearCache();
|
||||
Thread->IntBackend->ClearCache();
|
||||
|
||||
|
||||
if (Thread->CompileService) {
|
||||
Thread->CompileService->ClearCache(Thread, GuestRIP);
|
||||
}
|
||||
|
||||
if (GuestRIP == 0) {
|
||||
Thread->IRLists.clear();
|
||||
}
|
||||
@@ -612,10 +624,9 @@ namespace FEXCore::Context {
|
||||
}
|
||||
}
|
||||
|
||||
uintptr_t Context::CompileBlock(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestRIP) {
|
||||
void *CodePtr {nullptr};
|
||||
void *Context::CompileCode(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestRIP) {
|
||||
uint8_t const *GuestCode{};
|
||||
if (Thread->CTX->Config.UnifiedMemory) {
|
||||
if (Config.UnifiedMemory) {
|
||||
GuestCode = reinterpret_cast<uint8_t const*>(GuestRIP);
|
||||
}
|
||||
else {
|
||||
@@ -638,7 +649,7 @@ namespace FEXCore::Context {
|
||||
LogMan::Msg::E("Had Frontend decoder error");
|
||||
Stop(false /* Ignore Current Thread */);
|
||||
}
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto CodeBlocks = Thread->FrontendDecoder->GetDecodedBlocks();
|
||||
@@ -671,7 +682,7 @@ namespace FEXCore::Context {
|
||||
else {
|
||||
ExistingCodePtr = MemoryMapper.GetPointer<uintptr_t>(Block.Entry + BlockInstructionsLength);
|
||||
}
|
||||
|
||||
|
||||
memcpy(&existing, (void*)(ExistingCodePtr), DecodedInfo->InstSize);
|
||||
auto CodeChanged = Thread->OpDispatcher->_ValidateCode(existing, ExistingCodePtr, DecodedInfo->InstSize);
|
||||
|
||||
@@ -684,9 +695,9 @@ namespace FEXCore::Context {
|
||||
Thread->OpDispatcher->_RemoveCodeEntry(GuestRIP);
|
||||
Thread->OpDispatcher->_StoreContext(IR::GPRClass, 8, offsetof(FEXCore::Core::CPUState, rip), Thread->OpDispatcher->_Constant(Block.Entry + BlockInstructionsLength));
|
||||
Thread->OpDispatcher->_ExitFunction();
|
||||
|
||||
|
||||
auto NextOpBlock = Thread->OpDispatcher->CreateNewCodeBlock();
|
||||
|
||||
|
||||
Thread->OpDispatcher->SetFalseJumpTarget(InvalidateCodeCond, NextOpBlock);
|
||||
Thread->OpDispatcher->SetCurrentCodeBlock(NextOpBlock);
|
||||
}
|
||||
@@ -717,7 +728,7 @@ namespace FEXCore::Context {
|
||||
if (TotalInstructions == 0) {
|
||||
// Couldn't handle any instruction in op dispatcher
|
||||
Thread->OpDispatcher->ResetWorkingList();
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
else {
|
||||
uint8_t GPRSize = Config.Is64BitMode ? 8 : 4;
|
||||
@@ -808,7 +819,28 @@ namespace FEXCore::Context {
|
||||
}
|
||||
|
||||
// Attempt to get the CPU backend to compile this code
|
||||
CodePtr = Thread->CPUBackend->CompileCode(IRList, DebugData);
|
||||
return Thread->CPUBackend->CompileCode(IRList, DebugData);
|
||||
}
|
||||
|
||||
uintptr_t Context::CompileBlock(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestRIP) {
|
||||
if (Thread->CompileBlockReentrantRefCount != 0) {
|
||||
if (!Thread->CompileService) {
|
||||
Thread->CompileService = std::make_shared<FEXCore::CompileService>(this, Thread);
|
||||
Thread->CompileService->Initialize();
|
||||
}
|
||||
auto WorkItem = Thread->CompileService->CompileCode(GuestRIP);
|
||||
WorkItem->ServiceWorkDone.Wait();
|
||||
// Return here with the data in place
|
||||
Thread->IRLists.try_emplace(GuestRIP, WorkItem->IRList->CreateCopy());
|
||||
uintptr_t Ptr = AddBlockMapping(Thread, GuestRIP, WorkItem->CodePtr);
|
||||
LogMan::Throw::A(Ptr, "Couldn't add mapping from compile service!");
|
||||
WorkItem->SafeToClear = true;
|
||||
return Ptr;
|
||||
}
|
||||
|
||||
++Thread->CompileBlockReentrantRefCount;
|
||||
|
||||
void *CodePtr = CompileCode(Thread, GuestRIP);
|
||||
|
||||
if (CodePtr != nullptr) {
|
||||
// The core managed to compile the code.
|
||||
@@ -816,9 +848,11 @@ namespace FEXCore::Context {
|
||||
Symbols.Register(CodePtr, GuestRIP, DebugData->HostCodeSize);
|
||||
#endif
|
||||
|
||||
--Thread->CompileBlockReentrantRefCount;
|
||||
return AddBlockMapping(Thread, GuestRIP, CodePtr);
|
||||
}
|
||||
|
||||
--Thread->CompileBlockReentrantRefCount;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -889,6 +923,10 @@ namespace FEXCore::Context {
|
||||
Thread->IRLists.erase(GuestRIP);
|
||||
Thread->DebugData.erase(GuestRIP);
|
||||
Thread->BlockCache->Erase(GuestRIP);
|
||||
|
||||
if (Thread->CompileService) {
|
||||
Thread->CompileService->RemoveCodeEntry(GuestRIP);
|
||||
}
|
||||
}
|
||||
|
||||
// Debug interface
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace FEXCore {
|
||||
class BlockCache;
|
||||
class CompileService;
|
||||
}
|
||||
|
||||
namespace FEXCore::Context {
|
||||
@@ -79,7 +80,9 @@ namespace FEXCore::Core {
|
||||
|
||||
int StatusCode{};
|
||||
FEXCore::Context::ExitReason ExitReason {FEXCore::Context::ExitReason::EXIT_WAITING};
|
||||
|
||||
uint32_t CompileBlockReentrantRefCount{};
|
||||
std::shared_ptr<FEXCore::CompileService> CompileService;
|
||||
bool IsCompileService{false};
|
||||
};
|
||||
static_assert(offsetof(InternalThreadState, State) == 0, "InternalThreadState must have State be the first object");
|
||||
static_assert(std::is_standard_layout<InternalThreadState>::value, "This needs to be standard layout");
|
||||
|
||||
Reference in new issue
Block a user