Re: [PATCH 4/8] RFC: tcg: probe the TB jump cache inline instead of calling a helper
Matt Turner <[email protected]>
| Newsgroups | gmane.comp.emulators.qemu |
|---|---|
| Message-ID | <CAEdQ38HOp5ze-6dU9NpVePk=F2w6vK4pNoabnpwHO778HY3BDQ@mail.gmail.com> |
On Tue, Aug 18, 2026 at 6:27 PM Mohamed Mediouni <[email protected]> wrote: > > > > > On 18. Aug 2026, at 19:42, Matt Turner <[email protected]> wrote: > > > > Every indirect branch that cannot use goto_tb ends in > > tcg_gen_lookup_and_goto_ptr(), which calls helper_lookup_tb_ptr(). For an > > emulated compiler that is 8.4 billion helper calls in a single translation > > unit: 24.6% of all TB exits take this path, because jsr/ret/jmp have a > > register destination and because goto_tb is restricted to same-page > > targets. > > Hi, > > In your testing set, how many of those indirect branches were returns? Instrumenting things, here is the breakdown: count of jsr-family of all indirect dispatches RET 3408529228 46.7% 40.5% JSR 3377155399 46.3% 40.1% JMP 514369861 7.1% 6.1% JSR_COROUTINE 0 0% 0% jsr-family total 7300054488 86.7% Total indirect dispatches were 8422264155 (8.4 billion). The other 13.3% are non-jsr `DISAS_PC_UPDATED` exits. So returns are just under half, and calls and returns together are 93% of the indirect branches. The workload is almost entirely call/return pairs, which supports the return-stack idea rather than against it. But at the series tip the inline probe hit rate is 99.60% (8388942817 fast / 33321338 slow), so a return-stack would be competing with an already-cheap path. > I’ve been (very slowly...) hacking on something adjacent for being able > to leverage hardware return stacks, although it showed an exceedingly large > amount of issues - especially around the maintaining coherency edge cases... Hah, I started by going down the hardware return stacks route initially as well! > Your patch gets a lot of the way there on the perf side without a number > of the issues associated with that. > > > > > The helper itself is already tight, but each call pays for a call frame, > > the can_do_io store, the get_tb_cpu_state() indirect call through > > TCGCPUOps, curr_cflags(), and a breakpoint check, before it gets to the > > jump cache probe that almost always hits (95.8% for this workload). > > > > Emit the probe inline instead. The destination PC is already in a TCG > > temp, and the flags and cflags the destination must match are constants at > > translation time, so the fast path is a hash, three guarded loads and a > > goto_ptr. Only a miss calls the helper, which still owns filling the cache. > > > > Two details matter for the generated code. The flags and cflags guards are > > folded into a single aligned 64-bit load and compare, since the fields are > > adjacent. And each path emits its own goto_ptr rather than branching to a > > shared one: a temp live across the label is spilled and reloaded on every > > dispatch, which cost 6.3% on its own. > > > > Measured with qemu-alpha running an emulated alpha gcc 16.2.0 compiling > > the SQLite 3.45.1 amalgamation (255k lines, -O2) on an x86-64 host, LTO > > build, on top of the preceding three patches: > > > > before: 1,402,816,253,499 instructions > > after: 890,713,633,237 instructions -36.51% > > > > before: 115.75s wall clock > > after: 84.44s wall clock -27.05% > > > > The gap between the two is the point at which this stops being a > > straight-line win: the helper call was highly predictable work that the > > host pipelined well, so removing it retires far fewer instructions than it > > saves time. IPC falls from 2.48 to 2.15 across this patch for that reason. > > > > Despite emitting more code, this also reduces instruction cache pressure, > > because a dispatch no longer jumps into qemu's .text and evicts translated > > code: > > > > before: 11,476,318,964 L1-icache-load-misses > > after: 6,990,186,701 L1-icache-load-misses -39.1% > > > > The mechanism is visible directly in a profile: helper_lookup_tb_ptr() > > falls from 30.97% of samples to 0.42%, and qemu's own .text falls from > > 38.9% to 5.4%, with the balance moving into generated code. > > > > Combined with the three preceding patches, against an unmodified LTO > > build, 1,647,901,588,726 instructions fall to 890,713,633,237, or -45.95%. > > The emulated compiler produces byte-identical output throughout. > > > > Open issues, hence RFC: > > > > - The flags/cflags guards use the *current* TB's values as constants. That > > assumes the CPU flags feeding get_tb_cpu_state() cannot change within a > > TB, and that curr_cflags() cannot change under a running TB (gdb > > attaching to enable single-step would). Both need to be established or > > the values need to be loaded at runtime. > > - tcg/tcg-op.c has no business including accel/tcg/tb-jmp-cache.h or > > knowing the CPUJumpCache layout. The probe likely belongs in accel/tcg > > with a small emit helper exported from tcg/. > > I don’t think that one is a problem. I think you're right. I've updated the commit message.