Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thread scheduling fixes #722

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions include/kernel/kernel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class Kernel {
// Top 8 bits are the major version, bottom 8 are the minor version
u16 kernelVersion = 0;

u64 nextScheduledWakeupTick = std::numeric_limits<u64>::max();
// Shows whether a reschedule will be need
bool needReschedule = false;

Expand Down Expand Up @@ -94,6 +95,8 @@ class Kernel {
void signalArbiter(u32 waitingAddress, s32 threadCount);
void sleepThread(s64 ns);
void sleepThreadOnArbiter(u32 waitingAddress);
void sleepThreadOnArbiterWithTimeout(u32 waitingAddress, s64 timeoutNs);

void switchThread(int newThreadIndex);
void sortThreads();
std::optional<int> getNextThread();
Expand Down Expand Up @@ -214,6 +217,8 @@ class Kernel {
}
}

void addWakeupEvent(u64 tick);

Handle makeObject(KernelObjectType type) {
if (handleCounter > KernelHandles::Max) [[unlikely]] {
Helpers::panic("Hlep we somehow created enough kernel objects to overflow this thing");
Expand Down Expand Up @@ -253,5 +258,7 @@ class Kernel {
void sendGPUInterrupt(GPUInterrupt type) { serviceManager.sendGPUInterrupt(type); }
void clearInstructionCache();
void clearInstructionCacheRange(u32 start, u32 size);
void pollThreadWakeups();

u32 getSharedFontVaddr();
};
21 changes: 11 additions & 10 deletions include/kernel/kernel_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,17 @@ struct Session {
};

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
WaitArbiterTimeout, // Waiting on an address arbiter with timeout
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 {
Expand Down
7 changes: 4 additions & 3 deletions include/scheduler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ struct Scheduler {
VBlank = 0, // End of frame event
UpdateTimers = 1, // Update kernel timer objects
RunDSP = 2, // Make the emulated DSP run for one audio frame
SignalY2R = 3, // Signal that a Y2R conversion has finished
Panic = 4, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX)
ThreadWakeup = 3, // A thread is going to wake up and we need to reschedule threads
SignalY2R = 4, // Signal that a Y2R conversion has finished
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type 4 is duplicated?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoopsie

Panic = 5, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX)
TotalNumberOfEvents // How many event types do we have in total?
};
static constexpr usize totalNumberOfEvents = static_cast<usize>(EventType::TotalNumberOfEvents);
Expand Down Expand Up @@ -88,4 +89,4 @@ struct Scheduler {

return (arm11Clock * s64(ns)) / 1000000000;
}
};
};
11 changes: 10 additions & 1 deletion src/core/kernel/address_arbiter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ void Kernel::arbitrateAddress() {
break;
}

case ArbitrationType::WaitIfLessTimeout: {
s32 word = static_cast<s32>(mem.read32(address)); // Yes this is meant to be signed
if (word < value) {
sleepThreadOnArbiterWithTimeout(address, ns);
}
break;
}

