mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-29 14:09:40 +08:00
[Kernel] Phase 2: semaphores + event flags block in place
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.
This commit is contained in:
@@ -5,14 +5,12 @@ using System.Buffers.Binary;
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using SharpEmu.HLE;
|
using SharpEmu.HLE;
|
||||||
using SharpEmu.Libs.Fiber;
|
|
||||||
|
|
||||||
namespace SharpEmu.Libs.Kernel;
|
namespace SharpEmu.Libs.Kernel;
|
||||||
|
|
||||||
public static class KernelEventFlagCompatExports
|
public static class KernelEventFlagCompatExports
|
||||||
{
|
{
|
||||||
private const int MaxEventFlagNameLength = 31;
|
private const int MaxEventFlagNameLength = 31;
|
||||||
private const int HostWaitPumpMilliseconds = 1;
|
|
||||||
private const uint AttrThreadFifo = 0x01;
|
private const uint AttrThreadFifo = 0x01;
|
||||||
private const uint AttrThreadPriority = 0x02;
|
private const uint AttrThreadPriority = 0x02;
|
||||||
private const uint AttrSingle = 0x10;
|
private const uint AttrSingle = 0x10;
|
||||||
@@ -25,8 +23,7 @@ public static class KernelEventFlagCompatExports
|
|||||||
private static readonly ConcurrentDictionary<ulong, EventFlagState> _eventFlags = new();
|
private static readonly ConcurrentDictionary<ulong, EventFlagState> _eventFlags = new();
|
||||||
private static long _nextEventFlagHandle = 1;
|
private static long _nextEventFlagHandle = 1;
|
||||||
|
|
||||||
// Cached once: gating every call site avoids building the interpolated
|
// Cached once: gating every call site avoids building the interpolated trace string when disabled.
|
||||||
// trace string (and FormatFrameChain/FormatGuestWaitObject) when disabled.
|
|
||||||
private static readonly bool _traceEventFlag = string.Equals(
|
private static readonly bool _traceEventFlag = string.Equals(
|
||||||
Environment.GetEnvironmentVariable("SHARPEMU_LOG_EVENT_FLAG"), "1", StringComparison.Ordinal);
|
Environment.GetEnvironmentVariable("SHARPEMU_LOG_EVENT_FLAG"), "1", StringComparison.Ordinal);
|
||||||
|
|
||||||
@@ -128,11 +125,11 @@ public static class KernelEventFlagCompatExports
|
|||||||
lock (state.Gate)
|
lock (state.Gate)
|
||||||
{
|
{
|
||||||
state.Bits |= pattern;
|
state.Bits |= pattern;
|
||||||
|
// Wake threads parked in-place on the gate; each re-checks its pattern.
|
||||||
Monitor.PulseAll(state.Gate);
|
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}");
|
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);
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,137 +226,66 @@ public static class KernelEventFlagCompatExports
|
|||||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
Monitor.Enter(state.Gate);
|
// A zero-microsecond timeout degrades to an instant poll because the
|
||||||
try
|
// deadline is already in the past.
|
||||||
{
|
|
||||||
if (TryCompleteSatisfiedWait(ctx, state, pattern, waitMode, resultAddress, out var immediateWaitResult))
|
|
||||||
{
|
|
||||||
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
|
var hostDeadlineMs = timeoutAddress != 0
|
||||||
? Environment.TickCount64 + (timeoutUsec == 0
|
? Environment.TickCount64 + (timeoutUsec == 0
|
||||||
? 0L
|
? 0L
|
||||||
: Math.Max(1L, (timeoutUsec + 999L) / 1000L))
|
: Math.Max(1L, (timeoutUsec + 999L) / 1000L))
|
||||||
: long.MaxValue;
|
: long.MaxValue;
|
||||||
|
|
||||||
var currentGuestThread = GuestThreadExecution.CurrentGuestThreadHandle;
|
lock (state.Gate)
|
||||||
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),
|
|
||||||
() =>
|
|
||||||
{
|
{
|
||||||
if (satisfied)
|
if (TryCompleteSatisfiedWait(ctx, state, pattern, waitMode, resultAddress, out var immediateWaitResult))
|
||||||
{
|
{
|
||||||
return (int)blockedWaitResult;
|
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);
|
||||||
|
|
||||||
// Deadline expiry: report timeout with the current bits.
|
|
||||||
if (timeoutAddress != 0)
|
|
||||||
{
|
|
||||||
_ = TryWriteUInt32(ctx, timeoutAddress, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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++;
|
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}");
|
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}");
|
||||||
var releaseWaiter = true;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
Monitor.Exit(state.Gate);
|
if (GuestThreadBlocking.ShutdownRequested)
|
||||||
try
|
|
||||||
{
|
{
|
||||||
scheduler.Pump(ctx, "sceKernelWaitEventFlag");
|
if (timeoutAddress != 0) _ = TryWriteUInt32(ctx, timeoutAddress, 0);
|
||||||
}
|
_ = TryWriteResultPattern(ctx, resultAddress, state.Bits);
|
||||||
finally
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT);
|
||||||
{
|
|
||||||
Monitor.Enter(state.Gate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryCompleteSatisfiedWait(ctx, state, pattern, waitMode, resultAddress, out var pumpedWaitResult))
|
if (TryCompleteSatisfiedWait(ctx, state, pattern, waitMode, resultAddress, out var wokeResult))
|
||||||
{
|
{
|
||||||
state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1);
|
if (timeoutAddress != 0) _ = TryWriteUInt32(ctx, timeoutAddress, 0);
|
||||||
releaseWaiter = false;
|
if (_traceEventFlag) TraceEventFlag($"wait-wake handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} ret=0x{returnRip:X16}");
|
||||||
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, wokeResult);
|
||||||
return SetReturn(ctx, pumpedWaitResult);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var remaining = hostDeadlineMs - Environment.TickCount64;
|
var remaining = hostDeadlineMs - Environment.TickCount64;
|
||||||
if (timeoutAddress != 0 && remaining <= 0)
|
if (timeoutAddress != 0 && remaining <= 0)
|
||||||
{
|
{
|
||||||
state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1);
|
|
||||||
releaseWaiter = false;
|
|
||||||
_ = TryWriteUInt32(ctx, timeoutAddress, 0);
|
_ = TryWriteUInt32(ctx, timeoutAddress, 0);
|
||||||
_ = TryWriteResultPattern(ctx, resultAddress, state.Bits);
|
_ = 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}");
|
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);
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
Monitor.Wait(state.Gate, (int)Math.Min(remaining, HostWaitPumpMilliseconds));
|
_ = Monitor.Wait(state.Gate, (int)Math.Min(remaining, GuestThreadBlocking.WaitSliceMilliseconds));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
|
||||||
if (releaseWaiter)
|
|
||||||
{
|
{
|
||||||
state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1);
|
state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "PZku4ZrXJqg",
|
Nid = "PZku4ZrXJqg",
|
||||||
ExportName = "sceKernelCancelEventFlag",
|
ExportName = "sceKernelCancelEventFlag",
|
||||||
@@ -455,41 +381,6 @@ public static class KernelEventFlagCompatExports
|
|||||||
return true;
|
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) =>
|
private static bool TryWriteResultPattern(CpuContext ctx, ulong address, ulong bits) =>
|
||||||
address == 0 || ctx.TryWriteUInt64(address, bits);
|
address == 0 || ctx.TryWriteUInt64(address, bits);
|
||||||
|
|
||||||
@@ -519,19 +410,6 @@ public static class KernelEventFlagCompatExports
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryReadByte(CpuContext ctx, ulong address, out byte value)
|
|
||||||
{
|
|
||||||
Span<byte> 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)
|
private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value)
|
||||||
{
|
{
|
||||||
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||||
@@ -584,95 +462,4 @@ public static class KernelEventFlagCompatExports
|
|||||||
? frame.ReturnRip
|
? frame.ReturnRip
|
||||||
: 0UL;
|
: 0UL;
|
||||||
|
|
||||||
private static string FormatFrameChain(CpuContext ctx)
|
|
||||||
{
|
|
||||||
Span<ulong> 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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ public static class KernelSemaphoreCompatExports
|
|||||||
private sealed class KernelSemaphoreState
|
private sealed class KernelSemaphoreState
|
||||||
{
|
{
|
||||||
public required string Name { get; init; }
|
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 InitialCount { get; init; }
|
||||||
public required int MaxCount { get; init; }
|
public required int MaxCount { get; init; }
|
||||||
public int Count { get; set; }
|
public int Count { get; set; }
|
||||||
@@ -65,7 +63,6 @@ public static class KernelSemaphoreCompatExports
|
|||||||
_semaphores[handle] = new KernelSemaphoreState
|
_semaphores[handle] = new KernelSemaphoreState
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
WakeKey = GetSemaphoreWakeKey(handle),
|
|
||||||
InitialCount = initialCount,
|
InitialCount = initialCount,
|
||||||
MaxCount = maxCount,
|
MaxCount = maxCount,
|
||||||
Count = initialCount,
|
Count = initialCount,
|
||||||
@@ -111,148 +108,75 @@ public static class KernelSemaphoreCompatExports
|
|||||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
lock (semaphore.Gate)
|
||||||
{
|
{
|
||||||
if (semaphore.Count >= needCount)
|
if (semaphore.Count < needCount)
|
||||||
{
|
{
|
||||||
|
semaphore.WaitingThreads++;
|
||||||
|
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)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Count -= needCount;
|
||||||
if (timeoutAddress != 0)
|
if (timeoutAddress != 0)
|
||||||
{
|
{
|
||||||
_ = TryWriteUInt32(ctx, timeoutAddress, timeoutUsec);
|
_ = TryWriteUInt32(ctx, timeoutAddress, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_traceSema)
|
if (_traceSema)
|
||||||
{
|
{
|
||||||
TraceSemaphore($"wait handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count} timeout={(timeoutAddress == 0 ? "infinite" : timeoutUsec)}");
|
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)
|
|
||||||
{
|
|
||||||
var deadlineMs = timeoutAddress != 0
|
|
||||||
? Environment.TickCount64 + Math.Max(1L, timeoutUsec / 1000L)
|
|
||||||
: long.MaxValue;
|
|
||||||
lock (semaphore.Gate)
|
|
||||||
{
|
|
||||||
if (_traceSema)
|
|
||||||
{
|
|
||||||
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 = Math.Max(0, semaphore.WaitingThreads - 1);
|
|
||||||
_ = TryWriteUInt32(ctx, timeoutAddress, 0);
|
|
||||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
Monitor.Wait(semaphore.Gate, (int)Math.Min(remaining, 100));
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetSemaphoreWakeKey(uint handle) => $"sceKernelWaitSema:{handle:X8}";
|
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "12wOHk8ywb0",
|
Nid = "12wOHk8ywb0",
|
||||||
ExportName = "sceKernelPollSema",
|
ExportName = "sceKernelPollSema",
|
||||||
@@ -315,7 +239,7 @@ public static class KernelSemaphoreCompatExports
|
|||||||
}
|
}
|
||||||
|
|
||||||
semaphore.Count += signalCount;
|
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);
|
Monitor.PulseAll(semaphore.Gate);
|
||||||
if (_traceSema)
|
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);
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,7 +275,6 @@ public static class KernelSemaphoreCompatExports
|
|||||||
}
|
}
|
||||||
|
|
||||||
semaphore.Count = setCount < 0 ? semaphore.InitialCount : setCount;
|
semaphore.Count = setCount < 0 ? semaphore.InitialCount : setCount;
|
||||||
semaphore.WaitingThreads = 0;
|
|
||||||
Monitor.PulseAll(semaphore.Gate);
|
Monitor.PulseAll(semaphore.Gate);
|
||||||
if (_traceSema)
|
if (_traceSema)
|
||||||
{
|
{
|
||||||
@@ -362,7 +282,6 @@ public static class KernelSemaphoreCompatExports
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(GetSemaphoreWakeKey(handle));
|
|
||||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,7 +329,6 @@ public static class KernelSemaphoreCompatExports
|
|||||||
_semaphores[handle] = new KernelSemaphoreState
|
_semaphores[handle] = new KernelSemaphoreState
|
||||||
{
|
{
|
||||||
Name = $"posix@0x{semaphoreAddress:X16}",
|
Name = $"posix@0x{semaphoreAddress:X16}",
|
||||||
WakeKey = GetSemaphoreWakeKey(handle),
|
|
||||||
InitialCount = initialCount,
|
InitialCount = initialCount,
|
||||||
MaxCount = int.MaxValue,
|
MaxCount = int.MaxValue,
|
||||||
Count = initialCount,
|
Count = initialCount,
|
||||||
|
|||||||
Reference in New Issue
Block a user