Merge pull request #513 from Sonicadvance1/compile_service

Implements a threaded compile service routine
This commit is contained in:
Stefanos Kornilios Mitsis Poiitidis authored and GitHub committed 2020-11-18 20:57:16 +02:00
commit 511812dfd9
6 files changed
+293 -14

No files matched your search

+1
View File
@@ -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
+1
View File
@@ -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;
}
}
}
}
}
}
+58
View File
@@ -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
View File
@@ -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");