mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 19:06:15 +08:00
864cbb0fa0
* [Core] Add POSIX native execution and PS5 SELF support Extend the native backend, guest TLS, fixed-address memory, and loader paths needed by PS5 titles on Windows, Linux, and macOS. Keep workstation GC so high-core-count hosts do not reserve over fixed guest image bases. * [HLE] Expand PS5 service and media compatibility Add the kernel, threading, save-data, networking, audio, video-codec, font, dialog, and service exports required by newer PS5 software. Preserve every SysAbi NID currently registered by main while adding the compatibility surface used by ASTRO BOT. * [AGC/Vulkan] Extend Gen5 shader and presentation support Expand PM4 handling, Gen5 shader translation, MRT and packed export support, guest image tracking, depth initialization, texture aliasing, and Vulkan presentation. Add the performance overlay and address-filtered diagnostics used to validate ASTRO BOT with original shaders. * [Core] Align static TLS reservation across hosts * [Pad] Align primary user ID with UserService * [Gpu] Preserve runtime scalar buffers across renderer seam * [AGC] Restore omitted command helper exports * [Vulkan] Reuse primary views for promoted MRT targets * [Vulkan] Preserve scratch storage bindings in compute dispatches
426 lines
16 KiB
C#
426 lines
16 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using System.Runtime.InteropServices;
|
|
using SharpEmu.HLE;
|
|
|
|
namespace SharpEmu.Core.Cpu.Native;
|
|
|
|
/// <summary>
|
|
/// POSIX replacements for the kernel32 helpers the native backend embeds in
|
|
/// emitted x86-64 code. Every stub exposed here follows the Win64 calling
|
|
/// convention the emitted call sites were written for (first argument in
|
|
/// ECX, result in RAX, Win64 non-volatile registers preserved), so the
|
|
/// emission code stays identical across platforms.
|
|
/// </summary>
|
|
internal static unsafe class PosixHostStubs
|
|
{
|
|
private static readonly object Gate = new();
|
|
private static bool _initialized;
|
|
private static nint _tlsGetValueStub;
|
|
private static nint _queryPerformanceCounterStub;
|
|
private static nint _switchToThreadStub;
|
|
private static nint _sleepStub;
|
|
|
|
public static nint TlsGetValueStubAddress
|
|
{
|
|
get { EnsureInitialized(); return _tlsGetValueStub; }
|
|
}
|
|
|
|
public static nint QueryPerformanceCounterStubAddress
|
|
{
|
|
get { EnsureInitialized(); return _queryPerformanceCounterStub; }
|
|
}
|
|
|
|
public static nint SwitchToThreadStubAddress
|
|
{
|
|
get { EnsureInitialized(); return _switchToThreadStub; }
|
|
}
|
|
|
|
public static nint SleepStubAddress
|
|
{
|
|
get { EnsureInitialized(); return _sleepStub; }
|
|
}
|
|
|
|
public static nint CreateWorkerEvent()
|
|
{
|
|
if (OperatingSystem.IsMacOS())
|
|
{
|
|
return dispatch_semaphore_create(0);
|
|
}
|
|
|
|
var semaphore = Marshal.AllocHGlobal(64);
|
|
if (sem_init(semaphore, 0, 0) != 0)
|
|
{
|
|
Marshal.FreeHGlobal(semaphore);
|
|
return 0;
|
|
}
|
|
|
|
return semaphore;
|
|
}
|
|
|
|
public static bool SignalWorkerEvent(nint handle)
|
|
{
|
|
if (OperatingSystem.IsMacOS())
|
|
{
|
|
_ = dispatch_semaphore_signal(handle);
|
|
return true;
|
|
}
|
|
|
|
return sem_post(handle) == 0;
|
|
}
|
|
|
|
public static bool WaitWorkerEvent(nint handle, int timeoutMilliseconds)
|
|
{
|
|
if (OperatingSystem.IsMacOS())
|
|
{
|
|
if (timeoutMilliseconds < 0)
|
|
{
|
|
return dispatch_semaphore_wait(handle, ulong.MaxValue) == 0;
|
|
}
|
|
|
|
var deadline = dispatch_time(0, timeoutMilliseconds * 1_000_000L);
|
|
return dispatch_semaphore_wait(handle, deadline) == 0;
|
|
}
|
|
|
|
if (timeoutMilliseconds < 0)
|
|
{
|
|
while (sem_wait(handle) != 0)
|
|
{
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
var deadlineTicks = Environment.TickCount64 + timeoutMilliseconds;
|
|
while (sem_trywait(handle) != 0)
|
|
{
|
|
if (Environment.TickCount64 >= deadlineTicks)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Thread.Sleep(1);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static void DestroyWorkerEvent(nint handle)
|
|
{
|
|
if (handle == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (OperatingSystem.IsMacOS())
|
|
{
|
|
dispatch_release(handle);
|
|
return;
|
|
}
|
|
|
|
_ = sem_destroy(handle);
|
|
Marshal.FreeHGlobal(handle);
|
|
}
|
|
|
|
/// <summary>Allocates a pthread TLS key, mirroring kernel32!TlsAlloc.</summary>
|
|
public static uint TlsAlloc()
|
|
{
|
|
if (OperatingSystem.IsMacOS())
|
|
{
|
|
nuint key;
|
|
return pthread_key_create_mac(&key, 0) == 0 ? (uint)key : uint.MaxValue;
|
|
}
|
|
|
|
uint key32;
|
|
return pthread_key_create_linux(&key32, 0) == 0 ? key32 : uint.MaxValue;
|
|
}
|
|
|
|
public static bool TlsFree(uint key)
|
|
{
|
|
return OperatingSystem.IsMacOS()
|
|
? pthread_key_delete_mac((nuint)key) == 0
|
|
: pthread_key_delete_linux(key) == 0;
|
|
}
|
|
|
|
public static bool TlsSetValue(uint key, nint value)
|
|
{
|
|
return OperatingSystem.IsMacOS()
|
|
? pthread_setspecific_mac((nuint)key, value) == 0
|
|
: pthread_setspecific_linux(key, value) == 0;
|
|
}
|
|
|
|
public static nint TlsGetValue(uint key)
|
|
{
|
|
return OperatingSystem.IsMacOS()
|
|
? pthread_getspecific_mac((nuint)key)
|
|
: pthread_getspecific_linux(key);
|
|
}
|
|
|
|
/// <summary>Stable numeric id of the calling thread (kernel32!GetCurrentThreadId).</summary>
|
|
public static uint GetCurrentThreadId()
|
|
{
|
|
if (OperatingSystem.IsMacOS())
|
|
{
|
|
ulong tid;
|
|
return pthread_threadid_np(0, &tid) == 0 ? unchecked((uint)tid) : 0u;
|
|
}
|
|
|
|
return unchecked((uint)gettid());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wraps a managed callback (compiled for the SysV ABI on POSIX .NET) in a
|
|
/// thunk that accepts up to four integer arguments in the Win64 ABI the
|
|
/// emitted x86-64 call sites use. Win64 passes args in rcx/rdx/r8/r9 and
|
|
/// treats rdi/rsi as non-volatile; SysV expects rdi/rsi/rdx/rcx and
|
|
/// clobbers them, so the thunk saves rdi/rsi, shuffles the registers, keeps
|
|
/// the stack 16-byte aligned for the call, and forwards the rax result.
|
|
/// </summary>
|
|
public static nint CreateWin64ToSysVThunk(nint sysvTarget)
|
|
{
|
|
var page = (byte*)HostMemory.Alloc(
|
|
null,
|
|
4096,
|
|
HostMemory.MEM_COMMIT | HostMemory.MEM_RESERVE,
|
|
HostMemory.PAGE_EXECUTE_READWRITE);
|
|
if (page == null)
|
|
{
|
|
throw new OutOfMemoryException("Failed to allocate Win64->SysV thunk page");
|
|
}
|
|
|
|
var offset = 0;
|
|
Emit(page, ref offset, 0x57); // push rdi
|
|
Emit(page, ref offset, 0x56); // push rsi
|
|
Emit(page, ref offset, 0x48, 0x89, 0xCF); // mov rdi, rcx
|
|
Emit(page, ref offset, 0x48, 0x89, 0xD6); // mov rsi, rdx
|
|
Emit(page, ref offset, 0x4C, 0x89, 0xC2); // mov rdx, r8
|
|
Emit(page, ref offset, 0x4C, 0x89, 0xC9); // mov rcx, r9
|
|
Emit(page, ref offset, 0x48, 0x83, 0xEC, 0x08); // sub rsp, 8 (realign to 16)
|
|
EmitMovRaxImm64(page, ref offset, sysvTarget); // mov rax, target
|
|
Emit(page, ref offset, 0xFF, 0xD0); // call rax
|
|
Emit(page, ref offset, 0x48, 0x83, 0xC4, 0x08); // add rsp, 8
|
|
Emit(page, ref offset, 0x5E); // pop rsi
|
|
Emit(page, ref offset, 0x5F); // pop rdi
|
|
Emit(page, ref offset, 0xC3); // ret
|
|
|
|
if (!HostMemory.Protect(page, 4096, HostMemory.PAGE_EXECUTE_READ, out _))
|
|
{
|
|
throw new InvalidOperationException("Failed to protect Win64->SysV thunk page");
|
|
}
|
|
|
|
HostMemory.FlushInstructionCache(page, (nuint)offset);
|
|
return (nint)page;
|
|
}
|
|
|
|
private static void EnsureInitialized()
|
|
{
|
|
if (_initialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
lock (Gate)
|
|
{
|
|
if (_initialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
BuildStubs();
|
|
_initialized = true;
|
|
}
|
|
}
|
|
|
|
private static void BuildStubs()
|
|
{
|
|
var page = (byte*)HostMemory.Alloc(
|
|
null,
|
|
4096,
|
|
HostMemory.MEM_COMMIT | HostMemory.MEM_RESERVE,
|
|
HostMemory.PAGE_EXECUTE_READWRITE);
|
|
if (page == null)
|
|
{
|
|
throw new OutOfMemoryException("Failed to allocate POSIX host helper stub page");
|
|
}
|
|
|
|
var offset = 0;
|
|
_tlsGetValueStub = EmitTlsGetValue(page, ref offset);
|
|
_queryPerformanceCounterStub = EmitQueryPerformanceCounter(page, ref offset);
|
|
_switchToThreadStub = EmitSwitchToThread(page, ref offset);
|
|
_sleepStub = EmitSleep(page, ref offset);
|
|
|
|
if (!HostMemory.Protect(page, 4096, HostMemory.PAGE_EXECUTE_READ, out _))
|
|
{
|
|
throw new InvalidOperationException("Failed to protect POSIX host helper stub page");
|
|
}
|
|
|
|
HostMemory.FlushInstructionCache(page, (nuint)offset);
|
|
}
|
|
|
|
private static nint EmitTlsGetValue(byte* page, ref int offset)
|
|
{
|
|
var start = (nint)(page + offset);
|
|
if (OperatingSystem.IsMacOS())
|
|
{
|
|
// On macOS x86-64 pthread keys index the gs-based thread specific
|
|
// data array directly, so TlsGetValue(index in ecx) collapses to a
|
|
// single load that clobbers nothing but RAX.
|
|
Emit(page, ref offset, 0x89, 0xC8); // mov eax, ecx
|
|
Emit(page, ref offset, 0x65, 0x48, 0x8B, 0x04, 0xC5, 0, 0, 0, 0); // mov rax, gs:[rax*8]
|
|
Emit(page, ref offset, 0xC3); // ret
|
|
return start;
|
|
}
|
|
|
|
// Linux: call pthread_getspecific, preserving the registers that are
|
|
// volatile in SysV but non-volatile in Win64 (rsi, rdi).
|
|
var pthreadGetSpecific = ResolveLibcExport("pthread_getspecific");
|
|
Emit(page, ref offset, 0x56); // push rsi
|
|
Emit(page, ref offset, 0x57); // push rdi
|
|
Emit(page, ref offset, 0x48, 0x83, 0xEC, 0x08); // sub rsp, 8
|
|
Emit(page, ref offset, 0x89, 0xCF); // mov edi, ecx
|
|
EmitMovRaxImm64(page, ref offset, pthreadGetSpecific); // mov rax, imm64
|
|
Emit(page, ref offset, 0xFF, 0xD0); // call rax
|
|
Emit(page, ref offset, 0x48, 0x83, 0xC4, 0x08); // add rsp, 8
|
|
Emit(page, ref offset, 0x5F); // pop rdi
|
|
Emit(page, ref offset, 0x5E); // pop rsi
|
|
Emit(page, ref offset, 0xC3); // ret
|
|
return start;
|
|
}
|
|
|
|
private static nint EmitQueryPerformanceCounter(byte* page, ref int offset)
|
|
{
|
|
// BOOL QueryPerformanceCounter(LARGE_INTEGER* out in rcx): the emitted
|
|
// consumers only need a monotonically increasing counter, which rdtsc
|
|
// provides without leaving Win64-safe registers.
|
|
var start = (nint)(page + offset);
|
|
Emit(page, ref offset, 0x0F, 0x31); // rdtsc
|
|
Emit(page, ref offset, 0x48, 0xC1, 0xE2, 0x20); // shl rdx, 32
|
|
Emit(page, ref offset, 0x48, 0x09, 0xD0); // or rax, rdx
|
|
Emit(page, ref offset, 0x48, 0x89, 0x01); // mov [rcx], rax
|
|
Emit(page, ref offset, 0xB8, 0x01, 0x00, 0x00, 0x00); // mov eax, 1
|
|
Emit(page, ref offset, 0xC3); // ret
|
|
return start;
|
|
}
|
|
|
|
private static nint EmitSwitchToThread(byte* page, ref int offset)
|
|
{
|
|
var schedYield = ResolveLibcExport("sched_yield");
|
|
var start = (nint)(page + offset);
|
|
Emit(page, ref offset, 0x56); // push rsi
|
|
Emit(page, ref offset, 0x57); // push rdi
|
|
Emit(page, ref offset, 0x48, 0x83, 0xEC, 0x08); // sub rsp, 8
|
|
EmitMovRaxImm64(page, ref offset, schedYield); // mov rax, imm64
|
|
Emit(page, ref offset, 0xFF, 0xD0); // call rax
|
|
Emit(page, ref offset, 0x48, 0x83, 0xC4, 0x08); // add rsp, 8
|
|
Emit(page, ref offset, 0x5F); // pop rdi
|
|
Emit(page, ref offset, 0x5E); // pop rsi
|
|
Emit(page, ref offset, 0xB8, 0x01, 0x00, 0x00, 0x00); // mov eax, 1
|
|
Emit(page, ref offset, 0xC3); // ret
|
|
return start;
|
|
}
|
|
|
|
private static nint EmitSleep(byte* page, ref int offset)
|
|
{
|
|
// void Sleep(DWORD milliseconds in ecx) -> usleep(microseconds in edi).
|
|
var usleep = ResolveLibcExport("usleep");
|
|
var start = (nint)(page + offset);
|
|
Emit(page, ref offset, 0x56); // push rsi
|
|
Emit(page, ref offset, 0x57); // push rdi
|
|
Emit(page, ref offset, 0x48, 0x83, 0xEC, 0x08); // sub rsp, 8
|
|
Emit(page, ref offset, 0x89, 0xCF); // mov edi, ecx
|
|
Emit(page, ref offset, 0x81, 0xFF, 0xFF, 0x0F, 0x00, 0x00); // cmp edi, 0xFFF
|
|
Emit(page, ref offset, 0x76, 0x05); // jbe +5
|
|
Emit(page, ref offset, 0xBF, 0xFF, 0x0F, 0x00, 0x00); // mov edi, 0xFFF (cap at ~4s)
|
|
Emit(page, ref offset, 0x69, 0xFF, 0xE8, 0x03, 0x00, 0x00); // imul edi, edi, 1000
|
|
EmitMovRaxImm64(page, ref offset, usleep); // mov rax, imm64
|
|
Emit(page, ref offset, 0xFF, 0xD0); // call rax
|
|
Emit(page, ref offset, 0x48, 0x83, 0xC4, 0x08); // add rsp, 8
|
|
Emit(page, ref offset, 0x5F); // pop rdi
|
|
Emit(page, ref offset, 0x5E); // pop rsi
|
|
Emit(page, ref offset, 0xC3); // ret
|
|
return start;
|
|
}
|
|
|
|
private static nint ResolveLibcExport(string name)
|
|
{
|
|
var libc = NativeLibrary.Load(OperatingSystem.IsMacOS() ? "libSystem.dylib" : "libc.so.6");
|
|
return NativeLibrary.GetExport(libc, name);
|
|
}
|
|
|
|
private static void Emit(byte* page, ref int offset, params byte[] bytes)
|
|
{
|
|
foreach (var value in bytes)
|
|
{
|
|
page[offset++] = value;
|
|
}
|
|
}
|
|
|
|
private static void EmitMovRaxImm64(byte* page, ref int offset, nint value)
|
|
{
|
|
Emit(page, ref offset, 0x48, 0xB8);
|
|
*(long*)(page + offset) = value;
|
|
offset += sizeof(long);
|
|
}
|
|
|
|
[DllImport("libc", EntryPoint = "pthread_key_create", SetLastError = true)]
|
|
private static extern int pthread_key_create_mac(nuint* key, nint destructor);
|
|
|
|
[DllImport("libc", EntryPoint = "pthread_key_create", SetLastError = true)]
|
|
private static extern int pthread_key_create_linux(uint* key, nint destructor);
|
|
|
|
[DllImport("libc", EntryPoint = "pthread_key_delete")]
|
|
private static extern int pthread_key_delete_mac(nuint key);
|
|
|
|
[DllImport("libc", EntryPoint = "pthread_key_delete")]
|
|
private static extern int pthread_key_delete_linux(uint key);
|
|
|
|
[DllImport("libc", EntryPoint = "pthread_setspecific")]
|
|
private static extern int pthread_setspecific_mac(nuint key, nint value);
|
|
|
|
[DllImport("libc", EntryPoint = "pthread_setspecific")]
|
|
private static extern int pthread_setspecific_linux(uint key, nint value);
|
|
|
|
[DllImport("libc", EntryPoint = "pthread_getspecific")]
|
|
private static extern nint pthread_getspecific_mac(nuint key);
|
|
|
|
[DllImport("libc", EntryPoint = "pthread_getspecific")]
|
|
private static extern nint pthread_getspecific_linux(uint key);
|
|
|
|
[DllImport("libc")]
|
|
private static extern int pthread_threadid_np(nint thread, ulong* threadId);
|
|
|
|
[DllImport("libc")]
|
|
private static extern int gettid();
|
|
|
|
[DllImport("libc")]
|
|
private static extern nint dispatch_semaphore_create(long value);
|
|
|
|
[DllImport("libc")]
|
|
private static extern nint dispatch_semaphore_signal(nint semaphore);
|
|
|
|
[DllImport("libc")]
|
|
private static extern nint dispatch_semaphore_wait(nint semaphore, ulong timeout);
|
|
|
|
[DllImport("libc")]
|
|
private static extern ulong dispatch_time(ulong when, long deltaNanoseconds);
|
|
|
|
[DllImport("libc")]
|
|
private static extern void dispatch_release(nint handle);
|
|
|
|
[DllImport("libc")]
|
|
private static extern int sem_init(nint semaphore, int shared, uint value);
|
|
|
|
[DllImport("libc")]
|
|
private static extern int sem_post(nint semaphore);
|
|
|
|
[DllImport("libc")]
|
|
private static extern int sem_wait(nint semaphore);
|
|
|
|
[DllImport("libc")]
|
|
private static extern int sem_trywait(nint semaphore);
|
|
|
|
[DllImport("libc")]
|
|
private static extern int sem_destroy(nint semaphore);
|
|
}
|