WIP: fix(macos): start renderer threads with a 16 MiB stack #651
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!651
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/thread-stack"
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: fix · Risk: low · Tested on: macOS 26.7.1, Apple M4 Max (MoltenVK 1.4.2), arm64 and x86_64 under Rosetta · Needs: Windows, Linux (build and start)
Why
On a Mac the game crashed during its first start's shader preparation: start #6 of the macOS port died with SIGILL (exit
132) at about 93 s, 253 programs in. A thread the renderer starts on macOS gets a 512 KiB stack, where the same thread gets
8 MiB on Linux (RLIMIT_STACK) and on Windows (
longshot.exe's/stack). On macOS, MoltenVK converts each shader to Metal'slanguage with SPIRV-Cross on the thread that creates the pipeline, and SPIRV-Cross recurses once per block along a program's
control flow (
CFG::walk_from, 80 bytes a level in MoltenVK 1.4.2): one of the game's programs ran a preparation worker out ofstack. This PR starts every renderer thread on macOS with 16 MiB. It is part of the macOS port. See #420.
How to reproduce
Seen on: macOS 26.7.1, Apple M4 Max, MoltenVK 1.4.2 (main does not run on macOS yet: seen on the macOS port's integration
build f32adfa2, which carries the open macOS PRs)
Settings and switches: defaults, a first start (
RX_VK_LOAD=start), the program cache carried from the start beforeSteps: start the game and let the shader preparation run.
Expected: the preparation goes on to the precompile and the title.
Actual: the process ends with SIGILL inside the preparation (run #6,
reports/2026-11-14-mac-startup-05.md; the crashreport's stack is SPIRV-Cross's
CFG::walk_fromon aprepare_runworker; analysis inreports/2026-11-14-thread-stack.md).What changed
A new one-header helper,
thread_stack::thread, starts a thread with 16 MiB of stack on macOS and isstd::threaditselfeverywhere else; every thread the renderer starts goes through it. Linux and Windows compile exactly the
std::threadcodethey compiled before.
gpu/vk/thread_stack.h(new): on__APPLE__astd::thread-like class started bypthread_createwithpthread_attr_setstacksize(kBytes),kBytes= 16 MiB. It keepsstd::thread's contract (decay-copied function andarguments, an exception out of the thread ends the process,
joinable/join/detach/native_handle, moves,std::system_erroron failure,std::terminateon destroying a joinable one). Elsewhereusing thread = std::thread;.gpu/vk: 58 lines in 15 files (vk_draw.cpp18,vk_replay.cpp10,vk_texture.cpp5,vk_main.cpp4,vk_progcache.cpp4,vk_memory.cpp3,vk_menu.cpp3,vk_jobs.cpp2,vk_recorder.h2,warm_bounded.h2, and one each invk_bug_report.cpp,vk_framegen.cpp,vk_fsr4.cpp,vk_hangdump.cpp,vk_programs.cpp): the type name at the creation line, itsstd::vector<…>andfor (… & t : threads)lines, and one include.The inventory of the 36 kinds of thread and which reach the driver's compiler is in
reports/2026-11-14-thread-stack.md.mkwvk_init(gpu/vk/vk_main.cpp) logs once per start on macOS:vk: the renderer's threads start with 16 MiB of stack (thread_stack.h; a macOS thread's own is 512 KiB).tests/test_thread_stack.py+tests/thread_stack_test.cpp(new): a source check that no file undergpu/vkstartsa thread another way (
std::thread,std::jthread,std::async,pthread_create,CreateThread,_beginthread), and afixture that runs a 2 MiB-deep recursion on each kind of thread (detached, joined pool, pool left running, named, moved
arguments, member) through the real header; a
-DCONTROLbuild starts the same kinds asstd::thread, each of which mustoverflow on macOS (caught on an alternate signal stack, so no crash report). Seven source checks that matched the old
std::thread(spellings now matchthread_stack::thread((test_framelog_sink, test_gpl_library_cache,test_pipeline_share, test_png_publication, test_predicted_check, test_startup_progress,
test_texture_staging_publication), and eight checks that compile cut-out renderer code see the real
thread_stack.h(test_capture_copy_helpers, test_gpu_hang_watch, test_jobs_failure, test_jobs_wait_runs_queued, test_pipeline_cache_save,
test_prepare_stall, test_present_source_crop, test_recorder_sync). None was weakened.
Measurements
Verdict: fixes the crash: with this change the next starts (#7 cold and #8 warm on arm64, #9 cold on x86_64 under
Rosetta) logged the 16 MiB line and ended cleanly with no crash report, and #8 went from the 255 programs it inherited to
1,011, well past the program at 253 where #6 died. Not a performance change: nothing timed.
Baseline: main does not run on macOS yet, so both columns are the macOS port's integration builds, arm64, MoltenVK 1.4.2.
Before: integration f32adfa2 (run #6), without this change. After: integration a7711f3c (runs #7 and #8), with these three
commits. One run each.
Hardware: macOS 26.7.1, Apple M4 Max (40-core GPU), MoltenVK 1.4.2, 16-core CPU, 64 GB
Scene: the first start's shader preparation, windowless, defaults, 180 s each
Method: the runner's receipt (exit code), the crash-report folder before and after, and the game log's
vk:linesHow to reproduce: a first start on a macOS build without this change, then with it
CFG::walk_from)Run reports:
reports/2026-11-14-mac-startup-05.md(#5, #6),-07.md(#7, #8),-09.md.Flags and settings
None.
Testing
Tested: on macOS 26.7.1, Apple M4 Max, at this head (main
6c9edffeplus this PR):tests/test_thread_stack.py --arch arm64 --arch x86_64: pass. Each kind of thread ran 2 MiB deep on both arches throughthe helper, and each
std::threadcontrol overflowed (SIGBUS, caught: exit 3). On main6c9edffe(--source, an archive ofits
gpu/vk): fails on both arches, 58 thread starts without the helper and nothread_stack.h. Logs:reports/PubWave-files/PubPrep/p31-head-thread_stack.*,p31-base-thread_stack.*.tools/ls-suite.py: 271 pass, 17 fail.14 of the 17 fail on main
6c9edffeon macOS too (reports/Baseline-files/main-6c9edffe/), among themtest_pipeline_play_cache and test_pipeline_cache_save (
posix_fallocate, fixed by the macOS PR #476); the other 3(test_precompile_compute_guard, test_shader_module_guard: "Windows structured exceptions required";
test_present_aspect_vulkan: no Vulkan library to link on this Mac) are skipped on macOS in a full run and only fail here
because they were named. Every updated check and the new one pass.
reports/PubWave-files/PubPrep/p31-related-01/,p31-related-01-compare.txt..cppfiles compiled whole for arm64 and x86_64 with the renderer's flags (vk_menu.cppwith build.py'simgui flags,
vk_fsr4.cppwithLONGSHOT_FSR): 9 files build on both arches at this head; the other 4 (vk_draw.cpp,vk_menu.cpp,vk_progcache.cpp,vk_programs.cpp) stop on Linux-only calls with the same errors on main6c9edffe, andbuild on both arches with the macOS compile pieces of #476, #490 and #492 applied.
reports/PubWave-files/PubPrep/p31-tu-head/,p31-tu-base/(main),p31-tu-stack/,p31-tu-stack-menu-02/.git merge-treeof this head with each open macOS PR's head (#431, #437, #438, #479, #480, #490, #491, #492): no conflict,and none of them adds a thread start under
gpu/vk.Not tested: the game at this exact head (main does not run on macOS without the other macOS PRs; the runs above were on
integration builds), and Windows and Linux (no change in behaviour there:
thread_stack::threadisstd::thread; not builtor run on them: its test request covers the build and a start).
Must be tested:
7707c634) and main6c9edffethe same way, start each to the title and playa minute, expecting the build to succeed (off Apple every renderer thread is still a plain
std::thread) and the log tomatch main's with no new
vk:line (by hand: test request T-XXXX)Risks
What could break: on macOS each renderer thread reserves 16 MiB of address space instead of 512 KiB: about 1-1.5 GiB in
all at the peak (16 preparation workers, the job workers, the replay and presenter threads; up to 64 precompile workers on a
64-CPU Mac). [INFERENCE] Only touched stack pages are committed, so the memory footprint does not grow by it; not measured.
A thread start that fails now throws
std::system_errorasstd::threaddoes. Windows and Linux: nothing (the same type).Turning it off: revert the PR.
Compatibility: Nothing changes for players: no shader stamp, cache format or
MKWVK_API_VERSIONchange. On macOS one newlog line.
Reviewer notes
f0a62f7b..7707c634(3 commits): the helper and the 58 converted lines, one overflow message in the fixture, andthe eight checks that compile renderer code given the header.
6c9edffe; no dependency. The three commits were cherry-picked with-xfrommacos/thread-stack, which wasmade on the integration branch. Conflicts in the pick, all context only, resolved by keeping main's lines and converting the
thread starts in them:
gpu/vk/vk_draw.cpp(4 blocks): the integration branch's context carried the per-worker pipeline caches (#650); mainhas none of it, so only the include and the
thread_stack::threadlines were kept.gpu/vk/vk_main.cpp: the integration branch's#if defined(__APPLE__)block also held the Metal cache check (#653);only the stack line is in this PR's block.
gpu/vk/vk_memory.cpp: include lines beside #250'ssparse_mirror.h(cf605c85);vk_recorder.h: beside #162'slongshot_env.hinclude (98e2d95f);vk_replay.cpp: beside #238'sreadback_late.h(ba700f58), and #588(
82886e89) renamed the replay thread "the render thread" in its log line: main's text withthread_stack::thread.tests/test_recorder_sync.py: #60 (41421d48) copiesgpu_wait_stats.hbesidevk_recorder.hwhere this PR copiesthread_stack.h: both kept.finds no thread start without the helper under
gpu/vk(there were 58 on main6c9edffe).vk_draw.cppfunctions; #652's branch merges this one. #653 (Metal's cache folder) adds its check to the same#if defined(__APPLE__)block inmkwvk_init: keep both lines.git merge-tree).Issues
See #420
Checklist
Everyone
type(scope): description) of the status line's type.main(maine090cc48).python -I -B tests/run_all.pypasses (any failure that also fails onmainis named). (On the wave's merge of every macOS PR built with this one,453eb678: 553 passed, 32 failed, 18 skipped against main 6c9edffe's 531/32/17 on this Mac; the failures are main's own excepttest_split_ub_flush, which #658's tree at the time broke and its head fixes;test_collect_widenow passes; the new tests pass,test_thread_stackamong them, andtest_darwin_audio_service, Darwin-only, is skipped by the runner;reports/PubWave-files/waveA/.)third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.mainand passing here.tests/that reads changed source updated in this PR, not weakened.7707c6343301ffd9a93801ffd9a938a7c1f493deFolded into #849 (fix(macos): MoltenVK's precompile and pipelines on Metal)
Pull request closed