From 99a1a0133ddb4f575f49ae740a5cfa1e41d63ba7 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 27 Jan 2024 01:58:21 +0200 Subject: [PATCH 1/3] Initial idle skip implementation --- include/cpu_dynarmic.hpp | 2 ++ include/kernel/kernel.hpp | 1 + include/kernel/kernel_types.hpp | 1 + src/core/kernel/events.cpp | 2 ++ src/core/kernel/threads.cpp | 35 ++++++++++++++++++++++++++------- 5 files changed, 34 insertions(+), 7 deletions(-) diff --git a/include/cpu_dynarmic.hpp b/include/cpu_dynarmic.hpp index 97acddd0..048bc62a 100644 --- a/include/cpu_dynarmic.hpp +++ b/include/cpu_dynarmic.hpp @@ -179,6 +179,8 @@ class CPU { return scheduler; } + void addTicks(u64 ticks) { env.AddTicks(ticks); } + void clearCache() { jit->ClearCache(); } void runFrame(); }; \ No newline at end of file diff --git a/include/kernel/kernel.hpp b/include/kernel/kernel.hpp index d35d9031..e78a588a 100644 --- a/include/kernel/kernel.hpp +++ b/include/kernel/kernel.hpp @@ -95,6 +95,7 @@ public: void releaseMutex(Mutex* moo); void cancelTimer(Timer* timer); void signalTimer(Handle timerHandle, Timer* timer); + u64 getWakeupTick(s64 ns); // Wake up the thread with the highest priority out of all threads in the waitlist // Returns the index of the woken up thread diff --git a/include/kernel/kernel_types.hpp b/include/kernel/kernel_types.hpp index 79684e17..2c357037 100644 --- a/include/kernel/kernel_types.hpp +++ b/include/kernel/kernel_types.hpp @@ -123,6 +123,7 @@ struct Thread { bool waitAll; // For WaitSynchronizationN: The "out" pointer u32 outPointer; + u64 wakeupTick; // Thread context used for switching between threads std::array gprs; diff --git a/src/core/kernel/events.cpp b/src/core/kernel/events.cpp index 7da4788e..b5b1c06e 100644 --- a/src/core/kernel/events.cpp +++ b/src/core/kernel/events.cpp @@ -127,6 +127,7 @@ void Kernel::waitSynchronization1() { t.waitList.resize(1); t.status = ThreadStatus::WaitSync1; t.sleepTick = cpu.getTicks(); + t.wakeupTick = getWakeupTick(ns); t.waitingNanoseconds = ns; t.waitList[0] = handle; @@ -222,6 +223,7 @@ void Kernel::waitSynchronizationN() { t.outPointer = outPointer; t.waitingNanoseconds = ns; t.sleepTick = cpu.getTicks(); + t.wakeupTick = getWakeupTick(ns); for (s32 i = 0; i < handleCount; i++) { t.waitList[i] = waitObjects[i].first; // Add object to this thread's waitlist diff --git a/src/core/kernel/threads.cpp b/src/core/kernel/threads.cpp index 239a6617..cea48bd2 100644 --- a/src/core/kernel/threads.cpp +++ b/src/core/kernel/threads.cpp @@ -52,14 +52,8 @@ bool Kernel::canThreadRun(const Thread& t) { return true; } else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 || t.status == ThreadStatus::WaitSyncAny || t.status == ThreadStatus::WaitSyncAll) { - const u64 elapsedTicks = cpu.getTicks() - t.sleepTick; - - constexpr double ticksPerSec = double(CPU::ticksPerSec); - constexpr double nsPerTick = ticksPerSec / 1000000000.0; - // TODO: Set r0 to the correct error code on timeout for WaitSync{1/Any/All} - const s64 elapsedNs = s64(double(elapsedTicks) * nsPerTick); - return elapsedNs >= t.waitingNanoseconds; + return cpu.getTicks() >= t.wakeupTick; } // Handle timeouts and stuff here @@ -82,6 +76,14 @@ std::optional Kernel::getNextThread() { return std::nullopt; } +u64 Kernel::getWakeupTick(s64 ns) { + if (ns == -1) { + return std::numeric_limits::max(); + } + + return cpu.getTicks() + Scheduler::nsToCycles(ns); +} + // See if there is a higher priority, ready thread and switch to that void Kernel::rescheduleThreads() { Thread& current = threads[currentThreadIndex]; // Current running thread @@ -368,6 +370,24 @@ void Kernel::sleepThread(s64 ns) { if (index != idleThreadIndex) { switchThread(index); } + } else { + if (currentThreadIndex == idleThreadIndex) { + const Scheduler& scheduler = cpu.getScheduler(); + u64 timestamp = scheduler.nextTimestamp; + + for (auto i : threadIndices) { + const Thread& t = threads[i]; + if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 || t.status == ThreadStatus::WaitSyncAny || + t.status == ThreadStatus::WaitSyncAll) { + timestamp = std::min(timestamp, t.wakeupTick); + } + } + + if (timestamp > scheduler.currentTimestamp) { + u64 idleCycles = timestamp - scheduler.currentTimestamp; + cpu.addTicks(idleCycles); + } + } } } else { // If we're sleeping for >= 0 ns Thread& t = threads[currentThreadIndex]; @@ -375,6 +395,7 @@ void Kernel::sleepThread(s64 ns) { t.status = ThreadStatus::WaitSleep; t.waitingNanoseconds = ns; t.sleepTick = cpu.getTicks(); + t.wakeupTick = getWakeupTick(ns); requireReschedule(); } From 491b415759348c5cb5a93f7007e9865148a88ccb Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:38:27 +0200 Subject: [PATCH 2/3] Remove thread sleepTick/waitingNs --- include/kernel/kernel_types.hpp | 76 ++++++++++++++++----------------- src/core/kernel/events.cpp | 4 -- src/core/kernel/idle_thread.cpp | 13 +----- src/core/kernel/threads.cpp | 2 - 4 files changed, 37 insertions(+), 58 deletions(-) diff --git a/include/kernel/kernel_types.hpp b/include/kernel/kernel_types.hpp index 2c357037..01af4bd9 100644 --- a/include/kernel/kernel_types.hpp +++ b/include/kernel/kernel_types.hpp @@ -83,57 +83,53 @@ struct Port { }; struct Session { - Handle portHandle; // The port this session is subscribed to - Session(Handle portHandle) : portHandle(portHandle) {} + Handle portHandle; // The port this session is subscribed to + Session(Handle portHandle) : portHandle(portHandle) {} }; enum class ThreadStatus { - Running, // Currently running - Ready, // Ready to run - WaitArbiter, // Waiting on an address arbiter - WaitSleep, // Waiting due to a SleepThread SVC - WaitSync1, // Waiting for the single object in the wait list to be ready - WaitSyncAny, // Wait for one object of the many that might be in the wait list to be ready - WaitSyncAll, // Waiting for ALL sync objects in its wait list to be ready - WaitIPC, // Waiting for the reply from an IPC request - Dormant, // Created but not yet made ready - Dead // Run to completion, or forcefully terminated + Running, // Currently running + Ready, // Ready to run + WaitArbiter, // Waiting on an address arbiter + WaitSleep, // Waiting due to a SleepThread SVC + WaitSync1, // Waiting for the single object in the wait list to be ready + WaitSyncAny, // Wait for one object of the many that might be in the wait list to be ready + WaitSyncAll, // Waiting for ALL sync objects in its wait list to be ready + WaitIPC, // Waiting for the reply from an IPC request + Dormant, // Created but not yet made ready + Dead // Run to completion, or forcefully terminated }; struct Thread { - u32 initialSP; // Initial r13 value - u32 entrypoint; // Initial r15 value - u32 priority; - u32 arg; - ProcessorID processorID; - ThreadStatus status; - Handle handle; // OS handle for this thread - int index; // Index of the thread. 0 for the first thread, 1 for the second, and so on + u32 initialSP; // Initial r13 value + u32 entrypoint; // Initial r15 value + u32 priority; + u32 arg; + ProcessorID processorID; + ThreadStatus status; + Handle handle; // OS handle for this thread + int index; // Index of the thread. 0 for the first thread, 1 for the second, and so on - // The waiting address for threads that are waiting on an AddressArbiter - u32 waitingAddress; + // The waiting address for threads that are waiting on an AddressArbiter + u32 waitingAddress; - // The nanoseconds until a thread wakes up from being asleep or from timing out while waiting on an arbiter - u64 waitingNanoseconds; - // The tick this thread went to sleep on - u64 sleepTick; - // For WaitSynchronization(N): A vector of objects this thread is waiting for - std::vector waitList; - // For WaitSynchronizationN: Shows whether the object should wait for all objects in the wait list or just one - bool waitAll; - // For WaitSynchronizationN: The "out" pointer - u32 outPointer; + // For WaitSynchronization(N): A vector of objects this thread is waiting for + std::vector waitList; + // For WaitSynchronizationN: Shows whether the object should wait for all objects in the wait list or just one + bool waitAll; + // For WaitSynchronizationN: The "out" pointer + u32 outPointer; u64 wakeupTick; - // Thread context used for switching between threads - std::array gprs; - std::array fprs; // Stored as u32 because dynarmic does it - u32 cpsr; - u32 fpscr; - u32 tlsBase; // Base pointer for thread-local storage + // Thread context used for switching between threads + std::array gprs; + std::array fprs; // Stored as u32 because dynarmic does it + u32 cpsr; + u32 fpscr; + u32 tlsBase; // Base pointer for thread-local storage - // A list of threads waiting for this thread to terminate. Yes, threads are sync objects too. - u64 threadsWaitingForTermination; + // A list of threads waiting for this thread to terminate. Yes, threads are sync objects too. + u64 threadsWaitingForTermination; }; static const char* kernelObjectTypeToString(KernelObjectType t) { diff --git a/src/core/kernel/events.cpp b/src/core/kernel/events.cpp index b5b1c06e..b2f89fbf 100644 --- a/src/core/kernel/events.cpp +++ b/src/core/kernel/events.cpp @@ -126,9 +126,7 @@ void Kernel::waitSynchronization1() { auto& t = threads[currentThreadIndex]; t.waitList.resize(1); t.status = ThreadStatus::WaitSync1; - t.sleepTick = cpu.getTicks(); t.wakeupTick = getWakeupTick(ns); - t.waitingNanoseconds = ns; t.waitList[0] = handle; // Add the current thread to the object's wait list @@ -221,8 +219,6 @@ void Kernel::waitSynchronizationN() { t.waitList.resize(handleCount); t.status = ThreadStatus::WaitSyncAny; t.outPointer = outPointer; - t.waitingNanoseconds = ns; - t.sleepTick = cpu.getTicks(); t.wakeupTick = getWakeupTick(ns); for (s32 i = 0; i < handleCount; i++) { diff --git a/src/core/kernel/idle_thread.cpp b/src/core/kernel/idle_thread.cpp index 5abba373..d666968b 100644 --- a/src/core/kernel/idle_thread.cpp +++ b/src/core/kernel/idle_thread.cpp @@ -8,13 +8,6 @@ The code for our idle thread looks like this idle_thread_main: - mov r0, #4096 @ Loop counter - - .loop: - nop; nop; nop; nop @ NOP 4 times to waste some cycles - subs r0, #1 @ Decrement counter by 1, go back to looping if loop counter != 0 - bne .loop - // Sleep for 0 seconds with the SleepThread SVC, which just yields execution mov r0, #0 mov r1, #0 @@ -24,14 +17,10 @@ idle_thread_main: */ static constexpr u8 idleThreadCode[] = { - 0x01, 0x0A, 0xA0, 0xE3, // mov r0, #4096 - 0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, // nop (4 times) - 0x01, 0x00, 0x50, 0xE2, // subs r0, #1 - 0xF9, 0xFF, 0xFF, 0x1A, // bne loop 0x00, 0x00, 0xA0, 0xE3, // mov r0, #0 0x00, 0x10, 0xA0, 0xE3, // mov r1, #0 0x0A, 0x00, 0x00, 0xEF, // svc SleepThread - 0xF4, 0xFF, 0xFF, 0xEA // b idle_thread_main + 0xFB, 0xFF, 0xFF, 0xEA // b idle_thread_main }; // Set up an idle thread to run when no thread is able to run diff --git a/src/core/kernel/threads.cpp b/src/core/kernel/threads.cpp index cea48bd2..bf05fd89 100644 --- a/src/core/kernel/threads.cpp +++ b/src/core/kernel/threads.cpp @@ -393,8 +393,6 @@ void Kernel::sleepThread(s64 ns) { Thread& t = threads[currentThreadIndex]; t.status = ThreadStatus::WaitSleep; - t.waitingNanoseconds = ns; - t.sleepTick = cpu.getTicks(); t.wakeupTick = getWakeupTick(ns); requireReschedule(); From 8ff2d7f9b2bb2b70a7420d6482ba99dd36d16a98 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:44:01 +0200 Subject: [PATCH 3/3] Update comment --- src/core/kernel/threads.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/kernel/threads.cpp b/src/core/kernel/threads.cpp index bf05fd89..3a6201c1 100644 --- a/src/core/kernel/threads.cpp +++ b/src/core/kernel/threads.cpp @@ -77,6 +77,7 @@ std::optional Kernel::getNextThread() { } u64 Kernel::getWakeupTick(s64 ns) { + // Timeout == -1 means that the thread doesn't plan on waking up automatically if (ns == -1) { return std::numeric_limits::max(); }