mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-07 22:55:40 +12:00
Merge pull request #387 from wheremyfoodat/idle-skeep
Add idle skipping
This commit is contained in:
commit
5c39674907
6 changed files with 72 additions and 65 deletions
|
@ -179,6 +179,8 @@ class CPU {
|
||||||
return scheduler;
|
return scheduler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addTicks(u64 ticks) { env.AddTicks(ticks); }
|
||||||
|
|
||||||
void clearCache() { jit->ClearCache(); }
|
void clearCache() { jit->ClearCache(); }
|
||||||
void runFrame();
|
void runFrame();
|
||||||
};
|
};
|
|
@ -95,6 +95,7 @@ public:
|
||||||
void releaseMutex(Mutex* moo);
|
void releaseMutex(Mutex* moo);
|
||||||
void cancelTimer(Timer* timer);
|
void cancelTimer(Timer* timer);
|
||||||
void signalTimer(Handle timerHandle, 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
|
// Wake up the thread with the highest priority out of all threads in the waitlist
|
||||||
// Returns the index of the woken up thread
|
// Returns the index of the woken up thread
|
||||||
|
|
|
@ -83,56 +83,53 @@ struct Port {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Session {
|
struct Session {
|
||||||
Handle portHandle; // The port this session is subscribed to
|
Handle portHandle; // The port this session is subscribed to
|
||||||
Session(Handle portHandle) : portHandle(portHandle) {}
|
Session(Handle portHandle) : portHandle(portHandle) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ThreadStatus {
|
enum class ThreadStatus {
|
||||||
Running, // Currently running
|
Running, // Currently running
|
||||||
Ready, // Ready to run
|
Ready, // Ready to run
|
||||||
WaitArbiter, // Waiting on an address arbiter
|
WaitArbiter, // Waiting on an address arbiter
|
||||||
WaitSleep, // Waiting due to a SleepThread SVC
|
WaitSleep, // Waiting due to a SleepThread SVC
|
||||||
WaitSync1, // Waiting for the single object in the wait list to be ready
|
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
|
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
|
WaitSyncAll, // Waiting for ALL sync objects in its wait list to be ready
|
||||||
WaitIPC, // Waiting for the reply from an IPC request
|
WaitIPC, // Waiting for the reply from an IPC request
|
||||||
Dormant, // Created but not yet made ready
|
Dormant, // Created but not yet made ready
|
||||||
Dead // Run to completion, or forcefully terminated
|
Dead // Run to completion, or forcefully terminated
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Thread {
|
struct Thread {
|
||||||
u32 initialSP; // Initial r13 value
|
u32 initialSP; // Initial r13 value
|
||||||
u32 entrypoint; // Initial r15 value
|
u32 entrypoint; // Initial r15 value
|
||||||
u32 priority;
|
u32 priority;
|
||||||
u32 arg;
|
u32 arg;
|
||||||
ProcessorID processorID;
|
ProcessorID processorID;
|
||||||
ThreadStatus status;
|
ThreadStatus status;
|
||||||
Handle handle; // OS handle for this thread
|
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
|
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
|
// The waiting address for threads that are waiting on an AddressArbiter
|
||||||
u32 waitingAddress;
|
u32 waitingAddress;
|
||||||
|
|
||||||
// The nanoseconds until a thread wakes up from being asleep or from timing out while waiting on an arbiter
|
// For WaitSynchronization(N): A vector of objects this thread is waiting for
|
||||||
u64 waitingNanoseconds;
|
std::vector<Handle> waitList;
|
||||||
// The tick this thread went to sleep on
|
// For WaitSynchronizationN: Shows whether the object should wait for all objects in the wait list or just one
|
||||||
u64 sleepTick;
|
bool waitAll;
|
||||||
// For WaitSynchronization(N): A vector of objects this thread is waiting for
|
// For WaitSynchronizationN: The "out" pointer
|
||||||
std::vector<Handle> waitList;
|
u32 outPointer;
|
||||||
// For WaitSynchronizationN: Shows whether the object should wait for all objects in the wait list or just one
|
u64 wakeupTick;
|
||||||
bool waitAll;
|
|
||||||
// For WaitSynchronizationN: The "out" pointer
|
|
||||||
u32 outPointer;
|
|
||||||
|
|
||||||
// Thread context used for switching between threads
|
// Thread context used for switching between threads
|
||||||
std::array<u32, 16> gprs;
|
std::array<u32, 16> gprs;
|
||||||
std::array<u32, 32> fprs; // Stored as u32 because dynarmic does it
|
std::array<u32, 32> fprs; // Stored as u32 because dynarmic does it
|
||||||
u32 cpsr;
|
u32 cpsr;
|
||||||
u32 fpscr;
|
u32 fpscr;
|
||||||
u32 tlsBase; // Base pointer for thread-local storage
|
u32 tlsBase; // Base pointer for thread-local storage
|
||||||
|
|
||||||
// A list of threads waiting for this thread to terminate. Yes, threads are sync objects too.
|
// A list of threads waiting for this thread to terminate. Yes, threads are sync objects too.
|
||||||
u64 threadsWaitingForTermination;
|
u64 threadsWaitingForTermination;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const char* kernelObjectTypeToString(KernelObjectType t) {
|
static const char* kernelObjectTypeToString(KernelObjectType t) {
|
||||||
|
|
|
@ -126,8 +126,7 @@ void Kernel::waitSynchronization1() {
|
||||||
auto& t = threads[currentThreadIndex];
|
auto& t = threads[currentThreadIndex];
|
||||||
t.waitList.resize(1);
|
t.waitList.resize(1);
|
||||||
t.status = ThreadStatus::WaitSync1;
|
t.status = ThreadStatus::WaitSync1;
|
||||||
t.sleepTick = cpu.getTicks();
|
t.wakeupTick = getWakeupTick(ns);
|
||||||
t.waitingNanoseconds = ns;
|
|
||||||
t.waitList[0] = handle;
|
t.waitList[0] = handle;
|
||||||
|
|
||||||
// Add the current thread to the object's wait list
|
// Add the current thread to the object's wait list
|
||||||
|
@ -220,8 +219,7 @@ void Kernel::waitSynchronizationN() {
|
||||||
t.waitList.resize(handleCount);
|
t.waitList.resize(handleCount);
|
||||||
t.status = ThreadStatus::WaitSyncAny;
|
t.status = ThreadStatus::WaitSyncAny;
|
||||||
t.outPointer = outPointer;
|
t.outPointer = outPointer;
|
||||||
t.waitingNanoseconds = ns;
|
t.wakeupTick = getWakeupTick(ns);
|
||||||
t.sleepTick = cpu.getTicks();
|
|
||||||
|
|
||||||
for (s32 i = 0; i < handleCount; i++) {
|
for (s32 i = 0; i < handleCount; i++) {
|
||||||
t.waitList[i] = waitObjects[i].first; // Add object to this thread's waitlist
|
t.waitList[i] = waitObjects[i].first; // Add object to this thread's waitlist
|
||||||
|
|
|
@ -8,13 +8,6 @@
|
||||||
The code for our idle thread looks like this
|
The code for our idle thread looks like this
|
||||||
|
|
||||||
idle_thread_main:
|
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
|
// Sleep for 0 seconds with the SleepThread SVC, which just yields execution
|
||||||
mov r0, #0
|
mov r0, #0
|
||||||
mov r1, #0
|
mov r1, #0
|
||||||
|
@ -24,14 +17,10 @@ idle_thread_main:
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static constexpr u8 idleThreadCode[] = {
|
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, 0x00, 0xA0, 0xE3, // mov r0, #0
|
||||||
0x00, 0x10, 0xA0, 0xE3, // mov r1, #0
|
0x00, 0x10, 0xA0, 0xE3, // mov r1, #0
|
||||||
0x0A, 0x00, 0x00, 0xEF, // svc SleepThread
|
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
|
// Set up an idle thread to run when no thread is able to run
|
||||||
|
|
|
@ -52,14 +52,8 @@ bool Kernel::canThreadRun(const Thread& t) {
|
||||||
return true;
|
return true;
|
||||||
} else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1
|
} else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1
|
||||||
|| t.status == ThreadStatus::WaitSyncAny || t.status == ThreadStatus::WaitSyncAll) {
|
|| 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}
|
// TODO: Set r0 to the correct error code on timeout for WaitSync{1/Any/All}
|
||||||
const s64 elapsedNs = s64(double(elapsedTicks) * nsPerTick);
|
return cpu.getTicks() >= t.wakeupTick;
|
||||||
return elapsedNs >= t.waitingNanoseconds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle timeouts and stuff here
|
// Handle timeouts and stuff here
|
||||||
|
@ -82,6 +76,15 @@ std::optional<int> Kernel::getNextThread() {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<u64>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
return cpu.getTicks() + Scheduler::nsToCycles(ns);
|
||||||
|
}
|
||||||
|
|
||||||
// See if there is a higher priority, ready thread and switch to that
|
// See if there is a higher priority, ready thread and switch to that
|
||||||
void Kernel::rescheduleThreads() {
|
void Kernel::rescheduleThreads() {
|
||||||
Thread& current = threads[currentThreadIndex]; // Current running thread
|
Thread& current = threads[currentThreadIndex]; // Current running thread
|
||||||
|
@ -368,13 +371,30 @@ void Kernel::sleepThread(s64 ns) {
|
||||||
if (index != idleThreadIndex) {
|
if (index != idleThreadIndex) {
|
||||||
switchThread(index);
|
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<u64>(timestamp, t.wakeupTick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timestamp > scheduler.currentTimestamp) {
|
||||||
|
u64 idleCycles = timestamp - scheduler.currentTimestamp;
|
||||||
|
cpu.addTicks(idleCycles);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else { // If we're sleeping for >= 0 ns
|
} else { // If we're sleeping for >= 0 ns
|
||||||
Thread& t = threads[currentThreadIndex];
|
Thread& t = threads[currentThreadIndex];
|
||||||
|
|
||||||
t.status = ThreadStatus::WaitSleep;
|
t.status = ThreadStatus::WaitSleep;
|
||||||
t.waitingNanoseconds = ns;
|
t.wakeupTick = getWakeupTick(ns);
|
||||||
t.sleepTick = cpu.getTicks();
|
|
||||||
|
|
||||||
requireReschedule();
|
requireReschedule();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue