On Thu, Sep 10, 2026 at 10:39:40PM +0800, TANG Tiancheng wrote:
> Raw host ticks keep advancing while the VM is stopped. Use
> cpus_get_elapsed_ticks() for cycles and non-icount instruction counting,
> and retain icount_get_raw() for instructions under icount. Document that
> cpu_get_ticks() returns its stored value while VM ticks are disabled.
>
> Under icount, cycles are already virtual nanoseconds. Convert only raw
> instruction counts when scheduling overflow, avoiding a second scaling
> of cycle distances. Add a cycle-overflow regression with icount shift=3.
>
> Link: https://lists.nongnu.org/archive/html/qemu-devel/2025-10/msg00668.html
> Signed-off-by: TANG Tiancheng <[email protected]>
> Reviewed-by: Daniel Henrique Barboza <[email protected]>
Reviewed-by: Chao Liu <[email protected]>
Thanks,
Chao
> ---
> system/cpu-timers.c | 4 +-
> system/cpus.c | 6 +--
> target/riscv/tcg/csr.c | 8 +---
> target/riscv/tcg/pmu.c | 52 +++++++++++++++-----------
> target/riscv/tcg/pmu.h | 1 +
> tests/tcg/riscv64/sscofpmf-cycle-overflow.S | 58
> +++++++++++++++++++++++++++++
> tests/tcg/riscv64/system/meson.build | 7 ++++
> 7 files changed, 103 insertions(+), 33 deletions(-)
>
> diff --git a/system/cpu-timers.c b/system/cpu-timers.c
> index
> 9919b46230f1caf8be1a1b6ef00acd94678437ce..0415636aff3f774f0de61fdac3969f5b45ae6993
> 100644
> --- a/system/cpu-timers.c
> +++ b/system/cpu-timers.c
> @@ -118,8 +118,8 @@ void cpu_enable_ticks(void)
> }
>
> /*
> - * disable cpu_get_ticks() : the clock is stopped. You must not call
> - * cpu_get_ticks() after that.
> + * Freeze VM ticks. While disabled, cpu_get_ticks() returns the stored tick
> + * value instead of sampling the advancing host counter.
> * Caller must hold BQL which serves as mutex for vm_clock_seqlock.
> */
> void cpu_disable_ticks(void)
> diff --git a/system/cpus.c b/system/cpus.c
> index
> e11a5aab6a696962d94ad30ac38acd8867b1bf88..f61639ae78277fd90cbddb0b9f75b134e3cc1a17
> 100644
> --- a/system/cpus.c
> +++ b/system/cpus.c
> @@ -237,9 +237,9 @@ void cpus_set_virtual_clock(int64_t new_time)
> }
>
> /*
> - * return the time elapsed in VM between vm_start and vm_stop. Unless
> - * icount is active, cpus_get_elapsed_ticks() uses units of the host CPU
> cycle
> - * counter.
> + * Return VM-elapsed ticks. While VM ticks are disabled, passage of host time
> + * does not advance the returned value. Unless icount is active, the units
> are
> + * those of the host CPU cycle counter.
> */
> int64_t cpus_get_elapsed_ticks(void)
> {
> diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c
> index
> d15a2d096cb6e13cd123ff9ae82ee7c643c2a961..60caee32dc0cf5b6a8492e0cf8ff15f71acc2087
> 100644
> --- a/target/riscv/tcg/csr.c
> +++ b/target/riscv/tcg/csr.c
> @@ -1327,13 +1327,7 @@ static uint64_t
> riscv_pmu_ctr_get_fixed_counters_val(CPURISCVState *env,
> }
>
> if (!cfg_val) {
> - if (icount_enabled()) {
> - curr_val = inst ? icount_get_raw() : icount_get();
> - } else {
> - curr_val = cpu_get_host_ticks();
> - }
> -
> - return curr_val;
> + return riscv_pmu_read_fixed_source(env, inst);
> }
>
> /* Update counter before reading. */
> diff --git a/target/riscv/tcg/pmu.c b/target/riscv/tcg/pmu.c
> index
> f19f417e90e33a94d00007ef132ef4e154175b19..ea0ffe41258d4dbef9dc952655e6301c14ba1d23
> 100644
> --- a/target/riscv/tcg/pmu.c
> +++ b/target/riscv/tcg/pmu.c
> @@ -24,8 +24,14 @@
> #include "pmu.h"
> #include "exec/icount.h"
> #include "system/device_tree.h"
> +#include "system/cpu-timers.h"
>
> -#define RISCV_TIMEBASE_FREQ 1000000000 /* 1Ghz */
> +/*
> + * cpu_get_ticks() does not expose the host tick frequency. Use a 1 GHz
> + * approximation only when scheduling non-icount overflow checks; fixed
> + * counter values remain in host-tick units.
> + */
> +#define RISCV_PMU_HOST_TICK_HZ_ASSUMED 1000000000
>
> static bool riscv_pmu_counter_valid(RISCVCPU *cpu, uint32_t ctr_idx)
> {
> @@ -75,6 +81,19 @@ static bool riscv_pmu_counter_filtered(CPURISCVState *env,
> uint64_t cfg)
> (cfg & MHPMEVENT_BIT_UINH));
> }
>
> +/*
> + * VM-elapsed ticks stop advancing while VM ticks are disabled. Under
> + * icount, instruction events retain raw instruction-count units.
> + */
> +uint64_t riscv_pmu_read_fixed_source(CPURISCVState *env, bool instret)
> +{
> + if (instret && icount_enabled()) {
> + return icount_get_raw();
> + }
> +
> + return cpus_get_elapsed_ticks();
> +}
> +
> /*
> * Information needed to update counters:
> * new_priv, new_virt: To correctly save starting snapshot for the newly
> @@ -96,11 +115,7 @@ static void riscv_pmu_icount_update_priv(CPURISCVState
> *env,
> uint64_t *counter_arr;
> uint64_t delta;
>
> - if (icount_enabled()) {
> - current_icount = icount_get_raw();
> - } else {
> - current_icount = cpu_get_host_ticks();
> - }
> + current_icount = riscv_pmu_read_fixed_source(env, true);
>
> if (env->virt_enabled) {
> g_assert(env->priv <= PRV_S);
> @@ -137,11 +152,7 @@ static void riscv_pmu_cycle_update_priv(CPURISCVState
> *env,
> uint64_t *counter_arr;
> uint64_t delta;
>
> - if (icount_enabled()) {
> - current_ticks = icount_get();
> - } else {
> - current_ticks = cpu_get_host_ticks();
> - }
> + current_ticks = riscv_pmu_read_fixed_source(env, false);
>
> if (env->virt_enabled) {
> g_assert(env->priv <= PRV_S);
> @@ -286,17 +297,15 @@ static bool riscv_pmu_event_supported(uint32_t
> event_idx)
> }
> }
>
> -static int64_t pmu_icount_ticks_to_ns(int64_t value)
> +static int64_t pmu_ticks_to_ns(CPURISCVState *env, uint32_t ctr_idx,
> + int64_t value)
> {
> - int64_t ret = 0;
> -
> - if (icount_enabled()) {
> - ret = icount_to_ns(value);
> - } else {
> - ret = (NANOSECONDS_PER_SECOND / RISCV_TIMEBASE_FREQ) * value;
> + if (icount_enabled() &&
> + riscv_pmu_ctr_monitor_instructions(env, ctr_idx)) {
> + return icount_to_ns(value);
> }
>
> - return ret;
> + return (NANOSECONDS_PER_SECOND / RISCV_PMU_HOST_TICK_HZ_ASSUMED) * value;
> }
>
> void riscv_pmu_rebuild_event_map(CPURISCVState *env)
> @@ -448,8 +457,9 @@ int riscv_pmu_setup_timer(CPURISCVState *env, uint64_t
> value, uint32_t ctr_idx)
>
> if (riscv_pmu_ctr_monitor_cycles(env, ctr_idx) ||
> riscv_pmu_ctr_monitor_instructions(env, ctr_idx)) {
> - overflow_ns = pmu_icount_ticks_to_ns((int64_t)overflow_delta);
> - overflow_left = pmu_icount_ticks_to_ns(overflow_left) ;
> + overflow_ns = pmu_ticks_to_ns(env, ctr_idx,
> + (int64_t)overflow_delta);
> + overflow_left = pmu_ticks_to_ns(env, ctr_idx, overflow_left);
> } else {
> return -1;
> }
> diff --git a/target/riscv/tcg/pmu.h b/target/riscv/tcg/pmu.h
> index
> 910091690290cac9f77855f479bb9d90b2762efe..339a4b3ac09c4a91cddd9250824284203b16b4fa
> 100644
> --- a/target/riscv/tcg/pmu.h
> +++ b/target/riscv/tcg/pmu.h
> @@ -26,6 +26,7 @@ bool riscv_pmu_ctr_monitor_instructions(CPURISCVState *env,
> uint32_t target_ctr);
> bool riscv_pmu_ctr_monitor_cycles(CPURISCVState *env,
> uint32_t target_ctr);
> +uint64_t riscv_pmu_read_fixed_source(CPURISCVState *env, bool instret);
> void riscv_pmu_timer_cb(void *priv);
> void riscv_pmu_init(RISCVCPU *cpu, Error **errp);
> void riscv_pmu_rebuild_event_map(CPURISCVState *env);
> diff --git a/tests/tcg/riscv64/sscofpmf-cycle-overflow.S
> b/tests/tcg/riscv64/sscofpmf-cycle-overflow.S
> new file mode 100644
> index
> 0000000000000000000000000000000000000000..846d4651c4ee8f06df83bed85512ab9794552c28
> --- /dev/null
> +++ b/tests/tcg/riscv64/sscofpmf-cycle-overflow.S
> @@ -0,0 +1,58 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +
> + .option norvc
> + .option norelax
> +
> + .text
> + .global _start
> +_start:
> + /* UINT64_MAX - 4095 leaves 4096 cycle increments until overflow. */
> + csrw mhpmevent3, zero
> + li t0, -4096
> + csrw mhpmcounter3, t0
> + li t0, 1
> + csrw mhpmevent3, t0 /* mhpmevent3: cycles */
> + csrr t1, mcycle
> +
> +1:
> + csrr t0, mhpmevent3
> + beqz t0, fail
> + li t2, 1
> + slli t2, t2, 63
> + and t0, t0, t2
> + bnez t0, pass
> +
> + /*
> + * Allow 16384 cycles for OF to become visible. With shift=3, scaling
> + * the 4096-cycle distance twice would delay it to about 32768 cycles.
> + */
> + csrr t0, mcycle
> + sub t0, t0, t1
> + li t2, 16384
> + bltu t0, t2, 1b
> +
> +fail:
> + li a0, 1
> + j exit
> +
> +pass:
> + li a0, 0
> +
> +exit:
> + lla a1, semiargs
> + li t0, 0x20026 /* ADP_Stopped_ApplicationExit */
> + sd t0, 0(a1)
> + sd a0, 8(a1)
> + li a0, 0x20 /* TARGET_SYS_EXIT_EXTENDED */
> +
> + /* Semihosting call sequence. */
> + .balign 16
> + slli zero, zero, 0x1f
> + ebreak
> + srai zero, zero, 0x7
> + j .
> +
> + .data
> + .balign 16
> +semiargs:
> + .space 16
> diff --git a/tests/tcg/riscv64/system/meson.build
> b/tests/tcg/riscv64/system/meson.build
> index
> ebe78200fd551b42d3c99ae19ca03803797f803d..668a9a76070b6f16087b08ea84683d883312e951
> 100644
> --- a/tests/tcg/riscv64/system/meson.build
> +++ b/tests/tcg/riscv64/system/meson.build
> @@ -68,6 +68,13 @@ tests += {
> },
> }
>
> +tests += {
> + 'sscofpmf-cycle-overflow.S': {
> + 'cflags': cflags,
> + 'qemu_args': ['-cpu', 'max', '-icount', 'shift=3', qemu_args],
> + },
> +}
> +
> if 'qemu-system-riscv64' in emulators
> tcg_tests += {
> 'riscv64-softmmu': {
>
> --
> 2.43.0
>