Skip to content

macOS support #1

Open
Open

Description

@TheGuyWhoIsIdiot

It'd be wonderful having support with macOS as I am an intel Mac user.
For vulkan shaders load, you could attempt to use MoltenVK to process everything to load under Metal.

I'll probably make pull requests to introduce support for this project.

Activity

  1. boykopovar commented on Sep 24, 2026

    @boykopovar
    Owner

    I'm not against a pull request for macOS support if:

    1. It doesn't require reworking the overall rendering logic for a less efficient version.
    2. MoltenVk won't build on Linux and Windows (as far as I know, that's easy to do).
    3. It's x86-64, like yours.

    But for macOS, I'm more concerned about the libc.prx (especially the work of exceptions) and libkernel.prx implementation. These libraries contain a lot of platform-specific code.

  2. Vladislav-Kalinkin commented on Sep 25, 2026

    @Vladislav-Kalinkin

    Hello. Just about macOS. I'm implementing a similar project on Mac related to PS4 (a static recompiler on native Apple Silicon). If you want to support macOS, I won't mind if you use my code to support the processor (wie-project/PS4Recomp).

  3. franpeza commented on Sep 26, 2026

    @franpeza

    Hi! I was interested in macOS support, so I started experimenting in my own fork to see how far a port could get. It went further than I expected (I have a game booting and running with sound), so I'm sharing the progress here in case it's useful.

    Your conditions from this issue

    1. Rendering: the rendering logic is unchanged. Vulkan runs through MoltenVK. The only shader-side change: MoltenVK has no subgroup operations in vertex shaders, so the recompiler emits single-lane equivalents for stages the device reports as unsupported. The default is all stages supported, so nothing changes on Linux or Windows.
    2. MoltenVK: only fetched and staged on Apple; nothing is built on Linux or Windows.
    3. x86-64: yes, x86-64 under Rosetta 2.

    libc exceptions and libkernel (the concerns you mentioned)

    • The exception runtime now reads RTTI through its own Itanium-ABI structs instead of libstdc++ internals. Only the host glue for exception_ptr differs between libstdc++ and libc++.
    • libc.prx remains the process-wide exception runtime
    • The libkernel and libc changes are mostly #if APPLE branches.

    I also fixed a few problems that also affect other platforms (happy to send these as separate PRs)

    Not verified yet: the Linux and Windows builds of the shared changes (exception runtime, TLS scan, unwinder helpers). I'd check those before opening any PR.

    If this direction works for you, I'd be glad to split it into reviewable PRs.

  4. theChaosCoder commented on Sep 26, 2026

    @theChaosCoder

    Fyi Apple is phasing out Rosetta 2, with full support ending after macOS 27 (Golden Gate).

  5. TheGuyWhoIsIdiot commented on Sep 26, 2026

    @TheGuyWhoIsIdiot
    Author

    Fyi Apple is phasing out Rosetta 2, with full support ending after macOS 27 (Golden Gate).

    Support only ends at macOS 28, though users can always dual boot older Mac versions.

  6. theChaosCoder commented on Sep 26, 2026

    @theChaosCoder

    nope ends with 27. https://developer.apple.com/news/?id=w5ngl9k2

    macOS 27: Final release to support Rosetta — Intel-only apps will no longer run on Mac computers with Apple silicon after this update.

  7. Vladislav-Kalinkin commented on Sep 26, 2026

    @Vladislav-Kalinkin

    It's surprising that everyone heard that Rosetta 2 is dying, but its active implementation continues. Given that this is not a JIT emulator, it would be logical to make at least a fork with static translation of x64 instructions into C, and from it to ARM and adaptation to the specifics of macOS. It's much more difficult, of course, but I think it's cleaner. I don't know if I'll be able to finish my project (there is no such patience and incentive, because I don't play), but I managed to get to the first Unreal Engine 4 error in Hogwarts Legacy in my PS4 project. That is, at least the processor part is possible.

  8. TheGuyWhoIsIdiot commented on Sep 26, 2026

    @TheGuyWhoIsIdiot
    Author

    nope ends with 27. https://developer.apple.com/news/?id=w5ngl9k2

    macOS 27: Final release to support Rosetta — Intel-only apps will no longer run on Mac computers with Apple silicon after this update.

    After this update, which refers to macOS 28. I still can use Rosetta on a silicon chip Mac running this version, though with an explicit warning.

  9. TheGuyWhoIsIdiot commented on Sep 26, 2026

    @TheGuyWhoIsIdiot
    Author

    It's surprising that everyone heard that Rosetta 2 is dying, but its active implementation continues. Given that this is not a JIT emulator, it would be logical to make at least a fork with static translation of x64 instructions into C, and from it to ARM and adaptation to the specifics of macOS. It's much more difficult, of course, but I think it's cleaner. I don't know if I'll be able to finish my project (there is no such patience and incentive, because I don't play), but I managed to get to the first Unreal Engine 4 error in Hogwarts Legacy in my PS4 project. That is, at least the processor part is possible.

    I genuinely think this goes against the idea of the project which targets native execution rather than being transformation recompilation.

  10. Vladislav-Kalinkin commented on Sep 26, 2026

    @Vladislav-Kalinkin

    But tell me. And what about, for example, those who buy new Macs, which initially have the minimum version - 28? Even if you imagine the possibility of a rollback, most of you won't even know about it. And also what to do with Wine, Shadps4 and many other projects? “We won't support your version anymore?”

  11. mugurc commented on Oct 5, 2026

    @mugurc
    Contributor

    Hi @boykopovar, I have a macOS port working locally, rebased on current main. It runs x86-64 under Rosetta 2 and was tested on an M5 Max with macOS 26.5. Before opening pull requests I'd like to know how you want it split, and to avoid overlapping with @franpeza's port.

    Your conditions:

    1. Rendering is unchanged, and Vulkan goes through MoltenVK. The recompiler changes emit SPIR-V that means the same on every driver but survives SPIRV-Cross's translation to Metal:
      • ordered not-equal;
      • NaN tests on the bits;
      • subgroup results that SPIRV-Cross would otherwise move into ?: / &&;
      • a texture fence before image atomics.
    2. MoltenVK is not built anywhere. It comes from the Vulkan SDK at run time, and a packaging script copies it into an .app. The Linux and Windows builds are unchanged.
    3. Only x86-64.

    libc and libkernel:

    • Exceptions:
      • On macOS, host code keeps libc++abi, because libc++ binds __cxa_throw two-level. libc.prx's runtime serves guest code.
      • Guest frames are found through their PT_GNU_EH_FRAME, using a dl_iterate_phdr emulation over the original program headers that the relinker keeps.
      • Host frames are found through __TEXT,__eh_frame; the prx are linked with -no_compact_unwind.
    • The libkernel and libc changes are __APPLE__ branches:
      • shm_open instead of memfd;
      • mach_vm_map instead of MAP_FIXED_NOREPLACE;
      • guest TLS through a per-image pthread key: macOS cannot set the fs base, so the relinker rewrites %fs accesses into stubs;
      • the guest starts on a secondary thread, so AppKit keeps the main thread.

    Relinker:

    • --macos writes Mach-O executables and guest modules: rebases, flat-namespace binds, the export trie, guest TLS (including general-dynamic TLS across modules) and init/fini.
    • --to-intel places its stubs there too. For Rosetta it also lowers RDSEED, RDPID and CLWB. Rosetta runs AVX2, FMA and BMI, but not SSE4a, SHA, ADX, RDSEED, RDPID, CLWB, CLZERO or MONITORX.

    Tested on macOS:

    • All non-GPU tests pass.
    • 148 of the 166 AGC and shader tests pass through MoltenVK. 16 need f64, 64-bit atomics or the shader clock, which Metal does not have. One is still open: a signed zero in an SDWA f16 multiply.
    • I have no PS5, so no commercial title was tested. Testing used the fixtures and a small Breakout guest built like a PS5 title (video out, pad, audio out), which runs at 60 fps as a double-clickable app.

    The code was written with AI assistance; I reviewed and tested it.

    Proposed split, each against main:

    1. fix(recompiler): the SPIRV-Cross-safe emission above, neutral for other drivers.
    2. fix(libc): __gcc_personality_v0 exported for guest C code (it is missing on every platform), and strtoimax/strtoumax errno.
    3. feat(macos): the build and the prx libraries.
    4. feat(macos): relinker --macos (depends on 3).
    5. feat(macos): app bundle packaging.

    @franpeza, you mentioned a port that boots a game. I'd rather combine our work than duplicate it: if you are about to open PRs, tell me which parts you cover and I'll adapt mine.

  12. ymatrix1 commented on Oct 5, 2026

    @ymatrix1
    Contributor

    I'm currently helping other similar project that supports MacOS, but are still very early stage https://github.com/mattias800/prosper . Basically the idea is to work like Wine but for PS5, it doesn't need to rebuild the binaries to the platform.

  13. boykopovar commented on Oct 6, 2026

    @boykopovar
    Owner

    @mugurc Have you tested libc exception runtime modified on windows or linux? I'm also interested in whether there is any dependency on Rosetta, and whether your version will work without emulation on macOS with x86-64 (it turns out there are some). I think you can open a pr to support exceptions first.

  14. mugurc commented on Oct 6, 2026

    @mugurc
    Contributor

    @boykopovar Not yet: I only have this Mac, so it was tested on macOS only. Here is what the exception changes touch outside macOS:

    • __gcc_personality_v0 is also exported to guest code now, because guest C built with -fexceptions imports it, and CallPersonality recognizes that export. On Linux and Windows the host __gxx_personality_v0 and __gcc_personality_v0 are unchanged; they forward to the same code.
    • The exception_ptr model and the type_info declarations are only compiled with libc++/libc++abi (_LIBCPP_VERSION, _LIBCPPABI_VERSION). The Linux and Windows builds use libstdc++, so they don't see them.
    • Finding host frames through __TEXT,__eh_frame and the dl_iterate_phdr emulation over the guest program headers are __APPLE__ only.

    The PR's CI will run exception_runtime and exception_c_cleanup on Linux. They are UNIX-only, so Windows only gets the build.

    Rosetta: the build is plain x86-64 macOS, and nothing checks for Rosetta at run time. The one Rosetta-specific step is in the relinker: --macos lowers RDSEED to RDRAND, drops CLWB and turns RDPID into 0, because Rosetta lacks them. That output still runs on an Intel Mac, where it's just unnecessary, so I can put it behind its own flag and leave Intel output as it is. I have no Intel Mac, so I can't test there.

    I'll open the exceptions PR first, against current main.

  15. GeorgeHulpoi commented on Oct 6, 2026

    @GeorgeHulpoi

    +1. It would be nice to have support for MacOS.

  16. 164 remaining items

  17. rooklz commented on Oct 22, 2026

    @rooklz
    Contributor

    Two more, and a correction:

    • guest_host_standard_error is test(libkernel): throw through the guest runtime on macOS in the host standard error test #9654, a test-only change. Its last check looks for libstdc++'s what(): report kept, and on macOS libc++abi words the same report libc++abi: terminating due to uncaught exception of type std::runtime_error: report kept. The report does reach the host's stderr. Correction to my note above: the unwind tables don't matter for this one. The waitpid failure I saw was EINTR from running it under lldb; outside the debugger it only ever failed at the wording.
    • guest_math_exact passes here when the test is built at -O0 and fails at the optimised build, with the same libc. Every check in it compares 80-bit long double bits (signalling NaN pass-through, errno), so I suspect Rosetta's x87 emulation rather than libc. I'm not taking it; if someone has an Intel Mac, a run there would settle it.
  18. krishalivaibhav commented on Oct 22, 2026

    @krishalivaibhav
    Contributor

    Balatro (PPSA21402) on a fanless M2: what limited it to ~40 FPS, and 60 FPS with two changes. Update to my 13 October note above. Setup: MacBook Air M2 (no fan), 16 GB, macOS 27.2, MoltenVK 1.4.2, x86-64 under Rosetta 2, 1920x1080, the macOS series plus local changes (not main, which does not build on macOS).

    Symptoms. 60 FPS on the menu at first, falling to ~40 after 1.5–3 minutes, and 11–22 FPS on the shop and booster-pack screens. powermetrics showed the drop is thermal: the GPU was ~95% busy at full clock, then the Air lowered its GPU clock (1365 → 840 MHz) with the same work per frame. So the fix had to be less GPU work per frame, not a higher frame cap.

    Cause 1 — barycentric emulation on sprite draws (shop and pack screens). The title's sprite/text pixel shader keeps I/J live after its v_interp pairs, only as the value lanes outside the export mask keep. That sends every such draw through the generated tessellation stages, and on MoltenVK each one restarts the Metal render encoder (a full 1080p tile store and load). The pack screen issues ~730 of these per frame. A 1x1 scissor on them changed nothing; skipping only their draw call took the screen from ~11 to ~34 FPS. #9193 (imellag) fixes this by reading I/J from BaryCoordKHR on MoltenVK. On the same untouched pack scene with warm caches: 45.4 → 60.0 FPS, frame p99 25.4 → 18.4 ms, GPU busy 21.3 → 12.4 ms per frame; afterwards only the title's rect-list draws still use tessellation (about 4 per frame, under 1 ms). Details and traces are on #9193. I had written a narrower fix for the same case and am not opening it.

    Cause 2 — exact GCN float emulation in full-screen shaders. The menu background (a procedural swirl, ~7 ms) and the CRT pass (~3.8 ms) are full-screen 1080p shaders, and almost all of their cost is per-pixel ALU: every f32 operation carries NaN-propagation and denormal-flush selects, and each v_sin/v_cos computes the native result plus two or three polynomial series. MoltenVK fast math made no difference. A local, opt-in experiment that drops those (native min/max, plain mul/add/mad, native sin/cos) cut menu GPU time ~22%. It deliberately gives up bit-exact GCN behaviour, so it stays a local experiment, not a PR; I'd welcome views on whether a per-title or user option for that would be acceptable.

    Result. With the float experiment plus my narrower fix for cause 1 (the session predates testing #9193, which measured better on the pack scene), a 5-minute session with shop, packs and normal play held 58–60 FPS throughout, including past the point where the Air used to throttle, with GPU time at 10–11 ms per frame and no visual problems seen. Clip of the #9193 build in normal play, FPS in the title bar:

    balatro-m2-pr9193-60fps.mp4

    What's left. The command submission thread is 94–99% busy at 60 FPS on the pack screen (~455 draws per frame at ~20 µs each; vertex data is already used in place), so CPU is now the tightest margin. A finding worth knowing for anyone profiling on Mac: per-pass GPU timestamps (pass splits) cost several FPS by themselves here, and the overlapping per-pass ranges are not exclusive GPU time; the fence-based gpu_busy_ms_per_present in the trace summary is the reliable number.

    AI-assisted: yes (Claude Code). Measurements are from my M2; gameplay observations are mine.

  19. imellag commented on Oct 22, 2026

    @imellag
    Contributor

    Proposal: keep the f32 NaN emulation exact, but pay for it only when a NaN appears. This is about the same cost as @krishalivaibhav's "cause 2" above. It's a design I'd like feedback on before writing it.

    What it costs today. TranslationContext::nanResultF32 wraps every f32 add, sub, mul and fma: if a source is NaN, the result is the first NaN source (quieted in IEEE mode), otherwise a generated NaN becomes 0xffc00000. In MSL that is three or four (x & 0x7fffffff) > 0x7f800000 tests and selects per operation. Apple GPUs return 0x7fc00000 for every NaN result, so the payload can't come from the hardware.

    Measured on an M4 Pro (macOS 27.0.1, MoltenVK 1.4.2), main + #8658 + #9193 + #9263 + #8612. The off arm uses a local switch that returns the raw result; it is for measurement only and not exact:

    Same scene, warm caches, 10 s windows NaN emulation on off GPU per frame
    God of War Sons of Sparta, Cyclops fight 39.45 / 39.47 fps 46.59 fps, one run so far (+18%) 22.8 → 18.8 ms
    Planet of Lana, village 29.40 / 30.04 fps 31.69 / 31.48 fps (+6%) CPU-bound here

    The f32 denormal flush already runs in hardware on this stack (#8578: 0 of 5472 probe results differ), so these selects are the main per-operation emulation cost left.

    What I tried. A Metal compute microbenchmark: 16M threads, 4 independent chains of 48 mul/add/fma each, mathMode = .safe, variants interleaved over 40 rounds after warm-up, median GPU time. Every exact variant's output is compared bit for bit with the current form.

    Variant no NaN 0.1% of threads NaN 5% of threads NaN
    A: plain operations (not exact) 1.01 ms 1.01 ms 1.01 ms
    B: today's select chain (as SPIRV-Cross emits it) 5.81 ms 6.01 ms 5.78 ms
    C: per-operation if (isnan(r)) r = chain 4.53 ms 4.70 ms 4.53 ms
    D: plain region, exact re-run if any result was NaN 1.03 ms 1.12 ms 4.85 ms

    A branch per operation (C) saves only about a fifth. Doing a region plain and re-running it exactly only when a NaN shows up (D) costs about the same as plain code while NaNs are rare. It falls back to the cost of today's form when many lanes in a SIMD group hit a NaN.

    Proposal (D as an IR pass, exact for every input):

    1. Split each block into regions of emulated f32 operations: a region ends at a side effect (store, export, atomic, barrier), a cross-lane operation and a size cap.
    2. Fast path: the region's operations without the NaN selects.
    3. Check: OR together isnan of each fast result whose NaN could leave the region or be hidden inside it. A result whose only users are other emulated add/sub/mul/fma in the same region needs no check, because those operations always return a NaN when a source is NaN, and their own results are checked. Results used by anything else (legacy multiply's zero select, min/max, compares, conversions, bitcasts, live-outs) are checked.
    4. If the check is true, run the region again from its inputs with today's exact nanResultF32 chains and use those results.

    Why it is exact. When no checked result is NaN, no emulated operation in the region produced a NaN, so every select in today's chain returns the raw result. The fast path's bits are then the exact path's bits. When any result is NaN, the region runs today's code. So the existing emulation stays the only definition of the NaN behaviour, and no new RDNA hardware oracle is needed.

    Costs and risks to measure:

    • Region inputs stay live until the check, so register pressure goes up; the size cap bounds it.
    • The re-run doubles the region's code size.
    • Shaders that hit NaNs often (rare in practice) pay about what they pay today.

    Tests: the existing f32_arithmetic_nan, f32_separate_rounding and denormal tests, plus new cases with a NaN produced in the middle of a region and hidden by a min/max, a compare and the legacy multiply.

    Questions:

    • Is an IR pass that clones a region under a branch acceptable in the recompiler, or should the split happen in the SPIR-V backend, which already emits inline selections (SpirvBdaFault.cpp)?
    • Is anyone already working on the NaN selects?
    • @krishalivaibhav, would this cover the NaN part of your opt-in experiment without giving up exactness?

    AI-assisted: yes (Claude Code). The measurements are from my M4 Pro.

  20. krishalivaibhav commented on Oct 22, 2026

    @krishalivaibhav
    Contributor

    @imellag yes: your design would cover the NaN part of my experiment exactly, and on Balatro that is the largest single piece of it. I split the experiment apart to check.

    Setup: MacBook Air M2 (fanless), macOS 27.2, MoltenVK 1.4.2, the macOS series (42c3fd0 plus local changes; main doesn't build on macOS), Balatro (PPSA21402) main menu, which is dominated by two full-screen 1080p shaders: a procedural background (27 v_sin/v_cos, no samples) and the CRT pass (51 samples). Same build for every arm, each arm with its own warm shader cache, APS5_TRACE_SECONDS=12:15, no other trace or profile switch. A local measurement switch returns the raw result from TranslationContext::nanResultF32 (like your off arm, not exact).

    The f32 denormal flush is already in hardware on the M2 too: [float-controls] f32 denormal flush: hardware (0 of 5472 probe results differ from the emulated flush), so as on your M4 Pro the NaN selects are what is left of the per-operation cost.

    Generated code (SPIR-V from the shader dump, through spirv-cross --msl), the three heaviest pixel shaders:

    Shader MSL lines selects > 0x7f800000 NaN tests
    background (27 sin/cos) 1190 → 901 (−24%) 708 → 419 (−41%) 289 → 48
    CRT (51 samples, 23 sin/cos) 2255 → 1918 (−15%) 1642 → 1287 (−22%) 588 → 287
    third (12 sin/cos) 978 → 854 (−13%) 386 → 249 (−35%) 127 → 21

    GPU time per frame (fence-based gpu_busy_ms_per_present; the menu is vsync-locked, so FPS stays at 60 either way):

    runs GPU busy per present
    NaN emulation on 4 12.73, 12.77, 13.63, 13.81 ms (the last with other load on the machine)
    nanResultF32 off 4 11.83, 11.85, 11.89, 11.96 ms

    So about 0.8–1.7 ms per frame (7–13%) on this scene. For comparison, my earlier opt-in experiment as a whole (NaN, denormal and tiny-product flush off, native min/max/med3/clamp, v_mac as one fma, native sin/cos) measured 12.9 → 10.7–10.9 ms on the same menu; native sin/cos alone was about 1.1 ms of that, and native min/max and the fused v_mac were within noise.

    What your design would not reach, from the dumps: the NaN tests left after dropping nanResultF32 come from other paths: the signaling-NaN quieting around FPMin/Max/Med3 (ieeeMinMaxF32), conversions, and the range handling inside the v_sin/v_cos emulation (each computes the native result plus two or three polynomial series and selects between them). Those would stay at today's cost. The trig series is the other cost worth a separate look for shaders like this background.

    On the region split: these are long, mostly straight-line shaders (the background's only branches are a few bounds-checked uniform loads at the top), so one region per block would be large; the size cap and register pressure you mention will matter here. If you build it, I can run it on this scene on the M2 and post the same numbers.

    AI-assisted: yes (Claude Code). The measurements are from my M2.

  21. mugurc commented on Oct 22, 2026

    @mugurc
    Contributor

    @rooklz on guest_math: here (M5 Max, macOS 26.5.1, the series at 53db0ba) the rand_r(nullptr) case passes with the test binary as it is, and the first check that fails is acosf(0.5f) (0x3f860a91), which your #9668 covers. rand_r throws a host std::invalid_argument, which the system unwinder handles with the test's compact unwind. The test's own tables matter only where libc's unwinder walks its frame, that is libc's __cxa_throw (as in guest_bad_optional_access) and the _Unwind_* functions.

    So I would keep #8311 to the targets that need the tables, rather than change every test executable on macOS. If the rand_r case still ends in terminate on your machine, could you post what it prints, and whether your libc.prx shows __eh_frame under size -m? If it reproduces, I'll add guest_math_tests to #8311.

  22. jotitadev commented on Oct 22, 2026

    @jotitadev
    Contributor

    Follow-up on my 10-18 note about the two tests that are intermittent on Apple. I said agc_driver_cross_workgroup_flag didn't look like store visibility, and that was only partly right. Setup: M2 Max, macOS 27.2, x86-64 under Rosetta, #8658 @ f48446a, MoltenVK 1.4.2 and MoltenVK main + SPIRV-Cross #2704.

    agc_driver_cross_workgroup_flag: two causes. In a local copy of the test, the reader stores the poll count at which it saw the flag (40 wave64 and 40 wave32 dispatches per process, 6 rounds, plain store and atomic store alternating in the same binary):

    driver GLC store of the flag wave32 gave up wave32 seen on 1st poll wave64 gave up wave64 seen on 1st poll
    MoltenVK 1.4.2 plain (current) 21/240 0/240 15/240 0/240
    MoltenVK 1.4.2 OpAtomicStore device scope 1/240 236/240 22/240 134/240
    MoltenVK main + #2704 plain (current) 25/240 0/240 30/240 0/240
    MoltenVK main + #2704 OpAtomicStore device scope 6/240 224/240 12/240 119/240
    • Visibility. fix(shader): read GLC/DLC BDA dwords with device-scope atomic loads #2259 made the GLC load an OpAtomicLoad, but the GLC store is still OpStore ... Volatile in EmitBdaStoreAt. In the MSL it is a plain *(device uint*)p = 1u. On Apple the reader never sees that store on its first poll; it takes 300 to 1M polls. With the coherent aligned dword store as an OpAtomicStore at device scope, it sees the flag on the first poll in about 95% of wave32 dispatches.
    • Forward progress. The atomic store still leaves gave-ups, mostly in wave64. To check, I had the writing workgroup do one GLC load of the result word before it stores the flag. With that change the reader gives up in about 95% of dispatches, and the writer read 0 every time. So the writer started before the reader gave up, but its store did not land within 1M polls even as an atomic. The writing workgroup stalls while the other one spins, and neither Vulkan nor Metal guarantees forward progress between workgroups.

    So a store-side change like #2259's would be correct by the memory model, but it would not make this test reliable on Apple. I'm not proposing it on its own.

    agc_driver_uint8x4_color_target fails in 19 of 150 runs on MoltenVK 1.4.2 and 23 of 150 on MoltenVK main. The _no_resident variant passes 150 of 150 on both. It is nearly always one of the XOR cases (SW_64KB_D_X target with XOR: byte 0 is 90, expected 49, or the Z_X one): the target still holds the fill byte (0x5a), so the draw never reached guest memory. In 100 runs each on MoltenVK 1.4.2:

    run failures
    default (the probe says host imports resolve write protection: no ... imported ranges stay watched) 14-16
    APS5_WRITE_WATCH_IMPORTS=watch 11
    APS5_WRITE_WATCH_IMPORTS=unwatch 0 (also 0/100 on MoltenVK main)
    APS5_NO_HOST_IMPORT=1 0
    APS5_NO_WRITE_WATCH=1 0
    APS5_PACKET_EPOCH=1 14
    no address reuse between cases (blocks leaked) 11

    So on macOS, with host imports that stay watched, the resident target's write-back sometimes doesn't show up in the guest read. The full ^agc set with unwatch gives the same 18 failures as the default, so unwatch costs nothing in the suite. I haven't found where the write-back is lost: the probe may not cover this case on macOS, or the flush may race with re-arming the protection.

    Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

  23. tkcm01 commented on Oct 22, 2026

    @tkcm01
    Contributor

    @rooklz on guest_getrlimit: it passes on the M4, 3 runs of 3 (same tree as my earlier list), which fits your explanation. Here kern.maxfiles is 184320, kern.maxfilesperproc 92160 and kern.num_files about 8550 before and after the runs, so the per-process limit is reached long before the system table and the pipe loop ends with EMFILE. On your machine 122880 − ~70000 open leaves fewer than the 61440 per-process limit, hence ENFILE.

    AI-assisted: yes

  24. Mauro42K commented on Oct 22, 2026

    @Mauro42K

    Hi @Gonic0 — I completed the requested negative controls on my Apple M1 MacBook Pro (8 GB RAM, macOS 27.0.1).

    Tested commit: e0a3cce.

    I used a separate worktree and build, reverting each of the two macOS-specific blocks in core/libs/prx/libSceNet/Export.cpp independently.

    Results:

    • Both fixes present: guest_sce_net 5/5 PASS.
    • Leading-zero IPv4 check removed: Subprocess aborted.
    • EPROTOTYPE mapping removed: Subprocess aborted.
    • Both fixes restored: guest_sce_net 5/5 PASS again.

    These results support that both fixes are required for the test to pass on this M1 configuration.

    Limitation: CTest recorded the aborts but did not print the exact failing assertion or source line. I also attempted LLDB, but it stalled while launching the x86_64 executable under Rosetta 2, so I cannot independently confirm the specific assertion locations you observed on M5 Pro.

    The worktree was restored to a clean state. Detailed logs, patches, and hashes are retained locally.

    AI-assisted: yes. Codex was used to perform and document the validation.

  25. rooklz commented on Oct 22, 2026

    @rooklz
    Contributor

    M5 data point: 32 catalog homebrew titles run natively, converted with --macos --to-rosetta, on MoltenVK.

    Same tree as my earlier notes (ci/macos-series-2 at 53db0badb with #7524, the code of #7937 and the libSceSsl change of #7332), macOS 26.2, Apple clang 21, x86-64 under Rosetta 2; MoltenVK built from the MBridge copy with an x86-64 Vulkan loader, 75 s per title, no game data supplied. The emulator-side stops, grouped:

    Stop Titles
    AGC graphics: device does not support mesh shaders (this MoltenVK reports no mesh-shader feature) PPSA99641 Wind Waker HD, PPSA69424 Wave Race 64, PPSA69423 Rush 2, PPSA99640 Twilight Princess, PPSA99305 Dusklight, PPSA99314 Eternal Darkness
    AGC driver: a pixel shader on the mesh path reads barycentrics, and this device has neither VK_KHR_fragment_shader_barycentric nor mesh shaders (the extension is listed, its feature reports false) PPSA98511 ReKHCoM, PPSA99310 Mario 64 (Ghostship), PPSA99320 Ship of Harkinian (Vulkan)
    AGC graphics: last provoking vertex requires VK_EXT_provoking_vertex with provokingVertexLast enabled (listed, feature false; MVK_CONFIG_USE_METAL_PRIVATE_API=1 does not change it on this build) PPSA98001 PokeMMO, PPSA99993 Pulse
    vkCreateGraphicsPipelines: Vulkan result -3, Metal refusing a float4 output on a UINT attachment: #9783 PPSA99313 Doom 3 BFG, PPSA59322 ccleste
    Guest TLS access without a non-empty PT_TLS from the tree's relinker (empty PT_TLS plus fs: accesses); main's relinker converts all five, so this is already fixed there PPSA59322, PPSA99305, PPSA99309, PPSA99630, PPSA99314
    Forbidden syscall instruction; converts with --trap-sdk-syscall-stubs PPSA99004 ProsperoAI, PPSA99360 PS5-ECAM, PPSA69421 SuperTuxKart

    The rest stop in their own code: PPSA99620 and PPSA99621 (Ship of Harkinian, 2 Ship 2 Harkinian) crash in a guest thread reading 0x20(%r12) with r12 null (EXC_BAD_ACCESS, all frames in the title until libkernel's RunGuest), which looks like a missing-asset null since they have no OTR data here; the others ask for their game files and exit. PPSA99666 DOOM, PPSA47402 Syobon Action and PPSA99429 ApricotGS render frames (AGC compute presentation on DOOM, gpu-display flip counters on ApricotGS). Bring-up threads exist for PPSA98512 (#9758) and PPSA99630 (#9762).

    The first four rows are driver or MoltenVK capability gaps rather than title bugs; the mesh-shader and barycentric rows alone account for nine titles. I'm not taking any of them without asking here first.

    AI-assisted: yes

  26. Gonic0 commented on Oct 22, 2026

    @Gonic0

    Thanks @Mauro42K, that's exactly what I was hoping for: both blocks needed on an M1 with 27.0.1 as well as on the M5 Pro with 27.2. I'll add it to #9231's Tested section, crediting you.

    AI-assisted: yes

  27. tkcm01 commented on Oct 22, 2026

    @tkcm01
    Contributor

    guest_filesystem on the M4 (series at 53db0ba): two causes, and with both fixed it passes.

    1. Renaming a file over a dangling symlink returns EIO. libc++'s fs::equivalent reports errc::not_supported (not ENOENT) when a path doesn't resolve, and RenamePath_nid_no_patch only clears ENOENT. fix(libc): rename over a dangling symbolic link on macOS #9911 splits that guard out of Macos port #7201/feat: macOS host support (Apple Silicon via Rosetta 2, Vulkan on MoltenVK) #8658 on its own.
    2. @mugurc, this one is in the series rather than main: the macOS sceKernelGetdirentries in File/src/Stdio.cpp opens openat(fd, ".", O_DIRECTORY), which fails with ENOTDIR for a regular file, so sceKernelGetdents on a plain file returns 0x80020014 where the test (and FreeBSD) expect EINVAL (0x80020016). Locally if (listing < 0) return SceErrorFromErrno(errno == ENOTDIR ? GUEST_EINVAL : errno); makes guest_filesystem pass, and the full suite shows no new failure. (feat: macOS host support (Apple Silicon via Rosetta 2, Vulkan on MoltenVK) #8658's __getdirentries64 version may already behave differently here; I haven't checked it.) The Linux branch also returns EINVAL early for a guest socket via GuestSockets::IsOpen, which the macOS branch doesn't do.

    AI-assisted: yes

  28. tkcm01 commented on Oct 23, 2026

    @tkcm01
    Contributor

    M4 data point with Vulkan, and four of the remaining stable failures.

    Setup: Apple M4 (10-core GPU), macOS 27.0.1, Apple clang 21, x86-64 under Rosetta 2, the same series tree as my earlier list. Unlike @rooklz's M5 setup, no MoltenVK build was needed: in the LunarG Vulkan SDK 1.4.363.0 the loader, MoltenVK 1.4.2, vulkaninfo and the validation layer are already universal (x86_64 + arm64). I installed it headless into the home folder (--root ~/VulkanSDK/1.4.363.0 ... install copy_only=1, no admin rights), and DYLD_LIBRARY_PATH=<sdk>/macOS/lib plus VK_DRIVER_FILES=<sdk>/macOS/share/vulkan/icd.d/MoltenVK_icd.json is enough. The x86-64 vulkaninfo reports Apple M4, MoltenVK 1.4.2, API 1.4.357.

    ctest -j4 --timeout 120: 1648 tests, 58 failed, 95 skipped (M5: 87 failed, 93 skipped). The non-GPU failures are my earlier list and the two flaky tests. The 44 GPU failures, by first failing check:

    • f16 results lose the sign of zero (0x8000 comes back 0x0000), 13: dpp16_operands, f16_fma_denormal_modes, f16_conversion_float_mode, float16_fma_rounding, float16_misc, float16_modifiers, float16_rounding, ldexp_f16_inline_constants, packed_alu, packed_f16_denormal_modes, sdwa_float_selectors, sdwa_ldexp_f16, vop2_modifier_forms.
    • f32 signed zero and tininess: f32_denormal_flush, f32_output_modifier, f32_constant_factor_tininess. f64: f64_add_mul_denormal_modes, f64_rounding_denormal_modes (the other f64 tests skip).
    • No shaderClipDistance/shaderCullDistance: vertex_clip_cull_distances, vertex_clip_cull_draw, vertex_clip_cull_draw_no_key, vertex_user_data_tail.
    • Test fixtures that load libvulkan.so.1, 7: the six agc_driver_guest_memory_lifetime* (tests/GuestMemoryLifetimeSupport.hpp) and agc_driver_indirect_arguments_pending_producer. @jotitadev, these two files aren't in test(agc): load the Vulkan loader with portability enumeration on macOS #7886 (feat: macOS host support (Apple Silicon via Rosetta 2, Vulkan on MoltenVK) #8658 changes both); would you add them there? Also, with test(agc): load the Vulkan loader with portability enumeration on macOS #7886 applied and VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, its instances log VUID-VkDeviceCreateInfo-pProperties-04451 (VK_KHR_portability_subset not enabled), for example in agc_performance_overlay.
    • Metal fragment compile failure: pixel_interlock, pixel_sample_coverage. image_store dlc/tfe leaves 0xdeadbeef: image_cache_bits, image_texel_status.
    • One each: ds_permute, compute_fs_table_calls, image_srgb_sample_copy (and _forced), image_plane_bias_on_single_level_volume, multisample_retirement, sample_mask_multisample, pixel_sample_iteration, rect_list_indirect, stencil_ref_export_unused, tessellation.

    agc_performance_overlay_statistics: taking it, PR today. It is @jotitadev's measurement from #7886: ReadProcessPrivateBytes reads /proc/self/status, and task_info(TASK_VM_INFO).phys_footprint on macOS makes it pass. #7332 had it and is closed; #8658 has it too. I'm splitting it out with both credited. Here it makes agc_performance_overlay_statistics pass, and agc_performance_overlay too once #7886 is applied.

    guest_flexible_memory_declared: taking it, PR today. The declared case loads guest_flexible_memory_module.guest.prx, which is host-built, and expects its 512 MiB array in GuestImageSegmentBytes. macOS's dl_iterate_phdr (specifics/macos/GuestImages.cpp) only reports images with __APS5DATA,__meta. Every relinked module has that section, but the host-built fixture doesn't, so it isn't counted and the budget check fails. I'd fix the fixture rather than the runtime: on macOS it fills its own __meta at load with one PT_LOAD for the array, as ProcessParametersFixture.hpp does for the test program. #7201 would also make it pass, by synthesising headers in dl_iterate_phdr for every host image; the fixture change is test-only, and with it all five guest_flexible_memory_* pass here. @mugurc, you listed it as known in #7631; say so if you'd rather fix it in the runtime.

    agc_write_tracking: the cause, not a claim yet. It isn't page size. On macOS, ImportWatched brackets the whole import window in GuestArena::HostWrite, which opens and marks every page because a driver write into a read-only page fails with EFAULT. So every block of the window gets the CPU stamp. A probe here gives changed = {0, 1, 0} and cpu = {1, 1, 1}, so line 1151's cpu[2] == 0 can't hold, although the untouched block still reads as unchanged. #7201 has the matching __APPLE__ test change (UnchangedSince instead of cpu[2] == 0). With it the test gets past that check but then stops in CheckUnwatchedImport: an import that stays watched did not read its range before the import. I'll look at that one next and announce here before any PR.

    guest_itimer and guest_kernel_app_archive_mmap: a design question for @mugurc. Both install a guest terminate handler (_ZSt13set_terminatePFvvE_nid_postfix) and expect it to see an exception that libkernel throws: Signals.cpp's SIGALRM "not implemented", and ArchivedFile.cpp's mmap refusal out of the noexcept mmap_nid_postfix. On Linux, GCC_GLOBAL_ALIAS sends those throws through libc.prx's runtime. On macOS, host code keeps libc++abi by design, so libc++abi's terminate runs, prints libc++abi: terminating due to uncaught exception of type std::runtime_error: ... and aborts. The guest handler never runs. I see two ways to fix it:

    1. Test-side, like test(libkernel): throw through the guest runtime on macOS in the host standard error test #9654: on macOS the two tests also register the handler with the host std::set_terminate and read the exception through std::current_exception.
    2. Runtime-side: on macOS the guest set_terminate also installs a host handler that forwards to the guest one, and __cxa_current_exception_type/__cxa_current_primary_exception fall back to libc++abi's when libc.prx has no current exception. A title's own terminate handler would then see library throws as on Linux, but this mixes the two runtimes.

    Which one do you want? I'll only write it once you've picked.

    guest_math_exact on an Intel Mac (@rooklz's question) is running on a macos-15-intel runner; I'll post the result when it's done.

    AI-assisted: yes

  29. tkcm01 commented on Oct 23, 2026

    @tkcm01
    Contributor

    Follow-up on agc_write_tracking (M4, same tree).

    The second stop is in the series' describePages, not on main. With #7201's test change for line 1151, the test stops in CheckUnwatchedImport with an import that stays watched did not read its range before the import. The test makes one page PROT_NONE with a plain mprotect. The write watch isn't told, so its logical protection for that page is still read-write. On Apple, describePages (Execution/src/GuestMemory.cpp in ci/macos-series-2, both mach_vm_region loops) then sets readable = (logical & PROT_READ) != 0. That replaces the kernel's answer instead of narrowing it, so CheckRange sees the page as readable and doesn't throw. The read that follows faults, and the watch's handler (Watch::record) restores the logical read-write protection, so the import runs anyway. A probe gives threw=0 ran=1, logical protection 3 for the page.

    Arming only removes PROT_WRITE, so the kernel's read bit stays correct. Taking readable = kernel readable && (logical & PROT_READ) and writable = readable && (logical & PROT_WRITE) keeps the watch's write bit without losing a real PROT_NONE. #7201 already does this (region.readable && (watched < 0 || watched & PROT_READ)). #8658 has the same override as the series (GuestMemory.cpp around line 686 at its head). @mugurc @iamthehimansh, that's the change for your branches. Making the test's ProtectPage call GuestWriteWatchProtectionChanged also gets past the check, but it only hides the runtime behaviour.

    With #7201's line-1151 change plus the ProtectPage notification, the whole of agc_write_tracking passes here (I haven't run the describePages change itself), so nothing else in the test fails on macOS. So the only main piece is the line-1151 test change. It only matters once a macOS port with a correct describePages lands, so I'll leave it in #7201 unless someone wants it split out.

    AI-assisted: yes

  30. tkcm01 commented on Oct 23, 2026

    @tkcm01
    Contributor

    @rooklz guest_math_exact on a real Intel Mac: it fails there too, at the same check as on the M4, so it isn't Rosetta's x87 emulation.

    Intel: GitHub macos-15-intel runner, Intel Core i7-8700B, macOS 15.7.9, Xcode 26.3 (Apple clang 17.0.0, clang-1700.6.4.2), native x86-64. Same tree as our runs: ci/macos-series-2 at 53db0ba with #7524, the code of #7937 and the libSceSsl change of #7332 (tree d071648659). Release build, so -O3 (plus -g for line info). run

    M4: Apple clang 21, Rosetta 2, the same build as my earlier notes.

    The test's Require only calls abort(), so on both machines I temporarily made it print its line. Both fail first at CheckClassification's first check (line 101):

    const long double pseudoDenormal = Extended(0x0000, 0x8000000000000000ull);
    Require(_LDtest_nid_postfix(&pseudoDenormal) == -1 && __fpclassifyl_nid_postfix(pseudoDenormal) == -2);

    Together with your result that it passes at -O0, my guess (not checked in the disassembly) is that the optimised build moves the pseudo-denormal through x87 registers. An x87 load and store doesn't keep that encoding, so the library sees a different value from the one the test built. Real Intel hardware would do the same, which fits both machines failing. If so, it's the test (or how the argument reaches libc), not libc.

    guest_math also aborts on the Intel runner, with no output. I haven't looked into that one.

    AI-assisted: yes

  31. rooklz commented on Oct 23, 2026

    @rooklz
    Contributor

    @tkcm01 thanks, the Intel run settles it, and your guess checks out on the M5. A standalone probe that builds the same pseudo-denormal (se = 0x0000, mantissa 0x8000000000000000) with memcpy and reads its exponent field back through a const long double*:

    clang -O0: pointer 0000 by-value 0000
    clang -O3: pointer 0001 by-value 0001
    

    At -O3 Apple clang materialises the constant through fld/fstp (8 of them in the assembly of a 10-line file), and the x87 load of a pseudo-denormal normalises it: it comes back as 0x0001 with the same mantissa, a plain denormal-range normal, which is what _LDtest and __fpclassifyl then see. So the library gets a different operand from the one the test built, on Rosetta and on real Intel alike, and the test passes at -O0 only because nothing moves the value until the call. It is the test's construction, not libc: the operand has to reach the call through memory the compiler won't route through the FPU (build it in the callee from bytes, or keep it in a byte array and memcpy into the argument at the call). GCC on Linux keeps the bytes as they are, which is why main's CI never saw it.

    AI-assisted: yes

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions