mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 10:00:16 +02:00
We actually never use this anymore, we instead always pass zero for both, and then rely on the thread inheritance model or setting the values manually. Now that we expose visibility of the InternalThreadState to the frontend they just access it directly. Just a smidge of cleanup, NFC.
516 lines
17 KiB
C++
516 lines
17 KiB
C++
// SPDX-License-Identifier: MIT
|
|
|
|
#include "LinuxSyscalls/ThreadManager.h"
|
|
|
|
#include "LinuxSyscalls/Syscalls.h"
|
|
#include "LinuxSyscalls/SignalDelegator.h"
|
|
#include "LinuxSyscalls/Seccomp/SeccompEmulator.h"
|
|
|
|
#include <FEXHeaderUtils/Syscalls.h>
|
|
#include <FEXCore/Utils/Allocator.h>
|
|
#include <FEXCore/Utils/Profiler.h>
|
|
#include <FEXCore/fextl/fmt.h>
|
|
|
|
#include <sys/mman.h>
|
|
#include <sys/personality.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <git_version.h>
|
|
|
|
namespace FEX::HLE {
|
|
|
|
ThreadManager::StatAlloc::StatAlloc() {
|
|
Initialize();
|
|
SaveHeader(Is64BitMode() ? FEXCore::SHMStats::AppType::LINUX_64 : FEXCore::SHMStats::AppType::LINUX_32);
|
|
}
|
|
|
|
void ThreadManager::StatAlloc::Initialize() {
|
|
if (!ProfileStats()) {
|
|
return;
|
|
}
|
|
|
|
int fd = shm_open(fextl::fmt::format("fex-{}-stats", ::getpid()).c_str(), O_CREAT | O_TRUNC | O_RDWR, USER_PERMS);
|
|
if (fd == -1) {
|
|
return;
|
|
}
|
|
CurrentSize = sysconf(_SC_PAGESIZE);
|
|
CurrentSize = CurrentSize > 0 ? CurrentSize : FEXCore::Utils::FEX_PAGE_SIZE;
|
|
|
|
if (ftruncate(fd, CurrentSize) == -1) {
|
|
LogMan::Msg::EFmt("[StatAlloc] ftruncate failed");
|
|
goto err;
|
|
}
|
|
|
|
// Reserve a region of MAX_STATS_SIZE so we can grow the allocation buffer.
|
|
// Number of thread slots when ThreadStatsHeader == 64bytes and ThreadStats == 40bytes:
|
|
// 1 page: 99 slots
|
|
// 1 MB: 26211 slots
|
|
// 128 MB: 3355440 slots
|
|
Base = FEXCore::Allocator::mmap(nullptr, MAX_STATS_SIZE, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
|
|
if (Base == MAP_FAILED) {
|
|
LogMan::Msg::EFmt("[StatAlloc] mmap base failed");
|
|
Base = nullptr;
|
|
goto err;
|
|
}
|
|
|
|
FEXCore::Allocator::VirtualName("FEXMem_Misc", reinterpret_cast<void*>(Base), MAX_STATS_SIZE);
|
|
|
|
// Allocate a small working shared space for now, grow as necessary.
|
|
{
|
|
auto SharedBase = FEXCore::Allocator::mmap(Base, CurrentSize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0);
|
|
if (SharedBase == MAP_FAILED) {
|
|
LogMan::Msg::EFmt("[StatAlloc] mmap shm failed");
|
|
FEXCore::Allocator::munmap(Base, MAX_STATS_SIZE);
|
|
Base = nullptr;
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
err:
|
|
close(fd);
|
|
}
|
|
|
|
uint32_t ThreadManager::StatAlloc::FrontendAllocateSlots(uint32_t NewSize) {
|
|
if (CurrentSize == MAX_STATS_SIZE) {
|
|
// Allocator has reached maximum slots. We can't allocate anymore.
|
|
// New threads won't get stats.
|
|
return CurrentSize;
|
|
}
|
|
NewSize = std::min(MAX_STATS_SIZE, NewSize);
|
|
|
|
// When allocating more slots, open the fd without O_TRUNC | O_CREAT.
|
|
int fd = shm_open(fextl::fmt::format("fex-{}-stats", ::getpid()).c_str(), O_RDWR, USER_PERMS);
|
|
if (fd == -1) {
|
|
return CurrentSize;
|
|
}
|
|
|
|
if (ftruncate(fd, NewSize) == -1) {
|
|
LogMan::Msg::EFmt("[StatAlloc] ftruncate more failed");
|
|
close(fd);
|
|
return CurrentSize;
|
|
}
|
|
|
|
auto SharedBase = FEXCore::Allocator::mmap(Base, NewSize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0);
|
|
if (SharedBase == MAP_FAILED) {
|
|
LogMan::Msg::EFmt("[StatAlloc] allocate more mmap shm failed");
|
|
close(fd);
|
|
return CurrentSize;
|
|
}
|
|
|
|
close(fd);
|
|
return NewSize;
|
|
}
|
|
|
|
FEXCore::SHMStats::ThreadStats* ThreadManager::StatAlloc::AllocateSlot(uint32_t TID) {
|
|
std::scoped_lock lk(StatMutex);
|
|
return StatAllocBase::AllocateSlot(TID);
|
|
}
|
|
|
|
void ThreadManager::StatAlloc::DeallocateSlot(FEXCore::SHMStats::ThreadStats* AllocatedSlot) {
|
|
if (!AllocatedSlot) {
|
|
return;
|
|
}
|
|
|
|
std::scoped_lock lk(StatMutex);
|
|
StatAllocBase::DeallocateSlot(AllocatedSlot);
|
|
}
|
|
|
|
void ThreadManager::StatAlloc::CleanupForExit() {
|
|
shm_unlink(fextl::fmt::format("fex-{}-stats", ::getpid()).c_str());
|
|
}
|
|
|
|
void ThreadManager::StatAlloc::LockBeforeFork() {
|
|
if (!ProfileStats()) {
|
|
return;
|
|
}
|
|
StatMutex.lock();
|
|
}
|
|
|
|
void ThreadManager::StatAlloc::UnlockAfterFork(FEXCore::Core::InternalThreadState* Thread, bool Child) {
|
|
if (!ProfileStats()) {
|
|
return;
|
|
}
|
|
|
|
if (!Child) {
|
|
StatMutex.unlock();
|
|
return;
|
|
}
|
|
|
|
StatMutex.StealAndDropActiveLocks();
|
|
|
|
// shm_memory ownership is retained by the parent process, so the child must replace it with its own one.
|
|
// Otherwise this process will keep reporting in the original parent thread's stats region.
|
|
FEXCore::Allocator::munmap(Base, MAX_STATS_SIZE);
|
|
Base = nullptr;
|
|
CurrentSize = 0;
|
|
Head = nullptr;
|
|
Stats = nullptr;
|
|
StatTail = nullptr;
|
|
RemainingSlots = 0;
|
|
|
|
Thread->ThreadStats = nullptr;
|
|
|
|
Initialize();
|
|
SaveHeader(Is64BitMode() ? FEXCore::SHMStats::AppType::LINUX_64 : FEXCore::SHMStats::AppType::LINUX_32);
|
|
|
|
// Update this thread's ThreadStats object
|
|
auto ThreadObject = FEX::HLE::ThreadManager::GetStateObjectFromFEXCoreThread(Thread);
|
|
ThreadObject->Thread->ThreadStats = AllocateSlot(ThreadObject->ThreadInfo.TID);
|
|
}
|
|
|
|
uint64_t ThreadManager::SetSignalMask(uint64_t Mask) {
|
|
::syscall(SYSCALL_DEF(rt_sigprocmask), SIG_SETMASK, &Mask, &Mask, 8);
|
|
return Mask;
|
|
}
|
|
|
|
void ThreadManager::SetThreadName(const char* name) {
|
|
pthread_setname_np(pthread_self(), name);
|
|
}
|
|
|
|
constexpr size_t CALLRET_STACK_ALLOC_SIZE = FEXCore::Core::InternalThreadState::CALLRET_STACK_SIZE + 2 * FEXCore::Utils::FEX_PAGE_SIZE;
|
|
|
|
FEX::HLE::ThreadStateObject*
|
|
ThreadManager::CreateThread(const FEXCore::Core::CPUState* NewThreadState, uint64_t ParentTID, FEX::HLE::ThreadStateObject* InheritThread) {
|
|
auto ThreadStateObject = new FEX::HLE::ThreadStateObject;
|
|
|
|
ThreadStateObject->ThreadInfo.parent_tid = ParentTID;
|
|
ThreadStateObject->ThreadInfo.PID = ::getpid();
|
|
|
|
ThreadStateObject->ThreadInfo.TID = FHU::Syscalls::gettid();
|
|
|
|
ThreadStateObject->Thread = CTX->CreateThread(NewThreadState);
|
|
auto Frame = ThreadStateObject->Thread->CurrentFrame;
|
|
|
|
// Allocate the call-ret stack with guard pages on both sides
|
|
auto AllocBase =
|
|
reinterpret_cast<uint64_t>(FEXCore::Allocator::mmap(nullptr, CALLRET_STACK_ALLOC_SIZE, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
|
|
|
|
FEXCore::Allocator::VirtualName("FEXMem_CallRetStacks", reinterpret_cast<void*>(AllocBase), CALLRET_STACK_ALLOC_SIZE);
|
|
|
|
// Disable HUGEPAGE on callret stacks.
|
|
FEXCore::Allocator::VirtualTHPControl(reinterpret_cast<void*>(AllocBase), CALLRET_STACK_ALLOC_SIZE, FEXCore::Allocator::THPControl::Disable);
|
|
|
|
// Set the base used for invalidation to the start past the guard pages
|
|
ThreadStateObject->Thread->CallRetStackBase = reinterpret_cast<void*>(AllocBase + FEXCore::Utils::FEX_PAGE_SIZE);
|
|
::mprotect(ThreadStateObject->Thread->CallRetStackBase, FEXCore::Core::InternalThreadState::CALLRET_STACK_SIZE, PROT_READ | PROT_WRITE);
|
|
Frame->State.callret_sp = ThreadStateObject->GetCallRetStackInfo().DefaultLocation;
|
|
|
|
ThreadStateObject->Thread->FrontendPtr = ThreadStateObject;
|
|
if (ProfileStats()) {
|
|
ThreadStateObject->Thread->ThreadStats = Stat.AllocateSlot(ThreadStateObject->ThreadInfo.TID);
|
|
}
|
|
|
|
// GDT and LDT are tracked per thread.
|
|
Frame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_GDT] = &ThreadStateObject->gdt[0];
|
|
// Mirror LDT to the GDT by default. Not technically correctly, but fixes crashes in unittests.
|
|
Frame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_LDT] = &ThreadStateObject->gdt[0];
|
|
|
|
if (InheritThread) {
|
|
// If we are inheriting thread data then we inherit both the gdt and ldt arrays.
|
|
// They are then forked from the parent thread.
|
|
static_assert(sizeof(ThreadStateObject->gdt) == (8 * 32));
|
|
memcpy(ThreadStateObject->gdt, InheritThread->gdt, sizeof(ThreadStateObject->gdt));
|
|
if (InheritThread->ldt_entry_count) {
|
|
const auto new_ldt_size = InheritThread->ldt_entry_count * FEX::HLE::SyscallHandler::LDT_ENTRY_SIZE;
|
|
ThreadStateObject->ldt_entries = reinterpret_cast<FEXCore::Core::CPUState::gdt_segment*>(
|
|
FEXCore::Allocator::mmap(nullptr, new_ldt_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
|
|
FEXCore::Allocator::VirtualName("FEXMem_Misc", reinterpret_cast<void*>(ThreadStateObject->ldt_entries), new_ldt_size);
|
|
|
|
ThreadStateObject->ldt_entry_count = InheritThread->ldt_entry_count;
|
|
memcpy(ThreadStateObject->ldt_entries, InheritThread->ldt_entries, new_ldt_size);
|
|
}
|
|
} else {
|
|
// Without any thread data to inherit, setup the default gdt.
|
|
// Default code segment indexes match the numbers that the Linux kernel uses.
|
|
Frame->State.cs_idx = FEXCore::Core::CPUState::DEFAULT_USER_CS << 3;
|
|
auto GDT = FEXCore::Core::CPUState::GetSegmentFromIndex(Frame->State, Frame->State.cs_idx);
|
|
FEXCore::Core::CPUState::SetGDTBase(GDT, 0);
|
|
FEXCore::Core::CPUState::SetGDTLimit(GDT, 0xF'FFFFU);
|
|
Frame->State.cs_cached =
|
|
FEXCore::Core::CPUState::CalculateGDTBase(*FEXCore::Core::CPUState::GetSegmentFromIndex(Frame->State, Frame->State.cs_idx));
|
|
|
|
if (Is64BitMode()) {
|
|
GDT->L = 1; // L = Long Mode = 64-bit
|
|
GDT->D = 0; // D = Default Operand SIze = Reserved
|
|
} else {
|
|
GDT->L = 0; // L = Long Mode = 32-bit
|
|
GDT->D = 1; // D = Default Operand Size = 32-bit
|
|
}
|
|
}
|
|
|
|
if (InheritThread) {
|
|
FEX::HLE::_SyscallHandler->SeccompEmulator.InheritSeccompFilters(InheritThread, ThreadStateObject);
|
|
ThreadStateObject->persona = InheritThread->persona;
|
|
} else {
|
|
ThreadStateObject->persona = ::personality(0xffffffff);
|
|
}
|
|
|
|
++IdleWaitRefCount;
|
|
return ThreadStateObject;
|
|
}
|
|
|
|
void ThreadManager::DestroyThread(FEX::HLE::ThreadStateObject* Thread, bool NeedsTLSUninstall) {
|
|
{
|
|
std::lock_guard lk(ThreadCreationMutex);
|
|
auto It = std::find(Threads.begin(), Threads.end(), Thread);
|
|
LOGMAN_THROW_A_FMT(It != Threads.end(), "Thread wasn't in Threads");
|
|
Threads.erase(It);
|
|
if (Threads.empty()) {
|
|
Thread->Thread->CTX->FlushAndCloseCodeMap();
|
|
}
|
|
}
|
|
|
|
Stat.DeallocateSlot(Thread->Thread->ThreadStats);
|
|
|
|
HandleThreadDeletion(Thread, NeedsTLSUninstall);
|
|
}
|
|
|
|
void ThreadManager::StopThread(FEX::HLE::ThreadStateObject* Thread) {
|
|
SignalDelegation->SignalThread(Thread->Thread, SignalEvent::Stop);
|
|
}
|
|
|
|
void ThreadManager::HandleThreadDeletion(FEX::HLE::ThreadStateObject* Thread, bool NeedsTLSUninstall) {
|
|
if (Thread->ExecutionThread) {
|
|
if (Thread->ExecutionThread->joinable()) {
|
|
Thread->ExecutionThread->join(nullptr);
|
|
}
|
|
|
|
if (Thread->ExecutionThread->IsSelf()) {
|
|
Thread->ExecutionThread->detach();
|
|
}
|
|
}
|
|
|
|
if (NeedsTLSUninstall) {
|
|
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED
|
|
// Sanity check. This can only be called from the owning thread.
|
|
{
|
|
const auto pid = ::getpid();
|
|
const auto tid = FHU::Syscalls::gettid();
|
|
LOGMAN_THROW_A_FMT(Thread->ThreadInfo.PID == pid && Thread->ThreadInfo.TID == tid, "Can't delete TLS data from a different thread!");
|
|
}
|
|
#endif
|
|
FEXCore::Allocator::UninstallTLSData(Thread->Thread);
|
|
}
|
|
|
|
// Free the call-ret stack
|
|
FEXCore::Allocator::munmap(reinterpret_cast<void*>(Thread->GetCallRetStackInfo().AllocationBase), CALLRET_STACK_ALLOC_SIZE);
|
|
|
|
// If the LDT segment exists then deallocate it.
|
|
if (Thread->ldt_entry_count) {
|
|
FEXCore::Allocator::munmap(Thread->ldt_entries, Thread->ldt_entry_count * FEX::HLE::SyscallHandler::LDT_ENTRY_SIZE);
|
|
}
|
|
|
|
CTX->DestroyThread(Thread->Thread);
|
|
FEX::HLE::_SyscallHandler->SeccompEmulator.FreeSeccompFilters(Thread);
|
|
|
|
delete Thread;
|
|
--IdleWaitRefCount;
|
|
IdleWaitCV.notify_all();
|
|
}
|
|
|
|
void ThreadManager::NotifyPause() {
|
|
// Tell all the threads that they should pause
|
|
std::lock_guard lk(ThreadCreationMutex);
|
|
for (auto& Thread : Threads) {
|
|
SignalDelegation->SignalThread(Thread->Thread, SignalEvent::Pause);
|
|
}
|
|
}
|
|
|
|
void ThreadManager::Pause() {
|
|
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED
|
|
// Sanity check. This can't be called from an emulation thread.
|
|
{
|
|
const auto pid = ::getpid();
|
|
const auto tid = FHU::Syscalls::gettid();
|
|
std::lock_guard lk(ThreadCreationMutex);
|
|
for (auto& Thread : Threads) {
|
|
LOGMAN_THROW_A_FMT(!(Thread->ThreadInfo.PID == pid && Thread->ThreadInfo.TID == tid), "Can't put threads to sleep from inside "
|
|
"emulation thread!");
|
|
}
|
|
}
|
|
#endif
|
|
NotifyPause();
|
|
WaitForIdle();
|
|
}
|
|
|
|
void ThreadManager::Run() {
|
|
// Spin up all the threads
|
|
std::lock_guard lk(ThreadCreationMutex);
|
|
for (auto& Thread : Threads) {
|
|
Thread->SignalReason.store(SignalEvent::Return);
|
|
}
|
|
}
|
|
|
|
void ThreadManager::WaitForIdleWithTimeout() {
|
|
std::unique_lock<std::mutex> lk(IdleWaitMutex);
|
|
bool WaitResult = IdleWaitCV.wait_for(lk, std::chrono::milliseconds(1500), [this] { return IdleWaitRefCount.load() == 0; });
|
|
|
|
if (!WaitResult) {
|
|
// The wait failed, this will occur if we stepped in to a syscall
|
|
// That's okay, we just need to pause the threads manually
|
|
NotifyPause();
|
|
}
|
|
|
|
// We have sent every thread a pause signal
|
|
// Now wait again because they /will/ be going to sleep
|
|
WaitForIdle();
|
|
}
|
|
|
|
void ThreadManager::WaitForThreadsToRun() {
|
|
size_t NumThreads {};
|
|
{
|
|
std::lock_guard lk(ThreadCreationMutex);
|
|
NumThreads = Threads.size();
|
|
}
|
|
|
|
// Spin while waiting for the threads to start up
|
|
std::unique_lock<std::mutex> lk(IdleWaitMutex);
|
|
IdleWaitCV.wait(lk, [this, NumThreads] { return IdleWaitRefCount.load() >= NumThreads; });
|
|
|
|
Running = true;
|
|
}
|
|
|
|
void ThreadManager::Step() {
|
|
LogMan::Msg::AFmt("ThreadManager::Step currently not implemented");
|
|
{
|
|
std::lock_guard lk(ThreadCreationMutex);
|
|
// Walk the threads and tell them to clear their caches
|
|
// Useful when our block size is set to a large number and we need to step a single instruction
|
|
for (auto& Thread : Threads) {
|
|
CTX->ClearCodeCache(Thread->Thread, false);
|
|
}
|
|
}
|
|
|
|
// TODO: Set to single step mode.
|
|
Run();
|
|
WaitForThreadsToRun();
|
|
WaitForIdle();
|
|
// TODO: Set back to full running mode.
|
|
}
|
|
|
|
void ThreadManager::Stop(bool IgnoreCurrentThread) {
|
|
pid_t tid = FHU::Syscalls::gettid();
|
|
FEX::HLE::ThreadStateObject* CurrentThread {};
|
|
|
|
// Tell all the threads that they should stop
|
|
{
|
|
std::lock_guard lk(ThreadCreationMutex);
|
|
for (auto& Thread : Threads) {
|
|
if (IgnoreCurrentThread && Thread->ThreadInfo.TID == tid) {
|
|
// If we are callign stop from the current thread then we can ignore sending signals to this thread
|
|
// This means that this thread is already gone
|
|
} else if (Thread->ThreadInfo.TID == tid) {
|
|
// We need to save the current thread for last to ensure all threads receive their stop signals
|
|
CurrentThread = Thread;
|
|
continue;
|
|
}
|
|
|
|
StopThread(Thread);
|
|
}
|
|
}
|
|
|
|
// Stop the current thread now if we aren't ignoring it
|
|
if (CurrentThread) {
|
|
StopThread(CurrentThread);
|
|
}
|
|
}
|
|
|
|
void ThreadManager::SleepThread(FEXCore::Context::Context* CTX, FEXCore::Core::CpuStateFrame* Frame) {
|
|
auto ThreadObject = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
|
|
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED
|
|
// Sanity check. This can only be called from the owning thread.
|
|
{
|
|
const auto pid = ::getpid();
|
|
const auto tid = FHU::Syscalls::gettid();
|
|
LOGMAN_THROW_A_FMT(ThreadObject->ThreadInfo.PID == pid && ThreadObject->ThreadInfo.TID == tid, "Can't delete TLS data from a different "
|
|
"thread!");
|
|
}
|
|
#endif
|
|
|
|
--IdleWaitRefCount;
|
|
IdleWaitCV.notify_all();
|
|
|
|
ThreadObject->ThreadSleeping = true;
|
|
|
|
// Go to sleep
|
|
ThreadObject->ThreadPaused.Wait();
|
|
|
|
++IdleWaitRefCount;
|
|
ThreadObject->ThreadSleeping = false;
|
|
|
|
IdleWaitCV.notify_all();
|
|
}
|
|
|
|
void ThreadManager::UnpauseThread(FEX::HLE::ThreadStateObject* Thread) {
|
|
Thread->ThreadPaused.NotifyOne();
|
|
}
|
|
|
|
void ThreadManager::LockBeforeFork() {
|
|
Stat.LockBeforeFork();
|
|
}
|
|
|
|
void ThreadManager::UnlockAfterFork(FEXCore::Core::InternalThreadState* LiveThread, bool Child) {
|
|
Stat.UnlockAfterFork(LiveThread, Child);
|
|
if (!Child) {
|
|
return;
|
|
}
|
|
|
|
// This function is called after fork
|
|
// We need to cleanup some of the thread data that is dead
|
|
for (auto& DeadThread : Threads) {
|
|
// The fork parent retains ownership of ThreadStats
|
|
DeadThread->Thread->ThreadStats = nullptr;
|
|
|
|
if (DeadThread->Thread == LiveThread) {
|
|
continue;
|
|
}
|
|
|
|
// Despite what google searches may susgest, glibc actually has special code to handle forks
|
|
// with multiple active threads.
|
|
// It cleans up the stacks of dead threads and marks them as terminated.
|
|
// It also cleans up a bunch of internal mutexes.
|
|
|
|
// FIXME: TLS is probally still alive. Investigate
|
|
|
|
// Deconstructing the Interneal thread state should clean up most of the state.
|
|
// But if anything on the now deleted stack is holding a refrence to the heap, it will be leaked
|
|
CTX->DestroyThread(DeadThread->Thread);
|
|
delete DeadThread;
|
|
|
|
// FIXME: Make sure sure nothing gets leaked via the heap. Ideas:
|
|
// * Make sure nothing is allocated on the heap without ref in InternalThreadState
|
|
// * Surround any code that heap allocates with a per-thread mutex.
|
|
// Before forking, the the forking thread can lock all thread mutexes.
|
|
}
|
|
|
|
// Remove all threads but the live thread from Threads
|
|
Threads.clear();
|
|
|
|
auto ThreadObject = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(LiveThread->CurrentFrame);
|
|
Threads.push_back(ThreadObject);
|
|
|
|
// Clean up dead stacks
|
|
FEXCore::Threads::Thread::CleanupAfterFork();
|
|
|
|
// We now only have one thread.
|
|
IdleWaitRefCount = 1;
|
|
ThreadCreationMutex.StealAndDropActiveLocks();
|
|
}
|
|
|
|
void ThreadManager::WaitForIdle() {
|
|
std::unique_lock<std::mutex> lk(IdleWaitMutex);
|
|
IdleWaitCV.wait(lk, [this] { return IdleWaitRefCount.load() == 0; });
|
|
|
|
Running = false;
|
|
}
|
|
|
|
ThreadManager::~ThreadManager() {
|
|
std::lock_guard lk(ThreadCreationMutex);
|
|
|
|
for (auto& Thread : Threads) {
|
|
HandleThreadDeletion(Thread);
|
|
}
|
|
Threads.clear();
|
|
}
|
|
} // namespace FEX::HLE
|