mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Threads: A delay can end before it starts waiting
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. On hardware that makes sceKernelDelayThread(0) return at once about 60% of the time. On a thread's first wait after it starts, a delay of 1 does so about two times in three as well (pspautotests threads/scheduling/delayzero). We always waited at least 210us. The choice is pseudo-random off the tick count, not the tick phase, since our cycle counts are regular enough for a polling loop to lock into never yielding. Threads remember whether they've waited since starting (Thread savestate section version 6). Also moves threads/vpl/create into the passing tests: re-recorded on 6.61, it agrees with what we do for partitions 8 and 9. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
13dee85375
commit
af8088b790
4 files changed
+38
-7
No files matched your search
@@ -419,7 +419,7 @@ void PSPThread::Cleanup() {
|
||||
}
|
||||
|
||||
void PSPThread::DoState(PointerWrap &p) {
|
||||
auto s = p.Section("Thread", 1, 5);
|
||||
auto s = p.Section("Thread", 1, 6);
|
||||
if (!s)
|
||||
return;
|
||||
|
||||
@@ -460,6 +460,11 @@ void PSPThread::DoState(PointerWrap &p) {
|
||||
Do(p, waitingThreads);
|
||||
Do(p, pausedWaits);
|
||||
}
|
||||
if (s >= 6) {
|
||||
Do(p, hasWaited);
|
||||
} else {
|
||||
hasWaited = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1443,6 +1448,7 @@ void __KernelWaitCurThread(WaitType type, SceUID waitID, u32 waitValue, u32 time
|
||||
WARN_LOG_REPORT(Log::sceKernel, "Waiting thread for %d that was already waiting for %d", type, thread->nt.waitType);
|
||||
thread->nt.waitID = waitID;
|
||||
thread->nt.waitType = type;
|
||||
thread->hasWaited = true;
|
||||
__KernelChangeThreadState(thread, ThreadStatus(THREADSTATUS_WAIT | (thread->nt.status & THREADSTATUS_SUSPEND)));
|
||||
thread->nt.numReleases++;
|
||||
thread->waitInfo.waitValue = waitValue;
|
||||
@@ -1884,6 +1890,7 @@ void __KernelResetThread(PSPThread *t, int lowestPriority) {
|
||||
|
||||
t->nt.exitStatus = SCE_KERNEL_ERROR_NOT_DORMANT;
|
||||
t->isProcessingCallbacks = false;
|
||||
t->hasWaited = false;
|
||||
t->currentCallbackId = 0;
|
||||
t->currentMipscallId = 0;
|
||||
t->pendingMipsCalls.clear();
|
||||
@@ -2606,10 +2613,30 @@ static s64 __KernelDelayThreadUs(u64 usec) {
|
||||
return usec + 10;
|
||||
}
|
||||
|
||||
// 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.
|
||||
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;
|
||||
}
|
||||
|
||||
int sceKernelDelayThreadCB(u32 usec) {
|
||||
hleEatCycles(2000);
|
||||
// Note: Sometimes (0) won't delay, potentially based on how much the thread is doing.
|
||||
// But a loop with just 0 often does delay, and games depend on this. So we err on that side.
|
||||
if (__KernelDelayReturnsAtOnce(usec) && !__KernelCurHasReadyCallbacks()) {
|
||||
return hleLogDebug(Log::sceKernel, 0, "deadline already passed");
|
||||
}
|
||||
SceUID curThread = __KernelGetCurThread();
|
||||
s64 delayUs = __KernelDelayThreadUs(usec);
|
||||
__KernelScheduleWakeup(curThread, delayUs);
|
||||
@@ -2619,8 +2646,9 @@ int sceKernelDelayThreadCB(u32 usec) {
|
||||
|
||||
int sceKernelDelayThread(u32 usec) {
|
||||
hleEatCycles(2000);
|
||||
// Note: Sometimes (0) won't delay, potentially based on how much the thread is doing.
|
||||
// But a loop with just 0 often does delay, and games depend on this. So we err on that side.
|
||||
if (__KernelDelayReturnsAtOnce(usec)) {
|
||||
return hleLogDebug(Log::sceKernel, 0, "deadline already passed");
|
||||
}
|
||||
SceUID curThread = __KernelGetCurThread();
|
||||
s64 delayUs = __KernelDelayThreadUs(usec);
|
||||
__KernelScheduleWakeup(curThread, delayUs);
|
||||
|
||||
@@ -305,6 +305,8 @@ public:
|
||||
KernelThreadDebugInterface debug;
|
||||
|
||||
bool isProcessingCallbacks = false;
|
||||
// False until the thread first waits after being started (see __KernelDelayReturnsAtOnce).
|
||||
bool hasWaited = true;
|
||||
u32 currentMipscallId = -1;
|
||||
SceUID currentCallbackId = -1;
|
||||
|
||||
|
||||
+1
-1
Submodule pspautotests updated: 4a0982f174...73755a0066.
@@ -389,6 +389,7 @@ tests_good = [
|
||||
"threads/mutex/unlock",
|
||||
"threads/mutex/unlock2",
|
||||
"threads/scheduling/dispatch",
|
||||
"threads/scheduling/delayzero",
|
||||
"threads/semaphores/semaphores",
|
||||
"threads/semaphores/cancel",
|
||||
"threads/semaphores/create",
|
||||
@@ -425,6 +426,7 @@ tests_good = [
|
||||
"threads/tls/refer",
|
||||
"threads/vpl/allocate",
|
||||
"threads/vpl/cancel",
|
||||
"threads/vpl/create",
|
||||
"threads/vpl/delete",
|
||||
"threads/vpl/fifo",
|
||||
"threads/vpl/free",
|
||||
@@ -602,7 +604,6 @@ tests_next = [
|
||||
"threads/scheduling/scheduling",
|
||||
"threads/threads/create",
|
||||
"threads/tls/memory",
|
||||
"threads/vpl/create",
|
||||
"umd/io/umd_io",
|
||||
"umd/raw_access/raw_access",
|
||||
"utility/msgdialog/dialog",
|
||||
|
||||
Reference in new issue
Block a user