Files
wiicompiled/runtime/src/hle/os/os_thread.cpp
T
patchzyy ec226e8348 init
2026-08-23 17:10:50 +02:00

718 lines
27 KiB
C++

// Guest thread lifecycle HLE (fiber-backed) plus the thread-list/priority-queue
// helpers shared with the scheduler.
#include <cstdint>
#include <iostream>
#include "abi_bridge.h"
#include "memory.h"
#include "hle_stubs.h"
#include "ppc_runtime.h"
#include "fiber_manager.h"
#include "runtime_log.h"
#include "os_internal.h"
namespace OsHleInternal {
void RemoveThreadFromList(uint32_t threadPtr)
{
UnlinkGuestListNode(threadPtr, kThreadListNextOffset, kThreadListPrevOffset,
kThreadListHeadAddr, kThreadListTailAddr);
}
void UpdatePendingMaskForQueue(uint32_t queueEntry)
{
if (queueEntry < kThreadQueueArrayAddr ||
queueEntry >= (kThreadQueueArrayAddr + kThreadQueueArrayBytes) ||
((queueEntry - kThreadQueueArrayAddr) % 8u) != 0) {
return;
}
if (::Memory::Read32(queueEntry) != 0) {
return;
}
const uint32_t priority = (queueEntry - kThreadQueueArrayAddr) / 8u;
const uint32_t pending = ::Memory::Read32(kSchedulerPendingFlagAddr);
::Memory::Write32(kSchedulerPendingFlagAddr, pending & ~(1u << (31u - priority)));
}
void RemoveThreadFromQueue(uint32_t threadPtr)
{
const uint32_t queuePtr = ::Memory::Read32(threadPtr + kThreadQueueOffset);
if (queuePtr == 0) {
return;
}
UnlinkGuestListNode(threadPtr, kThreadNextOffset, kThreadPrevOffset, queuePtr, queuePtr + 4u);
::Memory::Write32(threadPtr + kThreadQueueOffset, 0);
UpdatePendingMaskForQueue(queuePtr);
}
int32_t ComputeThreadEffectivePriority(uint32_t threadPtr)
{
int32_t priority = static_cast<int32_t>(::Memory::Read32(threadPtr + kThreadBasePriorityOffset));
for (uint32_t mutexPtr = ::Memory::Read32(threadPtr + kThreadMutexQueueOffset);
mutexPtr != 0;
mutexPtr = ::Memory::Read32(mutexPtr + kMutexThreadNextOffset)) {
const uint32_t waiterThread = ::Memory::Read32(mutexPtr + kMutexWaitQueueHeadOffset);
if (waiterThread == 0) {
continue;
}
const int32_t waiterPriority =
static_cast<int32_t>(::Memory::Read32(waiterThread + kThreadPriorityOffset));
if (waiterPriority < priority) {
priority = waiterPriority;
}
}
return priority;
}
bool IsThpVideoDecoderEntry(uint32_t entryFunc)
{
switch (entryFunc) {
case 0x805529A8u:
case 0x80552A74u:
return true;
default:
return false;
}
}
void InsertThreadIntoQueueByPriority(uint32_t queuePtr, uint32_t threadPtr, int32_t priority)
{
::Memory::Write32(threadPtr + kThreadQueueOffset, queuePtr);
uint32_t insertBefore = ::Memory::Read32(queuePtr);
while (insertBefore != 0) {
const int32_t queuedPriority =
static_cast<int32_t>(::Memory::Read32(insertBefore + kThreadPriorityOffset));
if (queuedPriority > priority) {
break;
}
insertBefore = ::Memory::Read32(insertBefore + kThreadNextOffset);
}
if (insertBefore == 0) {
const uint32_t tail = ::Memory::Read32(queuePtr + 4u);
if (tail == 0) {
::Memory::Write32(queuePtr, threadPtr);
} else {
::Memory::Write32(tail + kThreadNextOffset, threadPtr);
}
::Memory::Write32(threadPtr + kThreadPrevOffset, tail);
::Memory::Write32(threadPtr + kThreadNextOffset, 0);
::Memory::Write32(queuePtr + 4u, threadPtr);
} else {
::Memory::Write32(threadPtr + kThreadNextOffset, insertBefore);
const uint32_t prev = ::Memory::Read32(insertBefore + kThreadPrevOffset);
::Memory::Write32(insertBefore + kThreadPrevOffset, threadPtr);
::Memory::Write32(threadPtr + kThreadPrevOffset, prev);
if (prev == 0) {
::Memory::Write32(queuePtr, threadPtr);
} else {
::Memory::Write32(prev + kThreadNextOffset, threadPtr);
}
}
if (queuePtr >= kThreadQueueArrayAddr &&
queuePtr < (kThreadQueueArrayAddr + kThreadQueueArrayBytes) &&
((queuePtr - kThreadQueueArrayAddr) % 8u) == 0) {
const uint32_t queueIndex = (queuePtr - kThreadQueueArrayAddr) / 8u;
const uint32_t pending = ::Memory::Read32(kSchedulerPendingFlagAddr);
::Memory::Write32(kSchedulerPendingFlagAddr, pending | (1u << (31u - queueIndex)));
}
}
uint32_t SetThreadEffectivePriority(uint32_t threadPtr, int32_t priority)
{
const uint16_t state = ::Memory::Read16(threadPtr + kThreadStateOffset);
if (state == 3u) {
return 0;
}
if (state < 3u) {
if (state == kThreadStateReady) {
RemoveThreadFromQueue(threadPtr);
::Memory::Write32(threadPtr + kThreadPriorityOffset, static_cast<uint32_t>(priority));
const uint32_t queueEntry =
kThreadQueueArrayAddr + static_cast<uint32_t>(priority) * 8u;
InsertThreadIntoQueueByPriority(queueEntry, threadPtr, priority);
::Memory::Write32(kSchedulerReschedCounterAddr, 1);
} else if (state != 0u) {
::Memory::Write32(kSchedulerReschedCounterAddr, 1);
::Memory::Write32(threadPtr + kThreadPriorityOffset, static_cast<uint32_t>(priority));
}
return 0;
}
if (state < 5u) {
const uint32_t queuePtr = ::Memory::Read32(threadPtr + kThreadQueueOffset);
RemoveThreadFromQueue(threadPtr);
::Memory::Write32(threadPtr + kThreadPriorityOffset, static_cast<uint32_t>(priority));
if (queuePtr != 0) {
InsertThreadIntoQueueByPriority(queuePtr, threadPtr, priority);
}
const uint32_t mutexPtr = ::Memory::Read32(threadPtr + kThreadMutexOffset);
if (mutexPtr != 0) {
return ::Memory::Read32(mutexPtr + kMutexOwnerOffset);
}
}
return 0;
}
} // namespace OsHleInternal
namespace {
void PropagateMutexOwnerPriority(uint32_t mutexPtr)
{
if (mutexPtr == 0) {
return;
}
uint32_t ownerThread = ::Memory::Read32(mutexPtr + kMutexOwnerOffset);
while (ownerThread != 0 &&
static_cast<int32_t>(::Memory::Read32(ownerThread + kThreadSuspendOffset)) < 1) {
const int32_t ownerPriority = ComputeThreadEffectivePriority(ownerThread);
if (::Memory::Read32(ownerThread + kThreadPriorityOffset) ==
static_cast<uint32_t>(ownerPriority)) {
break;
}
ownerThread = SetThreadEffectivePriority(ownerThread, ownerPriority);
}
}
void MarkFiberThreadTerminated(uint32_t threadPtr, uint16_t finalState)
{
if (!Fiber::GuestFiberManager::IsInitialized()) {
return;
}
Fiber::ThreadState fiberState = Fiber::ThreadState::MORIBUND;
if (finalState == 0) {
fiberState = Fiber::ThreadState::WAITING;
}
Fiber::GuestFiberManager::ExitGuestThread(threadPtr, fiberState);
}
void WakeThreadJoiners(CpuContext* cpu, uint32_t threadPtr)
{
if (!cpu) {
return;
}
cpu->gpr[3] = threadPtr + kThreadJoinQueueOffset;
OSWakeupThread_HLE_801aaaa4(cpu);
}
void UnlockAllThreadMutexes(CpuContext* cpu, uint32_t threadPtr)
{
if (!cpu) {
return;
}
CpuContextScope scope(cpu);
cpu->gpr[3] = threadPtr;
InvokeIndirectCpu(0x801A8088u, cpu); // __OSUnlockAllMutex
}
// Shared tail of OSExitThread/OSCancelThread: clears context, delists if detached, marks
// final state, and wakes joiners/mutex waiters. Only OSExitThread publishes an exit value.
void TerminateThreadCommon(CpuContext* cpu, uint32_t threadPtr, bool publishExitValue,
uint32_t exitValue)
{
OS__ClearContext_801a2098(threadPtr);
const uint16_t attributes = ::Memory::Read16(threadPtr + kThreadAttrOffset);
const bool detached = (attributes & 1u) != 0;
const uint16_t finalState = detached ? 0u : kThreadStateMoribund;
if (detached) {
RemoveThreadFromList(threadPtr);
} else if (publishExitValue) {
::Memory::Write32(threadPtr + kThreadExitValueOffset, exitValue);
}
::Memory::Write16(threadPtr + kThreadStateOffset, finalState);
MarkFiberThreadTerminated(threadPtr, finalState);
UnlockAllThreadMutexes(cpu, threadPtr);
WakeThreadJoiners(cpu, threadPtr);
}
} // namespace
// Fiber-based threading HLE: each guest OSThread gets a host fiber for cooperative
// context switching without blocking the main thread.
// OSCreateThread (0x801a9e84)
// Creates a new guest thread and associates a host fiber with it.
extern "C" void OSCreateThread_HLE_801a9e84(CpuContext* ctx)
{
CpuContext* cpu = ctx ? ctx : &GetPersistentCpuContext();
// r3..r9 = thread struct, entry func, entry arg, stack top, stack size, priority, attributes.
const uint32_t threadPtr = cpu->gpr[3];
const uint32_t entryFunc = cpu->gpr[4];
const uint32_t entryArg = cpu->gpr[5];
const uint32_t stackTop = cpu->gpr[6];
const uint32_t stackSize = cpu->gpr[7];
const int32_t priority = static_cast<int32_t>(cpu->gpr[8]);
const uint16_t attributes = static_cast<uint16_t>(cpu->gpr[9]);
// Validate priority range
if (priority < 0 || priority > 31) {
RT_LOG(RT_TAG_OS) << "OSCreateThread: invalid priority " << priority << std::endl;
cpu->gpr[3] = 0; // Return failure
return;
}
// Create the guest fiber
if (Fiber::GuestFiberManager::IsInitialized()) {
Fiber::GuestFiberManager::CreateGuestFiber(threadPtr, entryFunc, entryArg, stackTop);
if (Fiber::GuestFiberManager::GetFiber(threadPtr) != nullptr) {
const uint32_t hid2 = cpu->hid2 != 0 ? cpu->hid2 : 0x10000000u;
Fiber::GuestFiberManager::GetFiber(threadPtr)->cpuContext.hid2 = hid2;
}
}
// Initialize guest thread structure (matching original SDK behavior)
try {
const uint32_t alignedStack = stackTop & 0xFFFFFFF8u;
// Thread state and attributes
::Memory::Write16(threadPtr + 0x2C8u, 1); // state = READY
::Memory::Write16(threadPtr + 0x2CAu, attributes & 1); // attributes (detached)
::Memory::Write32(threadPtr + 0x2D4u, priority); // base priority
::Memory::Write32(threadPtr + 0x2D0u, priority); // effective priority
::Memory::Write32(threadPtr + 0x2CCu, 1); // suspend count = 1 (created suspended)
::Memory::Write32(threadPtr + 0x2D8u, 0xFFFFFFFFu); // exit value
// Queue pointers
::Memory::Write32(threadPtr + 0x2F0u, 0);
::Memory::Write32(threadPtr + 0x2ECu, 0);
::Memory::Write32(threadPtr + 0x2E8u, 0);
::Memory::Write32(threadPtr + 0x2F8u, 0);
::Memory::Write32(threadPtr + 0x2F4u, 0);
// Stack setup - write frame markers
::Memory::Write32(alignedStack - 8, 0);
::Memory::Write32(alignedStack - 4, 0);
// Let the translated SDK path initialize the guest OSContext exactly
// like OSInitContext, then apply the OSCreateThread-specific overrides
// that follow in the original PPC.
{
CpuContextScope scope(cpu);
cpu->gpr[3] = threadPtr;
cpu->gpr[4] = entryFunc;
cpu->gpr[5] = alignedStack - 8;
InvokeIndirectCpu(0x801A20BCu, cpu); // OSInitContext
}
if (IsThpVideoDecoderEntry(entryFunc)) {
// Decoder threads restore their own context via OSLoadContext, so the saved
// context needs these GQR2-GQR5 values or paired-single THP decode breaks
// under the all-zero OSInitContext defaults.
::Memory::Write32(threadPtr + 0x1ACu, 0x00040004u);
::Memory::Write32(threadPtr + 0x1B0u, 0x00050005u);
::Memory::Write32(threadPtr + 0x1B4u, 0x00060006u);
::Memory::Write32(threadPtr + 0x1B8u, 0x00070007u);
}
::Memory::Write32(threadPtr + 0x84u, 0x801AA0F0u); // LR = OSExitThread
::Memory::Write32(threadPtr + 0x0Cu, entryArg); // r3 = argument
// Stack info
::Memory::Write32(threadPtr + 0x304u, stackTop);
::Memory::Write32(threadPtr + 0x308u, stackTop - stackSize);
::Memory::Write32(stackTop - stackSize, 0xDEADBABEu); // Stack guard
// Thread list linkage
::Memory::Write32(threadPtr + 0x30Cu, 0);
::Memory::Write32(threadPtr + 0x310u, 0);
::Memory::Write32(threadPtr + 0x314u, 0);
// Match the original OSCreateThread slow-path initialization that runs
// once scheduler globals are live. THP worker threads depend on these
// queue/list blocks being fully zeroed.
constexpr uint32_t kSchedulerInitFlagAddr = 0x80347130u;
constexpr uint32_t kThreadAttrSourceAddr = 0x80385AA8u;
if (Memory::Contains(kSchedulerInitFlagAddr, 4) &&
::Memory::Read32(kSchedulerInitFlagAddr) != 0) {
uint32_t srr1 = ::Memory::Read32(threadPtr + 0x19Cu);
srr1 |= 0x900u;
::Memory::Write32(threadPtr + 0x19Cu, srr1);
uint16_t modeFlags = ::Memory::Read16(threadPtr + 0x1A2u);
modeFlags = static_cast<uint16_t>(modeFlags | 0x1u);
::Memory::Write16(threadPtr + 0x1A2u, modeFlags);
if (Memory::Contains(kThreadAttrSourceAddr, 4)) {
const uint32_t attr = (::Memory::Read32(kThreadAttrSourceAddr) & 0xF8u) | 0x4u;
::Memory::Write32(threadPtr + 0x194u, attr);
}
for (uint32_t offset = 0; offset < 0x80u; offset += 4u) {
::Memory::Write32(threadPtr + 0x90u + offset, 0);
::Memory::Write32(threadPtr + 0x1C8u + offset, 0);
}
}
// Add to global thread list (matching original SDK logic exactly)
const int32_t irqState = OS__DisableInterrupts_801a65ac();
// Read thread list tail (last added thread)
const uint32_t tailThread = ::Memory::Read32(kThreadListTailAddr);
// The new thread becomes the head if the queue is empty.
uint32_t newHead = threadPtr;
if (tailThread != 0) {
// There's an existing tail - link it to the new thread
::Memory::Write32(tailThread + 0x2FCu, threadPtr); // tail->next = new
// Keep existing head
newHead = ::Memory::Read32(kThreadListHeadAddr);
}
// Update thread list head
::Memory::Write32(kThreadListHeadAddr, newHead);
// Link new thread into list
::Memory::Write32(threadPtr + 0x300u, tailThread); // new->prev = old tail
::Memory::Write32(threadPtr + 0x2FCu, 0); // new->next = 0
// New thread becomes the tail
::Memory::Write32(kThreadListTailAddr, threadPtr);
OS__RestoreInterrupts_801a65d4(irqState);
cpu->gpr[3] = 1; // Return success
} catch (const ::Memory::AccessViolation& e) {
LogMemoryError(RT_TAG_OS, "OSCreateThread", e);
cpu->gpr[3] = 0; // Return failure
}
}
PPC_NATIVE_OVERRIDE_VOID(801A9E84, OSCreateThread_HLE_801a9e84, (CpuContext* ctx), (ctx));
extern "C" void OSExitThread_HLE_801aa0f0(CpuContext* ctx)
{
CpuContext* cpu = ctx ? ctx : &GetPersistentCpuContext();
const uint32_t exitValue = cpu->gpr[3];
const int32_t irqState = OS__DisableInterrupts_801a65ac();
try {
const uint32_t threadPtr = ::Memory::Read32(kOSRunningContextAddr);
if (threadPtr == 0) {
OS__RestoreInterrupts_801a65d4(irqState);
return;
}
TerminateThreadCommon(cpu, threadPtr, true, exitValue);
::Memory::Write32(kSchedulerReschedCounterAddr, 1);
cpu->gpr[3] = 0;
SelectThread_801a9c08(cpu);
} catch (const ::Memory::AccessViolation& e) {
LogMemoryError(RT_TAG_OS, "OSExitThread", e);
}
OS__RestoreInterrupts_801a65d4(irqState);
}
PPC_NATIVE_OVERRIDE_VOID(801AA0F0, OSExitThread_HLE_801aa0f0, (CpuContext* ctx), (ctx));
extern "C" void OSCancelThread_HLE_801aa1d4(CpuContext* ctx)
{
CpuContext* cpu = ctx ? ctx : &GetPersistentCpuContext();
const uint32_t threadPtr = cpu->gpr[3];
if (threadPtr == 0) {
return;
}
const int32_t irqState = OS__DisableInterrupts_801a65ac();
try {
const uint16_t state = ::Memory::Read16(threadPtr + kThreadStateOffset);
if (state == 3 || state == 0 || state > 4) {
OS__RestoreInterrupts_801a65d4(irqState);
return;
}
if (state == kThreadStateReady) {
const int32_t suspend = static_cast<int32_t>(::Memory::Read32(threadPtr + kThreadSuspendOffset));
if (suspend < 1) {
RemoveThreadFromQueue(threadPtr);
}
} else if (state == kThreadStateRunning) {
::Memory::Write32(kSchedulerReschedCounterAddr, 1);
} else if (state == kThreadStateWaiting) {
RemoveThreadFromQueue(threadPtr);
}
TerminateThreadCommon(cpu, threadPtr, false, 0);
if (::Memory::Read32(kSchedulerReschedCounterAddr) != 0) {
cpu->gpr[3] = 0;
SelectThread_801a9c08(cpu);
}
} catch (const ::Memory::AccessViolation& e) {
LogMemoryError(RT_TAG_OS, "OSCancelThread", e);
}
OS__RestoreInterrupts_801a65d4(irqState);
}
PPC_NATIVE_OVERRIDE_VOID(801AA1D4, OSCancelThread_HLE_801aa1d4, (CpuContext* ctx), (ctx));
extern "C" void OSJoinThread_HLE_801aa3ac(CpuContext* ctx)
{
CpuContext* cpu = ctx ? ctx : &GetPersistentCpuContext();
const uint32_t threadPtr = cpu->gpr[3];
const uint32_t outExitValue = cpu->gpr[4];
if (threadPtr == 0) {
cpu->gpr[3] = 0;
return;
}
const int32_t irqState = OS__DisableInterrupts_801a65ac();
uint32_t result = 0;
try {
const uint16_t attributes = ::Memory::Read16(threadPtr + kThreadAttrOffset);
uint16_t state = ::Memory::Read16(threadPtr + kThreadStateOffset);
const uint32_t joinHead = ::Memory::Read32(threadPtr + kThreadJoinQueueOffset);
if ((attributes & 1u) == 0 && state != kThreadStateMoribund && joinHead == 0) {
cpu->gpr[3] = threadPtr + kThreadJoinQueueOffset;
OSSleepThread_HLE_801aa9b8(cpu);
state = ::Memory::Read16(threadPtr + kThreadStateOffset);
bool foundInList = false;
if (state != 0) {
for (uint32_t it = ::Memory::Read32(kThreadListHeadAddr); it != 0;
it = ::Memory::Read32(it + kThreadListNextOffset)) {
if (it == threadPtr) {
foundInList = true;
break;
}
}
}
if (!foundInList && state != kThreadStateMoribund) {
OS__RestoreInterrupts_801a65d4(irqState);
cpu->gpr[3] = 0;
return;
}
}
if (state == kThreadStateMoribund) {
if (outExitValue != 0) {
::Memory::Write32(outExitValue, ::Memory::Read32(threadPtr + kThreadExitValueOffset));
}
RemoveThreadFromList(threadPtr);
::Memory::Write16(threadPtr + kThreadStateOffset, 0);
result = 1;
}
} catch (const ::Memory::AccessViolation& e) {
LogMemoryError(RT_TAG_OS, "OSJoinThread", e);
result = 0;
}
OS__RestoreInterrupts_801a65d4(irqState);
cpu->gpr[3] = result;
}
PPC_NATIVE_OVERRIDE_VOID(801AA3AC, OSJoinThread_HLE_801aa3ac, (CpuContext* ctx), (ctx));
extern "C" void OSDetachThread_HLE_801aa4ec(CpuContext* ctx)
{
CpuContext* cpu = ctx ? ctx : &GetPersistentCpuContext();
const uint32_t threadPtr = cpu->gpr[3];
if (threadPtr == 0) {
return;
}
const int32_t irqState = OS__DisableInterrupts_801a65ac();
try {
const uint16_t attributes = ::Memory::Read16(threadPtr + kThreadAttrOffset);
::Memory::Write16(threadPtr + kThreadAttrOffset, attributes | 1u);
const uint16_t state = ::Memory::Read16(threadPtr + kThreadStateOffset);
if (state == kThreadStateMoribund) {
RemoveThreadFromList(threadPtr);
::Memory::Write16(threadPtr + kThreadStateOffset, 0);
MarkFiberThreadTerminated(threadPtr, 0);
}
WakeThreadJoiners(cpu, threadPtr);
} catch (const ::Memory::AccessViolation& e) {
LogMemoryError(RT_TAG_OS, "OSDetachThread", e);
}
OS__RestoreInterrupts_801a65d4(irqState);
}
PPC_NATIVE_OVERRIDE_VOID(801AA4EC, OSDetachThread_HLE_801aa4ec, (CpuContext* ctx), (ctx));
extern "C" void OSSuspendThread_HLE_801aa6a8(CpuContext* ctx)
{
CpuContext* cpu = ctx ? ctx : &GetPersistentCpuContext();
const uint32_t threadPtr = cpu->gpr[3];
if (threadPtr == 0) {
cpu->gpr[3] = 0;
return;
}
const int32_t irqState = OS__DisableInterrupts_801a65ac();
try {
const int32_t suspendCount =
static_cast<int32_t>(::Memory::Read32(threadPtr + kThreadSuspendOffset));
::Memory::Write32(threadPtr + kThreadSuspendOffset,
static_cast<uint32_t>(suspendCount + 1));
if (suspendCount == 0) {
const uint16_t state = ::Memory::Read16(threadPtr + kThreadStateOffset);
if (state < 3u) {
if (state == kThreadStateReady) {
RemoveThreadFromQueue(threadPtr);
} else if (state != 0u) {
::Memory::Write32(kSchedulerReschedCounterAddr, 1);
::Memory::Write16(threadPtr + kThreadStateOffset, kThreadStateReady);
}
} else if (state < 5u) {
const uint32_t queuePtr = ::Memory::Read32(threadPtr + kThreadQueueOffset);
RemoveThreadFromQueue(threadPtr);
::Memory::Write32(threadPtr + kThreadPriorityOffset, kSuspendedWaitPriority);
if (queuePtr != 0) {
InsertThreadIntoQueueByPriority(queuePtr, threadPtr, kSuspendedWaitPriority);
}
PropagateMutexOwnerPriority(::Memory::Read32(threadPtr + kThreadMutexOffset));
}
if (Fiber::GuestFiberManager::IsInitialized()) {
Fiber::GuestFiberManager::SuspendGuestThread(threadPtr);
}
if (::Memory::Read32(kSchedulerReschedCounterAddr) != 0) {
cpu->gpr[3] = 0;
SelectThread_801a9c08(cpu);
}
}
cpu->gpr[3] = static_cast<uint32_t>(suspendCount);
} catch (const ::Memory::AccessViolation& e) {
LogMemoryError(RT_TAG_OS, "OSSuspendThread", e);
cpu->gpr[3] = 0;
}
OS__RestoreInterrupts_801a65d4(irqState);
}
PPC_NATIVE_OVERRIDE_VOID(801AA6A8, OSSuspendThread_HLE_801aa6a8, (CpuContext* ctx), (ctx));
// OSResumeThread (0x801aa58c)
// Resumes a suspended thread, making it eligible for scheduling.
extern "C" void OSResumeThread_HLE_801aa58c(CpuContext* ctx)
{
CpuContext* cpu = ctx ? ctx : &GetPersistentCpuContext();
const uint32_t threadPtr = cpu->gpr[3];
if (threadPtr == 0) {
cpu->gpr[3] = 0;
return;
}
const int32_t irqState = OS__DisableInterrupts_801a65ac();
try {
// Read current suspend count
const int32_t suspendCount = static_cast<int32_t>(::Memory::Read32(threadPtr + 0x2CCu));
const int32_t newSuspend = suspendCount - 1;
if (newSuspend < 0) {
::Memory::Write32(threadPtr + 0x2CCu, 0);
} else {
::Memory::Write32(threadPtr + 0x2CCu, static_cast<uint32_t>(newSuspend));
if (newSuspend == 0) {
CancelSleepTimer(threadPtr);
ClearOutstandingPark(threadPtr);
const uint16_t state = ::Memory::Read16(threadPtr + 0x2C8u);
if (state == kThreadStateWaiting) {
const uint32_t queuePtr = ::Memory::Read32(threadPtr + kThreadQueueOffset);
RemoveThreadFromQueue(threadPtr);
const int32_t priority = ComputeThreadEffectivePriority(threadPtr);
::Memory::Write32(threadPtr + kThreadPriorityOffset, static_cast<uint32_t>(priority));
if (queuePtr != 0) {
InsertThreadIntoQueueByPriority(queuePtr, threadPtr, priority);
}
if (Fiber::GuestFiberManager::IsInitialized()) {
Fiber::GuestFiberManager::SuspendGuestThread(threadPtr);
}
PropagateMutexOwnerPriority(::Memory::Read32(threadPtr + kThreadMutexOffset));
} else if (state == kThreadStateReady) {
const int32_t priority = ComputeThreadEffectivePriority(threadPtr);
::Memory::Write32(threadPtr + kThreadPriorityOffset, static_cast<uint32_t>(priority));
const uint32_t queueEntry =
kThreadQueueArrayAddr + static_cast<uint32_t>(priority) * 8u;
InsertThreadIntoQueueByPriority(queueEntry, threadPtr, priority);
::Memory::Write32(kSchedulerReschedCounterAddr, 1);
if (Fiber::GuestFiberManager::IsInitialized()) {
if (threadPtr == kDefaultThreadContextAddr && !Fiber::GuestFiberManager::HasFiber(threadPtr)) {
Fiber::GuestFiberManager::RegisterMainThreadAsFiber(threadPtr, cpu);
}
Fiber::GuestFiberManager::ResumeGuestThread(threadPtr);
}
} else {
// Neither Waiting nor Ready: no path reschedules the fiber, so the thread
// would be lost. A thread stuck Running (its park raced the timer pump) is
// recovered as Ready; terminated threads stay dead.
RT_LOG(RT_TAG_OS) << "OSResumeThread: thread 0x" << std::hex << threadPtr
<< std::dec << " reached suspend count 0 in state=" << state
<< (state == kThreadStateRunning ? "; recovering as Ready"
: "; no wake path - thread lost")
<< std::endl;
if (state == kThreadStateRunning &&
!Fiber::GuestFiberManager::IsTerminated(threadPtr)) {
::Memory::Write16(threadPtr + kThreadStateOffset, kThreadStateReady);
const int32_t priority = ComputeThreadEffectivePriority(threadPtr);
::Memory::Write32(threadPtr + kThreadPriorityOffset,
static_cast<uint32_t>(priority));
const uint32_t queueEntry =
kThreadQueueArrayAddr + static_cast<uint32_t>(priority) * 8u;
InsertThreadIntoQueueByPriority(queueEntry, threadPtr, priority);
::Memory::Write32(kSchedulerReschedCounterAddr, 1);
if (Fiber::GuestFiberManager::IsInitialized()) {
Fiber::GuestFiberManager::ResumeGuestThread(threadPtr);
}
}
}
if (::Memory::Read32(kSchedulerReschedCounterAddr) != 0) {
cpu->gpr[3] = 0;
SelectThread_801a9c08(cpu);
}
}
}
cpu->gpr[3] = static_cast<uint32_t>(suspendCount);
} catch (const ::Memory::AccessViolation& e) {
LogMemoryError(RT_TAG_OS, "OSResumeThread", e);
cpu->gpr[3] = 0;
}
OS__RestoreInterrupts_801a65d4(irqState);
}
PPC_NATIVE_OVERRIDE_VOID(801AA58C, OSResumeThread_HLE_801aa58c, (CpuContext* ctx), (ctx));