On Thu, Sep 17, 2026 at 4:26 AM Richard Henderson
<[email protected]> wrote:
>
> From: Matt Turner <[email protected]>
Thanks for reworking this; routing the system-mode update through
update_guest_debug is much nicer than the version I posted.
Three optional nits inline. None of those need a respin on my account.
> curr_cflags() is called once per TB dispatch, from helper_lookup_tb_ptr()
> and from the cpu_exec() loop. It recomputes the same value every time:
>
> uint32_t cflags = cpu->tcg_cflags;
> if (unlikely(cpu_single_stepping(cpu))) { ... }
> else if (qatomic_read(&one_insn_per_tb)) { ... }
> else if (qemu_loglevel_mask(CPU_LOG_TB_NOCHAIN)) { ... }
>
> That is three loads and three branches on the hottest path in the
> interpreter, for state that changes only when gdb enables single-step,
> when one-insn-per-tb is toggled, or when the log mask changes.
>
> None of the three has to be sampled at dispatch time. Fold each into
> CPUState::tcg_cflags where it changes and curr_cflags() becomes a single
> load of a field that TB lookup has to read anyway.
>
> The derived bits -- CF_COUNT_MASK, CF_NO_GOTO_TB, CF_NO_GOTO_PTR and
> CF_SINGLE_STEP -- are never set by tcg_cflags_set(), so tcg_update_cflags()
> can recompute them in place without disturbing the rest, and conversely
> tcg_cflags_set() ORs in its bits without disturbing them.
>
> There are three places to call it:
>
> - tcg_exec_realizefn(), so that a CPU created after the command line has
> been parsed starts out with the right value. This covers user-only,
> where tcg_cpu_init_cflags() is not reached. linux-user's cpu_copy()
> copies tcg_cflags wholesale, so a cloned thread inherits it.
>
> - cpu_single_step(), which changes one CPU. gdb is the only caller that
> matters; in system mode it runs with the vCPUs stopped, and in user mode
> gdb_continue_partial() can reach a thread that is still running, because
> gdb_handlesig() stops only the thread that trapped. That is exactly the
> plain cross-thread store to another CPU's CPUState that
> cpu->singlestep_flags already was, read back by that CPU through
> cpu_single_stepping() in curr_cflags(). This patch changes which field
> carries it, not who writes it or how.
>
> - hmp_one_insn_per_tb() and hmp_log(), which change every CPU while the
> vCPUs are running, so the update is queued with async_run_on_cpu() and
> each CPU writes its own cflags from its own thread. The command line
> spellings of those two settings need nothing: they are parsed before
> any CPU is realized, so tcg_exec_realizefn() picks them up.
>
> 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, in a
> build configured with --enable-lto:
>
> before: 1,646,994,254,249 instructions
> after: 1,562,204,796,597 instructions -5.15%
>
> That workload issues 8.4 billion dispatches, so the per-call saving is
> small but the aggregate is not. The emulated compiler produces
> byte-identical output before and after.
>
> Wall clock does not move: 133.19s to 132.58s, a 0.46% difference against a
> run-to-run spread larger than that. The removed work is a few predictable
> loads and branches that the host executes largely in parallel with the
> surrounding dispatch, so this patch is worth taking for the instruction
> count and for what it enables, not for a time saving that can be measured
> on its own.
>
> v4: Update the cflags from the HMP handlers for 'log' and 'one-insn-per-tb'
> rather than from qemu_set_log_internal() and the accelerator property
> setter. Those are the paths that reach a running vCPU, and the monitor
> is the only thing that does. Suggested by Richard Henderson.
>
> v4: Queue the per-CPU update with async_run_on_cpu() rather than
> async_safe_run_on_cpu(). Halting the other vCPUs buys nothing: the
> queued work already runs on the owning CPU's own thread. Suggested by
> Alex Bennee, who also asked whether there are cross-vCPU updates of
> tcg_cflags at all. With this change the monitor path has none: the
> only remaining writer from another thread is cpu_single_step(), above,
> which is neither new nor made worse here.
>
> v4: Move the stub to accel/stubs/, which is where the other accelerator
> stubs live.
>
> Signed-off-by: Matt Turner <[email protected]>
> Reviewed-by: Richard Henderson <[email protected]>
> Signed-off-by: Richard Henderson <[email protected]>
> Message-ID: <[email protected]>
> ---
> accel/tcg/internal-common.h | 11 +++++++++--
> include/system/tcg.h | 12 ++++++++++++
> accel/tcg/cpu-exec-common.c | 33 ++++++++++++++++++++++++++++++---
> accel/tcg/cpu-exec.c | 3 +++
> accel/tcg/tcg-accel-ops.c | 2 ++
> cpu-target.c | 5 ++++-
> monitor/hmp-cmds.c | 7 +++++++
> system/runstate-hmp-cmds.c | 18 ++++++++++++------
> 8 files changed, 79 insertions(+), 12 deletions(-)
>
> diff --git a/accel/tcg/internal-common.h b/accel/tcg/internal-common.h
> index 9e7be2d78df..853d1b51eeb 100644
> --- a/accel/tcg/internal-common.h
> +++ b/accel/tcg/internal-common.h
> @@ -69,8 +69,15 @@ void tlb_destroy(CPUState *cpu);
> bool tcg_exec_realizefn(CPUState *cpu, Error **errp);
> void tcg_exec_unrealizefn(CPUState *cpu);
>
> -/* current cflags for hashing/comparison */
> -uint32_t curr_cflags(CPUState *cpu);
> +/*
> + * Current cflags for hashing/comparison. Everything that feeds into the
> + * value is folded into CPUState::tcg_cflags when it changes, by
> + * tcg_update_cflags(), so that TB dispatch only has to load it.
> + */
> +static inline uint32_t curr_cflags(CPUState *cpu)
> +{
> + return cpu->tcg_cflags;
> +}
>
> void tb_check_watchpoint(CPUState *cpu, uintptr_t retaddr);
>
> diff --git a/include/system/tcg.h b/include/system/tcg.h
> index 7622dcea302..2c2dbc753b3 100644
> --- a/include/system/tcg.h
> +++ b/include/system/tcg.h
> @@ -17,6 +17,18 @@ extern bool tcg_allowed;
> #define tcg_enabled() 0
> #endif
>
> +/*
> + * Recompute the parts of CPUState::tcg_cflags that TB dispatch consumes but
> + * tcg_cflags_set() does not provide: gdb single-step, one-insn-per-tb and
> + * the CPU_LOG_TB_NOCHAIN log flag. Call whenever one of those changes.
> + *
> + * tcg_update_cflags() updates one CPU and must be called from that CPU's
> + * thread, or with it stopped. tcg_update_all_cflags() updates every CPU
> + * and is safe to call from the monitor while the vCPUs run.
> + */
> +void tcg_update_cflags(CPUState *cpu);
> +void tcg_update_all_cflags(void);
> +
> /**
> * qemu_tcg_mttcg_enabled:
> * Check whether we are running MultiThread TCG or not.
> diff --git a/accel/tcg/cpu-exec-common.c b/accel/tcg/cpu-exec-common.c
> index 44e84344f3b..9f3517f36b6 100644
> --- a/accel/tcg/cpu-exec-common.c
> +++ b/accel/tcg/cpu-exec-common.c
> @@ -36,9 +36,16 @@ void tcg_cflags_set(CPUState *cpu, uint32_t flags)
> cpu->tcg_cflags |= flags;
> }
>
> -uint32_t curr_cflags(CPUState *cpu)
> +/*
> + * The bits of CPUState::tcg_cflags that tcg_cflags_set() never sets, because
> + * they are derived from gdb single-step, one-insn-per-tb and -d nochain.
> + */
tcg_cflags_set() could assert(!(flags & CF_DERIVED)).
> +#define CF_DERIVED (CF_COUNT_MASK | CF_NO_GOTO_TB | CF_NO_GOTO_PTR | \
> + CF_SINGLE_STEP)
> +
> +void tcg_update_cflags(CPUState *cpu)
> {
> - uint32_t cflags = cpu->tcg_cflags;
> + uint32_t cflags = cpu->tcg_cflags & ~CF_DERIVED;
>
> /*
> * Record gdb single-step. We should be exiting the TB by raising
> @@ -55,7 +62,27 @@ uint32_t curr_cflags(CPUState *cpu)
> cflags |= CF_NO_GOTO_TB;
> }
>
> - return cflags;
> + cpu->tcg_cflags = cflags;
> +}
> +
> +static void tcg_update_cflags_work(CPUState *cpu, run_on_cpu_data data)
> +{
> + tcg_update_cflags(cpu);
> +}
> +
> +void tcg_update_all_cflags(void)
> +{
> + CPUState *cpu;
> +
Could assert(bql_locked()) here, since CPU_FOREACH plus
async_run_on_cpu() requires it.
> + /*
> + * one-insn-per-tb and -d nochain can both be changed from the monitor
> + * while the vCPUs are running. Queue the update onto each CPU rather
> + * than writing tcg_cflags from here, so that the field is only ever
> + * written by the CPU that owns it.
> + */
> + CPU_FOREACH(cpu) {
> + async_run_on_cpu(cpu, tcg_update_cflags_work, RUN_ON_CPU_NULL);
> + }
> }
>
> /* exit the current TB, but without causing any exception to be raised */
> diff --git a/accel/tcg/cpu-exec.c b/accel/tcg/cpu-exec.c
> index 46b723cb734..5226cfb7650 100644
> --- a/accel/tcg/cpu-exec.c
> +++ b/accel/tcg/cpu-exec.c
> @@ -1071,6 +1071,9 @@ bool tcg_exec_realizefn(CPUState *cpu, Error **errp)
> tcg_target_initialized = true;
> }
>
> + /* Pick up one-insn-per-tb and -d nochain from the command line. */
> + tcg_update_cflags(cpu);
> +
> cpu->tb_jmp_cache = g_new0(CPUJumpCache, 1);
> tlb_init(cpu);
> #ifndef CONFIG_USER_ONLY
> diff --git a/accel/tcg/tcg-accel-ops.c b/accel/tcg/tcg-accel-ops.c
> index 9c3d2214162..2e50df20c18 100644
> --- a/accel/tcg/tcg-accel-ops.c
> +++ b/accel/tcg/tcg-accel-ops.c
> @@ -48,6 +48,7 @@
> #include "tcg-accel-ops-mttcg.h"
> #include "tcg-accel-ops-rr.h"
> #include "tcg-accel-ops-icount.h"
> +#include "internal-common.h"
tcg_update_cflags() is declared in "system/tcg.h", which this file
already includes. The new include looks unnecessary.
>
> /* common functionality among all TCG variants */
>
> @@ -222,6 +223,7 @@ static void tcg_accel_ops_init(AccelClass *ac)
> ops->insert_gdbstub_breakpoint = tcg_insert_gdbstub_breakpoint;
> ops->remove_gdbstub_breakpoint = tcg_remove_gdbstub_breakpoint;
> ops->remove_all_gdbstub_breakpoints = tcg_remove_all_gdbstub_breakpoints;
> + ops->update_guest_debug = tcg_update_cflags;
> }
>
> static void tcg_accel_ops_class_init(ObjectClass *oc, const void *data)
> diff --git a/cpu-target.c b/cpu-target.c
> index 4783845c9bf..9bd1605e831 100644
> --- a/cpu-target.c
> +++ b/cpu-target.c
> @@ -24,6 +24,7 @@
> #include "exec/replay-core.h"
> #include "exec/log.h"
> #include "hw/core/cpu.h"
> +#include "system/tcg.h"
> #include "trace/trace-root.h"
>
> /* enable or disable single step mode. EXCP_DEBUG is returned by the
> @@ -35,7 +36,9 @@ void cpu_single_step(CPUState *cpu, unsigned flags)
> cpu->singlestep_flags, flags);
> cpu->singlestep_flags = flags;
>
> -#if !defined(CONFIG_USER_ONLY)
> +#ifdef CONFIG_USER_ONLY
> + tcg_update_cflags(cpu);
> +#else
> const AccelOpsClass *ops = cpus_get_accel();
> if (ops->update_guest_debug) {
> ops->update_guest_debug(cpu);
> diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
> index 91701ddf331..9b1b94f8ea9 100644
> --- a/monitor/hmp-cmds.c
> +++ b/monitor/hmp-cmds.c
> @@ -40,6 +40,7 @@
> #include "system/hw_accel.h"
> #include "system/memory.h"
> #include "system/system.h"
> +#include "system/tcg.h"
> #include "disas/disas.h"
>
> /* Please update hmp-commands.hx when adding or changing commands */
> @@ -340,6 +341,12 @@ void hmp_log(MonitorHMP *hmp, const QDict *qdict)
>
> if (!qemu_set_log(mask, &err)) {
> error_report_err(err);
> + return;
> + }
> +
> + /* CPU_LOG_TB_NOCHAIN feeds into the per-CPU cflags. */
> + if (tcg_enabled()) {
> + tcg_update_all_cflags();
> }
> }
>
> diff --git a/system/runstate-hmp-cmds.c b/system/runstate-hmp-cmds.c
> index ad70b53f8ab..eb34d14d062 100644
> --- a/system/runstate-hmp-cmds.c
> +++ b/system/runstate-hmp-cmds.c
> @@ -22,6 +22,7 @@
> #include "qapi/qapi-commands-run-state.h"
> #include "qobject/qdict.h"
> #include "qemu/accel.h"
> +#include "system/tcg.h"
>
> void hmp_info_status(MonitorHMP *hmp, const QDict *qdict)
> {
> @@ -43,16 +44,17 @@ void hmp_info_status(MonitorHMP *hmp, const QDict *qdict)
>
> void hmp_one_insn_per_tb(MonitorHMP *hmp, const QDict *qdict)
> {
> - const char *option = qdict_get_try_str(qdict, "option");
> - AccelState *accel = current_accel();
> + const char *option;
> + AccelState *accel;
> bool newval;
>
> - if (!object_property_find(OBJECT(accel), "one-insn-per-tb")) {
> - monitor_hmp_printf(hmp,
> - "This accelerator does not support setting
> one-insn-per-tb\n");
> + if (!tcg_enabled()) {
> + monitor_hmp_printf(hmp, "This accelerator does not support "
> + "setting one-insn-per-tb\n");
> return;
> }
>
> + option = qdict_get_try_str(qdict, "option");
> if (!option || !strcmp(option, "on")) {
> newval = true;
> } else if (!strcmp(option, "off")) {
> @@ -61,9 +63,13 @@ void hmp_one_insn_per_tb(MonitorHMP *hmp, const QDict
> *qdict)
> monitor_hmp_printf(hmp, "unexpected option %s\n", option);
> return;
> }
> - /* If the property exists then setting it can never fail */
> +
> + accel = current_accel();
> object_property_set_bool(OBJECT(accel), "one-insn-per-tb",
> newval, &error_abort);
> +
> + /* one-insn-per-tb feeds into the per-CPU cflags. */
> + tcg_update_all_cflags();
> }
>
> void hmp_watchdog_action(MonitorHMP *hmp, const QDict *qdict)
> --
> 2.53.0
>