diff --git a/Ryujinx.Core/OsHle/Handles/KProcessScheduler.cs b/Ryujinx.Core/OsHle/Handles/KProcessScheduler.cs index 1c35b23c..a154b1c6 100644 --- a/Ryujinx.Core/OsHle/Handles/KProcessScheduler.cs +++ b/Ryujinx.Core/OsHle/Handles/KProcessScheduler.cs @@ -1,130 +1,16 @@ using Ryujinx.Core.Logging; using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading; namespace Ryujinx.Core.OsHle.Handles { class KProcessScheduler : IDisposable { - private const int LowestPriority = 0x40; - - private class SchedulerThread : IDisposable - { - public KThread Thread { get; private set; } - - public bool IsActive { get; set; } - - public AutoResetEvent WaitSync { get; private set; } - public ManualResetEvent WaitActivity { get; private set; } - public AutoResetEvent WaitSched { get; private set; } - - public SchedulerThread(KThread Thread) - { - this.Thread = Thread; - - IsActive = true; - - WaitSync = new AutoResetEvent(false); - - WaitActivity = new ManualResetEvent(true); - - WaitSched = new AutoResetEvent(false); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - WaitSync.Dispose(); - - WaitActivity.Dispose(); - - WaitSched.Dispose(); - } - } - } - - private class ThreadQueue - { - private List Threads; - - public ThreadQueue() - { - Threads = new List(); - } - - public void Push(SchedulerThread Thread) - { - lock (Threads) - { - Threads.Add(Thread); - } - } - - public SchedulerThread Pop(int MinPriority = LowestPriority) - { - lock (Threads) - { - SchedulerThread SchedThread; - - int HighestPriority = MinPriority; - - int HighestPrioIndex = -1; - - for (int Index = 0; Index < Threads.Count; Index++) - { - SchedThread = Threads[Index]; - - if (HighestPriority > SchedThread.Thread.ActualPriority) - { - HighestPriority = SchedThread.Thread.ActualPriority; - - HighestPrioIndex = Index; - } - } - - if (HighestPrioIndex == -1) - { - return null; - } - - SchedThread = Threads[HighestPrioIndex]; - - Threads.RemoveAt(HighestPrioIndex); - - return SchedThread; - } - } - - public bool HasThread(SchedulerThread SchedThread) - { - lock (Threads) - { - return Threads.Contains(SchedThread); - } - } - - public bool Remove(SchedulerThread SchedThread) - { - lock (Threads) - { - return Threads.Remove(SchedThread); - } - } - } - private ConcurrentDictionary AllThreads; - private ThreadQueue[] WaitingToRun; + private ThreadQueue WaitingToRun; - private HashSet ActiveProcessors; + private int ActiveCores; private object SchedLock; @@ -136,14 +22,7 @@ namespace Ryujinx.Core.OsHle.Handles AllThreads = new ConcurrentDictionary(); - WaitingToRun = new ThreadQueue[4]; - - for (int Index = 0; Index < 4; Index++) - { - WaitingToRun[Index] = new ThreadQueue(); - } - - ActiveProcessors = new HashSet(); + WaitingToRun = new ThreadQueue(); SchedLock = new object(); } @@ -159,7 +38,7 @@ namespace Ryujinx.Core.OsHle.Handles return; } - if (ActiveProcessors.Add(Thread.ProcessorId)) + if (AddActiveCore(Thread)) { Thread.Thread.Execute(); @@ -167,7 +46,7 @@ namespace Ryujinx.Core.OsHle.Handles } else { - WaitingToRun[Thread.ProcessorId].Push(SchedThread); + WaitingToRun.Push(SchedThread); PrintDbgThreadInfo(Thread, "waiting to run."); } @@ -182,18 +61,18 @@ namespace Ryujinx.Core.OsHle.Handles { if (AllThreads.TryRemove(Thread, out SchedulerThread SchedThread)) { - WaitingToRun[Thread.ProcessorId].Remove(SchedThread); + WaitingToRun.Remove(SchedThread); SchedThread.Dispose(); } - SchedulerThread NewThread = WaitingToRun[Thread.ProcessorId].Pop(); + SchedulerThread NewThread = WaitingToRun.Pop(Thread.ActualCore); if (NewThread == null) { - Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {Thread.ProcessorId}!"); + Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {Thread.ActualCore}!"); - ActiveProcessors.Remove(Thread.ProcessorId); + RemoveActiveCore(Thread.ActualCore); return; } @@ -228,7 +107,7 @@ namespace Ryujinx.Core.OsHle.Handles throw new InvalidOperationException(); } - Suspend(Thread.ProcessorId); + Suspend(Thread); SchedThread.WaitSync.WaitOne(); @@ -242,7 +121,7 @@ namespace Ryujinx.Core.OsHle.Handles throw new InvalidOperationException(); } - Suspend(Thread.ProcessorId); + Suspend(Thread); bool Result = SchedThread.WaitSync.WaitOne(Timeout); @@ -261,11 +140,13 @@ namespace Ryujinx.Core.OsHle.Handles SchedThread.WaitSync.Set(); } - public void Suspend(int ProcessorId) + public void Suspend(KThread Thread) { lock (SchedLock) { - SchedulerThread SchedThread = WaitingToRun[ProcessorId].Pop(); + PrintDbgThreadInfo(Thread, "suspended."); + + SchedulerThread SchedThread = WaitingToRun.Pop(Thread.ActualCore); if (SchedThread != null) { @@ -273,9 +154,9 @@ namespace Ryujinx.Core.OsHle.Handles } else { - Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {ProcessorId}!"); + Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {Thread.ActualCore}!"); - ActiveProcessors.Remove(ProcessorId); + RemoveActiveCore(Thread.ActualCore); } } } @@ -288,7 +169,9 @@ namespace Ryujinx.Core.OsHle.Handles { lock (SchedLock) { - SchedulerThread SchedThread = WaitingToRun[Thread.ProcessorId].Pop(Thread.ActualPriority); + SchedulerThread SchedThread = WaitingToRun.Pop( + Thread.ActualCore, + Thread.ActualPriority); if (SchedThread == null) { @@ -307,7 +190,7 @@ namespace Ryujinx.Core.OsHle.Handles { //Just stop running the thread if it's not active, //and run whatever is waiting to run with the higuest priority. - Suspend(Thread.ProcessorId); + Suspend(Thread); } Resume(Thread); @@ -333,14 +216,14 @@ namespace Ryujinx.Core.OsHle.Handles lock (SchedLock) { - if (ActiveProcessors.Add(Thread.ProcessorId)) + if (AddActiveCore(Thread)) { PrintDbgThreadInfo(Thread, "resuming execution..."); return; } - WaitingToRun[Thread.ProcessorId].Push(SchedThread); + WaitingToRun.Push(SchedThread); PrintDbgThreadInfo(Thread, "entering wait state..."); } @@ -354,6 +237,8 @@ namespace Ryujinx.Core.OsHle.Handles { if (!SchedThread.Thread.Thread.Execute()) { + PrintDbgThreadInfo(SchedThread.Thread, "waked."); + SchedThread.WaitSched.Set(); } else @@ -362,6 +247,14 @@ namespace Ryujinx.Core.OsHle.Handles } } + public void Resort(KThread Thread) + { + if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread)) + { + WaitingToRun.Resort(SchedThread); + } + } + private bool IsActive(KThread Thread) { if (!AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread)) @@ -372,11 +265,62 @@ namespace Ryujinx.Core.OsHle.Handles return SchedThread.IsActive; } + private bool AddActiveCore(KThread Thread) + { + lock (SchedLock) + { + //First, try running it on Ideal Core. + int CoreMask = 1 << Thread.IdealCore; + + if ((ActiveCores & CoreMask) == 0) + { + ActiveCores |= CoreMask; + + Thread.ActualCore = Thread.IdealCore; + + return true; + } + + //If that fails, then try running on any core allowed by Core Mask. + CoreMask = Thread.CoreMask & ~ActiveCores; + + if (CoreMask != 0) + { + CoreMask &= -CoreMask; + + ActiveCores |= CoreMask; + + for (int Bit = 0; Bit < 32; Bit++) + { + if (((CoreMask >> Bit) & 1) != 0) + { + Thread.ActualCore = Bit; + + return true; + } + } + + throw new InvalidOperationException(); + } + + return false; + } + } + + private void RemoveActiveCore(int Core) + { + lock (SchedLock) + { + ActiveCores &= ~(1 << Core); + } + } + private void PrintDbgThreadInfo(KThread Thread, string Message) { Log.PrintDebug(LogClass.KernelScheduler, "(" + "ThreadId = " + Thread.ThreadId + ", " + - "ProcessorId = " + Thread.ProcessorId + ", " + + "ActualCore = " + Thread.ActualCore + ", " + + "IdealCore = " + Thread.IdealCore + ", " + "ActualPriority = " + Thread.ActualPriority + ", " + "WantedPriority = " + Thread.WantedPriority + ") " + Message); } diff --git a/Ryujinx.Core/OsHle/Handles/KThread.cs b/Ryujinx.Core/OsHle/Handles/KThread.cs index 2084c2ba..1a044665 100644 --- a/Ryujinx.Core/OsHle/Handles/KThread.cs +++ b/Ryujinx.Core/OsHle/Handles/KThread.cs @@ -7,9 +7,13 @@ namespace Ryujinx.Core.OsHle.Handles { public AThread Thread { get; private set; } + public int CoreMask { get; set; } + public long MutexAddress { get; set; } public long CondVarAddress { get; set; } + private Process Process; + public KThread NextMutexThread { get; set; } public KThread NextCondVarThread { get; set; } @@ -18,16 +22,24 @@ namespace Ryujinx.Core.OsHle.Handles public int ActualPriority { get; private set; } public int WantedPriority { get; private set; } - public int ProcessorId { get; private set; } + public int IdealCore { get; private set; } + public int ActualCore { get; set; } public int WaitHandle { get; set; } public int ThreadId => Thread.ThreadId; - public KThread(AThread Thread, int ProcessorId, int Priority) + public KThread( + AThread Thread, + Process Process, + int IdealCore, + int Priority) { - this.Thread = Thread; - this.ProcessorId = ProcessorId; + this.Thread = Thread; + this.Process = Process; + this.IdealCore = IdealCore; + + CoreMask = 1 << IdealCore; ActualPriority = WantedPriority = Priority; } @@ -54,59 +66,138 @@ namespace Ryujinx.Core.OsHle.Handles { ActualPriority = CurrPriority; - UpdateWaitList(); + UpdateWaitLists(); MutexOwner?.UpdatePriority(); } } - private void UpdateWaitList() + private void UpdateWaitLists() + { + UpdateMutexList(); + UpdateCondVarList(); + + Process.Scheduler.Resort(this); + } + + private void UpdateMutexList() { KThread OwnerThread = MutexOwner; - if (OwnerThread != null) + if (OwnerThread == null) { - //The MutexOwner field should only be non null when the thread is - //waiting for the lock, and the lock belongs to another thread. - if (OwnerThread == this) + return; + } + + //The MutexOwner field should only be non-null when the thread is + //waiting for the lock, and the lock belongs to another thread. + if (OwnerThread == this) + { + throw new InvalidOperationException(); + } + + lock (OwnerThread) + { + //Remove itself from the list. + KThread CurrThread = OwnerThread; + + while (CurrThread.NextMutexThread != null) { - throw new InvalidOperationException(); + if (CurrThread.NextMutexThread == this) + { + CurrThread.NextMutexThread = NextMutexThread; + + break; + } + + CurrThread = CurrThread.NextMutexThread; } - lock (OwnerThread) - { - //Remove itself from the list. - KThread CurrThread = OwnerThread; + //Re-add taking new priority into account. + CurrThread = OwnerThread; - while (CurrThread.NextMutexThread != null) + while (CurrThread.NextMutexThread != null) + { + if (CurrThread.NextMutexThread.ActualPriority > ActualPriority) { - if (CurrThread.NextMutexThread == this) + break; + } + + CurrThread = CurrThread.NextMutexThread; + } + + NextMutexThread = CurrThread.NextMutexThread; + + CurrThread.NextMutexThread = this; + } + } + + private void UpdateCondVarList() + { + lock (Process.ThreadArbiterListLock) + { + if (Process.ThreadArbiterListHead == null) + { + return; + } + + //Remove itself from the list. + bool Found; + + KThread CurrThread = Process.ThreadArbiterListHead; + + if (Found = (Process.ThreadArbiterListHead == this)) + { + Process.ThreadArbiterListHead = Process.ThreadArbiterListHead.NextCondVarThread; + } + else + { + while (CurrThread.NextCondVarThread != null) + { + if (CurrThread.NextCondVarThread == this) { - CurrThread.NextMutexThread = NextMutexThread; + CurrThread.NextCondVarThread = NextCondVarThread; + + Found = true; break; } - CurrThread = CurrThread.NextMutexThread; + CurrThread = CurrThread.NextCondVarThread; } - - //Re-add taking new priority into account. - CurrThread = OwnerThread; - - while (CurrThread.NextMutexThread != null) - { - if (CurrThread.NextMutexThread.ActualPriority < ActualPriority) - { - break; - } - - CurrThread = CurrThread.NextMutexThread; - } - - NextMutexThread = CurrThread.NextMutexThread; - - CurrThread.NextMutexThread = this; } + + if (!Found) + { + return; + } + + //Re-add taking new priority into account. + if (Process.ThreadArbiterListHead == null || + Process.ThreadArbiterListHead.ActualPriority > ActualPriority) + { + NextCondVarThread = Process.ThreadArbiterListHead; + + Process.ThreadArbiterListHead = this; + + return; + } + + CurrThread = Process.ThreadArbiterListHead; + + while (CurrThread.NextCondVarThread != null) + { + if (CurrThread.NextCondVarThread.ActualPriority > ActualPriority) + { + break; + } + + CurrThread = CurrThread.NextCondVarThread; + } + + NextCondVarThread = CurrThread.NextCondVarThread; + + CurrThread.NextCondVarThread = this; } } } diff --git a/Ryujinx.Core/OsHle/Handles/SchedulerThread.cs b/Ryujinx.Core/OsHle/Handles/SchedulerThread.cs new file mode 100644 index 00000000..4a8b4c09 --- /dev/null +++ b/Ryujinx.Core/OsHle/Handles/SchedulerThread.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading; + +namespace Ryujinx.Core.OsHle.Handles +{ + class SchedulerThread : IDisposable + { + public KThread Thread { get; private set; } + + public SchedulerThread Next { get; set; } + + public bool IsActive { get; set; } + + public AutoResetEvent WaitSync { get; private set; } + public ManualResetEvent WaitActivity { get; private set; } + public AutoResetEvent WaitSched { get; private set; } + + public SchedulerThread(KThread Thread) + { + this.Thread = Thread; + + IsActive = true; + + WaitSync = new AutoResetEvent(false); + + WaitActivity = new ManualResetEvent(true); + + WaitSched = new AutoResetEvent(false); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing) + { + WaitSync.Dispose(); + + WaitActivity.Dispose(); + + WaitSched.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Handles/ThreadQueue.cs b/Ryujinx.Core/OsHle/Handles/ThreadQueue.cs new file mode 100644 index 00000000..41fbd81e --- /dev/null +++ b/Ryujinx.Core/OsHle/Handles/ThreadQueue.cs @@ -0,0 +1,158 @@ +namespace Ryujinx.Core.OsHle.Handles +{ + class ThreadQueue + { + private const int LowestPriority = 0x40; + + private SchedulerThread Head; + + private object ListLock; + + public ThreadQueue() + { + ListLock = new object(); + } + + public void Push(SchedulerThread Wait) + { + lock (ListLock) + { + //Ensure that we're not creating circular references + //by adding a thread that is already on the list. + if (HasThread(Wait)) + { + return; + } + + if (Head == null || Head.Thread.ActualPriority > Wait.Thread.ActualPriority) + { + Wait.Next = Head; + + Head = Wait; + + return; + } + + SchedulerThread Curr = Head; + + while (Curr.Next != null) + { + if (Curr.Next.Thread.ActualPriority > Wait.Thread.ActualPriority) + { + break; + } + + Curr = Curr.Next; + } + + Wait.Next = Curr.Next; + Curr.Next = Wait; + } + } + + public SchedulerThread Pop(int Core, int MinPriority = LowestPriority) + { + lock (ListLock) + { + int CoreMask = 1 << Core; + + SchedulerThread Prev = null; + SchedulerThread Curr = Head; + + while (Curr != null) + { + KThread Thread = Curr.Thread; + + if (Thread.ActualPriority <= MinPriority && (Thread.CoreMask & CoreMask) != 0) + { + if (Prev != null) + { + Prev.Next = Curr.Next; + } + else + { + Head = Head.Next; + } + + break; + } + + Prev = Curr; + Curr = Curr.Next; + } + + return Curr; + } + } + + public bool Remove(SchedulerThread Thread) + { + lock (ListLock) + { + if (Head == null) + { + return false; + } + else if (Head == Thread) + { + Head = Head.Next; + + return true; + } + + SchedulerThread Prev = Head; + SchedulerThread Curr = Head.Next; + + while (Curr != null) + { + if (Curr == Thread) + { + Prev.Next = Curr.Next; + + return true; + } + + Prev = Curr; + Curr = Curr.Next; + } + + return false; + } + } + + public bool Resort(SchedulerThread Thread) + { + lock (ListLock) + { + if (Remove(Thread)) + { + Push(Thread); + + return true; + } + + return false; + } + } + + public bool HasThread(SchedulerThread Thread) + { + lock (ListLock) + { + SchedulerThread Curr = Head; + + while (Curr != null) + { + if (Curr == Thread) + { + return true; + } + + Curr = Curr.Next; + } + + return false; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Kernel/SvcHandler.cs b/Ryujinx.Core/OsHle/Kernel/SvcHandler.cs index be394c4b..bbbd0fb0 100644 --- a/Ryujinx.Core/OsHle/Kernel/SvcHandler.cs +++ b/Ryujinx.Core/OsHle/Kernel/SvcHandler.cs @@ -22,8 +22,6 @@ namespace Ryujinx.Core.OsHle.Kernel private ConcurrentDictionary SyncWaits; - private object CondVarLock; - private HashSet<(HSharedMem, long)> MappedSharedMems; private ulong CurrentHeapSize; @@ -80,8 +78,6 @@ namespace Ryujinx.Core.OsHle.Kernel SyncWaits = new ConcurrentDictionary(); - CondVarLock = new object(); - MappedSharedMems = new HashSet<(HSharedMem, long)>(); } diff --git a/Ryujinx.Core/OsHle/Kernel/SvcSystem.cs b/Ryujinx.Core/OsHle/Kernel/SvcSystem.cs index e5b080a8..24317bdf 100644 --- a/Ryujinx.Core/OsHle/Kernel/SvcSystem.cs +++ b/Ryujinx.Core/OsHle/Kernel/SvcSystem.cs @@ -131,7 +131,7 @@ namespace Ryujinx.Core.OsHle.Kernel Handles[HandlesCount] = WaitEvent; - Process.Scheduler.Suspend(CurrThread.ProcessorId); + Process.Scheduler.Suspend(CurrThread); int HandleIndex; @@ -237,7 +237,7 @@ namespace Ryujinx.Core.OsHle.Kernel if (Session != null) { - Process.Scheduler.Suspend(CurrThread.ProcessorId); + Process.Scheduler.Suspend(CurrThread); IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr); diff --git a/Ryujinx.Core/OsHle/Kernel/SvcThread.cs b/Ryujinx.Core/OsHle/Kernel/SvcThread.cs index ee45c02c..71f3347a 100644 --- a/Ryujinx.Core/OsHle/Kernel/SvcThread.cs +++ b/Ryujinx.Core/OsHle/Kernel/SvcThread.cs @@ -75,7 +75,7 @@ namespace Ryujinx.Core.OsHle.Kernel } else { - Process.Scheduler.Suspend(CurrThread.ProcessorId); + Process.Scheduler.Suspend(CurrThread); Thread.Sleep(NsTimeConverter.GetTimeMs(Ns)); @@ -132,7 +132,7 @@ namespace Ryujinx.Core.OsHle.Kernel private void SvcGetCurrentProcessorNumber(AThreadState ThreadState) { - ThreadState.X0 = (ulong)Process.GetThread(ThreadState.Tpidr).ProcessorId; + ThreadState.X0 = (ulong)Process.GetThread(ThreadState.Tpidr).ActualCore; } private void SvcGetThreadId(AThreadState ThreadState) diff --git a/Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs b/Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs index 57608cda..0ca2a5f9 100644 --- a/Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs +++ b/Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs @@ -260,17 +260,23 @@ namespace Ryujinx.Core.OsHle.Kernel WaitThread.MutexAddress = MutexAddress; WaitThread.CondVarAddress = CondVarAddress; - lock (CondVarLock) + lock (Process.ThreadArbiterListLock) { - KThread CurrThread = Process.ThreadArbiterList; + KThread CurrThread = Process.ThreadArbiterListHead; - if (CurrThread != null) + if (CurrThread == null || CurrThread.ActualPriority > WaitThread.ActualPriority) + { + WaitThread.NextCondVarThread = Process.ThreadArbiterListHead; + + Process.ThreadArbiterListHead = WaitThread; + } + else { bool DoInsert = CurrThread != WaitThread; while (CurrThread.NextCondVarThread != null) { - if (CurrThread.NextCondVarThread.ActualPriority < WaitThread.ActualPriority) + if (CurrThread.NextCondVarThread.ActualPriority > WaitThread.ActualPriority) { break; } @@ -293,10 +299,6 @@ namespace Ryujinx.Core.OsHle.Kernel CurrThread.NextCondVarThread = WaitThread; } } - else - { - Process.ThreadArbiterList = WaitThread; - } } Ns.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state..."); @@ -315,10 +317,10 @@ namespace Ryujinx.Core.OsHle.Kernel private void CondVarSignal(long CondVarAddress, int Count) { - lock (CondVarLock) + lock (Process.ThreadArbiterListLock) { KThread PrevThread = null; - KThread CurrThread = Process.ThreadArbiterList; + KThread CurrThread = Process.ThreadArbiterListHead; while (CurrThread != null && (Count == -1 || Count > 0)) { @@ -330,7 +332,7 @@ namespace Ryujinx.Core.OsHle.Kernel } else { - Process.ThreadArbiterList = CurrThread.NextCondVarThread; + Process.ThreadArbiterListHead = CurrThread.NextCondVarThread; } CurrThread.NextCondVarThread = null; @@ -401,7 +403,7 @@ namespace Ryujinx.Core.OsHle.Kernel return; } - if (CurrThread.NextMutexThread.ActualPriority < WaitThread.ActualPriority) + if (CurrThread.NextMutexThread.ActualPriority > WaitThread.ActualPriority) { break; } diff --git a/Ryujinx.Core/OsHle/Process.cs b/Ryujinx.Core/OsHle/Process.cs index 3ccbc016..8520b9ad 100644 --- a/Ryujinx.Core/OsHle/Process.cs +++ b/Ryujinx.Core/OsHle/Process.cs @@ -38,7 +38,9 @@ namespace Ryujinx.Core.OsHle public KProcessScheduler Scheduler { get; private set; } - public KThread ThreadArbiterList { get; set; } + public KThread ThreadArbiterListHead { get; set; } + + public object ThreadArbiterListLock { get; private set; } public KProcessHandleTable HandleTable { get; private set; } @@ -70,6 +72,8 @@ namespace Ryujinx.Core.OsHle Memory = new AMemory(); + ThreadArbiterListLock = new object(); + HandleTable = new KProcessHandleTable(); AppletState = new AppletStateMgr(); @@ -196,7 +200,7 @@ namespace Ryujinx.Core.OsHle AThread CpuThread = new AThread(GetTranslator(), Memory, EntryPoint); - KThread Thread = new KThread(CpuThread, ProcessorId, Priority); + KThread Thread = new KThread(CpuThread, this, ProcessorId, Priority); int Handle = HandleTable.OpenHandle(Thread);