Re: [RFC PATCH 4/8] RFC: tcg: probe the TB jump cache inline instead of calling a helper
Philippe Mathieu-Daudé <[email protected]>
| Newsgroups | org.nongnu.qemu-devel |
|---|---|
| Message-ID | <[email protected]> |
On 17/8/26 21:00, Matt Turner 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. > > 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/. > - The jump cache entry is read without qatomic_read(); entries are > invalidated concurrently by setting tb to NULL. > - Only wired up for alpha so far, and only for 64-bit guest PCs. > > Signed-off-by: Matt Turner <[email protected]> > --- > include/tcg/tcg-op-common.h | 2 + > target/alpha/translate.c | 6 ++- > tcg/tcg-op.c | 77 +++++++++++++++++++++++++++++++++++++ > 3 files changed, 83 insertions(+), 2 deletions(-) > > diff --git ./include/tcg/tcg-op-common.h ./include/tcg/tcg-op-common.h > index 1fe342db0d..52cd0d3eab 100644 > --- ./include/tcg/tcg-op-common.h > +++ ./include/tcg/tcg-op-common.h > @@ -84,6 +84,8 @@ void tcg_gen_goto_tb(unsigned idx); > * this op is equivalent to calling tcg_gen_exit_tb() with 0 as the argument. > */ > void tcg_gen_lookup_and_goto_ptr(void); > +void tcg_gen_lookup_and_goto_ptr_inline(TCGv_i64 pc, uint32_t flags, > + uint32_t cflags); > +/* > + * As tcg_gen_lookup_and_goto_ptr(), but probe the TB jump cache inline > + * instead of calling helper_lookup_tb_ptr() unconditionally. @pc must > + * hold the destination guest PC; @flags and @cflags are the values the > + * destination TB must have been translated with. Without looking at the body, this reads as these method arguments better fit: void tcg_gen_lookup_and_goto_ptr_inline(TCGv_i64 pc, const TranslationBlock *tb); > + */ > +void tcg_gen_lookup_and_goto_ptr_inline(TCGv_i64 pc, uint32_t flags, > + uint32_t cflags)