diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs
index f13662e44..979806a18 100644
--- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs
+++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs
@@ -1,4 +1,5 @@
using ARMeilleure.State;
+using Ryujinx.Common.Logging;
using Ryujinx.Cpu.AppleHv.Arm;
using Ryujinx.Memory.Tracking;
using System;
@@ -17,9 +18,7 @@ namespace Ryujinx.Cpu.AppleHv
{
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
if (currentEl == (uint)ExceptionLevel.EL1h)
- {
return _impl.ElrEl1;
- }
return _impl.Pc;
}
}
@@ -69,9 +68,7 @@ namespace Ryujinx.Cpu.AppleHv
set
{
if (value)
- {
throw new NotSupportedException();
- }
}
}
@@ -82,11 +79,13 @@ namespace Ryujinx.Cpu.AppleHv
private readonly IHvExecutionContext _shadowContext;
private IHvExecutionContext _impl;
private int _shouldStep;
-
private readonly ExceptionCallbacks _exceptionCallbacks;
-
private int _interruptRequested;
+ // GPU Sync control
+ private int _syncCounter;
+ private int _strongSyncCounter;
+
public HvExecutionContext(ICounter counter, ExceptionCallbacks exceptionCallbacks)
{
_counter = counter;
@@ -108,38 +107,17 @@ namespace Ryujinx.Cpu.AppleHv
///
public void SetV(int index, V128 value) => _impl.SetV(index, value);
- private void InterruptHandler()
- {
- _exceptionCallbacks.InterruptCallback?.Invoke(this);
- }
-
- private void BreakHandler(ulong address, int imm)
- {
- _exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
- }
-
- private void StepHandler()
- {
- _exceptionCallbacks.StepCallback?.Invoke(this);
- }
-
- private void SupervisorCallHandler(ulong address, int imm)
- {
- _exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
- }
-
- private void UndefinedHandler(ulong address, int opCode)
- {
- _exceptionCallbacks.UndefinedCallback?.Invoke(this, address, opCode);
- }
+ private void InterruptHandler() => _exceptionCallbacks.InterruptCallback?.Invoke(this);
+ private void BreakHandler(ulong address, int imm) => _exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
+ private void StepHandler() => _exceptionCallbacks.StepCallback?.Invoke(this);
+ private void SupervisorCallHandler(ulong address, int imm) => _exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
+ private void UndefinedHandler(ulong address, int opCode) => _exceptionCallbacks.UndefinedCallback?.Invoke(this, address, opCode);
///
public void RequestInterrupt()
{
if (Interlocked.Exchange(ref _interruptRequested, 1) == 0 && _impl is HvExecutionContextVcpu impl)
- {
impl.RequestInterrupt();
- }
}
private bool GetAndClearInterruptRequested()
@@ -161,13 +139,9 @@ namespace Ryujinx.Cpu.AppleHv
{
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
if (currentEl == (uint)ExceptionLevel.EL1h)
- {
_impl.ElrEl1 = value;
- }
else
- {
_impl.Pc = value;
- }
}
}
@@ -181,9 +155,11 @@ namespace Ryujinx.Cpu.AppleHv
public unsafe void Execute(HvMemoryManager memoryManager, ulong address)
{
HvVcpu vcpu = HvVcpuPool.Instance.Create(memoryManager.AddressSpace, _shadowContext, SwapContext);
-
HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError();
+ _syncCounter = 0;
+ _strongSyncCounter = 0;
+
while (Running)
{
if (Interlocked.CompareExchange(ref _shouldStep, 0, 1) == 1)
@@ -192,16 +168,23 @@ namespace Ryujinx.Cpu.AppleHv
if (currentEl == (uint)ExceptionLevel.EL1h)
{
HvApi.hv_vcpu_get_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
- spsr |= (1 << 21);
+ spsr |= (1U << 21);
HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, spsr);
}
else
{
- Pstate |= (1 << 21);
+ Pstate |= (1U << 21);
}
HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.MDSCR_EL1, 1);
}
+ // Adaptive GPU synchronization to prevent 0 FPS
+ if (++_syncCounter % 12 == 0)
+ {
+ TryGpuSync();
+ _syncCounter = 0;
+ }
+
HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError();
HvExitReason reason = vcpu.ExitInfo->Reason;
@@ -212,9 +195,7 @@ namespace Ryujinx.Cpu.AppleHv
ExceptionClass hvEc = (ExceptionClass)(hvEsr >> 26);
if (hvEc != ExceptionClass.HvcAarch64)
- {
throw new Exception($"Unhandled exception from guest kernel with ESR 0x{hvEsr:X} ({hvEc}).");
- }
address = SynchronousException(memoryManager, ref vcpu);
HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError();
@@ -245,10 +226,31 @@ namespace Ryujinx.Cpu.AppleHv
HvVcpuPool.Instance.Destroy(vcpu, SwapContext);
}
+ // TryGpuSync() is called periodically in the main Execute() loop. The "syncing" value can be tuned based on gameplay results.
+ // This feature it to be followed-up and further completed in a future PR.
+ private void TryGpuSync()
+ {
+ try
+ {
+ Thread.Yield();
+
+ if (++_strongSyncCounter % 6 == 0)
+ {
+ Thread.Yield();
+ }
+ }
+ catch (Exception ex)
+ {
+ if (_strongSyncCounter % 100 == 0)
+ {
+ Logger.Warning?.Print(LogClass.Gpu, $"[AppleHv] GPU sync issue: {ex.Message}");
+ }
+ }
+ }
+
private ulong SynchronousException(HvMemoryManager memoryManager, ref HvVcpu vcpu)
{
ulong vcpuHandle = vcpu.Handle;
-
HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.ELR_EL1, out ulong elr).ThrowOnError();
HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.ESR_EL1, out ulong esr).ThrowOnError();
@@ -259,16 +261,20 @@ namespace Ryujinx.Cpu.AppleHv
case ExceptionClass.DataAbortLowerEl:
DataAbort(memoryManager.Tracking, vcpuHandle, (uint)esr);
break;
+
case ExceptionClass.TrappedMsrMrsSystem:
InstructionTrap((uint)esr);
HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.ELR_EL1, elr + 4UL).ThrowOnError();
break;
+
case ExceptionClass.SvcAarch64:
ReturnToPool(vcpu);
ushort id = (ushort)esr;
SupervisorCallHandler(elr - 4UL, id);
+ Thread.Yield(); // MoltenVK causes extremely frequent SVC exits, and HVF handles them in a busy loop. Hypervisor.Framework accelerates the guest CPU, and without periodic yielding/flushing, MoltenVK's presentation queue can starve, causing permanent 0 FPS deadlock.
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
break;
+
case ExceptionClass.SoftwareStepLowerEl:
HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
spsr &= ~((ulong)(1 << 21));
@@ -278,21 +284,23 @@ namespace Ryujinx.Cpu.AppleHv
StepHandler();
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
break;
+
case ExceptionClass.BrkAarch64:
ReturnToPool(vcpu);
BreakHandler(elr, (ushort)esr);
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
break;
+
default:
throw new Exception($"Unhandled guest exception {ec}.");
}
// Make sure we will continue running at EL0.
if (memoryManager.AddressSpace.GetAndClearUserTlbInvalidationPending())
- {
+
// TODO: Invalidate only the range that was modified?
return HvAddressSpace.KernelRegionTlbiEretAddress;
- }
+
return HvAddressSpace.KernelRegionEretAddress;
}
@@ -305,7 +313,6 @@ namespace Ryujinx.Cpu.AppleHv
if (farValid)
{
HvApi.hv_vcpu_get_sys_reg(vcpu, HvSysReg.FAR_EL1, out ulong far).ThrowOnError();
-
ulong size = 1UL << accessSizeLog2;
if (!tracking.VirtualMemoryEvent(far, size, write))
@@ -349,9 +356,7 @@ namespace Ryujinx.Cpu.AppleHv
private void WriteRt(uint rt, ulong value)
{
if (rt < 31)
- {
SetX((int)rt, value);
- }
}
private void ReturnToPool(HvVcpu vcpu)
@@ -369,8 +374,6 @@ namespace Ryujinx.Cpu.AppleHv
_impl = newContext;
}
- public void Dispose()
- {
- }
+ public void Dispose() { }
}
}
diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs
index 9ef03e61e..9f313f2f0 100644
--- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs
+++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs
@@ -1,5 +1,7 @@
using ARMeilleure.State;
+using Ryujinx.Common.Logging;
using Ryujinx.Memory;
+using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
@@ -14,8 +16,31 @@ namespace Ryujinx.Cpu.AppleHv
private static readonly SetSimdFpReg _setSimdFpReg;
private static readonly nint _setSimdFpRegNativePtr;
+ public static bool AggressiveMode { get; set; } = false;
+ private bool _earlyBootPhase = true;
+
public ulong ThreadUid { get; set; }
+ private readonly ulong[] _x = new ulong[32];
+ private readonly V128[] _v = new V128[32];
+
+ private ulong _pc;
+ private ulong _elrEl1;
+ private ulong _esrEl1;
+ private ulong _tpidrEl0;
+ private ulong _tpidrroEl0;
+ private ulong _fpcr;
+ private ulong _fpsr;
+ private ulong _pstateRaw;
+
+ private long _fallbackCount;
+ private long _lastWarningTicks;
+ private const long WarningCooldownTicks = 500_000_000; // 0.5 seconds
+
+ private readonly ulong _vcpu;
+ private int _interruptRequested;
+ private readonly object _registerLock = new object();
+
static HvExecutionContextVcpu()
{
// .NET does not support passing vectors by value, so we need to pass a pointer and use a native
@@ -33,155 +58,293 @@ namespace Ryujinx.Cpu.AppleHv
}
}
+ public HvExecutionContextVcpu(ulong vcpu)
+ {
+ _vcpu = vcpu;
+ Reset();
+ }
+
+ public void Reset()
+ {
+ lock (_registerLock)
+ {
+ _pstateRaw = 0x80000000UL;
+ _pc = 0;
+ _elrEl1 = 0;
+ _esrEl1 = 0;
+ _tpidrEl0 = 0;
+ _tpidrroEl0 = 0;
+ _fpcr = 0;
+ _fpsr = 0;
+
+ Array.Clear(_x, 0, _x.Length);
+ Array.Clear(_v, 0, _v.Length);
+
+ _fallbackCount = 0;
+ _lastWarningTicks = 0;
+ _interruptRequested = 0;
+ _earlyBootPhase = true;
+ }
+ }
+
+ private void LogHvWarning(string operation, string regName, string extra = "")
+ {
+ if (AggressiveMode) return;
+
+ long now = DateTime.UtcNow.Ticks;
+ if (now - _lastWarningTicks <= WarningCooldownTicks) return;
+
+ string msg = $"[AppleHv] BadArgument on {operation} {regName} | PC=0x{_pc:X16}";
+ if (!string.IsNullOrEmpty(extra)) msg += $" | {extra}";
+ msg += $" | Total: {Interlocked.Read(ref _fallbackCount)}";
+
+ Logger.Warning?.Print(LogClass.Cpu, msg);
+ _lastWarningTicks = now;
+ }
+
public ulong Pc
{
- get
- {
- HvApi.hv_vcpu_get_reg(_vcpu, HvReg.PC, out ulong pc).ThrowOnError();
- return pc;
- }
- set
- {
- HvApi.hv_vcpu_set_reg(_vcpu, HvReg.PC, value).ThrowOnError();
- }
+ get { lock (_registerLock) return GetRegCached(HvReg.PC, ref _pc, "PC"); }
+ set { lock (_registerLock) SetRegCached(HvReg.PC, value, ref _pc, "PC"); }
}
public ulong ElrEl1
{
- get
- {
- HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.ELR_EL1, out ulong elr).ThrowOnError();
- return elr;
- }
- set
- {
- HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.ELR_EL1, value).ThrowOnError();
- }
+ get { lock (_registerLock) return GetSysRegCached(HvSysReg.ELR_EL1, ref _elrEl1, "ELR_EL1"); }
+ set { lock (_registerLock) SetSysRegCached(HvSysReg.ELR_EL1, value, ref _elrEl1, "ELR_EL1"); }
}
public ulong EsrEl1
{
- get
- {
- HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.ESR_EL1, out ulong esr).ThrowOnError();
- return esr;
- }
- set
- {
- HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.ESR_EL1, value).ThrowOnError();
- }
+ get { lock (_registerLock) return GetSysRegCached(HvSysReg.ESR_EL1, ref _esrEl1, "ESR_EL1"); }
+ set { lock (_registerLock) SetSysRegCached(HvSysReg.ESR_EL1, value, ref _esrEl1, "ESR_EL1"); }
}
public long TpidrEl0
{
- get
- {
- HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.TPIDR_EL0, out ulong tpidrEl0).ThrowOnError();
- return (long)tpidrEl0;
- }
- set
- {
- HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.TPIDR_EL0, (ulong)value).ThrowOnError();
- }
+ get { lock (_registerLock) return (long)GetSysRegCached(HvSysReg.TPIDR_EL0, ref _tpidrEl0, "TPIDR_EL0"); }
+ set { lock (_registerLock) SetSysRegCached(HvSysReg.TPIDR_EL0, (ulong)value, ref _tpidrEl0, "TPIDR_EL0"); }
}
public long TpidrroEl0
{
- get
- {
- HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.TPIDRRO_EL0, out ulong tpidrroEl0).ThrowOnError();
- return (long)tpidrroEl0;
- }
- set
- {
- HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.TPIDRRO_EL0, (ulong)value).ThrowOnError();
- }
+ get { lock (_registerLock) return (long)GetSysRegCached(HvSysReg.TPIDRRO_EL0, ref _tpidrroEl0, "TPIDRRO_EL0"); }
+ set { lock (_registerLock) SetSysRegCached(HvSysReg.TPIDRRO_EL0, (ulong)value, ref _tpidrroEl0, "TPIDRRO_EL0"); }
}
public uint Pstate
{
get
{
- HvApi.hv_vcpu_get_reg(_vcpu, HvReg.CPSR, out ulong cpsr).ThrowOnError();
- return (uint)cpsr;
+ lock (_registerLock)
+ {
+ HvResult res = HvApi.hv_vcpu_get_reg(_vcpu, HvReg.CPSR, out ulong val);
+ if (res == HvResult.BadArgument)
+ {
+ Interlocked.Increment(ref _fallbackCount);
+ LogHvWarning("Get", "CPSR (Pstate)");
+ return (uint)_pstateRaw;
+ }
+ res.ThrowOnError();
+ _pstateRaw = val;
+ return (uint)val;
+ }
}
set
{
- HvApi.hv_vcpu_set_reg(_vcpu, HvReg.CPSR, (ulong)value).ThrowOnError();
+ lock (_registerLock)
+ {
+ HvResult res = HvApi.hv_vcpu_set_reg(_vcpu, HvReg.CPSR, value);
+ if (res == HvResult.BadArgument)
+ {
+ Interlocked.Increment(ref _fallbackCount);
+ LogHvWarning("Set", "CPSR (Pstate)", $"value=0x{value:X}");
+ }
+ else res.ThrowOnError();
+ _pstateRaw = value;
+ }
}
}
public uint Fpcr
{
- get
- {
- HvApi.hv_vcpu_get_reg(_vcpu, HvReg.FPCR, out ulong fpcr).ThrowOnError();
- return (uint)fpcr;
- }
- set
- {
- HvApi.hv_vcpu_set_reg(_vcpu, HvReg.FPCR, (ulong)value).ThrowOnError();
- }
+ get { lock (_registerLock) return (uint)GetRegCached(HvReg.FPCR, ref _fpcr, "FPCR"); }
+ set { lock (_registerLock) SetRegCached(HvReg.FPCR, value, ref _fpcr, "FPCR"); }
}
public uint Fpsr
{
- get
- {
- HvApi.hv_vcpu_get_reg(_vcpu, HvReg.FPSR, out ulong fpsr).ThrowOnError();
- return (uint)fpsr;
- }
- set
- {
- HvApi.hv_vcpu_set_reg(_vcpu, HvReg.FPSR, (ulong)value).ThrowOnError();
- }
- }
-
- private readonly ulong _vcpu;
- private int _interruptRequested;
-
- public HvExecutionContextVcpu(ulong vcpu)
- {
- _vcpu = vcpu;
+ get { lock (_registerLock) return (uint)GetRegCached(HvReg.FPSR, ref _fpsr, "FPSR"); }
+ set { lock (_registerLock) SetRegCached(HvReg.FPSR, value, ref _fpsr, "FPSR"); }
}
public ulong GetX(int index)
{
- if (index == 31)
+ lock (_registerLock)
{
- HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.SP_EL0, out ulong value).ThrowOnError();
- return value;
- }
- else
- {
- HvApi.hv_vcpu_get_reg(_vcpu, HvReg.X0 + (uint)index, out ulong value).ThrowOnError();
- return value;
+ ulong value;
+ string regName = index == 31 ? "SP_EL0" : $"X{index}";
+
+ if (index == 31)
+ {
+ HvResult res = HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.SP_EL0, out value);
+ if (res == HvResult.BadArgument)
+ {
+ Interlocked.Increment(ref _fallbackCount);
+ LogHvWarning("GetX", regName);
+ return _x[31];
+ }
+ res.ThrowOnError();
+ return _x[31] = value;
+ }
+
+ if ((uint)index > 30) return 0;
+
+ if (index == 0 && _earlyBootPhase && _pc == 0)
+ {
+ return _x[0];
+ }
+
+ HvResult resX = HvApi.hv_vcpu_get_reg(_vcpu, HvReg.X0 + (uint)index, out value);
+ if (resX == HvResult.BadArgument)
+ {
+ Interlocked.Increment(ref _fallbackCount);
+ LogHvWarning("GetX", regName);
+ return _x[index];
+ }
+ resX.ThrowOnError();
+ return _x[index] = value;
}
}
public void SetX(int index, ulong value)
{
- if (index == 31)
+ lock (_registerLock)
{
- HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.SP_EL0, value).ThrowOnError();
- }
- else
- {
- HvApi.hv_vcpu_set_reg(_vcpu, HvReg.X0 + (uint)index, value).ThrowOnError();
+ string regName = index == 31 ? "SP_EL0" : $"X{index}";
+
+ if (index == 31)
+ {
+ HvResult res = HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.SP_EL0, value);
+ if (res == HvResult.BadArgument)
+ {
+ Interlocked.Increment(ref _fallbackCount);
+ LogHvWarning("SetX", regName, $"value=0x{value:X16}");
+ _x[31] = value;
+ return;
+ }
+ res.ThrowOnError();
+ _x[31] = value;
+ }
+ else if ((uint)index <= 30)
+ {
+ HvResult res = HvApi.hv_vcpu_set_reg(_vcpu, HvReg.X0 + (uint)index, value);
+ if (res == HvResult.BadArgument)
+ {
+ Interlocked.Increment(ref _fallbackCount);
+ LogHvWarning("SetX", regName, $"value=0x{value:X16}");
+ _x[index] = value;
+ return;
+ }
+ res.ThrowOnError();
+ _x[index] = value;
+ }
}
}
public V128 GetV(int index)
{
- HvApi.hv_vcpu_get_simd_fp_reg(_vcpu, HvSimdFPReg.Q0 + (uint)index, out HvSimdFPUchar16 value).ThrowOnError();
- return new V128(value.Low, value.High);
+ lock (_registerLock)
+ {
+ if ((uint)index > 31) return default;
+
+ HvResult res = HvApi.hv_vcpu_get_simd_fp_reg(_vcpu, HvSimdFPReg.Q0 + (uint)index, out HvSimdFPUchar16 val);
+ if (res == HvResult.BadArgument)
+ {
+ Interlocked.Increment(ref _fallbackCount);
+ LogHvWarning("GetV", $"Q{index}");
+ return _v[index];
+ }
+ res.ThrowOnError();
+ return _v[index] = new V128(val.Low, val.High);
+ }
}
public void SetV(int index, V128 value)
{
- _setSimdFpReg(_vcpu, HvSimdFPReg.Q0 + (uint)index, value, _setSimdFpRegNativePtr).ThrowOnError();
+ lock (_registerLock)
+ {
+ if ((uint)index > 31) return;
+
+ HvResult res = _setSimdFpReg(_vcpu, HvSimdFPReg.Q0 + (uint)index, value, _setSimdFpRegNativePtr);
+ if (res == HvResult.BadArgument)
+ {
+ Interlocked.Increment(ref _fallbackCount);
+ LogHvWarning("SetV", $"Q{index}");
+ _v[index] = value;
+ return;
+ }
+ res.ThrowOnError();
+ _v[index] = value;
+ }
}
+ private ulong GetRegCached(HvReg reg, ref ulong cached, string name)
+ {
+ HvResult res = HvApi.hv_vcpu_get_reg(_vcpu, reg, out ulong val);
+ if (res == HvResult.BadArgument)
+ {
+ Interlocked.Increment(ref _fallbackCount);
+ LogHvWarning("GetReg", name);
+ return cached;
+ }
+ res.ThrowOnError();
+ return cached = val;
+ }
+
+ private void SetRegCached(HvReg reg, ulong value, ref ulong cached, string name)
+ {
+ HvResult res = HvApi.hv_vcpu_set_reg(_vcpu, reg, value);
+ if (res == HvResult.BadArgument)
+ {
+ Interlocked.Increment(ref _fallbackCount);
+ LogHvWarning("SetReg", name, $"value=0x{value:X16}");
+ cached = value;
+ return;
+ }
+ res.ThrowOnError();
+ cached = value;
+ }
+
+ private ulong GetSysRegCached(HvSysReg reg, ref ulong cached, string name)
+ {
+ HvResult res = HvApi.hv_vcpu_get_sys_reg(_vcpu, reg, out ulong val);
+ if (res == HvResult.BadArgument)
+ {
+ Interlocked.Increment(ref _fallbackCount);
+ LogHvWarning("GetSysReg", name);
+ return cached;
+ }
+ res.ThrowOnError();
+ return cached = val;
+ }
+
+ private void SetSysRegCached(HvSysReg reg, ulong value, ref ulong cached, string name)
+ {
+ HvResult res = HvApi.hv_vcpu_set_sys_reg(_vcpu, reg, value);
+ if (res == HvResult.BadArgument)
+ {
+ Interlocked.Increment(ref _fallbackCount);
+ LogHvWarning("SetSysReg", name, $"value=0x{value:X16}");
+ cached = value;
+ return;
+ }
+ res.ThrowOnError();
+ cached = value;
+ }
+
+ public long GetFallbackCount() => Interlocked.Read(ref _fallbackCount);
+
public void RequestInterrupt()
{
if (Interlocked.Exchange(ref _interruptRequested, 1) == 0)
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
index aaa4ccd99..4f06752cc 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
@@ -689,13 +689,26 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
return context.Pstate & 0xFF0FFE20;
}
+ private const long ContextCacheDurationTicks = 800_000; // ~0.8ms cache lifetime
+
+ private ThreadContext _cachedContext;
+ private long _contextCacheTimestamp;
+ private bool _hasValidContextCache;
+
private ThreadContext GetCurrentContext()
{
+ var now = DateTime.UtcNow.Ticks;
+
+ // Cache hit
+ if (_hasValidContextCache && (now - _contextCacheTimestamp) < ContextCacheDurationTicks)
+ {
+ return _cachedContext;
+ }
+
const int MaxRegistersAArch32 = 15;
const int MaxFpuRegistersAArch32 = 16;
ThreadContext context = new();
-
Span registersSpan = context.Registers.AsSpan();
Span fpuRegistersSpan = context.FpuRegisters.AsSpan();
@@ -705,12 +718,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
registersSpan[i] = Context.GetX(i);
}
-
for (int i = 0; i < fpuRegistersSpan.Length; i++)
{
fpuRegistersSpan[i] = Context.GetV(i);
}
-
context.Fp = Context.GetX(29);
context.Lr = Context.GetX(30);
context.Sp = Context.GetX(31);
@@ -724,12 +735,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
registersSpan[i] = (uint)Context.GetX(i);
}
-
for (int i = 0; i < MaxFpuRegistersAArch32; i++)
{
fpuRegistersSpan[i] = Context.GetV(i);
}
-
context.Pc = (uint)Context.Pc;
context.Pstate = GetPsr(Context);
context.Tpidr = (uint)Context.TpidrroEl0;
@@ -738,6 +747,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
context.Fpcr = (uint)Context.Fpcr;
context.Fpsr = (uint)Context.Fpsr;
+ // Update cache
+ _cachedContext = context;
+ _contextCacheTimestamp = now;
+ _hasValidContextCache = true;
+
return context;
}