Astro Bot stack: VEH/TBB, title clear, swapchain fallback, Psml MFSR (#528)

* Cpu/Kernel: harden VEH trampoline and keep TBB on native workers

Route FastFail/CLR/stack-overflow around managed VEH, serialize managed
entry with a recursive spinlock, require native workers for tbb_thead,
and abandon pthread mutexes when a guest thread is torn down by worker
abort so splash waiters are not left holding locks forever.

* Cpu: soft-fail TBB native worker storms and cap concurrent Runs

Throwing on worker/prologue faults killed the process mid tbb_thead
burst (FailFast 0xC0000409). Soft-return 0x80020012, limit in-flight
native Runs (default 2), and keep prewarm small so back-to-back boots
do not need an artificial settle delay.

* Agc/VideoOut: poison-only empty-SRT reject and clear procedural ES/PS

Skip QueueSubmit only when Address-0 image slots remain; run the
Astro title clear pair via CmdClearColorImage so the pass executes
without descriptors that lose the device.

* VideoOut: recreate swapchain with fallback extent on 0x0 surface

Minimized Win32 surfaces report 0x0 / MaxImageExtent=0; deferring
recreate forever left an OutOfDate swapchain with no presents.
Clamp to last/default size and recreate instead of early-return.

* Psml: stub MFSR init/shared/context and dispatch packet size

Astro Bot asserts in GfxRenderStagePSSR when scePsmlMfsrInit is unresolved
(Mfsr initialized failed). Soft HLE for the MFSR shared-resource and
800M3_2 context path plus dispatch packet size lets boot pass splash to
first frame without claiming real upscaling.

* Psml: stub MFSR GetDispatchMfsrPacket900 for logo PSSR

Astro StartLevel ps_logo asserted GfxRenderStagePSSR.cpp:266 when
GetDispatchMfsrPacket900 (RUNLFro+qok) was unresolved. Return SCE_OK
from SizeInDwords so the 900 fill runs, soft-clear the guest packet
buffer, and register 1000/1100 siblings for the same ABI.
This commit is contained in:
Mike Saito
2026-07-23 12:37:44 +03:00
committed by GitHub
parent 7a108c6f87
commit 96fde5764f
13 changed files with 2472 additions and 162 deletions
@@ -153,6 +153,64 @@ public static class KernelPthreadCompatExports
static KernelPthreadCompatExports()
{
RunSynchronizationSelfChecks();
GuestThreadExecution.GuestThreadAbandoned += AbandonMutexesOwnedByThread;
}
/// <summary>
/// Force-release mutexes still owned by a guest thread that is being torn
/// down without a clean unlock (TBB worker_abort, abrupt exit). Otherwise
/// waiters can spin forever and block splash→first GPU submit.
/// </summary>
public static int AbandonMutexesOwnedByThread(ulong threadId, string reason)
{
if (threadId == 0)
{
return 0;
}
var released = 0;
var wakeKeys = new List<string>();
foreach (var pair in _mutexStates)
{
var state = pair.Value;
string? wakeKey = null;
lock (state)
{
if (state.OwnerThreadId != threadId || state.RecursionCount <= 0)
{
continue;
}
state.OwnerThreadId = 0;
state.RecursionCount = 0;
wakeKey = state.Waiters.First?.Value.Cooperative == true
? state.Waiters.First.Value.WakeKey
: null;
Monitor.PulseAll(state);
released++;
Console.Error.WriteLine(
$"[LOADER][WARN] pthread_mutex_abandon mutex=0x{pair.Key:X16} " +
$"owner={KernelPthreadState.DescribeThreadHandle(threadId)} " +
$"reason={reason} waiters={state.Waiters.Count}");
}
if (wakeKey is not null)
{
wakeKeys.Add(wakeKey);
}
}
foreach (var wakeKey in wakeKeys)
{
_ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(wakeKey, 1);
}
if (released > 0)
{
Console.Error.Flush();
}
return released;
}
[SysAbiExport(
+31 -3
View File
@@ -27,8 +27,12 @@ internal static class KernelPthreadState
internal static ulong GetCurrentThreadHandle()
{
var guestThreadHandle = GuestThreadExecution.CurrentGuestThreadHandle;
if (guestThreadHandle != 0 && TryGetThreadIdentity(guestThreadHandle, out _))
// Prefer the bound guest handle even when it is not yet in Threads.
// Falling through to a synthetic ThreadStatic handle while a guest
// thread is bound causes mutex owner mismatches (unlock PERM → hang).
if (guestThreadHandle != 0)
{
EnsureGuestThreadIdentity(guestThreadHandle);
return guestThreadHandle;
}
@@ -39,15 +43,27 @@ internal static class KernelPthreadState
internal static ulong GetCurrentThreadUniqueId()
{
var guestThreadHandle = GuestThreadExecution.CurrentGuestThreadHandle;
if (guestThreadHandle != 0 && TryGetThreadIdentity(guestThreadHandle, out var identity))
if (guestThreadHandle != 0)
{
return identity.UniqueId;
return EnsureGuestThreadIdentity(guestThreadHandle).UniqueId;
}
EnsureCurrentThreadRegistered();
return _currentThreadUniqueId;
}
internal static string DescribeThreadHandle(ulong threadHandle)
{
if (threadHandle == 0)
{
return "none";
}
return TryGetThreadIdentity(threadHandle, out var identity)
? $"0x{threadHandle:X16}('{identity.Name}')"
: $"0x{threadHandle:X16}";
}
internal static ulong CreateThreadHandle(string name)
{
var uniqueId = unchecked((ulong)Interlocked.Increment(ref _nextUniqueThreadId));
@@ -59,6 +75,18 @@ internal static class KernelPthreadState
return Threads.TryGetValue(threadHandle, out identity);
}
private static ThreadIdentity EnsureGuestThreadIdentity(ulong guestThreadHandle)
{
if (Threads.TryGetValue(guestThreadHandle, out var existing))
{
return existing;
}
var uniqueId = unchecked((ulong)Interlocked.Increment(ref _nextUniqueThreadId));
var identity = new ThreadIdentity(uniqueId, $"Guest-0x{guestThreadHandle:X}");
return Threads.GetOrAdd(guestThreadHandle, identity);
}
private static void EnsureCurrentThreadRegistered()
{
if (_currentThreadHandle != 0)