On Thu, Sep 17, 2026 at 5:33 AM Richard Henderson
<[email protected]> wrote:
>
> From: Matt Turner <[email protected]>
>
> At translation time the caller often already has the destination PC
> in a temp and knows the flags, cflags and cs_base any destination it
> may reach has to match because they are the same as the ones as the
> current block being generated.  But the promise that the flags are
> known (or unchanged) is subtle.
>
> So add new entry points with new semantics and --enable-debug-tcg
> checks against get_tb_cpu_state() at run time.
>
> Signed-off-by: Matt Turner <[email protected]>
> [rth: Split out target changes; add jc3 variant; expand via tcg-op-def.h.inc]
> Signed-off-by: Richard Henderson <[email protected]>
> ---
>  accel/tcg/tcg-runtime.h      |  4 +++
>  include/tcg/tcg-op-common.h  | 29 +++++++++++++++++++
>  include/tcg/tcg-op.h         |  4 +++
>  include/tcg/tcg-op-def.h.inc |  2 ++
>  accel/tcg/cpu-exec.c         | 27 ++++++++++++++++++
>  tcg/tcg-op.c                 | 55 ++++++++++++++++++++++++++++++++----
>  6 files changed, 115 insertions(+), 6 deletions(-)
>
> diff --git a/accel/tcg/tcg-runtime.h b/accel/tcg/tcg-runtime.h
> index 0dda2079fd0..098c2235b5f 100644
> --- a/accel/tcg/tcg-runtime.h
> +++ b/accel/tcg/tcg-runtime.h
> @@ -9,6 +9,10 @@ DEF_HELPER_FLAGS_1(ctpop_i64, TCG_CALL_NO_RWG_SE, i64, i64)
>
>  DEF_HELPER_FLAGS_1(lookup_tb_ptr, TCG_CALL_NO_WG_SE, cptr, env)
>
> +#ifdef CONFIG_DEBUG_TCG
> +DEF_HELPER_FLAGS_4(goto_jc_check, TCG_CALL_NO_WG, void, env, i64, i64, i32)
> +#endif
> +
>  DEF_HELPER_FLAGS_1(exit_atomic, TCG_CALL_NO_WG, noreturn, env)
>
>  #ifndef IN_HELPER_PROTO
> diff --git a/include/tcg/tcg-op-common.h b/include/tcg/tcg-op-common.h
> index 2bcf737aa56..5102f9bb4dd 100644
> --- a/include/tcg/tcg-op-common.h
> +++ b/include/tcg/tcg-op-common.h
> @@ -85,6 +85,35 @@ void tcg_gen_goto_tb(unsigned idx);
>   */
>  void tcg_gen_lookup_and_goto_ptr(void);
>
> +/**
> + * tcg_gen_goto_jc3_i32() - dispatch to the destination TB via the jump cache
> + * tcg_gen_goto_jc3_i64() - dispatch to the destination TB via the jump cache
> + * @pc: temp holding the destination guest PC
> + * @cs_base: temp holding the destination guest cs_base
> + * @flags: the destination guest flags
> + *
> + * The contract is that the values provided inline are exactly the
> + * cpu state that would be returned by TCGCPUOps::get_tb_cpu_state.
> + * --enable-debug-tcg checks the contract at runtime.
> + *
> + * Using those values, the lookup may be done inline.
> + *
> + * Actually declared and defined via tcg-op-def.h.inc.
> + */
> +
> +/**
> + * tcg_gen_goto_jc_i32() - dispatch to the destination TB via the jump cache
> + * tcg_gen_goto_jc_i64() - dispatch to the destination TB via the jump cache
> + * @pc: temp holding the destination guest PC
> + *
> + * Similar, but cs_base and flags are taken from the current TB.
> + *
> + * Thus the contract is that no cpu state changes have occured

typo: occurred

> + * that affect the translation block flags.
> + *
> + * Actually declared and defined via tcg-op-def.h.inc.
> + */
> +
>  void tcg_gen_plugin_cb(unsigned from);
>  QEMU_ARG_NONNULL void tcg_gen_plugin_mem_cb(TCGv_i64 addr, unsigned meminfo);
>
> diff --git a/include/tcg/tcg-op.h b/include/tcg/tcg-op.h
> index a7d001c959c..02732652b8f 100644
> --- a/include/tcg/tcg-op.h
> +++ b/include/tcg/tcg-op.h
> @@ -175,6 +175,8 @@ typedef TCGv_i64 TCGv;
>  #define tcg_gen_atomic_umax_fetch_tl tcg_gen_atomic_umax_fetch_i64
>  #define tcg_gen_dup_tl_vec  tcg_gen_dup_i64_vec
>  #define tcg_gen_dup_tl tcg_gen_dup_i64
> +#define tcg_gen_goto_jc_tl tcg_gen_goto_jc_i64
> +#define tcg_gen_goto_jc3_tl tcg_gen_goto_jc3_i64
>  #define dup_const_tl dup_const
>  #else
>  #define tcg_gen_movi_tl tcg_gen_movi_i32
> @@ -299,6 +301,8 @@ typedef TCGv_i64 TCGv;
>  #define tcg_gen_atomic_umax_fetch_tl tcg_gen_atomic_umax_fetch_i32
>  #define tcg_gen_dup_tl_vec  tcg_gen_dup_i32_vec
>  #define tcg_gen_dup_tl tcg_gen_dup_i32
> +#define tcg_gen_goto_jc_tl tcg_gen_goto_jc_i32
> +#define tcg_gen_goto_jc3_tl tcg_gen_goto_jc3_i32
>
>  #define dup_const_tl(VECE, C)                                      \
>      (__builtin_constant_p(VECE)                                    \
> diff --git a/include/tcg/tcg-op-def.h.inc b/include/tcg/tcg-op-def.h.inc
> index b60edca6e42..97024fcb222 100644
> --- a/include/tcg/tcg-op-def.h.inc
> +++ b/include/tcg/tcg-op-def.h.inc
> @@ -31,6 +31,8 @@ DEF2(ext16u, TCGV, TCGV)
>  DEF4(extract, TCGV, TCGV, unsigned, unsigned)
>  DEF4(extract2, TCGV, TCGV, TCGV, unsigned)
>  DEF3(eqv, TCGV, TCGV, TCGV)
> +DEF1(goto_jc, TCGV)
> +DEF3(goto_jc3, TCGV, TCGv_i64, unsigned)
>  DEF3(ld, TCGV, TCGv_ptr, tcg_target_long)
>  DEF3(ld8s, TCGV, TCGv_ptr, tcg_target_long)
>  DEF3(ld8u, TCGV, TCGv_ptr, tcg_target_long)
> diff --git a/accel/tcg/cpu-exec.c b/accel/tcg/cpu-exec.c
> index 5226cfb7650..1493fe0c963 100644
> --- a/accel/tcg/cpu-exec.c
> +++ b/accel/tcg/cpu-exec.c
> @@ -407,6 +407,33 @@ const void *HELPER(lookup_tb_ptr)(CPUArchState *env)
>      return tb->tc.ptr;
>  }
>
> +#ifdef CONFIG_DEBUG_TCG
> +/**
> + * helper_goto_jc_check: check the contract of tcg_gen_goto_jc_*()
> + * @env: current cpu state
> + * @pc: the destination pc the caller passed at translation time
> + * @cs_base: the cs_base the dispatching block was translated with
> + * @flags: the flags the dispatching block was translated with
> + *
> + * A goto_jc looks the destination up on the caller's @pc with the flags and
> + * cs_base of the block doing the dispatching, so all three have to be what
> + * get_tb_cpu_state() reports by the time the dispatch runs.  That is a
> + * property of the translator, not of the generated code, so check it here
> + * rather than leaving a target that gets it wrong to be debugged as a block
> + * running with someone else's flags.
> + */
> +void HELPER(goto_jc_check)(CPUArchState *env, uint64_t pc, uint64_t cs_base,
> +                           uint32_t flags)
> +{
> +    CPUState *cpu = env_cpu(env);
> +    TCGTBCPUState s = cpu->cc->tcg_ops->get_tb_cpu_state(cpu);
> +
> +    assert(s.pc == pc);
> +    assert(s.flags == flags);
> +    assert(s.cs_base == cs_base);
> +}
> +#endif
> +
>  /* Return the current PC from CPU, which may be cached in TB. */
>  static vaddr log_pc(CPUState *cpu, const TranslationBlock *tb)
>  {
> diff --git a/tcg/tcg-op.c b/tcg/tcg-op.c
> index d271d83a7d2..09d9a7963ae 100644
> --- a/tcg/tcg-op.c
> +++ b/tcg/tcg-op.c
> @@ -277,6 +277,7 @@ void tcg_gen_plugin_mem_cb(TCGv_i64 addr, unsigned 
> meminfo)
>  #define DEF6(NAME, T1, T2, T3, T4, T5, T6) \
>      QEMU_ARG_NONNULL static void glue(gen_,NAME)(TCGType, T1, T2, T3, T4, 
> T5, T6);
>
> +#define TCGv_i64  TCGTemp *
>  #define TCGv_ptr  TCGTemp *
>  #define TCGV      TCGTemp *
>  #define TINT      int64_t
> @@ -286,6 +287,7 @@ void tcg_gen_plugin_mem_cb(TCGv_i64 addr, unsigned 
> meminfo)
>  #undef TINT
>  #undef TCGV
>  #undef TCGv_ptr
> +#undef TCGv_i64
>
>  #undef DEF1
>  #undef DEF2
> @@ -1940,20 +1942,61 @@ void tcg_gen_goto_tb(unsigned idx)
>      tcg_gen_op1i(INDEX_op_goto_tb, 0, idx);
>  }
>
> +static void gen_lookup_tb_ptr_and_goto(void)
> +{
> +    TCGv_ptr ptr = tcg_temp_ebb_new_ptr();
> +
> +    gen_helper_lookup_tb_ptr(ptr, tcg_env);
> +    tcg_gen_op1i(INDEX_op_goto_ptr, TCG_TYPE_PTR, tcgv_ptr_arg(ptr));
> +    tcg_temp_free_ptr(ptr);
> +}
> +
>  void tcg_gen_lookup_and_goto_ptr(void)
>  {
> -    TCGv_ptr ptr;
> -
>      if (tcg_ctx->gen_tb->cflags & CF_NO_GOTO_PTR) {
>          tcg_gen_exit_tb(NULL, 0);
>          return;
>      }
>
>      plugin_gen_disable_mem_helpers();
> -    ptr = tcg_temp_ebb_new_ptr();
> -    gen_helper_lookup_tb_ptr(ptr, tcg_env);
> -    tcg_gen_op1i(INDEX_op_goto_ptr, TCG_TYPE_PTR, tcgv_ptr_arg(ptr));
> -    tcg_temp_free_ptr(ptr);
> +    gen_lookup_tb_ptr_and_goto();
> +}
> +
> +static void gen_goto_jc3(TCGType type, TCGTemp *pc,
> +                         TCGTemp *cs_base, unsigned flags)
> +{
> +    plugin_gen_disable_mem_helpers();

[...]

> +    gen_lookup_tb_ptr_and_goto();
> +}