case ArbitrationType::Signal:
signalArbiter(address, value);
break;
Expand All @@ -98,8 +106,9 @@ void Kernel::signalArbiter(u32 waitingAddress, s32 threadCount) {
// Wake threads with the highest priority threads being woken up first
for (auto index : threadIndices) {
Thread& t = threads[index];
if (t.status == ThreadStatus::WaitArbiter && t.waitingAddress == waitingAddress) {
if ((t.status == ThreadStatus::WaitArbiter || t.status == ThreadStatus::WaitArbiterTimeout) && t.waitingAddress == waitingAddress) {
t.status = ThreadStatus::Ready;
t.gprs[0] = Result::Success; // Return that the arbiter was actually signalled and that we didn't timeout
count += 1;

// Check if we've reached the max number of. If count < 0 then all threads are released.
Expand Down
2 changes: 2 additions & 0 deletions src/core/kernel/events.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ void Kernel::waitSynchronization1() {
// Add the current thread to the object's wait list
object->getWaitlist() |= (1ull << currentThreadIndex);

addWakeupEvent(t.wakeupTick);
requireReschedule();
}
}
Expand Down Expand Up @@ -231,6 +232,7 @@ void Kernel::waitSynchronizationN() {
waitObjects[i].second->getWaitlist() |= (1ull << currentThreadIndex); // And add the thread to the object's waitlist
}

addWakeupEvent(t.wakeupTick);
requireReschedule();
} else {
Helpers::panic("WaitSynchronizationN with waitAll");
Expand Down
8 changes: 6 additions & 2 deletions src/core/kernel/kernel.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#include <cassert>
#include "kernel.hpp"
#include "kernel_types.hpp"

#include <cassert>
#include <limits>

#include "cpu.hpp"
#include "kernel_types.hpp"

Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config)
: cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this, config) {
Expand Down Expand Up @@ -159,6 +162,7 @@ void Kernel::reset() {
threadIndices.clear();
serviceManager.reset();

nextScheduledWakeupTick = std::numeric_limits<u64>::max();
needReschedule = false;

// Allocate handle #0 to a dummy object and make a main process object
Expand Down
59 changes: 57 additions & 2 deletions src/core/kernel/threads.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include <algorithm>
#include <bit>
#include <cassert>
#include <cstring>
#include <limits>

#include "arm_defs.hpp"
#include "kernel.hpp"
Expand Down Expand Up @@ -50,8 +52,7 @@ void Kernel::sortThreads() {
bool Kernel::canThreadRun(const Thread& t) {
if (t.status == ThreadStatus::Ready) {
return true;
} else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1
|| t.status == ThreadStatus::WaitSyncAny || t.status == ThreadStatus::WaitSyncAll) {
} else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 || t.status == ThreadStatus::WaitSyncAny || t.status == ThreadStatus::WaitSyncAll || t.status == ThreadStatus::WaitArbiterTimeout) {
// TODO: Set r0 to the correct error code on timeout for WaitSync{1/Any/All}
return cpu.getTicks() >= t.wakeupTick;
}
Expand Down Expand Up @@ -216,6 +217,23 @@ void Kernel::sleepThreadOnArbiter(u32 waitingAddress) {
requireReschedule();
}

void Kernel::sleepThreadOnArbiterWithTimeout(u32 waitingAddress, s64 timeoutNs) {
regs[0] = Result::OS::Timeout; // This will be overwritten with success if we don't timeout

// Timeout is 0, don't bother waiting, instantly timeout
if (timeoutNs == 0) {
return;
}

Thread& t = threads[currentThreadIndex];
t.status = ThreadStatus::WaitArbiterTimeout;
t.waitingAddress = waitingAddress;
t.wakeupTick = getWakeupTick(timeoutNs);

addWakeupEvent(t.wakeupTick);
requireReschedule();
}

// Acquires an object that is **ready to be acquired** without waiting on it
void Kernel::acquireSyncObject(KernelObject* object, const Thread& thread) {
switch (object->type) {
Expand Down Expand Up @@ -396,6 +414,7 @@ void Kernel::sleepThread(s64 ns) {
t.status = ThreadStatus::WaitSleep;
t.wakeupTick = getWakeupTick(ns);

addWakeupEvent(t.wakeupTick);
requireReschedule();
}
}
Expand Down Expand Up @@ -697,3 +716,39 @@ bool Kernel::shouldWaitOnObject(KernelObject* object) {
return true;
}
}

void Kernel::pollThreadWakeups() {
rescheduleThreads();
bool haveSleepingThread = false;
u64 nextWakeupTick = std::numeric_limits<u64>::max();

for (auto index : threadIndices) {
const Thread& t = threads[index];

if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 || t.status == ThreadStatus::WaitSyncAny ||
t.status == ThreadStatus::WaitSyncAll || t.status == ThreadStatus::WaitArbiterTimeout) {
nextWakeupTick = std::min<u64>(nextWakeupTick, t.wakeupTick);
haveSleepingThread = true;
}
}

auto& scheduler = cpu.getScheduler();

if (haveSleepingThread && nextWakeupTick > scheduler.currentTimestamp) {
nextScheduledWakeupTick = nextWakeupTick;
scheduler.addEvent(Scheduler::EventType::ThreadWakeup, nextWakeupTick);
} else {
nextScheduledWakeupTick = std::numeric_limits<u64>::max();
}
}

void Kernel::addWakeupEvent(u64 tick) {
// We only need to queue the event if the tick of the wakeup is coming sooner than our next scheduled wakeup.
if (nextScheduledWakeupTick > tick) {
nextScheduledWakeupTick = tick;
auto& scheduler = cpu.getScheduler();

scheduler.removeEvent(Scheduler::EventType::ThreadWakeup);
scheduler.addEvent(Scheduler::EventType::ThreadWakeup, tick);
}
}
1 change: 1 addition & 0 deletions src/emulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ void Emulator::pollScheduler() {
break;
}

case Scheduler::EventType::ThreadWakeup: kernel.pollThreadWakeups(); break;
case Scheduler::EventType::UpdateTimers: kernel.pollTimers(); break;
case Scheduler::EventType::RunDSP: {
dsp->runAudioFrame(time);
Expand Down
Loading