core: unify clock dispatch logic, add precise clocks, and enforce coalesced time writes (#71)

Comprehensive refactoring of the system time subsystem to unify clock dispatching, support precise clock extensions, and secure memory boundaries against partial state corruption.

Centralized Clock Dispatch Engine:
- Extracted shared elapsed-tick calculation and clock-routing math into a unified internal static bool ResolveClockTime() dispatch engine under KernelRuntimeCompatExports.cs.
- Moved all clock identifiers from KernelMemoryCompatExports to KernelRuntimeCompatExports as internal const int constants to eliminate cross-file duplication while preserving raw compiler switch-case layout optimizations.
- Added native alias mapping support for CLOCK_REALTIME_PRECISE (9) and CLOCK_MONOTONIC_PRECISE (11).
- Hardened the Orbis sceKernelClockGettime path by routing it through the new dispatcher, resolving a pre-existing logic flaw where any non-zero clock_id incorrectly fell back to monotonic time. Invalid IDs now properly fail with ORBIS_GEN2_ERROR_INVALID_ARGUMENT.

Coalesced Single-Transaction Memory Writes:
- Replaced consecutive isolated 8-byte scalar writes across POSIX clock_gettime, gettimeofday, and Orbis sceKernelClockGettime/sceKernelGettimeofday with safe single-transaction 16-byte stackalloc byte buffer writes via BinaryPrimitives and ctx.Memory.TryWrite. This entirely prevents partial memory state corruption on virtual page boundaries.
- Implemented a single 8-byte coalesced zero-fill transaction for the deprecated/legacy timezone buffer (timezoneAddress != 0), aligning it with standard FreeBSD stub behavior.
- Standardized POSIX failure path routines. Write faults cleanly issue TrySetErrno(ctx, Efault) while safely omitting explicit manual Rax writes, letting the import dispatcher natively sign-extend the return -1 value to 0xFFFFFFFFFFFFFFFF.

Zero-Alloc Host RDTSC Execution Stub:
- Patched CreateRdtscReader() to stream native architecture opcodes out of stack-allocated spans directly into host executable memory zones (VirtualAlloc) via unsafe { Buffer.MemoryCopy(...) }, completely removing the high-frequency .ToArray() runtime allocation overhead on the hot path.

Files: KernelRuntimeCompatExports.cs, KernelMemoryCompatExports.cs
This commit is contained in:
Mike Saito
2026-07-11 23:39:44 +03:00
committed by GitHub
parent 3a24db567f
commit 5e76554514
2 changed files with 45 additions and 37 deletions
@@ -67,13 +67,6 @@ public static class KernelMemoryCompatExports
private const int Einval = 22;
private const int Erange = 34;
private const int Struncate = 80;
private const int ClockRealtime = 0;
private const int ClockVirtual = 1;
private const int ClockProf = 2;
private const int ClockMonotonic = 4;
private const int ClockUptime = 5;
private const int ClockRealtimeFast = 10;
private const int ClockMonotonicFast = 12;
private const nuint DefaultLibcHeapAlignment = 16;
private const ushort KernelStatModeDirectory = 0x41FF;
private const ushort KernelStatModeRegular = 0x81FF;
@@ -2011,29 +2004,11 @@ public static class KernelMemoryCompatExports
long seconds;
long nanoseconds;
switch (clockId)
if (!KernelRuntimeCompatExports.ResolveClockTime(clockId, out seconds, out nanoseconds))
{
case ClockRealtime:
case ClockRealtimeFast:
case ClockVirtual:
case ClockProf:
{
var now = DateTimeOffset.UtcNow;
seconds = now.ToUnixTimeSeconds();
nanoseconds = (now.Ticks % TimeSpan.TicksPerSecond) * 100;
break;
}
case ClockMonotonic:
case ClockMonotonicFast:
case ClockUptime:
KernelRuntimeCompatExports.GetProcessMonotonicTime(out seconds, out nanoseconds);
break;
default:
KernelRuntimeCompatExports.TrySetErrno(ctx, Einval);
ctx[CpuRegister.Rax] = unchecked((ulong)-1);
return -1;
KernelRuntimeCompatExports.TrySetErrno(ctx, Einval);
ctx[CpuRegister.Rax] = unchecked((ulong)-1);
return -1;
}
Span<byte> timespecBuffer = stackalloc byte[16];
@@ -14,6 +14,15 @@ namespace SharpEmu.Libs.Kernel;
public static class KernelRuntimeCompatExports
{
internal const int ClockRealtime = 0;
internal const int ClockVirtual = 1;
internal const int ClockProf = 2;
internal const int ClockMonotonic = 4;
internal const int ClockUptime = 5;
internal const int ClockRealtimePrecise = 9;
internal const int ClockMonotonicPrecise = 11;
internal const int ClockRealtimeFast = 10;
internal const int ClockMonotonicFast = 12;
private const int Efault = 14;
private const ulong TlsErrnoOffset = 0x40;
private const ulong TlsStackChkGuardBaseOffset = 0x800;
@@ -187,15 +196,9 @@ public static class KernelRuntimeCompatExports
long seconds;
long nanoseconds;
if (clockId == 0)
if (!ResolveClockTime(clockId, out seconds, out nanoseconds))
{
var now = DateTimeOffset.UtcNow;
seconds = now.ToUnixTimeSeconds();
nanoseconds = (now.Ticks % TimeSpan.TicksPerSecond) * 100;
}
else
{
GetProcessMonotonicTime(out seconds, out nanoseconds);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
Span<byte> timespecBuffer = stackalloc byte[16];
@@ -632,6 +635,36 @@ public static class KernelRuntimeCompatExports
return address != 0 && ctx.TryWriteInt32(address, value);
}
internal static bool ResolveClockTime(int clockId, out long seconds, out long nanoseconds)
{
switch (clockId)
{
case ClockRealtime:
case ClockRealtimePrecise:
case ClockRealtimeFast:
case ClockVirtual:
case ClockProf:
{
var now = DateTimeOffset.UtcNow;
seconds = now.ToUnixTimeSeconds();
nanoseconds = (now.Ticks % TimeSpan.TicksPerSecond) * 100;
return true;
}
case ClockMonotonic:
case ClockMonotonicPrecise:
case ClockMonotonicFast:
case ClockUptime:
GetProcessMonotonicTime(out seconds, out nanoseconds);
return true;
default:
seconds = 0;
nanoseconds = 0;
return false;
}
}
internal static void GetProcessMonotonicTime(out long seconds, out long nanoseconds)
{
var elapsedTicks = Stopwatch.GetTimestamp() - _processStartCounter;