From 19dc71ba220b857e4e1fb0f0271cde49a0ebd559 Mon Sep 17 00:00:00 2001 From: Gutemberg Ribeiro Date: Sat, 18 Jul 2026 21:50:24 +0100 Subject: [PATCH] [Kernel] Phase 2: semaphores + event flags block in place MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second phase of the 1:1 threading rework. Semaphores and event flags now block the guest thread's host thread in place on their existing gate object, dropping the RequestCurrentThreadBlock cooperative path, the Pump-driven fallback loop, and the WakeBlockedThreads wake-key hand-off. - sceKernelWaitSema / sem_wait: park on Monitor.Wait(gate); SignalSema/Cancel pulse. Monitor releases the gate and parks atomically, so a signal issued the instant before the park cannot be lost. Semantics follow FreeBSD ksem/sem_wait (acquire when count>=need, else sleep). The separate WaitSemaphoreOnHostThread fallback is folded into the one path. - sceKernelWaitEventFlag: park on Monitor.Wait(gate), re-evaluating the AND/OR pattern with optional clear on each wake (reusing the existing IsSatisfied/ApplyClearMode/TryCompleteSatisfiedWait helpers); Set/Cancel pulse. Removes the cooperative predicate/resume closures, the scheduler Pump fallback, and the RE-era wait-object/frame-chain diagnostics that only existed to debug the old lost-wake path. Waits are sliced (GuestThreadBlocking.WaitSliceMilliseconds) purely so teardown unwinds parked threads. Net removal of the WakeKey field, the per-handle wake-key formatting, and ~200 lines of dead machinery. Verified (Metal, headless, vs baseline): void 6.3M imports (unregressed, 0 stalls), Dream Sara / Dead Cells / Hades / Astro / Silent Hill / Lunar all unregressed — Dead Cells advanced 5.2M->10.3M and Hades holds ~35M/45s. Zero unrecovered crashes across all eight games (Astro's one fault is the pre-existing recovered TBB functor-construction race). All 351 Libs tests pass. Lunar's 800K stall is unchanged — its producer-never-runs root is deeper (equeue/main-thread, phases 3-4), not semaphore mechanics. --- .../Kernel/KernelEventFlagCompatExports.cs | 299 +++--------------- .../Kernel/KernelSemaphoreCompatExports.cs | 180 +++-------- 2 files changed, 92 insertions(+), 387 deletions(-) diff --git a/src/SharpEmu.Libs/Kernel/KernelEventFlagCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelEventFlagCompatExports.cs index d993c960..0bfce600 100644 --- a/src/SharpEmu.Libs/Kernel/KernelEventFlagCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelEventFlagCompatExports.cs @@ -5,14 +5,12 @@ using System.Buffers.Binary; using System.Collections.Concurrent; using System.Text; using SharpEmu.HLE; -using SharpEmu.Libs.Fiber; namespace SharpEmu.Libs.Kernel; public static class KernelEventFlagCompatExports { private const int MaxEventFlagNameLength = 31; - private const int HostWaitPumpMilliseconds = 1; private const uint AttrThreadFifo = 0x01; private const uint AttrThreadPriority = 0x02; private const uint AttrSingle = 0x10; @@ -25,8 +23,7 @@ public static class KernelEventFlagCompatExports private static readonly ConcurrentDictionary _eventFlags = new(); private static long _nextEventFlagHandle = 1; - // Cached once: gating every call site avoids building the interpolated - // trace string (and FormatFrameChain/FormatGuestWaitObject) when disabled. + // Cached once: gating every call site avoids building the interpolated trace string when disabled. private static readonly bool _traceEventFlag = string.Equals( Environment.GetEnvironmentVariable("SHARPEMU_LOG_EVENT_FLAG"), "1", StringComparison.Ordinal); @@ -128,11 +125,11 @@ public static class KernelEventFlagCompatExports lock (state.Gate) { state.Bits |= pattern; + // Wake threads parked in-place on the gate; each re-checks its pattern. Monitor.PulseAll(state.Gate); if (_traceEventFlag) TraceEventFlag($"set handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} ret=0x{returnRip:X16}"); } - _ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(GetEventFlagWakeKey(handle)); return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK); } @@ -229,134 +226,63 @@ public static class KernelEventFlagCompatExports return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); } - Monitor.Enter(state.Gate); - try + // A zero-microsecond timeout degrades to an instant poll because the + // deadline is already in the past. + var hostDeadlineMs = timeoutAddress != 0 + ? Environment.TickCount64 + (timeoutUsec == 0 + ? 0L + : Math.Max(1L, (timeoutUsec + 999L) / 1000L)) + : long.MaxValue; + + lock (state.Gate) { if (TryCompleteSatisfiedWait(ctx, state, pattern, waitMode, resultAddress, out var immediateWaitResult)) { + if (_traceEventFlag) TraceEventFlag($"poll handle=0x{handle:X16} pattern=0x{pattern:X16} mode=0x{waitMode:X2} bits=0x{state.Bits:X16} ret=0x{returnRip:X16}"); return SetReturn(ctx, immediateWaitResult); } - // Timed waits block on a deadline instead of returning TIMED_OUT - // immediately; a zero-microsecond timeout still degrades to an - // instant poll because the deadline is already in the past. - var deadline = timeoutAddress != 0 - ? GuestThreadExecution.ComputeDeadlineTimestamp(TimeSpan.FromMicroseconds(timeoutUsec)) - : 0; - var hostDeadlineMs = timeoutAddress != 0 - ? Environment.TickCount64 + (timeoutUsec == 0 - ? 0L - : Math.Max(1L, (timeoutUsec + 999L) / 1000L)) - : long.MaxValue; - - var currentGuestThread = GuestThreadExecution.CurrentGuestThreadHandle; - var currentFiber = FiberExports.GetCurrentFiberAddressForDiagnostics(ctx); - var managedThread = Environment.CurrentManagedThreadId; - var blockedWaitResult = OrbisGen2Result.ORBIS_GEN2_OK; - var satisfied = false; - var requestedBlock = GuestThreadExecution.RequestCurrentThreadBlock( - ctx, - "sceKernelWaitEventFlag", - GetEventFlagWakeKey(handle), - () => + // In-place block on the flag gate. Monitor.Wait releases the gate + // and parks atomically, so a concurrent SetEventFlag's PulseAll + // cannot be lost between the satisfy check and the park. On wake + // the predicate is re-evaluated (Set semantics on FreeBSD-style + // event flags: OR/AND over the bit pattern, optional clear). + state.WaitingThreads++; + if (_traceEventFlag) TraceEventFlag($"wait-block handle=0x{handle:X16} pattern=0x{pattern:X16} waiters={state.WaitingThreads} guest_thread=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} ret=0x{returnRip:X16}"); + try + { + while (true) { - if (satisfied) + if (GuestThreadBlocking.ShutdownRequested) { - return (int)blockedWaitResult; + if (timeoutAddress != 0) _ = TryWriteUInt32(ctx, timeoutAddress, 0); + _ = TryWriteResultPattern(ctx, resultAddress, state.Bits); + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT); } - // Deadline expiry: report timeout with the current bits. - if (timeoutAddress != 0) + if (TryCompleteSatisfiedWait(ctx, state, pattern, waitMode, resultAddress, out var wokeResult)) + { + if (timeoutAddress != 0) _ = TryWriteUInt32(ctx, timeoutAddress, 0); + if (_traceEventFlag) TraceEventFlag($"wait-wake handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} ret=0x{returnRip:X16}"); + return SetReturn(ctx, wokeResult); + } + + var remaining = hostDeadlineMs - Environment.TickCount64; + if (timeoutAddress != 0 && remaining <= 0) { _ = TryWriteUInt32(ctx, timeoutAddress, 0); + _ = TryWriteResultPattern(ctx, resultAddress, state.Bits); + if (_traceEventFlag) TraceEventFlag($"wait-timeout handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} ret=0x{returnRip:X16}"); + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT); } - _ = TryWriteResultPattern(ctx, resultAddress, state.Bits); - return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT; - }, - () => - { - if (!TryPrepareBlockedWait( - ctx, - state, - pattern, - waitMode, - resultAddress, - out var preparedResult)) - { - return false; - } - - blockedWaitResult = preparedResult; - satisfied = true; - return true; - }, - deadline); - if (_traceEventFlag) TraceEventFlag($"wait-unsatisfied handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} guest_thread=0x{currentGuestThread:X16} fiber=0x{currentFiber:X16} managed={managedThread} block={requestedBlock} ret=0x{returnRip:X16} frames={FormatFrameChain(ctx)}"); - if (_traceEventFlag) TraceEventFlag($"wait-object handle=0x{handle:X16} name='{state.Name}' {FormatGuestWaitObject(ctx)}"); - if (!requestedBlock) - { - var scheduler = GuestThreadExecution.Scheduler; - if (scheduler is null) - { - return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN); - } - - state.WaitingThreads++; - if (_traceEventFlag) TraceEventFlag($"wait-pump handle=0x{handle:X16} pattern=0x{pattern:X16} waiters={state.WaitingThreads} guest_thread=0x{currentGuestThread:X16} fiber=0x{currentFiber:X16} managed={managedThread} ret=0x{returnRip:X16}"); - var releaseWaiter = true; - try - { - while (true) - { - Monitor.Exit(state.Gate); - try - { - scheduler.Pump(ctx, "sceKernelWaitEventFlag"); - } - finally - { - Monitor.Enter(state.Gate); - } - - if (TryCompleteSatisfiedWait(ctx, state, pattern, waitMode, resultAddress, out var pumpedWaitResult)) - { - state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1); - releaseWaiter = false; - if (_traceEventFlag) TraceEventFlag($"wait-wake handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} waiters={state.WaitingThreads} ret=0x{returnRip:X16}"); - return SetReturn(ctx, pumpedWaitResult); - } - - var remaining = hostDeadlineMs - Environment.TickCount64; - if (timeoutAddress != 0 && remaining <= 0) - { - state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1); - releaseWaiter = false; - _ = TryWriteUInt32(ctx, timeoutAddress, 0); - _ = TryWriteResultPattern(ctx, resultAddress, state.Bits); - if (_traceEventFlag) TraceEventFlag($"wait-timeout handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} ret=0x{returnRip:X16}"); - return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT); - } - - Monitor.Wait(state.Gate, (int)Math.Min(remaining, HostWaitPumpMilliseconds)); - } - } - finally - { - if (releaseWaiter) - { - state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1); - } + _ = Monitor.Wait(state.Gate, (int)Math.Min(remaining, GuestThreadBlocking.WaitSliceMilliseconds)); } } - - state.WaitingThreads++; - if (_traceEventFlag) TraceEventFlag($"wait-block handle=0x{handle:X16} pattern=0x{pattern:X16} waiters={state.WaitingThreads} guest_thread=0x{currentGuestThread:X16} fiber=0x{currentFiber:X16} managed={managedThread} ret=0x{returnRip:X16}"); - return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK); - } - finally - { - Monitor.Exit(state.Gate); + finally + { + state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1); + } } } @@ -455,41 +381,6 @@ public static class KernelEventFlagCompatExports return true; } - private static bool TryPrepareBlockedWait( - CpuContext ctx, - EventFlagState state, - ulong pattern, - uint waitMode, - ulong resultAddress, - out OrbisGen2Result result) - { - lock (state.Gate) - { - result = OrbisGen2Result.ORBIS_GEN2_OK; - if (!IsSatisfied(state.Bits, pattern, waitMode)) - { - return false; - } - - if (!TryWriteResultPattern(ctx, resultAddress, state.Bits)) - { - result = OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; - } - else - { - ApplyClearMode(state, pattern, waitMode); - } - - state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1); - if (_traceEventFlag) TraceEventFlag( - $"wait-wake pattern=0x{pattern:X16} mode=0x{waitMode:X2} bits=0x{state.Bits:X16} waiters={state.WaitingThreads}"); - return true; - } - } - - private static string GetEventFlagWakeKey(ulong handle) => - $"event_flag:0x{handle:X16}"; - private static bool TryWriteResultPattern(CpuContext ctx, ulong address, ulong bits) => address == 0 || ctx.TryWriteUInt64(address, bits); @@ -519,19 +410,6 @@ public static class KernelEventFlagCompatExports return true; } - private static bool TryReadByte(CpuContext ctx, ulong address, out byte value) - { - Span buffer = stackalloc byte[1]; - if (!ctx.Memory.TryRead(address, buffer)) - { - value = 0; - return false; - } - - value = buffer[0]; - return true; - } - private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value) { Span buffer = stackalloc byte[sizeof(uint)]; @@ -584,95 +462,4 @@ public static class KernelEventFlagCompatExports ? frame.ReturnRip : 0UL; - private static string FormatFrameChain(CpuContext ctx) - { - Span returns = stackalloc ulong[4]; - var count = 0; - var frame = ctx[CpuRegister.Rbp]; - for (var index = 0; index < returns.Length && frame != 0; index++) - { - if (!ctx.TryReadUInt64(frame, out var nextFrame) || - !ctx.TryReadUInt64(frame + sizeof(ulong), out var returnAddress)) - { - break; - } - - returns[count++] = returnAddress; - if (nextFrame <= frame) - { - break; - } - - frame = nextFrame; - } - - return count switch - { - 0 => "none", - 1 => $"0x{returns[0]:X16}", - 2 => $"0x{returns[0]:X16},0x{returns[1]:X16}", - 3 => $"0x{returns[0]:X16},0x{returns[1]:X16},0x{returns[2]:X16}", - _ => $"0x{returns[0]:X16},0x{returns[1]:X16},0x{returns[2]:X16},0x{returns[3]:X16}", - }; - } - - private static string FormatGuestWaitObject(CpuContext ctx) - { - var r12 = ctx[CpuRegister.R12]; - var r13 = ctx[CpuRegister.R13]; - var objectAddress = r12 != 0 - ? r12 - : r13 >= 0xA8 - ? r13 - 0xA8 - : 0; - - var builder = new StringBuilder(256); - builder.Append($"r12=0x{r12:X16} r13=0x{r13:X16}"); - if (objectAddress == 0) - { - return builder.ToString(); - } - - builder.Append($" obj=0x{objectAddress:X16}"); - AppendUInt32(builder, ctx, objectAddress + 0x58, "o58"); - AppendUInt32(builder, ctx, objectAddress + 0x5C, "o5C"); - AppendUInt64(builder, ctx, objectAddress + 0x60, "o60"); - AppendByte(builder, ctx, objectAddress + 0x6C, "state6C"); - AppendByte(builder, ctx, objectAddress + 0x6D, "o6D"); - AppendByte(builder, ctx, objectAddress + 0xA0, "waitA0"); - AppendByte(builder, ctx, objectAddress + 0xA1, "stateA1"); - AppendByte(builder, ctx, objectAddress + 0xA2, "oA2"); - AppendUInt64(builder, ctx, objectAddress + 0xA8, "eventA8"); - if (r13 != 0) - { - AppendUInt64(builder, ctx, r13, "r13_0"); - AppendUInt64(builder, ctx, r13 + 8, "r13_8"); - } - - return builder.ToString(); - } - - private static void AppendByte(StringBuilder builder, CpuContext ctx, ulong address, string name) - { - if (TryReadByte(ctx, address, out var value)) - { - builder.Append($" {name}=0x{value:X2}"); - } - } - - private static void AppendUInt32(StringBuilder builder, CpuContext ctx, ulong address, string name) - { - if (TryReadUInt32(ctx, address, out var value)) - { - builder.Append($" {name}=0x{value:X8}"); - } - } - - private static void AppendUInt64(StringBuilder builder, CpuContext ctx, ulong address, string name) - { - if (TryReadUInt64(ctx, address, out var value)) - { - builder.Append($" {name}=0x{value:X16}"); - } - } } diff --git a/src/SharpEmu.Libs/Kernel/KernelSemaphoreCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelSemaphoreCompatExports.cs index 49e338ac..d81e2467 100644 --- a/src/SharpEmu.Libs/Kernel/KernelSemaphoreCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelSemaphoreCompatExports.cs @@ -17,8 +17,6 @@ public static class KernelSemaphoreCompatExports private sealed class KernelSemaphoreState { public required string Name { get; init; } - // Formatted once at creation; signal/wait/cancel/delete all wake through this key. - public required string WakeKey { get; init; } public required int InitialCount { get; init; } public required int MaxCount { get; init; } public int Count { get; set; } @@ -65,7 +63,6 @@ public static class KernelSemaphoreCompatExports _semaphores[handle] = new KernelSemaphoreState { Name = name, - WakeKey = GetSemaphoreWakeKey(handle), InitialCount = initialCount, MaxCount = maxCount, Count = initialCount, @@ -111,148 +108,75 @@ public static class KernelSemaphoreCompatExports return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); } - lock (semaphore.Gate) - { - if (semaphore.Count >= needCount) - { - semaphore.Count -= needCount; - if (timeoutAddress != 0) - { - _ = TryWriteUInt32(ctx, timeoutAddress, timeoutUsec); - } - - if (_traceSema) - { - TraceSemaphore($"wait handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count} timeout={(timeoutAddress == 0 ? "infinite" : timeoutUsec)}"); - } - return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK); - } - - semaphore.WaitingThreads++; - } - - // Block cooperatively: the wake predicate atomically acquires the - // tokens (so a wake commits the acquisition), while the resume - // handler distinguishes a real acquisition from a deadline expiry. - var acquired = false; - var deadline = timeoutAddress != 0 - ? GuestThreadExecution.ComputeDeadlineTimestamp(TimeSpan.FromMicroseconds(timeoutUsec)) - : 0; - - bool WakePredicate() - { - lock (semaphore.Gate) - { - if (semaphore.Count >= needCount) - { - semaphore.Count -= needCount; - semaphore.WaitingThreads = Math.Max(0, semaphore.WaitingThreads - 1); - acquired = true; - return true; - } - - return false; - } - } - - int ResumeWait() - { - if (timeoutAddress != 0) - { - _ = TryWriteUInt32(ctx, timeoutAddress, 0); - } - - if (acquired) - { - if (_traceSema) - { - TraceSemaphore($"wait-wake handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}"); - } - return (int)OrbisGen2Result.ORBIS_GEN2_OK; - } - - lock (semaphore.Gate) - { - semaphore.WaitingThreads = Math.Max(0, semaphore.WaitingThreads - 1); - } - - if (_traceSema) - { - TraceSemaphore($"wait-timeout handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}"); - } - return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT; - } - - if (GuestThreadExecution.RequestCurrentThreadBlock( - ctx, - "sceKernelWaitSema", - GetSemaphoreWakeKey(handle), - ResumeWait, - WakePredicate, - deadline)) - { - if (_traceSema) - { - TraceSemaphore($"wait-block handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count} timeout={(timeoutAddress == 0 ? "infinite" : timeoutUsec)} waiters={semaphore.WaitingThreads} {FormatCallSite(ctx)}"); - } - return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK); - } - - // Not a guest thread (or no scheduler): fall back to a host-thread - // wait so the semantics still hold on non-cooperative callers. - return WaitSemaphoreOnHostThread(ctx, semaphore, handle, needCount, timeoutAddress, timeoutUsec); - } - - private static int WaitSemaphoreOnHostThread( - CpuContext ctx, - KernelSemaphoreState semaphore, - uint handle, - int needCount, - ulong timeoutAddress, - uint timeoutUsec) - { + // In-place block on the semaphore gate. Monitor.Wait releases the gate + // and parks atomically, so a concurrent SignalSema's PulseAll cannot be + // lost between the count check and the park. Semantics mirror FreeBSD + // ksem / sem_wait: acquire when count>=need, else sleep until posted or + // the deadline lapses. Waits are sliced only so teardown can unwind. var deadlineMs = timeoutAddress != 0 ? Environment.TickCount64 + Math.Max(1L, timeoutUsec / 1000L) : long.MaxValue; + lock (semaphore.Gate) { - if (_traceSema) + if (semaphore.Count < needCount) { - TraceSemaphore( - $"wait-host-block handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} " + - $"count={semaphore.Count} timeout={(timeoutAddress == 0 ? "infinite" : timeoutUsec)} {FormatCallSite(ctx)}"); - } - while (semaphore.Count < needCount) - { - var remaining = deadlineMs - Environment.TickCount64; - if (timeoutAddress != 0 && remaining <= 0) + semaphore.WaitingThreads++; + if (_traceSema) { - semaphore.WaitingThreads = Math.Max(0, semaphore.WaitingThreads - 1); - _ = TryWriteUInt32(ctx, timeoutAddress, 0); - return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT); + TraceSemaphore($"wait-block handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count} timeout={(timeoutAddress == 0 ? "infinite" : timeoutUsec)} waiters={semaphore.WaitingThreads} {FormatCallSite(ctx)}"); } - Monitor.Wait(semaphore.Gate, (int)Math.Min(remaining, 100)); + try + { + while (semaphore.Count < needCount) + { + if (GuestThreadBlocking.ShutdownRequested) + { + if (timeoutAddress != 0) + { + _ = TryWriteUInt32(ctx, timeoutAddress, 0); + } + + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT); + } + + var remaining = deadlineMs - Environment.TickCount64; + if (timeoutAddress != 0 && remaining <= 0) + { + if (_traceSema) + { + TraceSemaphore($"wait-timeout handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}"); + } + + _ = TryWriteUInt32(ctx, timeoutAddress, 0); + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT); + } + + _ = Monitor.Wait(semaphore.Gate, (int)Math.Min(remaining, GuestThreadBlocking.WaitSliceMilliseconds)); + } + } + finally + { + semaphore.WaitingThreads = Math.Max(0, semaphore.WaitingThreads - 1); + } } semaphore.Count -= needCount; - semaphore.WaitingThreads = Math.Max(0, semaphore.WaitingThreads - 1); - if (_traceSema) - { - TraceSemaphore( - $"wait-host-wake handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count} {FormatCallSite(ctx)}"); - } if (timeoutAddress != 0) { _ = TryWriteUInt32(ctx, timeoutAddress, 0); } + if (_traceSema) + { + TraceSemaphore($"wait handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count} timeout={(timeoutAddress == 0 ? "infinite" : timeoutUsec)}"); + } + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK); } } - private static string GetSemaphoreWakeKey(uint handle) => $"sceKernelWaitSema:{handle:X8}"; - [SysAbiExport( Nid = "12wOHk8ywb0", ExportName = "sceKernelPollSema", @@ -315,7 +239,7 @@ public static class KernelSemaphoreCompatExports } semaphore.Count += signalCount; - // Wake host-thread waiters parked in the fallback path. + // Wake threads parked in-place on the gate; each re-checks the count. Monitor.PulseAll(semaphore.Gate); if (_traceSema) { @@ -323,9 +247,6 @@ public static class KernelSemaphoreCompatExports } } - // Wake cooperatively-blocked guest threads; their wake predicate - // acquires the tokens atomically, so this respects the new count. - _ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(GetSemaphoreWakeKey(handle)); return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK); } @@ -354,7 +275,6 @@ public static class KernelSemaphoreCompatExports } semaphore.Count = setCount < 0 ? semaphore.InitialCount : setCount; - semaphore.WaitingThreads = 0; Monitor.PulseAll(semaphore.Gate); if (_traceSema) { @@ -362,7 +282,6 @@ public static class KernelSemaphoreCompatExports } } - _ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(GetSemaphoreWakeKey(handle)); return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK); } @@ -410,7 +329,6 @@ public static class KernelSemaphoreCompatExports _semaphores[handle] = new KernelSemaphoreState { Name = $"posix@0x{semaphoreAddress:X16}", - WakeKey = GetSemaphoreWakeKey(handle), InitialCount = initialCount, MaxCount = int.MaxValue, Count = initialCount,