WIP: fix(macos): start renderer threads with a 16 MiB stack #651

Closed
mooki wants to merge 3 commits from mooki/Longshot:mac/thread-stack into main
Member

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's
language 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 of
stack. 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 before
Steps: 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 crash
report's stack is SPIRV-Cross's CFG::walk_from on a prepare_run worker; analysis in reports/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 is std::thread itself
everywhere else; every thread the renderer starts goes through it. Linux and Windows compile exactly the std::thread code
they compiled before.

  • gpu/vk/thread_stack.h (new): on __APPLE__ a std::thread-like class started by pthread_create with
    pthread_attr_setstacksize(kBytes), kBytes = 16 MiB. It keeps std::thread's contract (decay-copied function and
    arguments, an exception out of the thread ends the process, joinable/join/detach/native_handle, moves,
    std::system_error on failure, std::terminate on destroying a joinable one). Elsewhere using thread = std::thread;.
  • Every thread start under gpu/vk: 58 lines in 15 files (vk_draw.cpp 18, vk_replay.cpp 10, vk_texture.cpp 5,
    vk_main.cpp 4, vk_progcache.cpp 4, vk_memory.cpp 3, vk_menu.cpp 3, vk_jobs.cpp 2, vk_recorder.h 2,
    warm_bounded.h 2, and one each in vk_bug_report.cpp, vk_framegen.cpp, vk_fsr4.cpp, vk_hangdump.cpp,
    vk_programs.cpp): the type name at the creation line, its std::vector<…> and for (… & 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: tests/test_thread_stack.py + tests/thread_stack_test.cpp (new): a source check that no file under gpu/vk starts
    a thread another way (std::thread, std::jthread, std::async, pthread_create, CreateThread, _beginthread), and a
    fixture 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 -DCONTROL build starts the same kinds as std::thread, each of which must
    overflow on macOS (caught on an alternate signal stack, so no crash report). Seven source checks that matched the old
    std::thread( spellings now match thread_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: lines
How to reproduce: a first start on a macOS build without this change, then with it

Before: integration @ f32adfa2 (run #6) After: integration @ a7711f3c (run #7, cold) After: integration @ a7711f3c (run #8, warm)
end SIGILL (exit 132) at about 93 s clean exit (0) at 180 s clean exit (0) at 180 s
programs reached 253 (died there) 243 1,011 (from 255 carried)
crash reports 1 (CFG::walk_from) 0 0
#7, #8: vk: the renderer's threads start with 16 MiB of stack (thread_stack.h; a macOS thread's own is 512 KiB)

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 6c9edffe plus this PR):

  • tests/test_thread_stack.py --arch arm64 --arch x86_64: pass. Each kind of thread ran 2 MiB deep on both arches through
    the helper, and each std::thread control overflowed (SIGBUS, caught: exit 3). On main 6c9edffe (--source, an archive of
    its gpu/vk): fails on both arches, 58 thread starts without the helper and no thread_stack.h. Logs:
    reports/PubWave-files/PubPrep/p31-head-thread_stack.*, p31-base-thread_stack.*.
  • Every check that reads one of the 15 changed renderer files (288 checks) through tools/ls-suite.py: 271 pass, 17 fail.
    14 of the 17 fail on main 6c9edffe on macOS too (reports/Baseline-files/main-6c9edffe/), among them
    test_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.
  • The 13 changed .cpp files compiled whole for arm64 and x86_64 with the renderer's flags (vk_menu.cpp with build.py's
    imgui flags, vk_fsr4.cpp with LONGSHOT_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 main 6c9edffe, and
    build 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-tree of 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.
  • In the game (integration builds carrying these commits): runs #7 and #8 on arm64 and #9 on x86_64 under Rosetta, above.

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::thread is std::thread; not built
or run on them: its test request covers the build and a start).

Must be tested:

  • Windows and Linux, any GPU: build this PR (head 7707c634) and main 6c9edffe the same way, start each to the title and play
    a minute, expecting the build to succeed (off Apple every renderer thread is still a plain std::thread) and the log to
    match 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_error as std::thread does. 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_VERSION change. On macOS one new
log line.

Reviewer notes

  • Review f0a62f7b..7707c634 (3 commits): the helper and the 58 converted lines, one overflow message in the fixture, and
    the eight checks that compile renderer code given the header.
  • Based on main 6c9edffe; no dependency. The three commits were cherry-picked with -x from macos/thread-stack, which was
    made 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); main
      has none of it, so only the include and the thread_stack::thread lines 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's sparse_mirror.h (cf605c85); vk_recorder.h: beside #162's
      longshot_env.h include (98e2d95f); vk_replay.cpp: beside #238's readback_late.h (ba700f58), and #588
      (82886e89) renamed the replay thread "the render thread" in its log line: main's text with thread_stack::thread.
    • tests/test_recorder_sync.py: #60 (41421d48) copies gpu_wait_stats.h beside vk_recorder.h where this PR copies
      thread_stack.h: both kept.
  • Main's own thread starts since the topic was written (#588's render thread among them) are all converted: the source check
    finds no thread start without the helper under gpu/vk (there were 58 on main 6c9edffe).
  • Sibling macOS PRs: #650 (per-worker pipeline caches) and #652 (the precompile behind the title) start threads in the same
    vk_draw.cpp functions; #652's branch merges this one. #653 (Metal's cache folder) adds its check to the same
    #if defined(__APPLE__) block in mkwvk_init: keep both lines.
  • Conflicts with main: none. With the open macOS PRs (#431, #437, #438, #479, #480, #490, #491, #492): none (git merge-tree).

Issues

See #420

Checklist

Everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description) of the status line's type.
  • Why gives the reason plainly and links the issues that fit (the tracker searched).
  • Measurements has a verdict, or one line saying why there is nothing to measure.
  • Testing names what was tested, what wasn't, and what must still be tested, where and how. (Test requests not open yet.)
  • Rebased on current main (main e090cc48).
  • python -I -B tests/run_all.py passes (any failure that also fails on main is 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 except test_split_ub_flush, which #658's tree at the time broke and its head fixes; test_collect_wide now passes; the new tests pass, test_thread_stack among them, and test_darwin_audio_service, Darwin-only, is skipped by the runner; reports/PubWave-files/waveA/.)
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

When it applies

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • A game-free test added for the fix, failing on main and passing here.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
**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's language 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 of stack. 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 before **Steps:** 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 crash report's stack is SPIRV-Cross's `CFG::walk_from` on a `prepare_run` worker; analysis in `reports/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 is `std::thread` itself everywhere else; every thread the renderer starts goes through it. Linux and Windows compile exactly the `std::thread` code they compiled before. - `gpu/vk/thread_stack.h` (new): on `__APPLE__` a `std::thread`-like class started by `pthread_create` with `pthread_attr_setstacksize(kBytes)`, `kBytes` = 16 MiB. It keeps `std::thread`'s contract (decay-copied function and arguments, an exception out of the thread ends the process, `joinable`/`join`/`detach`/`native_handle`, moves, `std::system_error` on failure, `std::terminate` on destroying a joinable one). Elsewhere `using thread = std::thread;`. - Every thread start under `gpu/vk`: 58 lines in 15 files (`vk_draw.cpp` 18, `vk_replay.cpp` 10, `vk_texture.cpp` 5, `vk_main.cpp` 4, `vk_progcache.cpp` 4, `vk_memory.cpp` 3, `vk_menu.cpp` 3, `vk_jobs.cpp` 2, `vk_recorder.h` 2, `warm_bounded.h` 2, and one each in `vk_bug_report.cpp`, `vk_framegen.cpp`, `vk_fsr4.cpp`, `vk_hangdump.cpp`, `vk_programs.cpp`): the type name at the creation line, its `std::vector<…>` and `for (… & 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: `tests/test_thread_stack.py` + `tests/thread_stack_test.cpp` (new): a source check that no file under `gpu/vk` starts a thread another way (`std::thread`, `std::jthread`, `std::async`, `pthread_create`, `CreateThread`, `_beginthread`), and a fixture 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 `-DCONTROL` build starts the same kinds as `std::thread`, each of which must overflow on macOS (caught on an alternate signal stack, so no crash report). Seven source checks that matched the old `std::thread(` spellings now match `thread_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:` lines How to reproduce: a first start on a macOS build without this change, then with it | | Before: integration @ f32adfa2 (run #6) | After: integration @ a7711f3c (run #7, cold) | After: integration @ a7711f3c (run #8, warm) | |---|---|---|---| | end | SIGILL (exit 132) at about 93 s | clean exit (0) at 180 s | clean exit (0) at 180 s | | programs reached | 253 (died there) | 243 | 1,011 (from 255 carried) | | crash reports | 1 (`CFG::walk_from`) | 0 | 0 | ``` #7, #8: vk: the renderer's threads start with 16 MiB of stack (thread_stack.h; a macOS thread's own is 512 KiB) ``` 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 6c9edffe plus this PR): - `tests/test_thread_stack.py --arch arm64 --arch x86_64`: pass. Each kind of thread ran 2 MiB deep on both arches through the helper, and each `std::thread` control overflowed (SIGBUS, caught: exit 3). On main 6c9edffe (`--source`, an archive of its `gpu/vk`): fails on both arches, 58 thread starts without the helper and no `thread_stack.h`. Logs: `reports/PubWave-files/PubPrep/p31-head-thread_stack.*`, `p31-base-thread_stack.*`. - Every check that reads one of the 15 changed renderer files (288 checks) through `tools/ls-suite.py`: 271 pass, 17 fail. 14 of the 17 fail on main 6c9edffe on macOS too (`reports/Baseline-files/main-6c9edffe/`), among them test_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`. - The 13 changed `.cpp` files compiled whole for arm64 and x86_64 with the renderer's flags (`vk_menu.cpp` with build.py's imgui flags, `vk_fsr4.cpp` with `LONGSHOT_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 main 6c9edffe, and build 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-tree` of 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`. - In the game (integration builds carrying these commits): runs #7 and #8 on arm64 and #9 on x86_64 under Rosetta, above. **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::thread` is `std::thread`; not built or run on them: its test request covers the build and a start). **Must be tested:** - Windows and Linux, any GPU: build this PR (head 7707c634) and main 6c9edffe the same way, start each to the title and play a minute, expecting the build to succeed (off Apple every renderer thread is still a plain `std::thread`) and the log to match 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_error` as `std::thread` does. 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_VERSION` change. On macOS one new log line. ## Reviewer notes - Review `f0a62f7b..7707c634` (3 commits): the helper and the 58 converted lines, one overflow message in the fixture, and the eight checks that compile renderer code given the header. - Based on main 6c9edffe; no dependency. The three commits were cherry-picked with `-x` from `macos/thread-stack`, which was made 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); main has none of it, so only the include and the `thread_stack::thread` lines 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's `sparse_mirror.h` (`cf605c85`); `vk_recorder.h`: beside #162's `longshot_env.h` include (`98e2d95f`); `vk_replay.cpp`: beside #238's `readback_late.h` (`ba700f58`), and #588 (`82886e89`) renamed the replay thread "the render thread" in its log line: main's text with `thread_stack::thread`. - `tests/test_recorder_sync.py`: #60 (`41421d48`) copies `gpu_wait_stats.h` beside `vk_recorder.h` where this PR copies `thread_stack.h`: both kept. - Main's own thread starts since the topic was written (#588's render thread among them) are all converted: the source check finds no thread start without the helper under `gpu/vk` (there were 58 on main 6c9edffe). - Sibling macOS PRs: #650 (per-worker pipeline caches) and #652 (the precompile behind the title) start threads in the same `vk_draw.cpp` functions; #652's branch merges this one. #653 (Metal's cache folder) adds its check to the same `#if defined(__APPLE__)` block in `mkwvk_init`: keep both lines. - Conflicts with main: none. With the open macOS PRs (#431, #437, #438, #479, #480, #490, #491, #492): none (`git merge-tree`). ## Issues See #420 ## Checklist ### Everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`) of the status line's type. - [x] Why gives the reason plainly and links the issues that fit (the tracker searched). - [x] Measurements has a verdict, or one line saying why there is nothing to measure. - [ ] Testing names what was tested, what wasn't, and what must still be tested, where and how. (Test requests not open yet.) - [x] Rebased on current `main` (main e090cc48). - [x] `python -I -B tests/run_all.py` passes (any failure that also fails on `main` is 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 except `test_split_ub_flush`, which #658's tree at the time broke and its head fixes; `test_collect_wide` now passes; the new tests pass, `test_thread_stack` among them, and `test_darwin_audio_service`, Darwin-only, is skipped by the runner; `reports/PubWave-files/waveA/`.) - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### When it applies - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] A game-free test added for the fix, failing on `main` and passing here. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened.
macOS starts a std::thread with 512 KiB of stack, where Linux starts one
with 8 MiB and Windows with longshot.exe's 8 MiB. On macOS MoltenVK
converts each shader with SPIRV-Cross on the thread that creates the
pipeline, recursing once per block of a program's control flow, and a
prepare_run worker overran its 512 KiB there (CFG::walk_from under
vkCreateComputePipelines from seed_compute, start #6, 2026-11-14).

gpu/vk/thread_stack.h: thread_stack::thread is std::thread itself off
macOS; on macOS a std::thread-like thread (construction with decayed
arguments, joinable, join, detach, native_handle, moves) started by
pthread_create with thread_stack::kBytes (16 MiB) of stack. Every
thread the renderer starts goes through it: the preparation's pools
(prepare_run, state_list_prepare, warm_libraries, precompile_at_create,
precompile_run, predict_run, warm_bounded::run), the job workers and
the serial builder, the replay, parallel-replay, presenter and
frame-generation threads, the program cache's loaders and the rest.
mkwvk_init logs the size once per start on macOS.

tests/test_thread_stack.py: no file under gpu/vk starts a thread any
other way; each kind of thread runs a recursion 2 MiB deep through the
helper, and on macOS each overflows as a std::thread (caught, no crash
report). Seven source checks follow the new spelling.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit 48b49054b1c64851b3e8c974e64dc0b757b2ced3)
The first thread whose stack overflows says so and ends the process;
the others wait in their handlers, so the message is not interleaved.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit 62d0991e0cfb5402202f8f14e9eb799d5c20d262)
test: the checks that compile renderer code with its threads see thread_stack.h
All checks were successful
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 3s
CI / Workflow lint (actionlint) (pull_request) Successful in 6s
Third-party parts / As their notes say (pull_request) Successful in 9s
PR description / Description follows the template (pull_request_target) Successful in 4s
7707c63433
Eight checks cut renderer functions that start threads (or copy
vk_jobs.cpp and vk_recorder.h) into fixtures of their own; they now
build with gpu/vk/thread_stack.h beside them: 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
and test_recorder_sync.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit fd46ff431f9f62b1125582a4a3434a8cc92c551e)
mooki force-pushed mac/thread-stack from 7707c63433
All checks were successful
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 3s
CI / Workflow lint (actionlint) (pull_request) Successful in 6s
Third-party parts / As their notes say (pull_request) Successful in 9s
PR description / Description follows the template (pull_request_target) Successful in 4s
to 01ffd9a938
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 16s
CI / Conventional Commit title (pull_request) Successful in 14s
CI / Workflow lint (actionlint) (pull_request) Successful in 19s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 18s
Third-party parts / As their notes say (pull_request) Successful in 15s
2026-11-15 07:25:29 +00:00
Compare
mooki force-pushed mac/thread-stack from 01ffd9a938
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 16s
CI / Conventional Commit title (pull_request) Successful in 14s
CI / Workflow lint (actionlint) (pull_request) Successful in 19s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 18s
Third-party parts / As their notes say (pull_request) Successful in 15s
to a7c1f493de
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 5s
CI / Conventional Commit title (pull_request) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 7s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 6s
Third-party parts / As their notes say (pull_request) Successful in 11s
Website / Website on Cloudflare (pull_request_target) Has been skipped
2026-11-15 13:10:09 +00:00
Compare
Author
Member

Folded into #849 (fix(macos): MoltenVK's precompile and pipelines on Metal)

Folded into #849 (fix(macos): MoltenVK's precompile and pipelines on Metal)
mooki closed this pull request 2026-11-15 18:08:25 +00:00
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 5s
CI / Conventional Commit title (pull_request) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 7s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 6s
Third-party parts / As their notes say (pull_request) Successful in 11s
Website / Website on Cloudflare (pull_request_target) Has been skipped

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!651
No description provided.