Re: [PATCH] target/riscv: do not count ECALL in minstret

Daniel Henrique Barboza <[email protected]> Wed, 29 Jul 2026 17:13:53 -0300
Newsgroups org.nongnu.qemu-riscv,org.nongnu.qemu-devel
Message-ID <[email protected]>

On 7/29/2026 12:22 AM, Zephyr Li wrote:
> With icount enabled, helper_raise_exception() leaves ECALL in
> icount_get_raw() because it exits without restoring the current TB
> state. This makes minstret count an instruction that does not retire.
> 
> Adjust only the fixed minstret baseline so that mcycle accounting
> remains unchanged.
> 
> Add an RV64 softmmu regression test for the issue.

That's a good habit that I need to incorporate as well.

> 

Fixes: 4fe8ae0906 ("target/riscv: Combine mhpmcounter and mhpmcounterh")

> Resolves: https://gitlab.com/qemu-project/qemu/-/issues/4087
> Signed-off-by: Zephyr Li <[email protected]>
> ---

Reviewed-by: Daniel Henrique Barboza <[email protected]>

>   target/riscv/tcg/op_helper.c              |  6 +++
>   target/riscv/tcg/pmu.c                    | 44 +++++++++++++-----
>   target/riscv/tcg/pmu.h                    |  1 +
>   tests/tcg/riscv64/Makefile.softmmu-target |  4 ++
>   tests/tcg/riscv64/test-minstret-ecall.S   | 55 +++++++++++++++++++++++
>   5 files changed, 98 insertions(+), 12 deletions(-)
>   create mode 100644 tests/tcg/riscv64/test-minstret-ecall.S
> 
> diff --git a/target/riscv/tcg/op_helper.c b/target/riscv/tcg/op_helper.c
> index ba3c7da375..acdccd1a97 100644
> --- a/target/riscv/tcg/op_helper.c
> +++ b/target/riscv/tcg/op_helper.c
> @@ -21,6 +21,9 @@
>   #include "qemu/osdep.h"
>   #include "cpu.h"
>   #include "target/riscv/tcg/csr.h"
> +#ifndef CONFIG_USER_ONLY
> +#include "pmu.h"
> +#endif
>   #include "internals.h"
>   #include "exec/cputlb.h"
>   #include "accel/tcg/cpu-ldst.h"
> @@ -47,6 +50,9 @@ G_NORETURN void riscv_raise_exception(CPURISCVState *env,
>   
>   void helper_raise_exception(CPURISCVState *env, uint32_t exception)
>   {
> +#ifndef CONFIG_USER_ONLY
> +    riscv_pmu_decr_instret(env);
> +#endif
>       riscv_raise_exception(env, exception, 0);
>   }
>   
> diff --git a/target/riscv/tcg/pmu.c b/target/riscv/tcg/pmu.c
> index 38ad2737e1..1a4658319b 100644
> --- a/target/riscv/tcg/pmu.c
> +++ b/target/riscv/tcg/pmu.c
> @@ -49,6 +49,21 @@ static bool riscv_pmu_counter_enabled(RISCVCPU *cpu, uint32_t ctr_idx)
>       }
>   }
>   
> +static bool riscv_pmu_counter_filtered(CPURISCVState *env, uint64_t cfg)
> +{
> +    bool virt_on = env->virt_enabled;
> +
> +    return (env->priv == PRV_M && (cfg & MHPMEVENT_BIT_MINH)) ||
> +           (env->priv == PRV_S && virt_on &&
> +            (cfg & MHPMEVENT_BIT_VSINH)) ||
> +           (env->priv == PRV_U && virt_on &&
> +            (cfg & MHPMEVENT_BIT_VUINH)) ||
> +           (env->priv == PRV_S && !virt_on &&
> +            (cfg & MHPMEVENT_BIT_SINH)) ||
> +           (env->priv == PRV_U && !virt_on &&
> +            (cfg & MHPMEVENT_BIT_UINH));
> +}
> +
>   /*
>    * Information needed to update counters:
>    *  new_priv, new_virt: To correctly save starting snapshot for the newly
> @@ -147,12 +162,27 @@ void riscv_pmu_update_fixed_ctrs(CPURISCVState *env,
>       riscv_pmu_icount_update_priv(env, newpriv, new_virt);
>   }
>   
> +void riscv_pmu_decr_instret(CPURISCVState *env)
> +{
> +    if (!icount_enabled() ||
> +        (env->mcountinhibit & COUNTEREN_IR) ||
> +        riscv_pmu_counter_filtered(env, env->minstretcfg)) {
> +        return;
> +    }
> +
> +    /*
> +     * minstret is derived from icount, which includes the current
> +     * instruction.  Move the baseline forward to exclude an instruction
> +     * that raises an exception and therefore does not retire.
> +     */
> +    env->pmu_ctrs[2].mhpmcounter_prev++;
> +}
> +
>   int riscv_pmu_incr_ctr(RISCVCPU *cpu, enum riscv_pmu_event_idx event_idx)
>   {
>       uint32_t ctr_idx;
>       CPURISCVState *env = &cpu->env;
>       uint64_t max_val = UINT64_MAX;
> -    bool virt_on = env->virt_enabled;
>       PMUCTRState *counter;
>       gpointer value;
>   
> @@ -170,17 +200,7 @@ int riscv_pmu_incr_ctr(RISCVCPU *cpu, enum riscv_pmu_event_idx event_idx)
>           return -1;
>       }
>   
> -    /* Privilege mode filtering */
> -    if ((env->priv == PRV_M &&
> -        (env->mhpmevent_val[ctr_idx] & MHPMEVENT_BIT_MINH)) ||
> -        (env->priv == PRV_S && virt_on &&
> -        (env->mhpmevent_val[ctr_idx] & MHPMEVENT_BIT_VSINH)) ||
> -        (env->priv == PRV_U && virt_on &&
> -        (env->mhpmevent_val[ctr_idx] & MHPMEVENT_BIT_VUINH)) ||
> -        (env->priv == PRV_S && !virt_on &&
> -        (env->mhpmevent_val[ctr_idx] & MHPMEVENT_BIT_SINH)) ||
> -        (env->priv == PRV_U && !virt_on &&
> -        (env->mhpmevent_val[ctr_idx] & MHPMEVENT_BIT_UINH))) {
> +    if (riscv_pmu_counter_filtered(env, env->mhpmevent_val[ctr_idx])) {
>           return 0;
>       }
>   
> diff --git a/target/riscv/tcg/pmu.h b/target/riscv/tcg/pmu.h
> index b4f1e469a2..2429c01b77 100644
> --- a/target/riscv/tcg/pmu.h
> +++ b/target/riscv/tcg/pmu.h
> @@ -36,6 +36,7 @@ int riscv_pmu_setup_timer(CPURISCVState *env, uint64_t value,
>                             uint32_t ctr_idx);
>   void riscv_pmu_update_fixed_ctrs(CPURISCVState *env, privilege_mode_t newpriv,
>                                    bool new_virt);
> +void riscv_pmu_decr_instret(CPURISCVState *env);
>   RISCVException riscv_pmu_read_ctr(CPURISCVState *env, target_ulong *val,
>                                     bool upper_half, uint32_t ctr_idx);
>   
> diff --git a/tests/tcg/riscv64/Makefile.softmmu-target b/tests/tcg/riscv64/Makefile.softmmu-target
> index 82be8a2c91..42038ce3b4 100644
> --- a/tests/tcg/riscv64/Makefile.softmmu-target
> +++ b/tests/tcg/riscv64/Makefile.softmmu-target
> @@ -24,6 +24,10 @@ EXTRA_RUNS += run-test-mepc-masking
>   run-test-mepc-masking: test-mepc-masking
>   	$(call run-test, $<, $(QEMU) $(QEMU_OPTS)$<)
>   
> +EXTRA_RUNS += run-test-minstret-ecall
> +run-test-minstret-ecall: test-minstret-ecall
> +	$(call run-test, $<, $(QEMU) -icount shift=1 $(QEMU_OPTS)$<)
> +
>   EXTRA_RUNS += run-plugin-doubletrap
>   run-plugin-doubletrap: doubletrap
>   	$(call run-test, $<, \
> diff --git a/tests/tcg/riscv64/test-minstret-ecall.S b/tests/tcg/riscv64/test-minstret-ecall.S
> new file mode 100644
> index 0000000000..ab268f7f22
> --- /dev/null
> +++ b/tests/tcg/riscv64/test-minstret-ecall.S
> @@ -0,0 +1,55 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +
> +	.option	norvc
> +
> +	.text
> +	.global	_start
> +_start:
> +	lla	t0, trap
> +	csrw	mtvec, t0
> +
> +	/*
> +	 * The first CSR read retires after obtaining s0.  The ecall does not
> +	 * retire, so the trap handler must observe a difference of one.
> +	 */
> +	csrr	s0, minstret
> +	ecall
> +	sub	t0, s1, s0
> +	li	t1, 1
> +	bne	t0, t1, fail
> +
> +	li	a0, 0
> +	j	_exit
> +
> +trap:
> +	csrr	s1, minstret
> +	csrr	t0, mcause
> +	li	t1, 11		/* Environment call from M-mode */
> +	bne	t0, t1, fail
> +
> +	csrr	t0, mepc
> +	addi	t0, t0, 4
> +	csrw	mepc, t0
> +	mret
> +
> +fail:
> +	li	a0, 1
> +
> +_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