Files
FEX-Emu--FEX/Source/Interface/Core/Core.cpp
T

892 lines
30 KiB
C++

#include "Common/MathUtils.h"
#include "Common/Paths.h"
#include "Interface/Context/Context.h"
#include "Interface/Core/BlockCache.h"
#include "Interface/Core/BlockSamplingData.h"
#include "Interface/Core/Core.h"
#include "Interface/Core/DebugData.h"
#include "Interface/Core/OpcodeDispatcher.h"
#include "Interface/Core/Interpreter/InterpreterCore.h"
#include "Interface/Core/JIT/JITCore.h"
#include "Interface/Core/LLVMJIT/LLVMCore.h"
#include "Interface/IR/Passes/RegisterAllocationPass.h"
#include <FEXCore/Config/Config.h>
#include <FEXCore/Core/CodeLoader.h>
#include <FEXCore/Core/CoreState.h>
#include <FEXCore/Core/CPUBackend.h>
#include <FEXCore/Core/X86Enums.h>
#include <fstream>
#include "Interface/Core/GdbServer.h"
constexpr uint64_t STACK_OFFSET = 0xc000'0000;
constexpr uint64_t FS_OFFSET = 0xb000'0000;
constexpr uint64_t FS_SIZE = 0x1000'0000;
namespace FEXCore::CPU {
bool CreateCPUCore(FEXCore::Context::Context *CTX) {
// This should be used for generating things that are shared between threads
CTX->CPUID.Init();
return true;
}
}
namespace FEXCore::Core {
constexpr std::array<std::string_view const, 22> FlagNames = {
"CF",
"",
"PF",
"",
"AF",
"",
"ZF",
"SF",
"TF",
"IF",
"DF",
"OF",
"IOPL",
"",
"NT",
"",
"RF",
"VM",
"AC",
"VIF",
"VIP",
"ID",
};
std::string_view const& GetFlagName(unsigned Flag) {
return FlagNames[Flag];
}
constexpr std::array<std::string_view const, 16> RegNames = {
"rax",
"rbx",
"rcx",
"rdx",
"rsi",
"rdi",
"rbp",
"rsp",
"r8",
"r9",
"r10",
"r11",
"r12",
"r13",
"r14",
"r15",
};
std::string_view const& GetGRegName(unsigned Reg) {
return RegNames[Reg];
}
namespace DefaultFallbackCore {
class DefaultFallbackCore final : public FEXCore::CPU::CPUBackend {
public:
explicit DefaultFallbackCore(FEXCore::Core::ThreadState *Thread)
: ThreadState {reinterpret_cast<FEXCore::Core::InternalThreadState*>(Thread)} {
}
~DefaultFallbackCore() override = default;
std::string GetName() override { return "Default Fallback"; }
void *MapRegion(void *HostPtr, uint64_t VirtualGuestPtr, uint64_t Size) override {
return HostPtr;
}
void Initialize() override {}
bool NeedsOpDispatch() override { return false; }
void *CompileCode(FEXCore::IR::IRListView<true> const *IR, FEXCore::Core::DebugData *DebugData) override {
LogMan::Msg::E("Fell back to default code handler at RIP: 0x%lx", ThreadState->State.State.rip);
return nullptr;
}
private:
FEXCore::Core::InternalThreadState *ThreadState;
};
FEXCore::CPU::CPUBackend *CPUCreationFactory(FEXCore::Context::Context* CTX, FEXCore::Core::ThreadState *Thread) {
return new DefaultFallbackCore(Thread);
}
}
}
namespace FEXCore::Context {
Context::Context()
: FrontendDecoder {this}
, SyscallHandler {this} {
FallbackCPUFactory = FEXCore::Core::DefaultFallbackCore::CPUCreationFactory;
PassManager.AddDefaultPasses();
PassManager.AddDefaultValidationPasses();
#ifdef BLOCKSTATS
BlockData = std::make_unique<FEXCore::BlockSamplingData>();
#endif
}
bool Context::GetFilenameHash(std::string const &Filename, std::string &Hash) {
// Calculate a hash for the input file
std::ifstream Input (Filename.c_str(), std::ios::in | std::ios::binary | std::ios::ate);
if (Input.is_open()) {
std::streampos Size;
Size = Input.tellg();
Input.seekg(0, std::ios::beg);
std::string Data;
Data.resize(Size);
Input.read(&Data.at(0), Size);
Input.close();
std::hash<std::string> string_hash;
Hash = std::to_string(string_hash(Data));
return true;
}
return false;
}
void Context::AddThreadRIPsToEntryList(FEXCore::Core::InternalThreadState *Thread) {
for (auto &IR : Thread->IRLists) {
EntryList.insert(IR.first);
}
}
void Context::SaveEntryList() {
std::string const &Filename = SyscallHandler.GetFilename();
std::string hash_string;
if (GetFilenameHash(Filename, hash_string)) {
auto DataPath = FEXCore::Paths::GetDataPath();
DataPath += "/EntryCache/Entries_" + hash_string;
std::ofstream Output (DataPath.c_str(), std::ios::out | std::ios::binary);
if (Output.is_open()) {
for (auto Entry : EntryList) {
Output.write(reinterpret_cast<char const*>(&Entry), sizeof(Entry));
}
Output.close();
}
}
}
void Context::LoadEntryList() {
std::string const &Filename = SyscallHandler.GetFilename();
std::string hash_string;
if (GetFilenameHash(Filename, hash_string)) {
auto DataPath = FEXCore::Paths::GetDataPath();
DataPath += "/EntryCache/Entries_" + hash_string;
std::ifstream Input (DataPath.c_str(), std::ios::in | std::ios::binary | std::ios::ate);
if (Input.is_open()) {
std::streampos Size;
Size = Input.tellg();
Input.seekg(0, std::ios::beg);
std::string Data;
Data.resize(Size);
Input.read(&Data.at(0), Size);
Input.close();
size_t EntryCount = Size / sizeof(uint64_t);
uint64_t *Entries = reinterpret_cast<uint64_t*>(&Data.at(0));
for (size_t i = 0; i < EntryCount; ++i) {
EntryList.insert(Entries[i]);
}
}
}
}
Context::~Context() {
ShouldStop.store(true);
Pause();
{
std::lock_guard<std::mutex> lk(ThreadCreationMutex);
for (auto &Thread : Threads) {
Thread->ExecutionThread.join();
}
for (auto &Thread : Threads) {
AddThreadRIPsToEntryList(Thread);
}
for (auto &Thread : Threads) {
delete Thread;
}
Threads.clear();
}
SaveEntryList();
}
bool Context::InitCore(FEXCore::CodeLoader *Loader) {
LocalLoader = Loader;
using namespace FEXCore::Core;
FEXCore::Core::CPUState NewThreadState{};
// Initialize default CPU state
NewThreadState.rip = ~0ULL;
for (int i = 0; i < 16; ++i) {
NewThreadState.gregs[i] = 0;
}
for (int i = 0; i < 16; ++i) {
NewThreadState.xmm[i][0] = 0xDEADBEEFULL;
NewThreadState.xmm[i][1] = 0xBAD0DAD1ULL;
}
memset(NewThreadState.flags, 0, 32);
NewThreadState.gs = 0;
NewThreadState.fs = FS_OFFSET;
NewThreadState.flags[1] = 1;
FEXCore::Core::InternalThreadState *Thread = CreateThread(&NewThreadState, 0, 0);
// We are the parent thread
ParentThread = Thread;
uintptr_t MemoryBase = MemoryMapper.GetBaseOffset<uintptr_t>(0);
auto MemoryMapperFunction = [&](uint64_t Base, uint64_t Size) -> void* {
Thread->BlockCache->HintUsedRange(Base, Base);
return MapRegion(Thread, Base, Size, true);
};
Loader->MapMemoryRegion(MemoryMapperFunction);
// Set up all of our memory mappings
MapRegion(Thread, FS_OFFSET, FS_SIZE, true);
void *StackPointer = MapRegion(Thread, STACK_OFFSET, Loader->StackSize(), true);
Thread->State.State.gregs[X86State::REG_RSP] = Loader->SetupStack(StackPointer, STACK_OFFSET);
// Now let the code loader setup memory
auto MemoryWriterFunction = [&](void const *Data, uint64_t Addr, uint64_t Size) -> void {
// Writes the machine code to be emulated in to memory
memcpy(reinterpret_cast<void*>(MemoryBase + Addr), Data, Size);
};
Loader->LoadMemory(MemoryWriterFunction);
Loader->GetInitLocations(&InitLocations);
auto TLSSlotWriter = [&](void const *Data, uint64_t Size) -> void {
memcpy(reinterpret_cast<void*>(MemoryBase + FS_OFFSET), Data, Size);
};
// Offset next thread's FS_OFFSET by slot size
uint64_t SlotSize = Loader->InitializeThreadSlot(TLSSlotWriter);
Thread->State.State.rip = StartingRIP = Loader->DefaultRIP();
InitializeThread(Thread);
return true;
}
void Context::StartGdbServer() {
if (!DebugServer) {
DebugServer = std::make_unique<GdbServer>(this);
StartPaused = true;
}
}
void Context::StopGdbServer() {
DebugServer.reset();
}
void Context::WaitForIdle() {
do {
bool AllPaused = true;
{
// Grab the mutex lock so a thread doesn't try and spin up while we are waiting
for (size_t i = 0; i < Threads.size(); ++i) {
if (Threads[i]->State.RunningEvents.Running.load() || Threads[i]->State.RunningEvents.WaitingToStart.load()) {
AllPaused = false;
break;
}
}
}
if (AllPaused)
break;
PauseWait.WaitFor(std::chrono::milliseconds(10));
} while (true);
Running = false;
}
void Context::NotifyPause() {
// Tell all the threads that they should pause
std::lock_guard<std::mutex> lk(ThreadCreationMutex);
for (auto &Thread : Threads) {
Thread->State.RunningEvents.ShouldPause.store(true);
}
for (auto &Thread : Threads) {
Thread->StartRunning.NotifyAll();
}
Running = true;
}
void Context::Pause() {
NotifyPause();
WaitForIdle();
}
void Context::Run() {
// Spin up all the threads
std::lock_guard<std::mutex> lk(ThreadCreationMutex);
for (auto &Thread : Threads) {
Thread->State.RunningEvents.ShouldPause.store(false);
Thread->State.RunningEvents.WaitingToStart.store(true);
}
for (auto &Thread : Threads) {
Thread->StartRunning.NotifyAll();
}
Running = true;
}
void Context::Step() {
FEXCore::Config::SetConfig(this, FEXCore::Config::CONFIG_SINGLESTEP, 1);
Run();
WaitForIdle();
FEXCore::Config::SetConfig(this, FEXCore::Config::CONFIG_SINGLESTEP, 0);
}
FEXCore::Context::ExitReason Context::RunUntilExit() {
if(!StartPaused)
Run();
while(true) {
this->WaitForIdle();
auto reason = ParentThread->ExitReason;
// Don't return if a custom exit handling the exit
if (!CustomExitHandler || reason == ExitReason::EXIT_SHUTDOWN) {
return reason;
}
}
}
void Context::InitializeThread(FEXCore::Core::InternalThreadState *Thread) {
Thread->CPUBackend->Initialize();
Thread->FallbackBackend->Initialize();
// Compile all of our cached entries
LogMan::Msg::D("Precompiling: %ld blocks...", EntryList.size());
for (auto Entry : EntryList) {
CompileRIP(Thread, Entry);
}
LogMan::Msg::D("Done", EntryList.size());
// This will create the execution thread but it won't actually start executing
Thread->ExecutionThread = std::thread(&Context::ExecutionThread, this, Thread);
// Wait for the thread to have started
Thread->ThreadWaiting.Wait();
}
void Context::RunThread(FEXCore::Core::InternalThreadState *Thread) {
// Tell the thread to start executing
Thread->StartRunning.NotifyAll();
}
FEXCore::Core::InternalThreadState* Context::CreateThread(FEXCore::Core::CPUState *NewThreadState, uint64_t ParentTID, uint64_t ChildTID) {
FEXCore::Core::InternalThreadState *Thread{};
// Grab the new thread object
{
std::lock_guard<std::mutex> lk(ThreadCreationMutex);
Thread = Threads.emplace_back(new FEXCore::Core::InternalThreadState);
Thread->State.ThreadManager.TID = ++ThreadID;
}
Thread->OpDispatcher = std::make_unique<FEXCore::IR::OpDispatchBuilder>();
Thread->OpDispatcher->SetMultiblock(Config.Multiblock);
Thread->BlockCache = std::make_unique<FEXCore::BlockCache>(this);
Thread->CTX = this;
// Copy over the new thread state to the new object
memcpy(&Thread->State.State, NewThreadState, sizeof(FEXCore::Core::CPUState));
// Set up the thread manager state
Thread->State.ThreadManager.parent_tid = ParentTID;
Thread->State.ThreadManager.child_tid = ChildTID;
// Create CPU backend
switch (Config.Core) {
case FEXCore::Config::CONFIG_INTERPRETER: Thread->CPUBackend.reset(FEXCore::CPU::CreateInterpreterCore(this)); break;
case FEXCore::Config::CONFIG_IRJIT: Thread->CPUBackend.reset(FEXCore::CPU::CreateJITCore(this, Thread)); break;
case FEXCore::Config::CONFIG_LLVMJIT: Thread->CPUBackend.reset(FEXCore::CPU::CreateLLVMCore(Thread)); break;
case FEXCore::Config::CONFIG_CUSTOM: Thread->CPUBackend.reset(CustomCPUFactory(this, &Thread->State)); break;
default: LogMan::Msg::A("Unknown core configuration");
}
Thread->FallbackBackend.reset(FallbackCPUFactory(this, &Thread->State));
LogMan::Throw::A(!Thread->FallbackBackend->NeedsOpDispatch(), "Fallback CPU backend must not require OpDispatch");
return Thread;
}
IR::RegisterAllocationPass *Context::GetRegisterAllocatorPass() {
if (!RAPass) {
RAPass = new IR::RegisterAllocationPass();
PassManager.InsertPass(RAPass);
}
return RAPass;
}
uintptr_t Context::AddBlockMapping(FEXCore::Core::InternalThreadState *Thread, uint64_t Address, void *Ptr) {
auto BlockMapPtr = Thread->BlockCache->AddBlockMapping(Address, Ptr);
if (BlockMapPtr == 0) {
Thread->BlockCache->ClearCache();
BlockMapPtr = Thread->BlockCache->AddBlockMapping(Address, Ptr);
LogMan::Throw::A(BlockMapPtr, "Couldn't add mapping after clearing mapping cache");
}
return BlockMapPtr;
}
uintptr_t Context::CompileBlock(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestRIP) {
void *CodePtr {nullptr};
uint8_t const *GuestCode = MemoryMapper.GetPointer<uint8_t const*>(GuestRIP);
// Do we already have this in the IR cache?
auto IR = Thread->IRLists.find(GuestRIP);
FEXCore::IR::IRListView<true> *IRList {};
FEXCore::Core::DebugData *DebugData {};
if (IR == Thread->IRLists.end()) {
bool HadDispatchError {false};
uint64_t TotalInstructions {0};
uint64_t TotalInstructionsLength {0};
if (!FrontendDecoder.DecodeInstructionsAtEntry(GuestCode, GuestRIP)) {
if (Config.BreakOnFrontendFailure) {
LogMan::Msg::E("Had Frontend decoder error");
ShouldStop = true;
}
return 0;
}
auto CodeBlocks = FrontendDecoder.GetDecodedBlocks();
Thread->OpDispatcher->BeginFunction(GuestRIP, CodeBlocks);
for (size_t j = 0; j < CodeBlocks->size(); ++j) {
FEXCore::Frontend::Decoder::DecodedBlocks const &Block = CodeBlocks->at(j);
// Set the block entry point
Thread->OpDispatcher->SetNewBlockIfChanged(Block.Entry);
uint64_t BlockInstructionsLength {};
uint64_t InstsInBlock = Block.NumInstructions;
for (size_t i = 0; i < InstsInBlock; ++i) {
FEXCore::X86Tables::X86InstInfo const* TableInfo {nullptr};
FEXCore::X86Tables::DecodedInst const* DecodedInfo {nullptr};
TableInfo = Block.DecodedInstructions[i].TableInfo;
DecodedInfo = &Block.DecodedInstructions[i];
if (TableInfo->OpcodeDispatcher) {
auto Fn = TableInfo->OpcodeDispatcher;
std::invoke(Fn, Thread->OpDispatcher, DecodedInfo);
if (Thread->OpDispatcher->HadDecodeFailure()) {
if (Config.BreakOnFrontendFailure) {
LogMan::Msg::E("Had OpDispatcher error at 0x%lx", GuestRIP);
ShouldStop = true;
}
HadDispatchError = true;
}
else {
BlockInstructionsLength += DecodedInfo->InstSize;
TotalInstructionsLength += DecodedInfo->InstSize;
++TotalInstructions;
}
}
else {
LogMan::Msg::E("Missing OpDispatcher at 0x%lx", Block.Entry + BlockInstructionsLength);
HadDispatchError = true;
}
// If we had a dispatch error then leave early
if (HadDispatchError) {
if (TotalInstructions == 0) {
// Couldn't handle any instruction in op dispatcher
Thread->OpDispatcher->ResetWorkingList();
return 0;
}
else {
// We had some instructions. Early exit
Thread->OpDispatcher->_StoreContext(8, offsetof(FEXCore::Core::CPUState, rip), Thread->OpDispatcher->_Constant(Block.Entry + BlockInstructionsLength));
Thread->OpDispatcher->_ExitFunction();
break;
}
}
if (Thread->OpDispatcher->FinishOp(DecodedInfo->PC + DecodedInfo->InstSize, i + 1 == InstsInBlock)) {
break;
}
}
}
Thread->OpDispatcher->Finalize();
// Run the passmanager over the IR from the dispatcher
PassManager.Run(Thread->OpDispatcher.get());
if (Thread->OpDispatcher->ShouldDump) {
std::stringstream out;
auto NewIR = Thread->OpDispatcher->ViewIR();
FEXCore::IR::Dump(&out, &NewIR);
printf("IR 0x%lx:\n%s\n@@@@@\n", GuestRIP, out.str().c_str());
}
// Create a copy of the IR and place it in this thread's IR cache
auto AddedIR = Thread->IRLists.try_emplace(GuestRIP, Thread->OpDispatcher->CreateIRCopy());
Thread->OpDispatcher->ResetWorkingList();
auto Debugit = Thread->DebugData.try_emplace(GuestRIP);
Debugit.first->second.GuestCodeSize = TotalInstructionsLength;
Debugit.first->second.GuestInstructionCount = TotalInstructions;
IRList = AddedIR.first->second.get();
DebugData = &Debugit.first->second;
Thread->Stats.BlocksCompiled.fetch_add(1);
}
else {
IRList = IR->second.get();
}
// Attempt to get the CPU backend to compile this code
CodePtr = Thread->CPUBackend->CompileCode(IRList, DebugData);
if (CodePtr != nullptr) {
// The core managed to compile the code.
#if ENABLE_JITSYMBOLS
Symbols.Register(CodePtr, GuestRIP, DebugData->HostCodeSize);
#endif
return AddBlockMapping(Thread, GuestRIP, CodePtr);
}
return 0;
}
using BlockFn = void (*)(FEXCore::Core::InternalThreadState *Thread);
uintptr_t Context::CompileFallbackBlock(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestRIP) {
// We have ONE more chance to try and fallback to the fallback CPU backend
// This will most likely fail since regular code use won't be using a fallback core.
// It's mainly for testing new instruction encodings
void *CodePtr = Thread->FallbackBackend->CompileCode(nullptr, nullptr);
if (CodePtr) {
uintptr_t Ptr = reinterpret_cast<uintptr_t >(AddBlockMapping(Thread, GuestRIP, CodePtr));
return Ptr;
}
return 0;
}
void Context::HandleExit(FEXCore::Core::InternalThreadState *thread) {
PauseWait.NotifyAll();
// The first thread here gets to handle the exit.
// If a thread is exiting due to error or debug, it will be the first thread here
if(ExitMutex.try_lock()) {
if (this->Threads.size() > 1) {
if (!thread->State.RunningEvents.ShouldPause) {
// A thread has exited without being asked, Tell the other threads to pause
NotifyPause();
}
// Wait for all other threads to be paused
WaitForIdle();
}
Running = false;
if (CustomExitHandler) {
CustomExitHandler(thread->State.ThreadManager.TID, thread->ExitReason);
}
ExitMutex.unlock();
}
}
void Context::ExecutionThread(FEXCore::Core::InternalThreadState *Thread) {
Thread->ExitReason = FEXCore::Context::ExitReason::EXIT_WAITING;
Thread->ThreadWaiting.NotifyAll();
Thread->StartRunning.Wait();
if (ShouldStop.load() || Thread->State.RunningEvents.ShouldStop.load()) {
ShouldStop = true;
Thread->State.RunningEvents.ShouldStop.store(true);
Thread->State.RunningEvents.Running.store(false);
Thread->ExitReason = FEXCore::Context::ExitReason::EXIT_SHUTDOWN;
return;
}
Thread->ExitReason = FEXCore::Context::ExitReason::EXIT_NONE;
Thread->State.RunningEvents.Running = true;
Thread->State.RunningEvents.ShouldPause = false;
constexpr uint32_t CoreDebugLevel = 0;
bool Initializing = false;
uint64_t InitializationStep = 0;
if (Initializing) {
Thread->State.State.rip = ~0ULL;
}
else {
Thread->State.State.rip = StartingRIP;
}
if (Thread->CPUBackend->HasCustomDispatch()) {
Thread->CPUBackend->ExecuteCustomDispatch(&Thread->State);
}
else {
while (!ShouldStop.load() && !Thread->State.RunningEvents.ShouldStop.load()) {
if (Initializing) {
if (Thread->State.State.rip == ~0ULL) {
if (InitializationStep < InitLocations.size()) {
Thread->State.State.gregs[X86State::REG_RSP] -= 8;
*MemoryMapper.GetPointer<uint64_t*>(Thread->State.State.gregs[X86State::REG_RSP]) = ~0ULL;
LogMan::Msg::D("Going down init path: 0x%lx", InitLocations[InitializationStep]);
Thread->State.State.rip = InitLocations[InitializationStep++];
}
else {
Initializing = false;
Thread->State.State.rip = StartingRIP;
}
}
}
uint64_t GuestRIP = Thread->State.State.rip;
if (CoreDebugLevel >= 1) {
char const *Name = LocalLoader->FindSymbolNameInRange(GuestRIP);
LogMan::Msg::D(">>>>RIP: 0x%lx: '%s'", GuestRIP, Name ? Name : "<Unknown>");
}
if (!Thread->CPUBackend->NeedsOpDispatch()) {
BlockFn Ptr = reinterpret_cast<BlockFn>(Thread->CPUBackend->CompileCode(nullptr, nullptr));
Ptr(Thread);
}
else {
// Do have have this block compiled?
auto it = Thread->BlockCache->FindBlock(GuestRIP);
if (it == 0) {
// If not compile it
it = CompileBlock(Thread, GuestRIP);
}
// Did we successfully compile this block?
if (it != 0) {
// Block is compiled, run it
BlockFn Ptr = reinterpret_cast<BlockFn>(it);
Ptr(Thread);
}
else {
// We have ONE more chance to try and fallback to the fallback CPU backend
// This will most likely fail since regular code use won't be using a fallback core.
// It's mainly for testing new instruction encodings
uintptr_t CodePtr = CompileFallbackBlock(Thread, GuestRIP);
if (CodePtr) {
BlockFn Ptr = reinterpret_cast<BlockFn>(CodePtr);
Ptr(Thread);
}
else {
// Let the frontend know that something has happened that is unhandled
Thread->State.RunningEvents.ShouldPause = true;
Thread->ExitReason = FEXCore::Context::ExitReason::EXIT_UNKNOWNERROR;
}
}
}
if (CoreDebugLevel >= 2) {
int i = 0;
LogMan::Msg::D("\tGPR[%d]: %016lx %016lx %016lx %016lx", i, Thread->State.State.gregs[i + 0], Thread->State.State.gregs[i + 1], Thread->State.State.gregs[i + 2], Thread->State.State.gregs[i + 3]);
i += 4;
LogMan::Msg::D("\tGPR[%d]: %016lx %016lx %016lx %016lx", i, Thread->State.State.gregs[i + 0], Thread->State.State.gregs[i + 1], Thread->State.State.gregs[i + 2], Thread->State.State.gregs[i + 3]);
i += 4;
LogMan::Msg::D("\tGPR[%d]: %016lx %016lx %016lx %016lx", i, Thread->State.State.gregs[i + 0], Thread->State.State.gregs[i + 1], Thread->State.State.gregs[i + 2], Thread->State.State.gregs[i + 3]);
i += 4;
LogMan::Msg::D("\tGPR[%d]: %016lx %016lx %016lx %016lx", i, Thread->State.State.gregs[i + 0], Thread->State.State.gregs[i + 1], Thread->State.State.gregs[i + 2], Thread->State.State.gregs[i + 3]);
uint64_t PackedFlags{};
for (i = 0; i < 32; ++i) {
PackedFlags |= static_cast<uint64_t>(Thread->State.State.flags[i]) << i;
}
LogMan::Msg::D("\tFlags: %016lx", PackedFlags);
}
if (CoreDebugLevel >= 3) {
int i = 0;
LogMan::Msg::D("\tXMM[%d][0]: %016lx %016lx %016lx %016lx", i, Thread->State.State.xmm[i + 0][0], Thread->State.State.xmm[i + 1][0], Thread->State.State.xmm[i + 2][0], Thread->State.State.xmm[i + 3][0]);
LogMan::Msg::D("\tXMM[%d][1]: %016lx %016lx %016lx %016lx", i, Thread->State.State.xmm[i + 0][1], Thread->State.State.xmm[i + 1][1], Thread->State.State.xmm[i + 2][1], Thread->State.State.xmm[i + 3][1]);
i += 4;
LogMan::Msg::D("\tXMM[%d][0]: %016lx %016lx %016lx %016lx", i, Thread->State.State.xmm[i + 0][0], Thread->State.State.xmm[i + 1][0], Thread->State.State.xmm[i + 2][0], Thread->State.State.xmm[i + 3][0]);
LogMan::Msg::D("\tXMM[%d][1]: %016lx %016lx %016lx %016lx", i, Thread->State.State.xmm[i + 0][1], Thread->State.State.xmm[i + 1][1], Thread->State.State.xmm[i + 2][1], Thread->State.State.xmm[i + 3][1]);
i += 4;
LogMan::Msg::D("\tXMM[%d][0]: %016lx %016lx %016lx %016lx", i, Thread->State.State.xmm[i + 0][0], Thread->State.State.xmm[i + 1][0], Thread->State.State.xmm[i + 2][0], Thread->State.State.xmm[i + 3][0]);
LogMan::Msg::D("\tXMM[%d][1]: %016lx %016lx %016lx %016lx", i, Thread->State.State.xmm[i + 0][1], Thread->State.State.xmm[i + 1][1], Thread->State.State.xmm[i + 2][1], Thread->State.State.xmm[i + 3][1]);
i += 4;
LogMan::Msg::D("\tXMM[%d][0]: %016lx %016lx %016lx %016lx", i, Thread->State.State.xmm[i + 0][0], Thread->State.State.xmm[i + 1][0], Thread->State.State.xmm[i + 2][0], Thread->State.State.xmm[i + 3][0]);
LogMan::Msg::D("\tXMM[%d][1]: %016lx %016lx %016lx %016lx", i, Thread->State.State.xmm[i + 0][1], Thread->State.State.xmm[i + 1][1], Thread->State.State.xmm[i + 2][1], Thread->State.State.xmm[i + 3][1]);
uint64_t PackedFlags{};
for (i = 0; i < 32; ++i) {
PackedFlags |= static_cast<uint64_t>(Thread->State.State.flags[i]) << i;
}
LogMan::Msg::D("\tFlags: %016lx", PackedFlags);
}
if (Thread->State.RunningEvents.ShouldStop.load()) {
// If it is the parent thread that died then just leave
// XXX: This doesn't make sense when the parent thread doesn't outlive its children
if (Thread->State.ThreadManager.GetTID() == 1) {
ShouldStop = true;
Thread->ExitReason = FEXCore::Context::ExitReason::EXIT_SHUTDOWN;
}
break;
}
if (RunningMode == FEXCore::Context::CoreRunningMode::MODE_SINGLESTEP || Thread->State.RunningEvents.ShouldPause) {
Thread->State.RunningEvents.Running = false;
Thread->State.RunningEvents.WaitingToStart = false;
// If something previously hasn't set the exit state then set it now
if (Thread->ExitReason == FEXCore::Context::ExitReason::EXIT_NONE)
Thread->ExitReason = FEXCore::Context::ExitReason::EXIT_DEBUG;
HandleExit(Thread);
Thread->StartRunning.Wait();
// If we set it to debug then set it back to none after this
// We want to retain the state if the frontend decides to leave
if (Thread->ExitReason == FEXCore::Context::ExitReason::EXIT_DEBUG)
Thread->ExitReason = FEXCore::Context::ExitReason::EXIT_NONE;
Thread->State.RunningEvents.Running = true;
}
}
}
Thread->State.RunningEvents.WaitingToStart = false;
Thread->State.RunningEvents.Running = false;
}
// Debug interface
void Context::CompileRIP(FEXCore::Core::InternalThreadState *Thread, uint64_t RIP) {
uint64_t RIPBackup = Thread->State.State.rip;
Thread->State.State.rip = RIP;
// Erase the RIP from all the storage backings if it exists
Thread->IRLists.erase(RIP);
Thread->DebugData.erase(RIP);
Thread->BlockCache->Erase(RIP);
// We don't care if compilation passes or not
CompileBlock(Thread, RIP);
Thread->State.State.rip = RIPBackup;
}
void *Context::MapRegion(FEXCore::Core::InternalThreadState *Thread, uint64_t Offset, uint64_t Size, bool Fixed) {
void *Ptr = MemoryMapper.MapRegion(Offset, Size, Fixed);
Thread->CPUBackend->MapRegion(Ptr, Offset, Size);
Thread->FallbackBackend->MapRegion(Ptr, Offset, Size);
return Ptr;
}
void Context::MirrorRegion(FEXCore::Core::InternalThreadState *Thread, void *HostPtr, uint64_t Offset, uint64_t Size) {
Thread->CPUBackend->MapRegion(HostPtr, Offset, Size);
Thread->FallbackBackend->MapRegion(HostPtr, Offset, Size);
}
void *Context::ShmBase() {
return MemoryMapper.GetMemoryBase();
}
void Context::CopyMemoryMapping([[maybe_unused]] FEXCore::Core::InternalThreadState*, FEXCore::Core::InternalThreadState *ChildThread) {
auto Regions = MemoryMapper.MappedRegions;
for (auto const& Region : Regions) {
ChildThread->CPUBackend->MapRegion(Region.Ptr, Region.Offset, Region.Size);
ChildThread->FallbackBackend->MapRegion(Region.Ptr, Region.Offset, Region.Size);
}
}
uint64_t Context::GetThreadCount() const {
return Threads.size();
}
FEXCore::Core::RuntimeStats *Context::GetRuntimeStatsForThread(uint64_t Thread) {
return &Threads[Thread]->Stats;
}
FEXCore::Core::CPUState Context::GetCPUState() {
return ParentThread->State.State;
}
void Context::GetMemoryRegions(std::vector<FEXCore::Memory::MemRegion> *Regions) {
Regions->clear();
Regions->resize(MemoryMapper.MappedRegions.size());
memcpy(&Regions->at(0), &MemoryMapper.MappedRegions.at(0), sizeof(FEXCore::Memory::MemRegion) * MemoryMapper.MappedRegions.size());
}
bool Context::GetDebugDataForRIP(uint64_t RIP, FEXCore::Core::DebugData *Data) {
auto it = ParentThread->DebugData.find(RIP);
if (it == ParentThread->DebugData.end()) {
return false;
}
memcpy(Data, &it->second, sizeof(FEXCore::Core::DebugData));
return true;
}
bool Context::FindHostCodeForRIP(uint64_t RIP, uint8_t **Code) {
uintptr_t HostCode = ParentThread->BlockCache->FindBlock(RIP);
if (!HostCode) {
return false;
}
*Code = reinterpret_cast<uint8_t*>(HostCode);
return true;
}
// XXX:
// bool Context::FindIRForRIP(uint64_t RIP, FEXCore::IR::IntrusiveIRList **ir) {
// auto IR = ParentThread->IRLists.find(RIP);
// if (IR == ParentThread->IRLists.end()) {
// return false;
// }
// //*ir = &IR->second;
// return true;
// }
// void Context::SetIRForRIP(uint64_t RIP, FEXCore::IR::IntrusiveIRList *const ir) {
// //ParentThread->IRLists.try_emplace(RIP, *ir);
// }
FEXCore::Core::ThreadState *Context::GetThreadState() {
return &ParentThread->State;
}
}