WIP: fix(vk): heavy compiles run alone under Metal's compiler service; the first giant behind the game no longer waits for 16 GB #784
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!784
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/giant-compile-2"
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: medium · Tested on: macOS 26.7.1, Apple M4 Max (MoltenVK main 5189f8de with the argbuf-slots patch), game-free on arm64 and x86_64 under Rosetta; GPU probe GC-02 on arm64 (history) · Needs: macOS, Apple silicon (a start to play on the rebased stack); Windows and Linux, any GPU (build and start)
Why
On a Mac, Metal compiles every pipeline in its compiler service, in a few service processes the game shares. Four of the
game's mesh programs drawn through the compute fallback are giants: 347,000 and 464,000 SPIR-V words, with a 9 KB local
memory array reached by about 8,000 access chains. Each takes one to several GB of a service process. With two of the
464k ones in one process, or three giants, the process runs out of the address ranges it keeps every compile in
(
Failed to allocate segment from range group - out of space) and dies; Metal hands the same compiles to a new process,which dies the same way. Run #31 lost four service processes in a row and three giants were never made; run #35 lost the two
464k ones. Alone, a giant is made: 5c945415 was made in every run from #17 to #31, and GPU probe GC-02's twin of the 464k
giants was made alone, even after 48 other compiles. Separately, #669's memory gate let no giant start behind the game in run #32: it waited for 16 GB
available, which a loaded game never leaves. This PR compiles such heavy programs alone, nothing else of the precompile
beside them, and lets the first giant behind the game start unless memory is critical. It is part of the macOS port. See #420.
How to reproduce
Seen on: macOS 26.7.1, Apple M4 Max, MoltenVK main 5189f8de with the argbuf-slots patch; main does not start on macOS yet:
seen on the macOS port's integration builds b4732392 (run #31) and 3f872b0c (runs #32 and #35), which carry the open macOS PRs
Settings and switches: defaults (
RX_VK_COMPILER_SERVICEon, MoltenVK's), the field-dawn save, slot 00Steps: a start from the field-dawn save with START and A at the title, the precompile building the giants before and
behind the game; read the
vk: slow compile: …lines for 5096bcc7, 6dbfda5a, 55d1da3f and 5c945415, and the system log'sMTLCompilerServicelines.Expected: every giant pipeline made.
Actual: run #31: 5096bcc7, 6dbfda5a and 55d1da3f
not madeafter 460-463 s, four compiler service processes ended withthe range-group line; run #35: 55d1da3f and 5c945415
not madeafter 133 s; run #32: no giant started behind the game forthe whole run (
reports/2026-11-15-giant-compile-2.md§2.4, the run reports).What changed
Under
compiler_service(MoltenVK's driver; the precompile'sbehind_worker), a heavy compile runs alone: it starts onlyonce no other run of the precompile is in flight, and nothing else starts until it ends. Heavy means a module whose local
memory weight (its largest Private or Function array's access chains times its words) reaches 10,000,000, or 1,000,000 SPIR-V
words. Behind the game, with no giant in flight one starts unless the memory pressure is critical. Every other driver, and
the precompile's gate called as before, run the code they ran before.
gpu/vk/precompile_handoff.h:heavy_weight(RX_VK_HEAVY_WEIGHT),heavy_words(RX_VK_HEAVY_WORDS),heavy_compile;Giants::nextwithheavy_ofandalone: a heavy run claimed while others run waits in its own queue; while the hold,play's want or giant, or critical memory keep it back the other runs go on; once only the runs in flight do, nothing new is
taken until they drain. Every run counts in flight until
run_left. The 7-argumentnextis the gate as before.memory_lets_first_giant: the first giant behind the game starts unless the pressure is critical; the room(
RX_VK_GIANT_ROOM_MB) bounds the second and those after it.gpu/vk/private_memory.hlocal_weight;gpu/vk/vk_shader.hStage::local_weight;gpu/vk/vk_progcache.cppnote_local_weight, beside each of the three places a stage's SPIR-V is noted, for modules over 100,000 words, whateverRX_VK_LMEM_LOG, on macOS only (694edb10).gpu/vk/vk_draw.cppprecompile_run: each item's weight and whether it is heavy (its stages' largest weight);behind_workerasks
nextwith them and lets each run go; lines: the count at setup (vk: precompile: N heavy compiles, each run alone …),… deferred: a heavy compile runs alone …,… a heavy compile begins alone …, the done line's count; the memory lines saythe first-giant rule.
docs/development.md: theRX_VK_HEAVY_WEIGHT,RX_VK_HEAVY_WORDSrow; theRX_VK_GIANT_ROOM_MBrow says the first-giant rule(
27753a2e).tests/test_giant_alone.py+giant_alone_test.cpp(new: the measure on the logged modules, drain-then-alone step bystep, the waits for play's giant, play's want, the hold and critical memory with the other runs going on, 16 workers over 400
items with 6 heavy: every item once, 0 runs beside a heavy one; the old
next()unchanged; the source's wiring);giant_memory_test.cpp(the first-giant rule);test_loading_stall.py(held_backchecked in three places). GC-02'smodule generator (
tests/giant_shape_ir.cpp,test_giant_shape.py) is its own test-only PR, #787: it needs theshader stack's test helpers (#697), which this base does not carry, and nothing here uses it.
Measurements
Verdict: the mechanism is measured; the game with this PR is not yet. GPU probe GC-02 compiled synthetic giants made by
LS's own lowering in Metal's compiler service: one of four times the giants' size ended every service process it was given,
alone (about 4.6 GB each); the twin of the 464k giants was made alone (1.07 GB), after 48 other compiles too; what the
service pays for is control flow (the same program straight-line 121 MiB, branchy 759 MiB), not the local array (moved to a
storage buffer, 747 MiB). So scheduling a heavy compile alone is what makes the game's giants; no public switch helps (Metal's
MTL_MONOLITHIC_COMPILERis ignored; MoltenVK's maximize-concurrency switch spawned no more service processes).Baseline: main does not start on macOS. The game columns are the integration builds named (runs #31 and #35, without this
change); GC-02 is a standalone probe (no game code or data), arm64, the probe's pinned MoltenVK main 5189f8de with the
argbuf-slots patch.
Hardware: macOS 26.7.1 (25G241), Apple M4 Max (40-core GPU), 16-core CPU, 64 GB
Scene: runs #31/#35: a start from the field-dawn save, START and A at the title, windowless (#31) and windowed (#35); GC-02: 12
synthetic modules from GC-02's generator (
tests/giant_shape_ir.cpp, #787), each compiled in a fresh process, thenladders of 2-16 at once
Method: runs: the precompile's
slow compilelines (made or not, seconds) per giant; GC-02: made or not per module, eachservice process's lifetime peak footprint (
proc_pid_rusage, every 0.5 s), every made pipeline dispatched and its out wordscompared bit for bit; one run (
reports/GiantCompile2-files/gc02/runs-gc02-arm64/run.json7154e184…)How to reproduce: GC-02's
runcommand (reports/2026-11-15-giant-compile-2.md§6); a start readingvk: precompile: … heavylines and the giants'
slow compilelines694edb10Reports:
reports/2026-11-15-giant-compile-2.md(the giants' shape, GC-01 and GC-02, the routes, the branch),reports/2026-11-14-mac-startup-31.md,-32.md,reports/2026-11-15-mac-startup-35-windowed.md.Flags and settings
Two new switches, read only with
RX_VK_COMPILER_SERVICE(MoltenVK); one switch's rule changes. The diff against main alsoshows the switches of the PRs it stacks on (#669's and #681's stacks), theirs to list:
HOS_KEEP_FREED_PAGES,HOS_ZERO_ALL_PAGES,RX_NVN_WATCH_AFTER,RX_NVN_WATCH_ALL,RX_NVN_WATCH_TEXELS_SRC,RX_VK_ALONE_TRIES,RX_VK_COMPILE_QOS,RX_VK_DLSS_ACTORS,RX_VK_GIANT_COMPILES,RX_VK_GIANT_HOLD_PRESENTS,RX_VK_GIANT_HOLD_S,RX_VK_GIANT_WORDS,RX_VK_GPU_DUMP,RX_VK_LIBRARY,RX_VK_LMEM_LOG,RX_VK_LMEM_LOG_MIN,RX_VK_MESH_IND_COUNT_WORD,RX_VK_NO_UPSCALE_STUB,RX_VK_SET_FOR_LENGTHS,RX_VK_SET_FOR_TABLES,RX_VK_SKIP_LOG,RX_VK_SKIP_PROG,RX_VK_SLOW_COMPILE_S,RX_VK_TEXEL_CODECS,RX_VK_UNIFIED_BUDGET,RX_VK_WORKER_CACHES.RX_VK_HEAVY_WEIGHT=<n>0: none by weightRX_VK_HEAVY_WORDS=<words>0: none by sizeRX_VK_GIANT_ROOM_MB=<MB>Testing
Tested: on macOS 26.7.1, Apple M4 Max:
e090cc48, #669 with #681 merged and the two merge fixes, this PR), every run under the shared check slotwith a fresh HOME and TMPDIR (
reports/PubWave-files/PubLate5/StartTrio/proof/gc2b-*; the first runs at06881766, whosecode is this head's,
gc2-*):test_giant_alone(PASS: heavy compiles run alone, with nothing else of the precompile in flight, none dropped; the first giant behind the game starts unless the memory pressure is critical),test_giant_memory,test_loading_stall,test_private_memory,test_precompile_handoff,test_warm_handoff,test_seg_gathers; at06881766alsotest_retry_alone,test_program_cache_cleanup,test_program_cache_one_variant(both arches) and
test_intel_quick_compute.4f888d6dwith this PR's test files, exit 1 on each arch:test_giant_alone(use of undeclared identifier 'local_weight'),test_giant_memory(FAIL: warning, none in flight: the first giant not startedand 19 more),test_loading_stall(Giants: a giant starts while the board holds, or is not counted on it: the thirdheld_back).2c91cc6b)test_loading_stall,test_warm_handoffandtest_precompile_handofffail (arm64) on thep/plainnames and the unnamed skips, and at5a6f21fetest_private_memoryfails (
the stage's length use … is not followed by note_private_memory×3); each passes at this head.vk_draw.cpp,vk_progcache.cppand the files the base's merge combines (vk_main.cpp,vk_memory.cpp,vk_replay.cpp,vk_texture.cpp,vk_vma.cpp) compiled whole for arm64 and x86_64 with main's renderer flags (#476's, #492's and #490'smacOS compile pieces merged in a scratch tree only): 14 of 14, exit 0 (
…/StartTrio/compile/giant-compile-2/, at06881766:gpu/,docs/andhost/are the same at this head).macos/giant-compile-278f3e5b3, on the integration branch79e2e006): its named checks passed(8/8 twice, 2/2),
test_giant_aloneand the updatedgiant_memory_test.cppfailed on79e2e006's code;vk_draw.cppandvk_progcache.cppcompiled for arm64 and x86_64; its full suite atb969a068585 passed, 3 failed, 16 skipped (the base'stest_menu_i18n, and two program-cache fixtures fixed byb694aeb4) (reports/GiantCompile2-files/checks/).unifdef -U__APPLE__, base against head):precompile_handoff.h,private_memory.h,vk_draw.cpp,vk_progcache.cppandvk_shader.hdiffer: the gate's code and lines are compiled everywhere and reached only undercompiler_service(pre->hand), and off macOSnote_local_weightscans nothing(
reports/PubWave-files/PubLate5/StartTrio/offapple-giant-compile-2.txt).Known gap, being fixed on this branch: game run #36 on the integration carrying this PR (2b4b7b8e, windowed) had 0 XPC
errors, and
5096bcc7began alone and was made in 91.5 s. But compute pipeline44511d69(126,010 words) was in flight from05:28:55 to the run's end (641 s in run #32 too), and the alone rule waits for every compile in flight. So
6dbfda5a,55d1da3fand5c945415never began: 1 of 8 deferred heavy compiles began in the 15-minute hold. A fix that stops anever-ending compile from blocking the heavy ones is in progress, and this PR stays WIP until it is here.
Not tested: whether the 1.09M-word mesh fallbacks fit alone; a program heavier than one service process can hold alone
(GC-02's 4× synthetic) cannot be made by any scheduling, and none is known in the game; Windows and Linux: not built there
(the new code there is compiled and not reached); its test request covers it.
Must be tested:
vk: precompile: N heavy compiles, each run alone …at setup,… a heavy compile begins alone …for each of the four giants,each giant
made, noMTLCompilerServicerange-group line in a livelog stream, and behind the game a giant startedwhile the pressure is not critical (by hand: the parent's game run)
694edb10, it carries #669 and #681 and what they carry) and #669's head thesame way and start each to the title, expecting no
heavy compilesline (the gate needscompiler_service) and every otherline to match #669's (by hand: test request T-XXXX)
Risks
What could break: on MoltenVK the precompile drains its runs before each heavy compile and starts nothing beside it, so
the four giants (and any 1M-word module) compile one after another: the precompile can take longer where they used to share
the service, and longer still before the title if they come before the hand-off. Behind the game the first giant starts at
warning pressure too, where #669 waited. A heavy program that dies even alone is still not made (as before), and its dispatch
compiles it where first needed.
Turning it off:
RX_VK_HEAVY_WEIGHT=0 RX_VK_HEAVY_WORDS=0(no compile is heavy),RX_VK_COMPILER_SERVICE=0(none of theprecompile's path), or revert the PR.
Compatibility: Nothing changes for players outside macOS. No shader-stamp file changes (
vk_progcache.cpp,vk_shader.h,precompile_handoff.h,private_memory.handvk_draw.cppare not hashed), no cache format change, noMKWVK_API_VERSIONchange.Reviewer notes
4f888d6d..694edb10: the topic's three code commits picked with-xfrommacos/giant-compile-2(b969a068heavycompiles alone,
b694aeb4the weight inprivate_memory.h,78f3e5b3a comment), each the source commit line for line withno conflict, then two of mine:
27753a2e(theRX_VK_GIANT_ROOM_MBrow: #669's row, which the topic's tree did not have, saidevery giant waits for the room) and
694edb10(note_local_weighton macOS only: off it the scan had no reader; the checkfollows). Read
Giants::nextandheavy_readyfirst, thenbehind_worker. The topic's first commit,31ff4adc(GC-02'smodule generator, test-only), is #787, on #697.
Giants, the memory gate, the compile board, and what #669 carries: #666, #665, #664,#652, #650, #651, #642) and on #681 (
private_memory.h's scan, which gives the weight, andnote_private_memory's places;and the renderer stack #681 carries, #663 and below). The branch is #669's head
4d16d199with #681's headf9dc41acmerged(
2c91cc6b), resolved as the integration branch79e2e006has it:vk_shader.h(Stage's fields),vk_progcache.cpp(thethree places a stage is noted),
vk_vendor.h,vk_main.cpp(the vendor fixes line),vk_draw.cpp(dispatch(): #681'sprogram names and stand-in admission with #669's named skips),
docs/development.md(rows).5a6f21feand4f888d6d(#669 × #681), sibling items: whichever of the two lands second needs them;this PR carries them:
5a6f21fe: #669's LoadingStall names every dispatch left out and its test allows no bareSKIP_HERE()indispatch();#681's SegBind brings five: each is
SKIP_DISPATCHwith the integration branch's reason (off macOS the file's text isunchanged: SegBind's dispatch path is Apple-only);
seg_gathers_test.cppgets theSKIP_DISPATCHstand-in;test_loading_stall,test_warm_handoffandtest_precompile_handoffread the dispatch's program asplain(#681'sstack's name) and the count of named skips as 13 (#666's own comment said the segmented binding's five would come).
4f888d6d: #681'stest_private_memorylooks fornote_private_memoryon the line after a stage's.array_length; on#669's stack the stage's
.wordsline (#652) sits between them, so its source check looks after the.wordsline, withthe integration branch's regex and messages (found by the wave suite at
4cc29486).git merge-tree; none of this PR's own commits beyond its base's):docs/development.mdandgpu/vk/vk_vendor.hwith #785 (rows and neighbouring fields: keep both);gpu/vk/vk_draw.cpp'sdispatch()with #786, #667 and the other PRs that carry #666 without #681's stack (the base merge's: resolve as here);
the rest are #669's and #681's own.
35465d12): inherited from the base (vk_draw.cpp,test_pipeline_play_cache.py); none of this PR's own.reports/2026-11-1*.md(the port's design notes), which are not in this repository.Issues
See #420
Checklist
Everyone
type(scope): description) of the status line's type.main. (On maine090cc48through #6694d16d199and #681f9dc41ac; main has moved since (35465d12): a delta sweep follows.)python -I -B tests/run_all.pypasses (any failure that also fails onmainis named). (On wave Sc08ae52d= this head694edb10with #7865c37ec10and #785a4d31a59merged: 586 passed, 31 failed, 18 skipped against main e090cc48's 544/32/17 on this Mac; the 31 failures are main's own and nothing that passes on main fails;test_collect_widenow passes; 41 new tests pass;reports/PubWave-files/PubLate5/wave/full-S-c08ae52d/.)third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.mainand passing here. (They fail on this PR's base with this PR's test files; main has no precompile gate.)tests/that reads changed source updated in this PR, not weakened.7707c634) under the precompile hand-off c19fe5a59d263e462d) under the giant compiles' memory gate b5016895f8Every scan down vk_memory.cpp's import table by start (pool_lookup's covering import and merge plan, bg_drain, prefetch_chunks, import_in_background, memory_note_gpu_write and memory_guest_imported) stopped once an import ended more than 1 GiB below its address, on the belief that no import is larger than that. pool_lookup's own merge escape leaves an import above 1 GiB over the smaller ones inside it ("left overlapping"), and the device allows imports up to max_import_size: a scan stopped at a small import inside a large one, missed the large one, and the same bytes were imported again, two memories over the same bytes. import_reach.h counts the live imports by span class; its bound is above every live span, within twice the largest. imported_add and imported_take, now the table's only ways in and out, keep the count. A scan stops only where no import further down can reach its address, so it also stops sooner than before when the imports are small. The covering-import checks in pool_lookup and import_in_background look past the last import at or below the range for the same reason. Tests: test_import_reach.py runs the actual scans with a 4 GiB import over a 16 MiB chunk, left by the merge escape (imports are recorded ranges: no payload, no Vulkan, no game). Its lookup, query, note, drain, background and prefetch cases fail on main5e072fc8and pass here, and a source check keeps every change to the table in the two helpers. test_import_windows runs pool_lookup with the helpers; test_mirror_gpu_only names the new publication point. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <[email protected]>A guest span larger than one device allocation (the cap on MoltenVK/M4 Max is 41,747,087,360 bytes) is bound as pieces: one descriptor per allocation segment, at the descriptor's own binding (reports/2026-11-14-full-range-design.md sections 4.1-4.4). This is the shader half; the draw side (descriptors, layout, gathers) comes later. - runtime_info.h: SegEntry {Storage|Table|TexBuf|ImgBuf, desc, pieces} and RuntimeInfo::segmented. - Back end: a segmented storage buffer is an array of pieces at its binding (a table count * pieces, entry k's piece j at k * pieces + j); its loads, stores and atomics call DontInline helpers made after main, which find the piece by the pieces' OpArrayLength in order, with constant indices. The global memory fallbacks sum the pieces' lengths for a segmented descriptor's bound end and branch to its piece (single, native candidate, table written out and in the loop). Texel and image buffers are arrays too, their fetches, reads, writes and atomics through helpers that pick the piece by OpImageQuerySize, and a texel buffer's size query sums the pieces. ImageQuery was declared for every module already. With RuntimeInfo::segmented empty nothing new is made: the SPIR-V is byte-identical. - vk_shader: SegSig (entries, hash) on Program, seg_sig and seg_variant; program_segmented finds or translates the variant of a plain program with plain's texture questions and answers and checks its descriptors are plain's; program_pick, mesh_program_locked, program_find_plain, loaded_variants and portable_variant keep the plain and segmented variants apart; every stage's RuntimeInfo::segmented comes from Program::seg. - vk_progcache: a segmented variant is never written to the program cache nor listed among the loaded programs. - tests/test_seg_shader.py + tests/seg_shader_ir.cpp: synthetic IR programs through the real back end, plain and segmented; structure checks, spirv-val (Vulkan 1.3, uniform buffer standard layout), and with --base-tree a byte comparison of the plain modules against an older tree. Five source-reading checks follow the new Program field and the atomics' new branch. The translator stamp changes with this (eden/, vk_shader.cpp, sizeof(Program)): every program cache is rebuilt once. Land it in the same release as the vk_api.h change that already does so. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit b7928876dab846485f667c1c934d34d25ecef035)044635ea added vma_startup_rollback (vk_vma.cpp, run after a failed replay_start), which calls vmaDestroyAllocator. vk_vma.cpp includes vk_work.h last so that RX_VK_RECORDER=defer/thread drains before its destructions, and every such boundary call in a vk_work.h file must be in RX_REC_BOUNDARIES with a drain-first macro. This one was not, and test_recorder_sync caught it ("vk_vma.cpp calls ['vmaDestroyAllocator'], which vk_work.h does not drain before"): it passes at 58c5c5aa (044635ea's parent) and fails at 044635ea. Add it to the list and to vk_work.h, next to vmaDestroyBuffer and vmaDestroyImage. With the recorder off (the default) boundary() is a relaxed load and a branch. Rollback runs before any image or buffer exists, so nothing should be held there today, but the allocator is no longer destroyed while a recorder could still hold packets. The test is unchanged (sources: 42 boundaries drained, was 41). Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <[email protected]>The interfaces the four segmented-consumer lanes (SegBind, SegXfer, TexelDesc, NullShader; reports/2026-11-14-seg-consumer-plan.md section 3) code against, frozen before they fork. Inert: no caller of anything new, no SPIR-V, layout, push range or stream change, sizeof(Program) unchanged. - vk_internal.h / vk_memory.cpp, SegMem's consumer interface: gpu_span_any() (a relaxed count of the live domains, stored in seg_commit; domains are kept for the run), gpu_span (gpu_buffer's flavour of gpu_span_bind: import buffers, no written marks, no mirror), SpanLayout::bind_stamp (gpu_bind_stamp(): a segment can be imported again with its cuts kept, so cut_gen alone never keys piece buffers), span_unused_piece's semantics ({null_storage, 0, 1}), and the SegArena API (SegArenaRef, seg_arena_take, seg_gather, seg_scatter; stub bodies after stream_alloc that give no room and log, SegBind's to fill). - Review fixes in the same interface (renderer-core review P2-3, P3.2): every piece of gpu_span_bind carries its buffer's device address (the import's or the mirror's; 0 only with bufferDeviceAddress off), and its one-piece path stores the translated guest address, not a virtual one. - vk_texture.h/.cpp: texel_view_pieces / texel_piece_views, stubs that return 1 and false (TexelDesc's to fill). - vk_shader.h: Program::presence_words, in what was padding. - Back end: PRESENCE_PUSH_OFFSET (88), Profile::emulate_null_descriptors, Bindings::presence / presence_read with the bit numbering rule. - tests: test_resolver_coverage lists gpu_span_bind / gpu_span sites too (scoped to gpu/vk: gpu/render has an rx::gpu_span of its own), checks that gpu_span follows its own gpu_buffer, and splits SITES into SITES_BASE plus one block per lane. test_segmented_domains executes the contract (the count 0, 1, 2 -> 1 on a merge, 0; gpu_span's pieces; addresses with the fixture's gpu_buffer_bind returning none as the real one does; the bind stamp; a virtual address's guest). test_seg_shader accepts a base tree of this back end's generation and compares every module. - docs/segmented-binding.md: the gate (every new behaviour Apple-only, other platforms byte-identical) and one heading per lane. Conflict on this branch (not with main): tests/test_seg_shader.py's docstring, against the array_pointer_calls paragraph #P20's msl-arrayptr port added; both kept, this paragraph first. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit 95fa06d4e6526601bd2e1bc88c2f8f5720855350)The segmented single storage array's case (plan 4.3 item 5) had no negative: the check always cut its own tree. With --source <B2 archive> it fails as required ("moltenvk.segmented_single want=set got=push"). Conflict with #P28 (read_write textures; not with main): the docstring keeps #P28's paragraph with this usage line, and main() cuts from --source's vk_draw.cpp with #P28's read_write count (as in the macOS integration, Integration 17). Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit 5824f6828e21cf810dcceaa6be425a9224b15994)plain7a498e5865plain1e1a3b3e0df9dc41ac, #681) into the giant compiles' memory gate (mac/giant-mem-gate4d16d199, #669) 2c91cc6b52Run 31's three giant mesh fallbacks (a 9.1-9.3 KB local memory array behind about 8,000 access chains) ended four MTLCompilerService instances in a row when three of them shared one: the hardened service keeps every compile of one instance in two pointer ranges of about 8 GiB together ("Failed to allocate segment from range group - out of space"). Run 35 lost the two 464k-word ones together the same way. Each was made alone, or two to an instance, in runs 18, 21, 26, 31 and 35. - precompile_handoff.h: heavy_compile (RX_VK_HEAVY_WEIGHT, local memory weight 10,000,000: the largest Private or Function array's access chains times its words; RX_VK_HEAVY_WORDS, 1,000,000 SPIR-V words). Giants::next takes heavy_of: a heavy run starts only when no run of the precompile is in flight (every run counts until run_left) and nothing else starts while it runs; while the hold, play or memory keep it back the others go on, and once only the runs in flight do, they drain. The giants: 18.2M and 18.5M; every other module noted in runs 23-31 at most 5.4M. - Behind the game, with no giant in flight one starts unless the memory pressure is critical (memory_lets_first_giant): run 32 held all 1,302 giants for its whole run for want of 16384 MB available. The room bounds the second giant and those after it. - vk_progcache.cpp: note_local_weight, for every module over 100,000 words where the program cache notes a stage, whatever RX_VK_LMEM_LOG. vk_draw.cpp: the precompile's heavy table, behind_worker's run_left, the setup, deferral, begin and done lines. - docs/development.md: the RX_VK_HEAVY_WEIGHT row. - tests: test_giant_alone.py (new; giant_alone_test.cpp), giant_memory_test.cpp for the first-giant rule, test_loading_stall.py's count of held_back checks (three now). Only MoltenVK's compiler_service path changes (behind_worker, pre->hand); the gate's 7-argument next() is as before. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit b969a0683549042c4937de78976f0b10c07ecc42)694edb10a099e43bd2f3Folded into #849 (fix(macos): MoltenVK's precompile and pipelines on Metal)
Pull request closed