Files
wiicompiled/runtime/src/fiber_manager.cpp
T

782 lines
27 KiB
C++

#include "fiber_manager.h"
#include "memory.h"
#include "abi_bridge.h"
#include "hle_stubs.h"
#include "runtime_log.h"
// Defined in hle/os/os_sleep.cpp; the sleep-timer table is file-local there.
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <iostream>
#include <iomanip>
#include <sstream>
#if !defined(_WIN32)
#include "libco.h"
#endif
namespace Fiber {
#if !defined(_WIN32)
namespace {
// libco's co_create() entry points take no argument, unlike CreateFiber(size, FiberProc, param).
// CreateGuestFiber() stages the guest thread address here immediately before the first co_switch
// into a freshly created cothread; FiberProcTrampoline reads it exactly once, at the top of the
// fiber's very first activation. Safe because guest fibers are strictly cooperative on a single
// OS thread: nothing else can run (and so nothing else can overwrite this) between the staging
// write and the trampoline's read of it.
thread_local uint32_t s_pendingFiberArg = 0;
} // namespace
#endif
std::mutex GuestFiberManager::s_mutex;
std::unordered_map<uint32_t, GuestFiber> GuestFiberManager::s_fibers;
std::vector<void*> GuestFiberManager::s_fibersPendingDelete;
void* GuestFiberManager::s_schedulerFiber = nullptr;
uint32_t GuestFiberManager::s_currentGuestThread = 0;
bool GuestFiberManager::s_initialized = false;
thread_local CpuContext* GuestFiberManager::s_cpuContext = nullptr;
void GuestFiberManager::PurgePendingFibers() {
std::vector<void*> toDelete;
{
std::lock_guard<std::mutex> lock(s_mutex);
toDelete.swap(s_fibersPendingDelete);
}
#if defined(_WIN32)
const void* current = GetCurrentFiber();
for (void* f : toDelete) {
if (f && f != current) {
DeleteFiber(f);
}
}
#else
const void* current = co_active();
for (void* f : toDelete) {
if (f && f != current) {
co_delete(static_cast<cothread_t>(f));
}
}
#endif
}
// Global VI retrace counter
std::atomic<uint32_t> g_viRetracePendingCount{0};
// =============================================================================
// Guest OS memory layout constants
// =============================================================================
namespace {
constexpr uint32_t kOSCurrentContextAddr = 0x800000d4u;
constexpr uint32_t kOSRunningContextAddr = 0x800000e4u; // OSGetCurrentThread reads this
constexpr uint32_t kThreadQueueArrayAddr = 0x803477b0u;
constexpr uint32_t kSchedulerPendingFlagAddr = 0x80386920u;
constexpr uint32_t kSchedulerReschedCounterAddr = 0x8038691cu;
constexpr uint32_t kSchedulerIdleFlagAddr = 0x80386918u;
// OSThread structure offsets
constexpr uint32_t kThreadStateOffset = 0x2C8u;
constexpr uint32_t kThreadAttrOffset = 0x2CAu;
constexpr uint32_t kThreadSuspendOffset = 0x2CCu;
constexpr uint32_t kThreadPriorityOffset = 0x2D0u;
constexpr uint32_t kThreadExitValueOffset = 0x2D8u;
constexpr uint32_t kThreadNextOffset = 0x2E0u;
constexpr uint32_t kThreadPrevOffset = 0x2E4u;
constexpr uint32_t kThreadQueueOffset = 0x2DCu;
constexpr uint32_t kThreadJoinQueueOffset = 0x2E8u;
// OSContext offsets (for saving/loading fiber context)
constexpr uint32_t kCtxGprOffset = 0x00u;
constexpr uint32_t kCtxCrOffset = 0x80u;
constexpr uint32_t kCtxLrOffset = 0x84u;
constexpr uint32_t kCtxCtrOffset = 0x88u;
constexpr uint32_t kCtxXerOffset = 0x8Cu;
constexpr uint32_t kCtxSrr0Offset = 0x198u;
constexpr uint32_t kCtxSrr1Offset = 0x19Cu;
constexpr uint32_t kCtxGqrOffset = 0x1A8u;
void ClearPendingMaskForEmptyGuestQueue(uint32_t queueEntry)
{
if (queueEntry < kThreadQueueArrayAddr ||
((queueEntry - kThreadQueueArrayAddr) % 8u) != 0) {
return;
}
const uint32_t priority = (queueEntry - kThreadQueueArrayAddr) / 8u;
if (priority >= 32 || Memory::Read32(queueEntry) != 0) {
return;
}
const uint32_t pending = Memory::Read32(kSchedulerPendingFlagAddr);
Memory::Write32(kSchedulerPendingFlagAddr, pending & ~(1u << (31u - priority)));
}
void RemoveGuestThreadFromQueue(uint32_t threadPtr)
{
const uint32_t queuePtr = Memory::Read32(threadPtr + kThreadQueueOffset);
if (queuePtr == 0) {
return;
}
const uint32_t next = Memory::Read32(threadPtr + kThreadNextOffset);
const uint32_t prev = Memory::Read32(threadPtr + kThreadPrevOffset);
if (next != 0) {
Memory::Write32(next + kThreadPrevOffset, prev);
} else {
Memory::Write32(queuePtr + 4u, prev);
}
if (prev != 0) {
Memory::Write32(prev + kThreadNextOffset, next);
} else {
Memory::Write32(queuePtr, next);
}
Memory::Write32(threadPtr + kThreadQueueOffset, 0);
Memory::Write32(threadPtr + kThreadNextOffset, 0);
Memory::Write32(threadPtr + kThreadPrevOffset, 0);
ClearPendingMaskForEmptyGuestQueue(queuePtr);
}
void WakeGuestThreadsOnQueueNoSwitch(uint32_t queueAddr)
{
constexpr int kMaxWake = 256;
for (int woke = 0; woke < kMaxWake; ++woke) {
const uint32_t thread = Memory::Read32(queueAddr);
if (thread == 0) {
return;
}
const uint32_t next = Memory::Read32(thread + kThreadNextOffset);
if (next == 0) {
Memory::Write32(queueAddr + 4u, 0);
} else {
Memory::Write32(next + kThreadPrevOffset, 0);
}
Memory::Write32(queueAddr, next);
Memory::Write32(thread + kThreadNextOffset, 0);
Memory::Write32(thread + kThreadPrevOffset, 0);
const uint16_t state = Memory::Read16(thread + kThreadStateOffset);
if (state == 0 || state == 8) {
Memory::Write32(thread + kThreadQueueOffset, 0);
continue;
}
Memory::Write16(thread + kThreadStateOffset, 1);
const int32_t suspend = static_cast<int32_t>(Memory::Read32(thread + kThreadSuspendOffset));
if (suspend >= 1) {
Memory::Write32(thread + kThreadQueueOffset, 0);
continue;
}
int32_t priority = static_cast<int32_t>(Memory::Read32(thread + kThreadPriorityOffset));
priority = std::clamp(priority, 0, 31);
const uint32_t runQueue = kThreadQueueArrayAddr + static_cast<uint32_t>(priority) * 8u;
const uint32_t tail = Memory::Read32(runQueue + 4u);
if (tail == 0) {
Memory::Write32(runQueue, thread);
} else {
Memory::Write32(tail + kThreadNextOffset, thread);
}
Memory::Write32(thread + kThreadPrevOffset, tail);
Memory::Write32(thread + kThreadNextOffset, 0);
Memory::Write32(runQueue + 4u, thread);
Memory::Write32(thread + kThreadQueueOffset, runQueue);
const uint32_t pending = Memory::Read32(kSchedulerPendingFlagAddr);
Memory::Write32(kSchedulerPendingFlagAddr, pending | (1u << (31u - static_cast<uint32_t>(priority))));
Memory::Write32(kSchedulerReschedCounterAddr, 1);
GuestFiberManager::ResumeGuestThread(thread);
}
RT_LOG(RT_TAG_OS) << "WakeGuestThreadsOnQueueNoSwitch: hit safety limit at 0x"
<< std::hex << queueAddr << std::dec << std::endl;
}
} // namespace
// =============================================================================
// GuestFiberManager Implementation
// =============================================================================
void GuestFiberManager::Initialize() {
std::lock_guard<std::mutex> lock(s_mutex);
if (s_initialized) {
return;
}
#if defined(_WIN32)
// Convert the main thread to a fiber (the scheduler fiber)
s_schedulerFiber = ConvertThreadToFiber(nullptr);
if (!s_schedulerFiber) {
// May already be a fiber
s_schedulerFiber = GetCurrentFiber();
if (!s_schedulerFiber) {
RT_LOG(RT_TAG_OS) << "FATAL: Failed to initialize scheduler fiber!" << std::endl;
ShowRuntimeFatalPopup("guest scheduler initialization failed",
"Windows could not create the scheduler fiber required to run guest threads.");
std::abort();
}
}
#else
// co_active() returns a handle for whichever native stack is currently running, creating one
// on first call if needed - the libco analogue of ConvertThreadToFiber(nullptr): it converts
// this call's own stack into a switchable target without altering control flow.
s_schedulerFiber = co_active();
#endif
s_currentGuestThread = 0;
s_initialized = true;
}
void GuestFiberManager::Shutdown() {
std::lock_guard<std::mutex> lock(s_mutex);
#if defined(_WIN32)
for (auto& [addr, fiber] : s_fibers) {
if (fiber.fiber && !fiber.isSchedulerFiber) {
DeleteFiber(fiber.fiber);
fiber.fiber = nullptr;
}
}
s_fibers.clear();
// Convert scheduler fiber back to thread
if (s_schedulerFiber) {
ConvertFiberToThread();
s_schedulerFiber = nullptr;
}
#else
for (auto& [addr, fiber] : s_fibers) {
if (fiber.fiber && !fiber.isSchedulerFiber) {
co_delete(static_cast<cothread_t>(fiber.fiber));
fiber.fiber = nullptr;
}
}
s_fibers.clear();
// Unlike ConvertFiberToThread, libco has no "undo" for co_active(): the scheduler's own
// stack was never separately allocated, so there is nothing to release here.
s_schedulerFiber = nullptr;
#endif
s_initialized = false;
}
bool GuestFiberManager::IsInitialized() {
return s_initialized;
}
bool GuestFiberManager::CreateGuestFiber(uint32_t guestThreadAddr, uint32_t entryPoint,
uint32_t entryArg, uint32_t stackBase) {
if (!s_initialized) {
RT_LOG(RT_TAG_OS) << "CreateGuestFiber called before initialization!" << std::endl;
return false;
}
std::lock_guard<std::mutex> lock(s_mutex);
// Check if fiber already exists for this thread - if so, reset it
auto existingIt = s_fibers.find(guestThreadAddr);
if (existingIt != s_fibers.end()) {
// Delete the old fiber if it exists and is not the scheduler fiber
if (existingIt->second.fiber && !existingIt->second.isSchedulerFiber) {
#if defined(_WIN32)
DeleteFiber(existingIt->second.fiber);
#else
co_delete(static_cast<cothread_t>(existingIt->second.fiber));
#endif
}
s_fibers.erase(existingIt);
}
GuestFiber gf;
gf.entryPoint = entryPoint;
gf.entryArg = entryArg;
gf.state = ThreadState::WAITING; // Starts suspended
gf.terminated = false;
gf.isSchedulerFiber = false;
// Initialize CPU context with entry point info
std::memset(&gf.cpuContext, 0, sizeof(CpuContext));
gf.cpuContext.gpr[1] = stackBase; // Stack pointer
gf.cpuContext.gpr[3] = entryArg; // First argument
gf.cpuContext.lr = 0; // No return address
gf.cpuContext.pc = entryPoint;
gf.cpuContext.srr0 = entryPoint;
#if defined(_WIN32)
// Create Windows fiber with reasonable stack size
// Use host stack size (64KB should be plenty for translated code)
constexpr size_t kHostStackSize = 64 * 1024;
gf.fiber = CreateFiber(kHostStackSize, FiberProc, reinterpret_cast<void*>(static_cast<uintptr_t>(guestThreadAddr)));
if (!gf.fiber) {
DWORD err = GetLastError();
RT_LOG(RT_TAG_OS) << "CreateFiber failed for thread 0x"
<< std::hex << guestThreadAddr
<< " error=" << std::dec << err << std::endl;
return false;
}
#else
// libco's co_create() entry point takes no argument; SwitchToThread() stages guestThreadAddr
// into s_pendingFiberArg immediately before the co_switch that first activates this handle.
constexpr unsigned int kHostStackSize = 64 * 1024;
gf.fiber = co_create(kHostStackSize, &FiberProcTrampoline);
if (!gf.fiber) {
RT_LOG(RT_TAG_OS) << "co_create failed for thread 0x"
<< std::hex << guestThreadAddr << std::dec << std::endl;
return false;
}
#endif
s_fibers[guestThreadAddr] = gf;
return true;
}
void GuestFiberManager::ResumeGuestThread(uint32_t guestThreadAddr) {
std::lock_guard<std::mutex> lock(s_mutex);
auto it = s_fibers.find(guestThreadAddr);
if (it == s_fibers.end()) {
RT_LOG(RT_TAG_OS) << "ResumeGuestThread: no fiber for 0x"
<< std::hex << guestThreadAddr << std::dec << std::endl;
return;
}
if (it->second.terminated) {
RT_LOG(RT_TAG_OS) << "ResumeGuestThread: thread 0x"
<< std::hex << guestThreadAddr << " already terminated" << std::dec << std::endl;
return;
}
it->second.state = ThreadState::READY;
}
void GuestFiberManager::SuspendGuestThread(uint32_t guestThreadAddr) {
std::lock_guard<std::mutex> lock(s_mutex);
auto it = s_fibers.find(guestThreadAddr);
if (it == s_fibers.end()) {
return;
}
it->second.state = ThreadState::WAITING;
}
void GuestFiberManager::ExitGuestThread(uint32_t guestThreadAddr, ThreadState finalState) {
std::lock_guard<std::mutex> lock(s_mutex);
auto it = s_fibers.find(guestThreadAddr);
if (it == s_fibers.end()) {
return;
}
it->second.state = finalState;
it->second.terminated = true;
if (s_currentGuestThread == guestThreadAddr) {
s_currentGuestThread = 0;
}
if (it->second.fiber && !it->second.isSchedulerFiber) {
#if defined(_WIN32)
const void* current = GetCurrentFiber();
if (it->second.fiber == current) {
s_fibersPendingDelete.push_back(it->second.fiber);
} else {
DeleteFiber(it->second.fiber);
}
#else
const void* current = co_active();
if (it->second.fiber == current) {
// Deleting the coroutine we're currently executing on would free the very stack
// this call is running on; defer it (PurgePendingFibers) until some other fiber is
// active, exactly like the Windows branch above.
s_fibersPendingDelete.push_back(it->second.fiber);
} else {
co_delete(static_cast<cothread_t>(it->second.fiber));
}
#endif
it->second.fiber = nullptr;
}
}
void GuestFiberManager::SwitchToThread(uint32_t guestThreadAddr, CpuContext* cpu) {
PurgePendingFibers();
if (!s_initialized) {
RT_LOG(RT_TAG_OS) << "SwitchToThread called before initialization!" << std::endl;
return;
}
void* fiberHandle = nullptr;
uint32_t previousThread = 0;
CpuContext callerContext{};
const bool haveCallerContext = (cpu != nullptr);
CpuContext targetContext{};
bool haveTargetContext = false;
if (cpu) {
callerContext = *cpu;
}
{
std::lock_guard<std::mutex> lock(s_mutex);
auto it = s_fibers.find(guestThreadAddr);
if (it == s_fibers.end()) {
RT_LOG(RT_TAG_OS) << "SwitchToThread: no fiber for 0x"
<< std::hex << guestThreadAddr << std::dec << std::endl;
return;
}
fiberHandle = it->second.fiber;
if (!fiberHandle || it->second.terminated) {
RT_LOG(RT_TAG_OS) << "SwitchToThread: invalid fiber for 0x"
<< std::hex << guestThreadAddr << std::dec << std::endl;
return;
}
// Remember what thread we're switching from
previousThread = s_currentGuestThread;
// Save current thread's CPU context if switching from a guest thread
if (s_currentGuestThread != 0 && cpu) {
auto currentIt = s_fibers.find(s_currentGuestThread);
if (currentIt != s_fibers.end()) {
currentIt->second.cpuContext = *cpu;
}
}
// Update current thread
s_currentGuestThread = guestThreadAddr;
it->second.state = ThreadState::RUNNING;
if (cpu) {
targetContext = it->second.cpuContext;
haveTargetContext = true;
}
}
// Store CPU context pointer for the target fiber to use
s_cpuContext = cpu;
// Check if we're already on the target fiber (e.g., switching to main thread
// when we're already on the scheduler fiber)
#if defined(_WIN32)
void* currentFiber = GetCurrentFiber();
#else
void* currentFiber = co_active();
#endif
if (currentFiber == fiberHandle) {
// Already executing on the target host fiber. This is common for the
// default guest thread, which also owns the scheduler fiber. Keep the
// live CPU context instead of restoring a possibly stale saved copy
// from before the guest thread slept.
return;
}
if (cpu && haveTargetContext) {
*cpu = targetContext;
// FPSCR travels with the guest-thread context, and its NI bit is
// modeled through the per-host-thread MXCSR; every context restore
// must re-mirror it.
MkwApplyHostNiMode(cpu->fpscr);
}
// Switch to the target fiber (the target fiber will load its own context)
#if defined(_WIN32)
SwitchToFiber(fiberHandle);
#else
// Staged for FiberProcTrampoline's first (and only) read; a no-op for a fiber that has
// already started, since resuming it re-enters mid-function rather than through the
// trampoline's entry point.
s_pendingFiberArg = guestThreadAddr;
co_switch(static_cast<cothread_t>(fiberHandle));
#endif
// When we return here, the fiber that issued SwitchToThread has resumed.
// That does not automatically mean the previous guest thread became runnable
// again; a different thread may simply have yielded back to the scheduler.
{
std::lock_guard<std::mutex> lock(s_mutex);
uint32_t runningContext = 0;
uint32_t currentContext = 0;
try {
runningContext = Memory::Read32(kOSRunningContextAddr);
currentContext = Memory::Read32(kOSCurrentContextAddr);
} catch (const Memory::AccessViolation&) {
runningContext = 0;
currentContext = 0;
}
const bool resumedPreviousThread =
previousThread != 0 &&
runningContext == previousThread &&
currentContext == previousThread;
if (resumedPreviousThread) {
auto it = s_fibers.find(previousThread);
if (it != s_fibers.end()) {
if (cpu) {
*cpu = it->second.cpuContext;
MkwApplyHostNiMode(cpu->fpscr);
}
it->second.state = ThreadState::RUNNING;
}
s_currentGuestThread = previousThread;
} else {
if (cpu && haveCallerContext) {
*cpu = callerContext;
MkwApplyHostNiMode(cpu->fpscr);
}
s_currentGuestThread = 0;
}
}
}
uint32_t GuestFiberManager::GetCurrentGuestThread() {
return s_currentGuestThread;
}
GuestFiber* GuestFiberManager::GetFiber(uint32_t guestThreadAddr) {
std::lock_guard<std::mutex> lock(s_mutex);
auto it = s_fibers.find(guestThreadAddr);
return it != s_fibers.end() ? &it->second : nullptr;
}
bool GuestFiberManager::HasFiber(uint32_t guestThreadAddr) {
std::lock_guard<std::mutex> lock(s_mutex);
return s_fibers.count(guestThreadAddr) > 0;
}
bool GuestFiberManager::IsTerminated(uint32_t guestThreadAddr) {
std::lock_guard<std::mutex> lock(s_mutex);
auto it = s_fibers.find(guestThreadAddr);
return it != s_fibers.end() && it->second.terminated;
}
bool GuestFiberManager::RegisterMainThreadAsFiber(uint32_t guestThreadAddr, CpuContext* cpu) {
if (!s_initialized) {
RT_LOG(RT_TAG_OS) << "RegisterMainThreadAsFiber called before initialization!" << std::endl;
return false;
}
std::lock_guard<std::mutex> lock(s_mutex);
// Check if already registered
if (s_fibers.count(guestThreadAddr)) {
return true;
}
// Register the current host fiber (scheduler fiber) as this guest thread's fiber
GuestFiber gf;
gf.fiber = s_schedulerFiber; // The main fiber IS the scheduler fiber
gf.entryPoint = 0;
gf.entryArg = 0;
gf.state = ThreadState::RUNNING;
gf.terminated = false;
gf.isSchedulerFiber = true; // This is special - it's both scheduler AND main thread
// Copy current CPU context
if (cpu) {
gf.cpuContext = *cpu;
}
s_fibers[guestThreadAddr] = gf;
s_currentGuestThread = guestThreadAddr;
return true;
}
void GuestFiberManager::ProcessTimerEvents(CpuContext* cpu) {
constexpr uint32_t kMaxRetracesPerSlice = 16;
const uint32_t pending = g_viRetracePendingCount.exchange(0, std::memory_order_acq_rel);
if (pending == 0) {
return;
}
const uint32_t retracesToProcess = std::min(pending, kMaxRetracesPerSlice);
const uint32_t remaining = pending - retracesToProcess;
if (remaining != 0) {
g_viRetracePendingCount.fetch_add(remaining, std::memory_order_release);
}
for (uint32_t i = 0; i < retracesToProcess; ++i) {
VI_HLE_ForceRetrace(cpu);
}
}
void GuestFiberManager::SwitchToScheduler() {
#if defined(_WIN32)
SwitchToFiber(s_schedulerFiber);
#else
co_switch(static_cast<cothread_t>(s_schedulerFiber));
#endif
}
#if defined(_WIN32)
void CALLBACK GuestFiberManager::FiberProc(void* param)
#else
void GuestFiberManager::FiberProc(void* param)
#endif
{
uint32_t guestThreadAddr = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(param));
// Get our fiber info
GuestFiber* fiber = nullptr;
uint32_t entryPoint = 0;
uint32_t entryArg = 0;
{
std::lock_guard<std::mutex> lock(s_mutex);
auto it = s_fibers.find(guestThreadAddr);
if (it == s_fibers.end()) {
RT_LOG(RT_TAG_OS) << "FiberProc: fiber not found!" << std::endl;
SwitchToScheduler();
return;
}
fiber = &it->second;
entryPoint = fiber->entryPoint;
entryArg = fiber->entryArg;
}
// Get the CPU context
CpuContext* cpu = s_cpuContext;
if (!cpu) {
cpu = &GetPersistentCpuContext();
}
// Guest OSContext: r2 (TOC/SDA2) at 0x08, r13 (SDA) at 0x34. Both must load correctly or
// translated code loses access to global/static data.
try {
// Load r2 (TOC/SDA2 pointer)
cpu->gpr[2] = Memory::Read32(guestThreadAddr + 0x08u);
// Load r13 (SDA pointer) - this is CRITICAL for global data access
cpu->gpr[13] = Memory::Read32(guestThreadAddr + 0x34u);
// Load stack pointer from guest context
cpu->gpr[1] = Memory::Read32(guestThreadAddr + 0x04u);
// Load saved LR
cpu->lr = Memory::Read32(guestThreadAddr + 0x84u);
} catch (const Memory::AccessViolation& e) {
RT_LOG(RT_TAG_OS) << "Failed to load guest context from 0x" << std::hex << guestThreadAddr
<< ": " << e.what() << std::dec << std::endl;
}
// Set up context for thread entry
cpu->gpr[3] = entryArg;
cpu->pc = entryPoint;
cpu->srr0 = entryPoint;
// Create a CpuContextScope for this fiber
CpuContextScope scope(cpu);
int startDeferAttempts = 0;
while (entryPoint == 0x8024373c) { // EGG::Thread::start
uint32_t vtable = 0;
uint32_t startFn = 0;
try {
vtable = Memory::Read32(entryArg);
if (vtable >= 0x80000000u) {
startFn = Memory::Read32(vtable + 0x0Cu);
}
} catch (const Memory::AccessViolation&) {
vtable = 0;
startFn = 0;
}
if (vtable >= 0x80000000u && startFn >= 0x80000000u) {
break;
}
if (startDeferAttempts++ > 50) {
RT_LOG(RT_TAG_OS) << "EGG::Thread::start target still invalid (vtable=0x" << std::hex << vtable
<< ", fn=0x" << startFn << ") after retries; continuing anyway." << std::dec << std::endl;
break;
}
SwitchToScheduler();
}
// The deferral loop above yields to the scheduler and therefore can resume
// with registers from a different guest fiber in the shared CpuContext.
cpu->gpr[3] = entryArg;
cpu->pc = entryPoint;
cpu->srr0 = entryPoint;
// Call the translated thread entry function
const auto* info = TranslatedFunctionRegistry::FindByAddressPtr(entryPoint);
if (info) {
InvokeIndirectCpu(entryPoint, cpu);
} else {
RT_LOG(RT_TAG_OS) << "Thread entry 0x" << std::hex << entryPoint
<< " not found in registry!" << std::dec << std::endl;
}
// Thread entry functions normally return into OSExitThread on hardware.
// Our host fiber call boundary observes the return directly, so complete the
// guest OSThread lifecycle here before handing control back to the scheduler.
try {
RemoveGuestThreadFromQueue(guestThreadAddr);
const uint16_t attributes = Memory::Read16(guestThreadAddr + kThreadAttrOffset);
const bool detached = (attributes & 1u) != 0;
const uint16_t finalState = detached ? 0u : static_cast<uint16_t>(ThreadState::MORIBUND);
if (!detached) {
Memory::Write32(guestThreadAddr + kThreadExitValueOffset, 0);
}
Memory::Write16(guestThreadAddr + kThreadStateOffset, finalState);
WakeGuestThreadsOnQueueNoSwitch(guestThreadAddr + kThreadJoinQueueOffset);
if (Memory::Read32(kOSRunningContextAddr) == guestThreadAddr) {
Memory::Write32(kOSRunningContextAddr, 0);
}
if (Memory::Read32(kOSCurrentContextAddr) == guestThreadAddr) {
Memory::Write32(kOSCurrentContextAddr, 0);
}
Memory::Write32(kSchedulerReschedCounterAddr, 1);
} catch (const Memory::AccessViolation& e) {
RT_LOG(RT_TAG_OS) << "Thread return cleanup failed for 0x" << std::hex
<< guestThreadAddr << " at 0x" << e.address() << std::dec
<< " (" << e.reason() << ")" << std::endl;
}
{
std::lock_guard<std::mutex> lock(s_mutex);
auto it = s_fibers.find(guestThreadAddr);
if (it != s_fibers.end()) {
it->second.terminated = true;
it->second.state = ThreadState::MORIBUND;
}
s_currentGuestThread = 0;
}
// Return to scheduler
SwitchToScheduler();
}
#if !defined(_WIN32)
void GuestFiberManager::FiberProcTrampoline() {
const uint32_t guestThreadAddr = s_pendingFiberArg;
FiberProc(reinterpret_cast<void*>(static_cast<uintptr_t>(guestThreadAddr)));
// FiberProc always calls SwitchToScheduler() on every exit path and never falls off its own
// end; this is only a safety net in case that ever changes; falling off co_create's entry
// function is otherwise undefined behavior (libco's own crash() fallback aborts instead).
SwitchToScheduler();
}
#endif
} // namespace Fiber