Files
PS2Recomp/ps2xRuntime/src/lib/Kernel/EeScheduler.cpp
T
Ran-j d91d2c1b75 feat: added EE clock Hz
fix: fix MPEG out of sync with new EE refactor
2026-08-07 21:54:33 -03:00

2039 lines
59 KiB
C++

#include "runtime/ee_scheduler.h"
#include "ps2_log.h"
#include "ps2_runtime_macros.h"
#include <algorithm>
#include <cassert>
#include <cstring>
#include <limits>
#include <stdexcept>
namespace
{
constexpr int KE_OK = 0;
constexpr int KE_ERROR = -1;
constexpr int KE_ILLEGAL_PRIORITY = -403;
constexpr int KE_ILLEGAL_THID = -406;
constexpr int KE_UNKNOWN_THID = -407;
constexpr int KE_UNKNOWN_SEMID = -408;
constexpr int KE_UNKNOWN_EVFID = -409;
constexpr int KE_DORMANT = -413;
constexpr int KE_NOT_DORMANT = -414;
constexpr int KE_NOT_SUSPEND = -415;
constexpr int KE_NOT_WAIT = -416;
constexpr int KE_RELEASE_WAIT = -418;
constexpr int KE_SEMA_ZERO = -419;
constexpr int KE_SEMA_OVF = -420;
constexpr int KE_EVF_COND = -421;
constexpr int KE_WAIT_DELETE = -425;
constexpr uint32_t WEF_OR = 0x01u;
constexpr uint32_t WEF_CLEAR = 0x10u;
constexpr uint32_t WEF_CLEAR_ALL = 0x20u;
constexpr auto kVBlankPeriod = std::chrono::microseconds(16667);
constexpr auto kVBlankDuration = std::chrono::microseconds(500);
constexpr uint64_t kAlarmTickMicroseconds = 64u;
constexpr uint32_t kDebugPublishDispatchInterval = 4096u;
constexpr uint64_t microsecondsToEeCycles(uint64_t microseconds)
{
return (microseconds * EeScheduler::kEeClockHz + 999999ull) / 1000000ull;
}
constexpr uint64_t kVBlankPeriodCycles = microsecondsToEeCycles(16667u);
constexpr uint64_t kVBlankDurationCycles = microsecondsToEeCycles(500u);
constexpr uint64_t kAlarmTickCycles = microsecondsToEeCycles(kAlarmTickMicroseconds);
template <typename Map>
int allocatePositiveId(int &nextId, const Map &objects)
{
const int first = std::max(1, nextId);
int candidate = first;
do
{
if (!objects.contains(candidate))
{
nextId = (candidate == std::numeric_limits<int>::max()) ? 1 : candidate + 1;
return candidate;
}
candidate = (candidate == std::numeric_limits<int>::max()) ? 1 : candidate + 1;
} while (candidate != first);
return 0;
}
}
EeScheduler::EeScheduler(PS2Runtime &runtime)
: m_runtime(runtime)
{
}
EeScheduler::~EeScheduler()
{
requestStop();
}
void EeScheduler::reset(uint8_t *rdram, const R5900Context &mainContext)
{
m_executorThread = std::this_thread::get_id();
m_rdram = rdram;
m_readyQueues = {};
m_threads.clear();
m_semaphores.clear();
m_eventFlags.clear();
m_alarms.clear();
m_intcHandlers.clear();
m_dmacHandlers.clear();
m_nextThreadId = kFirstThreadId;
m_nextInvocationThreadId = -1;
m_nextSemaphoreId = 1;
m_nextEventFlagId = 1;
m_nextAlarmId = 1;
m_nextIntcHandlerId = 1;
m_nextDmacHandlerId = 1;
m_intcHeadOrder = 0;
m_intcTailOrder = 1000;
m_dmacHeadOrder = 0;
m_dmacTailOrder = 1000;
m_enabledIntcMask = 0xFFFFFFFFu;
m_enabledDmacMask = 0xFFFFFFFFu;
m_currentThreadId = 0;
m_rescheduleRequested = false;
m_timeSliceExpired = false;
m_insideInterrupt = false;
m_eeCycle = 0u;
m_sliceEndCycle = kDefaultTimeSliceCycles;
m_stopRequested.store(false, std::memory_order_release);
m_checkpointPending.store(false, std::memory_order_release);
m_debugPublishCountdown = 0u;
{
std::lock_guard lock(m_eventMutex);
m_events.clear();
m_deadlines.clear();
m_pendingInvocations.clear();
}
m_eventSequence = 0;
m_invocationSequence = 0;
m_vsyncTick = 0;
m_vsyncFlagAddress = 0;
m_vsyncTickAddress = 0;
m_gsVSyncCallback = 0;
m_gsVSyncCallbackGp = 0;
m_gsVSyncCallbackSp = 0;
m_runtime.memory().gs().vsyncTick.store(0u, std::memory_order_release);
GuestThread main{};
main.id = kMainThreadId;
main.context = mainContext;
main.entry = mainContext.pc;
main.stack = getRegU32(&mainContext, 29);
main.gp = getRegU32(&mainContext, 28);
main.initialPriority = 0;
main.currentPriority = 0;
main.status = EeThreadStatus::Ready;
m_threads.emplace(main.id, std::move(main));
m_readyQueues[0].push_back(kMainThreadId);
scheduleEvent(m_eeCycle + kVBlankPeriodCycles,
std::chrono::steady_clock::now() + kVBlankPeriod,
EeEvent{EeEventType::VBlankStart, 0, 0});
publishSnapshot();
}
void EeScheduler::run()
{
assertExecutor();
m_running.store(true, std::memory_order_release);
while (!m_stopRequested.load(std::memory_order_acquire))
{
processPendingEvents();
if (m_stopRequested.load(std::memory_order_acquire))
{
break;
}
if (m_currentThreadId == 0)
{
GuestThread *next = selectReady();
if (!next && m_pendingInvocations.empty())
{
publishSnapshot();
waitForEvent();
continue;
}
if (next)
{
makeRunning(*next);
}
else
{
GuestThread *owner = &acquireInvocationThread();
GuestInvocation invocation = std::move(m_pendingInvocations.front());
m_pendingInvocations.pop_front();
owner->status = EeThreadStatus::Running;
m_currentThreadId = owner->id;
renewTimeSlice();
if (getRegU32(&invocation.context, 29) == 0u)
{
SET_GPR_U32(&invocation.context, 29, invocationStackTop());
}
owner->invocations.push_back(std::move(invocation));
}
}
GuestThread *running = currentThread();
assert(running != nullptr);
if (running->resumeCompletion)
{
auto completion = std::move(running->resumeCompletion);
running->resumeCompletion = {};
try
{
completion(running->activeContext());
}
catch (const EeDispatcherTransfer &)
{
}
if (m_currentThreadId == 0)
{
continue;
}
}
R5900Context &context = running->activeContext();
if (m_debugPublishCountdown == 0u)
{
copyMainContextToRuntime();
publishSnapshot();
m_debugPublishCountdown = kDebugPublishDispatchInterval - 1u;
}
else
{
--m_debugPublishCountdown;
}
m_runtime.m_debugPc.store(context.pc, std::memory_order_relaxed);
m_runtime.m_debugRa.store(getRegU32(&context, 31), std::memory_order_relaxed);
m_runtime.m_debugSp.store(getRegU32(&context, 29), std::memory_order_relaxed);
m_runtime.m_debugGp.store(getRegU32(&context, 28), std::memory_order_relaxed);
if (context.pc == 0u)
{
if (!running->invocations.empty())
{
GuestInvocation completed = std::move(running->invocations.back());
running->invocations.pop_back();
if (completed.onComplete)
{
try
{
completed.onComplete(completed.context, running->activeContext());
}
catch (const EeDispatcherTransfer &)
{
}
}
continue;
}
makeDormant(*running);
m_currentThreadId = 0;
continue;
}
if (!m_pendingInvocations.empty())
{
GuestInvocation invocation = std::move(m_pendingInvocations.front());
m_pendingInvocations.pop_front();
if (getRegU32(&invocation.context, 29) == 0u)
{
SET_GPR_U32(&invocation.context, 29, invocationStackTop());
}
running->invocations.push_back(std::move(invocation));
continue;
}
if (!m_runtime.hasFunction(context.pc))
{
if (!running->invocations.empty())
{
context.pc = 0u;
}
else
{
m_runtime.reportMissingFunction(m_rdram,
&context,
context.pc,
context.pc,
PS2Runtime::GuestBranchKind::DirectJump,
"EE scheduler");
makeDormant(*running);
m_currentThreadId = 0;
}
continue;
}
PS2Runtime::RecompiledFunction function = m_runtime.lookupFunction(context.pc);
if (checkpointDue(kGuestDispatchCycles))
{
continue;
}
try
{
m_insideInterrupt = !running->invocations.empty() && running->invocations.back().kind == GuestInvocationKind::Interrupt;
m_guestExecuting.store(true, std::memory_order_release);
function(m_rdram, &context, &m_runtime);
m_guestExecuting.store(false, std::memory_order_release);
m_insideInterrupt = false;
}
catch (const EeDispatcherTransfer &)
{
m_guestExecuting.store(false, std::memory_order_release);
m_insideInterrupt = false;
}
catch (...)
{
m_guestExecuting.store(false, std::memory_order_release);
m_running.store(false, std::memory_order_release);
publishSnapshot();
throw;
}
processPendingEvents();
if (m_rescheduleRequested && m_currentThreadId != 0)
{
GuestThread *preempted = currentThread();
assert(preempted != nullptr);
enqueueReady(*preempted, !m_timeSliceExpired);
m_currentThreadId = 0;
m_rescheduleRequested = false;
m_timeSliceExpired = false;
}
}
m_guestExecuting.store(false, std::memory_order_release);
m_running.store(false, std::memory_order_release);
copyMainContextToRuntime();
publishSnapshot();
}
void EeScheduler::requestStop()
{
m_stopRequested.store(true, std::memory_order_release);
m_checkpointPending.store(true, std::memory_order_release);
m_eventCv.notify_all();
}
void EeScheduler::postEvent(EeEvent event)
{
if (event.type == EeEventType::Stop)
{
requestStop();
return;
}
{
std::lock_guard lock(m_eventMutex);
m_events.push_back(event);
m_checkpointPending.store(true, std::memory_order_release);
}
m_eventCv.notify_one();
}
bool EeScheduler::checkpointDue(uint32_t cycles) noexcept
{
accountCycles(cycles);
if (m_checkpointPending.load(std::memory_order_acquire) ||
m_stopRequested.load(std::memory_order_acquire))
{
return true;
}
const uint64_t nextEventCycle = m_nextDeadlineCycle.load(std::memory_order_acquire);
if (nextEventCycle != 0u && m_eeCycle >= nextEventCycle)
{
m_checkpointPending.store(true, std::memory_order_release);
return true;
}
if (m_eeCycle < m_sliceEndCycle)
{
return false;
}
const GuestThread *running = currentThread();
if (running != nullptr && hasReadyAtOrAbovePriority(running->currentPriority))
{
m_rescheduleRequested = true;
m_timeSliceExpired = true;
return true;
}
renewTimeSlice();
return false;
}
void EeScheduler::accountCycles(uint32_t cycles) noexcept
{
m_eeCycle += std::max<uint64_t>(1u, cycles);
}
bool EeScheduler::isExecutingGuest() const noexcept
{
return m_guestExecuting.load(std::memory_order_acquire);
}
int EeScheduler::createThread(const EeThreadCreateParams &params)
{
assertExecutor();
if (params.priority < 1 || params.priority >= kPriorityCount)
{
return KE_ILLEGAL_PRIORITY;
}
const int id = allocateThreadId();
if (id == 0)
{
return KE_ERROR;
}
GuestThread thread{};
thread.id = id;
thread.entry = params.entry;
thread.stack = params.stack;
thread.stackSize = params.stackSize;
thread.gp = params.gp;
thread.attr = params.attr;
thread.option = params.option;
thread.initialPriority = params.priority;
thread.currentPriority = params.priority;
thread.status = EeThreadStatus::Dormant;
m_threads.emplace(id, std::move(thread));
publishSnapshot();
return id;
}
int EeScheduler::deleteThread(int id, uint32_t &ownedStack)
{
assertExecutor();
ownedStack = 0;
if (id <= kMainThreadId)
{
return KE_ILLEGAL_THID;
}
auto it = m_threads.find(id);
if (it == m_threads.end())
{
return KE_UNKNOWN_THID;
}
if (it->second.status != EeThreadStatus::Dormant)
{
return KE_NOT_DORMANT;
}
if (it->second.ownsStack)
{
ownedStack = it->second.stack;
}
m_threads.erase(it);
publishSnapshot();
return KE_OK;
}
int EeScheduler::startThread(int id, uint32_t arg, const R5900Context &caller, bool interruptSafe)
{
assertExecutor();
GuestThread *target = thread(id);
if (!target)
{
return KE_UNKNOWN_THID;
}
if (target->status != EeThreadStatus::Dormant)
{
return KE_NOT_DORMANT;
}
target->context = R5900Context{};
target->context.pc = target->entry;
target->arg = arg;
target->suspendCount = 0;
target->wakeupCount = 0;
target->wait = {};
SET_GPR_U32(&target->context, 4, arg);
SET_GPR_U32(&target->context, 28, target->gp != 0u ? target->gp : getRegU32(&caller, 28));
const uint32_t stackTop = target->stack != 0u
? (target->stack + target->stackSize) & ~0xFu
: getRegU32(&caller, 29);
SET_GPR_U32(&target->context, 29, stackTop);
SET_GPR_U32(&target->context, 31, 0u);
enqueueReady(*target);
requestPreemptionIfHigher(*target, interruptSafe);
publishSnapshot();
return KE_OK;
}
[[noreturn]] void EeScheduler::exitCurrent(bool deleteThreadRecord)
{
assertExecutor();
GuestThread *exiting = currentThread();
assert(exiting != nullptr);
const int id = exiting->id;
const uint32_t ownedStack = deleteThreadRecord && exiting->ownsStack ? exiting->stack : 0u;
makeDormant(*exiting);
m_currentThreadId = 0;
if (deleteThreadRecord && id != kMainThreadId)
{
m_threads.erase(id);
}
if (ownedStack != 0u)
{
m_runtime.guestFree(ownedStack);
}
publishSnapshot();
throw EeDispatcherTransfer{};
}
int EeScheduler::terminateThread(int id, uint32_t &ownedStack, bool interruptSafe)
{
assertExecutor();
ownedStack = 0;
if (id == 0 || id == m_currentThreadId)
{
return KE_ILLEGAL_THID;
}
GuestThread *target = thread(id);
if (!target)
{
return KE_UNKNOWN_THID;
}
if (target->status == EeThreadStatus::Dormant)
{
return KE_DORMANT;
}
if (target->ownsStack)
{
ownedStack = target->stack;
target->ownsStack = false;
}
makeDormant(*target);
(void)interruptSafe;
publishSnapshot();
return KE_OK;
}
int EeScheduler::suspendThread(int id, bool interruptSafe)
{
assertExecutor();
if (id == 0)
{
id = m_currentThreadId;
}
GuestThread *target = thread(id);
if (!target)
{
return KE_UNKNOWN_THID;
}
if (target->status == EeThreadStatus::Dormant)
{
return KE_DORMANT;
}
++target->suspendCount;
switch (target->status)
{
case EeThreadStatus::Running:
target->status = EeThreadStatus::Suspended;
m_currentThreadId = 0;
m_rescheduleRequested = true;
break;
case EeThreadStatus::Ready:
removeReady(*target);
target->status = EeThreadStatus::Suspended;
break;
case EeThreadStatus::Waiting:
target->status = EeThreadStatus::WaitingSuspended;
break;
case EeThreadStatus::WaitingSuspended:
case EeThreadStatus::Suspended:
break;
case EeThreadStatus::Dormant:
break;
}
if (interruptSafe && m_insideInterrupt)
{
m_rescheduleRequested = true;
}
publishSnapshot();
return KE_OK;
}
int EeScheduler::resumeThread(int id, bool interruptSafe)
{
assertExecutor();
if (id == 0)
{
return KE_ILLEGAL_THID;
}
GuestThread *target = thread(id);
if (!target)
{
return KE_UNKNOWN_THID;
}
if (target->suspendCount == 0)
{
return KE_NOT_SUSPEND;
}
--target->suspendCount;
if (target->suspendCount != 0)
{
return KE_OK;
}
if (target->status == EeThreadStatus::WaitingSuspended)
{
target->status = EeThreadStatus::Waiting;
}
else if (target->status == EeThreadStatus::Suspended)
{
enqueueReady(*target);
requestPreemptionIfHigher(*target, interruptSafe);
}
publishSnapshot();
return KE_OK;
}
void EeScheduler::sleepCurrent()
{
assertExecutor();
GuestThread *self = currentThread();
assert(self != nullptr);
if (self->wakeupCount != 0u)
{
--self->wakeupCount;
setReturnS32(&self->activeContext(), KE_OK);
return;
}
blockCurrent(EeWaitState{EeWaitReason::Sleep, std::monostate{}});
}
int EeScheduler::wakeupThread(int id, bool interruptSafe)
{
assertExecutor();
if (id == 0 || id == m_currentThreadId)
{
return KE_ILLEGAL_THID;
}
GuestThread *target = thread(id);
if (!target)
{
return KE_UNKNOWN_THID;
}
if (target->status == EeThreadStatus::Dormant)
{
return KE_DORMANT;
}
if ((target->status == EeThreadStatus::Waiting || target->status == EeThreadStatus::WaitingSuspended) &&
target->wait.reason == EeWaitReason::Sleep)
{
makeReady(*target, KE_OK, interruptSafe);
}
else
{
++target->wakeupCount;
}
publishSnapshot();
return KE_OK;
}
int EeScheduler::cancelWakeup(int id)
{
assertExecutor();
if (id == 0)
{
id = m_currentThreadId;
}
GuestThread *target = thread(id);
if (!target)
{
return KE_UNKNOWN_THID;
}
const int old = static_cast<int>(target->wakeupCount);
target->wakeupCount = 0;
publishSnapshot();
return old;
}
int EeScheduler::changePriority(int id, int priority, bool interruptSafe, int &oldPriority)
{
assertExecutor();
if (priority < 1 || priority >= kPriorityCount)
{
return KE_ILLEGAL_PRIORITY;
}
if (id == 0)
{
id = m_currentThreadId;
}
GuestThread *target = thread(id);
if (!target)
{
return KE_UNKNOWN_THID;
}
oldPriority = target->currentPriority;
if (target->status == EeThreadStatus::Ready)
{
removeReady(*target);
target->currentPriority = priority;
enqueueReady(*target);
requestPreemptionIfHigher(*target, interruptSafe);
}
else
{
target->currentPriority = priority;
if (target->status == EeThreadStatus::Running)
{
for (int p = 0; p < target->currentPriority; ++p)
{
if (!m_readyQueues[p].empty())
{
m_rescheduleRequested = true;
break;
}
}
}
}
publishSnapshot();
return KE_OK;
}
int EeScheduler::rotateReadyQueue(int priority, bool interruptSafe)
{
assertExecutor();
if (priority == 0)
{
const GuestThread *self = currentThread();
priority = self ? self->currentPriority : 0;
}
if (priority < 0 || priority >= kPriorityCount)
{
return KE_ILLEGAL_PRIORITY;
}
GuestThread *self = currentThread();
if (self && self->currentPriority == priority)
{
enqueueReady(*self);
m_currentThreadId = 0;
m_rescheduleRequested = true;
}
else
{
auto &queue = m_readyQueues[priority];
if (queue.size() > 1u)
{
const int head = queue.front();
queue.pop_front();
queue.push_back(head);
}
}
(void)interruptSafe;
publishSnapshot();
return KE_OK;
}
int EeScheduler::releaseWait(int id, bool interruptSafe)
{
assertExecutor();
if (id == 0)
{
return KE_ILLEGAL_THID;
}
GuestThread *target = thread(id);
if (!target)
{
return KE_UNKNOWN_THID;
}
if (target->status != EeThreadStatus::Waiting && target->status != EeThreadStatus::WaitingSuspended)
{
return KE_NOT_WAIT;
}
removeFromWaitObject(*target);
makeReady(*target, KE_RELEASE_WAIT, interruptSafe);
publishSnapshot();
return KE_OK;
}
void EeScheduler::transferIfRequested(bool interruptSafe)
{
assertExecutor();
if (interruptSafe || m_insideInterrupt || !m_rescheduleRequested)
{
return;
}
if (m_currentThreadId != 0)
{
GuestThread *self = currentThread();
assert(self != nullptr);
enqueueReady(*self, true);
m_currentThreadId = 0;
}
m_rescheduleRequested = false;
m_timeSliceExpired = false;
publishSnapshot();
throw EeDispatcherTransfer{};
}
int EeScheduler::createSemaphore(int initCount, int maxCount, uint32_t attr, uint32_t option)
{
assertExecutor();
if (maxCount <= 0 || initCount < 0 || initCount > maxCount)
{
return KE_ERROR;
}
const int id = allocatePositiveId(m_nextSemaphoreId, m_semaphores);
if (id == 0)
{
return KE_ERROR;
}
EeSemaphore semaphore{};
semaphore.id = id;
semaphore.count = initCount;
semaphore.maxCount = maxCount;
semaphore.initCount = initCount;
semaphore.attr = attr;
semaphore.option = option;
m_semaphores.emplace(id, std::move(semaphore));
publishSnapshot();
return id;
}
int EeScheduler::deleteSemaphore(int id, bool interruptSafe)
{
assertExecutor();
auto it = m_semaphores.find(id);
if (it == m_semaphores.end())
{
return KE_UNKNOWN_SEMID;
}
std::deque<int> waiters = std::move(it->second.waiters);
m_semaphores.erase(it);
for (const int threadId : waiters)
{
if (GuestThread *waiter = thread(threadId))
{
makeReady(*waiter, KE_WAIT_DELETE, interruptSafe);
}
}
publishSnapshot();
return id;
}
int EeScheduler::signalSemaphore(int id, bool interruptSafe)
{
assertExecutor();
EeSemaphore *object = semaphore(id);
if (!object)
{
return KE_UNKNOWN_SEMID;
}
if (!object->waiters.empty())
{
const int waiterId = object->waiters.front();
object->waiters.pop_front();
GuestThread *waiter = thread(waiterId);
assert(waiter != nullptr);
makeReady(*waiter, id, interruptSafe);
publishSnapshot();
return id;
}
if (object->count == object->maxCount)
{
return KE_SEMA_OVF;
}
++object->count;
publishSnapshot();
return id;
}
int EeScheduler::pollSemaphore(int id)
{
assertExecutor();
EeSemaphore *object = semaphore(id);
if (!object)
{
return KE_UNKNOWN_SEMID;
}
if (object->count == 0)
{
return KE_SEMA_ZERO;
}
--object->count;
publishSnapshot();
return id;
}
void EeScheduler::waitSemaphore(int id)
{
assertExecutor();
EeSemaphore *object = semaphore(id);
if (!object)
{
GuestThread *self = currentThread();
assert(self != nullptr);
setReturnS32(&self->activeContext(), KE_UNKNOWN_SEMID);
return;
}
if (object->count != 0)
{
--object->count;
GuestThread *self = currentThread();
assert(self != nullptr);
setReturnS32(&self->activeContext(), id);
publishSnapshot();
return;
}
GuestThread *self = currentThread();
assert(self != nullptr);
object->waiters.push_back(self->id);
blockCurrent(EeWaitState{EeWaitReason::Semaphore, EeSemaphoreWait{id}});
}
int EeScheduler::createEventFlag(uint32_t initialBits, uint32_t attr, uint32_t option)
{
assertExecutor();
const int id = allocatePositiveId(m_nextEventFlagId, m_eventFlags);
if (id == 0)
{
return KE_ERROR;
}
EeEventFlag flag{};
flag.id = id;
flag.attr = attr;
flag.option = option;
flag.initBits = initialBits;
flag.bits = initialBits;
m_eventFlags.emplace(id, std::move(flag));
publishSnapshot();
return id;
}
int EeScheduler::deleteEventFlag(int id, bool interruptSafe)
{
assertExecutor();
auto it = m_eventFlags.find(id);
if (it == m_eventFlags.end())
{
return KE_UNKNOWN_EVFID;
}
std::deque<int> waiters = std::move(it->second.waiters);
m_eventFlags.erase(it);
for (const int threadId : waiters)
{
if (GuestThread *waiter = thread(threadId))
{
makeReady(*waiter, KE_WAIT_DELETE, interruptSafe);
}
}
publishSnapshot();
return KE_OK;
}
int EeScheduler::setEventFlag(int id, uint32_t bits, bool interruptSafe)
{
assertExecutor();
EeEventFlag *flag = eventFlag(id);
if (!flag)
{
return KE_UNKNOWN_EVFID;
}
flag->bits |= bits;
finishEventWaiters(*flag, interruptSafe);
publishSnapshot();
return KE_OK;
}
int EeScheduler::clearEventFlag(int id, uint32_t mask)
{
assertExecutor();
EeEventFlag *flag = eventFlag(id);
if (!flag)
{
return KE_UNKNOWN_EVFID;
}
flag->bits &= mask;
publishSnapshot();
return KE_OK;
}
int EeScheduler::pollEventFlag(int id, uint32_t bits, uint32_t mode, uint32_t &observedBits)
{
assertExecutor();
EeEventFlag *flag = eventFlag(id);
if (!flag)
{
return KE_UNKNOWN_EVFID;
}
if (!eventCondition(flag->bits, bits, mode))
{
return KE_EVF_COND;
}
observedBits = flag->bits;
if ((mode & WEF_CLEAR_ALL) != 0u)
{
flag->bits = 0;
}
else if ((mode & WEF_CLEAR) != 0u)
{
flag->bits &= ~bits;
}
publishSnapshot();
return KE_OK;
}
void EeScheduler::waitEventFlag(int id, uint32_t bits, uint32_t mode, uint32_t resultAddress)
{
assertExecutor();
EeEventFlag *flag = eventFlag(id);
GuestThread *self = currentThread();
assert(self != nullptr);
if (!flag)
{
setReturnS32(&self->activeContext(), KE_UNKNOWN_EVFID);
return;
}
if (eventCondition(flag->bits, bits, mode))
{
const uint32_t observed = flag->bits;
writeGuestU32(resultAddress, observed);
if ((mode & WEF_CLEAR_ALL) != 0u)
{
flag->bits = 0;
}
else if ((mode & WEF_CLEAR) != 0u)
{
flag->bits &= ~bits;
}
setReturnS32(&self->activeContext(), KE_OK);
publishSnapshot();
return;
}
flag->waiters.push_back(self->id);
blockCurrent(EeWaitState{EeWaitReason::EventFlag,
EeEventFlagWait{id, bits, mode, resultAddress}});
}
int EeScheduler::setAlarm(uint16_t ticks,
uint32_t handler,
uint32_t argument,
uint32_t gp,
uint32_t sp)
{
assertExecutor();
if (handler == 0u || !m_runtime.hasFunction(handler))
{
return KE_ERROR;
}
const int id = allocatePositiveId(m_nextAlarmId, m_alarms);
if (id == 0)
{
return KE_ERROR;
}
m_alarms.emplace(id, EeAlarm{id, ticks, handler, argument, gp, sp});
const uint64_t tickCount = ticks == 0u ? 1u : static_cast<uint64_t>(ticks);
scheduleEvent(m_eeCycle + tickCount * kAlarmTickCycles,
std::chrono::steady_clock::now() + std::chrono::microseconds(tickCount * kAlarmTickMicroseconds),
EeEvent{EeEventType::Alarm, static_cast<uint32_t>(id), 0});
return id;
}
int EeScheduler::cancelAlarm(int id)
{
assertExecutor();
if (m_alarms.erase(id) == 0u)
{
return KE_ERROR;
}
{
std::lock_guard lock(m_eventMutex);
std::erase_if(m_deadlines, [id](const ScheduledEvent &scheduled)
{ return scheduled.event.type == EeEventType::Alarm &&
scheduled.event.id == static_cast<uint32_t>(id); });
updateNextDeadline();
}
return KE_OK;
}
void EeScheduler::queueInvocation(GuestInvocation invocation)
{
assertExecutor();
invocation.sequence = ++m_invocationSequence;
m_pendingInvocations.push_back(std::move(invocation));
m_checkpointPending.store(true, std::memory_order_release);
}
[[noreturn]] void EeScheduler::invokeCurrent(GuestInvocation invocation)
{
assertExecutor();
GuestThread *owner = currentThread();
assert(owner != nullptr);
if (getRegU32(&invocation.context, 29) == 0u)
{
SET_GPR_U32(&invocation.context, 29, invocationStackTop());
}
invocation.sequence = ++m_invocationSequence;
owner->invocations.push_back(std::move(invocation));
publishSnapshot();
throw EeDispatcherTransfer{};
}
[[noreturn]] void EeScheduler::invokeCurrentSequence(std::vector<GuestInvocation> invocations)
{
assertExecutor();
GuestThread *owner = currentThread();
assert(owner != nullptr);
assert(!invocations.empty());
for (auto it = invocations.rbegin(); it != invocations.rend(); ++it)
{
if (getRegU32(&it->context, 29) == 0u)
{
SET_GPR_U32(&it->context, 29, invocationStackTop());
}
it->sequence = ++m_invocationSequence;
owner->invocations.push_back(std::move(*it));
}
publishSnapshot();
throw EeDispatcherTransfer{};
}
bool EeScheduler::hasInvocation(GuestInvocationKind kind, uint64_t tag) const
{
const GuestThread *owner = currentThread();
if (!owner)
{
return false;
}
return std::any_of(owner->invocations.begin(), owner->invocations.end(),
[kind, tag](const GuestInvocation &invocation)
{
return invocation.kind == kind && invocation.tag == tag;
});
}
uint32_t EeScheduler::invocationStackTop()
{
assertExecutor();
const GuestThread *owner = currentThread();
if (!owner)
{
throw std::logic_error("EE invocation stack requested without a current guest context");
}
const size_t depth = owner ? owner->invocations.size() : 0u;
const uint64_t key = (static_cast<uint64_t>(static_cast<uint32_t>(owner->id)) << 32u) |
static_cast<uint32_t>(depth);
const auto existing = m_invocationStackTops.find(key);
if (existing != m_invocationStackTops.end())
{
return existing->second;
}
constexpr uint32_t kInvocationStackSize = 0x4000u;
const uint32_t top = m_runtime.reserveAsyncCallbackStack(kInvocationStackSize, 16u);
if (top == 0u)
{
throw std::runtime_error("EE invocation stack space exhausted");
}
m_invocationStackTops.emplace(key, top);
return top;
}
int EeScheduler::addIrqHandler(bool dmac,
uint32_t cause,
uint32_t handler,
bool append,
uint32_t argument,
uint32_t gp,
uint32_t sp)
{
assertExecutor();
auto &handlers = dmac ? m_dmacHandlers : m_intcHandlers;
int &nextId = dmac ? m_nextDmacHandlerId : m_nextIntcHandlerId;
const int id = allocatePositiveId(nextId, handlers);
if (id == 0)
{
return KE_ERROR;
}
int &head = dmac ? m_dmacHeadOrder : m_intcHeadOrder;
int &tail = dmac ? m_dmacTailOrder : m_intcTailOrder;
handlers.emplace(id,
EeIrqHandler{id,
cause,
handler,
argument,
gp,
sp,
true,
append ? ++tail : --head});
return id;
}
int EeScheduler::removeIrqHandler(bool dmac, uint32_t cause, int id)
{
assertExecutor();
auto &handlers = dmac ? m_dmacHandlers : m_intcHandlers;
auto it = handlers.find(id);
if (it != handlers.end() && it->second.cause == cause)
{
handlers.erase(it);
}
return KE_OK;
}
int EeScheduler::setIrqHandlerEnabled(bool dmac, int id, bool enabled)
{
assertExecutor();
auto &handlers = dmac ? m_dmacHandlers : m_intcHandlers;
auto it = handlers.find(id);
if (it != handlers.end())
{
it->second.enabled = enabled;
}
return KE_OK;
}
int EeScheduler::setIrqCauseEnabled(bool dmac, uint32_t cause, bool enabled)
{
assertExecutor();
if (cause < 32u)
{
uint32_t &mask = dmac ? m_enabledDmacMask : m_enabledIntcMask;
if (enabled)
{
mask |= 1u << cause;
}
else
{
mask &= ~(1u << cause);
}
}
return KE_OK;
}
void EeScheduler::dispatchIrq(bool dmac, uint32_t cause)
{
assertExecutor();
const uint32_t mask = dmac ? m_enabledDmacMask : m_enabledIntcMask;
if (cause < 32u && (mask & (1u << cause)) == 0u)
{
return;
}
const auto &handlers = dmac ? m_dmacHandlers : m_intcHandlers;
std::vector<EeIrqHandler> matching;
for (const auto &[id, handler] : handlers)
{
(void)id;
if (handler.enabled && handler.cause == cause && handler.handler != 0u &&
m_runtime.hasFunction(handler.handler))
{
matching.push_back(handler);
}
}
std::sort(matching.begin(), matching.end(), [](const EeIrqHandler &left, const EeIrqHandler &right)
{ return left.order < right.order; });
for (const EeIrqHandler &handler : matching)
{
GuestInvocation invocation{};
invocation.kind = GuestInvocationKind::Interrupt;
invocation.context.pc = handler.handler;
SET_GPR_U32(&invocation.context, 4, cause);
SET_GPR_U32(&invocation.context, 5, handler.argument);
SET_GPR_U32(&invocation.context, 28, handler.gp);
SET_GPR_U32(&invocation.context, 29, handler.sp);
SET_GPR_U32(&invocation.context, 31, 0u);
queueInvocation(std::move(invocation));
}
}
void EeScheduler::setVSyncFlag(uint32_t flagAddress, uint32_t tickAddress)
{
assertExecutor();
m_vsyncFlagAddress = flagAddress;
m_vsyncTickAddress = tickAddress;
writeGuestU32(flagAddress, 0u);
if (tickAddress != 0u)
{
const uint32_t physical = tickAddress & 0x1FFFFFFFu;
if (m_rdram && physical <= PS2_RAM_SIZE - sizeof(uint64_t))
{
const uint64_t zero = 0u;
std::memcpy(m_rdram + physical, &zero, sizeof(zero));
}
}
}
uint64_t EeScheduler::currentVSyncTick() const noexcept
{
return m_vsyncTick;
}
uint32_t EeScheduler::setGsVSyncCallback(uint32_t callback, uint32_t gp, uint32_t sp)
{
assertExecutor();
(void)sp;
const uint32_t previous = m_gsVSyncCallback;
m_gsVSyncCallback = callback;
m_gsVSyncCallbackGp = gp;
m_gsVSyncCallbackSp = 0u;
return previous;
}
[[noreturn]] void EeScheduler::waitVSync(uint64_t afterTick, int fixedResult, std::function<void(R5900Context &)> completion)
{
blockCurrent(EeWaitState{
EeWaitReason::VSync,
EeVSyncWait{afterTick, fixedResult},
std::move(completion)});
}
void EeScheduler::completeVSync(uint64_t tick)
{
assertExecutor();
std::vector<int> completed;
for (const auto &[id, candidate] : m_threads)
{
if ((candidate.status == EeThreadStatus::Waiting || candidate.status == EeThreadStatus::WaitingSuspended) &&
candidate.wait.reason == EeWaitReason::VSync &&
std::get<EeVSyncWait>(candidate.wait.payload).afterTick < tick)
{
completed.push_back(id);
}
}
std::sort(completed.begin(), completed.end());
for (const int id : completed)
{
GuestThread *waiter = thread(id);
assert(waiter != nullptr);
const EeVSyncWait wait = std::get<EeVSyncWait>(waiter->wait.payload);
const int result = wait.fixedResult >= 0
? wait.fixedResult
: static_cast<int>((tick - 1u) & 1u);
makeReady(*waiter, result, false);
}
publishSnapshot();
}
void EeScheduler::completeExternalWait(uint32_t type, uint64_t token, int result)
{
assertExecutor();
std::vector<int> completed;
for (const auto &[id, candidate] : m_threads)
{
if ((candidate.status != EeThreadStatus::Waiting && candidate.status != EeThreadStatus::WaitingSuspended) ||
(candidate.wait.reason != EeWaitReason::External &&
candidate.wait.reason != EeWaitReason::Mpeg))
{
continue;
}
const auto &external = std::get<EeExternalWait>(candidate.wait.payload);
if (external.type == type && external.token == token)
{
completed.push_back(id);
}
}
std::sort(completed.begin(), completed.end());
for (const int id : completed)
{
GuestThread *waiter = thread(id);
assert(waiter != nullptr);
makeReady(*waiter, result, false);
}
publishSnapshot();
}
[[noreturn]] void EeScheduler::waitExternal(EeWaitReason reason,
uint32_t type,
uint64_t token,
std::function<void(R5900Context &)> completion)
{
EeWaitState wait{reason, EeExternalWait{type, token}, std::move(completion)};
blockCurrent(std::move(wait));
}
GuestThread *EeScheduler::thread(int id)
{
auto it = m_threads.find(id);
return it == m_threads.end() ? nullptr : &it->second;
}
const GuestThread *EeScheduler::thread(int id) const
{
auto it = m_threads.find(id);
return it == m_threads.end() ? nullptr : &it->second;
}
EeSemaphore *EeScheduler::semaphore(int id)
{
auto it = m_semaphores.find(id);
return it == m_semaphores.end() ? nullptr : &it->second;
}
const EeSemaphore *EeScheduler::semaphore(int id) const
{
auto it = m_semaphores.find(id);
return it == m_semaphores.end() ? nullptr : &it->second;
}
EeEventFlag *EeScheduler::eventFlag(int id)
{
auto it = m_eventFlags.find(id);
return it == m_eventFlags.end() ? nullptr : &it->second;
}
const EeEventFlag *EeScheduler::eventFlag(int id) const
{
auto it = m_eventFlags.find(id);
return it == m_eventFlags.end() ? nullptr : &it->second;
}
GuestThread *EeScheduler::currentThread()
{
return thread(m_currentThreadId);
}
const GuestThread *EeScheduler::currentThread() const
{
return thread(m_currentThreadId);
}
int EeScheduler::currentThreadId() const noexcept
{
return m_currentThreadId;
}
R5900Context *EeScheduler::currentContext()
{
GuestThread *self = currentThread();
return self ? &self->activeContext() : nullptr;
}
uint8_t *EeScheduler::rdram() const noexcept
{
return m_rdram;
}
void EeScheduler::bindMainContextForSyscall(R5900Context &ctx, uint8_t *rdram)
{
if (m_executorThread == std::thread::id{})
{
reset(rdram, ctx);
GuestThread *main = selectReady();
assert(main != nullptr);
makeRunning(*main);
return;
}
assertExecutor();
m_rdram = rdram;
if (m_currentThreadId == 0)
{
GuestThread *main = thread(kMainThreadId);
assert(main != nullptr);
assert(main->status == EeThreadStatus::Ready);
removeReady(*main);
makeRunning(*main);
}
}
EeKernelSnapshot EeScheduler::snapshot() const
{
std::lock_guard lock(m_snapshotMutex);
return m_snapshot;
}
void EeScheduler::publishSnapshot()
{
EeKernelSnapshot next{};
next.sequence = ++m_snapshotSequence;
next.eeCycle = m_eeCycle;
next.sliceEndCycle = m_sliceEndCycle;
next.nextEventCycle = m_nextDeadlineCycle.load(std::memory_order_acquire);
next.runningThreadId = m_currentThreadId;
next.threads.reserve(m_threads.size());
for (const auto &[id, item] : m_threads)
{
if (id < 0)
{
continue;
}
EeThreadSnapshot snapshot{};
snapshot.id = id;
snapshot.pc = item.activeContext().pc;
snapshot.entry = item.entry;
snapshot.stack = item.stack;
snapshot.stackSize = item.stackSize;
snapshot.gp = item.gp;
snapshot.initialPriority = item.initialPriority;
snapshot.currentPriority = item.currentPriority;
snapshot.status = item.status;
snapshot.waitReason = item.wait.reason;
snapshot.waitId = waitObjectId(item.wait);
snapshot.suspendCount = item.suspendCount;
snapshot.wakeupCount = item.wakeupCount;
next.threads.push_back(snapshot);
}
std::sort(next.threads.begin(), next.threads.end(), [](const auto &left, const auto &right)
{ return left.id < right.id; });
next.semaphores.reserve(m_semaphores.size());
for (const auto &[id, item] : m_semaphores)
{
next.semaphores.push_back(EeSemaphoreSnapshot{id,
item.count,
item.maxCount,
static_cast<uint32_t>(item.waiters.size())});
}
std::sort(next.semaphores.begin(), next.semaphores.end(), [](const auto &left, const auto &right)
{ return left.id < right.id; });
next.eventFlags.reserve(m_eventFlags.size());
for (const auto &[id, item] : m_eventFlags)
{
next.eventFlags.push_back(EeEventFlagSnapshot{id,
item.bits,
item.initBits,
item.attr,
static_cast<uint32_t>(item.waiters.size())});
}
std::sort(next.eventFlags.begin(), next.eventFlags.end(), [](const auto &left, const auto &right)
{ return left.id < right.id; });
{
std::lock_guard lock(m_snapshotMutex);
m_snapshot = std::move(next);
}
}
void EeScheduler::assertExecutor() const
{
assert(m_executorThread == std::this_thread::get_id());
}
int EeScheduler::allocateThreadId()
{
for (int attempts = 0; attempts <= kLastThreadId - kFirstThreadId; ++attempts)
{
const int candidate = m_nextThreadId;
m_nextThreadId = candidate == kLastThreadId ? kFirstThreadId : candidate + 1;
if (!m_threads.contains(candidate))
{
return candidate;
}
}
return 0;
}
GuestThread &EeScheduler::acquireInvocationThread()
{
for (auto &[id, candidate] : m_threads)
{
if (id < 0 && candidate.status == EeThreadStatus::Dormant && candidate.invocations.empty())
{
return candidate;
}
}
GuestThread dispatcher{};
dispatcher.id = m_nextInvocationThreadId--;
dispatcher.initialPriority = 0;
dispatcher.currentPriority = 0;
dispatcher.status = EeThreadStatus::Dormant;
return m_threads.emplace(dispatcher.id, std::move(dispatcher)).first->second;
}
void EeScheduler::enqueueReady(GuestThread &item, bool front)
{
assert(item.currentPriority >= 0 && item.currentPriority < kPriorityCount);
item.status = EeThreadStatus::Ready;
auto &queue = m_readyQueues[item.currentPriority];
if (front)
{
queue.push_front(item.id);
}
else
{
queue.push_back(item.id);
}
}
void EeScheduler::removeReady(GuestThread &item)
{
if (item.status != EeThreadStatus::Ready)
{
return;
}
auto &queue = m_readyQueues[item.currentPriority];
auto it = std::find(queue.begin(), queue.end(), item.id);
assert(it != queue.end());
queue.erase(it);
}
GuestThread *EeScheduler::selectReady()
{
for (auto &queue : m_readyQueues)
{
if (queue.empty())
{
continue;
}
const int id = queue.front();
queue.pop_front();
GuestThread *selected = thread(id);
assert(selected != nullptr);
assert(selected->status == EeThreadStatus::Ready);
return selected;
}
return nullptr;
}
void EeScheduler::makeRunning(GuestThread &item)
{
assert(m_currentThreadId == 0);
assert(item.status == EeThreadStatus::Ready);
item.status = EeThreadStatus::Running;
m_currentThreadId = item.id;
renewTimeSlice();
}
void EeScheduler::makeDormant(GuestThread &item)
{
removeReady(item);
removeFromWaitObject(item);
item.status = EeThreadStatus::Dormant;
item.wait = {};
item.resumeCompletion = {};
item.suspendCount = 0;
item.wakeupCount = 0;
item.invocations.clear();
}
void EeScheduler::removeFromWaitObject(GuestThread &item)
{
const int id = item.id;
if (item.wait.reason == EeWaitReason::Semaphore)
{
const int objectId = std::get<EeSemaphoreWait>(item.wait.payload).id;
if (EeSemaphore *object = semaphore(objectId))
{
auto it = std::find(object->waiters.begin(), object->waiters.end(), id);
if (it != object->waiters.end())
{
object->waiters.erase(it);
}
}
}
else if (item.wait.reason == EeWaitReason::EventFlag)
{
const int objectId = std::get<EeEventFlagWait>(item.wait.payload).id;
if (EeEventFlag *object = eventFlag(objectId))
{
auto it = std::find(object->waiters.begin(), object->waiters.end(), id);
if (it != object->waiters.end())
{
object->waiters.erase(it);
}
}
}
item.wait = {};
}
void EeScheduler::blockCurrent(EeWaitState wait)
{
GuestThread *self = currentThread();
assert(self != nullptr);
self->wait = std::move(wait);
self->status = self->suspendCount == 0 ? EeThreadStatus::Waiting : EeThreadStatus::WaitingSuspended;
m_currentThreadId = 0;
publishSnapshot();
throw EeDispatcherTransfer{};
}
void EeScheduler::makeReady(GuestThread &item, int result, bool interruptSafe)
{
auto completion = std::move(item.wait.completion);
item.wait = {};
setReturnS32(&item.activeContext(), result);
item.resumeCompletion = std::move(completion);
if (item.suspendCount != 0)
{
item.status = EeThreadStatus::Suspended;
return;
}
enqueueReady(item);
requestPreemptionIfHigher(item, interruptSafe);
}
void EeScheduler::requestPreemptionIfHigher(const GuestThread &readyThread, bool interruptSafe)
{
const GuestThread *running = currentThread();
if (!running || readyThread.currentPriority >= running->currentPriority)
{
return;
}
m_rescheduleRequested = true;
if (interruptSafe || m_insideInterrupt)
{
m_checkpointPending.store(true, std::memory_order_release);
}
}
void EeScheduler::applyPendingPreemption()
{
if (!m_rescheduleRequested)
{
return;
}
if (m_currentThreadId == 0)
{
m_rescheduleRequested = false;
m_timeSliceExpired = false;
return;
}
GuestThread *self = currentThread();
assert(self != nullptr);
enqueueReady(*self, !m_timeSliceExpired);
m_currentThreadId = 0;
m_rescheduleRequested = false;
m_timeSliceExpired = false;
}
void EeScheduler::processPendingEvents()
{
assertExecutor();
processDueDeadlines();
std::deque<EeEvent> pending;
{
std::lock_guard lock(m_eventMutex);
pending.swap(m_events);
}
for (const EeEvent &event : pending)
{
processEvent(event);
}
{
std::lock_guard lock(m_eventMutex);
const uint64_t nextEventCycle = m_nextDeadlineCycle.load(std::memory_order_acquire);
const bool cycleEventDue = nextEventCycle != 0u && m_eeCycle >= nextEventCycle;
const bool pendingWork = !m_events.empty() || cycleEventDue || m_stopRequested.load(std::memory_order_acquire);
m_checkpointPending.store(pendingWork, std::memory_order_release);
}
applyPendingPreemption();
}
void EeScheduler::processDueDeadlines()
{
for (;;)
{
std::vector<ScheduledEvent> due;
std::chrono::steady_clock::time_point pacingDeadline{};
{
std::unique_lock lock(m_eventMutex);
const auto now = std::chrono::steady_clock::now();
for (const ScheduledEvent &item : m_deadlines)
{
if (item.deadlineCycle <= m_eeCycle &&
(pacingDeadline == std::chrono::steady_clock::time_point{} ||
item.hostDeadline < pacingDeadline))
{
pacingDeadline = item.hostDeadline;
}
}
if (pacingDeadline == std::chrono::steady_clock::time_point{})
{
updateNextDeadline();
return;
}
if (now < pacingDeadline)
{
m_eventCv.wait_until(lock, pacingDeadline, [this]()
{ return !m_events.empty() ||
m_stopRequested.load(std::memory_order_acquire); });
if (!m_events.empty() || m_stopRequested.load(std::memory_order_acquire))
{
updateNextDeadline();
return;
}
}
const auto pacedNow = std::chrono::steady_clock::now();
auto firstFuture = std::partition(m_deadlines.begin(), m_deadlines.end(),
[this, pacedNow](const ScheduledEvent &item)
{ return item.deadlineCycle <= m_eeCycle &&
item.hostDeadline <= pacedNow; });
due.insert(due.end(),
std::make_move_iterator(m_deadlines.begin()),
std::make_move_iterator(firstFuture));
m_deadlines.erase(m_deadlines.begin(), firstFuture);
updateNextDeadline();
}
std::sort(due.begin(), due.end(), [](const ScheduledEvent &left, const ScheduledEvent &right)
{
if (left.deadlineCycle != right.deadlineCycle)
{
return left.deadlineCycle < right.deadlineCycle;
}
if (left.event.type != right.event.type)
{
return left.event.type < right.event.type;
}
if (left.event.id != right.event.id)
{
return left.event.id < right.event.id;
}
return left.sequence < right.sequence; });
if (due.empty())
{
return;
}
for (ScheduledEvent &scheduled : due)
{
if (scheduled.event.type == EeEventType::VBlankStart)
{
scheduleEvent(scheduled.deadlineCycle + kVBlankDurationCycles,
scheduled.hostDeadline + kVBlankDuration,
EeEvent{EeEventType::VBlankEnd, 0, m_vsyncTick + 1u});
scheduleEvent(scheduled.deadlineCycle + kVBlankPeriodCycles,
scheduled.hostDeadline + kVBlankPeriod,
EeEvent{EeEventType::VBlankStart, 0, 0});
}
processEvent(scheduled.event);
}
}
}
void EeScheduler::processEvent(const EeEvent &event)
{
switch (event.type)
{
case EeEventType::Stop:
requestStop();
break;
case EeEventType::VBlankStart:
++m_vsyncTick;
m_runtime.memory().gs().vsyncTick.store(m_vsyncTick, std::memory_order_release);
if ((m_vsyncTick & 1u) != 0u)
{
m_runtime.memory().gs().csr.fetch_or(0x2000ull, std::memory_order_acq_rel);
}
else
{
m_runtime.memory().gs().csr.fetch_and(~0x2000ull, std::memory_order_acq_rel);
}
writeGuestU32(m_vsyncFlagAddress, 1u);
if (m_vsyncTickAddress != 0u)
{
const uint32_t physical = m_vsyncTickAddress & 0x1FFFFFFFu;
if (m_rdram && physical <= PS2_RAM_SIZE - sizeof(uint64_t))
{
std::memcpy(m_rdram + physical, &m_vsyncTick, sizeof(m_vsyncTick));
}
}
m_vsyncFlagAddress = 0u;
m_vsyncTickAddress = 0u;
completeVSync(m_vsyncTick);
if (m_gsVSyncCallback != 0u && m_runtime.hasFunction(m_gsVSyncCallback))
{
GuestInvocation invocation{};
invocation.kind = GuestInvocationKind::GsCallback;
invocation.context.pc = m_gsVSyncCallback;
SET_GPR_U32(&invocation.context, 4, static_cast<uint32_t>(m_vsyncTick));
SET_GPR_U32(&invocation.context, 28, m_gsVSyncCallbackGp);
SET_GPR_U32(&invocation.context, 29, m_gsVSyncCallbackSp);
SET_GPR_U32(&invocation.context, 31, 0u);
queueInvocation(std::move(invocation));
}
dispatchIrq(false, 2u);
break;
case EeEventType::ExternalWake:
completeExternalWait(event.id, event.value, KE_OK);
break;
case EeEventType::VBlankEnd:
dispatchIrq(false, 3u);
break;
case EeEventType::Dmac:
break;
case EeEventType::Alarm:
{
auto it = m_alarms.find(static_cast<int>(event.id));
if (it == m_alarms.end())
{
break;
}
const EeAlarm alarm = it->second;
m_alarms.erase(it);
GuestInvocation invocation{};
invocation.kind = GuestInvocationKind::Alarm;
invocation.context.pc = alarm.handler;
SET_GPR_U32(&invocation.context, 4, static_cast<uint32_t>(alarm.id));
SET_GPR_U32(&invocation.context, 5, static_cast<uint32_t>(alarm.ticks));
SET_GPR_U32(&invocation.context, 6, alarm.argument);
SET_GPR_U32(&invocation.context, 28, alarm.gp);
SET_GPR_U32(&invocation.context, 29, alarm.sp);
SET_GPR_U32(&invocation.context, 31, 0u);
queueInvocation(std::move(invocation));
break;
}
}
}
void EeScheduler::finishEventWaiters(EeEventFlag &flag, bool interruptSafe)
{
for (auto it = flag.waiters.begin(); it != flag.waiters.end();)
{
GuestThread *waiter = thread(*it);
assert(waiter != nullptr);
const EeEventFlagWait wait = std::get<EeEventFlagWait>(waiter->wait.payload);
if (!eventCondition(flag.bits, wait.bits, wait.mode))
{
++it;
continue;
}
const uint32_t observed = flag.bits;
writeGuestU32(wait.resultAddress, observed);
if ((wait.mode & WEF_CLEAR_ALL) != 0u)
{
flag.bits = 0;
}
else if ((wait.mode & WEF_CLEAR) != 0u)
{
flag.bits &= ~wait.bits;
}
it = flag.waiters.erase(it);
makeReady(*waiter, KE_OK, interruptSafe);
}
}
bool EeScheduler::eventCondition(uint32_t current, uint32_t requested, uint32_t mode)
{
return (mode & WEF_OR) != 0u ? (current & requested) != 0u
: (current & requested) == requested;
}
int EeScheduler::waitObjectId(const EeWaitState &wait)
{
switch (wait.reason)
{
case EeWaitReason::Semaphore:
return std::get<EeSemaphoreWait>(wait.payload).id;
case EeWaitReason::EventFlag:
return std::get<EeEventFlagWait>(wait.payload).id;
default:
return 0;
}
}
void EeScheduler::writeGuestU32(uint32_t address, uint32_t value)
{
if (address == 0u)
{
return;
}
const uint32_t physical = address & 0x1FFFFFFFu;
if (!m_rdram || physical > PS2_RAM_SIZE - sizeof(value))
{
return;
}
std::memcpy(m_rdram + physical, &value, sizeof(value));
}
void EeScheduler::waitForEvent()
{
std::unique_lock lock(m_eventMutex);
if (!m_events.empty() || m_stopRequested.load(std::memory_order_acquire))
{
return;
}
if (m_deadlines.empty())
{
m_eventCv.wait(lock, [this]()
{ return !m_events.empty() || m_stopRequested.load(std::memory_order_acquire); });
return;
}
const auto next = std::min_element(m_deadlines.begin(), m_deadlines.end(),
[](const ScheduledEvent &left, const ScheduledEvent &right)
{
if (left.deadlineCycle != right.deadlineCycle)
{
return left.deadlineCycle < right.deadlineCycle;
}
return left.sequence < right.sequence;
});
const uint64_t deadlineCycle = next->deadlineCycle;
const auto hostDeadline = next->hostDeadline;
const bool signaled = m_eventCv.wait_until(lock, hostDeadline, [this]()
{ return !m_events.empty() ||
m_stopRequested.load(std::memory_order_acquire); });
if (!signaled)
{
m_eeCycle = std::max(m_eeCycle, deadlineCycle);
m_checkpointPending.store(true, std::memory_order_release);
}
}
void EeScheduler::scheduleEvent(uint64_t deadlineCycle,
std::chrono::steady_clock::time_point hostDeadline,
EeEvent event)
{
{
std::lock_guard lock(m_eventMutex);
m_deadlines.push_back(ScheduledEvent{deadlineCycle, hostDeadline, event, ++m_eventSequence});
updateNextDeadline();
}
m_eventCv.notify_one();
}
void EeScheduler::updateNextDeadline()
{
if (m_deadlines.empty())
{
m_nextDeadlineCycle.store(0u, std::memory_order_release);
return;
}
const auto it = std::min_element(m_deadlines.begin(), m_deadlines.end(),
[](const ScheduledEvent &left, const ScheduledEvent &right)
{
if (left.deadlineCycle != right.deadlineCycle)
{
return left.deadlineCycle < right.deadlineCycle;
}
return left.sequence < right.sequence;
});
m_nextDeadlineCycle.store(it->deadlineCycle, std::memory_order_release);
}
bool EeScheduler::hasReadyAtOrAbovePriority(int priority) const
{
const int last = std::clamp(priority, 0, kPriorityCount - 1);
for (int p = 0; p <= last; ++p)
{
if (!m_readyQueues[static_cast<size_t>(p)].empty())
{
return true;
}
}
return false;
}
void EeScheduler::renewTimeSlice()
{
m_sliceEndCycle = m_eeCycle + kDefaultTimeSliceCycles;
m_timeSliceExpired = false;
}
void EeScheduler::copyMainContextToRuntime()
{
const GuestThread *main = thread(kMainThreadId);
if (main)
{
m_runtime.m_cpuContext = main->context;
}
}