mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 19:06:15 +08:00
[CPU] Preserve blocked leaf import waiters (#350)
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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<>);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user