Repository navigation
feat: macOS on Apple Silicon host support (Rosetta 2 and MoltenVK) - #7332
ycarogabriel-lgtm wants to merge 282 commits into
Conversation
…able (cherry picked from commit ed8bf0b)
The slide fix only trimmed the part of a rounded range that no registered range covers. The guest rounds the range it protects to 16 KiB on the absolute address, and an image dyld loads at a multiple of 4 KiB is not on that grid: the end of a RELRO region, rounded up, reached into the writable data behind it in the same segment, made it read-only, and the first store there faulted (SIGBUS, KERN_PROTECTION_FAILURE). Roughly half the launches of a real title died this way. DoMprotect now rounds relative to where the ELF address 0 of the image containing the range ended up, as the console does with its 16 KiB aligned images (GuestAllocationsGuestPages). Ranges outside a guest image keep the absolute rounding. The trim stays as a safeguard. The mprotect-pages fixture has a 32 KiB data segment whose first 16 KiB page is made read-only, then stores into the second page; the runner launches it 16 times. It exits with SIGBUS without the change and 150 launches over all four slides give the expected exit code with it. Reported by morten-holm, who cleared MH_PIE and saw the crashes stop.
- sceKernelGetModuleInfoForUnwind left eh_frame_addr and eh_frame_size at zero for a guest image. A title's own unwinder asks for the frame it is in, finds no table, and a C++ throw ends in terminate. The fields are now read from the eh_frame_hdr the image keeps in its program headers: eh_frame_ptr is decoded (absolute or pc-relative), and the records are walked to the terminator, inside the segment that holds them, the way the Windows branch does. - sceSysmoduleGetModuleInfoForUnwind read /proc/self/maps outside Windows and threw on every call on macOS. It answers from mach_vm_region and dladdr, with the region as seg0. The unwind-info fixture has a PT_GNU_EH_FRAME and one CIE, asks for its own information and exits with eh_frame_size (16); it exits with 0 without the change. Reported by Cloud-Multiplayer.
…writable directory - The relinker names a bundled guest module @executable_path/app0/sce_module/..., and the packager put the title's app0 under Contents/Resources/game, so dyld aborted at launch with "Library not loaded" for any title with a bundled module. Contents/MacOS/app0 is now a relative link to it. - The launcher entered the bundle's own game folder, because AnyPS5 maps /app0 and the mount points under the working directory. Saves were written inside the .app, and /download0 did not exist for a title that writes there without mounting it. The title now runs from ~/Library/Application Support/AnyPS5/<title id>, where app0 links to the bundle's game folder and download0 exists; the bundle stays read-only. test_package_macos_app builds a bundle from fake inputs, checks that the module resolves through Contents/MacOS/app0, and runs the launcher with a temporary HOME and a stand-in eboot: it must start in the run directory, see app0/sce_sys/param.json and find download0. A relinked bundled-module fixture packaged this way runs and exits with its code; before, dyld aborted. Reported by DavidSamir and mrzacarias.
main moved 1242 commits since the port's base. The relinker, NID patcher and --to-rosetta now come from the merged pull requests, so their files take main's version; the port keeps only the macOS fixtures and their runner there. Conflicts resolved: - VideoOutDriver.cpp, Flip.cpp: main's restructured flip path with the port's main-thread wrappers (OnMainThread) and runTests/Aps5StartGuest around the test environment. - Strings.cpp, RuntimeSupport.cpp: the prefix-letter check of main with the port's errno-preserving ConvertInteger; strtoimax/strtoumax stay in Strings.cpp. - ModuleInfo.cpp: sceKernelGetModuleInfoFromAddr, sceKernelGetModuleList and sceKernelGetModuleInfo throw as not implemented on macOS. - DirectMemory.cpp, GuestAllocations.cpp, CMake files and the docs: both sides. - SpirvAluEmitterMath.cpp takes main's ordered not-equal; FloatInstructions.cpp keeps the port's sign fix on top of main's overflow clamp. Code main added for Linux or Windows, made to build and work on macOS: - Time.cpp, General.cpp, SharedMemory.cpp, Stdio.cpp, Open.cpp, Resolver.cpp, Process.cpp, Thread.cpp, SignalSets.cpp, ThreadPriority.cpp, GuestArena.cpp, GuestBufferMemory.cpp: tm_zone, clock and st_mtimespec spellings, an anonymous file without memfd, fstatfs without sys/vfs.h, fcntl F_FULLFSYNC for fdatasync, open(O_CREAT) on a directory is EISDIR, argv from the Mach-O instead of /proc/self/cmdline, per-thread CPU time from thread_info (the thread CPU clock barely advances under Rosetta), the signal-set assembly through the shared Apple-aware macros, a mach thread precedence for APS5_THREAD_PRIORITY, and no chunked dma-buf import. - GuestWriteWatch.cpp: a Fresh state for pages nothing has collected, so that CollectArmed and CollectFresh behave as on Linux; a re-registered page keeps the watch's own logical protection only while the watch holds it protected. - DirectMemory.cpp reports a guest mprotect to the write watch, and GuestMemory.cpp reads the watch's logical protection for watched pages. - Rename onto a dangling symlink, getdirentries leaving errno alone on success, and the wide integer conversions keeping errno. Tests: the arena-, rtkit-, dma-buf- and import-specific checks run only where they exist; run_macos_fixtures.py relinks the Rosetta fixture with --to-rosetta and checks that --to-intel alone still faults. ctest without a Vulkan loader: 712 tests, 0 failed, 264 skipped, twice in a row.
Cloudberry Kingdom (PPSA23827) imports kqueue and kevent from libkernel to watch its content directories: it opens a directory, registers EVFILT_VNODE with EV_ADD | EV_ENABLE | EV_CLEAR and the delete, write, extend, link and rename notes, and polls the queue with a timeout. No library exported either function, so the title stopped at load. On a macOS host both now pass through to the host's kqueue. Guest descriptors are host descriptors there, and EVFILT_VNODE, the change flags up to EV_DISPATCH and the vnode notes up to NOTE_REVOKE have FreeBSD 11's values, which static assertions pin. Events are returned through a host buffer and copied to the guest. Every other filter, later flags and notes, and guest sockets throw. Other hosts do not build the file, so the import stays absent there. guest_kqueue watches a temporary file through a write, an unlink and the removal of the watch, and checks the rejected changes and the EFAULT and EBADF results. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ut reading st_size The unwinder in a title's own libc.prx asks sceKernelGetModuleInfoFromAddr for the eh_frame of the frame it is in. On macOS the call was not implemented, and on every host it threw when the caller's st_size was not the full size. Cloudberry Kingdom (PPSA23827) passes a stale st_size (8, or a pointer), so its first C++ throw, a minute or two in, ended the process with `unsupported st_size`. - The caller's st_size is no longer read: shadPS4's process.cpp fills the structure whatever it holds. The result still carries the full size. - On macOS the image is found through dl_iterate_phdr over the program headers a converted image keeps, with the code the Linux branch uses for the name, the load segments, the TLS image and the eh_frame. The id comes from the image's Mach-O header. tls_index, init_proc_addr and fini_proc_addr stay 0, and an image opened through the loader gets a stable id of its own; TechnicalDebt.md records both. The module-info fixture asks for its own information with flags 2 and st_size 8 and exits with eh_frame_size, 20 with the terminator; it is the unwind-info fixture with another import. guest_module_info expects a short st_size to succeed; it is not built on macOS and was not run. The title now runs past its first throw (230 s without a stop). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Relinked titles run on Apple silicon as x86-64 code under Rosetta, but CI runs the write watch tests there only as arm64 and the register context tests only on Intel macOS and Linux. Add x86_64 builds of both on macos-15, installing Rosetta first, as BUILD.md documents for local runs.
…kqueue feat(libkernel): kqueue and kevent on macOS hosts
…module-info-macos feat(libkernel): sceKernelGetModuleInfoFromAddr on macOS hosts, without reading st_size
Resolves eleven conflicts with 874 commits of main (0a8b1d3): - Asm helpers: main's AsmMacros.hpp gains the Mach-O spellings (APS5_ASM_SYMBOL, APS5_ASM_FUNCTION_END) and replaces our AsmFunction.hpp, whose users now include AsmMacros.hpp. - GuestWriteWatch (macOS): main's per-page "queried" bookkeeping on re-registration is kept together with the Fresh bit. - Stdio: main's guest-socket routing for writev/pread/pwrite comes first, the macOS empty-iovec check after it. - GuestMemory: main's shared PROCMAP_QUERY lookup replaces ProcMapsQueryFd. - vLdexpF16: main's rewrite is kept and the input sign is OR-ed in, because Metal returns +0.0 for -0.0 scaled up. - TechnicalDebt.md: main's wording with the macOS notes added.
- FileStream and StandardStreams call feof_unlocked, ferror_unlocked, getc_unlocked and putc_unlocked unqualified, as they are macros on macOS. - Udp: equeue wait and readiness use poll, as macOS has no ppoll or POLLRDHUP. - Exception: sceKernelRaiseException is not implemented on macOS and the guest_raise_exception test skips there. - UserContext: the getcontext assembly calls its helper through APS5_ASM_SYMBOL; AtomicSharedPtr gains compare_exchange_strong for the mutex fallback. - Equeue: main's kqueue and kevent stubs are left out on macOS, where Kqueue.cpp implements them. - DirectoryDescriptor: a directory descriptor's path comes from F_GETPATH, so fchdir works. - Tests adapted to macOS: guest_udp (a connected UDP socket rejects an addressed send with EISCONN, as on the console), guest_kernel_stack_protect (page query through mach_vm_region), guest_ampr_markers (AMM checks need the guest arena), guest_ppsa01530_exports (no kqueue and kevent stubs), agc_descriptor_pool_reset_failure (host std::set_terminate) and guest_pthread_mutexattr (<initializer_list>).
Deliver the guest exception on macOS the way Linux does: pthread_kill sends the host SIGUSR1 and the handler converts the Darwin x86-64 mcontext (__darwin_mcontext64: __ss registers, FXSAVE image from __fs.__fpu_fcw) to the guest's FreeBSD layout and back. Darwin does not expose the stack segment, so ss reads as 0. A static_assert checks that the host float state is laid out like an FXSAVE image. guest_raise_exception no longer skips on macOS. The test's global assembly label gets the Mach-O underscore and its SIGUSR1 constant is renamed, since SIGUSR1 is a macro on Darwin.
Conflicts: the write watch takes main's fresh-page tracking (boykopovar#3703) in place of the port's own; main's comparison fix replaces the port's ordered not-equal; the kqueue, getc_unlocked and socketpair notes in TechnicalDebt.md and the Rosetta sentence in BUILD.md are kept next to main's new entries; the Apple branches of ModuleInfo.cpp, Stdio.cpp and SharedMemory.cpp stay beside main's additions. The macOS build of what main added follows in the next commit.
dl_iterate_phdr reports how many images were added and removed, from dyld's callbacks, because the unwind table cache in sceKernelGetModuleInfoFromAddr is dropped when an image is unloaded. Named shm_open and shm_unlink keep throwing as not implemented: main's host names are 52 characters where macOS allows 31, and its permission check reads /dev/shm. The tests follow the platforms they cover: log_macros uses a datagram pair with 1 MiB buffers, since macOS has no SOCK_SEQPACKET pair; guest_amm_placement skips without the guest arena; guest_shm_open expects the throw and leaves out the size-bounded write, which only Linux implements; guest_memory leaves out the flexible memory accounting of a plain dylib, which dl_iterate_phdr does not describe.
# Conflicts: # core/libs/tests/GuestAmpr.cpp # docs/user/USAGE.md
O_DIRECT is Linux-only, the physical span index is also used by the macOS write watch, the stack protect test needs sys/mman.h and the getrlimit test initializer_list. The kernel write command test needs the guest arena like the other AMM tests.
…rate_phdr; test(libkernel): ask the arena once in the guest memory test The callbacks were registered by a constructor attribute, which the conventions refuse; dyld reports the images that are loaded at the first call, so the counts are the same. The guest memory test reads GuestArenaAvailable once.
# Conflicts: # core/libs/prx/libSceFont/src/SystemFont.cpp # core/libs/prx/libSceNpEntitlementAccess/Export.cpp
# Conflicts: # core/libs/prx/libc/src/Strings.cpp
feof_unlocked, ferror_unlocked, getc_unlocked and putc_unlocked are macros on macOS, so they are called without the global scope qualifier. The remaining differences are the ones Darwin's headers and libSystem have: - CLOCK_REALTIME_COARSE and st_mtim do not exist; the write mark uses CLOCK_REALTIME and st_mtimespec. - tm_zone is a non-const char*. - __cxa_thread_atexit returns void, so the wrapper reports success. - Darwin's stack protector reads __stack_chk_guard from libSystem; the guard of the guest libc has the postfixed name, which receives the NID of the original. - APS5_EXPORT spells the symbols with the leading underscore of Mach-O. - GuestArenaSetProtection and PosixProtection are built on macOS as on Linux. - The fclose failure test fills /dev/full, which exists on Linux only. exception_runtime relies on the Itanium unwinder of libc, which is not registered on macOS (the host libc++abi is used), so its test is only registered elsewhere.
The exception is delivered like on Linux: the host SIGUSR1 interrupts the target thread and the handler runs on it. Darwin hands the handler an mcontext_t that points to separate general register and float state structures instead of holding the registers itself. The general registers are copied one by one; the float state of x86-64 Darwin starts with an FXSAVE image (checked by static_assert against the offsets of the control word, MXCSR and the first and last XMM register), so the 512 bytes the guest context carries are copied from and to it, and Rosetta applies the modified context when the handler returns. The test's SIGUSR1 constant is renamed, as the macro of the same name exists on macOS, and its spin function uses the assembly macros.
The process, thread and clock functions that read /proc or call glibc extensions: - The program arguments come from _NSGetArgc and _NSGetArgv instead of /proc/self/cmdline. - The current CPU comes from pthread_cpu_number_np. - getrusage for a thread, the CPU clock of a thread and the CPU time of a guest thread (clock_gettime with the thread CPU clock) read the Mach thread_info of the thread; macOS has no pthread_getcpuclockid. - The stack limits of a thread come from pthread_get_stackaddr_np and pthread_get_stacksize_np. - sleeping with nanosleep restores errno, because macOS leaves ETIMEDOUT in it on success and the guest's errno is the host's. The first local of a guest thread lies 4,464 bytes below the top of its stack on macOS, more than the frame the pthread stack test allowed, so the test allows two frames there.
The guest socket layer relies on a few Linux calls: - The equeue wait uses poll with the timeout rounded up to a millisecond; macOS has no ppoll. - A readable stream socket whose peer closed is reported with POLLHUP by peeking one byte; macOS has no POLLRDHUP. - FIONREAD on a datagram socket reports SO_NREAD, the size of the next datagram; macOS adds the source address to FIONREAD. - socketpair rejects SOCK_SEQPACKET, which has no AF_UNIX pair on macOS. - gethostbyname replaces gethostbyname_r, which macOS lacks. The UDP test sends to an address from an unconnected socket on macOS, where sendto with an address on a connected socket fails with EISCONN, and the log test uses a datagram pair with enlarged buffers instead of SOCK_SEQPACKET.
libSceAudioIn and libSceAudioOut linked SDL2 statically, as every library that uses it does. On macOS the Objective-C runtime registers the classes of SDL from each copy it finds, and when two libraries of one process hold a copy the runtime warns that one of the two is used and which one is undefined. They link libSceAgcDriver instead, which holds the SDL of the process. The other platforms are unchanged.
AppKit, which SDL uses for windows, Metal surfaces, event pumping and game controllers, works only on the main thread, and a guest never hands its thread back. The macOS relinker already emits a call to Aps5StartGuest as the entry of a relinked executable; nothing provided it. - Aps5StartGuest runs the guest entry on a new thread with the stack size of the main thread (at least 8 MiB) and serves the main dispatch queue from the run loop of the main thread. The guest ends the process with exit; a guest entry that returns aborts. - Aps5RunOnMainThread runs a function on the main thread and waits for it, or in place where the main thread is the caller or Aps5StartGuest was never called. - libSceVideoOut sends its SDL calls through OnMainThread, which carries the exceptions back to the caller: the initialisation and shutdown of the video and game controller subsystems, the window, its Vulkan surface and drawable size, the event polling and the pad, mouse and keyboard input, which are created and destroyed there. On the other platforms the functions run in place. - The flip test starts its tests through Aps5StartGuest on macOS, as a relinked guest does.
macOS has no /proc/self/maps or MAP_FIXED_NOREPLACE, so the arena never came up and every guest allocation threw. Enumerate host regions with mach_vm_region and reserve free chunks with a fixed mach_vm_map, which fails instead of replacing. This skips dyld, the Rosetta runtime, and ranges the kernel never lets a process map (0x10_0000_0000-0x70_0000_0000). The POSIX commit/protection paths now build on macOS unchanged. Adds standalone guest_arena tests (x86_64 under Rosetta and arm64).
Move the explanatory comments into docs/dev/debt/libc.md, as the conventions require.
|
Rechecked on the same M4 Pro (macOS 27.0.1, Rosetta 2): head
The leeway is a quarter only up to about 16 ms. Beyond that The new - void* localOnly = dlopen_nid_postfix(argv[1], 2);
+ const std::filesystem::path localPath = "anyps5-local-module-for-test.prx";
+ std::filesystem::copy_file(argv[1], localPath, std::filesystem::copy_options::overwrite_existing);
+ void* localOnly = dlopen_nid_postfix(localPath.string().c_str(), 2);
Require(localOnly != nullptr && dlsym_nid_postfix(localOnly, "GuestModuleAdd") != nullptr);
Require(dlsym_nid_postfix(nullptr, "GuestModuleAdd") == nullptr);
Require(dlclose_nid_postfix(localOnly) == 0);
+ std::filesystem::remove(localPath);
Not checked: the GPU suite and titles on this head (no Vulkan SDK installed right now), so not yet the FMOD Studio fix in a title; Linux and Windows. AI-assisted: yes (Claude Code). I ran all of the above on my machine. |
Keeps the fork's main on the same upstream base as the macOS pull request branch (boykopovar#7332), so the two do not drift apart and later community macOS merges resolve against current code. The merge was conflict-free here and leaves the code identical to the PR branch, including upstream's port-zero connect check, which the PR branch had to restore by hand after its boykopovar#7559 merge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings the pull request up to the upstream commits since its last update; they merge without conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a keeps free space beside host regions (@imellag) Includes imellag's guest arena test and the macOS 27 measurement of the unmappable range, as on the fork's main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… on macOS hosts (@mugurc) Takes mugurc's NativeListening, shared by getsockopt, connect and the listening read event, in place of this branch's equivalent HostSocketListening helper, so the branch and the open pull request carry the same code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Guest mmap of a file or shared memory object went through a Linux-only path: on macOS it accepted only descriptors recognized as shared memory and then threw ENOTSUP, so MAP_SHARED of an anonymous shm_open object failed with EOPNOTSUPP. The Linux path (descriptor pinning, access-mode checks, regular files, placement in the guest arena) now also builds for macOS, where the helpers it uses already exist. The guest's errno is the host errno. The posix memory calls set it inside catch blocks, and on macOS the C++ runtime's release of the exception when the handler ends can change errno, so a refused mprotect reported ENOENT instead of EACCES. mmap, munmap, mprotect, msync and mincore now record the error and set it again when they return, after that release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit f766181)
…n macOS XNU refuses readv and writev with no vectors, so the macOS path answers them itself after checking that the descriptor is open; for writev and pwritev it returned 0 even on a read-only descriptor, where the console and Linux return EBADF, as guest_named_shm_lifecycle checks. The write variants now check the access mode first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit b9dd063)
shm_open and shm_unlink of a named object threw NotImplemented on macOS, and the Linux path cannot be reused as is: XNU's POSIX shared memory takes names of at most 31 characters (ours are 52), refuses fchmod, O_TRUNC and a second ftruncate, and is never executable. On macOS named objects are now files in $TMPDIR/anyps5-shm, the role /dev/shm plays on Linux, locked with flock on that directory; anonymous objects are created there too, so both are recognized as shared memory through F_GETPATH and get the console's write and seek rules (a write past the end does not extend the object). guest_named_shm_open, _lifecycle, _permissions and _concurrency and guest_getlogin_no_session now run on macOS. The two descriptor-reuse tests stay Linux-only because they interpose the host fcntl through ELF symbol preemption, which macOS's two-level namespace does not allow. guest_shm_open now expects named objects to work on macOS, and the lifecycle test skips the one check that macOS's copy-at-map MAP_PRIVATE cannot meet; both differences are in the libkernel debt file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 8d9e057)
sceLibcBacktraceGetBufferSize and sceLibcBacktraceSelf threw NotImplemented outside Linux. They capture frames with backtrace() and name modules with dladdr, which macOS provides with the same behaviour, so the Linux implementation now builds there and guest_backtrace runs and passes on macOS. exception_c_cleanup, which was kept off macOS while host frames with only compact unwind info could not be stepped, also passes since that unwinding was ported from boykopovar#7201 and now runs on macOS; guest_image_anonymous stays off, as it reads /proc symlinks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit ac61fad)
ba14865 to
2860277
Compare
…#7332 The fork's main had fallen behind the pull request branch on two fronts: the 169 upstream commits (53 on its first-parent line) since the last sync, and the commits made on the PR branch from the M4 Pro review (the precise wait capped at 6 ms of spin above 24 ms, the guest scope test loading a real .prx, the 0xfc0000000 range attributed to the macOS host rather than Rosetta) and the web converter's macOS batch fixes. Each conflict is resolved to the PR branch's version, which already combines upstream with the macOS paths, and the files the PR branch changed outside the conflicts are taken from it too. The relinker CLI now keeps --macos-fixed-base beside upstream's --patches and --verify-patches, and refuses it in verify mode like the other fixed bases. The code is identical to the PR branch afterwards; only the fork's own notes (docs/metal-exploration, the macOS validation section) differ. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings the branch up to main at 384840a with a merge instead of another rebase, so the commits reviewers have already tested and cited keep their hashes and the branch can be updated without force pushes. Conflicts and macOS adaptations: - SpirvModuleSetup.cpp: main's shader layer change (boykopovar#6164, which this branch carried) did not keep the guard that skips the SPV_EXT_shader_viewport_index_layer declaration when the Layer output already emitted it; the branch's version, which has it, is kept. - CliArgs.cpp: main's --patches and --verify-patches options sit beside --macos-fixed-base, which verify mode now refuses like the other fixed bases, and the usage text lists both. - the other conflicts (precise wait, NetCtl test, web converter, socket options, libkernel debt) resolve as on the previous update. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps the pull request mergeable after 33 more upstream commits, by merge so the hashes reviewers cite stay valid. The one conflict is in sceKernelWritev and sceKernelPwritev: main now flushes pending GPU results into the written buffers before a file write (a64d038), and this branch returns early on macOS for an empty vector so a write to a read-only descriptor fails with EBADF, as on the console. Both are kept; the early return comes first, and with no vectors there is nothing for the flush to do. The new compiler-rt builtins and fmaf build and pass on macOS without changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps the pull request mergeable after 16 more upstream commits, again by merge so cited hashes stay valid. The only conflict is in the libkernel debt file, where main extends the kqueue line for read events on descriptor 0 and this branch adds the macOS part of the JIT shared memory line beside it; both are kept. The new kevent, 16-byte __sync and FreeType code builds and passes on macOS unchanged. The new multisample retirement test needs a follow-up, since it expects multisampled storage images, which MoltenVK lacks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…has it agc_driver_multisample_retirement failed on macOS: its last stage promotes a 4x color target to a storage image, and MoltenVK reports no shaderStorageImageMultisample because Metal cannot write multisampled textures from shaders. The driver already refuses that access with a clear message when the feature is off, which is the correct behaviour, so the test, not the driver, assumed too much. The storage stage now runs only when the context has multisampled storage images; the idle, recorded and sampled retirement stages still run everywhere and pass on MoltenVK. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings the fork's main to the state of boykopovar#7332 after the upstream merges of today (main at d71853c), so the fork keeps the same code as the pull request. That includes the empty-vector write check living beside main's GPU flush in Stdio.cpp, the libkernel debt lines from both sides, and the multisample retirement test that skips storage promotion on devices without multisampled storage images. The fork keeps its own layout: the macOS validation section in TechnicalDebt.md and the guide under tools/macos/README.md, so the identical copy at docs/user/MACOS.md that the PR branch carries is removed here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thank you @imellag for the M4 Pro recheck. All three points are on the branch now:
@jotitadev, your About the history: the branch was rebased twice today, so |
What
macOS on Apple Silicon as a host: relinked titles run as x86-64 Mach-O under Rosetta 2 and draw through MoltenVK. This branch brings the community's open macOS pull requests together on current
main, makes them pass the whole test suite on an Apple Silicon Mac, and adds the fixes that only showed up once everything ran together. It is meant as one reviewable picture of the port; each part can still be taken from its own pull request, and I am glad to split it further, reduce it onto whichever of the overlapping PRs the maintainers prefer, or close it.Integrated pull requests (merged or cherry-picked, conflicts resolved against current
main)ci/macos-series-*): Mach-O TLS, main thread for AppKit, module info, memory protection and guest pages, MoltenVK device, AGC thread priority, single SDL audio, LoadExec (fix(libc): build the host-facing libc code on macOS #4627, feat(libkernel): sceKernelRaiseException on macOS hosts #4630, feat(libSceSystemService): LoadExec on macOS hosts #6248 and the rest of the series)Ported from #7201 (marion909's port; each commit is co-authored by @marion909, as @blawesom suggested reconciling the two)
forincrement and drops the header phi updates, which hung the GPUAPS5_DUMP_DRAW_SHADERdumps every stage of the draws that use one shaderexception_runtimenow runs on macOSANYPS5_VULKAN_LIBRARYNot ported from #7201, with the reason: AF_UNIX seqpacket pairs emulated over datagram sockets (XNU reports
ECONNRESETandEDESTADDRREQwhere FreeBSD's seqpacket gives EOF andEPIPE, so they stay refused as the debt file says);sendtowith an address on a connected UDP socket turned intosend(FreeBSD returnsEISCONN, whichguest_udpchecks); presenting from the host main thread and its own guest runtime base (this branch already has equivalent, tested paths from mugurc and yripper); the AvPlayer end-of-stream wake-up (generic logic gated on__APPLE__with no test that reproduces the problem).From the review and title runs on this PR (thanks @jotitadev and @imellag)
RTLD_GLOBALon the macOS host, because relinked modules bind imports by flat lookup and dyld leavesRTLD_LOCALimages out of it; this is what stopped FMOD Studio from loading in Planet of Lana and God of War: Sons of Sparta. The guest's owndlsymscope is unchanged andguest_dynamic_loadernow checks itprecise_waitnow passes 10 of 10 on the M1 (it was 3 of 6, and 0 of 10 on the M4 Pro)tools/macos/setup-game.shunwraps withtools/unwrap_self.py, which accepts both SELF magics, instead of a duplicate that refused0xEEF51454dumpsguest_refused_connect) is recorded in the libkernel debt file: it did not reproduce here, and a retry could not be checked without a reproductionUpstream tests that only ran on Linux, now run on macOS
shm_open/shm_unlinkof named objects work on macOS as files in$TMPDIR/anyps5-shm(XNU's POSIX shm takes names of at most 31 characters and refusesfchmod,O_TRUNCand a secondftruncate);guest_named_shm_open,_lifecycle,_permissionsand_concurrencyrun there. Guestmmapof files and shm objects now uses the Linux path on macOS, andEBADFfor empty-vector writes on read-only descriptors matches the consolemprotectreportedENOENTinstead ofEACCES)sceLibcBacktraceGetBufferSize/sceLibcBacktraceSelfwork on macOS (guest_backtrace);exception_c_cleanupandguest_getlogin_no_sessionrun toocpu_topology,guest_image_anonymous,guest_module_infoand the ELF relinker execution tests read Linux-only interfaces (sched_getaffinity,/proc, ELF fixtures)Fixes added here (XNU and Apple libc++ differences, each with a test that failed before)
SO_ACCEPTCONNthroughproc_pidfdinfo;shutdown(SHUT_RD)after the peer'sSHUT_WRand on unconnected datagram sockets; urgent TCP data reported topollthroughselect; datagram byte counts fromSO_NREADin kevent, fstat and ioctl, because XNU'sFIONREADadds the sender address; IPv4-mapped answers dropped from IPv6 lookupsmprotect;munlockallunlocks each recorded rangeacosfthrough double;mktimebefore 1900;fflushleaves reading streams alone; host terminations forwarded to the guest's terminate; statics that guest threads still use at exit kept alivegetdirentriesrefuses plain files withEINVAL, as upstream now requires--exclude-libs), so a program linking Mbed TLS keeps its own allocatorjn/yn, as Apple's libc++ has no C++17 special math functionspipe2, which the Xcode 27 SDK weak-links and macOS 26 lacksdocs/user/MACOS.mdandtools/macosscripts to set up and launch a titleTASK_VM_INFO; sRGB storage images keep their UNORM format because Metal encodes stores to sRGB textures; the AGC driver memory that feat(agc): link calls through the AGC FS table #6160 maps at0xfe0000000is held by Rosetta, so the driver starts without it and says so (AGC debt file)Known limits (in the debt files)
VK_KHR_shader_clock, so those GPU tests fail by design.Investigation notes (kept out of the repository): with MoltenVK 1.4.2 the remaining GPU failures, the same on the M1 and on @imellag's M4 Pro, are:
0x8000/0x80000000read as zero) in f16/f32 denormal, rounding and output-modifier tests,packed_alu,sdwa_float_selectorsanddpp16_operands(SPIRV-Cross Ratchet & Clank: Rift Apart: SIGSEGV since #861, an address-less 64 GiB reservation now takes the addresses the title hints next #2704), and a product that rounds up to the smallest normal reads zero inf32_constant_factor_tininessshaderCullDistancetessellation: the instanced patch in column 0 does not take its colour from the relative patch indexpixel_interlock: MoltenVK does not compileraster_order_groupon an argument-buffer fieldrect_list_indirect: draws instead of aborting since the zero-area culling, but differs from the direct drawimage_plane_bias_on_single_level_volume: the device has no min/max sampler reductionTitle runs by @imellag on an M4 Pro (details in the comments): Planet of Lana (PPSA19450) and God of War: Sons of Sparta (PPSA28997) stopped on the FMOD Studio load and, for God of War, the IL2CPP heap crash, both addressed above; with them they reach the menu and gameplay at about 10 and 15 to 27 fps, with background corruption that mostly follows the macOS write watch and is not solved here. ccleste (PPSA59322) stops on a pixel shader that reads barycentrics without a vertex shader before it, as in #5079. A native Metal backend and KosmicKrisp under a native arm64 host were assessed and are not part of this pull request.
Tested
From
a231179acon,maincomes into this branch by merge, not rebase, so the hashes in the review comments stay valid.macOS 26.6.2 (25G83) on an Apple M1 (8-core GPU, 16 GiB), x86-64 build under Rosetta 2, AppleClang 21, Vulkan SDK 1.4.363.0 with MoltenVK 1.4.2:
ctest -E "^agc|video_?out|flip|display" -j4) on this head (57bccfbfc,mainatd71853cf2merged in), countingpatched_libraries, which timed out on the first load of the libraries after the build and passed alone three times.precise_wait,guest_socket_read_writeandguest_kernel_fcntlare timing-sensitive and have failed now and then in earlier runs, passing alonectest -R '^agc'with the MoltenVK ICD, excludingagc_display_buffer_dcc,agc_driver_flipandagc_driver_lds_atomics64, which need a window or 64-bit LDS atomics): 708/727 on4a1c11a91, the previous merge; 18 of the failures are the ones listed under Investigation notes, andagc_driver_cross_workgroup_flag, which fails now and then under load, passed alone 5/5. On this head the multisample tests that the newermaincommits touch pass, 15/15;agc_driver_multisample_retirementchecks storage promotion only where the device has multisampled storage images, which MoltenVK lackspython3 tools/check_conventions.py --base origin/main: passesChecklist
main; no other open PR implements the same functions — it is based on currentmain; it deliberately contains the open macOS PRs listed under Depends on and overlaps Macos port #7201, which ports macOS tooNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)main), and Macos port #7201 and feat(aot): consolidate native ARM64 compiler, runtime and consumers #5021 (overlapping ports and consolidations, per the overlap comment)🤖 Generated with Claude Code