This drops the CF_NO_GOTO_PTR check that tcg_gen_lookup_and_goto_ptr()
does, so every call site converted to goto_jc keeps chaining when the
cflags say not to.  Patch 9 doesn't restore it either: CF_NO_GOTO_PTR
there only selects the helper over the inline probe, and the helper
looks up with curr_cflags() and chains.

CF_NO_GOTO_PTR is set for gdb single-step and in
cpu_exec_step_atomic(), so single-step runs away on all five converted
targets.  With qemu-alpha -g and a raw RSP client sending 's',
counting "Trace" lines
in -d exec:

  6c9d78bbc4:              1 TB per step, every step
  + this series:           step 271:  5835 TBs
                           step 277:  1737 TBs
                           step 281: 40723 TBs, pc advanced 92 bytes

Fixed by mirroring tcg_gen_lookup_and_goto_ptr():

    static void gen_goto_jc3(TCGType type, TCGTemp *pc,
                             TCGTemp *cs_base, unsigned flags)
    {
        if (tcg_ctx->gen_tb->cflags & CF_NO_GOTO_PTR) {
            tcg_gen_exit_tb(NULL, 0);
            return;
        }

        plugin_gen_disable_mem_helpers();

Note this also makes the comment in target/ppc/translate.c wrong in
gen_lookup_and_goto_ptr() and ppc_tr_tb_stop():

        /*
         * tcg_gen_lookup_and_goto_ptr will exit the TB if
         * CF_NO_GOTO_PTR is set. Count insns now.
         */
        if (ctx->base.tb->flags & CF_NO_GOTO_PTR) {
            pmu_count_insns(ctx);
        }

        tcg_gen_goto_jc_tl(cpu_nip);

Reply via email to