Kernel waits: Share waiter ordering and clearing between objects

Priority-ordered waiting lists were sorted with a comparator wrapper per
object (msgpipe, fpl, vpl), or searched with a copy of the same function
(mutex, mbx). HLEKernel::SortWaitingThreadsByPriority() and
FindBestPriorityWaiter() now do both for any waiting list, of thread ids
or of structs with a threadID.

HLEKernel::ClearWaitingThreads() replaces the identical cancel/delete
loops in semaphores, event flags, fpl and vpl.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-09-30 09:06:30 -06:00
1 parent cc5ee42dda
commit 80938529a9
7 files changed
+64 -112

No files matched your search

+48
View File
@@ -345,6 +345,54 @@ inline void CleanupWaitingThreads(WaitType waitType, SceUID uid, std::vector<T>
waitingThreads.resize(size);
}
// Ends every wait on an object with the given result (for cancel and delete), through the object's
// function for releasing one waiter:
// bool Unlock(KO *ko, WaitInfoType &waitingThreadInfo, u32 &error, int result, bool &wokeThreads)
template <typename KO, class UnlockFunc>
inline bool ClearWaitingThreads(KO *ko, int result, UnlockFunc Unlock) {
u32 error;
bool wokeThreads = false;
for (auto &waiting : ko->waitingThreads) {
Unlock(ko, waiting, error, result, wokeThreads);
}
ko->waitingThreads.clear();
return wokeThreads;
}
// A waiting list holds either thread ids or structs with a threadID.
inline SceUID WaitingThreadID(const SceUID &threadID) {
return threadID;
}
template <typename T>
inline SceUID WaitingThreadID(const T &waitInfo) {
return waitInfo.threadID;
}
// For objects created with the priority attribute: best priority first, and among equals the order
// they started waiting in.
template <typename T>
inline void SortWaitingThreadsByPriority(std::vector<T> &waitingThreads) {
std::stable_sort(waitingThreads.begin(), waitingThreads.end(), [](const T &a, const T &b) {
return __KernelThreadSortPriority(WaitingThreadID(a), WaitingThreadID(b));
});
}
// The first waiter with the best priority, without reordering the list.
template <typename T>
inline typename std::vector<T>::iterator FindBestPriorityWaiter(std::vector<T> &waitingThreads) {
_dbg_assert_msg_(!waitingThreads.empty(), "FindBestPriorityWaiter: no threads");
auto best = waitingThreads.end();
u32 bestPriority = 0xFFFFFFFF;
for (auto iter = waitingThreads.begin(); iter != waitingThreads.end(); ++iter) {
const u32 priority = __KernelGetThreadPrio(WaitingThreadID(*iter));
if (priority < bestPriority) {
best = iter;
bestPriority = priority;
}
}
return best;
}
template <typename T>
inline void RemoveWaitingThread(std::vector<T> &waitingThreads, const SceUID threadID) {
waitingThreads.erase(std::remove(waitingThreads.begin(), waitingThreads.end(), threadID), waitingThreads.end());
+2 -12
View File
@@ -135,16 +135,6 @@ static bool __KernelUnlockEventFlagForThread(EventFlag *e, EventFlagTh &th, u32
return true;
}
static bool __KernelClearEventFlagThreads(EventFlag *e, int reason) {
u32 error;
bool wokeThreads = false;
for (auto &event : e->waitingThreads)
__KernelUnlockEventFlagForThread(e, event, error, reason, wokeThreads);
e->waitingThreads.clear();
return wokeThreads;
}
void __KernelEventFlagBeginCallback(SceUID threadID, SceUID prevCallbackId) {
auto result = HLEKernel::WaitBeginCallback<EventFlag, WAITTYPE_EVENTFLAG, EventFlagTh>(threadID, prevCallbackId);
if (result == HLEKernel::WAIT_CB_SUCCESS)
@@ -206,7 +196,7 @@ u32 sceKernelCancelEventFlag(SceUID uid, u32 pattern, u32 numWaitThreadsPtr) {
e->nef.currentPattern = pattern;
if (__KernelClearEventFlagThreads(e, SCE_KERNEL_ERROR_WAIT_CANCEL))
if (HLEKernel::ClearWaitingThreads(e, SCE_KERNEL_ERROR_WAIT_CANCEL, __KernelUnlockEventFlagForThread))
hleReSchedule("event flag canceled");
hleEatCycles(580);
@@ -233,7 +223,7 @@ u32 sceKernelDeleteEventFlag(SceUID uid) {
u32 error;
EventFlag *e = kernelObjects.Get<EventFlag>(uid, error);
if (e) {
bool wokeThreads = __KernelClearEventFlagThreads(e, SCE_KERNEL_ERROR_WAIT_DELETE);
bool wokeThreads = HLEKernel::ClearWaitingThreads(e, SCE_KERNEL_ERROR_WAIT_DELETE, __KernelUnlockEventFlagForThread);
if (wokeThreads)
hleReSchedule("event flag deleted");
+1 -19
View File
@@ -249,24 +249,6 @@ void __KernelMbxTimeout(u64 userdata, int cyclesLate)
HLEKernel::WaitExecTimeout<Mbx, WAITTYPE_MBX>(threadID);
}
static std::vector<MbxWaitingThread>::iterator __KernelMbxFindPriority(std::vector<MbxWaitingThread> &waiting)
{
_dbg_assert_msg_(!waiting.empty(), "__KernelMutexFindPriority: Trying to find best of no threads.");
std::vector<MbxWaitingThread>::iterator iter, end, best = waiting.end();
u32 best_prio = 0xFFFFFFFF;
for (iter = waiting.begin(), end = waiting.end(); iter != end; ++iter) {
u32 iter_prio = __KernelGetThreadPrio(iter->threadID);
if (iter_prio < best_prio) {
best = iter;
best_prio = iter_prio;
}
}
_dbg_assert_msg_(best != waiting.end(), "__KernelMutexFindPriority: Returning invalid best thread.");
return best;
}
SceUID sceKernelCreateMbx(const char *name, u32 attr, u32 optAddr)
{
if (!name)
@@ -353,7 +335,7 @@ int sceKernelSendMbx(SceUID id, u32 packetAddr)
while (!wokeThreads && !m->waitingThreads.empty())
{
if ((m->nmb.attr & SCE_KERNEL_MBA_THPRI) != 0)
iter = __KernelMbxFindPriority(m->waitingThreads);
iter = HLEKernel::FindBestPriorityWaiter(m->waitingThreads);
else
iter = m->waitingThreads.begin();
+7 -39
View File
@@ -509,22 +509,6 @@ void __KernelFplEndCallback(SceUID threadID, SceUID prevCallbackId)
DEBUG_LOG(Log::sceKernel, "sceKernelAllocateFplCB: Resuming mbx wait from callback");
}
static bool __FplThreadSortPriority(FplWaitingThread thread1, FplWaitingThread thread2)
{
return __KernelThreadSortPriority(thread1.threadID, thread2.threadID);
}
static bool __KernelClearFplThreads(FPL *fpl, int reason)
{
u32 error;
bool wokeThreads = false;
for (auto iter = fpl->waitingThreads.begin(), end = fpl->waitingThreads.end(); iter != end; ++iter)
__KernelUnlockFplForThread(fpl, *iter, error, reason, wokeThreads);
fpl->waitingThreads.clear();
return wokeThreads;
}
static void __KernelSortFplThreads(FPL *fpl)
{
// Remove any that are no longer waiting.
@@ -532,7 +516,7 @@ static void __KernelSortFplThreads(FPL *fpl)
HLEKernel::CleanupWaitingThreads(WAITTYPE_FPL, uid, fpl->waitingThreads);
if ((fpl->nf.attr & PSP_FPL_ATTR_PRIORITY) != 0)
std::stable_sort(fpl->waitingThreads.begin(), fpl->waitingThreads.end(), __FplThreadSortPriority);
HLEKernel::SortWaitingThreadsByPriority(fpl->waitingThreads);
}
int sceKernelCreateFpl(const char *name, u32 mpid, u32 attr, u32 blockSize, u32 numBlocks, u32 optPtr) {
@@ -616,7 +600,7 @@ int sceKernelDeleteFpl(SceUID uid)
return hleLogDebug(Log::sceKernel, error, "invalid fpl");
}
bool wokeThreads = __KernelClearFplThreads(fpl, SCE_KERNEL_ERROR_WAIT_DELETE);
bool wokeThreads = HLEKernel::ClearWaitingThreads(fpl, SCE_KERNEL_ERROR_WAIT_DELETE, __KernelUnlockFplForThread);
if (wokeThreads)
hleReSchedule("fpl deleted");
@@ -758,7 +742,7 @@ int sceKernelCancelFpl(SceUID uid, u32 numWaitThreadsPtr) {
if (Memory::IsValid4AlignedAddress(numWaitThreadsPtr)) {
Memory::WriteUnchecked_U32(fpl->nf.numWaitThreads, numWaitThreadsPtr);
}
bool wokeThreads = __KernelClearFplThreads(fpl, SCE_KERNEL_ERROR_WAIT_CANCEL);
bool wokeThreads = HLEKernel::ClearWaitingThreads(fpl, SCE_KERNEL_ERROR_WAIT_CANCEL, __KernelUnlockFplForThread);
if (wokeThreads)
hleReSchedule("fpl canceled");
return hleLogDebug(Log::sceKernel, 0);
@@ -1286,22 +1270,6 @@ void __KernelVplEndCallback(SceUID threadID, SceUID prevCallbackId)
DEBUG_LOG(Log::sceKernel, "sceKernelAllocateVplCB: Resuming mbx wait from callback");
}
static bool __VplThreadSortPriority(VplWaitingThread thread1, VplWaitingThread thread2)
{
return __KernelThreadSortPriority(thread1.threadID, thread2.threadID);
}
static bool __KernelClearVplThreads(VPL *vpl, int reason)
{
u32 error;
bool wokeThreads = false;
for (auto iter = vpl->waitingThreads.begin(), end = vpl->waitingThreads.end(); iter != end; ++iter)
__KernelUnlockVplForThread(vpl, *iter, error, reason, wokeThreads);
vpl->waitingThreads.clear();
return wokeThreads;
}
static void __KernelSortVplThreads(VPL *vpl)
{
// Remove any that are no longer waiting.
@@ -1309,7 +1277,7 @@ static void __KernelSortVplThreads(VPL *vpl)
HLEKernel::CleanupWaitingThreads(WAITTYPE_VPL, uid, vpl->waitingThreads);
if ((vpl->nv.attr & PSP_VPL_ATTR_PRIORITY) != 0)
std::stable_sort(vpl->waitingThreads.begin(), vpl->waitingThreads.end(), __VplThreadSortPriority);
HLEKernel::SortWaitingThreadsByPriority(vpl->waitingThreads);
}
SceUID sceKernelCreateVpl(const char *name, int partition, u32 attr, u32 vplSize, u32 optPtr) {
@@ -1383,7 +1351,7 @@ int sceKernelDeleteVpl(SceUID uid) {
return hleLogError(Log::sceKernel, error);
} else {
DEBUG_LOG(Log::sceKernel, "sceKernelDeleteVpl(%i)", uid);
bool wokeThreads = __KernelClearVplThreads(vpl, SCE_KERNEL_ERROR_WAIT_DELETE);
bool wokeThreads = HLEKernel::ClearWaitingThreads(vpl, SCE_KERNEL_ERROR_WAIT_DELETE, __KernelUnlockVplForThread);
if (wokeThreads)
hleReSchedule("vpl deleted");
@@ -1588,7 +1556,7 @@ int sceKernelCancelVpl(SceUID uid, u32 numWaitThreadsPtr)
if (Memory::IsValid4AlignedAddress(numWaitThreadsPtr))
Memory::WriteUnchecked_U32(vpl->nv.numWaitThreads, numWaitThreadsPtr);
bool wokeThreads = __KernelClearVplThreads(vpl, SCE_KERNEL_ERROR_WAIT_CANCEL);
bool wokeThreads = HLEKernel::ClearWaitingThreads(vpl, SCE_KERNEL_ERROR_WAIT_CANCEL, __KernelUnlockVplForThread);
if (wokeThreads)
hleReSchedule("vpl canceled");
@@ -1750,7 +1718,7 @@ static void __KernelSortTlsplThreads(TLSPL *tls)
HLEKernel::CleanupWaitingThreads(WAITTYPE_TLSPL, uid, tls->waitingThreads);
if ((tls->ntls.attr & PSP_FPL_ATTR_PRIORITY) != 0)
std::stable_sort(tls->waitingThreads.begin(), tls->waitingThreads.end(), __KernelThreadSortPriority);
HLEKernel::SortWaitingThreadsByPriority(tls->waitingThreads);
}
int __KernelFreeTls(TLSPL *tls, SceUID threadID)
+1 -6
View File
@@ -50,11 +50,6 @@ static const u32 MSGPIPE_WAIT_VALUE_RECV = 1;
// NativeMsgPipe/MsgPipeWaitingThread/MsgPipe itself now live in sceKernelMsgPipe.h - see the
// comment on the class there for why.
static bool __KernelMsgPipeThreadSortPriority(const MsgPipeWaitingThread &thread1, const MsgPipeWaitingThread &thread2)
{
return __KernelThreadSortPriority(thread1.threadID, thread2.threadID);
}
bool MsgPipeWaitingThread::IsStillWaiting(SceUID waitID) const
{
return HLEKernel::VerifyWait(threadID, WAITTYPE_MSGPIPE, waitID);
@@ -213,7 +208,7 @@ void MsgPipe::SortThreads(std::vector<MsgPipeWaitingThread> &waitingThreads, boo
HLEKernel::CleanupWaitingThreads(WAITTYPE_MSGPIPE, GetUID(), waitingThreads);
if (usePrio)
std::stable_sort(waitingThreads.begin(), waitingThreads.end(), __KernelMsgPipeThreadSortPriority);
HLEKernel::SortWaitingThreadsByPriority(waitingThreads);
}
void MsgPipe::SortReceiveThreads()
+2 -22
View File
@@ -199,26 +199,6 @@ static void __KernelMutexEraseLock(PSPMutex *mutex) {
mutex->nm.lockThread = -1;
}
static std::vector<SceUID>::iterator __KernelMutexFindPriority(std::vector<SceUID> &waiting)
{
_dbg_assert_msg_(!waiting.empty(), "__KernelMutexFindPriority: Trying to find best of no threads.");
std::vector<SceUID>::iterator iter, end, best = waiting.end();
u32 best_prio = 0xFFFFFFFF;
for (iter = waiting.begin(), end = waiting.end(); iter != end; ++iter)
{
u32 iter_prio = __KernelGetThreadPrio(*iter);
if (iter_prio < best_prio)
{
best = iter;
best_prio = iter_prio;
}
}
_dbg_assert_msg_(best != waiting.end(), "__KernelMutexFindPriority: Returning invalid best thread.");
return best;
}
static bool __KernelUnlockMutexForThread(PSPMutex *mutex, SceUID threadID, u32 &error, int result) {
if (!HLEKernel::VerifyWait(threadID, WAITTYPE_MUTEX, mutex->GetUID()))
return false;
@@ -379,7 +359,7 @@ static bool __KernelUnlockMutex(PSPMutex *mutex, u32 &error) {
while (!wokeThreads && !mutex->waitingThreads.empty())
{
if ((mutex->nm.attr & PSP_MUTEX_ATTR_PRIORITY) != 0)
iter = __KernelMutexFindPriority(mutex->waitingThreads);
iter = HLEKernel::FindBestPriorityWaiter(mutex->waitingThreads);
else
iter = mutex->waitingThreads.begin();
@@ -770,7 +750,7 @@ bool __KernelUnlockLwMutex(T workarea, u32 &error) {
while (!wokeThreads && !mutex->waitingThreads.empty())
{
if ((mutex->nm.attr & PSP_MUTEX_ATTR_PRIORITY) != 0)
iter = __KernelMutexFindPriority(mutex->waitingThreads);
iter = HLEKernel::FindBestPriorityWaiter(mutex->waitingThreads);
else
iter = mutex->waitingThreads.begin();
+3 -14
View File
@@ -119,17 +119,6 @@ void __KernelSemaEndCallback(SceUID threadID, SceUID prevCallbackId)
// Resume all waiting threads (for delete / cancel.)
// Returns true if it woke any threads.
static bool __KernelClearSemaThreads(PSPSemaphore *s, int reason) {
u32 error;
bool wokeThreads = false;
std::vector<SceUID>::iterator iter, end;
for (iter = s->waitingThreads.begin(), end = s->waitingThreads.end(); iter != end; ++iter)
__KernelUnlockSemaForThread(s, *iter, error, reason, wokeThreads);
s->waitingThreads.clear();
return wokeThreads;
}
int sceKernelCancelSema(SceUID id, int newCount, u32 numWaitThreadsPtr)
{
u32 error;
@@ -152,7 +141,7 @@ int sceKernelCancelSema(SceUID id, int newCount, u32 numWaitThreadsPtr)
else
s->ns.currentCount = newCount;
if (__KernelClearSemaThreads(s, SCE_KERNEL_ERROR_WAIT_CANCEL))
if (HLEKernel::ClearWaitingThreads(s, SCE_KERNEL_ERROR_WAIT_CANCEL, __KernelUnlockSemaForThread))
hleReSchedule("semaphore canceled");
return hleNoLog(0);
@@ -203,7 +192,7 @@ int sceKernelDeleteSema(SceUID id) {
} else {
DEBUG_LOG(Log::sceKernel, "sceKernelDeleteSema(%i)", id);
bool wokeThreads = __KernelClearSemaThreads(s, SCE_KERNEL_ERROR_WAIT_DELETE);
bool wokeThreads = HLEKernel::ClearWaitingThreads(s, SCE_KERNEL_ERROR_WAIT_DELETE, __KernelUnlockSemaForThread);
if (wokeThreads)
hleReSchedule("semaphore deleted");
@@ -252,7 +241,7 @@ int sceKernelSignalSema(SceUID id, int signal) {
s->ns.currentCount += signal;
if ((s->ns.attr & PSP_SEMA_ATTR_PRIORITY) != 0)
std::stable_sort(s->waitingThreads.begin(), s->waitingThreads.end(), __KernelThreadSortPriority);
HLEKernel::SortWaitingThreadsByPriority(s->waitingThreads);
bool wokeThreads = false;
retry: