Skip to content

feat(libkernel): JIT shared memory on macOS hosts - #7937

Merged
adriadam10 merged 8 commits into
boykopovar:mainfrom
DavidSamir:feat/libkernel-jit-shared-memory-macos
Oct 23, 2026
Merged

adriadam10 merged 8 commits into
boykopovar:mainfrom
DavidSamir:feat/libkernel-jit-shared-memory-macos

Conversation

@DavidSamir

Copy link
Copy Markdown
Contributor

What

JIT shared memory (sceKernelJitCreateSharedMemory, sceKernelJitCreateAliasOfSharedMemory, sceKernelJitMapSharedMemory) on macOS hosts. On main, Jit.cpp creates its object with memfd_create, so libkernel does not compile on macOS (use of undeclared identifier 'MFD_CLOEXEC'). XPSemu (PPSA97358) maps QEMU's translation buffer through these calls.

  1. feat(libkernel): back JIT shared memory with an unlinked temporary file on macOS. The object is an mkstemp file in the temporary directory, unlinked at once and marked close-on-exec, the way shm_open already makes its anonymous objects on macOS (File/src/SharedMemory.cpp).
  2. fix(libkernel): make JIT shared memory views executable after mapping them on macOS. macOS refuses to create an executable shared file mapping (mmap returns EPERM for PROT_READ | PROT_EXEC and for read-write-execute), but mprotect adds execute to the same mapping. DoMapShared, whose only caller is the JIT map, maps without execute and then applies the full protection on macOS.
  3. test(libkernel): guest_jit_shared_memory runs on macOS. The case that maps a JIT descriptor through the guest's mmap is skipped there: file-backed guest mmap throws on macOS (DoMapFileBacked_nid_no_patch), which is a separate topic.
  4. docs(debt): a macOS entry next to the JIT entry: the backing, the execute-after-map step, the Rosetta behaviour below, and the unsupported mmap of a descriptor.

Measured under Rosetta 2 with a standalone x86-64 program (the probe is attached below, not added): code written through a read-write view runs from a read-execute view of the same object the first time, but rewriting it after it has run leaves the old translation in place (the old value is returned again) until the read-execute view's protection changes. A single read-write-execute view sees its own rewrites. XPSemu and VLC map one read-write-execute view (__wrap_mmap in their ps5/compat/mmap.c), so they are not affected. A title that writes through an alias and executes through another view after rewriting would run stale code; the debt entry says so.

Tested

