feat(profile): HOS_HLE_STATS=1 counts HLE, host-call and NVN export calls #153
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 @ 8015c9a
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!153
Loading…
Reference in a new issue
No description provided.
Delete branch "klimovich008/Longshot:feat/hle-call-counters"
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?
Port of NotRexed/Hookshot#2580 (draft there). It went through five gpt-6.1-sol review rounds, as that PR's description says. Each round's findings were fixed, and the last round found none. It has no GitHub reviews. The commits are the same, rebased onto Longshot
main, plus one that gives the test Longshot's file names.Why
The CPU profile's calls column came from a local patch that nobody else could rebuild. With this PR, anyone can count how often each HLE function, host call and NVN export runs, behind a switch that is off by default.
What changed
host/hos.h: theHLE(sym)macro counts entries in a tail-called arm (hos_hle_counted). With counting off, an entry pays one flag test and a branch.host/hos_hostcall.c: logshle:blocks about every 5 s, each stamped with the monotonic clock times it covers (begin_ns,end_ns). Host calls are counted ina64_call_missanda64_lookup_host. The host-call table is read under its lock and the counters are published atomically. If the report thread can't start, counting stays off.host/hos_main.c: turns counting on before the game starts.host/nvn_native.c: counts SDK export calls innvn2_dispatch.scripts/cpu_report.py(--calls):HOS_SAMPLE_ALLstacks;scripts/crit.py: names an HLE body that wasn't inlined after itsimp_<sym>entry.docs/development.md,docs/cpu-profile.md: the switch and how to collect a counting run. The profile's existing calls and per-call columns predate the clock-checked joins, so they are marked for regeneration. This PR does not recompute them.tests/test_hle_stats.py,tests/test_cpu_report.py: switching on and off, NVN exports, 32-bit wrap, concurrent callers with the reporter running, a failed thread start, the complete-block and window checks, and nine broken variants that must be caught.Flags and settings
HOS_HLE_STATS=1hle:call counts about every 5 s, with clock boundaries, forcpu_report.py --callsMeasurements
main:
839e85ff(2026-11-13)this PR: feat/hle-call-counters @
a4ce1b54, merged onto839e85fffor the build.Why the numbers still hold on main
5e072fc8:839e85ffis the team's current measurement base. Since then, main's changes to the files this PR touches (host/hos.h,host/hos_main.c,host/nvn_native.c) give some log lines a level (hos_log_at). None of them is on the HLE entry path, and both merges (525fcf60with514591a3,055a8facwith5e072fc8) applied without conflicts in this PR's files. Main's other changes affect this PR's two arms and main alike.Hardware: AMD RX 9070 XT (Adrenalin 26.10.2), Ryzen 7 5700X3D, 64 GB, Windows 11
Scene: Hyrule Field, fast camera turn (the standard test); 16:9 (
HOS_WINDOW_SIZE=1280x720,HOS_ASPECT=1.7778),HOS_FPS_MAX=240,HOS_FPS_LOG=1, the fixed test profiletest-profile-169.ini, no upscaler or frame generationMethod:
HOS_HLE_STATS=1.host/hos.h.longshot.exe6dc5a7f0,longshot_vk.dll8eb6d56dlongshot.exeab2af78d,longshot_vk.dllce3c0a8dHow to reproduce: from claude-perf,
python -I std_test.py --runtime <this PR> --name <n> --arm stats=HOS_HLE_STATS=1 --arm main@<main runtime>= --pairs 3 --options-ini test-profile-169.ini839e85ffa4ce1b54, counting offa4ce1b54,HOS_HLE_STATS=1Diagnostic. With 3 pairs, std_test labels any effect under 5% "diagnostic", and every effect here is under 5%:
Both comparisons change sign between pairs, so these three pairs resolve no cost either way. The single slow frames over 250 ms (603 ms on main, 474 ms with counting on) are the base's stalls; main has them too.
Reports with counting on: every counting-on run logged complete, clock-stamped
hle:blocks (12 to 15 per run, about every 5.0005 s, each starting at the previous one'send_ns). None logged "could not start".The Hookshot PR's earlier measurement (32:9, Hookshot
46780503, 8 pairs, no measurable cost with counting off) is in its description. It is not comparable with the 16:9 numbers here.Testing
Tested:
055a8fac, which isa4ce1b54merged with main5e072fc8:python -I -B tests/run_all.py --jobs 4(Windows, llvm-mingw 20261104): 521 passed, 5 failed, 36 skipped. All 5 fail the same way on a clean checkout of main5e072fc8on this PC (520 passed, 5 failed, 36 skipped there):tests/test_framegen_resources.py,tests/test_module_ids.py,tests/test_pad_layout.py,tests/test_oot_state_tap.py,tests/test_tested_configurations.py.host/hos_hostcall.c,host/hos_main.c,host/nvn_native.c) syntax-compile on Linux with gcc 15.2 (Ubuntu, WSL) and the build's Linux host flags (-I host/posix -D_GNU_SOURCE, x86-64-v3,A64_POLLING,A64_WRITE_TRACK), and on Windows with clang targeting MSVC. Not linked on Linux, and no Linux game build.a4ce1b54:tests/test_hle_stats.pypasses with llvm-mingw clang++ and with g++ (MSYS2 UCRT64);tests/test_cpu_report.pypasses;host/*.csyntax-compiles with llvm-mingw, except five that need third-party headers not installed here (dav1d, zstd, opus, ffmpeg). None of the five use the counters.839e85ffwith this PR merged.Not tested:
cpu_report.py --callsreported the estimates as unavailable instead of guessing, which is the intended behaviour. The estimate path is covered by the test fixtures only.Needs other testers:
HOS_HLE_STATS=1logs completehle:blocks about every 5 s while the game runs normally; unset, nohle:lines, expecting both (T-DWGM)Screenshots
Nothing on screen.
Risk and rollback
The
HLE(sym)macro is on every HLE entry's path. With counting off, the only change is one flag test, and neither measurement shows a cost (the Longshot A/B is diagnostic at 3 pairs). With it on, an extra thread wakes about every 5 s and every counted call does an atomic increment. Reverting the commits removes it all.Reviewer notes
Start with
hos.h(the macro) andhos_hostcall.c(the counters and the report thread). The calls and per-call columns already indocs/cpu-profile.mdare left for a regenerated profile. This PR only marks them.Issues
None.
Checklist
For everyone
type(scope): description).main.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above).third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.tests/that reads changed source updated in this PR, not weakened.🤖 Generated with Claude Code
Merged main
514591a3(525fcf60, no force-push). run_all: 463 passed, 4 failed, 67 skipped; all 4 also fail on main. First Longshot measurement: standard test at 16:9 on839e85ff, 3 complete pairs across two batches (21:05–21:48Z), main against this PR with counting off and on. Diagnostic: every effect is under 5% and changes sign between pairs. Rejected runs are listed under Measurements. Opened Linux test request T-DWGM. The per-call estimates are still untested on a real run.5e072fc8into feat/hle-call-countersMerged main
5e072fc8(055a8fac, no force-push, no conflicts in this PR's files). run_all: 521 passed, 5 failed, 36 skipped; all 5 fail the same way on clean main5e072fc8here. The changed host files now also syntax-compile on Linux (gcc 15.2, the build's Linux host flags); the full Linux build stays with T-DWGM. Taking it out of WIP.WIP: feat(profile): HOS_HLE_STATS=1 counts HLE, host-call and NVN export callsto feat(profile): HOS_HLE_STATS=1 counts HLE, host-call and NVN export callsIndependent Windows code/test review of
055a8fac63: no blocking findings.Reviewed the disabled HLE-entry path, counter publication/bounds/wrap, report-thread start failure, NVN dispatch counting and complete clock-window joins in cpu_report. Native test_hle_stats.py and test_cpu_report.py both pass (2 check groups, 0 failures, 0 skips). An additional tiny authored crit.py input confirms that baseline loses an outlined HLE body label while this head restores it and matches the imp-entry reference. Existing Windows profiler/parser checks pass; their Linux ELF execution case skips on this host. The isolated review worktree is clean.
This approval is based on that source and focused native evidence. I did not independently repeat the game measurements, off/on overhead comparison, full suite, Linux link/game run, TSAN or production reporter-thread behavior. The per-call estimates remain fixture-covered as described in the PR; the Linux game request T-DWGM remains outstanding. No game data or build artifacts were transferred.