mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-27 09:05:28 -04:00
refactor: from guest threads to EE scheduler
This commit is contained in:
@@ -17,11 +17,10 @@
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "ps2_log.h"
|
||||
#include "runtime/ps2_address.h"
|
||||
@@ -40,6 +39,8 @@ namespace ps2x::iop
|
||||
|
||||
class PS2IopHostAdapter;
|
||||
class PS2IopTransport;
|
||||
class EeScheduler;
|
||||
struct EeEvent;
|
||||
|
||||
enum PS2Exception
|
||||
{
|
||||
@@ -248,7 +249,6 @@ inline void ps2TraceGuestWrite(uint8_t *rdram,
|
||||
(void)valueHi;
|
||||
(void)op;
|
||||
(void)ctx;
|
||||
// TODO we dont need this anymore so on next release it will be deleted
|
||||
}
|
||||
|
||||
inline void ps2TraceGuestRangeWrite(uint8_t *rdram,
|
||||
@@ -262,7 +262,6 @@ inline void ps2TraceGuestRangeWrite(uint8_t *rdram,
|
||||
(void)size;
|
||||
(void)op;
|
||||
(void)ctx;
|
||||
// TODO we dont need this anymore so on next release it will be deleted
|
||||
}
|
||||
|
||||
class PS2Runtime
|
||||
@@ -321,46 +320,6 @@ public:
|
||||
SkipCallDebug = 3,
|
||||
};
|
||||
|
||||
class GuestExecutionScope
|
||||
{
|
||||
public:
|
||||
explicit GuestExecutionScope(PS2Runtime *runtime) noexcept;
|
||||
~GuestExecutionScope();
|
||||
|
||||
GuestExecutionScope(const GuestExecutionScope &) = delete;
|
||||
GuestExecutionScope &operator=(const GuestExecutionScope &) = delete;
|
||||
|
||||
private:
|
||||
PS2Runtime *m_runtime = nullptr;
|
||||
};
|
||||
|
||||
class GuestExecutionReleaseScope
|
||||
{
|
||||
public:
|
||||
explicit GuestExecutionReleaseScope(PS2Runtime *runtime) noexcept;
|
||||
~GuestExecutionReleaseScope();
|
||||
|
||||
GuestExecutionReleaseScope(const GuestExecutionReleaseScope &) = delete;
|
||||
GuestExecutionReleaseScope &operator=(const GuestExecutionReleaseScope &) = delete;
|
||||
|
||||
private:
|
||||
PS2Runtime *m_runtime = nullptr;
|
||||
uint32_t m_depth = 0u;
|
||||
};
|
||||
|
||||
class DeferredGuestYieldScope
|
||||
{
|
||||
public:
|
||||
explicit DeferredGuestYieldScope(bool &pendingOut) noexcept;
|
||||
~DeferredGuestYieldScope();
|
||||
|
||||
DeferredGuestYieldScope(const DeferredGuestYieldScope &) = delete;
|
||||
DeferredGuestYieldScope &operator=(const DeferredGuestYieldScope &) = delete;
|
||||
|
||||
private:
|
||||
bool &m_pendingOut;
|
||||
};
|
||||
|
||||
bool replaceFunction(uint32_t address, RecompiledFunction func);
|
||||
// TODO remove this later need to update all tests
|
||||
bool registerFunction(uint32_t address, RecompiledFunction func);
|
||||
@@ -413,31 +372,27 @@ public:
|
||||
uint32_t guestHeapLimit() const;
|
||||
uint32_t reserveAsyncCallbackStack(uint32_t size, uint32_t alignment = 16u);
|
||||
|
||||
void dispatchLoop(uint8_t *rdram, R5900Context *ctx);
|
||||
|
||||
void drainCompletedDmacHandlers(uint8_t *rdram);
|
||||
|
||||
bool shouldPreemptGuestExecution();
|
||||
void yieldGuestExecutionAfterWake();
|
||||
void waitForGuestExecutionHandoff();
|
||||
void waitForGuestExecutionHandoff(uint64_t baselineEpoch);
|
||||
uint64_t guestExecutionHandoffEpochSnapshot() const
|
||||
{
|
||||
return m_guestExecutionHandoffEpoch.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void requestStop();
|
||||
bool isStopRequested() const;
|
||||
|
||||
uint32_t guestExecutionWaiterCountForTesting() const
|
||||
{
|
||||
return m_guestExecutionWaiters.load(std::memory_order_acquire);
|
||||
}
|
||||
EeScheduler &eeScheduler();
|
||||
const EeScheduler &eeScheduler() const;
|
||||
void postEeEvent(EeEvent event);
|
||||
bool eeCheckpointDue() const noexcept;
|
||||
|
||||
uint64_t guestExecutionHandoffTimeouts() const
|
||||
struct EeExitHandlerRegistration
|
||||
{
|
||||
return m_guestExecutionHandoffTimeouts.load(std::memory_order_relaxed);
|
||||
}
|
||||
uint32_t function = 0;
|
||||
uint32_t argument = 0;
|
||||
};
|
||||
void addEeExitHandler(int threadId, uint32_t function, uint32_t argument);
|
||||
std::vector<EeExitHandlerRegistration> takeEeExitHandlers(int threadId);
|
||||
void removeEeExitHandlers(int threadId);
|
||||
bool findEeSyscallOverride(uint32_t syscallNumber, uint32_t &handler) const;
|
||||
void setEeSyscallOverride(uint8_t *rdram, uint32_t syscallNumber, uint32_t handler);
|
||||
void initializeEeKernelState(uint8_t *rdram);
|
||||
|
||||
uint8_t Load8(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr);
|
||||
uint16_t Load16(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr);
|
||||
@@ -502,12 +457,6 @@ private:
|
||||
uint32_t allocateGuestBlockLocked(uint32_t size, uint32_t alignment);
|
||||
void freeGuestBlockLocked(uint32_t guestAddr);
|
||||
void coalesceGuestHeapLocked();
|
||||
void enterGuestExecution();
|
||||
void leaveGuestExecution();
|
||||
uint32_t releaseGuestExecution();
|
||||
void reacquireGuestExecution(uint32_t depth);
|
||||
void markGuestExecutionAcquired();
|
||||
|
||||
void HandleIntegerOverflow(R5900Context *ctx);
|
||||
|
||||
[[nodiscard]] ps2x::iop::RpcAbi selectIopRpcAbi(const ps2x::iop::RpcAbiRequest &request) const;
|
||||
@@ -515,9 +464,8 @@ private:
|
||||
void notifyIopSifTransfer(uint8_t *rdram, const ps2x::iop::SifTransfer &transfer);
|
||||
void resetIop();
|
||||
|
||||
friend class GuestExecutionScope;
|
||||
friend class GuestExecutionReleaseScope;
|
||||
friend class PS2IopTransport;
|
||||
friend class EeScheduler;
|
||||
|
||||
private:
|
||||
PS2Memory m_memory;
|
||||
@@ -530,12 +478,11 @@ private:
|
||||
VU1Interpreter m_vu0;
|
||||
VU1Interpreter m_vu1;
|
||||
R5900Context m_cpuContext;
|
||||
mutable std::recursive_mutex m_guestExecutionMutex;
|
||||
mutable std::atomic<uint32_t> m_guestExecutionWaiters{0u};
|
||||
mutable std::mutex m_guestExecutionHandoffMutex;
|
||||
mutable std::condition_variable m_guestExecutionHandoffCv;
|
||||
std::atomic<uint64_t> m_guestExecutionHandoffEpoch{0u};
|
||||
std::atomic<uint64_t> m_guestExecutionHandoffTimeouts{0u};
|
||||
std::unique_ptr<EeScheduler> m_eeScheduler;
|
||||
mutable std::mutex m_eeKernelStateMutex;
|
||||
std::unordered_map<int, std::vector<EeExitHandlerRegistration>> m_eeExitHandlers;
|
||||
std::unordered_map<uint32_t, uint32_t> m_eeSyscallOverrides;
|
||||
std::unordered_set<uint32_t> m_eeSyscallMirrorAddresses;
|
||||
mutable std::mutex m_guestHeapMutex;
|
||||
mutable std::mutex m_asyncCallbackStackMutex;
|
||||
std::vector<GuestHeapBlock> m_guestHeapBlocks;
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
|
||||
std::string translatePs2Path(const char *ps2Path);
|
||||
|
||||
extern std::atomic<int> g_activeThreads;
|
||||
|
||||
inline std::mutex g_sys_fd_mutex;
|
||||
|
||||
namespace ps2_syscalls
|
||||
@@ -29,15 +27,10 @@ namespace ps2_syscalls
|
||||
|
||||
bool dispatchNumericSyscall(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void dispatchDmacHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, uint32_t cause);
|
||||
void initializeGuestKernelState(uint8_t *rdram);
|
||||
void initializeGuestKernelState(uint8_t *rdram, PS2Runtime *runtime);
|
||||
void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, uint32_t encodedSyscallId);
|
||||
void notifyRuntimeStop();
|
||||
void joinAllGuestHostThreads();
|
||||
void detachAllGuestHostThreads();
|
||||
void EnsureVSyncWorkerRunning(uint8_t *rdram, PS2Runtime *runtime);
|
||||
uint64_t GetCurrentVSyncTick();
|
||||
uint64_t WaitForNextVSyncTick(uint8_t *rdram, PS2Runtime *runtime);
|
||||
void WaitVSyncTick(uint8_t *rdram, PS2Runtime *runtime);
|
||||
uint64_t GetCurrentVSyncTick(PS2Runtime *runtime);
|
||||
void WaitVSyncTick(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, int fixedResult = -1);
|
||||
}
|
||||
|
||||
#endif // PS2_SYSCALLS_H
|
||||
|
||||
@@ -0,0 +1,435 @@
|
||||
#pragma once
|
||||
|
||||
#include "ps2_runtime.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
// This exception is the EE equivalent of a longjmp to the dispatcher. It is
|
||||
// not an error and must only be caught at EeScheduler::run().
|
||||
struct EeDispatcherTransfer final
|
||||
{
|
||||
};
|
||||
|
||||
enum class EeThreadStatus : uint8_t
|
||||
{
|
||||
Running,
|
||||
Ready,
|
||||
Waiting,
|
||||
WaitingSuspended,
|
||||
Suspended,
|
||||
Dormant,
|
||||
};
|
||||
|
||||
enum class EeWaitReason : uint8_t
|
||||
{
|
||||
None,
|
||||
Sleep,
|
||||
Semaphore,
|
||||
EventFlag,
|
||||
VSync,
|
||||
External,
|
||||
Mpeg,
|
||||
};
|
||||
|
||||
struct EeSemaphoreWait
|
||||
{
|
||||
int id = 0;
|
||||
};
|
||||
|
||||
struct EeEventFlagWait
|
||||
{
|
||||
int id = 0;
|
||||
uint32_t bits = 0;
|
||||
uint32_t mode = 0;
|
||||
uint32_t resultAddress = 0;
|
||||
};
|
||||
|
||||
struct EeVSyncWait
|
||||
{
|
||||
uint64_t afterTick = 0;
|
||||
int fixedResult = -1;
|
||||
};
|
||||
|
||||
struct EeExternalWait
|
||||
{
|
||||
uint32_t type = 0;
|
||||
uint64_t token = 0;
|
||||
};
|
||||
|
||||
using EeWaitPayload = std::variant<std::monostate,
|
||||
EeSemaphoreWait,
|
||||
EeEventFlagWait,
|
||||
EeVSyncWait,
|
||||
EeExternalWait>;
|
||||
|
||||
struct EeWaitState
|
||||
{
|
||||
EeWaitReason reason = EeWaitReason::None;
|
||||
EeWaitPayload payload{};
|
||||
std::function<void(R5900Context &)> completion;
|
||||
};
|
||||
|
||||
enum class GuestInvocationKind : uint8_t
|
||||
{
|
||||
Interrupt,
|
||||
Alarm,
|
||||
GsCallback,
|
||||
RpcCallback,
|
||||
SyscallOverride,
|
||||
ExitHandler,
|
||||
HleCall,
|
||||
};
|
||||
|
||||
struct GuestInvocation
|
||||
{
|
||||
GuestInvocationKind kind = GuestInvocationKind::Interrupt;
|
||||
uint64_t sequence = 0;
|
||||
uint64_t tag = 0;
|
||||
R5900Context context{};
|
||||
std::function<void(const R5900Context &, R5900Context &)> onComplete;
|
||||
};
|
||||
|
||||
struct GuestThread
|
||||
{
|
||||
int id = 0;
|
||||
R5900Context context{};
|
||||
uint32_t entry = 0;
|
||||
uint32_t stack = 0;
|
||||
uint32_t stackSize = 0;
|
||||
uint32_t gp = 0;
|
||||
uint32_t attr = 0;
|
||||
uint32_t option = 0;
|
||||
uint32_t arg = 0;
|
||||
int initialPriority = 0;
|
||||
int currentPriority = 0;
|
||||
EeThreadStatus status = EeThreadStatus::Dormant;
|
||||
int suspendCount = 0;
|
||||
uint32_t wakeupCount = 0;
|
||||
bool ownsStack = false;
|
||||
uint32_t tlsBase = 0;
|
||||
EeWaitState wait{};
|
||||
std::function<void(R5900Context &)> resumeCompletion;
|
||||
std::vector<GuestInvocation> invocations;
|
||||
|
||||
[[nodiscard]] R5900Context &activeContext()
|
||||
{
|
||||
return invocations.empty() ? context : invocations.back().context;
|
||||
}
|
||||
|
||||
[[nodiscard]] const R5900Context &activeContext() const
|
||||
{
|
||||
return invocations.empty() ? context : invocations.back().context;
|
||||
}
|
||||
};
|
||||
|
||||
struct EeSemaphore
|
||||
{
|
||||
int id = 0;
|
||||
int count = 0;
|
||||
int maxCount = 0;
|
||||
int initCount = 0;
|
||||
uint32_t attr = 0;
|
||||
uint32_t option = 0;
|
||||
std::deque<int> waiters;
|
||||
};
|
||||
|
||||
struct EeEventFlag
|
||||
{
|
||||
int id = 0;
|
||||
uint32_t attr = 0;
|
||||
uint32_t option = 0;
|
||||
uint32_t initBits = 0;
|
||||
uint32_t bits = 0;
|
||||
std::deque<int> waiters;
|
||||
};
|
||||
|
||||
struct EeAlarm
|
||||
{
|
||||
int id = 0;
|
||||
uint16_t ticks = 0;
|
||||
uint32_t handler = 0;
|
||||
uint32_t argument = 0;
|
||||
uint32_t gp = 0;
|
||||
uint32_t sp = 0;
|
||||
};
|
||||
|
||||
struct EeIrqHandler
|
||||
{
|
||||
int id = 0;
|
||||
uint32_t cause = 0;
|
||||
uint32_t handler = 0;
|
||||
uint32_t argument = 0;
|
||||
uint32_t gp = 0;
|
||||
uint32_t sp = 0;
|
||||
bool enabled = true;
|
||||
int order = 0;
|
||||
};
|
||||
|
||||
struct EeThreadSnapshot
|
||||
{
|
||||
int id = 0;
|
||||
uint32_t pc = 0;
|
||||
uint32_t entry = 0;
|
||||
uint32_t stack = 0;
|
||||
uint32_t stackSize = 0;
|
||||
uint32_t gp = 0;
|
||||
int initialPriority = 0;
|
||||
int currentPriority = 0;
|
||||
EeThreadStatus status = EeThreadStatus::Dormant;
|
||||
EeWaitReason waitReason = EeWaitReason::None;
|
||||
int waitId = 0;
|
||||
int suspendCount = 0;
|
||||
uint32_t wakeupCount = 0;
|
||||
};
|
||||
|
||||
struct EeSemaphoreSnapshot
|
||||
{
|
||||
int id = 0;
|
||||
int count = 0;
|
||||
int maxCount = 0;
|
||||
uint32_t waiters = 0;
|
||||
};
|
||||
|
||||
struct EeEventFlagSnapshot
|
||||
{
|
||||
int id = 0;
|
||||
uint32_t bits = 0;
|
||||
uint32_t initBits = 0;
|
||||
uint32_t attr = 0;
|
||||
uint32_t waiters = 0;
|
||||
};
|
||||
|
||||
struct EeKernelSnapshot
|
||||
{
|
||||
uint64_t sequence = 0;
|
||||
int runningThreadId = 0;
|
||||
std::vector<EeThreadSnapshot> threads;
|
||||
std::vector<EeSemaphoreSnapshot> semaphores;
|
||||
std::vector<EeEventFlagSnapshot> eventFlags;
|
||||
};
|
||||
|
||||
enum class EeEventType : uint8_t
|
||||
{
|
||||
Stop,
|
||||
VBlankStart,
|
||||
VBlankEnd,
|
||||
Dmac,
|
||||
ExternalWake,
|
||||
Alarm,
|
||||
};
|
||||
|
||||
struct EeEvent
|
||||
{
|
||||
EeEventType type = EeEventType::ExternalWake;
|
||||
uint32_t id = 0;
|
||||
uint64_t value = 0;
|
||||
};
|
||||
|
||||
struct EeThreadCreateParams
|
||||
{
|
||||
uint32_t attr = 0;
|
||||
uint32_t entry = 0;
|
||||
uint32_t stack = 0;
|
||||
uint32_t stackSize = 0;
|
||||
uint32_t gp = 0;
|
||||
int priority = 0;
|
||||
uint32_t option = 0;
|
||||
};
|
||||
|
||||
class EeScheduler
|
||||
{
|
||||
public:
|
||||
static constexpr int kMainThreadId = 1;
|
||||
static constexpr int kFirstThreadId = 2;
|
||||
static constexpr int kLastThreadId = 255;
|
||||
static constexpr int kPriorityCount = 128;
|
||||
|
||||
explicit EeScheduler(PS2Runtime &runtime);
|
||||
~EeScheduler();
|
||||
|
||||
EeScheduler(const EeScheduler &) = delete;
|
||||
EeScheduler &operator=(const EeScheduler &) = delete;
|
||||
|
||||
void reset(uint8_t *rdram, const R5900Context &mainContext);
|
||||
void run();
|
||||
void requestStop();
|
||||
void postEvent(EeEvent event);
|
||||
[[nodiscard]] bool checkpointDue() const noexcept;
|
||||
[[nodiscard]] bool isExecutingGuest() const noexcept;
|
||||
|
||||
// Kernel object API. All calls except postEvent/requestStop execute on the
|
||||
// EE executor and therefore need no host synchronization.
|
||||
int createThread(const EeThreadCreateParams ¶ms);
|
||||
int deleteThread(int id, uint32_t &ownedStack);
|
||||
int startThread(int id, uint32_t arg, const R5900Context &caller, bool interruptSafe);
|
||||
[[noreturn]] void exitCurrent(bool deleteThread);
|
||||
int terminateThread(int id, uint32_t &ownedStack, bool interruptSafe);
|
||||
int suspendThread(int id, bool interruptSafe);
|
||||
int resumeThread(int id, bool interruptSafe);
|
||||
void sleepCurrent();
|
||||
int wakeupThread(int id, bool interruptSafe);
|
||||
int cancelWakeup(int id);
|
||||
int changePriority(int id, int priority, bool interruptSafe, int &oldPriority);
|
||||
int rotateReadyQueue(int priority, bool interruptSafe);
|
||||
int releaseWait(int id, bool interruptSafe);
|
||||
void transferIfRequested(bool interruptSafe);
|
||||
|
||||
int createSemaphore(int initCount, int maxCount, uint32_t attr, uint32_t option);
|
||||
int deleteSemaphore(int id, bool interruptSafe);
|
||||
int signalSemaphore(int id, bool interruptSafe);
|
||||
int pollSemaphore(int id);
|
||||
void waitSemaphore(int id);
|
||||
|
||||
int createEventFlag(uint32_t initialBits, uint32_t attr, uint32_t option);
|
||||
int deleteEventFlag(int id, bool interruptSafe);
|
||||
int setEventFlag(int id, uint32_t bits, bool interruptSafe);
|
||||
int clearEventFlag(int id, uint32_t mask);
|
||||
int pollEventFlag(int id, uint32_t bits, uint32_t mode, uint32_t &observedBits);
|
||||
void waitEventFlag(int id, uint32_t bits, uint32_t mode, uint32_t resultAddress);
|
||||
|
||||
int setAlarm(uint16_t ticks, uint32_t handler, uint32_t argument, uint32_t gp, uint32_t sp);
|
||||
int cancelAlarm(int id);
|
||||
void queueInvocation(GuestInvocation invocation);
|
||||
[[noreturn]] void invokeCurrent(GuestInvocation invocation);
|
||||
[[noreturn]] void invokeCurrentSequence(std::vector<GuestInvocation> invocations);
|
||||
[[nodiscard]] bool hasInvocation(GuestInvocationKind kind, uint64_t tag) const;
|
||||
[[nodiscard]] uint32_t invocationStackTop();
|
||||
|
||||
int addIrqHandler(bool dmac,
|
||||
uint32_t cause,
|
||||
uint32_t handler,
|
||||
bool append,
|
||||
uint32_t argument,
|
||||
uint32_t gp,
|
||||
uint32_t sp);
|
||||
int removeIrqHandler(bool dmac, uint32_t cause, int id);
|
||||
int setIrqHandlerEnabled(bool dmac, int id, bool enabled);
|
||||
int setIrqCauseEnabled(bool dmac, uint32_t cause, bool enabled);
|
||||
void dispatchIrq(bool dmac, uint32_t cause);
|
||||
void setVSyncFlag(uint32_t flagAddress, uint32_t tickAddress);
|
||||
[[nodiscard]] uint64_t currentVSyncTick() const noexcept;
|
||||
uint32_t setGsVSyncCallback(uint32_t callback, uint32_t gp, uint32_t sp);
|
||||
|
||||
[[noreturn]] void waitVSync(uint64_t afterTick, int fixedResult = -1);
|
||||
void completeVSync(uint64_t tick);
|
||||
void completeExternalWait(uint32_t type, uint64_t token, int result);
|
||||
[[noreturn]] void waitExternal(EeWaitReason reason,
|
||||
uint32_t type,
|
||||
uint64_t token,
|
||||
std::function<void(R5900Context &)> completion = {});
|
||||
|
||||
[[nodiscard]] GuestThread *thread(int id);
|
||||
[[nodiscard]] const GuestThread *thread(int id) const;
|
||||
[[nodiscard]] EeSemaphore *semaphore(int id);
|
||||
[[nodiscard]] const EeSemaphore *semaphore(int id) const;
|
||||
[[nodiscard]] EeEventFlag *eventFlag(int id);
|
||||
[[nodiscard]] const EeEventFlag *eventFlag(int id) const;
|
||||
[[nodiscard]] GuestThread *currentThread();
|
||||
[[nodiscard]] const GuestThread *currentThread() const;
|
||||
[[nodiscard]] int currentThreadId() const noexcept;
|
||||
[[nodiscard]] R5900Context *currentContext();
|
||||
[[nodiscard]] uint8_t *rdram() const noexcept;
|
||||
|
||||
// Direct syscall tests use the same main-thread record without starting a
|
||||
// second executor. Production execution calls reset() before run().
|
||||
void bindMainContextForSyscall(R5900Context &ctx, uint8_t *rdram);
|
||||
|
||||
[[nodiscard]] EeKernelSnapshot snapshot() const;
|
||||
void publishSnapshot();
|
||||
|
||||
private:
|
||||
struct ScheduledEvent
|
||||
{
|
||||
std::chrono::steady_clock::time_point deadline{};
|
||||
EeEvent event{};
|
||||
uint64_t sequence = 0;
|
||||
};
|
||||
|
||||
void assertExecutor() const;
|
||||
[[nodiscard]] int allocateThreadId();
|
||||
GuestThread &acquireInvocationThread();
|
||||
void enqueueReady(GuestThread &thread, bool front = false);
|
||||
void removeReady(GuestThread &thread);
|
||||
[[nodiscard]] GuestThread *selectReady();
|
||||
void makeRunning(GuestThread &thread);
|
||||
void makeDormant(GuestThread &thread);
|
||||
void removeFromWaitObject(GuestThread &thread);
|
||||
[[noreturn]] void blockCurrent(EeWaitState wait);
|
||||
void makeReady(GuestThread &thread, int result, bool interruptSafe);
|
||||
void requestPreemptionIfHigher(const GuestThread &readyThread, bool interruptSafe);
|
||||
void applyPendingPreemption();
|
||||
void processPendingEvents();
|
||||
void processDueDeadlines();
|
||||
void processEvent(const EeEvent &event);
|
||||
void finishEventWaiters(EeEventFlag &flag, bool interruptSafe);
|
||||
[[nodiscard]] static bool eventCondition(uint32_t current, uint32_t requested, uint32_t mode);
|
||||
static int waitObjectId(const EeWaitState &wait);
|
||||
void writeGuestU32(uint32_t address, uint32_t value);
|
||||
void waitForEvent();
|
||||
void scheduleEvent(std::chrono::steady_clock::time_point deadline, EeEvent event);
|
||||
void updateNextDeadline();
|
||||
void copyMainContextToRuntime();
|
||||
|
||||
PS2Runtime &m_runtime;
|
||||
uint8_t *m_rdram = nullptr;
|
||||
std::array<std::deque<int>, kPriorityCount> m_readyQueues{};
|
||||
std::unordered_map<int, GuestThread> m_threads;
|
||||
std::unordered_map<int, EeSemaphore> m_semaphores;
|
||||
std::unordered_map<int, EeEventFlag> m_eventFlags;
|
||||
std::unordered_map<int, EeAlarm> m_alarms;
|
||||
std::unordered_map<int, EeIrqHandler> m_intcHandlers;
|
||||
std::unordered_map<int, EeIrqHandler> m_dmacHandlers;
|
||||
int m_nextThreadId = kFirstThreadId;
|
||||
int m_nextInvocationThreadId = -1;
|
||||
int m_nextSemaphoreId = 1;
|
||||
int m_nextEventFlagId = 1;
|
||||
int m_nextAlarmId = 1;
|
||||
int m_nextIntcHandlerId = 1;
|
||||
int m_nextDmacHandlerId = 1;
|
||||
int m_intcHeadOrder = 0;
|
||||
int m_intcTailOrder = 1000;
|
||||
int m_dmacHeadOrder = 0;
|
||||
int m_dmacTailOrder = 1000;
|
||||
uint32_t m_enabledIntcMask = 0xFFFFFFFFu;
|
||||
uint32_t m_enabledDmacMask = 0xFFFFFFFFu;
|
||||
int m_currentThreadId = 0;
|
||||
bool m_rescheduleRequested = false;
|
||||
bool m_insideInterrupt = false;
|
||||
std::thread::id m_executorThread{};
|
||||
std::atomic<bool> m_running{false};
|
||||
std::atomic<bool> m_guestExecuting{false};
|
||||
std::atomic<bool> m_stopRequested{false};
|
||||
std::atomic<bool> m_checkpointPending{false};
|
||||
|
||||
mutable std::mutex m_eventMutex;
|
||||
std::condition_variable m_eventCv;
|
||||
std::deque<EeEvent> m_events;
|
||||
std::vector<ScheduledEvent> m_deadlines;
|
||||
std::deque<GuestInvocation> m_pendingInvocations;
|
||||
uint64_t m_eventSequence = 0;
|
||||
uint64_t m_invocationSequence = 0;
|
||||
uint64_t m_vsyncTick = 0;
|
||||
uint32_t m_vsyncFlagAddress = 0;
|
||||
uint32_t m_vsyncTickAddress = 0;
|
||||
uint32_t m_gsVSyncCallback = 0;
|
||||
uint32_t m_gsVSyncCallbackGp = 0;
|
||||
uint32_t m_gsVSyncCallbackSp = 0;
|
||||
std::unordered_map<uint64_t, uint32_t> m_invocationStackTops;
|
||||
std::atomic<int64_t> m_nextDeadlineNanoseconds{0};
|
||||
|
||||
mutable std::mutex m_snapshotMutex;
|
||||
EeKernelSnapshot m_snapshot;
|
||||
uint64_t m_snapshotSequence = 0;
|
||||
};
|
||||
@@ -204,20 +204,16 @@ struct GSRegisters
|
||||
uint64_t extdata; // External data
|
||||
uint64_t extwrite; // External write
|
||||
uint64_t bgcolor; // Background color
|
||||
// Status. Concurrency contract: the vsync worker thread toggles the FIELD bit
|
||||
// (bit 13) once per tick; guest threads issue write-one-to-clear writes against
|
||||
// the SIGNAL/FINISH status bits (0..1) via the MMIO path; the GIF sets SIGNAL
|
||||
// and FINISH from yet another thread. All three interleave, so this register
|
||||
// must be updated with atomic RMWs only (no load-then-store pairs anywhere).
|
||||
// Status remains atomic because the renderer/UI can observe it while the
|
||||
// single EE executor updates SIGNAL, FINISH and FIELD.
|
||||
std::atomic<uint64_t> csr;
|
||||
std::atomic<uint64_t> vsyncTick;
|
||||
uint64_t imr; // Interrupt mask
|
||||
uint64_t busdir; // Bus direction
|
||||
uint64_t siglblid; // Signal label ID
|
||||
};
|
||||
static_assert(sizeof(GSRegisters) == (19u * sizeof(uint64_t)), "GSRegisters layout changed unexpectedly");
|
||||
static_assert(sizeof(GSRegisters) == (20u * sizeof(uint64_t)), "GSRegisters layout changed unexpectedly");
|
||||
static_assert(alignof(GSRegisters) == alignof(uint64_t), "GSRegisters alignment must remain 64-bit");
|
||||
// CSR is written by the vsync worker while guest threads concurrently read/write it
|
||||
// (MMIO) and the GIF sets SIGNAL/FINISH; a lock-free atomic keeps that path wait-free.
|
||||
static_assert(std::atomic<uint64_t>::is_always_lock_free, "GS CSR atomic must be lock-free on all supported targets");
|
||||
|
||||
// PS2 VIF (VPU Interface) registers
|
||||
|
||||
Reference in New Issue
Block a user