WIP: feat(macos): run the host on macOS: build, runtime, control, crash records and the Vulkan loader #845
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
beanstalk: approved @ 8d361dc
beanstalk: approved @ 9d8356e
beanstalk: approved @ abf2227
beanstalk: approved @ d82f202
beanstalk: approved @ e5f1e40
beanstalk: approved @ ee8ab5f
ci: approved
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
regression
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!845
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/topic-macos-host"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Type: feat · Risk: medium · Tested on: macOS 26.7.1, Apple M4 Max (arm64; x86_64 under Rosetta) · Needs: Windows and Linux, any GPU
Why
Nobody can build or run LS on a Mac today. The builder takes the Linux route there and hands Apple's tools ELF options and x86
CPU flags; the host's POSIX layer is written for Linux (futex waits,
ucontextfibers,/proc, Linux's signal context,MAP_FIXED_NOREPLACEandmemfd_createfor the guest window,getrandom,process_vm_readv), its jumps and pause hints arex86 only, and the renderer looks for
libvulkan.so.1with X11 or Wayland surfaces and never enables the portabilityextensions MoltenVK requires. The game-free runner thinks a Mac is Linux, and the control bridge, crash records,
longshot_ctland the builder's memory gate have no macOS route. This PR is the macOS host: it builds on a Mac, natively onApple silicon and as x86_64 under Rosetta, packages a relocatable game folder for macOS 14.4 and later, and runs the host with
its control, crash records and the Vulkan loader. It is the first part of the macOS port. See #420.
What changed
The build scripts get a Mach-O command path (arm64 or x86_64, macOS 14.4) and a packager; the host gets native Darwin
memory, waits, faults, threads, fibers, processes, profiler, control sockets and crash records, each in Apple-only branches or
new
host/posix/darwin_*.cfiles; the renderer loads a Vulkan loader or MoltenVK on macOS; the game-free runner, the builder'smemory gate and
longshot_ctlknow macOS. Windows and Linux keep their code, but for a few stated every-platform changes(Testing, off Apple). The seventeen original pull requests, in commit order:
e81d4af4; Was: #454):tests/run_all.pyreportsmacos, reads amacosskip reason and keeps macOS timing apart; 18 checks a Mac has no subjectfor are skipped there; three fixtures get Apple-only repairs.
441182e2; Was: #477):host/posix/darwin_cpu.c(new) has the arm64 jump in assembly and ayieldpause; the x86 bodies stay for every othertarget. Test:
tests/test_arm64_host.py(new).10e52402; Was: #478):host/posix/darwin_mem.c(new) reserves the guest window with Mach, zeroes freed bytes exactly and keeps 4 KiB guest rules on16 KiB hosts;
hos_mem.c,hos_svc.c,hos_main.candwin32_posix.ccall it under__APPLE__. Test:tests/test_darwin_guest_memory.py(new).9784b1b5; Was: #436):host/posix/darwin_fault.c(new) reads and writes Darwin's signal context on both CPU routes forwin32_posix.c's faultbridge. Test:
tests/test_darwin_fault_context.py(new).a492f0ac; Was: #437):host/posix/darwin_wait.canddarwin_wait_state.c(new): waits on awake uptime, address waits over one sequence word keptin the executable (so the renderer's copy and the executable's wake each other), SRW locks. Test:
tests/test_darwin_waits.py(new).58bdbdc0; Was:#438):
gpu/vk/vk_loader.h,vk_main.cpp,host/posix/darwin_vulkan.c(new) find a loader or MoltenVK (RX_VK_LIBRARY, then beside the game), keep SDL on thatlibrary, take SDL's instance extensions, enable the portability extensions, and log what refuses an instance or device;
MkwVkHostgains two Apple callbacks (AppleMKWVK_API_VERSION24). Tests:tests/test_vk_darwin_admission.py,tests/test_vk_darwin_sdl.py(new).6e361411; Was: #490): Mach threadidentities and times, guarded native fibers (
darwin_fiber.c), dyld paths, same-process restart andposix_spawnchildren(
darwin_process.c), memory counters and the low-memory notice, a Mach sampling profiler (darwin_prof.c), the menu'sshare commands (
vk_menu.cpp); on Linux it also hardens Discord IPC (host/discord_rpc.c) and the thread error paths(
win32_posix.c). Test:tests/test_darwin_host.py(new).f68637f8; Was: #453):nx2/darwin_toolchain.pyandnx2/darwin_build.py(new) form the macOS commands (RX_ARCH); Windows' and Linux' arrays moveinto four functions unchanged, element for element, pinned by
tests/build_flags_main.json. Test:tests/test_macos_build_flags.py(new).9a1094da; Was:#479): each Darwin helper goes to its image; every library is
checked against the target (architecture, macOS 14.4, the closure, dyld's binding) and the game folder carries relocated,
signed copies (
nx2/darwin_package.py,nx2/darwin_archive.py, new). Test:tests/test_macos_package.py(new).25400283; Was: #455):free_mb()andtree_rss()callnx2/darwin_memory.py(new: VM counters, libproc). Test:tests/test_mem_gate_darwin.py.1b3354a0; Was: #456):host/posix/darwin_control_sockets.c(new), called from small Apple-only branches ofhost/control.c.68d940ca; Was: #457): it waitsfor the bridge to listen and reaps it when a check fails;
host/control.cgains a hook only the test build compiles.eb8d5573; Was: #476):fcntl(F_PREALLOCATE)ingpu/vk/pipeline_cache_file.handF_RDADVISEinvk_progcache.cppon macOS.570a5277; Was:#480): one owner in the executable (
darwin_guest_mem.c, new),import pins and native-page watches, frees under the allocator's lock, byte-atomic dirty marks.
98c070dc; Was:#492): an Apple branch in
vk_programs.cpp,vk_shader.cppdeclares
environ, and the debug tools' eight guards inhos_main.cname__APPLE__.e5a10343; Was: #511):getentropyinhos_report_run.c,mach_vm_read_overwriteand dyld's image list inhos_crash_record.c.longshot_ctlsessions (40c1c5f5; Was: #516):process_start()inscripts/longshot_ctl.pyreads the kernel start time and the boot session throughsysctl.docs/development.md(the macOS build, its inputs and switches),docs/control.md,docs/pipeline-cache-storage.md.Measurements
Verdict: works game-free on both Mac routes, and Windows' and Linux' builds keep main's commands: every member's test
fails on main (or cannot build there) and passes here, on arm64 and on x86_64 under Rosetta where it takes an architecture.
The macOS integration builds carrying these commits played on both routes (Testing). No timing: this is a port's host, with
nothing to speed up. The full suite at this head: 572 passed, 15 failed, 17 skipped against main's 543/33/17 on this Mac, with
no check failing that passes on main.
Baseline: the members' before values were measured before their rebase onto main
aa95d35f, on main6c9edffeor on mainwith the members a pull request depended on (as each one's description says); the after values are at this head.
Hardware: macOS 26.7.1, Apple M4 Max, 16-core CPU, 64 GB, Apple clang 21.0.0 (game-free: no GPU)
Scene: none (game-free fixtures; the game runs under Testing)
Method: each member's test before and after, two interleaved runs each (the members' runs); at this head each once per
architecture; the suite once through
tools/ls-suite.pyHow to reproduce: the commands under Testing, in this tree and in main's with the test files copied in
aa95d35f40c1c5f5macostest_arm64_host.pytest_darwin_guest_memory.pytest_darwin_fault_context.pytest_darwin_waits.pytest_darwin_host.py; the suitetest_macos_build_flags.pytest_macos_build_flags,test_macos_package,test_finish_outputNo module named 'darwin_package')build.py)test_control.pyquit slow20 of 20,quit fail20 of 20test_collect_widevk_programs.cpp,vk_shader.cppcompiles; debug-tools testtest_report_run,test_crash_recordtest_longshot_ctl.pyFlags and settings
New: one renderer switch read on macOS only (
RX_VK_LIBRARY), one build switch (RX_ARCH) and four macOS build inputs;existing switches that work, or say they cannot, on macOS for the first time. Nothing changes off macOS.
RX_VK_LIBRARY=<path>RX_ARCH=arm64/x86_64LONGSHOT_MOLTENVKlibMoltenVK.dylibLONGSHOT_SDL3libSDL3.0.dylibLONGSHOT_SDL2LONGSHOT_DARWIN_ARCHIVE_RECEIPT,LONGSHOT_DARWIN_ARCHIVE_RECEIPT_SHA256RX_MARCH,EMSDK,SDKROOT,LONGSHOT_FFMPEG,LONGSHOT_SDL2,RX_VK_ONLY(existing; changed on macOS)x86-64-v3on Apple silicon)x86-64-v2; SDL2 packaged and checked; a renderer-only macOS build skips the packagingLONGSHOT_NGX_SDK,LONGSHOT_FSR(existing; refused on macOS)HOS_PROFILE,HOS_THREAD_TIMES,HOS_SAMPLE,HOS_SAMPLE_ALL,HOS_SAMPLE_FILE,HOS_CHAIN,RX_MAPS(existing)HOS_WATCH,HOS_PIN_BY_CORE,HOS_PIN,HOS_RESERVE_CORE(existing)HOS_ZERO_ALL_PAGES,HOS_KEEP_FREED_PAGES,RX_NVN_WATCH_TEXELS_SRC,RX_NVN_WATCH_AFTER,RX_NVN_WATCH_ALL(existing)main's meaningTesting
Tested: on macOS 26.7.1, Apple M4 Max, at this head
40c1c5f5(a clean worktree):where the test takes an architecture (
--arch, or a fixture built with-arch x86_64): the runner's declaration andschedule tests,
test_arm64_host,test_darwin_guest_memory, the crash inventory,test_darwin_fault_context,test_darwin_waits(native and the two-image mode both arches),test_vk_darwin_admission,test_vk_darwin_sdl,test_darwin_host,test_display_startup_failure,test_low_memory_notice,test_prof_symbols,test_macos_build_flags,test_cpu_target,test_shader_stamp_cache,test_third_party_objects,test_work_lock,test_finish_output,test_macos_package,test_mem_gate_darwin, the memory gate's run_all checks,test_control, the cache checks throughrun_all (7 checks, both arches),
test_collect_wide,test_debug_tools_linux,test_crash_record,test_report_run,test_longshot_ctl: every run exit 0. (The first x86_64 run of the cache checks ran natively by a fault in the runner, aPATH=word left unexpanded; it was run again under Rosetta: 7 passed.)python -I -B tests/run_all.pythroughtools/ls-suite.pyat this head: 572 passed, 15 failed, 17 skipped, against mainaa95d35f's 543/33/17 on this Mac, compared by test name: no check that passes on main fails here. New and passing: the 11new tests. Fixed (FAIL on main, pass here): 17, the members' macOS repairs (
test_collect_wide,test_control,test_crash_record,test_longshot_ctl,test_low_memory_notice,test_prof_symbols,test_mem_gate,test_mem_gate_children,test_mirror_trim,test_oot_state_tap,test_report_runand six cache checks).test_appimage_package_retryis skipped on macOS by #454's declaration (main's runner took the Mac for Linux and ran it);test_dav1d_av1runs and passes (skipped on main: #454 links dav1d on macOS). The 15 failures fail on main too, on this Mac:test_readback_late,test_appimage_player_build,test_options_budget_once,test_vk_pgo,test_crash_dialog,test_walk_key,test_lift_cleanup_error,test_game_layout,test_options_path,test_make_game_cache,test_cpm_discovery,test_pipeline_tour,test_linux_preflight,test_env_on,test_split_giants.mkwvk::name onegpu/vkfile declares and another defines; each defining.cppcompiled with the tree's ownrenderer_flags(), arm64 and x86_64): 0 of 594 declared names lack a definition, 66 of66 objects compiled, 541 cross-file symbols per architecture all defined.
unifdef -U__APPLE__of every changed non-test C/C++ file, main against this head, compared as tokens): 22files are main's text; the 11 new
.cfiles hold only Apple blocks, and the 9 new headers off Apple are an include guard or#pragma once(darwin_mem.h's declarations are included only inside__APPLE__blocks). The six that differ are thestated every-platform parts:
host/discord_rpc.c(#490, Linux:SOCK_CLOEXEC, the exactsockaddr_unlength, a too-longpath skipped,
EINTRretried),host/posix/win32_posix.c(#490, Linux: a failed thread allocation returnsNULLwithERROR_NOT_ENOUGH_MEMORY, a failedpthread_createdestroys its mutex and condition variable,GetThreadIdof a bad handlesets
ERROR_INVALID_HANDLE, the record'sstruct HThreadtag),host/posix/windows.h(ERROR_INSUFFICIENT_BUFFER, acomment),
host/hos_thread.c(<emmintrin.h>on x86-64 only, true on every Windows and Linux target; the affinity call'sresult read into an unused variable),
host/hos_main.c(conditions that also name__APPLE__: the same off Apple) andhost/control.c(#457's hook, compiled only with the test build'sHOS_CONTROL_NO_GAME).E9a on I41
78ec1c47: a 1,197 s hold in play, 12 of 12 must-shows, 0 skipped draws, a clean end(
reports/2026-11-15-mac-perf-E9.md); x86_64 under Rosetta: run #34 on I383f872b0c: 16 m 52 s of play, every drawdrawn, 19 of 19 checks, a clean exit (
reports/2026-11-14-mac-startup-34-x86_64.md).Not tested: Windows and Linux: nothing was built or run there (the test request below): Discord presence with the new
socket code, the thread-creation error paths (they need an allocation or
pthread_createfailure),scripts/prof.pyon a realLinux capture, the debug tools and
test_copy_savethere. A complete macOS game build from this PR alone (the renderer layersare the next topics); a real restart, share or Discord client on macOS; macOS 14.4 itself (the deployment target; every run
was on 26.7.1); an Intel Mac.
Must be tested:
aa95d35fthe same way, runpython -I -B tests/run_all.pyon each andstart each to the title, expecting the new tests to pass or skip with their macOS reason, no other check to change status,
and the
vk:lines to match main's (the shader caches are prepared again once, as with anyvk_shader.cppedit) (TestFarm: @Beanstalk windows linux, build, run_all and start to the title)
Risks
What could break: on Linux, Discord presence (the new socket flags and length) and the thread-creation error paths; the
build scripts' Windows and Linux arrays moved into functions (pinned element for element against main's). On macOS
everything is new: the host, the build and the package have run game-free and in the integration builds' game runs only.
Turning it off: revert the PR (macOS code is behind
__APPLE__or in Darwin-only files; nothing on Windows or Linux isswitched).
Compatibility: every platform lifts the game again once after updating (
nx2/kit.pyand the newnx2/darwin_toolchain.pyare lift sources) and prepares its shaders again once (vk_shader.cppis in the translator'sstamp:
ebbf160ce9626041on main,6241b7736c8060e3here); the builder compiles the host's objects again once (hos.handhost/posix/*.hchange). The vk ABI:MKWVK_API_VERSIONis 24 on macOS only (two callbacks appended toMkwVkHost); Windows and Linux keep main's 23 with the samestructure. Saves and
longshot_options.iniare unchanged; on macOSlongshot_ctl's session files store the new processidentity.
Issues
See #420. The macOS topic PRs, merged in this order; "topic N" in each description means these: 1 #845 (macos-host), 2 #846 (vulkan-on-metal, on 1), 3 #847 (mvk-descriptor-limits), 4 #848 (shader-backend, on 2, 3 and #431), 5 #849 (precompile-metal, on 4), 6 #850 (draw-path, on 4), 7 #851 (draw-path-quads-mesh, on 6, #705 and #707), 8 #852 (mac-perf, on 7).
Checklist
Everyone
type(scope): description) of the status line's type.main: onaa95d35f.python -I -B tests/run_all.pypasses (any failure that also fails onmainis named). (macOS: 572/15/17, the 15 failures also fail on main, named under Testing; Windows and Linux: the test request.)third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.MKWVK_API_VERSIONbumped (gpu/vk/vk_api.h'sMkwVkHostorMkwVkRendererchanged).mainand passing here.tests/that reads changed source updated in this PR, not weakened.This conflicts with
mainnow:nx2/build.py,nx2/make_game.pyanddocs/development.mdchanged onmainwhen the WebGPU renderer was removed from the Windows build (#577:wgpu_native.dlland its build lines are gone). Could you rebase onto currentmain? The conflicts looked like lines the two sides both touched next to each other, but they repeat in many of this stack's commits, so I left it to you rather than guess.View command line instructions
Manual merge helper
Use this merge commit message when completing the merge manually.
Checkout
From your project repository, check out a new branch and test the changes.