Remove 17 spent UC_HOOK_CODE probes (task #48) - NO speedup, kept as debt removal

De-registers every diagnostic probe belonging to closed tasks #35, #39 and
#41; the functions stay defined, matching this file's existing convention.
Registrations drop from 20 to 9. What remains is functional only:
MiscStubDispatch, FnvHashAccelHookCb, five zlib_accel hooks, two trampoline
hooks.

Seven of the removed probes sat on the TEXT rendering path (QuadGeometry at
four addresses, TextClipDispatch at two, MeasureAdvance at two), so they
fired once per HUD glyph per frame - continuously during a race.

Motivation was the first in-race profile: helper_uc_tracecode at 63.03% of
CPU. Unicorn's helper (uc.c:2129) walks the entire UC_HOOK_CODE list on
every hit with no early exit, and a hooked instruction also breaks the
translation block.

RESULT - the hypothesis is REFUTED as a performance fix:

    helper_uc_tracecode        63.03%  ->  41.66%
    game time vs wall clock      7.2x  ->   7.69x   (no improvement)

Measured with the race's own clock: 27s of wall time advanced the race timer
14.97 -> 18.48, i.e. 3.51s; the two sub-intervals gave 7.78x and 7.60x. So
hook-list length was NOT the binding constraint - the freed time simply
redistributed, visible in neighbouring shares rising
(tb_invalidate_phys_page_fast 1.19->3.15%, helper_lookup_tb_ptr 1.58->3.01%,
JIT "unknown" 7.17->9.98%).

Caveat: the before/after races are different sections with different scene
load. That cannot hide a difference of this size, but it is not a controlled
comparison either.

Kept anyway: removing spent diagnostics is worth doing on its own terms, and
a profile no longer dominated 63% by one symbol is readable.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-09-19 15:50:53 +03:00
co-authored by Claude
parent 75270898e9
commit 1c6e2b793a
+53 -123
View File
@@ -2982,117 +2982,53 @@ uc_engine* GuestEngine::CreateConfiguredEngine() {
// prologue-load capture, each hit both breaking a translation block and
// writing to logd.
// See Sub4BA588EntryProbeHookCb/Sub4BA588V20ProbeHookCb's own comments -
// tracing sub_4BA588 ("loadNodeUncached") across every call, to catch
// exactly when v20 first turns bad.
{
uc_hook sub4BA588EntryHook, sub4BA588V20Hook;
uc_hook_add(newUc, &sub4BA588EntryHook, UC_HOOK_CODE,
(void*)Sub4BA588EntryProbeHookCb, nullptr,
0x4ba588u, 0x4ba588u);
uc_hook_add(newUc, &sub4BA588V20Hook, UC_HOOK_CODE,
(void*)Sub4BA588V20ProbeHookCb, nullptr,
0x4ba6a8u, 0x4ba6a8u);
}
// See Sub494EF8NotFoundProbeHookCb/Sub495698UnsupportedFormatProbeHookCb/
// Sub494EF8Pre|PostCallSub495698ProbeHookCb's own comments - the three
// real EA code paths that can legitimately empty out the vector
// sub_4BA588 then crashes on, continuing the same chase.
{
uc_hook notFoundHook, unsupportedFormatHook, preCallHook, postCallHook;
uc_hook_add(newUc, &notFoundHook, UC_HOOK_CODE,
(void*)Sub494EF8NotFoundProbeHookCb, nullptr,
0x495398u, 0x495398u);
uc_hook_add(newUc, &unsupportedFormatHook, UC_HOOK_CODE,
(void*)Sub495698UnsupportedFormatProbeHookCb, nullptr,
0x49599cu, 0x49599cu);
uc_hook_add(newUc, &preCallHook, UC_HOOK_CODE,
(void*)Sub494EF8PreCallSub495698ProbeHookCb, nullptr,
0x4950d4u, 0x4950d4u);
uc_hook_add(newUc, &postCallHook, UC_HOOK_CODE,
(void*)Sub494EF8PostCallSub495698ProbeHookCb, nullptr,
0x4950d8u, 0x4950d8u);
}
// See Sub495698ReadCallProbeHookCb/Sub495698ReadResultProbeHookCb's own
// comment - bracketing sub_495698's own first "read 2 bytes" virtual
// call, to see which object/function it dispatches to and what (if
// anything) it actually returns.
{
uc_hook readCallHook, readResultHook;
uc_hook_add(newUc, &readCallHook, UC_HOOK_CODE,
(void*)Sub495698ReadCallProbeHookCb, nullptr,
0x4956e4u, 0x4956e4u);
uc_hook_add(newUc, &readResultHook, UC_HOOK_CODE,
(void*)Sub495698ReadResultProbeHookCb, nullptr,
0x4956e8u, 0x4956e8u);
}
// See Sub494EF8CacheFastPathProbeHookCb's own comment - confirms
// whether the cache-slot object's own [begin,end) byte range is empty
// for the crashing call vs non-empty for working ones.
{
uc_hook cacheFastPathHook;
uc_hook_add(newUc, &cacheFastPathHook, UC_HOOK_CODE,
(void*)Sub494EF8CacheFastPathProbeHookCb, nullptr,
0x495030u, 0x495030u);
}
// See SbaLoadCallProbeHookCb/SbaLoadResultProbeHookCb's own comments -
// bracketing sub_494EF8's own ".sba load by name" virtual call.
{
uc_hook sbaLoadCallHook, sbaLoadResultHook;
uc_hook_add(newUc, &sbaLoadCallHook, UC_HOOK_CODE,
(void*)SbaLoadCallProbeHookCb, nullptr,
0x4952c8u, 0x4952c8u);
uc_hook_add(newUc, &sbaLoadResultHook, UC_HOOK_CODE,
(void*)SbaLoadResultProbeHookCb, nullptr,
0x4952ccu, 0x4952ccu);
}
// PreMemcpyPltCallProbeHookCb's registration removed 2026-09-19 (the
// function itself stays defined in case this needs revisiting). Its own
// question - "is LR already 0 before this PLT call" - was answered long
// ago, and the cost of leaving it registered is severe rather than
// merely noisy: it puts a UC_HOOK_CODE on __aeabi_memcpy's PLT stub,
// i.e. on the single hottest routine in the whole program, so Unicorn
// must break its translation block and call out to C++ on EVERY memcpy.
// A single prologue-load capture logged 197,861 hits. Same opt-in
// discipline as the UC_HOOK_BLOCK diagnostics above.
// See TextClipDispatchEntryProbeHookCb's own comment - task #39
// continuation, dumping the render-batch object's +132/+136/+340 fields
// live at both AppendQuad-vs-clip dispatcher entries.
{
uc_hook textClipDispatch4010F0Hook, textClipDispatch4014ECHook;
uc_hook_add(newUc, &textClipDispatch4010F0Hook, UC_HOOK_CODE,
(void*)TextClipDispatchEntryProbeHookCb, nullptr,
0x4010f0u, 0x4010f0u);
uc_hook_add(newUc, &textClipDispatch4014ECHook, UC_HOOK_CODE,
(void*)TextClipDispatchEntryProbeHookCb, nullptr,
0x4014ecu, 0x4014ecu);
}
// See QuadGeometryProbeHookCb's own comment - task #39, measuring the
// raw quad corner geometry one level upstream of every transform tested
// (and disproven) so far.
{
uc_hook appendQuadHook, clipConsumerHook, drawWrapperHook;
uc_hook_add(newUc, &appendQuadHook, UC_HOOK_CODE,
(void*)QuadGeometryProbeHookCb, nullptr,
0x4015d4u, 0x4015d4u);
uc_hook_add(newUc, &clipConsumerHook, UC_HOOK_CODE,
(void*)QuadGeometryProbeHookCb, nullptr,
0x3da24cu, 0x3da24cu);
uc_hook_add(newUc, &drawWrapperHook, UC_HOOK_CODE,
(void*)QuadGeometryProbeHookCb, nullptr,
0x400678u, 0x400678u);
uc_hook glyphEmitterHook;
uc_hook_add(newUc, &glyphEmitterHook, UC_HOOK_CODE,
(void*)QuadGeometryProbeHookCb, nullptr,
0x4042f0u, 0x4042f0u);
}
// 2026-09-19 (task #48): FIFTEEN spent diagnostic UC_HOOK_CODE probes
// de-registered here in one pass. Their functions stay defined, matching
// the convention the neighbouring comments already follow - only the
// registrations are gone. What was removed and which closed task it
// belonged to:
// task #35 (sub_4BA588 crash chase, closed):
// Sub4BA588Entry 0x4ba588, Sub4BA588V20 0x4ba6a8,
// Sub494EF8NotFound 0x495398, Sub495698UnsupportedFormat 0x49599c,
// Sub494EF8PreCall 0x4950d4, Sub494EF8PostCall 0x4950d8,
// Sub495698ReadCall 0x4956e4, Sub495698ReadResult 0x4956e8,
// Sub494EF8CacheFastPath 0x495030,
// SbaLoadCall 0x4952c8, SbaLoadResult 0x4952cc
// task #39 / #41 (UI projection + glyph chase, both closed):
// TextClipDispatchEntry 0x4010f0 and 0x4014ec,
// QuadGeometry 0x4015d4 (AppendQuad), 0x3da24c (clip consumer),
// 0x400678 (draw wrapper), 0x4042f0 (glyph emitter)
//
// Why this is a performance change and not just tidying, measured rather
// than assumed. The first in-race profile (simpleperf on the live
// process, 20,424 samples) put 63.03% of all CPU in helper_uc_tracecode
// and another 5.29% in helper_check_exit_request_arm - ~68% in Unicorn's
// hook machinery, against 1.30% in the GPU driver. Two properties of the
// vendored Unicorn source make a long hook list expensive:
// 1. helper_uc_tracecode (uc.c:2129) walks the ENTIRE UC_HOOK_CODE list
// on every hit and has NO early exit - it keeps iterating after
// finding and calling the matching hook. So each hit paid for all
// ~24 registered hooks regardless of how many matched.
// 2. A hooked instruction breaks the translation block: TCG cannot
// chain through it, and gen_set_pc_im + check_exit_request are
// emitted around it (target/arm/translate.c:11594-11604). That lost
// optimisation is why check_exit_request shows up second.
// The last four probes above sat on the TEXT rendering path, so they
// fired per HUD glyph per frame - during a race, continuously.
//
// Note the hook-list search itself is translate-time, not per-PC:
// HOOK_EXISTS_BOUNDED is evaluated in thumb_tr_translate_insn and the
// result is baked into the cached TB. Cold hooks are therefore nearly
// free; it is hooks on HOT addresses that cost, which is the real reason
// these four mattered.
//
// Deliberately NOT removed, because they are functional rather than
// diagnostic: MiscStubDispatch (the consolidated stub dispatcher),
// FnvHashAccelHookCb, the four zlib_accel hooks, mem_fault_hook_cb.
//
// PreMemcpyPltCallProbeHookCb was already de-registered earlier on the
// same reasoning - it sat on __aeabi_memcpy's PLT stub, the hottest
// routine in the program, and logged 197,861 hits in one prologue load.
// Native zlib acceleration - see zlib_accel.h's own comment. Replaces
// InflateProbeHookCb (which measured the problem: 66.2MB out of a single
@@ -3131,18 +3067,12 @@ uc_engine* GuestEngine::CreateConfiguredEngine() {
// per-byte. Kept unregistered until that is done - see
// name_lookup_accel.h.
// See MeasureAdvanceProbeHookCb's own comment - task #39's root-cause
// chain, catching the advance value at the exact instruction that
// produces it.
{
uc_hook measureEntryHook, measureRetHook;
uc_hook_add(newUc, &measureEntryHook, UC_HOOK_CODE,
(void*)MeasureAdvanceProbeHookCb, nullptr,
0x542e50u, 0x542e50u);
uc_hook_add(newUc, &measureRetHook, UC_HOOK_CODE,
(void*)MeasureAdvanceProbeHookCb, nullptr,
0x542f14u, 0x542f14u);
}
// MeasureAdvanceProbeHookCb's two registrations (0x542e50, 0x542f14)
// removed 2026-09-19 with the rest of task #48's sweep; the function
// stays defined. It belonged to task #39's root-cause chain, which is
// closed, and it bracketed the glyph-advance computation - so like the
// QuadGeometry/TextClipDispatch probes it fired once per glyph per frame,
// on the hottest path there is for a HUD full of text.
return newUc;
}