Repository navigation
perf(lift): one page mark per straight-line group of stores through a base register - #912
Conversation
… base register A64_WRITE_TRACK marks the page of every non-SP store, two byte stores and the address arithmetic each. In a straight-line run (no label, branch, call, return, poll, barrier, atomic, exclusive, SVC or trap) the stores through one base register plus constants spanning at most 4 KB now share one A64_MARK of their byte span, placed after the group's last data store and before the base register changes or the run ends. The pages marked are the same, and each data store is still followed by a release store of its page's mark before any guest synchronisation. 43% fewer mark stores in the lifted game, about 24 MB less code. A64_MARK_EACH=1 at lift time keeps a mark per store. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…edit stops the lift Review of NotRexed#912. The groups changed the lifted text before nx2/oot_patches.py matched it exactly: OUT_EDITS[0] (nnMain's create arg) no longer matched, apply_output_size only warned, and the Output resolution was ignored. - Order: emit_function no longer groups. nx2/lift.py edit_c applies the patches, then a64_lift.group_marks over the written code_*.c files, last, once, with the lift's stats (reported). The kit's own write() keeps a mark per store; A64_MARK_EACH=1 still turns the groups off (in the lift key). - Required edits: on the supported build's whole standalone lift, every OUT_EDITS place must be patched, or found patched, exactly once, or the lift stops before the key is written. Other builds, partial lifts and the other patches warn as before. - Run ends, conservative: a line whose braces do not balance (no mark moves into or out of a multi-line block), STLR/LDAR in suyu's spelling, and any call-like token not on a short list of plain loads, stores and pure helpers (f_*, hos_* from the patches, A64_*, atomics, unknown helpers). - The pass is idempotent (its own marks are not run ends), takes the nearest _b/_a definition on a line, skips functions with local registers, and shares REG_WRITE (now with ++/--) with reg_writes. About 6x faster than before: functions with fewer than two stores skipped, lines with no open group skipped, cheap substring tests before each regex. - Lifted v2.0.6 C: mark stores 2,521,896 -> 1,454,508 (42.3% fewer; the first version 43.1%). Idempotent on all 80 files. - tests/test_mark_groups.py: the patches-then-groups sequence through lift.edit_c with the OUT_EDITS places (no game code), the old order as a failing control, the patched function compiled and run with and without groups (same memory, same pages), the review's address forms, the 4096 span, STLR/LDAR, blocks, host calls, A64_MARK_EACH, the lift key, stats, idempotence, and a random page-set check. nx2/test_leaf.py groups its functions as the lift does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nt every made and raw output-size place Review of NotRexed#912 (review229). - chunks.write removes an earlier lift's code_hot_*.c and code_hot.c.whole whether or not this lift writes a code_hot.c. With a hot list, the old pieces (patched already) stayed until make_game's split_hot, after the lift: nx2/oot_patches.py and the groups read them beside the new code_hot.c, an output-size place counted twice, and the required check failed a normal rebuild. split_hot makes this lift's pieces after it. - apply_output_size counts each made place and each raw place outside a made one (a new text can hold its old one), per file. A file with a place made twice, or made and raw, was counted once and accepted; now it is not once, and the supported build's lift stops. A file made once is accepted and left as it is. - tests/test_mark_groups.py: a rebuild over the first lift's patched hot pieces (chunks.write, edit_c, split_hot, twice) finds each place once; made once accepted; made twice, made and raw ([3], and [4] whose new text holds its old one), and raw in another file rejected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
b7d5a93 to
816a472
Compare
|
Thanks, all addressed in 3fa6300 and 816a472 (rebased on current main):
Not rerun here: test_insn and test_leaf (no capstone/unicorn or game), and no game run. |
nx2/lift.py imports nso.py, which imports lz4 for NSO decompression; the Python source checks have no lz4, so the three tests through lift.edit_c failed at import. They read no NSO: lz4 is stood in for like capstone. (test_output_resolution's hos_rt_pool_size assertion fails on main too.) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s tools The order tests imported nx2/lift.py for edit_c, and lift.py imports the whole lifter at load time (capstone, nso.py's lz4, adr_entries' numpy): the CI runners have neither lz4 nor numpy, so the three tests of the patches-then-groups order errored out. edit_c (the patches, then the groups, over the written code_*.c) now lives beside chunks.write, which already writes those files and imports a64_lift; lift.py calls chunks.edit_c. The lz4/capstone stand-ins in the test go. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks. Fixed in 1c5258f: stubbing lz4 got past that import, but On the nits: yes, the supported lift stopping under |
## [2.7.0](v2.6.4...v2.7.0) (2026-11-09) ### Features * microphone input for the ocarina's input streams ([8a17b8c](8a17b8c)) * microphone input on SDL2 too (the Linux AppImage's) ([eadc184](eadc184)) * **vk:** AMD FSR 4 upscaling and frame generation through AMD's runtime ([65003cc](65003cc)) * **vk:** say why DLSS 5 neural rendering is refused, and the RTX 20-40 route ([60f1d2e](60f1d2e)) ### Bug Fixes * **audio:** 5.1 surround when the default output has it ([NotRexed#1124](https://github.com/notquiteog/Hookshot/issues/1124)) ([b0abeca](b0abeca)) * **audio:** a finished wave buffer reaches the game at the renderer update, not mid-pass ([28e4a11](28e4a11)) * **builder:** stop when generated source cleanup fails ([d254e6f](d254e6f)) * contain pipeline cache save allocation failures ([4518a20](4518a20)), closes [NotRexed#1022](https://github.com/notquiteog/Hookshot/issues/1022) * **lift:** clear an earlier lift's hot pieces before the patches; count every made and raw output-size place ([816a472](816a472)), closes [NotRexed#912](https://github.com/notquiteog/Hookshot/issues/912) * **lift:** edit_c in chunks.py, so its tests import without the lift's tools ([1c5258f](1c5258f)) * **lift:** group page marks after the patches; a missing output-size edit stops the lift ([3fa6300](3fa6300)) * microphone names past 60 bytes, unplugs that keep sending ([NotRexed#937](https://github.com/notquiteog/Hookshot/issues/937), third review) ([e77bd79](e77bd79)) * of two microphones with one name, the one chosen opens ([602d96e](602d96e)) * preserve NUP member names during implicit reads ([1fdfeff](1fdfeff)) * **release:** the Windows release installs the renderer CI built, with DLSS, NR and FSR 4 ([0fa18bd](0fa18bd)) * the microphone retry path ([NotRexed#937](https://github.com/notquiteog/Hookshot/issues/937), second review) ([50f2392](50f2392)) * the microphone review's points ([NotRexed#937](https://github.com/notquiteog/Hookshot/issues/937)) ([27a9b7d](27a9b7d)) * **vk:** a draw that waits for its pipeline runs the build at once if it is still queued ([aafbaf5](aafbaf5)) * **vk:** keep the last sRGB UNORM surface format, so drivers that list only sRGB keep theirs ([3408437](3408437)) * **vk:** Linux/Wayland: take an sRGB surface format for the swapchain ([7416487](7416487)) * **vk:** no mirror is made inside a parallel replay's segment (an earlier segment read it unfilled) ([8f0ed68](8f0ed68)) * **vulkan:** preserve guest mip levels in partial clears ([d7a1207](d7a1207)) * **vulkan:** release modules from rejected cache records ([2838e89](2838e89)) * **widescreen:** the save picture shows the middle 16:9 on ultrawide ([NotRexed#952](https://github.com/notquiteog/Hookshot/issues/952)) ([e2e155e](e2e155e)) ### Performance Improvements * **fpx:** keep the hardware result for FMUL/FMLA S when the flags are already set ([6de3bf1](6de3bf1)) * **lift:** one page mark per straight-line group of stores through a base register ([825861b](825861b)) * stream sampled profiles instead of loading every line ([8cf6d1e](8cf6d1e))
## [2.0.12](v2.0.11...v2.0.12) (2026-11-09) ### Features * a timed span per FSR pass, so the cost has a number per pass ([1d7f262](1d7f262)) * add a Quit option to the gameplay menu ([0b02149](0b02149)) * **audio:** Audio output in the Insert menu: Auto, Stereo or 5.1 ([4856258](4856258)), closes [NotRexed#1182](https://github.com/notquiteog/Hookshot/issues/1182) * census the render target sets, to settle whether a G-buffer exists ([8f2fda3](8f2fda3)) * compile AMD's FSR 3.1 upscaler passes from the FidelityFX SDK ([37afa2e](37afa2e)), closes [NotRexed#577](https://github.com/notquiteog/Hookshot/issues/577) * compose the scene's view projection, and transpose it for the shader ([df82d90](df82d90)) * count FSR evaluations, because nine passes a call is cheap or ruinous ([85a3ea2](85a3ea2)) * create FSR 3.1's pipelines and internal images ([6b51433](6b51433)) * derive camera motion from the scene's depth, and keep that depth ([29c7cbe](29c7cbe)) * DLSS at the seam, and a stage switch for the FSR cost ([6aa9f48](6aa9f48)) * DLSS Super Resolution through NGX, behind a build-time SDK gate ([aff6abb](aff6abb)) * frame generation asks for its own extensions and the whole input set ([ad291d1](ad291d1)) * FSR 3 frame interpolation, and a jitter for the upscalers to undo ([384c41b](384c41b)) * give FSR the assembled depth and camera motion ([481b957](481b957)) * HOS_DISPLAY_VRR, which is what actually holds the frame rate at 60 ([c65c68e](c65c68e)) * HOS_FPS_FORCE, because raising the limiter alone does not hold ([2008ac7](2008ac7)) * HOS_VSYNC_HZ, the hardcoded 60 that actually paces the game ([5eb9f8b](5eb9f8b)) * link NGX properly, frame generation at the seam, AMD's interpolation sources ([43e3360](43e3360)) * Monitor's shape for screens narrower than 16:9 (4:3, 16:10, 5:4) ([b44c9cd](b44c9cd)) * name each network binding's role in the upscaler input contract ([d9566a1](d9566a1)) * publish Ocarina's camera, and make a backend reachable without the stand-in ([8bd7c8d](8bd7c8d)) * record FSR 3.1's nine dispatches ([8769c80](8769c80)) * route the game's upscale dispatch through one backend seam ([1a036c8](1a036c8)) * RX_VK_VP_HUNT, to find the view projection from a value only it holds ([3c02213](3c02213)) * the hunt found the projection, so widen it to find the view ([2946a32](2946a32)) * the parallel lock says which site took it, and the answer is two sites ([b8543c9](b8543c9)) * the two backends have rows in the Insert menu ([cf8b3b1](cf8b3b1)) * the two lock sites say why they had to take it ([614f552](614f552)) * the upscaler and frame generation follow the menu while you play ([61d7d4a](61d7d4a)) * the upscalers and frame generation are the native settings, not new rows ([a0ce7de](a0ce7de)) * the vsync event follows the display, and 0 turns it off ([a67e471](a67e471)) * **vk:** AMD FSR 4 upscaling and frame generation through AMD's runtime ([65003cc](65003cc)) * **vk:** choose the frame generation method in the Insert menu ([c4f7d84](c4f7d84)) * **vk:** DLSS 5 neural rendering through NGX directly, beside Streamline ([7784399](7784399)) * **vk:** DLSS 5 neural rendering, with the model's own settings in the menu ([2851bc6](2851bc6)) * **vk:** frame generation Auto: a frame for each of the display's refreshes ([a5a7440](a5a7440)) * **vk:** say why DLSS 5 neural rendering is refused, and the RTX 20-40 route ([60f1d2e](60f1d2e)) * **vk:** time a capture's GPU wait for its late pages apart in the log ([e3b622f](e3b622f)) ### Bug Fixes * a fork releases its own work, not upstream's ([47dd097](47dd097)) * a Windows build could ask for DLSS and then fail to link ([d72441a](d72441a)) * **audio:** 5.1 surround when the default output has it ([NotRexed#1124](https://github.com/notquiteog/Hookshot/issues/1124)) ([b0abeca](b0abeca)) * **audio:** a finished wave buffer reaches the game at the renderer update, not mid-pass ([28e4a11](28e4a11)) * **build:** a Windows build linked the ARM64 NGX library, not the x64 one ([70b7d25](70b7d25)) * **builder:** copies from a read-only install stay writable ([70ffa5e](70ffa5e)) * **builder:** stop when generated source cleanup fails ([d254e6f](d254e6f)) * **builder:** the window icon is copied writable too ([4b2d871](4b2d871)) * **build:** NGX asks for Microsoft's C++ runtime, which llvm-mingw has not ([114e917](114e917)) * **build:** the feature announcement swallowed the whole Vulkan build ([1004f37](1004f37)) * **build:** the twelve MSVC runtime symbols NGX asks of an llvm-mingw link ([d00159a](d00159a)) * clear the SPD counter between the pyramid passes (did not fix the cost) ([66e8c3a](66e8c3a)) * contain pipeline cache save allocation failures ([4518a20](4518a20)), closes [NotRexed#1022](https://github.com/notquiteog/Hookshot/issues/1022) * HOS_FPS_FORCE must not touch the nominal rate: it is slow motion on screen ([8681f2e](8681f2e)) * **lift:** clear an earlier lift's hot pieces before the patches; count every made and raw output-size place ([816a472](816a472)), closes [NotRexed#912](https://github.com/notquiteog/Hookshot/issues/912) * **lift:** edit_c in chunks.py, so its tests import without the lift's tools ([1c5258f](1c5258f)) * **lift:** group page marks after the patches; a missing output-size edit stops the lift ([3fa6300](3fa6300)) * **linux:** the AppImage's parts list names the repository it is built in ([2228816](2228816)) * narrow screens: window at the picture's shape, save picture's middle band, comments ([3060261](3060261)), closes [NotRexed#1183](https://github.com/notquiteog/Hookshot/issues/1183) * preserve NUP member names during implicit reads ([1fdfeff](1fdfeff)) * publish the camera from the inlined projection copies too ([c630d59](c630d59)) * read the upscaler contract in descriptor order, not from draw_sampled ([492bcd0](492bcd0)) * **release:** the fork's releases follow upstream's v2.0.x line ([d495994](d495994)) * **release:** the setup, the Builder and the game update from the repository they were built in ([6fd4364](6fd4364)) * **release:** the Windows release installs the renderer CI built, with DLSS, NR and FSR 4 ([0fa18bd](0fa18bd)) * scan for the view projection once a target set, not once a draw ([12d4d83](12d4d83)) * snapshot the projection's fields, and name what caps the frame rate ([3790556](3790556)) * the conventionalcommits preset has to match the changelog writer ([ca6595f](ca6595f)) * the DLSS contract test links again ([86b6988](86b6988)) * the game's network takes one texture, so it is not a temporal upscaler ([853cfe3](853cfe3)) * the history reset the document specifies, which nothing implemented ([9858599](9858599)) * the host camera symbols must be weak, or the renderer does not load at all ([70ecdf4](70ecdf4)) * the release's commit-analyzer preset is a package, and it was not declared ([3ff9fbd](3ff9fbd)) * the renderer would not load, and the build now says so ([c871a3c](c871a3c)) * the shader check compared glslang's version, and ran where glslang is not ([f48483c](f48483c)) * the view projection scan ran per draw, which stuttered hard ([2d9b4e8](2d9b4e8)) * **vk:** a draw that waits for its pipeline runs the build at once if it is still queued ([aafbaf5](aafbaf5)) * **vk:** DLSS frame generation sets NGX's parameters through its C functions ([2c19b63](2c19b63)) * **vk:** frame generation Auto: the snap distance is not named near (windows.h defines it empty) ([d156a6d](d156a6d)) * **vk:** keep the last sRGB UNORM surface format, so drivers that list only sRGB keep theirs ([3408437](3408437)) * **vk:** Linux/Wayland: take an sRGB surface format for the swapchain ([7416487](7416487)) * **vk:** no mirror is made inside a parallel replay's segment (an earlier segment read it unfilled) ([8f0ed68](8f0ed68)) * **vk:** tag the uplift colour pair as whole resources, as the shipping integration does ([dd3e61e](dd3e61e)) * **vulkan:** bind only bytes accessed by buffer-image copies ([b0a9034](b0a9034)) * **vulkan:** clean up failed mesh argument initialization ([b2f5692](b2f5692)) * **vulkan:** preserve guest mip levels in partial clears ([d7a1207](d7a1207)) * **vulkan:** release modules from rejected cache records ([2838e89](2838e89)) * **widescreen:** the save picture shows the middle 16:9 on ultrawide ([NotRexed#952](https://github.com/notquiteog/Hookshot/issues/952)) ([e2e155e](e2e155e)) ### Performance Improvements * **fpx:** keep the hardware result for FMUL/FMLA S when the flags are already set ([6de3bf1](6de3bf1)) * **lift:** one page mark per straight-line group of stores through a base register ([825861b](825861b)) * **lift:** the interrupt poll before each backward goto, calling a64_poll_cur() with no argument, so a loop's guest registers stay in host registers ([0d6ed03](0d6ed03)) * stream sampled profiles instead of loading every line ([8cf6d1e](8cf6d1e))
What this fixes
With
A64_WRITE_TRACK, every non-SP guest store marks its page with two release byte stores. Lifted code spends a measurable share of game-thread time on those marks.Why merge
A group of stores through one base register, in a straight-line run spanning at most 4 KB, now gets one shared
A64_MARK. It covers the same two pages, placed before anything that ends the run. Mark stores drop from 2,521,896 to 1,454,508 (−42.3%) on lifted v2.0.6 code.Change
nx2/lift.pyedit_cruns the text patches first, thena64_lift.group_marksonce over the written C. This keepsoot_patches.py's exact-text edits intact (the review's blocker).chunks.writeclears an earlier lift's hot pieces before patching.A64_MARK_EACH=1turns grouping off.Tested
tests/test_mark_groups.py, 21 cases:edit_c, with the old order as a control that must fail;All 21 deliberate sabotages fail it.
Not covered
test_insn/test_leafnot run (needs Unicorn and a game dump). No full lift or game run of this revision.🤖 Generated with Claude Code