Fix UnmanagedCallersOnly boot crash & Core Engine Improvements (CPU, HLE, AGC) (#81)

* [agc] WAIT_REG_MEM suspend/resume, draw packet fixes, new HLE exports, debug cleanup

Rebased onto upstream 79a7437 (par274/sharpemu, rewritten history).

- GpuWaitRegistry: DCBs suspended on unsatisfied WAIT_REG_MEM are re-polled
  against guest memory on every submit; fixed 64-bit and standard packet parse
  offsets, apply the mask, treat PM4 compare function 0 as "always".
- TryReadSubmittedDrawCount: accept the 5-dword ItDrawIndex2 form emitted by
  DcbDrawIndex (count at +4); menu draws were silently discarded before.
- sceAgcDriverSubmitMultiDcbs: reversed ABI (rdi=address array, rsi=dword
  sizes, rdx=count).
- VideoOut: vblank events, sceVideoOutGetFlipStatus, buffers registered via
  sceVideoOutRegisterBuffers are valid flip targets.
- New HLE: libc stdio (fopen/fread/fseek/ftell/fclose/fgets), Dinkumware
  _Getpctype ctype table, NpTrophy2 stubs, AMPR PAK sequential-read tracker,
  MsgDialog lifecycle, NGS2 alt NIDs + dummy vtable for handle objects,
  guarded memset intrinsic, abort()/strcasecmp null-arg recovery.
- Removed investigation-only code (INT3 breakpoints, qfont/mcpp dumps,
  error-candidate printf traces, unconditional debug logs).

First rendered frame: Quake (PPSA01880) presents a 1920x1080 guest frame.

* Implemented a guarded native intrinsic (rep movsb) in DirectExecutionBackend to bypass HLE dispatch overhead, while preserving memory safety checks.

* [hle] clock_gettime clock ids, AudioOut2 canary fix, NID rebinds, new offline stubs

- clock_gettime (lLMT9vJAck0): support CLOCK_SECOND and the *_PRECISE/*_FAST
  variants instead of returning EINVAL, which games treated as fatal and
  retried in a tight loop.
- AudioOut2: context param writes shrunk to the guest-observed layout (the
  old 0x80-byte reset smashed the stack canary at +0x60 and killed audio
  init); ContextQueryMemory writes the single u64 the caller expects.
- NGS2: dropped wrong alt-NID aliases (they hash to sceImeUpdate,
  sceMouseRead, sceSystemGestureUpdateAllTouchRecognizer - now bound in
  their real libraries); added sceNgs2PanInit; fixed VoiceGetState NIDs.
- New verified stubs: sceUltInitialize, sceNpUniversalDataSystemDestroyHandle,
  sceNpGetOnlineId, sceNpGetNpReachabilityState, sceImeKeyboardOpen,
  sceImeKeyboardGetResourceId, sceMouseOpen, sceKernelAprGetFileSize.
- Import gateway: unwind guest workers at dispatch during backend teardown;
  env-gated SHARPEMU_LOG_THREAD_MODE tracing.

* [cpu] Isolate guest execution on native worker threads

Guest entry stubs no longer run above CLR-managed frames: each run is handed
to a pooled raw OS thread whose loop is emitted native code. While guest code
executes there is not a single managed frame on the thread and it stays in
preemptive GC mode, so the GC never walks a frame chain interleaved with
guest stubs that carry no CLR unwind info (the ReversePInvokeBadTransition /
UnmanagedCallersOnly FailFast class of crashes on pumped guest threads).

- NativeGuestExecutor: CreateThread + emitted run loop (WaitForSingleObject,
  UnmanagedCallersOnly prologue/epilogue, entry stub call, SetEvent). The
  prologue rebinds guest TLS, the host-RSP slot, thread affinity and the
  Active* ambient per run, so workers carry no guest identity and pool
  freely; the orchestrating managed thread parks in a preemptive wait.
- All three entry sites route through RunGuestEntryStub: guest thread
  entries, blocked-continuation resumes, and the main ExecuteEntry.
- Teardown stops workers before any executable stub or TLS index they
  reference is freed; a worker that will not stop leaks its loop instead of
  freeing running code.
- Kill switch: SHARPEMU_DISABLE_NATIVE_GUEST_WORKERS=1 restores the inline
  calli path.
This commit is contained in:
Foued Attar
2026-07-12 17:04:12 +02:00
committed by GitHub
parent d2fca37716
commit cdee77521e
14 changed files with 1168 additions and 199 deletions
@@ -19,6 +19,16 @@ public sealed partial class DirectExecutionBackend
private static ulong ImportDispatchGatewayManaged(nint backendHandle, int importIndex, nint argPackPtr)
{
if (LogThreadMode)
{
_threadModeGatewayCalls++;
_threadModeGatewayDepth++;
if (!_threadModeGatewayFirstLogged)
{
_threadModeGatewayFirstLogged = true;
TraceThreadMode($"gateway_first import={importIndex} total={_threadModeGatewayCalls}");
}
}
try
{
if (!(GCHandle.FromIntPtr(backendHandle).Target is DirectExecutionBackend directExecutionBackend))
@@ -36,6 +46,13 @@ public sealed partial class DirectExecutionBackend
$"[LOADER][ERROR] ImportDispatchGatewayManaged exception: {ex.GetType().Name}: {ex.Message}");
return 18446744071562199298uL;
}
finally
{
if (LogThreadMode)
{
_threadModeGatewayDepth--;
}
}
}
private unsafe static int RawVectoredHandlerManaged(void* exceptionInfo)
@@ -69,6 +86,12 @@ public sealed partial class DirectExecutionBackend
WriteCtxU64(contextRecord, 152, nextRsp);
WriteCtxU64(contextRecord, 248, returnRip);
Interlocked.Increment(ref _rawSentinelRecoveries);
if (LogThreadMode)
{
TraceThreadMode(
$"sentinel_recover rip=0x{value:X16} -> 0x{returnRip:X16} rsp=0x{rsp:X16} -> 0x{nextRsp:X16} " +
$"gateway_depth={_threadModeGatewayDepth}");
}
return -1;
}
return 0;
@@ -204,6 +227,17 @@ public sealed partial class DirectExecutionBackend
cpuContext[CpuRegister.Rax] = 1uL;
return 1uL;
}
// Backend teardown in progress: unwind this worker to the host instead of
// dispatching. RunGuestThread parks it as Blocked with no wake handler, its
// host thread exits, and RequestGuestThreadTeardown can finish before any
// executable memory is freed.
if (isGuestWorker &&
_guestTeardownRequested &&
TryYieldGuestThreadToHostStub(argPackPtr, num, num7, importStubEntry.Nid, "backend teardown"))
{
cpuContext[CpuRegister.Rax] = 0uL;
return 0uL;
}
bool flag0 = ShouldSuppressStrlenTrace(importStubEntry.Nid);
bool flag = num7 >= 2156221920u && num7 <= 2156225024u;
bool flag2 = num7 >= 2156351360u && num7 <= 2156352080u;