mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-18 22:42:23 +00:00
macOS (Apple Silicon) support: native build running under Rosetta 2 with MoltenVK (#102)
* macos: POSIX platform layer (host fault handler, virtual memory) - hostException: Mach/POSIX signal-based host fault handler mirroring the Windows vectored handler (SIGSEGV/SIGBUS/SIGILL), behind '#elif defined(__APPLE__)'. Windows and Linux branches are untouched. - sysLinuxVirtual: mach_vm_region-based is_mapped (no /proc/self/maps on macOS), PTHREAD_MUTEX_NORMAL, and a MAP_FIXED carve-in-place allocator that never leaves an unmapped hole for dyld/Rosetta/Metal to claim. All __APPLE__-guarded; the original Linux allocator path is preserved verbatim. - sysLinuxDbg/sysLinuxFileIO: libgen.h for basename(), and a POSIX opendir/readdir implementation of SysFileGetDents (previously a stub). * macos: guest kernel backing, threads, and POSIX libs - memory/memoryAddressSpace: anonymous shm_open backing (macOS has no memfd_create) and re-reserve-on-unmap so a guest MAP_FIXED remap never destroys a host mapping (__APPLE__-guarded). Drops host <sys/mman.h> MAP_* macros so the guest's constexpr MAP_* constants compile (no-op on Windows). - pthread: undef the host PTHREAD_STACK_MIN macro; on non-Windows, pin the stack-switch asm's guest rsp/rbp to callee-saved r14/r15 (the template clobbers r12/r13); __APPLE__ no-op for the absent pthread_condattr_setclock. The Windows stack-switch asm is byte-identical. - network: define SOCKET/INVALID_SOCKET for the non-Windows paths (were undefined at 20+ use sites), and rename the non-Windows kernel_clock_* helpers to the CamelCase names the callers already use. Both repair the shared non-Windows build. Integer reinterpret_cast -> static_cast. * loader: pin stack-switch asm operands to callee-saved registers RunEntry switches to the guest stack with an asm template that clobbers r12/r13 before consuming its inputs. With plain "r" constraints the compiler may place func/guest_rsp/guest_rbp into r12/r13, so 'callq *func' jumps through the saved host rsp -- a latent miscompile on every platform that any unrelated codegen change can trigger. Pin the inputs to rbx/r14/r15, which the template never touches and the SysV callee preserves. General correctness fix (not macOS-specific); the same fix is applied to pthread RunOnGuestStack. * macos: Vulkan/MoltenVK graphics enablement - pageManager: GPU write-tracking via Mach VM queries + mprotect (mprotect cannot report the previous protection, so the expected-old check is dropped), thread id via pthread_mach_thread_np, 4 KB page-size check -- __APPLE__-guarded. - shaders/renderDraw/vulkanWindow: MoltenVK lacks VK_EXT_color_write_enable, VK_EXT_depth_clip_enable and depthBounds; fall back to static color-write masks and default depth clipping, request VK_KHR_portability_subset, and reuse the graphics queue for present when only one queue is exposed. Non-Apple pipeline/extension setup is unchanged. - window: optional borderless window (KYTY_BORDERLESS) to sidestep a Rosetta NSException in macOS window chrome. * graphics: macOS thread identity for region tracking Region-ownership locks resolve the current thread via GetCurrentThreadId() on Windows and EXIT elsewhere. Use the Mach thread port on macOS (nonzero per-thread id; 0 stays the no-owner sentinel). * macos: marshal AppKit window operations to the main thread The present worker thread shows the window, updates its icon and title, and recreates a lost Vulkan surface. AppKit traps with 'Must only be used from the main thread' when these run off the main thread. Route them through a small task queue drained by the SDL main loop (an SDL_USEREVENT wakes the loop when it is blocked in SDL_WaitEvent). Title updates are fire-and-forget; showing the window and surface recreation wait for completion. Windows and Linux call the SDL functions directly, as before. * build: ignore the _Build output directory _Build is the conventional out-of-tree build location (only _Build/vscode-clang was ignored); a stray git add could sweep build artifacts into a commit. * macos: isolate stack-switch workaround --------- Co-authored-by: nmzik <Nmzik@mail.ru>
This commit is contained in:
+2
-1
@@ -2,4 +2,5 @@
|
||||
.vs/
|
||||
.idea/
|
||||
build/
|
||||
_Build/vscode-clang/
|
||||
_Build/vscode-clang/
|
||||
_Build/
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
#elif defined(__APPLE__)
|
||||
#include <csignal>
|
||||
#include <sys/ucontext.h>
|
||||
#endif
|
||||
|
||||
// IWYU pragma: no_include <errhandlingapi.h>
|
||||
@@ -118,6 +121,98 @@ static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
|
||||
return handler(info) ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
static std::atomic<Handler> g_handler {nullptr};
|
||||
static std::atomic_uint32_t g_install_state {0};
|
||||
static thread_local bool g_in_exception_filter = false;
|
||||
|
||||
static_assert(decltype(g_handler)::is_always_lock_free);
|
||||
static_assert(decltype(g_install_state)::is_always_lock_free);
|
||||
|
||||
[[noreturn]] static void FailFast(const char* reason) noexcept {
|
||||
std::fputs("HostException fail-fast: ", stderr);
|
||||
std::fputs(reason != nullptr ? reason : "unspecified", stderr);
|
||||
std::fputc('\n', stderr);
|
||||
std::fflush(stderr);
|
||||
std::_Exit(321);
|
||||
}
|
||||
|
||||
// Translate the x86-64 page-fault error code (mcontext __es.__err) into an access type.
|
||||
// bit 1 (0x2) = write, bit 4 (0x10) = instruction fetch, otherwise a read.
|
||||
static AccessViolationType DecodeAccess(uint64_t err) {
|
||||
if ((err & 0x10u) != 0) {
|
||||
return AccessViolationType::Execute;
|
||||
}
|
||||
if ((err & 0x2u) != 0) {
|
||||
return AccessViolationType::Write;
|
||||
}
|
||||
return AccessViolationType::Read;
|
||||
}
|
||||
|
||||
// POSIX signal handler that mirrors the Windows vectored handler: build an ExceptionInfo
|
||||
// from the mcontext and dispatch. A resolved fault (handler returns true) simply returns,
|
||||
// re-executing the faulting instruction against the now-fixed protection. An unresolved
|
||||
// fault restores the default disposition so the retry terminates the process.
|
||||
static void SignalHandler(int sig, siginfo_t* si, void* uctx) {
|
||||
if (g_in_exception_filter) {
|
||||
FailFast("nested exception while resolving a host fault");
|
||||
}
|
||||
g_in_exception_filter = true;
|
||||
|
||||
auto* uc = static_cast<ucontext_t*>(uctx);
|
||||
const auto* mc = uc->uc_mcontext;
|
||||
const auto& ss = mc->__ss;
|
||||
|
||||
ExceptionInfo info {};
|
||||
info.exception_address = ss.__rip;
|
||||
info.native_code = static_cast<uint32_t>(si->si_code);
|
||||
info.native_context = uctx;
|
||||
|
||||
if (sig == SIGILL) {
|
||||
info.type = ExceptionType::IllegalInstruction;
|
||||
} else {
|
||||
info.type = ExceptionType::AccessViolation;
|
||||
info.access_violation_type = DecodeAccess(mc->__es.__err);
|
||||
info.access_violation_vaddr = reinterpret_cast<uint64_t>(si->si_addr);
|
||||
}
|
||||
|
||||
info.rax = ss.__rax;
|
||||
info.rbx = ss.__rbx;
|
||||
info.rcx = ss.__rcx;
|
||||
info.rdx = ss.__rdx;
|
||||
info.rsi = ss.__rsi;
|
||||
info.rdi = ss.__rdi;
|
||||
info.rbp = ss.__rbp;
|
||||
info.rsp = ss.__rsp;
|
||||
info.r8 = ss.__r8;
|
||||
info.r9 = ss.__r9;
|
||||
info.r10 = ss.__r10;
|
||||
info.r11 = ss.__r11;
|
||||
info.r12 = ss.__r12;
|
||||
info.r13 = ss.__r13;
|
||||
info.r14 = ss.__r14;
|
||||
info.r15 = ss.__r15;
|
||||
|
||||
const auto handler = g_handler.load(std::memory_order_acquire);
|
||||
if (handler == nullptr) {
|
||||
FailFast("host exception callback is null");
|
||||
}
|
||||
|
||||
const bool resolved = handler(info);
|
||||
g_in_exception_filter = false;
|
||||
|
||||
if (resolved) {
|
||||
return; // retry the faulting instruction against the fixed mapping
|
||||
}
|
||||
|
||||
// Unresolved: restore the default action so the re-executed instruction terminates.
|
||||
struct sigaction dfl {};
|
||||
dfl.sa_handler = SIG_DFL;
|
||||
sigemptyset(&dfl.sa_mask);
|
||||
sigaction(sig, &dfl, nullptr);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool InstallHandler(Handler handler) {
|
||||
@@ -140,6 +235,36 @@ bool InstallHandler(Handler handler) {
|
||||
return false;
|
||||
}
|
||||
|
||||
g_install_state.store(2, std::memory_order_release);
|
||||
return true;
|
||||
#elif defined(__APPLE__)
|
||||
if (handler == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t expected_state = 0;
|
||||
if (!g_install_state.compare_exchange_strong(expected_state, 1, std::memory_order_acq_rel)) {
|
||||
return expected_state == 2 && g_handler.load(std::memory_order_acquire) == handler;
|
||||
}
|
||||
|
||||
g_handler.store(handler, std::memory_order_release);
|
||||
|
||||
struct sigaction sa {};
|
||||
sa.sa_sigaction = SignalHandler;
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
|
||||
// macOS raises SIGBUS for protection faults on some paths and SIGSEGV on others;
|
||||
// SIGILL covers instructions the host cannot execute (routed to the x64 emulator).
|
||||
bool ok = sigaction(SIGSEGV, &sa, nullptr) == 0 && sigaction(SIGBUS, &sa, nullptr) == 0 &&
|
||||
sigaction(SIGILL, &sa, nullptr) == 0;
|
||||
if (!ok) {
|
||||
g_handler.store(nullptr, std::memory_order_release);
|
||||
g_install_state.store(0, std::memory_order_release);
|
||||
printf("sigaction() failed to install the host fault handler\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_install_state.store(2, std::memory_order_release);
|
||||
return true;
|
||||
#else
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <utime.h>
|
||||
@@ -536,8 +537,26 @@ void SysFileFindFiles(const std::filesystem::path& /*path*/,
|
||||
EXIT("not implemented\n");
|
||||
}
|
||||
|
||||
void SysFileGetDents(const std::filesystem::path& /*path*/, std::vector<sys_dir_entry_t>& /*out*/) {
|
||||
EXIT("not implemented\n");
|
||||
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
|
||||
DIR* dir = opendir(path.c_str());
|
||||
if (dir == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
|
||||
sys_dir_entry_t r {};
|
||||
r.name = entry->d_name;
|
||||
if (entry->d_type == DT_REG) {
|
||||
r.is_file = true;
|
||||
} else if (entry->d_type == DT_DIR) {
|
||||
r.is_file = false;
|
||||
} else {
|
||||
// DT_UNKNOWN / symlink: resolve with stat.
|
||||
struct stat st {};
|
||||
r.is_file = (stat((path / entry->d_name).c_str(), &st) == 0) ? S_ISREG(st.st_mode) : true;
|
||||
}
|
||||
out.push_back(std::move(r));
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
bool SysFileCopyFile(const std::filesystem::path& /*src*/, const std::filesystem::path& /*dst*/) {
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
#include <pthread.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_vm.h>
|
||||
#endif
|
||||
|
||||
// IWYU pragma: no_include <asm/mman-common.h>
|
||||
// IWYU pragma: no_include <asm/mman.h>
|
||||
// IWYU pragma: no_include <bits/pthread_types.h>
|
||||
@@ -129,6 +134,32 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
|
||||
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
|
||||
ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
if (ptr != MAP_FAILED) {
|
||||
#if defined(__APPLE__)
|
||||
// Carve the aligned subrange out of the live mapping with MAP_FIXED (in-place
|
||||
// replacement) and trim the slack; never munmap the whole range first, or a
|
||||
// concurrent host mapping (dyld, Rosetta, Metal) could claim the hole and be
|
||||
// destroyed by the MAP_FIXED. Other platforms keep the original path below.
|
||||
auto aligned_addr = align_up(ret_addr, alignment);
|
||||
// NOLINTNEXTLINE
|
||||
void* fixed = mmap(reinterpret_cast<void*>(aligned_addr), size, protect,
|
||||
MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
if (fixed == MAP_FAILED) {
|
||||
munmap(ptr, size + alignment);
|
||||
ret_addr = 0;
|
||||
ptr = MAP_FAILED;
|
||||
} else {
|
||||
if (aligned_addr > ret_addr) {
|
||||
munmap(reinterpret_cast<void*>(ret_addr), aligned_addr - ret_addr);
|
||||
}
|
||||
const uintptr_t tail_start = aligned_addr + size;
|
||||
const uintptr_t resv_end = ret_addr + size + alignment;
|
||||
if (resv_end > tail_start) {
|
||||
munmap(reinterpret_cast<void*>(tail_start), resv_end - tail_start);
|
||||
}
|
||||
ptr = fixed;
|
||||
ret_addr = aligned_addr;
|
||||
}
|
||||
#else
|
||||
munmap(ptr, size + alignment);
|
||||
auto aligned_addr = align_up(ret_addr, alignment);
|
||||
#ifdef KYTY_FIXED_NOREPLACE
|
||||
@@ -146,6 +177,7 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
|
||||
ret_addr = 0;
|
||||
ptr = MAP_FAILED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,6 +197,27 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
|
||||
return ret_addr;
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// macOS has no /proc/self/maps; query the Mach VM map directly. mach_vm_region returns
|
||||
// the first mapped region at or above `region_addr`; if it begins before the end of the
|
||||
// requested range, the range overlaps an existing mapping.
|
||||
static bool is_mapped(void* ptr, size_t length) {
|
||||
auto query_addr = reinterpret_cast<mach_vm_address_t>(ptr);
|
||||
mach_vm_address_t region_addr = query_addr;
|
||||
mach_vm_size_t region_size = 0;
|
||||
vm_region_basic_info_data_64_t info {};
|
||||
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
|
||||
mach_port_t object_name = MACH_PORT_NULL;
|
||||
|
||||
kern_return_t kr =
|
||||
mach_vm_region(mach_task_self(), ®ion_addr, ®ion_size, VM_REGION_BASIC_INFO_64,
|
||||
reinterpret_cast<vm_region_info_t>(&info), &count, &object_name);
|
||||
if (kr != KERN_SUCCESS) {
|
||||
return false; // no region at or above the address → unmapped
|
||||
}
|
||||
return region_addr < (query_addr + length);
|
||||
}
|
||||
#else
|
||||
static bool is_mapped(void* ptr, size_t length) {
|
||||
FILE* file = fopen("/proc/self/maps", "r");
|
||||
char line[1024];
|
||||
@@ -189,6 +242,7 @@ static bool is_mapped(void* ptr, size_t length) {
|
||||
fclose(file);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
@@ -261,6 +315,33 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
|
||||
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
|
||||
ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
if (ptr != MAP_FAILED) {
|
||||
#if defined(__APPLE__)
|
||||
// Carve the aligned subrange out of the live reservation with MAP_FIXED (an
|
||||
// in-place replacement), then trim the slack. The range must never be
|
||||
// returned to the OS in between: another thread (dyld, Rosetta, Metal,
|
||||
// malloc) could claim the hole, and the subsequent MAP_FIXED would silently
|
||||
// destroy its mapping. Other platforms keep the original path below.
|
||||
auto aligned_addr = align_up(ret_addr, alignment);
|
||||
// NOLINTNEXTLINE
|
||||
void* fixed = mmap(reinterpret_cast<void*>(aligned_addr), size, PROT_NONE,
|
||||
MAP_FIXED | MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0);
|
||||
if (fixed == MAP_FAILED) {
|
||||
munmap(ptr, size + alignment);
|
||||
ret_addr = 0;
|
||||
ptr = MAP_FAILED;
|
||||
} else {
|
||||
if (aligned_addr > ret_addr) {
|
||||
munmap(reinterpret_cast<void*>(ret_addr), aligned_addr - ret_addr);
|
||||
}
|
||||
const uintptr_t tail_start = aligned_addr + size;
|
||||
const uintptr_t resv_end = ret_addr + size + alignment;
|
||||
if (resv_end > tail_start) {
|
||||
munmap(reinterpret_cast<void*>(tail_start), resv_end - tail_start);
|
||||
}
|
||||
ptr = fixed;
|
||||
ret_addr = aligned_addr;
|
||||
}
|
||||
#else
|
||||
munmap(ptr, size + alignment);
|
||||
auto aligned_addr = align_up(ret_addr, alignment);
|
||||
#ifdef KYTY_FIXED_NOREPLACE
|
||||
@@ -278,6 +359,7 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
|
||||
ret_addr = 0;
|
||||
ptr = MAP_FAILED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,12 @@
|
||||
#include <windows.h>
|
||||
#undef min
|
||||
#undef max
|
||||
#elif defined(__APPLE__)
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_vm.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -28,16 +34,55 @@ constexpr uint64_t PAGE_SIZE = TRACKER_PAGE_SIZE;
|
||||
constexpr uint64_t REGION_SIZE = TRACKER_REGION_SIZE;
|
||||
constexpr uint64_t ADDRESS_SIZE = TRACKER_ADDRESS_SIZE;
|
||||
constexpr uint64_t REGION_COUNT = ADDRESS_SIZE / REGION_SIZE;
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
// The tracker reuses Win32 memory-protection tags as internal page-state values (on
|
||||
// Windows they come from <windows.h> and are what VirtualQuery returns). Mirror the
|
||||
// canonical Win32 numeric values so the shared state-machine logic is identical.
|
||||
constexpr uint32_t PAGE_NOACCESS = 0x01;
|
||||
constexpr uint32_t PAGE_READONLY = 0x02;
|
||||
constexpr uint32_t PAGE_READWRITE = 0x04;
|
||||
#endif
|
||||
constexpr uint64_t REGION_PAGES = REGION_SIZE / PAGE_SIZE;
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
constexpr uint32_t NO_ACCESS_PROTECTION = PAGE_NOACCESS;
|
||||
constexpr uint32_t READ_ONLY_PROTECTION = PAGE_READONLY;
|
||||
constexpr uint32_t NO_ACCESS_PROTECTION = PAGE_NOACCESS;
|
||||
constexpr uint32_t READ_ONLY_PROTECTION = PAGE_READONLY;
|
||||
constexpr uint32_t READ_WRITE_PROTECTION = PAGE_READWRITE;
|
||||
#else
|
||||
constexpr uint32_t NO_ACCESS_PROTECTION = 0;
|
||||
constexpr uint32_t READ_ONLY_PROTECTION = 1;
|
||||
constexpr uint32_t READ_WRITE_PROTECTION = 2;
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// Map the tracker's Win32-style protection tags to POSIX mprotect flags.
|
||||
static int PageProtToPosix(uint32_t protection) {
|
||||
switch (protection) {
|
||||
case PAGE_NOACCESS: return PROT_NONE;
|
||||
case PAGE_READONLY: return PROT_READ;
|
||||
case PAGE_READWRITE: return PROT_READ | PROT_WRITE;
|
||||
default: return PROT_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
// Query the current protection of the page containing vaddr via the Mach VM map and
|
||||
// collapse it to the tracker's read/write tags (execute is irrelevant to write tracking).
|
||||
static uint32_t MachQueryPageProt(uint64_t vaddr) {
|
||||
auto region_addr = static_cast<mach_vm_address_t>(vaddr);
|
||||
mach_vm_size_t region_size = 0;
|
||||
vm_region_basic_info_data_64_t info {};
|
||||
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
|
||||
mach_port_t object_name = MACH_PORT_NULL;
|
||||
|
||||
kern_return_t kr =
|
||||
mach_vm_region(mach_task_self(), ®ion_addr, ®ion_size, VM_REGION_BASIC_INFO_64,
|
||||
reinterpret_cast<vm_region_info_t>(&info), &count, &object_name);
|
||||
if (kr != KERN_SUCCESS || region_addr > vaddr) {
|
||||
return PAGE_NOACCESS; // no region covering vaddr
|
||||
}
|
||||
if ((info.protection & VM_PROT_WRITE) != 0) {
|
||||
return PAGE_READWRITE;
|
||||
}
|
||||
if ((info.protection & VM_PROT_READ) != 0) {
|
||||
return PAGE_READONLY;
|
||||
}
|
||||
return PAGE_NOACCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
thread_local bool g_in_fault_resolution = false;
|
||||
@@ -78,6 +123,8 @@ thread_local bool g_in_fault_resolution = false;
|
||||
uint32_t CurrentThread() noexcept {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
return GetCurrentThreadId();
|
||||
#elif defined(__APPLE__)
|
||||
return static_cast<uint32_t>(pthread_mach_thread_np(pthread_self()));
|
||||
#else
|
||||
FailFast();
|
||||
#endif
|
||||
@@ -145,6 +192,12 @@ struct PageManager::Impl {
|
||||
Fatal("unsupported host page size 0x%08" PRIx32,
|
||||
static_cast<uint32_t>(info.dwPageSize));
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
// Under Rosetta the host page size is 4 KB, matching TRACKER_PAGE_SIZE.
|
||||
if (static_cast<uint64_t>(getpagesize()) != PAGE_SIZE) {
|
||||
Fatal("unsupported host page size 0x%08" PRIx32,
|
||||
static_cast<uint32_t>(getpagesize()));
|
||||
}
|
||||
#else
|
||||
Fatal("page-fault invalidation is not implemented on this platform");
|
||||
#endif
|
||||
@@ -225,6 +278,14 @@ struct PageManager::Impl {
|
||||
vaddr, static_cast<uint32_t>(info.State), static_cast<uint32_t>(info.Protect));
|
||||
}
|
||||
return info.Protect;
|
||||
#elif defined(__APPLE__)
|
||||
const uint32_t protection = MachQueryPageProt(vaddr);
|
||||
if (protection != PAGE_READWRITE) {
|
||||
Fatal("basic path requires PAGE_READWRITE at 0x%016" PRIx64 " (protection=0x%08" PRIx32
|
||||
")",
|
||||
vaddr, protection);
|
||||
}
|
||||
return protection;
|
||||
#else
|
||||
(void)vaddr;
|
||||
Fatal("page query is unsupported on this platform");
|
||||
@@ -245,6 +306,14 @@ struct PageManager::Impl {
|
||||
case PageFaultAccess::Write: return info.Protect == PAGE_READWRITE;
|
||||
default: return false;
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
const uint32_t protection = MachQueryPageProt(vaddr);
|
||||
switch (access) {
|
||||
case PageFaultAccess::Read:
|
||||
return protection == PAGE_READONLY || protection == PAGE_READWRITE;
|
||||
case PageFaultAccess::Write: return protection == PAGE_READWRITE;
|
||||
default: return false;
|
||||
}
|
||||
#else
|
||||
(void)vaddr;
|
||||
return false;
|
||||
@@ -265,6 +334,18 @@ struct PageManager::Impl {
|
||||
", expected=0x%08" PRIx32 ", new=0x%08" PRIx32,
|
||||
vaddr, static_cast<uint32_t>(old_protection), expected_old, protection);
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
// mprotect cannot report the previous protection, so the expected_old comparison
|
||||
// is dropped; the tracker is the sole mutator of these pages and drives the
|
||||
// transition from its own shadow state.
|
||||
(void)expected_old;
|
||||
if (mprotect(reinterpret_cast<void*>(static_cast<uintptr_t>(vaddr)), PAGE_SIZE,
|
||||
PageProtToPosix(protection)) != 0) {
|
||||
if (fault_path) {
|
||||
FailFast("mprotect fault transition failed");
|
||||
}
|
||||
Fatal("mprotect failed at 0x%016" PRIx64 ", new=0x%08" PRIx32, vaddr, protection);
|
||||
}
|
||||
#else
|
||||
(void)vaddr;
|
||||
(void)protection;
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include <windows.h>
|
||||
#undef min
|
||||
#undef max
|
||||
#elif defined(__APPLE__)
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -49,6 +51,9 @@ private:
|
||||
static uint32_t CurrentThread() noexcept {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
return GetCurrentThreadId();
|
||||
#elif defined(__APPLE__)
|
||||
// mach thread port is a nonzero per-thread id (0 is the "no owner" sentinel).
|
||||
return static_cast<uint32_t>(pthread_mach_thread_np(pthread_self()));
|
||||
#else
|
||||
EXIT("region tracking thread identity is unsupported on this platform\n");
|
||||
#endif
|
||||
|
||||
@@ -395,6 +395,10 @@ static void SetDynamicParams(const RenderCommandBuffer& buffer, vk::CommandBuffe
|
||||
dynamic_params.stencil_back.reference);
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// MoltenVK has no VK_EXT_color_write_enable; the pipeline is created without the
|
||||
// eColorWriteEnableEXT dynamic state and relies on the static colorWriteMask instead.
|
||||
#else
|
||||
vk::Bool32 enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
for (uint32_t i = 0; i < dynamic_params.color_write_count; i++) {
|
||||
enable[i] = (dynamic_params.color_write_enable[i] ? VK_TRUE : VK_FALSE);
|
||||
@@ -402,6 +406,7 @@ static void SetDynamicParams(const RenderCommandBuffer& buffer, vk::CommandBuffe
|
||||
if (dynamic_params.color_write_count != 0) {
|
||||
vk_buffer.setColorWriteEnableEXT(dynamic_params.color_write_count, enable);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool DrawHasValidVertexShader(const HW::Shader& sh_ctx) {
|
||||
|
||||
@@ -729,7 +729,13 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
|
||||
vk::PipelineRasterizationStateCreateInfo rasterizer {};
|
||||
rasterizer.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo;
|
||||
// MoltenVK lacks VK_EXT_depth_clip_enable; omit the depth-clip struct on macOS and accept
|
||||
// Vulkan's default depth clipping (enabled) instead of the PS5's clamp behavior.
|
||||
#if defined(__APPLE__)
|
||||
rasterizer.pNext = nullptr;
|
||||
#else
|
||||
rasterizer.pNext = &clip_ext;
|
||||
#endif
|
||||
rasterizer.flags = {};
|
||||
rasterizer.depthClampEnable = VK_FALSE;
|
||||
rasterizer.rasterizerDiscardEnable = VK_FALSE;
|
||||
@@ -807,7 +813,13 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
|
||||
vk::PipelineColorBlendStateCreateInfo color_blending {};
|
||||
color_blending.sType = vk::StructureType::ePipelineColorBlendStateCreateInfo;
|
||||
// MoltenVK lacks VK_EXT_color_write_enable; drop the dynamic color-write struct on macOS
|
||||
// and rely on each attachment's static colorWriteMask (all channels enabled by default).
|
||||
#if defined(__APPLE__)
|
||||
color_blending.pNext = nullptr;
|
||||
#else
|
||||
color_blending.pNext = &color_write;
|
||||
#endif
|
||||
color_blending.flags = {};
|
||||
color_blending.logicOpEnable = VK_FALSE;
|
||||
color_blending.logicOp = vk::LogicOp::eCopy;
|
||||
@@ -873,7 +885,11 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
depth_stencil_info.depthWriteEnable = (static_params.depth_write_enable ? VK_TRUE : VK_FALSE);
|
||||
depth_stencil_info.depthCompareOp = static_params.depth_compare_op;
|
||||
depth_stencil_info.depthBoundsTestEnable =
|
||||
#if defined(__APPLE__)
|
||||
VK_FALSE; // MoltenVK lacks the depthBounds feature; depth-bounds testing is disabled
|
||||
#else
|
||||
(static_params.depth_bounds_test_enable ? VK_TRUE : VK_FALSE);
|
||||
#endif
|
||||
depth_stencil_info.stencilTestEnable = (static_params.stencil_test_enable ? VK_TRUE : VK_FALSE);
|
||||
depth_stencil_info.front.failOp = static_params.stencil_front.failOp;
|
||||
depth_stencil_info.front.passOp = static_params.stencil_front.passOp;
|
||||
@@ -893,7 +909,9 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
vk::DynamicState::eStencilCompareMask,
|
||||
vk::DynamicState::eStencilReference,
|
||||
vk::DynamicState::eStencilWriteMask,
|
||||
vk::DynamicState::eColorWriteEnableEXT,
|
||||
#if !defined(__APPLE__)
|
||||
vk::DynamicState::eColorWriteEnableEXT, // unsupported by MoltenVK; static mask instead
|
||||
#endif
|
||||
};
|
||||
const auto dynamic_states_count =
|
||||
static_cast<uint32_t>(sizeof(dynamic_states) / sizeof(dynamic_states[0]));
|
||||
|
||||
@@ -586,7 +586,13 @@ void Swapchain::RefreshSurfaceSize() {
|
||||
void Swapchain::Recreate(bool surface_lost) {
|
||||
Destroy();
|
||||
if (surface_lost) {
|
||||
#if defined(__APPLE__)
|
||||
// Surface recreation goes through SDL_Vulkan_CreateSurface, which touches the
|
||||
// window's view/layer and must run on the main thread on macOS.
|
||||
m_window.RunOnMainThread([this] { m_window.RecreateSurface(); }, true);
|
||||
#else
|
||||
m_window.RecreateSurface();
|
||||
#endif
|
||||
}
|
||||
RefreshSurfaceSize();
|
||||
Create();
|
||||
@@ -797,9 +803,20 @@ void Presenter::Present(Frame& frame, bool reuse) {
|
||||
|
||||
auto& window = m_impl->window;
|
||||
if (window.window_hidden) {
|
||||
#if defined(__APPLE__)
|
||||
// AppKit traps if a window is shown off the main thread; marshal and wait so the
|
||||
// swapchain below is recreated against a visible window.
|
||||
window.RunOnMainThread(
|
||||
[&window] {
|
||||
window.UpdateIcon();
|
||||
SDL_ShowWindow(window.window);
|
||||
},
|
||||
true);
|
||||
#else
|
||||
window.UpdateIcon();
|
||||
|
||||
SDL_ShowWindow(window.window);
|
||||
#endif
|
||||
|
||||
window.window_hidden = false;
|
||||
m_impl->RecoverSwapchain(Swapchain::Status::Recreate);
|
||||
|
||||
@@ -217,7 +217,9 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
|
||||
if (color_write_ext.colorWriteEnable != VK_TRUE) {
|
||||
LOGF("colorWriteEnable is not supported\n");
|
||||
#if !defined(__APPLE__)
|
||||
skip_device = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (depth_clip_control.depthClipControl != VK_TRUE) {
|
||||
@@ -226,7 +228,9 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
}
|
||||
if (depth_clip_enable.depthClipEnable != VK_TRUE) {
|
||||
LOGF("depthClipEnable is not supported\n");
|
||||
#if !defined(__APPLE__)
|
||||
skip_device = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (features12.samplerMirrorClampToEdge != VK_TRUE) {
|
||||
@@ -271,7 +275,9 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
}
|
||||
if (device_features2.features.depthBounds != VK_TRUE) {
|
||||
LOGF("depthBounds is not supported\n");
|
||||
#if !defined(__APPLE__)
|
||||
skip_device = true;
|
||||
#endif
|
||||
}
|
||||
if (device_features2.features.shaderStorageImageWriteWithoutFormat != VK_TRUE) {
|
||||
LOGF("shaderStorageImageWriteWithoutFormat is not supported\n");
|
||||
@@ -494,7 +500,14 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
|
||||
vk::PhysicalDeviceDepthClipControlFeaturesEXT depth_clip_control {};
|
||||
depth_clip_control.sType = vk::StructureType::ePhysicalDeviceDepthClipControlFeaturesEXT;
|
||||
// MoltenVK lacks VK_EXT_depth_clip_enable and VK_EXT_color_write_enable, so drop those
|
||||
// feature structs from the chain on macOS (the renderer falls back to default depth
|
||||
// clipping and static color-write masks).
|
||||
#if defined(__APPLE__)
|
||||
depth_clip_control.pNext = nullptr;
|
||||
#else
|
||||
depth_clip_control.pNext = &depth_clip_enable;
|
||||
#endif
|
||||
depth_clip_control.depthClipControl = VK_TRUE;
|
||||
|
||||
vk::PhysicalDeviceVulkan12Features features12 {};
|
||||
@@ -541,7 +554,9 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
device_features.fragmentStoresAndAtomics = VK_TRUE;
|
||||
device_features.samplerAnisotropy = VK_TRUE;
|
||||
device_features.robustBufferAccess = VK_TRUE;
|
||||
device_features.depthBounds = VK_TRUE;
|
||||
#if !defined(__APPLE__)
|
||||
device_features.depthBounds = VK_TRUE; // unsupported by MoltenVK
|
||||
#endif
|
||||
device_features.shaderStorageImageWriteWithoutFormat = VK_TRUE;
|
||||
device_features.shaderStorageImageReadWithoutFormat = VK_TRUE;
|
||||
device_features.shaderImageGatherExtended = VK_TRUE;
|
||||
@@ -894,10 +909,20 @@ void WindowContext::CreateVulkan() {
|
||||
}
|
||||
surface = native_surface;
|
||||
|
||||
std::vector<const char*> device_extensions = {
|
||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME,
|
||||
VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME, VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME,
|
||||
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, "VK_KHR_maintenance1"};
|
||||
std::vector<const char*> device_extensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME,
|
||||
VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME,
|
||||
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,
|
||||
"VK_KHR_maintenance1"};
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// MoltenVK lacks VK_EXT_depth_clip_enable and VK_EXT_color_write_enable; the renderer
|
||||
// falls back to default depth clipping and static color-write masks on macOS. It also
|
||||
// requires VK_KHR_portability_subset per the Vulkan portability spec.
|
||||
device_extensions.push_back("VK_KHR_portability_subset");
|
||||
#else
|
||||
device_extensions.push_back(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME);
|
||||
device_extensions.push_back(VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME);
|
||||
#endif
|
||||
|
||||
#ifdef KYTY_ENABLE_DEBUG_PRINTF
|
||||
if (Config::SpirvDebugPrintfEnabled()) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "graphics/presentation/window.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "SDL.h"
|
||||
#include "SDL_error.h"
|
||||
#include "SDL_events.h"
|
||||
@@ -697,6 +699,52 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
void WindowContext::RunOnMainThread(std::function<void()> task, bool wait) {
|
||||
if (Common::Thread::IsMainThread()) {
|
||||
task();
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t ticket = 0;
|
||||
{
|
||||
Common::LockGuard lock(main_task_mutex);
|
||||
main_tasks.push_back(std::move(task));
|
||||
ticket = ++main_tasks_queued;
|
||||
}
|
||||
|
||||
// Wake the main loop in case it is blocked in SDL_WaitEvent.
|
||||
SDL_Event event {};
|
||||
event.type = SDL_USEREVENT;
|
||||
SDL_PushEvent(&event);
|
||||
|
||||
if (!wait) {
|
||||
return;
|
||||
}
|
||||
Common::LockGuard lock(main_task_mutex);
|
||||
while (main_tasks_run < ticket) {
|
||||
main_task_done.Wait(&main_task_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowContext::DrainMainThreadTasks() {
|
||||
std::vector<std::function<void()>> tasks;
|
||||
{
|
||||
Common::LockGuard lock(main_task_mutex);
|
||||
tasks.swap(main_tasks);
|
||||
}
|
||||
if (tasks.empty()) {
|
||||
return;
|
||||
}
|
||||
for (auto& task: tasks) {
|
||||
task();
|
||||
}
|
||||
Common::LockGuard lock(main_task_mutex);
|
||||
main_tasks_run += tasks.size();
|
||||
main_task_done.SignalAll();
|
||||
}
|
||||
#endif
|
||||
|
||||
void WindowContext::Run() {
|
||||
Common::Timer timer;
|
||||
timer.Start();
|
||||
@@ -706,6 +754,9 @@ void WindowContext::Run() {
|
||||
loop.paused.store(false, std::memory_order_release);
|
||||
|
||||
while (!loop.need_exit) {
|
||||
#if defined(__APPLE__)
|
||||
DrainMainThreadTasks();
|
||||
#endif
|
||||
if (SDL_PollEvent(&loop.event) != 0) {
|
||||
ProcessEvent(timer.GetTimeS());
|
||||
continue;
|
||||
@@ -747,9 +798,18 @@ static void WindowCreate(WindowContext& context) {
|
||||
|
||||
LOGF("WindowCreate(): width = %d, height = %d\n", width, height);
|
||||
|
||||
uint32_t window_flags = KYTY_SDL_WINDOW_FLAGS;
|
||||
#if defined(__APPLE__)
|
||||
// macOS 26 window chrome (CoreUI asset decode, SwiftUI titlebar) has been observed
|
||||
// throwing NSExceptions under Rosetta during the first CATransaction commit. A
|
||||
// borderless window skips that machinery entirely.
|
||||
if (std::getenv("KYTY_BORDERLESS") != nullptr) {
|
||||
window_flags |= static_cast<uint32_t>(SDL_WINDOW_BORDERLESS);
|
||||
}
|
||||
#endif
|
||||
context.window =
|
||||
SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, KYTY_SDL_WINDOWPOS_CENTERED,
|
||||
KYTY_SDL_WINDOWPOS_CENTERED, width, height, KYTY_SDL_WINDOW_FLAGS);
|
||||
KYTY_SDL_WINDOWPOS_CENTERED, width, height, window_flags);
|
||||
|
||||
context.window_hidden = true;
|
||||
|
||||
@@ -896,7 +956,13 @@ void WindowContext::UpdateTitle() {
|
||||
(has_app_ver ? " " : ""), device_name, processor_name,
|
||||
frame_num, current_fps);
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// AppKit traps on title changes off the main thread; fire-and-forget keeps present pacing.
|
||||
RunOnMainThread([this, fps = std::move(fps)] { SDL_SetWindowTitle(window, fps.c_str()); },
|
||||
false);
|
||||
#else
|
||||
SDL_SetWindowTitle(window, fps.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <functional>
|
||||
#endif
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class Presenter;
|
||||
@@ -47,6 +51,14 @@ struct WindowContext {
|
||||
void ProcessEvent(double time_seconds);
|
||||
void Run();
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// AppKit only allows window operations (show, icon, title, view/layer changes) on the
|
||||
// main thread; the present thread marshals them through the SDL main loop with these.
|
||||
// wait=true blocks until the task has run on the main thread.
|
||||
void RunOnMainThread(std::function<void()> task, bool wait);
|
||||
void DrainMainThreadTasks();
|
||||
#endif
|
||||
|
||||
GraphicContext graphic_ctx;
|
||||
SDL_Window* window = nullptr;
|
||||
bool window_hidden = true;
|
||||
@@ -60,6 +72,14 @@ struct WindowContext {
|
||||
char processor_name[64] = {0};
|
||||
|
||||
Common::Mutex mutex;
|
||||
|
||||
#if defined(__APPLE__)
|
||||
Common::Mutex main_task_mutex;
|
||||
Common::CondVar main_task_done;
|
||||
std::vector<std::function<void()>> main_tasks; // guarded by main_task_mutex
|
||||
uint64_t main_tasks_queued = 0; // guarded by main_task_mutex
|
||||
uint64_t main_tasks_run = 0; // guarded by main_task_mutex
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -191,9 +191,15 @@ public:
|
||||
SetFailure(failure_reason, FailureReason::ReserveAlignedFailed);
|
||||
return 0;
|
||||
}
|
||||
#if !defined(__APPLE__)
|
||||
// On macOS the PROT_NONE reservation is left in place: MapView's MAP_FIXED
|
||||
// replaces it atomically, so the range never becomes a hole that dyld,
|
||||
// Rosetta, or Metal background threads could claim (which the subsequent
|
||||
// MAP_FIXED would then silently destroy).
|
||||
if (!ReleaseReservedProbe(reserved, failure_reason)) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
if (MapFixed(reserved, size, backing_offset, mode, failure_reason)) {
|
||||
return reserved;
|
||||
}
|
||||
@@ -1061,7 +1067,17 @@ private:
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static bool UnmapView(void* vaddr, uint64_t size) { return munmap(vaddr, size) == 0; }
|
||||
static bool UnmapView(void* vaddr, uint64_t size) {
|
||||
#if defined(__APPLE__)
|
||||
// Re-reserve instead of freeing: the guest may MAP_FIXED-remap this range
|
||||
// later, and that must never destroy a host mapping (dyld, Rosetta, Metal)
|
||||
// that moved into the hole in the meantime.
|
||||
return mmap(vaddr, size, PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANON | MAP_NORESERVE,
|
||||
-1, 0) != MAP_FAILED;
|
||||
#else
|
||||
return munmap(vaddr, size) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool UnmapViewPreservePlaceholder(void* /*vaddr*/, uint64_t /*size*/) { return false; }
|
||||
|
||||
|
||||
+30
-4
@@ -58,6 +58,12 @@ namespace LibKernel {
|
||||
|
||||
LIB_NAME("libkernel", "libkernel");
|
||||
|
||||
// macOS's <pthread.h>/<limits.h> define PTHREAD_STACK_MIN as a macro; the emulator
|
||||
// wants its own guest-side value with the same name, so drop the host macro here.
|
||||
#ifdef PTHREAD_STACK_MIN
|
||||
#undef PTHREAD_STACK_MIN
|
||||
#endif
|
||||
|
||||
constexpr int KEYS_MAX = 256;
|
||||
constexpr int DESTRUCTOR_ITERATIONS = 4;
|
||||
constexpr size_t PTHREAD_STACK_DEFAULT = 0x100000;
|
||||
@@ -795,9 +801,9 @@ static KYTY_SYSV_ABI void* RunOnGuestStack(void* arg, pthread_entry_func_t func,
|
||||
}
|
||||
|
||||
// The guest ABI expects the entry argument in rdi and a 16-byte aligned stack before call.
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
asm volatile("pushq %%r12\n\t"
|
||||
"pushq %%r13\n\t"
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
"pushq %%r14\n\t"
|
||||
"pushq %%r15\n\t"
|
||||
"movq %%gs:0x08, %%r14\n\t"
|
||||
@@ -805,7 +811,6 @@ static KYTY_SYSV_ABI void* RunOnGuestStack(void* arg, pthread_entry_func_t func,
|
||||
"xorq %%rcx, %%rcx\n\t"
|
||||
"movq %%rcx, %%gs:0x08\n\t"
|
||||
"movq %%rcx, %%gs:0x10\n\t"
|
||||
#endif
|
||||
"movq %%rsp, %%r12\n\t"
|
||||
"movq %%rbp, %%r13\n\t"
|
||||
"movq %[guest_rsp], %%rsp\n\t"
|
||||
@@ -813,12 +818,10 @@ static KYTY_SYSV_ABI void* RunOnGuestStack(void* arg, pthread_entry_func_t func,
|
||||
"callq *%%rsi\n\t"
|
||||
"movq %%r13, %%rbp\n\t"
|
||||
"movq %%r12, %%rsp\n\t"
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
"movq %%r14, %%gs:0x08\n\t"
|
||||
"movq %%r15, %%gs:0x10\n\t"
|
||||
"popq %%r15\n\t"
|
||||
"popq %%r14\n\t"
|
||||
#endif
|
||||
"popq %%r13\n\t"
|
||||
"popq %%r12\n\t"
|
||||
: "=a"(ret), "+D"(arg), "+S"(func)
|
||||
@@ -826,6 +829,29 @@ static KYTY_SYSV_ABI void* RunOnGuestStack(void* arg, pthread_entry_func_t func,
|
||||
: "cc", "memory", "rcx", "rdx", "r8", "r9", "r10", "r11", "xmm0", "xmm1", "xmm2",
|
||||
"xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11",
|
||||
"xmm12", "xmm13", "xmm14", "xmm15");
|
||||
#else
|
||||
// Pin the stack operands to registers the template never touches (r14/r15 are
|
||||
// SysV callee-saved, so guest code preserves them): plain "r" constraints may
|
||||
// allocate them into r12/r13, which the template overwrites before use.
|
||||
register uintptr_t guest_rsp_reg asm("r14") = guest_rsp;
|
||||
register uintptr_t guest_rbp_reg asm("r15") = guest_rbp;
|
||||
asm volatile("pushq %%r12\n\t"
|
||||
"pushq %%r13\n\t"
|
||||
"movq %%rsp, %%r12\n\t"
|
||||
"movq %%rbp, %%r13\n\t"
|
||||
"movq %[guest_rsp], %%rsp\n\t"
|
||||
"movq %[guest_rbp], %%rbp\n\t"
|
||||
"callq *%%rsi\n\t"
|
||||
"movq %%r13, %%rbp\n\t"
|
||||
"movq %%r12, %%rsp\n\t"
|
||||
"popq %%r13\n\t"
|
||||
"popq %%r12\n\t"
|
||||
: "=a"(ret), "+D"(arg), "+S"(func)
|
||||
: [guest_rsp] "r"(guest_rsp_reg), [guest_rbp] "r"(guest_rbp_reg)
|
||||
: "cc", "memory", "rcx", "rdx", "r8", "r9", "r10", "r11", "xmm0", "xmm1", "xmm2",
|
||||
"xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11",
|
||||
"xmm12", "xmm13", "xmm14", "xmm15");
|
||||
#endif
|
||||
|
||||
g_guest_entry_return_rsp = 0;
|
||||
if (g_pthread_self != nullptr) {
|
||||
|
||||
@@ -14,6 +14,13 @@
|
||||
#endif
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// POSIX uses plain int file descriptors for sockets; provide the Winsock spellings
|
||||
// the shared (non-guarded) code paths reference.
|
||||
using SOCKET = int;
|
||||
static constexpr SOCKET INVALID_SOCKET = -1;
|
||||
#endif
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
@@ -345,28 +345,48 @@ static KYTY_SYSV_ABI void RunEntry(uint64_t addr, EntryParams* params, atexit_fu
|
||||
guest_root_frame[0] = 0;
|
||||
guest_root_frame[1] = 0;
|
||||
|
||||
asm volatile("pushq %%r12\n\t"
|
||||
"pushq %%r13\n\t"
|
||||
"movq %%rsp, %%r12\n\t"
|
||||
"movq %%rbp, %%r13\n\t"
|
||||
"movq %[guest_rsp], %%rsp\n\t"
|
||||
"movq %[guest_rbp], %%rbp\n\t"
|
||||
"callq *%[func]\n\t"
|
||||
"movq %%r13, %%rbp\n\t"
|
||||
"movq %%r12, %%rsp\n\t"
|
||||
"popq %%r13\n\t"
|
||||
"popq %%r12\n\t"
|
||||
:
|
||||
: [func] "r"(func), "D"(params),
|
||||
"S"(atexit_func), [guest_rsp] "r"(guest_rsp), [guest_rbp] "r"(guest_rbp)
|
||||
: "cc", "memory", "rax", "rcx", "rdx", "r8", "r9", "r10", "r11", "xmm0",
|
||||
"xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9",
|
||||
"xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15");
|
||||
#if defined(__APPLE__)
|
||||
// Clang on macOS can allocate plain "r" inputs to r12/r13, which the template
|
||||
// clobbers before consuming them. Pin the inputs to registers the SysV guest
|
||||
// preserves without changing register allocation on Windows or Linux.
|
||||
register entry_func_t func_reg asm("rbx") = func;
|
||||
register uintptr_t guest_rsp_reg asm("r14") = guest_rsp;
|
||||
register uintptr_t guest_rbp_reg asm("r15") = guest_rbp;
|
||||
#endif
|
||||
|
||||
asm volatile(
|
||||
"pushq %%r12\n\t"
|
||||
"pushq %%r13\n\t"
|
||||
"movq %%rsp, %%r12\n\t"
|
||||
"movq %%rbp, %%r13\n\t"
|
||||
"movq %[guest_rsp], %%rsp\n\t"
|
||||
"movq %[guest_rbp], %%rbp\n\t"
|
||||
"callq *%[func]\n\t"
|
||||
"movq %%r13, %%rbp\n\t"
|
||||
"movq %%r12, %%rsp\n\t"
|
||||
"popq %%r13\n\t"
|
||||
"popq %%r12\n\t"
|
||||
:
|
||||
#if defined(__APPLE__)
|
||||
: [func] "r"(func_reg), "D"(params),
|
||||
"S"(atexit_func), [guest_rsp] "r"(guest_rsp_reg), [guest_rbp] "r"(guest_rbp_reg)
|
||||
#else
|
||||
: [func] "r"(func), "D"(params),
|
||||
"S"(atexit_func), [guest_rsp] "r"(guest_rsp), [guest_rbp] "r"(guest_rbp)
|
||||
#endif
|
||||
: "cc", "memory", "rax", "rcx", "rdx", "r8", "r9", "r10", "r11", "xmm0", "xmm1", "xmm2",
|
||||
"xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11", "xmm12",
|
||||
"xmm13", "xmm14", "xmm15");
|
||||
return;
|
||||
}
|
||||
|
||||
uintptr_t guest_root_frame[2] = {};
|
||||
|
||||
#if defined(__APPLE__)
|
||||
register entry_func_t func_reg asm("rbx") = func;
|
||||
register uintptr_t guest_rbp_reg asm("r14") = reinterpret_cast<uintptr_t>(guest_root_frame);
|
||||
#endif
|
||||
|
||||
asm volatile("pushq %%r12\n\t"
|
||||
"pushq %%r13\n\t"
|
||||
"movq %%rbp, %%r12\n\t"
|
||||
@@ -376,8 +396,13 @@ static KYTY_SYSV_ABI void RunEntry(uint64_t addr, EntryParams* params, atexit_fu
|
||||
"popq %%r13\n\t"
|
||||
"popq %%r12\n\t"
|
||||
:
|
||||
#if defined(__APPLE__)
|
||||
: [func] "r"(func_reg), "D"(params),
|
||||
"S"(atexit_func), [guest_rbp] "r"(guest_rbp_reg)
|
||||
#else
|
||||
: [func] "r"(func), "D"(params),
|
||||
"S"(atexit_func), [guest_rbp] "r"(guest_root_frame)
|
||||
#endif
|
||||
: "cc", "memory", "rax", "rcx", "rdx", "r8", "r9", "r10", "r11", "xmm0", "xmm1",
|
||||
"xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11",
|
||||
"xmm12", "xmm13", "xmm14", "xmm15");
|
||||
|
||||
Reference in New Issue
Block a user