Apple M2, macOS 26, MoltenVK 1.4.x (Vulkan SDK 1.4.363), x86-64 build under Rosetta 2, Release, on main with @mugurc's macOS series (ci/macos-series-2, which main alone needs to build on macOS) and #6836.

  • guest_jit_shared_memory_tests: before this change libkernel does not compile on macOS; with it, the test passes: write through an alias and run through the executable view, one read-write-execute view, dup, dup2 keeping the descriptor table in step, and the invalid arguments. The mmap case is skipped on macOS.
  • XPSemu (PPSA97358, catalog build, relinked with --macos --to-rosetta --macos-fixed-base 0x400000), APS5_TRACE_MEMORY=1: the title maps its 128 MiB translation buffer, map shared 0x7400000000+0x8000000 prot=0x7, runs 16 s, prepares and draws its first shaders, then stops at the MoltenVK pipeline error fix(shader): preserve raw fragment exports for integer attachments #6235 covers (output of type float4 is not compatible with a MTLPixelFormatR8Uint color attachment). Nothing past that is tested.
  • Not tested: VLC (PPSA85300; the catalog's sha256 no longer matches its archive) and RetroArch (PPSA99169: its eboot makes no JIT call before handing over to picker/picker.bin).
  • Linux and Windows code is unchanged.

The probe (clang -arch x86_64 probe.c && ./a.out on the M2):

int fd = mkstemp(path); unlink(path); ftruncate(fd, 0x10000);
unsigned char* w = mmap(0, 0x10000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
unsigned char* x = mmap(0, 0x10000, PROT_READ, MAP_SHARED, fd, 0);
mprotect(x, 0x10000, PROT_READ | PROT_EXEC);              /* ok; mmap with PROT_EXEC: EPERM */
write mov eax, 42; ret through w; call x                   /* 42 */
write mov eax, 7;  ret through w; call x                   /* 42: old translation */
mprotect(x, R); mprotect(x, R | X); call x                 /* 7 */

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none (building main on macOS needs @mugurc's macOS series, as for the other macOS host changes)
  • Fixes: none
  • Unblocks: PPSA97358 on macOS (no bring-up discussion)
  • GPUs tested: n/a
  • AI-assisted: yes (Claude Code)

@github-actions github-actions Bot added the unblocks title Names a title ID on the Unblocks line label Oct 19, 2026
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 8658 core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp L1218
core/libs/prx/libkernel/DirectMemory/Jit.cpp L142-144

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 4449 core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp
PR 4630 docs/dev/debt/libkernel.md
PR 5021 core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp
core/libs/prx/libkernel/DirectMemory/Jit.cpp
docs/dev/debt/libkernel.md
PR 6044 core/libs/tests/GuestJitSharedMemory.cpp
PR 6149 docs/dev/debt/libkernel.md
PR 6740 docs/dev/debt/libkernel.md
PR 6830 docs/dev/debt/libkernel.md
PR 6936 docs/dev/debt/libkernel.md
PR 7049 docs/dev/debt/libkernel.md
PR 7088 docs/dev/debt/libkernel.md
PR 7105 core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp
docs/dev/debt/libkernel.md
PR 7121 docs/dev/debt/libkernel.md
PR 7149 core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp
docs/dev/debt/libkernel.md
PR 7201 core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp
core/libs/prx/libkernel/DirectMemory/Jit.cpp
PR 7509 docs/dev/debt/libkernel.md
PR 7565 docs/dev/debt/libkernel.md
PR 7653 docs/dev/debt/libkernel.md
PR 8010 docs/dev/debt/libkernel.md
PR 8520 docs/dev/debt/libkernel.md
PR 8553 docs/dev/debt/libkernel.md
PR 9074 docs/dev/debt/libkernel.md
PR 9133 docs/dev/debt/libkernel.md
PR 9256 docs/dev/debt/libkernel.md
PR 9669 docs/dev/debt/libkernel.md
PR 9693 docs/dev/debt/libkernel.md

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 19, 2026
@blawesom

Copy link
Copy Markdown
Contributor

Rebase handoff — rebased onto current main (authorship unchanged). One docs-debt conflict, unioned.

Verified on Linux (CachyOS, kernel 7.2.9, GCC 16.2.1, Ninja, Release, BUILD_TESTING=ON, AMD RX 9070 XT / RADV): builds; conventions pass; full suite green apart from the four known main regressions (#5444 Mesa, #7584 BDA, #7766, #7784), none from this PR (the JIT shared-memory path is macOS-gated).

To pick it up:

git fetch https://github.com/blawesom/AnyPS5 rebase/7937
git checkout <your branch>
git merge --ff-only FETCH_HEAD

Head is 66df8ce42.

…ared-memory-macos

# Conflicts:
#	docs/dev/debt/libkernel.md
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 19, 2026
@DavidSamir

Copy link
Copy Markdown
Contributor Author

Linux check of head 84746e14d8 merged onto main bf2c63f85. Ubuntu 24.04 in WSL2, i7-11700F, GCC 13.3, Ninja, Release, BUILD_TESTING=ON.

  • guest_shm_open, guest_jit_shared_memory and guest_msync pass in 3 runs of 3. This includes the new guest_shm_open case, which runs mov eax, 42; ret from an anonymous shm_open object through a read-execute mmap.
  • The __APPLE__ branches are not compiled on Linux, so this run covers the shared test and the Linux paths only.

AI-assisted: yes

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 20, 2026
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 20, 2026
@adriadam10 adriadam10 self-assigned this Oct 21, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 21, 2026
…ared-memory-macos

# Conflicts:
#	docs/dev/debt/libkernel.md
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 21, 2026
@maximilianfv

Copy link
Copy Markdown
Contributor

Test report: the macOS JIT mapping on an M1 Pro with macOS 27

The probe from the description, run on different hardware and a newer macOS: Apple M1 Pro, macOS 27.0.1, x86-64 under Rosetta 2 (clang -arch x86_64), unlinked mkstemp file in $TMPDIR. It reproduces every line of the M2 / macOS 26 results:

Step Result
mmap with PROT_READ | PROT_EXEC, MAP_SHARED EPERM
mmap with PROT_READ, then mprotect to PROT_READ | PROT_EXEC ok
write mov eax, 42; ret through the read-write view, call the read-execute view 42
rewrite to 7 through the read-write view, call again 42 (old translation)
mprotect the read-execute view to PROT_READ and back, call again 7
one view, mprotect read-write to read-write-execute, write 9, call, write 11, call 9, then 11

So the execute-after-map step also holds on macOS 27, and the stale-translation behaviour the debt entry describes is the same there.

Two side observations from the same host, for the debt entry if useful:

  • Executing from a shm_open object is different: its read-execute or read-write-execute view maps without error, but the first call raises SIGBUS, under Rosetta and natively. An mkstemp file, as this PR uses, does not have that problem. This matters only if the backing is ever switched to shm_open.
  • Natively on arm64 (not how titles run), mprotect to read-write-execute on the shared file view fails with EACCES, and the read-execute alias sees rewrites immediately. Rosetta-only titles are not affected.

Not run: this PR's guest_jit_shared_memory_tests itself, because main needs the macOS series to build here as well. This is the probe only.

AI-assisted: yes (Claude Code ran the probe and wrote the report).

@maximilianfv maximilianfv mentioned this pull request Oct 23, 2026
8 of 11 tasks
@adriadam10
adriadam10 merged commit 606999f into boykopovar:main Oct 23, 2026
10 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Re-checked after the red Linux run: the failing test (agc_driver_storage_write_back_no_window, segfault) is an AGC test on an older head, and this diff is __APPLE__-gated plus a debt entry and one test-skip line, so it can't reach that code. Diff unchanged since the first review; merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs libkernel unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants