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);