From 0b1dea43e8f727d6238cbddf2be46c5c61734d96 Mon Sep 17 00:00:00 2001 From: Peter Bonanni Date: Fri, 17 Jul 2026 19:59:13 -0400 Subject: [PATCH] [CPU] Preserve blocked leaf import waiters (#350) --- .../Native/DirectExecutionBackend.Imports.cs | 6 +- .../Cpu/Native/DirectExecutionBackend.cs | 72 ++--------- src/SharpEmu.HLE/GuestThreadExecution.cs | 21 ---- ...estThreadBlockWaiterRepresentationTests.cs | 112 ++++++++++++++++++ 4 files changed, 126 insertions(+), 85 deletions(-) create mode 100644 tests/SharpEmu.Libs.Tests/Cpu/GuestThreadBlockWaiterRepresentationTests.cs diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs index b64177a2..f1b738e6 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs @@ -1345,8 +1345,7 @@ public sealed partial class DirectExecutionBackend out var blockContinuation, out var hasBlockContinuation, out var blockWakeKey, - out var blockResumeHandler, - out var blockWakeHandler, + out var blockWaiter, out var blockDeadlineTimestamp); if (consumedThreadBlock && TryYieldGuestThreadToHostStub(argPackPtr, dispatchIndex, returnRip, importStubEntry.Nid, blockReason)) @@ -1357,8 +1356,7 @@ public sealed partial class DirectExecutionBackend GuestThreadExecution.CurrentGuestThreadHandle, blockContinuation, blockWakeKey, - blockResumeHandler, - blockWakeHandler, + blockWaiter, blockDeadlineTimestamp); } diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs index 3ceceb1c..14489129 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs @@ -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. public IGuestThreadBlockWaiter? BlockWaiter { get; set; } - public Func? BlockResumeHandler { get; set; } - - public Func? BlockWakeHandler { get; set; } - public long BlockDeadlineTimestamp { get; set; } public long ImportCount; @@ -3215,43 +3211,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I thread.HasBlockedContinuation = true; thread.BlockWakeKey = wakeKey; 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? resumeHandler, - Func? 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; TraceFocusedContinuation( "register", @@ -3567,11 +3526,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I owner.State = GuestThreadRunState.Blocked; 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.BlockReason = null; - owner.BlockWakeHandler = null; owner.BlockDeadlineTimestamp = 0; } } @@ -3583,7 +3541,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } GuestCpuContinuation continuation = default; - Func? resumeHandler = null; + IGuestThreadBlockWaiter? blockWaiter = null; while (!ActiveForcedGuestExit) { WakeExpiredBlockedGuestThreads(); @@ -3604,9 +3562,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I owner.BlockedContinuation = default; owner.HasBlockedContinuation = false; owner.BlockWakeKey = null; - resumeHandler = owner.BlockResumeHandler; - owner.BlockResumeHandler = null; - owner.BlockWakeHandler = null; + blockWaiter = owner.BlockWaiter; + owner.BlockWaiter = null; owner.BlockDeadlineTimestamp = 0; owner.BlockReason = null; owner.State = GuestThreadRunState.Running; @@ -3629,9 +3586,9 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I 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) { @@ -3844,8 +3801,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I bool savedHasBlockedContinuation; GuestCpuContinuation savedBlockedContinuation; string? savedBlockWakeKey; - Func? savedBlockResumeHandler; - Func? savedBlockWakeHandler; + IGuestThreadBlockWaiter? savedBlockWaiter; long savedBlockDeadlineTimestamp; ulong exceptionStackBase; lock (_guestThreadGate) @@ -3981,8 +3937,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I savedHasBlockedContinuation = target.HasBlockedContinuation; savedBlockedContinuation = target.BlockedContinuation; savedBlockWakeKey = target.BlockWakeKey; - savedBlockResumeHandler = target.BlockResumeHandler; - savedBlockWakeHandler = target.BlockWakeHandler; + savedBlockWaiter = target.BlockWaiter; savedBlockDeadlineTimestamp = target.BlockDeadlineTimestamp; target.State = GuestThreadRunState.Running; @@ -3992,8 +3947,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I target.HasBlockedContinuation = false; target.BlockedContinuation = default; target.BlockWakeKey = null; - target.BlockResumeHandler = null; - target.BlockWakeHandler = null; + target.BlockWaiter = null; target.BlockDeadlineTimestamp = 0; } @@ -4041,8 +3995,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I target.HasBlockedContinuation = savedHasBlockedContinuation; target.BlockedContinuation = savedBlockedContinuation; target.BlockWakeKey = savedBlockWakeKey; - target.BlockResumeHandler = savedBlockResumeHandler; - target.BlockWakeHandler = savedBlockWakeHandler; + target.BlockWaiter = savedBlockWaiter; target.BlockDeadlineTimestamp = savedBlockDeadlineTimestamp; // 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. if (target.State == GuestThreadRunState.Blocked && target.HasBlockedContinuation && - target.BlockWakeHandler is not null && - target.BlockWakeHandler()) + target.BlockWaiter is not null && + target.BlockWaiter.TryWake()) { target.State = GuestThreadRunState.Ready; target.BlockReason = null; - target.BlockWakeHandler = null; target.BlockDeadlineTimestamp = 0; _readyGuestThreads.Enqueue(target); Interlocked.Increment(ref _readyGuestThreadCount); diff --git a/src/SharpEmu.HLE/GuestThreadExecution.cs b/src/SharpEmu.HLE/GuestThreadExecution.cs index 90d6f081..ac6b214d 100644 --- a/src/SharpEmu.HLE/GuestThreadExecution.cs +++ b/src/SharpEmu.HLE/GuestThreadExecution.cs @@ -398,27 +398,6 @@ public static class GuestThreadExecution return true; } - public static bool TryConsumeCurrentThreadBlock( - out string reason, - out GuestCpuContinuation continuation, - out bool hasContinuation, - out string wakeKey, - out Func? resumeHandler, - out Func? 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) { if (timeout <= TimeSpan.Zero) diff --git a/tests/SharpEmu.Libs.Tests/Cpu/GuestThreadBlockWaiterRepresentationTests.cs b/tests/SharpEmu.Libs.Tests/Cpu/GuestThreadBlockWaiterRepresentationTests.cs new file mode 100644 index 00000000..2859852d --- /dev/null +++ b/tests/SharpEmu.Libs.Tests/Cpu/GuestThreadBlockWaiterRepresentationTests.cs @@ -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<>); + } +}