Commit 5be0222e0c for qemu.org

commit 5be0222e0c889ed52dea929d65f8c013f2a6d433
Author: Matt Turner <mattst88@gmail.com>
Date:   Mon Aug 31 23:48:02 2026 -0400

    accel/tcg: Skip the can_do_io stores in user-only builds

    Every translation block stores to cpu->neg.can_do_io twice: false before
    the first instruction, true before the last one. Nothing reads it in a
    user-only build. There is no memory-mapped I/O in linux-user, and every
    reader is in system_ss: cputlb.c, watchpoint.c, icount-common.c and
    tcg-accel-ops-icount.c.

    Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
    Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
    Signed-off-by: Matt Turner <mattst88@gmail.com>
    Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
    Message-ID: <20260901034808.3524945-4-mattst88@gmail.com>

diff --git a/accel/tcg/translator.c b/accel/tcg/translator.c
index 57daded60f..e994300694 100644
--- a/accel/tcg/translator.c
+++ b/accel/tcg/translator.c
@@ -21,12 +21,14 @@
 #include "disas/disas.h"
 #include "tb-internal.h"

+#ifndef CONFIG_USER_ONLY
 static void set_can_do_io(DisasContextBase *db, bool val)
 {
     QEMU_BUILD_BUG_ON(sizeof_field(CPUState, neg.can_do_io) != 1);
     tcg_gen_st8_i32(tcg_constant_i32(val), tcg_env,
                     offsetof(CPUState, neg.can_do_io) - sizeof(CPUState));
 }
+#endif

 bool translator_io_start(DisasContextBase *db)
 {
@@ -125,8 +127,10 @@ void translator_loop(CPUState *cpu, TranslationBlock *tb, int *max_insns,
 {
     uint32_t cflags = tb_cflags(tb);
     TCGOp *icount_start_insn;
-    TCGOp *first_insn_start = NULL;
     bool plugin_enabled;
+#ifndef CONFIG_USER_ONLY
+    TCGOp *first_insn_start = NULL;
+#endif

     tcg_ctx->addr_type = addr_type;

@@ -160,9 +164,11 @@ void translator_loop(CPUState *cpu, TranslationBlock *tb, int *max_insns,
         *max_insns = ++db->num_insns;
         ops->insn_start(db, cpu);
         db->insn_start = tcg_last_op();
+#ifndef CONFIG_USER_ONLY
         if (first_insn_start == NULL) {
             first_insn_start = db->insn_start;
         }
+#endif
         tcg_debug_assert(db->is_jmp == DISAS_NEXT);  /* no early exit */

         if (plugin_enabled) {
@@ -207,9 +213,14 @@ void translator_loop(CPUState *cpu, TranslationBlock *tb, int *max_insns,
     ops->tb_stop(db, cpu);
     gen_tb_end(tb, cflags, icount_start_insn, db->num_insns);

+#ifndef CONFIG_USER_ONLY
     /*
      * Manage can_do_io for the translation block: set to false before
      * the first insn and set to true before the last insn.
+     *
+     * Nothing reads can_do_io in user-only builds.  There is no MMIO
+     * there, and every reader (cputlb.c, watchpoint.c, icount) is in
+     * system_ss, so skip the two stores per TB entirely.
      */
     if (db->num_insns == 1) {
         tcg_debug_assert(first_insn_start == db->insn_start);
@@ -221,6 +232,7 @@ void translator_loop(CPUState *cpu, TranslationBlock *tb, int *max_insns,
     tcg_ctx->emit_before_op = db->insn_start;
     set_can_do_io(db, true);
     tcg_ctx->emit_before_op = NULL;
+#endif

     /* May be used by disas_log or plugin callbacks. */
     tb->size = db->pc_next - db->pc_first;