From 4d79f190897a4ff7f6d16bd9da3e0e65931d4c82 Mon Sep 17 00:00:00 2001 From: "M. Abdullah" <36810219+m1abdullahh@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:03:15 +0500 Subject: [PATCH] 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 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 --- .gitignore | 3 +- src/common/hostException.cpp | 125 ++++++++++++++++++ src/common/platform/sysLinuxFileIO.cpp | 23 +++- src/common/platform/sysLinuxVirtual.cpp | 82 ++++++++++++ src/graphics/host_gpu/pageManager.cpp | 95 ++++++++++++- src/graphics/host_gpu/regionManager.h | 5 + src/graphics/host_gpu/renderer/renderDraw.cpp | 5 + src/graphics/host_gpu/renderer/shaders.cpp | 20 ++- .../presentation/window/swapchain.cpp | 17 +++ .../presentation/window/vulkanWindow.cpp | 35 ++++- src/graphics/presentation/window/window.cpp | 68 +++++++++- .../presentation/window/windowInternal.h | 20 +++ src/kernel/memoryAddressSpace.inc | 18 ++- src/kernel/pthread.cpp | 34 ++++- src/libs/network.cpp | 7 + src/loader/runtimeLinker.cpp | 59 ++++++--- 16 files changed, 577 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index 92f28ecb..e58f8d67 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .vs/ .idea/ build/ -_Build/vscode-clang/ \ No newline at end of file +_Build/vscode-clang/ +_Build/ \ No newline at end of file diff --git a/src/common/hostException.cpp b/src/common/hostException.cpp index 5976869e..ef903443 100644 --- a/src/common/hostException.cpp +++ b/src/common/hostException.cpp @@ -6,6 +6,9 @@ #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #include // IWYU pragma: keep +#elif defined(__APPLE__) +#include +#include #endif // IWYU pragma: no_include @@ -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 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(uctx); + const auto* mc = uc->uc_mcontext; + const auto& ss = mc->__ss; + + ExceptionInfo info {}; + info.exception_address = ss.__rip; + info.native_code = static_cast(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(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 diff --git a/src/common/platform/sysLinuxFileIO.cpp b/src/common/platform/sysLinuxFileIO.cpp index 4df6ba1e..8903f74c 100644 --- a/src/common/platform/sysLinuxFileIO.cpp +++ b/src/common/platform/sysLinuxFileIO.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -536,8 +537,26 @@ void SysFileFindFiles(const std::filesystem::path& /*path*/, EXIT("not implemented\n"); } -void SysFileGetDents(const std::filesystem::path& /*path*/, std::vector& /*out*/) { - EXIT("not implemented\n"); +void SysFileGetDents(const std::filesystem::path& path, std::vector& 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*/) { diff --git a/src/common/platform/sysLinuxVirtual.cpp b/src/common/platform/sysLinuxVirtual.cpp index 5395af8d..1c5f8561 100644 --- a/src/common/platform/sysLinuxVirtual.cpp +++ b/src/common/platform/sysLinuxVirtual.cpp @@ -12,6 +12,11 @@ #include #include +#if defined(__APPLE__) +#include +#include +#endif + // IWYU pragma: no_include // IWYU pragma: no_include // IWYU pragma: no_include @@ -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(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(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(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(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(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(&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(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(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(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(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 } } diff --git a/src/graphics/host_gpu/pageManager.cpp b/src/graphics/host_gpu/pageManager.cpp index c448d94e..e669d06b 100644 --- a/src/graphics/host_gpu/pageManager.cpp +++ b/src/graphics/host_gpu/pageManager.cpp @@ -19,6 +19,12 @@ #include #undef min #undef max +#elif defined(__APPLE__) +#include +#include +#include +#include +#include #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 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(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(&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(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(info.dwPageSize)); } +#elif defined(__APPLE__) + // Under Rosetta the host page size is 4 KB, matching TRACKER_PAGE_SIZE. + if (static_cast(getpagesize()) != PAGE_SIZE) { + Fatal("unsupported host page size 0x%08" PRIx32, + static_cast(getpagesize())); + } #else Fatal("page-fault invalidation is not implemented on this platform"); #endif @@ -225,6 +278,14 @@ struct PageManager::Impl { vaddr, static_cast(info.State), static_cast(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(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(static_cast(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; diff --git a/src/graphics/host_gpu/regionManager.h b/src/graphics/host_gpu/regionManager.h index b30a470b..94fde40b 100644 --- a/src/graphics/host_gpu/regionManager.h +++ b/src/graphics/host_gpu/regionManager.h @@ -16,6 +16,8 @@ #include #undef min #undef max +#elif defined(__APPLE__) +#include #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(pthread_mach_thread_np(pthread_self())); #else EXIT("region tracking thread identity is unsupported on this platform\n"); #endif diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index 26f6e813..dce3a2c9 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -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) { diff --git a/src/graphics/host_gpu/renderer/shaders.cpp b/src/graphics/host_gpu/renderer/shaders.cpp index 2eb88dcc..3255d83e 100644 --- a/src/graphics/host_gpu/renderer/shaders.cpp +++ b/src/graphics/host_gpu/renderer/shaders.cpp @@ -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(sizeof(dynamic_states) / sizeof(dynamic_states[0])); diff --git a/src/graphics/presentation/window/swapchain.cpp b/src/graphics/presentation/window/swapchain.cpp index f2f6a517..7605a772 100644 --- a/src/graphics/presentation/window/swapchain.cpp +++ b/src/graphics/presentation/window/swapchain.cpp @@ -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); diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index c4df2780..2c32e3ba 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -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 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 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()) { diff --git a/src/graphics/presentation/window/window.cpp b/src/graphics/presentation/window/window.cpp index a241c362..bc636a0b 100644 --- a/src/graphics/presentation/window/window.cpp +++ b/src/graphics/presentation/window/window.cpp @@ -1,5 +1,7 @@ #include "graphics/presentation/window.h" +#include + #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 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> 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(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 diff --git a/src/graphics/presentation/window/windowInternal.h b/src/graphics/presentation/window/windowInternal.h index cb1c3fc2..21d27747 100644 --- a/src/graphics/presentation/window/windowInternal.h +++ b/src/graphics/presentation/window/windowInternal.h @@ -12,6 +12,10 @@ #include #include +#if defined(__APPLE__) +#include +#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 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> 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 diff --git a/src/kernel/memoryAddressSpace.inc b/src/kernel/memoryAddressSpace.inc index 20a59f86..fbeb8c8e 100644 --- a/src/kernel/memoryAddressSpace.inc +++ b/src/kernel/memoryAddressSpace.inc @@ -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; } diff --git a/src/kernel/pthread.cpp b/src/kernel/pthread.cpp index 8939ca51..9e278e4b 100644 --- a/src/kernel/pthread.cpp +++ b/src/kernel/pthread.cpp @@ -58,6 +58,12 @@ namespace LibKernel { LIB_NAME("libkernel", "libkernel"); +// macOS's / 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) { diff --git a/src/libs/network.cpp b/src/libs/network.cpp index 19579ef9..07a43f6c 100644 --- a/src/libs/network.cpp +++ b/src/libs/network.cpp @@ -14,6 +14,13 @@ #endif #else #include +#include +#include + +// 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" diff --git a/src/loader/runtimeLinker.cpp b/src/loader/runtimeLinker.cpp index ab3d29c7..2d93a426 100644 --- a/src/loader/runtimeLinker.cpp +++ b/src/loader/runtimeLinker.cpp @@ -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(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");