diff --git a/Examples/SimpleCodeLoader.cpp b/Examples/SimpleCodeLoader.cpp index 38a6a6df3..435a48fd8 100644 --- a/Examples/SimpleCodeLoader.cpp +++ b/Examples/SimpleCodeLoader.cpp @@ -54,7 +54,7 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char **argv) { FEXCore::Context::AddGuestMemoryRegion(CTX, SHM); FEXCore::Context::InitCore(CTX, &Test); - auto ShutdownReason = FEXCore::Context::RunLoop(CTX, true); + auto ShutdownReason = FEXCore::Context::RunUntilExit(CTX); LogMan::Msg::D("Reason we left VM: %d", ShutdownReason); FEXCore::Context::DestroyContext(CTX); diff --git a/Examples/SimplePrint.cpp b/Examples/SimplePrint.cpp index 36e32f104..600e0e595 100644 --- a/Examples/SimplePrint.cpp +++ b/Examples/SimplePrint.cpp @@ -104,7 +104,7 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char **argv) { FEXCore::Context::AddGuestMemoryRegion(CTX, SHM); FEXCore::Context::InitCore(CTX, &Test); - auto ShutdownReason = FEXCore::Context::RunLoop(CTX, true); + auto ShutdownReason = FEXCore::Context::RunUntilExit(CTX); LogMan::Msg::D("Reason we left VM: %d", ShutdownReason); FEXCore::Context::DestroyContext(CTX); diff --git a/Source/Interface/Context/Context.cpp b/Source/Interface/Context/Context.cpp index af6358e0f..bea280231 100644 --- a/Source/Interface/Context/Context.cpp +++ b/Source/Interface/Context/Context.cpp @@ -41,8 +41,26 @@ namespace FEXCore::Context { return CTX->InitCore(Loader); } - FEXCore::Context::ExitReason RunLoop(FEXCore::Context::Context *CTX, bool WaitForIdle) { - return CTX->RunLoop(WaitForIdle); + void SetExitHandler(FEXCore::Context::Context *CTX, + std::function handler) { + CTX->CustomExitHandler = handler; + } + + std::function GetExitHandler(FEXCore::Context::Context *CTX) { + return CTX->CustomExitHandler; + } + + void Run(FEXCore::Context::Context *CTX) { + CTX->Run(); + } + + void Step(FEXCore::Context::Context *CTX) { + CTX->Step(); + } + + + FEXCore::Context::ExitReason RunUntilExit(FEXCore::Context::Context *CTX) { + return CTX->RunUntilExit(); } FEXCore::Context::ExitReason GetExitReason(FEXCore::Context::Context *CTX) { @@ -65,6 +83,11 @@ namespace FEXCore::Context { CTX->Pause(); } + void Stop(FEXCore::Context::Context *CTX) { + CTX->ShouldStop = true; + CTX->Pause(); // Block until exit + } + void SetCustomCPUBackendFactory(FEXCore::Context::Context *CTX, CustomCPUFactoryType Factory) { CTX->CustomCPUFactory = std::move(Factory); } diff --git a/Source/Interface/Context/Context.h b/Source/Interface/Context/Context.h index 6c00eafca..0eeae0068 100644 --- a/Source/Interface/Context/Context.h +++ b/Source/Interface/Context/Context.h @@ -66,6 +66,7 @@ namespace FEXCore::Context { FEXCore::SyscallHandler SyscallHandler; CustomCPUFactoryType CustomCPUFactory; CustomCPUFactoryType FallbackCPUFactory; + std::function CustomExitHandler; #ifdef BLOCKSTATS std::unique_ptr BlockData; @@ -75,9 +76,11 @@ namespace FEXCore::Context { ~Context(); bool InitCore(FEXCore::CodeLoader *Loader); - FEXCore::Context::ExitReason RunLoop(bool WaitForIdle); + FEXCore::Context::ExitReason RunUntilExit(); bool IsPaused() const { return !Running; } void Pause(); + void Run(); + void Step(); // Debugger interface void CompileRIP(FEXCore::Core::InternalThreadState *Thread, uint64_t RIP); @@ -109,6 +112,8 @@ namespace FEXCore::Context { void InitializeThread(FEXCore::Core::InternalThreadState *Thread); void ExecutionThread(FEXCore::Core::InternalThreadState *Thread); void RunThread(FEXCore::Core::InternalThreadState *Thread); + void NotifyPause(); + void HandleExit(FEXCore::Core::InternalThreadState *Thread); uintptr_t AddBlockMapping(FEXCore::Core::InternalThreadState *Thread, uint64_t Address, void *Ptr); @@ -122,6 +127,9 @@ namespace FEXCore::Context { std::vector InitLocations; uint64_t StartingRIP; IR::RegisterAllocationPass *RAPass {}; + std::mutex ExitMutex; + + bool StartPaused = false; #if ENABLE_JITSYMBOLS FEXCore::JITSymbols Symbols; #endif diff --git a/Source/Interface/Core/Core.cpp b/Source/Interface/Core/Core.cpp index 0dccd11ed..1183e41cd 100644 --- a/Source/Interface/Core/Core.cpp +++ b/Source/Interface/Core/Core.cpp @@ -18,6 +18,7 @@ #include #include + #include #include "Interface/Core/GdbServer.h" @@ -281,10 +282,13 @@ namespace FEXCore::Context { // Offset next thread's FS_OFFSET by slot size uint64_t SlotSize = Loader->InitializeThreadSlot(TLSSlotWriter); - StartingRIP = Loader->DefaultRIP(); + Thread->State.State.rip = StartingRIP = Loader->DefaultRIP(); - auto gdb = new GdbServer(this, Loader); - gdb->StartAndBlock(); + if (false) { + auto gdb = new GdbServer(this, Loader); + gdb->StartAndBlock(); + StartPaused = true; + } InitializeThread(Thread); @@ -309,47 +313,63 @@ namespace FEXCore::Context { PauseWait.WaitFor(std::chrono::milliseconds(10)); } while (true); - } - void Context::Pause() { - // Tell all the threads that they should pause - { - std::lock_guard lk(ThreadCreationMutex); - for (auto &Thread : Threads) { - Thread->State.RunningEvents.ShouldPause.store(true); - } - - for (auto &Thread : Threads) { - Thread->StartRunning.NotifyAll(); - } - Running = true; - } - - WaitForIdle(); Running = false; } - FEXCore::Context::ExitReason Context::RunLoop(bool WaitForIdle) { - { - // Spin up all the threads - std::lock_guard 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::NotifyPause() { + // Tell all the threads that they should pause + std::lock_guard lk(ThreadCreationMutex); + for (auto &Thread : Threads) { + Thread->State.RunningEvents.ShouldPause.store(true); } - if (WaitForIdle) { + 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 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(); - return ParentThread->ExitReason; - } + auto reason = ParentThread->ExitReason; - return FEXCore::Context::ExitReason::EXIT_ASYNC_RUN; + // 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) { @@ -370,6 +390,7 @@ namespace FEXCore::Context { Thread->ThreadWaiting.Wait(); } + void Context::RunThread(FEXCore::Core::InternalThreadState *Thread) { // Tell the thread to start executing Thread->StartRunning.NotifyAll(); @@ -381,7 +402,7 @@ namespace FEXCore::Context { // Grab the new thread object { std::lock_guard lk(ThreadCreationMutex); - Thread = Threads.emplace_back(new FEXCore::Core::InternalThreadState{}); + Thread = Threads.emplace_back(new FEXCore::Core::InternalThreadState); Thread->State.ThreadManager.TID = ++ThreadID; } @@ -574,6 +595,32 @@ namespace FEXCore::Context { 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; @@ -720,7 +767,9 @@ namespace FEXCore::Context { if (Thread->ExitReason == FEXCore::Context::ExitReason::EXIT_NONE) Thread->ExitReason = FEXCore::Context::ExitReason::EXIT_DEBUG; - PauseWait.NotifyAll(); + HandleExit(Thread); + + Thread->StartRunning.Wait(); // If we set it to debug then set it back to none after this diff --git a/Source/Interface/Core/GdbServer.cpp b/Source/Interface/Core/GdbServer.cpp index 80a1ee05f..8654b85b0 100644 --- a/Source/Interface/Core/GdbServer.cpp +++ b/Source/Interface/Core/GdbServer.cpp @@ -463,8 +463,7 @@ void GdbServer::GdbServerLoop(std::unique_ptr stream) { } void GdbServer::StartThread(std::unique_ptr stream) { - //gdbServerThread = std::thread(GdbServerLoop, stream); - GdbServerLoop(std::move(stream)); + gdbServerThread = std::thread(&GdbServer::GdbServerLoop, this, std::move(stream)); } std::unique_ptr GdbServer::OpenSocket() { diff --git a/Source/Interface/Core/GdbServer.h b/Source/Interface/Core/GdbServer.h index 09915c349..b4da77a55 100644 --- a/Source/Interface/Core/GdbServer.h +++ b/Source/Interface/Core/GdbServer.h @@ -1,5 +1,6 @@ #include +#include #include "Interface/Context/Context.h" @@ -10,12 +11,15 @@ namespace FEXCore { class GdbServer { public: GdbServer(FEXCore::Context::Context *ctx, FEXCore::CodeLoader *Loader); - void StartAndBlock() { StartThread(OpenSocket()); } + void StartAndBlock() { StartThread(OpenSocket()); Wait(); } private: std::unique_ptr OpenSocket(); void StartThread(std::unique_ptr stream); void GdbServerLoop(std::unique_ptr stream); + void Wait() { + + } std::string ReadPacket(std::iostream &stream); void SendPacket(std::ostream &stream, std::string packet); @@ -30,6 +34,9 @@ private: FEXCore::Context::Context *CTX; std::thread gdbServerThread; uint64_t data_offset; + + std::condition_variable waiting; + }; } diff --git a/include/FEXCore/Core/Context.h b/include/FEXCore/Core/Context.h index 47f0c8c48..26bec5527 100644 --- a/include/FEXCore/Core/Context.h +++ b/include/FEXCore/Core/Context.h @@ -87,17 +87,50 @@ namespace FEXCore::Context { void SetApplicationFile(FEXCore::Context::Context *CTX, std::string const &File); + void SetExitHandler(FEXCore::Context::Context *CTX, std::function handler); + std::function GetExitHandler(FEXCore::Context::Context *CTX); + /** - * @brief Starts running the CPU core + * @brief Pauses execution on the CPU core * - * If WaitForIdle is enabled then this call will block until the thread exits or if single stepping is enabled, after the core steps one instruction + * Blocks until all threads have paused. + */ + void Pause(FEXCore::Context::Context *CTX); + + /** + * @brief Starts (or continues) the CPU core + * + * This function is async and returns immediately. + * Use RunUntilExit() for synchonous executions + * + */ + void Run(FEXCore::Context::Context *CTX); + + /** + * @brief Runs the CPU core until it exits + * + * If an Exit handler has been registered, this function won't return until the core + * has shutdown. * * @param CTX The context that we created - * @param WaitForIdle Should we wait for the core to be idle or not * - * @return The ExitReason for the parentthread. ASYNC_RUN if WaitForIdle was false + * @return The ExitReason for the parentthread. */ - ExitReason RunLoop(FEXCore::Context::Context *CTX, bool WaitForIdle); + ExitReason RunUntilExit(FEXCore::Context::Context *CTX); + + /** + * @brief Tells the core to shutdown + * + * Blocks until shutdown + */ + void Stop(FEXCore::Context::Context *CTX); + + /** + * @brief Executes one instruction + * + * Returns once execution is complete. + */ + void Step(FEXCore::Context::Context *CTX); /** * @brief [[threadsafe]] Returns the ExitReason of the parent thread. Typically used for async result status @@ -135,8 +168,6 @@ namespace FEXCore::Context { */ void SetCPUState(FEXCore::Context::Context *CTX, FEXCore::Core::CPUState *State); - void Pause(FEXCore::Context::Context *CTX); - /** * @brief Allows the frontend to pass in a custom CPUBackend creation factory *