diff --git a/Core/HLE/KernelWaitHelpers.h b/Core/HLE/KernelWaitHelpers.h index 196ee10060..0c02b74d10 100644 --- a/Core/HLE/KernelWaitHelpers.h +++ b/Core/HLE/KernelWaitHelpers.h @@ -30,7 +30,7 @@ namespace HLEKernel { // Cancels the pending timeout event for a wait that's being satisfied, and writes the time -// remaining back to the game. +// remaining back to the game. The event goes off WAIT_TIMEOUT_LATENCY_US after the deadline. // Note the clamp: UnscheduleEvent returns the scheduled time minus the current time, which goes // negative when the event is already overdue but hasn't been processed yet. Without the clamp we'd // write a huge bogus timeout back into the game's variable. @@ -38,7 +38,7 @@ inline void WriteRemainingTimeout(int waitTimer, SceUID threadID, u32 timeoutPtr if (timeoutPtr == 0 || waitTimer == -1) return; - s64 cyclesLeft = CoreTiming::UnscheduleEvent(waitTimer, threadID); + s64 cyclesLeft = CoreTiming::UnscheduleEvent(waitTimer, threadID) - usToCycles(WAIT_TIMEOUT_LATENCY_US); if (cyclesLeft < 0) cyclesLeft = 0; Memory::WriteOrException_U32((u32)cyclesToUs(cyclesLeft), timeoutPtr); diff --git a/Core/HLE/sceKernelEventFlag.cpp b/Core/HLE/sceKernelEventFlag.cpp index 734aad266b..0fa0e6f954 100644 --- a/Core/HLE/sceKernelEventFlag.cpp +++ b/Core/HLE/sceKernelEventFlag.cpp @@ -302,16 +302,8 @@ static void __KernelSetEventFlagTimeout(EventFlag *e, u32 timeoutPtr) { if (timeoutPtr == 0 || eventFlagWaitTimer == -1) return; - int micro = (int) Memory::ReadOrException_U32(timeoutPtr); - - // This seems like the actual timing of timeouts on hardware. - if (micro <= 1) - micro = 25; - else if (micro <= 209) - micro = 240; - - // This should call __KernelEventFlagTimeout() later, unless we cancel it. - CoreTiming::ScheduleEvent(usToCycles(micro), eventFlagWaitTimer, __KernelGetCurThread()); + u32 micro = Memory::ReadOrException_U32(timeoutPtr); + CoreTiming::ScheduleEvent(usToCycles(__KernelWaitTimeoutUs(micro)), eventFlagWaitTimer, __KernelGetCurThread()); } int sceKernelWaitEventFlag(SceUID id, u32 bits, u32 wait, u32 outBitsPtr, u32 timeoutPtr) { @@ -345,6 +337,8 @@ int sceKernelWaitEventFlag(SceUID id, u32 bits, u32 wait, u32 outBitsPtr, u32 ti return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_EVF_MULTI); } + if (__KernelWaitTimesOutAtOnce(timeoutPtr)) + return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once"); (void)hleLogDebug(Log::sceKernel, 0, "waiting"); // No match - must wait. @@ -408,6 +402,8 @@ int sceKernelWaitEventFlagCB(SceUID id, u32 bits, u32 wait, u32 outBitsPtr, u32 return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_EVF_MULTI); } + if (!doCallbackWait && __KernelWaitTimesOutAtOnce(timeoutPtr)) + return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once"); (void)hleLogDebug(Log::sceKernel, 0, "waiting"); // No match - must wait. diff --git a/Core/HLE/sceKernelMbx.cpp b/Core/HLE/sceKernelMbx.cpp index bd52ccf06c..51f6782cac 100644 --- a/Core/HLE/sceKernelMbx.cpp +++ b/Core/HLE/sceKernelMbx.cpp @@ -253,16 +253,8 @@ static void __KernelWaitMbx(Mbx *m, u32 timeoutPtr) if (timeoutPtr == 0 || mbxWaitTimer == -1) return; - int micro = (int) Memory::ReadOrException_U32(timeoutPtr); - - // This seems to match the actual timing. - if (micro <= 2) - micro = 20; - else if (micro <= 209) - micro = 250; - - // This should call __KernelMbxTimeout() later, unless we cancel it. - CoreTiming::ScheduleEvent(usToCycles(micro), mbxWaitTimer, __KernelGetCurThread()); + u32 micro = Memory::ReadOrException_U32(timeoutPtr); + CoreTiming::ScheduleEvent(usToCycles(__KernelWaitTimeoutUs(micro)), mbxWaitTimer, __KernelGetCurThread()); } static std::vector::iterator __KernelMbxFindPriority(std::vector &waiting) @@ -441,6 +433,8 @@ int sceKernelReceiveMbx(SceUID id, u32 packetAddrPtr, u32 timeoutPtr) { return hleLogDebug(Log::sceKernel, m->ReceiveMessage(packetAddrPtr), "sending first queue message"); } else { HLEKernel::RemoveWaitingThread(m->waitingThreads, __KernelGetCurThread()); + if (__KernelWaitTimesOutAtOnce(timeoutPtr)) + return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once"); m->AddWaitingThread(__KernelGetCurThread(), packetAddrPtr); __KernelWaitMbx(m, timeoutPtr); __KernelWaitCurThread(WAITTYPE_MBX, id, 0, timeoutPtr, false, "mbx waited"); @@ -460,6 +454,8 @@ int sceKernelReceiveMbxCB(SceUID id, u32 packetAddrPtr, u32 timeoutPtr) { return hleLogDebug(Log::sceKernel, m->ReceiveMessage(packetAddrPtr), "sending first queue message"); } else { HLEKernel::RemoveWaitingThread(m->waitingThreads, __KernelGetCurThread()); + if (__KernelWaitTimesOutAtOnce(timeoutPtr)) + return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once"); m->AddWaitingThread(__KernelGetCurThread(), packetAddrPtr); __KernelWaitMbx(m, timeoutPtr); __KernelWaitCurThread(WAITTYPE_MBX, id, 0, timeoutPtr, true, "mbx waited"); diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp index 136096053a..2eaa43ba37 100644 --- a/Core/HLE/sceKernelMemory.cpp +++ b/Core/HLE/sceKernelMemory.cpp @@ -641,19 +641,8 @@ static void __KernelSetFplTimeout(u32 timeoutPtr) if (timeoutPtr == 0 || fplWaitTimer == -1) return; - int micro = (int) Memory::ReadOrException_U32(timeoutPtr); - - // TODO: test for fpls. - // This happens to be how the hardware seems to time things. - if (micro <= 5) - micro = 20; - // Yes, this 7 is reproducible. 6 is (a lot) longer than 7. - else if (micro == 7) - micro = 25; - else if (micro <= 215) - micro = 250; - - CoreTiming::ScheduleEvent(usToCycles(micro), fplWaitTimer, __KernelGetCurThread()); + u32 micro = Memory::ReadOrException_U32(timeoutPtr); + CoreTiming::ScheduleEvent(usToCycles(__KernelWaitTimeoutUs(micro)), fplWaitTimer, __KernelGetCurThread()); } int sceKernelAllocateFpl(SceUID uid, u32 blockPtrAddr, u32 timeoutPtr) { @@ -671,6 +660,8 @@ int sceKernelAllocateFpl(SceUID uid, u32 blockPtrAddr, u32 timeoutPtr) { } else { SceUID threadID = __KernelGetCurThread(); HLEKernel::RemoveWaitingThread(fpl->waitingThreads, threadID); + if (__KernelWaitTimesOutAtOnce(timeoutPtr)) + return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once"); FplWaitingThread waiting = {threadID, blockPtrAddr}; fpl->waitingThreads.push_back(waiting); @@ -696,6 +687,8 @@ int sceKernelAllocateFplCB(SceUID uid, u32 blockPtrAddr, u32 timeoutPtr) { } else { SceUID threadID = __KernelGetCurThread(); HLEKernel::RemoveWaitingThread(fpl->waitingThreads, threadID); + if (__KernelWaitTimesOutAtOnce(timeoutPtr)) + return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once"); FplWaitingThread waiting = {threadID, blockPtrAddr}; fpl->waitingThreads.push_back(waiting); @@ -1492,18 +1485,8 @@ static void __KernelSetVplTimeout(u32 timeoutPtr) if (timeoutPtr == 0 || vplWaitTimer == -1) return; - int micro = (int) Memory::ReadOrException_U32(timeoutPtr); - - // This happens to be how the hardware seems to time things. - if (micro <= 5) - micro = 20; - // Yes, this 7 is reproducible. 6 is (a lot) longer than 7. - else if (micro == 7) - micro = 25; - else if (micro <= 215) - micro = 250; - - CoreTiming::ScheduleEvent(usToCycles(micro), vplWaitTimer, __KernelGetCurThread()); + u32 micro = Memory::ReadOrException_U32(timeoutPtr); + CoreTiming::ScheduleEvent(usToCycles(__KernelWaitTimeoutUs(micro)), vplWaitTimer, __KernelGetCurThread()); } int sceKernelAllocateVpl(SceUID uid, u32 size, u32 addrPtr, u32 timeoutPtr) @@ -1514,8 +1497,9 @@ int sceKernelAllocateVpl(SceUID uid, u32 size, u32 addrPtr, u32 timeoutPtr) VPL *vpl = kernelObjects.Get(uid, ignore); if (error == SCE_KERNEL_ERROR_NO_MEMORY) { - if (timeoutPtr != 0 && Memory::ReadOrException_U32(timeoutPtr) == 0) - return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT); + // Allocating does more before setting up the timeout, so short ones time out at once more often. + if (__KernelWaitTimesOutAtOnce(timeoutPtr, 117, 17)) + return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once"); if (vpl) { SceUID threadID = __KernelGetCurThread(); @@ -1544,8 +1528,9 @@ int sceKernelAllocateVplCB(SceUID uid, u32 size, u32 addrPtr, u32 timeoutPtr) VPL *vpl = kernelObjects.Get(uid, ignore); if (error == SCE_KERNEL_ERROR_NO_MEMORY) { - if (timeoutPtr != 0 && Memory::ReadOrException_U32(timeoutPtr) == 0) - return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT); + // Allocating does more before setting up the timeout, so short ones time out at once more often. + if (__KernelWaitTimesOutAtOnce(timeoutPtr, 117, 17)) + return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once"); if (vpl) { diff --git a/Core/HLE/sceKernelMsgPipe.cpp b/Core/HLE/sceKernelMsgPipe.cpp index 82a2f2108d..f045a20718 100644 --- a/Core/HLE/sceKernelMsgPipe.cpp +++ b/Core/HLE/sceKernelMsgPipe.cpp @@ -269,15 +269,16 @@ static bool __KernelSetMsgPipeTimeout(u32 timeoutPtr) { if (timeoutPtr == 0 || waitTimer == -1) return true; - int micro = (int)Memory::ReadOrException_U32(timeoutPtr); - if (micro <= 2) { + // Always at once up to 2us, never from 3us: what threads/msgpipe/send and receive record, where + // each call comes straight after a checkpoint and the code is presumably cold. Back to back the + // hardware does 2us at once less often, as other waits do. + if (__KernelWaitTimesOutAtOnce(timeoutPtr, 210, 55)) { // Don't wait or reschedule, just timeout immediately. return false; } - if (micro <= 210) - micro = 250; - CoreTiming::ScheduleEvent(usToCycles(micro), waitTimer, __KernelGetCurThread()); + const u32 micro = Memory::ReadOrException_U32(timeoutPtr); + CoreTiming::ScheduleEvent(usToCycles(__KernelWaitTimeoutUs(micro)), waitTimer, __KernelGetCurThread()); return true; } diff --git a/Core/HLE/sceKernelMutex.cpp b/Core/HLE/sceKernelMutex.cpp index da149aebba..26367bc09d 100644 --- a/Core/HLE/sceKernelMutex.cpp +++ b/Core/HLE/sceKernelMutex.cpp @@ -434,16 +434,10 @@ static void __KernelWaitMutex(PSPMutex *mutex, u32 timeoutPtr) { if (timeoutPtr == 0 || mutexWaitTimer == -1) return; - int micro = (int) Memory::ReadUnchecked_U32(timeoutPtr); - - // This happens to be how the hardware seems to time things. - if (micro <= 3) - micro = 25; - else if (micro <= 249) - micro = 250; + u32 micro = Memory::ReadUnchecked_U32(timeoutPtr); // This should call __KernelMutexTimeout() later, unless we cancel it. - CoreTiming::ScheduleEvent(usToCycles(micro), mutexWaitTimer, __KernelGetCurThread()); + CoreTiming::ScheduleEvent(usToCycles(__KernelWaitTimeoutUs(micro)), mutexWaitTimer, __KernelGetCurThread()); } int sceKernelCancelMutex(SceUID uid, int count, u32 numWaitThreadsPtr) { @@ -513,6 +507,8 @@ int sceKernelLockMutex(SceUID id, int count, u32 timeoutPtr) { } } + if (__KernelWaitTimesOutAtOnce(timeoutPtr)) + return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once"); SceUID threadID = __KernelGetCurThread(); // May be in a tight loop timing out (where we don't remove from waitingThreads yet), don't want to add duplicates. if (std::find(mutex->waitingThreads.begin(), mutex->waitingThreads.end(), threadID) == mutex->waitingThreads.end()) @@ -533,6 +529,8 @@ int sceKernelLockMutexCB(SceUID id, int count, u32 timeoutPtr) { if (error) return hleLogError(Log::sceKernel, error); + if (__KernelWaitTimesOutAtOnce(timeoutPtr)) + return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once"); SceUID threadID = __KernelGetCurThread(); // May be in a tight loop timing out (where we don't remove from waitingThreads yet), don't want to add duplicates. if (std::find(mutex->waitingThreads.begin(), mutex->waitingThreads.end(), threadID) == mutex->waitingThreads.end()) @@ -812,16 +810,10 @@ static void __KernelWaitLwMutex(LwMutex *mutex, u32 timeoutPtr) { if (timeoutPtr == 0 || lwMutexWaitTimer == -1) return; - int micro = (int) Memory::ReadUnchecked_U32(timeoutPtr); - - // This happens to be how the hardware seems to time things. - if (micro <= 3) - micro = 25; - else if (micro <= 249) - micro = 250; + u32 micro = Memory::ReadUnchecked_U32(timeoutPtr); // This should call __KernelLwMutexTimeout() later, unless we cancel it. - CoreTiming::ScheduleEvent(usToCycles(micro), lwMutexWaitTimer, __KernelGetCurThread()); + CoreTiming::ScheduleEvent(usToCycles(__KernelWaitTimeoutUs(micro)), lwMutexWaitTimer, __KernelGetCurThread()); } static bool __KernelUnlockLwMutexForThreadCheck(LwMutex *mutex, SceUID threadID, u32 &error, int result, bool &wokeThreads) @@ -901,6 +893,8 @@ int sceKernelLockLwMutex(u32 workareaPtr, int count, u32 timeoutPtr) { if (!mutex) { return hleLogError(Log::sceKernel, error); } else { + if (__KernelWaitTimesOutAtOnce(timeoutPtr)) + return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once"); SceUID threadID = __KernelGetCurThread(); // May be in a tight loop timing out (where we don't remove from waitingThreads yet), don't want to add duplicates. if (std::find(mutex->waitingThreads.begin(), mutex->waitingThreads.end(), threadID) == mutex->waitingThreads.end()) @@ -932,6 +926,8 @@ int sceKernelLockLwMutexCB(u32 workareaPtr, int count, u32 timeoutPtr) { if (!mutex) { return hleLogError(Log::sceKernel, error); } else { + if (__KernelWaitTimesOutAtOnce(timeoutPtr)) + return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once"); SceUID threadID = __KernelGetCurThread(); // May be in a tight loop timing out (where we don't remove from waitingThreads yet), don't want to add duplicates. if (std::find(mutex->waitingThreads.begin(), mutex->waitingThreads.end(), threadID) == mutex->waitingThreads.end()) diff --git a/Core/HLE/sceKernelSemaphore.cpp b/Core/HLE/sceKernelSemaphore.cpp index 41d43aea7b..b4278a7d05 100644 --- a/Core/HLE/sceKernelSemaphore.cpp +++ b/Core/HLE/sceKernelSemaphore.cpp @@ -294,16 +294,9 @@ static void __KernelSetSemaTimeout(PSPSemaphore *s, u32 timeoutPtr) { if (timeoutPtr == 0 || semaWaitTimer == -1) return; - int micro = (int)Memory::ReadOrException_U32(timeoutPtr); - - // This happens to be how the hardware seems to time things. - if (micro <= 3) - micro = 24; - else if (micro <= 249) - micro = 245; - + u32 micro = Memory::ReadOrException_U32(timeoutPtr); // This should call __KernelSemaTimeout() later, unless we cancel it. - CoreTiming::ScheduleEvent(usToCycles(micro), semaWaitTimer, __KernelGetCurThread()); + CoreTiming::ScheduleEvent(usToCycles(__KernelWaitTimeoutUs(micro)), semaWaitTimer, __KernelGetCurThread()); } static int __KernelWaitSema(SceUID id, int wantedCount, u32 timeoutPtr, bool processCallbacks) { @@ -327,6 +320,8 @@ static int __KernelWaitSema(SceUID id, int wantedCount, u32 timeoutPtr, bool pro if (s->ns.currentCount >= wantedCount && s->waitingThreads.size() == 0 && !hasCallbacks) { s->ns.currentCount -= wantedCount; } else { + if (!hasCallbacks && __KernelWaitTimesOutAtOnce(timeoutPtr)) + return SCE_KERNEL_ERROR_WAIT_TIMEOUT; SceUID threadID = __KernelGetCurThread(); // May be in a tight loop timing out (where we don't remove from waitingThreads yet), don't want to add duplicates. if (std::find(s->waitingThreads.begin(), s->waitingThreads.end(), threadID) == s->waitingThreads.end()) diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index 085581b450..d25f743598 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -496,6 +496,8 @@ struct BusySyscall { bool counting; }; static std::vector busySyscalls; +// See __KernelChance(). +static int g_chanceAccumulator = 50; static int eventBusySyscallDone = -1; static void __KernelBusySyscallDone(u64 userdata, int cyclesLate); static SceUID currentThread; @@ -707,6 +709,7 @@ static bool __KernelCheckResumeThreadEnd(PSPThread *t, SceUID waitingThreadID, u u32 timeoutPtr = __KernelGetWaitTimeoutPtr(waitingThreadID, error); // Note: unlike the helper in KernelWaitHelpers.h, we unschedule even without a timeout ptr. s64 cyclesLeft = CoreTiming::UnscheduleEvent(eventThreadEndTimeout, waitingThreadID); + cyclesLeft -= usToCycles(WAIT_TIMEOUT_LATENCY_US); if (cyclesLeft < 0) cyclesLeft = 0; if (timeoutPtr != 0) @@ -771,6 +774,7 @@ u32 HLEMipsCallReturnAddress() { } void __KernelThreadingInit() { + g_chanceAccumulator = 50; struct ThreadHack { u32 nid; u32 *addr; @@ -844,7 +848,7 @@ void __KernelThreadingDoState(PointerWrap &p) g_exitCallbackPending = false; } - auto s = p.Section("sceKernelThread", 1, 6); + auto s = p.Section("sceKernelThread", 1, 7); if (!s) return; @@ -906,6 +910,11 @@ void __KernelThreadingDoState(PointerWrap &p) busySyscalls.clear(); } CoreTiming::RestoreRegisterEvent(eventBusySyscallDone, "BusySyscallDone", &__KernelBusySyscallDone); + if (s >= 7) { + Do(p, g_chanceAccumulator); + } else { + g_chanceAccumulator = 50; + } __SetCurrentThread(kernelObjects.GetFast(currentThread), currentThread, __KernelGetThreadName(currentThread)); lastSwitchCycles = CoreTiming::GetTicks(currentMIPS); @@ -1503,6 +1512,42 @@ void __KernelCancelWakeup(SceUID threadID) CoreTiming::UnscheduleEvent(eventScheduledWakeup, threadID); } +// On hardware these outcomes depend on the phase of the clock. We go with the likelier one, except +// that always taking a likely-but-not-certain one could lock a polling loop into never yielding. +// Those are true for the given share of calls, spread evenly: 60 is true three times in five. +static bool __KernelChance(int percent) { + if (percent < 50) { + return false; + } else if (percent >= 100) { + return true; + } + g_chanceAccumulator += percent; + if (g_chanceAccumulator >= 100) { + g_chanceAccumulator -= 100; + return true; + } + return false; +} + +// How a wait's timeout plays out on hardware (pspautotests threads/scheduling/waittimeouts). The +// deadline is taken, and the alarm set up a moment later. If the deadline has passed by then, the +// wait fails with SCE_KERNEL_ERROR_WAIT_TIMEOUT at once, without giving up the CPU or writing the +// timeout back. For most calls that happens about 85% of the time for 0us, half the time for 1us, +// 15% for 2us and never from 3us on. sceKernelAllocateVpl does more first: always up to 1us, then +// about 17% less per us. Longer ones end max(timeout, 205us) plus about 35us after the call, the +// last few of which we already spend around the event. +bool __KernelWaitTimesOutAtOnce(u32 timeoutPtr, int basePercent, int stepPercent) { + if (!Memory::IsValid4AlignedAddress(timeoutPtr)) { + return false; + } + const u32 micro = std::min(Memory::ReadUnchecked_U32(timeoutPtr), 100U); + return __KernelChance(basePercent - (int)micro * stepPercent); +} + +s64 __KernelWaitTimeoutUs(u32 micro) { + return (s64)std::max(micro, 205U) + WAIT_TIMEOUT_LATENCY_US; +} + void hleThreadEndTimeout(u64 userdata, int cyclesLate) { SceUID threadID = (SceUID) userdata; @@ -1546,7 +1591,7 @@ void __KernelStopThread(SceUID threadID, int exitStatus, const char *reason) u32 timeoutPtr = __KernelGetWaitTimeoutPtr(waitingThread, error); if (HLEKernel::VerifyWait(waitingThread, WAITTYPE_THREADEND, threadID)) { - s64 cyclesLeft = CoreTiming::UnscheduleEvent(eventThreadEndTimeout, waitingThread); + s64 cyclesLeft = CoreTiming::UnscheduleEvent(eventThreadEndTimeout, waitingThread) - usToCycles(WAIT_TIMEOUT_LATENCY_US); if (cyclesLeft < 0) cyclesLeft = 0; if (timeoutPtr != 0) @@ -2614,22 +2659,15 @@ static s64 __KernelDelayThreadUs(u64 usec) { } // A delay's deadline is now + usec, and the clock is read again when the alarm is set. If the -// deadline has passed by then, the call returns 0 at once without giving up the CPU. The two reads are about 0.6us apart, so a delay of 0 returns at -// once about 60% of the time, and 1 almost never. On a thread's first wait after it starts, when the -// code is presumably out of the cache, they're about 1.65us apart: a delay of 1 returns at once -// about two times in three, and 2 never (pspautotests threads/scheduling/delayzero). -// Our cycle counts are too regular to use the tick phase (a polling loop could lock into never -// yielding), so it's pseudo-random off the tick count instead. +// deadline has passed by then, the call returns 0 at once without giving up the CPU. The two reads +// are about 0.6us apart, so a delay of 0 returns at once about 60% of the time, and 1 almost never. +// On a thread's first wait after it starts, when the code is presumably out of the cache, they're +// about 1.65us apart: a delay of 1 returns at once about two times in three, and 2 never +// (pspautotests threads/scheduling/delayzero). static bool __KernelDelayReturnsAtOnce(u32 usec) { const PSPThread *thread = __GetCurrentThread(); const int gapPercent = thread && !thread->hasWaited ? 165 : 60; - const int chancePercent = gapPercent - (int)std::min(usec, 2U) * 100; - if (chancePercent <= 0) { - return false; - } - u64 x = (u64)CoreTiming::GetTicks(currentMIPS) * 0x9E3779B97F4A7C15ULL; - x ^= x >> 29; - return (int)(x % 100) < chancePercent; + return __KernelChance(gapPercent - (int)std::min(usec, 2U) * 100); } int sceKernelDelayThreadCB(u32 usec) { @@ -2796,8 +2834,10 @@ int sceKernelWaitThreadEnd(SceUID threadID, u32 timeoutPtr) { } else { if (t->nt.status != THREADSTATUS_DORMANT) { + if (__KernelWaitTimesOutAtOnce(timeoutPtr)) + return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once"); if (Memory::IsValidAddress(timeoutPtr)) - __KernelScheduleThreadEndTimeout(currentThread, threadID, Memory::ReadUnchecked_U32(timeoutPtr)); + __KernelScheduleThreadEndTimeout(currentThread, threadID, __KernelWaitTimeoutUs(Memory::ReadUnchecked_U32(timeoutPtr))); if (std::find(t->waitingThreads.begin(), t->waitingThreads.end(), currentThread) == t->waitingThreads.end()) t->waitingThreads.push_back(currentThread); __KernelWaitCurThread(WAITTYPE_THREADEND, threadID, 0, timeoutPtr, false, "thread wait end"); @@ -2823,8 +2863,10 @@ int sceKernelWaitThreadEndCB(SceUID threadID, u32 timeoutPtr) { } else { if (t->nt.status != THREADSTATUS_DORMANT) { + if (__KernelWaitTimesOutAtOnce(timeoutPtr)) + return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once"); if (Memory::IsValidAddress(timeoutPtr)) - __KernelScheduleThreadEndTimeout(currentThread, threadID, Memory::ReadUnchecked_U32(timeoutPtr)); + __KernelScheduleThreadEndTimeout(currentThread, threadID, __KernelWaitTimeoutUs(Memory::ReadUnchecked_U32(timeoutPtr))); if (std::find(t->waitingThreads.begin(), t->waitingThreads.end(), currentThread) == t->waitingThreads.end()) t->waitingThreads.push_back(currentThread); __KernelWaitCurThread(WAITTYPE_THREADEND, threadID, 0, timeoutPtr, true, "thread wait end"); diff --git a/Core/HLE/sceKernelThread.h b/Core/HLE/sceKernelThread.h index 34ea7f6a44..3d498717f0 100644 --- a/Core/HLE/sceKernelThread.h +++ b/Core/HLE/sceKernelThread.h @@ -379,6 +379,11 @@ u32 __KernelGetWaitTimeoutPtr(SceUID threadID, u32 &error); SceUID __KernelGetWaitID(SceUID threadID, WaitType type, u32 &error); SceUID __KernelGetCurrentCallbackID(SceUID threadID, u32 &error); void __KernelWaitCurThread(WaitType type, SceUID waitId, u32 waitValue, u32 timeoutPtr, bool processCallbacks, const char *reason); +// See the definition for how hardware times waits out. +bool __KernelWaitTimesOutAtOnce(u32 timeoutPtr, int basePercent = 85, int stepPercent = 35); +s64 __KernelWaitTimeoutUs(u32 micro); +// How long after its deadline a wait's timeout goes off. Not part of the time left written back. +const int WAIT_TIMEOUT_LATENCY_US = 30; void __KernelWaitCallbacksCurThread(WaitType type, SceUID waitID, u32 waitValue, u32 timeoutPtr); void __KernelReSchedule(const char *reason = "no reason"); void __KernelReSchedule(bool doCallbacks, const char *reason); diff --git a/test.py b/test.py index a63ea7ca58..5e18a2e8a5 100755 --- a/test.py +++ b/test.py @@ -393,6 +393,8 @@ tests_good = [ "threads/mutex/unlock2", "threads/scheduling/dispatch", "threads/scheduling/delayzero", + "threads/scheduling/readyqueue", + "threads/scheduling/waittimeouts", "threads/semaphores/semaphores", "threads/semaphores/cancel", "threads/semaphores/create",