[CPU] Preserve blocked leaf import waiters (#350)

This commit is contained in:
Peter Bonanni
2026-07-17 19:59:13 -04:00
committed by GitHub
parent c9d018db8e
commit 0b1dea43e8
4 changed files with 126 additions and 85 deletions
@@ -1345,8 +1345,7 @@ public sealed partial class DirectExecutionBackend
out var blockContinuation, out var blockContinuation,
out var hasBlockContinuation, out var hasBlockContinuation,
out var blockWakeKey, out var blockWakeKey,
out var blockResumeHandler, out var blockWaiter,
out var blockWakeHandler,
out var blockDeadlineTimestamp); out var blockDeadlineTimestamp);
if (consumedThreadBlock && if (consumedThreadBlock &&
TryYieldGuestThreadToHostStub(argPackPtr, dispatchIndex, returnRip, importStubEntry.Nid, blockReason)) TryYieldGuestThreadToHostStub(argPackPtr, dispatchIndex, returnRip, importStubEntry.Nid, blockReason))
@@ -1357,8 +1356,7 @@ public sealed partial class DirectExecutionBackend
GuestThreadExecution.CurrentGuestThreadHandle, GuestThreadExecution.CurrentGuestThreadHandle,
blockContinuation, blockContinuation,
blockWakeKey, blockWakeKey,
blockResumeHandler, blockWaiter,
blockWakeHandler,
blockDeadlineTimestamp); blockDeadlineTimestamp);
} }
@@ -443,10 +443,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
// Stays set through the wake transition; Resume() consumes it when the thread pumps. // Stays set through the wake transition; Resume() consumes it when the thread pumps.
public IGuestThreadBlockWaiter? BlockWaiter { get; set; } public IGuestThreadBlockWaiter? BlockWaiter { get; set; }
public Func<int>? BlockResumeHandler { get; set; }
public Func<bool>? BlockWakeHandler { get; set; }
public long BlockDeadlineTimestamp { get; set; } public long BlockDeadlineTimestamp { get; set; }
public long ImportCount; public long ImportCount;
@@ -3215,43 +3211,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
thread.HasBlockedContinuation = true; thread.HasBlockedContinuation = true;
thread.BlockWakeKey = wakeKey; thread.BlockWakeKey = wakeKey;
thread.BlockWaiter = waiter; thread.BlockWaiter = waiter;
thread.BlockResumeHandler = null;
thread.BlockWakeHandler = null;
thread.BlockDeadlineTimestamp = blockDeadlineTimestamp;
TraceFocusedContinuation(
"register",
guestThreadHandle,
continuation,
wakeKey);
}
}
private void RegisterBlockedGuestThreadContinuation(
ulong guestThreadHandle,
GuestCpuContinuation continuation,
string wakeKey,
Func<int>? resumeHandler,
Func<bool>? wakeHandler,
long blockDeadlineTimestamp)
{
if (guestThreadHandle == 0 || continuation.Rip < 65536 || continuation.Rsp == 0)
{
return;
}
using (LockGate("RegisterBlockedContinuation"))
{
if (!_guestThreads.TryGetValue(guestThreadHandle, out var thread))
{
return;
}
thread.BlockedContinuation = continuation;
thread.HasBlockedContinuation = true;
thread.BlockWakeKey = wakeKey;
thread.BlockWaiter = null;
thread.BlockResumeHandler = resumeHandler;
thread.BlockWakeHandler = wakeHandler;
thread.BlockDeadlineTimestamp = blockDeadlineTimestamp; thread.BlockDeadlineTimestamp = blockDeadlineTimestamp;
TraceFocusedContinuation( TraceFocusedContinuation(
"register", "register",
@@ -3567,11 +3526,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
owner.State = GuestThreadRunState.Blocked; owner.State = GuestThreadRunState.Blocked;
owner.BlockReason = callbackReason ?? reason; owner.BlockReason = callbackReason ?? reason;
if (owner.BlockWakeHandler is not null && owner.BlockWakeHandler()) if (owner.BlockWaiter is not null && owner.BlockWaiter.TryWake())
{ {
owner.State = GuestThreadRunState.Ready; owner.State = GuestThreadRunState.Ready;
owner.BlockReason = null; owner.BlockReason = null;
owner.BlockWakeHandler = null;
owner.BlockDeadlineTimestamp = 0; owner.BlockDeadlineTimestamp = 0;
} }
} }
@@ -3583,7 +3541,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
} }
GuestCpuContinuation continuation = default; GuestCpuContinuation continuation = default;
Func<int>? resumeHandler = null; IGuestThreadBlockWaiter? blockWaiter = null;
while (!ActiveForcedGuestExit) while (!ActiveForcedGuestExit)
{ {
WakeExpiredBlockedGuestThreads(); WakeExpiredBlockedGuestThreads();
@@ -3604,9 +3562,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
owner.BlockedContinuation = default; owner.BlockedContinuation = default;
owner.HasBlockedContinuation = false; owner.HasBlockedContinuation = false;
owner.BlockWakeKey = null; owner.BlockWakeKey = null;
resumeHandler = owner.BlockResumeHandler; blockWaiter = owner.BlockWaiter;
owner.BlockResumeHandler = null; owner.BlockWaiter = null;
owner.BlockWakeHandler = null;
owner.BlockDeadlineTimestamp = 0; owner.BlockDeadlineTimestamp = 0;
owner.BlockReason = null; owner.BlockReason = null;
owner.State = GuestThreadRunState.Running; owner.State = GuestThreadRunState.Running;
@@ -3629,9 +3586,9 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
return false; return false;
} }
if (resumeHandler is not null) if (blockWaiter is not null)
{ {
continuation = continuation with { Rax = unchecked((ulong)(long)resumeHandler()) }; continuation = continuation with { Rax = unchecked((ulong)(long)blockWaiter.Resume()) };
} }
if (_logGuestThreads) if (_logGuestThreads)
{ {
@@ -3844,8 +3801,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
bool savedHasBlockedContinuation; bool savedHasBlockedContinuation;
GuestCpuContinuation savedBlockedContinuation; GuestCpuContinuation savedBlockedContinuation;
string? savedBlockWakeKey; string? savedBlockWakeKey;
Func<int>? savedBlockResumeHandler; IGuestThreadBlockWaiter? savedBlockWaiter;
Func<bool>? savedBlockWakeHandler;
long savedBlockDeadlineTimestamp; long savedBlockDeadlineTimestamp;
ulong exceptionStackBase; ulong exceptionStackBase;
lock (_guestThreadGate) lock (_guestThreadGate)
@@ -3981,8 +3937,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
savedHasBlockedContinuation = target.HasBlockedContinuation; savedHasBlockedContinuation = target.HasBlockedContinuation;
savedBlockedContinuation = target.BlockedContinuation; savedBlockedContinuation = target.BlockedContinuation;
savedBlockWakeKey = target.BlockWakeKey; savedBlockWakeKey = target.BlockWakeKey;
savedBlockResumeHandler = target.BlockResumeHandler; savedBlockWaiter = target.BlockWaiter;
savedBlockWakeHandler = target.BlockWakeHandler;
savedBlockDeadlineTimestamp = target.BlockDeadlineTimestamp; savedBlockDeadlineTimestamp = target.BlockDeadlineTimestamp;
target.State = GuestThreadRunState.Running; target.State = GuestThreadRunState.Running;
@@ -3992,8 +3947,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
target.HasBlockedContinuation = false; target.HasBlockedContinuation = false;
target.BlockedContinuation = default; target.BlockedContinuation = default;
target.BlockWakeKey = null; target.BlockWakeKey = null;
target.BlockResumeHandler = null; target.BlockWaiter = null;
target.BlockWakeHandler = null;
target.BlockDeadlineTimestamp = 0; target.BlockDeadlineTimestamp = 0;
} }
@@ -4041,8 +3995,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
target.HasBlockedContinuation = savedHasBlockedContinuation; target.HasBlockedContinuation = savedHasBlockedContinuation;
target.BlockedContinuation = savedBlockedContinuation; target.BlockedContinuation = savedBlockedContinuation;
target.BlockWakeKey = savedBlockWakeKey; target.BlockWakeKey = savedBlockWakeKey;
target.BlockResumeHandler = savedBlockResumeHandler; target.BlockWaiter = savedBlockWaiter;
target.BlockWakeHandler = savedBlockWakeHandler;
target.BlockDeadlineTimestamp = savedBlockDeadlineTimestamp; target.BlockDeadlineTimestamp = savedBlockDeadlineTimestamp;
// A condition/event wake can arrive while the parked thread is // A condition/event wake can arrive while the parked thread is
@@ -4052,12 +4005,11 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
// pthread wait remains parked forever after a GC suspension races it. // pthread wait remains parked forever after a GC suspension races it.
if (target.State == GuestThreadRunState.Blocked && if (target.State == GuestThreadRunState.Blocked &&
target.HasBlockedContinuation && target.HasBlockedContinuation &&
target.BlockWakeHandler is not null && target.BlockWaiter is not null &&
target.BlockWakeHandler()) target.BlockWaiter.TryWake())
{ {
target.State = GuestThreadRunState.Ready; target.State = GuestThreadRunState.Ready;
target.BlockReason = null; target.BlockReason = null;
target.BlockWakeHandler = null;
target.BlockDeadlineTimestamp = 0; target.BlockDeadlineTimestamp = 0;
_readyGuestThreads.Enqueue(target); _readyGuestThreads.Enqueue(target);
Interlocked.Increment(ref _readyGuestThreadCount); Interlocked.Increment(ref _readyGuestThreadCount);
-21
View File
@@ -398,27 +398,6 @@ public static class GuestThreadExecution
return true; return true;
} }
public static bool TryConsumeCurrentThreadBlock(
out string reason,
out GuestCpuContinuation continuation,
out bool hasContinuation,
out string wakeKey,
out Func<int>? resumeHandler,
out Func<bool>? wakeHandler,
out long blockDeadlineTimestamp)
{
var consumed = TryConsumeCurrentThreadBlock(
out reason,
out continuation,
out hasContinuation,
out wakeKey,
out var waiter,
out blockDeadlineTimestamp);
resumeHandler = waiter is null ? null : waiter.Resume;
wakeHandler = waiter is null ? null : waiter.TryWake;
return consumed;
}
public static long ComputeDeadlineTimestamp(TimeSpan timeout) public static long ComputeDeadlineTimestamp(TimeSpan timeout)
{ {
if (timeout <= TimeSpan.Zero) if (timeout <= TimeSpan.Zero)
@@ -0,0 +1,112 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Reflection;
using SharpEmu.Core.Cpu.Native;
using SharpEmu.HLE;
using Xunit;
namespace SharpEmu.Libs.Tests.Cpu;
public sealed class GuestThreadBlockWaiterRepresentationTests
{
[Fact]
public void SchedulerStoresOnlyTheWaiterObjectRepresentation()
{
var stateType = typeof(DirectExecutionBackend).GetNestedType(
"GuestThreadState",
BindingFlags.NonPublic);
Assert.NotNull(stateType);
var waiterProperty = stateType.GetProperty(
"BlockWaiter",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
Assert.NotNull(waiterProperty);
Assert.Equal(typeof(IGuestThreadBlockWaiter), waiterProperty.PropertyType);
Assert.Null(stateType.GetProperty(
"BlockResumeHandler",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
Assert.Null(stateType.GetProperty(
"BlockWakeHandler",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
var registerMethods = typeof(DirectExecutionBackend)
.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
.Where(method => method.Name == "RegisterBlockedGuestThreadContinuation")
.ToArray();
var registerMethod = Assert.Single(registerMethods);
Assert.Contains(
registerMethod.GetParameters(),
parameter => parameter.ParameterType == typeof(IGuestThreadBlockWaiter));
Assert.DoesNotContain(
registerMethod.GetParameters(),
parameter => IsFuncParameter(parameter.ParameterType));
var consumeMethods = typeof(GuestThreadExecution)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(method => method.Name == nameof(GuestThreadExecution.TryConsumeCurrentThreadBlock));
Assert.DoesNotContain(
consumeMethods.SelectMany(method => method.GetParameters()),
parameter => IsFuncParameter(parameter.ParameterType));
}
[Fact]
public void DelegateCompatibilityBridgeIsConsumedAsOneWaiterObject()
{
var previousThread = GuestThreadExecution.EnterGuestThread(0x1234);
try
{
var canWake = false;
var wakeCalls = 0;
var resumeCalls = 0;
Assert.True(GuestThreadExecution.RequestCurrentThreadBlock(
context: null,
reason: "test_wait",
wakeKey: "test_waiter:1",
resumeHandler: () =>
{
resumeCalls++;
return 42;
},
wakeHandler: () =>
{
wakeCalls++;
return canWake;
}));
Assert.True(GuestThreadExecution.TryConsumeCurrentThreadBlock(
out var reason,
out _,
out var hasContinuation,
out var wakeKey,
out IGuestThreadBlockWaiter? waiter,
out var deadline));
Assert.Equal("test_wait", reason);
Assert.Equal("test_waiter:1", wakeKey);
Assert.False(hasContinuation);
Assert.Equal(0, deadline);
Assert.NotNull(waiter);
Assert.False(waiter.TryWake());
canWake = true;
Assert.True(waiter.TryWake());
Assert.Equal(42, waiter.Resume());
Assert.Equal(2, wakeCalls);
Assert.Equal(1, resumeCalls);
}
finally
{
GuestThreadExecution.RestoreGuestThread(previousThread);
}
}
private static bool IsFuncParameter(Type parameterType)
{
var type = parameterType.IsByRef
? parameterType.GetElementType()
: parameterType;
return type is not null &&
type.IsGenericType &&
type.GetGenericTypeDefinition() == typeof(Func<>);
}
}