Repository navigation
Cheaper label checkpoints in the generated code (Thor main thread) - #222
Merged
Merged
Conversation
…nerated base The generated code checkpoints at every label (172,524 of them, 17% loop heads). The permit call on the checkpoint's rare path is an ordinary call as far as the compiler knows, and the context is passed to every callee, so every guest register in a host register had to be stored before each label and reloaded after it; a loop without calls kept its counter and pointers in memory, each iteration a store and a reload. On AArch64 Linux and Android (SFR_HIDDEN_CHECKPOINT, CMake option, default ON) the call is now made from a non-volatile inline asm statement that names no memory, through a trampoline; the count the permit sets comes back in x0 and is written back where the compiler sees it, and the permit's exceptions are caught on the far side and rethrown. The checkpoint also tells the compiler the thread state lies apart from the context and guest memory (the generated sfr::guest_checkpoint(ctx) now passes the function's base through a macro), so such a loop keeps its count in a register too. The GPR save/restore hooks (6% of the AYN Thor main thread) found guest memory's base through active_memory: three dependent loads before the first register could move, their hottest instruction. They now use the base the generated code passes them, which is the same pointer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every label of the generated code called sfr::guest_checkpoint(ctx), but only 29,100 of the 172,524 are loop heads (a label a goto at or after it returns to, switch cases included). A forward-only label is passed at most once between two function entries, which checkpoint themselves, so its checkpoint only counted down; two thirds of the checkpoint time on the AYN Thor's main thread was at such labels, and each one is a load, a decrement and a store on one count that every checkpoint of the thread chains through. The generator now emits the checkpoint only at loop heads. scripts/loop_head_checkpoints.py does the same to an existing output directory (the shipped generated code is not regenerated) and --check verifies the copy is the input less those lines. Guest-visible behaviour is unchanged; the permit's count now spans more guest work. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Profile of the generated code (statement and pattern breakdown), what was changed and measured (hidden label checkpoints, the helpers' base, loop-head-only checkpoints), how to ship the loop-head part with the current generated code, and the remaining proposals ranked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-code # Conflicts: # docs/performance.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Generated guest code is ~32% of a race frame's main thread on the AYN Thor (simpleperf, release 0.10.13). Label checkpoints and
enter_functionmake up a quarter of that time. They also make the code around them slower: the permit call on a checkpoint's rare path is an ordinary call as far as the compiler knows, so guest registers are stored before every label and loaded again after it, and loops keep their counters in memory.Changes
SFR_HIDDEN_CHECKPOINT(CMake option, default ON; only AArch64 Linux and Android): a label checkpoint's permit call is made from a non-volatileasmstatement throughsfr_checkpoint_trampoline. The count comes back in x0, and exceptions are caught and rethrown.__builtin_assume_separate_storagetells the compiler the thread state is separate from the context and guest memory.sfr::guest_checkpoint(ctx)passes the function'sbasethrough a macro. As a result, loops without calls keep guest registers and the count in host registers.basethe generated code passes them. Before, they readactive_memory->base(): three dependent loads, and the helpers' hottest instruction.scripts/loop_head_checkpoints.pyapplies the same rule to an existing output directory without regenerating it, and--checkverifies the result.Thor A/B (Dolphin Resort moving race, HOST_THREADS frames 13000-21000, versionCode 61 test builds)
SFR_TEST_RACE_DRAW_MS=10(~4,700 undrawn frames each)Tests
Shipping the loop-head part with the current shipped generated code needs two steps at release time:
python scripts/loop_head_checkpoints.py <out/recomp-midasm/diagnostic> <copy>, then the same command with--checkto verify the copy.-DSFR_DIAGNOSTIC_DIR=<copy>.Without the copy, the build still gets the hidden checkpoint and the helper change.
🤖 Generated with Claude Code