Re: [PATCH 5/5] core/cpu-common: initialise plugin state before thread creation
On 5/30/24 12:42, Alex Bennée wrote: Originally I tried to move where vCPU thread initialisation to later in realize. However pulling that thread (sic) got gnarly really quickly. It turns out some steps of CPU realization need values that can only be determined from the running vCPU thread. However having moved enough out of the thread creation we can now queue work before the thread starts (at least for TCG guests) and avoid the race between vcpu_init and other vcpu states a plugin might subscribe to. Signed-off-by: Alex Bennée --- hw/core/cpu-common.c | 20 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/hw/core/cpu-common.c b/hw/core/cpu-common.c index 6cfc01593a..bf1a7b8892 100644 --- a/hw/core/cpu-common.c +++ b/hw/core/cpu-common.c @@ -222,14 +222,6 @@ static void cpu_common_realizefn(DeviceState *dev, Error **errp) cpu_resume(cpu); } -/* Plugin initialization must wait until the cpu start executing code */ -#ifdef CONFIG_PLUGIN -if (tcg_enabled()) { -cpu->plugin_state = qemu_plugin_create_vcpu_state(); -async_run_on_cpu(cpu, qemu_plugin_vcpu_init__async, RUN_ON_CPU_NULL); -} -#endif - /* NOTE: latest generic point where the cpu is fully realized */ } @@ -273,6 +265,18 @@ static void cpu_common_initfn(Object *obj) QTAILQ_INIT(>watchpoints); cpu_exec_initfn(cpu); + +/* + * Plugin initialization must wait until the cpu start executing + * code, but we must queue this work before the threads are + * created to ensure we don't race. + */ +#ifdef CONFIG_PLUGIN +if (tcg_enabled()) { +cpu->plugin_state = qemu_plugin_create_vcpu_state(); +async_run_on_cpu(cpu, qemu_plugin_vcpu_init__async, RUN_ON_CPU_NULL); +} +#endif } static void cpu_common_finalize(Object *obj) Could you check it works for all combination? - user-mode - system-mode tcg - system-mode mttcg When I tried to move this code around, one of them didn't work correctly. Else, Reviewed-by: Pierrick Bouvier
Re: [PATCH 4/5] plugins: remove special casing for cpu->realized
On 5/30/24 12:42, Alex Bennée wrote: Now the condition variable is initialised early on we don't need to go through hoops to avoid calling async_run_on_cpu. Signed-off-by: Alex Bennée --- plugins/core.c | 6 +- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugins/core.c b/plugins/core.c index 0726bc7f25..badede28cf 100644 --- a/plugins/core.c +++ b/plugins/core.c @@ -65,11 +65,7 @@ static void plugin_cpu_update__locked(gpointer k, gpointer v, gpointer udata) CPUState *cpu = container_of(k, CPUState, cpu_index); run_on_cpu_data mask = RUN_ON_CPU_HOST_ULONG(*plugin.mask); -if (DEVICE(cpu)->realized) { -async_run_on_cpu(cpu, plugin_cpu_update__async, mask); -} else { -plugin_cpu_update__async(cpu, mask); -} +async_run_on_cpu(cpu, plugin_cpu_update__async, mask); } void plugin_unregister_cb__locked(struct qemu_plugin_ctx *ctx, Reviewed-by: Pierrick Bouvier
Re: [PATCH 3/5] cpu-target: don't set cpu->thread_id to bogus value
On 5/30/24 12:42, Alex Bennée wrote: The thread_id isn't valid until the threads are created. There is no point setting it here. The only thing that cares about the thread_id is qmp_query_cpus_fast. Signed-off-by: Alex Bennée --- cpu-target.c | 1 - 1 file changed, 1 deletion(-) diff --git a/cpu-target.c b/cpu-target.c index 5af120e8aa..499facf774 100644 --- a/cpu-target.c +++ b/cpu-target.c @@ -241,7 +241,6 @@ void cpu_exec_initfn(CPUState *cpu) cpu->num_ases = 0; #ifndef CONFIG_USER_ONLY -cpu->thread_id = qemu_get_thread_id(); cpu->memory = get_system_memory(); object_ref(OBJECT(cpu->memory)); #endif Reviewed-by: Pierrick Bouvier
Re: [PATCH 1/5] hw/core: expand on the alignment of CPUState
On 5/30/24 12:42, Alex Bennée wrote: Make the relationship between CPUState, ArchCPU and cpu_env a bit clearer in the kdoc comments. Signed-off-by: Alex Bennée --- include/hw/core/cpu.h | 14 ++ 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h index bb398e8237..35d345371b 100644 --- a/include/hw/core/cpu.h +++ b/include/hw/core/cpu.h @@ -391,7 +391,8 @@ struct qemu_work_item; #define CPU_UNSET_NUMA_NODE_ID -1 /** - * CPUState: + * struct CPUState - common state of one CPU core or thread. + * * @cpu_index: CPU index (informative). * @cluster_index: Identifies which cluster this CPU is in. * For boards which don't define clusters or for "loose" CPUs not assigned @@ -439,10 +440,15 @@ struct qemu_work_item; * @kvm_fetch_index: Keeps the index that we last fetched from the per-vCPU *dirty ring structure. * - * State of one CPU core or thread. + * @neg_align: The CPUState is the common part of a concrete ArchCPU + * which is allocated when an individual CPU instance is created. As + * such care is taken is ensure there is no gap between between + * CPUState and CPUArchState within ArchCPU. * - * Align, in order to match possible alignment required by CPUArchState, - * and eliminate a hole between CPUState and CPUArchState within ArchCPU. + * @neg: The architectural register state ("cpu_env") immediately follows CPUState + * in ArchCPU and is passed to TCG code. The @neg structure holds some + * common TCG CPU variables which are accessed with a negative offset + * from cpu_env. */ struct CPUState { /*< private >*/ Reviewed-by: Pierrick Bouvier
Re: [PATCH 2/5] cpu: move Qemu[Thread|Cond] setup into common code
/* share a single thread for all cpus with TCG */ snprintf(thread_name, VCPU_THREAD_NAME_SIZE, "ALL CPUs/TCG"); qemu_thread_create(cpu->thread, thread_name, rr_cpu_thread_fn, cpu, QEMU_THREAD_JOINABLE); - -single_tcg_halt_cond = cpu->halt_cond; -single_tcg_cpu_thread = cpu->thread; } else { -/* we share the thread */ +/* we share the thread, dump spare data */ +g_free(cpu->thread); +qemu_cond_destroy(cpu->halt_cond); cpu->thread = single_tcg_cpu_thread; cpu->halt_cond = single_tcg_halt_cond; + +/* copy the stuff done at start of rr_cpu_thread_fn */ cpu->thread_id = first_cpu->thread_id; cpu->neg.can_do_io = 1; cpu->created = true; diff --git a/hw/core/cpu-common.c b/hw/core/cpu-common.c index 0f0a247f56..6cfc01593a 100644 --- a/hw/core/cpu-common.c +++ b/hw/core/cpu-common.c @@ -261,6 +261,11 @@ static void cpu_common_initfn(Object *obj) cpu->nr_threads = 1; cpu->cflags_next_tb = -1; +/* allocate storage for thread info, initialise condition variables */ +cpu->thread = g_new0(QemuThread, 1); +cpu->halt_cond = g_new0(QemuCond, 1); +qemu_cond_init(cpu->halt_cond); + qemu_mutex_init(>work_mutex); qemu_lockcnt_init(>in_ioctl_lock); QSIMPLEQ_INIT(>work_list); diff --git a/target/i386/nvmm/nvmm-accel-ops.c b/target/i386/nvmm/nvmm-accel-ops.c index 6b2bfd9b9c..0ba31201e2 100644 --- a/target/i386/nvmm/nvmm-accel-ops.c +++ b/target/i386/nvmm/nvmm-accel-ops.c @@ -64,9 +64,6 @@ static void nvmm_start_vcpu_thread(CPUState *cpu) { char thread_name[VCPU_THREAD_NAME_SIZE]; -cpu->thread = g_new0(QemuThread, 1); -cpu->halt_cond = g_new0(QemuCond, 1); -qemu_cond_init(cpu->halt_cond); snprintf(thread_name, VCPU_THREAD_NAME_SIZE, "CPU %d/NVMM", cpu->cpu_index); qemu_thread_create(cpu->thread, thread_name, qemu_nvmm_cpu_thread_fn, diff --git a/target/i386/whpx/whpx-accel-ops.c b/target/i386/whpx/whpx-accel-ops.c index 189ae0f140..1a2b4e1c43 100644 --- a/target/i386/whpx/whpx-accel-ops.c +++ b/target/i386/whpx/whpx-accel-ops.c @@ -64,9 +64,6 @@ static void whpx_start_vcpu_thread(CPUState *cpu) { char thread_name[VCPU_THREAD_NAME_SIZE]; -cpu->thread = g_new0(QemuThread, 1); -cpu->halt_cond = g_new0(QemuCond, 1); -qemu_cond_init(cpu->halt_cond); snprintf(thread_name, VCPU_THREAD_NAME_SIZE, "CPU %d/WHPX", cpu->cpu_index); qemu_thread_create(cpu->thread, thread_name, whpx_cpu_thread_fn, Reviewed-by: Pierrick Bouvier
[PATCH v3 3/6] sysemu: generalise qtest_warp_clock as qemu_clock_advance_virtual_time
From: Alex Bennée Move the key functionality of moving time forward into the clock sub-system itself. This will allow us to plumb in time control into plugins. From: Alex Bennée Signed-off-by: Alex Bennée Signed-off-by: Pierrick Bouvier --- include/qemu/timer.h | 15 +++ system/qtest.c | 25 +++-- util/qemu-timer.c| 26 ++ 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/include/qemu/timer.h b/include/qemu/timer.h index 9a366e551fb..910587d8293 100644 --- a/include/qemu/timer.h +++ b/include/qemu/timer.h @@ -245,6 +245,21 @@ bool qemu_clock_run_timers(QEMUClockType type); */ bool qemu_clock_run_all_timers(void); +/** + * qemu_clock_advance_virtual_time(): advance the virtual time tick + * @target: target time in nanoseconds + * + * This function is used where the control of the flow of time has + * been delegated to outside the clock subsystem (be it qtest, icount + * or some other external source). You can ask the clock system to + * return @early at the first expired timer. + * + * Time can only move forward, attempts to reverse time would lead to + * an error. + * + * Returns: new virtual time. + */ +int64_t qemu_clock_advance_virtual_time(int64_t dest); /* * QEMUTimerList diff --git a/system/qtest.c b/system/qtest.c index ee8b139e982..e6f6b4e62d5 100644 --- a/system/qtest.c +++ b/system/qtest.c @@ -337,26 +337,6 @@ void qtest_set_virtual_clock(int64_t count) qatomic_set_i64(_clock_counter, count); } -static void qtest_clock_warp(int64_t dest) -{ -int64_t clock = cpus_get_virtual_clock(); -AioContext *aio_context; -assert(qtest_enabled()); -aio_context = qemu_get_aio_context(); -while (clock < dest) { -int64_t deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL, - QEMU_TIMER_ATTR_ALL); -int64_t warp = qemu_soonest_timeout(dest - clock, deadline); - -cpus_set_virtual_clock(cpus_get_virtual_clock() + warp); - -qemu_clock_run_timers(QEMU_CLOCK_VIRTUAL); -timerlist_run_timers(aio_context->tlg.tl[QEMU_CLOCK_VIRTUAL]); -clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); -} -qemu_clock_notify(QEMU_CLOCK_VIRTUAL); -} - static bool (*process_command_cb)(CharBackend *chr, gchar **words); void qtest_set_command_cb(bool (*pc_cb)(CharBackend *chr, gchar **words)) @@ -755,7 +735,8 @@ static void qtest_process_command(CharBackend *chr, gchar **words) ns = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL, QEMU_TIMER_ATTR_ALL); } -qtest_clock_warp(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns); +qemu_clock_advance_virtual_time( +qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns); qtest_send_prefix(chr); qtest_sendf(chr, "OK %"PRIi64"\n", (int64_t)qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)); @@ -781,7 +762,7 @@ static void qtest_process_command(CharBackend *chr, gchar **words) g_assert(words[1]); ret = qemu_strtoi64(words[1], NULL, 0, ); g_assert(ret == 0); -qtest_clock_warp(ns); +qemu_clock_advance_virtual_time(ns); qtest_send_prefix(chr); qtest_sendf(chr, "OK %"PRIi64"\n", (int64_t)qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)); diff --git a/util/qemu-timer.c b/util/qemu-timer.c index 6a0de33dd2b..213114be68c 100644 --- a/util/qemu-timer.c +++ b/util/qemu-timer.c @@ -645,6 +645,11 @@ int64_t qemu_clock_get_ns(QEMUClockType type) } } +static void qemu_virtual_clock_set_ns(int64_t time) +{ +return cpus_set_virtual_clock(time); +} + void init_clocks(QEMUTimerListNotifyCB *notify_cb) { QEMUClockType type; @@ -675,3 +680,24 @@ bool qemu_clock_run_all_timers(void) return progress; } + +int64_t qemu_clock_advance_virtual_time(int64_t dest) +{ +int64_t clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +AioContext *aio_context; +aio_context = qemu_get_aio_context(); +while (clock < dest) { +int64_t deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL, + QEMU_TIMER_ATTR_ALL); +int64_t warp = qemu_soonest_timeout(dest - clock, deadline); + +qemu_virtual_clock_set_ns(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + warp); + +qemu_clock_run_timers(QEMU_CLOCK_VIRTUAL); +timerlist_run_timers(aio_context->tlg.tl[QEMU_CLOCK_VIRTUAL]); +clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +} +qemu_clock_notify(QEMU_CLOCK_VIRTUAL); + +return clock; +} -- 2.39.2
[PATCH v3 4/6] qtest: move qtest_{get, set}_virtual_clock to accel/qtest/qtest.c
Signed-off-by: Pierrick Bouvier --- include/sysemu/qtest.h | 3 --- accel/qtest/qtest.c| 12 system/qtest.c | 12 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/include/sysemu/qtest.h b/include/sysemu/qtest.h index 45f3b7e1df5..c161d751650 100644 --- a/include/sysemu/qtest.h +++ b/include/sysemu/qtest.h @@ -34,9 +34,6 @@ void qtest_server_init(const char *qtest_chrdev, const char *qtest_log, Error ** void qtest_server_set_send_handler(void (*send)(void *, const char *), void *opaque); void qtest_server_inproc_recv(void *opaque, const char *buf); - -int64_t qtest_get_virtual_clock(void); -void qtest_set_virtual_clock(int64_t count); #endif #endif diff --git a/accel/qtest/qtest.c b/accel/qtest/qtest.c index 53182e6c2ae..bf14032d294 100644 --- a/accel/qtest/qtest.c +++ b/accel/qtest/qtest.c @@ -24,6 +24,18 @@ #include "qemu/main-loop.h" #include "hw/core/cpu.h" +static int64_t qtest_clock_counter; + +static int64_t qtest_get_virtual_clock(void) +{ +return qatomic_read_i64(_clock_counter); +} + +static void qtest_set_virtual_clock(int64_t count) +{ +qatomic_set_i64(_clock_counter, count); +} + static int qtest_init_accel(MachineState *ms) { return 0; diff --git a/system/qtest.c b/system/qtest.c index e6f6b4e62d5..ba210780ec0 100644 --- a/system/qtest.c +++ b/system/qtest.c @@ -325,18 +325,6 @@ static void qtest_irq_handler(void *opaque, int n, int level) } } -static int64_t qtest_clock_counter; - -int64_t qtest_get_virtual_clock(void) -{ -return qatomic_read_i64(_clock_counter); -} - -void qtest_set_virtual_clock(int64_t count) -{ -qatomic_set_i64(_clock_counter, count); -} - static bool (*process_command_cb)(CharBackend *chr, gchar **words); void qtest_set_command_cb(bool (*pc_cb)(CharBackend *chr, gchar **words)) -- 2.39.2
[PATCH v3 1/6] sysemu: add set_virtual_time to accel ops
From: Alex Bennée We are about to remove direct calls to individual accelerators for this information and will need a central point for plugins to hook into time changes. From: Alex Bennée Signed-off-by: Alex Bennée Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Pierrick Bouvier --- include/sysemu/accel-ops.h | 18 +- include/sysemu/cpu-timers.h| 3 ++- ...et-virtual-clock.c => cpus-virtual-clock.c} | 5 + system/cpus.c | 11 +++ stubs/meson.build | 2 +- 5 files changed, 36 insertions(+), 3 deletions(-) rename stubs/{cpus-get-virtual-clock.c => cpus-virtual-clock.c} (68%) diff --git a/include/sysemu/accel-ops.h b/include/sysemu/accel-ops.h index ef91fc28bbd..a0886722305 100644 --- a/include/sysemu/accel-ops.h +++ b/include/sysemu/accel-ops.h @@ -20,7 +20,12 @@ typedef struct AccelOpsClass AccelOpsClass; DECLARE_CLASS_CHECKERS(AccelOpsClass, ACCEL_OPS, TYPE_ACCEL_OPS) -/* cpus.c operations interface */ +/** + * struct AccelOpsClass - accelerator interfaces + * + * This structure is used to abstract accelerator differences from the + * core CPU code. Not all have to be implemented. + */ struct AccelOpsClass { /*< private >*/ ObjectClass parent_class; @@ -44,7 +49,18 @@ struct AccelOpsClass { void (*handle_interrupt)(CPUState *cpu, int mask); +/** + * @get_virtual_clock: fetch virtual clock + * @set_virtual_clock: set virtual clock + * + * These allow the timer subsystem to defer to the accelerator to + * fetch time. The set function is needed if the accelerator wants + * to track the changes to time as the timer is warped through + * various timer events. + */ int64_t (*get_virtual_clock)(void); +void (*set_virtual_clock)(int64_t time); + int64_t (*get_elapsed_ticks)(void); /* gdbstub hooks */ diff --git a/include/sysemu/cpu-timers.h b/include/sysemu/cpu-timers.h index d86738a378d..7bfa960fbd6 100644 --- a/include/sysemu/cpu-timers.h +++ b/include/sysemu/cpu-timers.h @@ -96,8 +96,9 @@ int64_t cpu_get_clock(void); void qemu_timer_notify_cb(void *opaque, QEMUClockType type); -/* get the VIRTUAL clock and VM elapsed ticks via the cpus accel interface */ +/* get/set VIRTUAL clock and VM elapsed ticks via the cpus accel interface */ int64_t cpus_get_virtual_clock(void); +void cpus_set_virtual_clock(int64_t new_time); int64_t cpus_get_elapsed_ticks(void); #endif /* SYSEMU_CPU_TIMERS_H */ diff --git a/stubs/cpus-get-virtual-clock.c b/stubs/cpus-virtual-clock.c similarity index 68% rename from stubs/cpus-get-virtual-clock.c rename to stubs/cpus-virtual-clock.c index fd447d53f3c..af7c1a1d403 100644 --- a/stubs/cpus-get-virtual-clock.c +++ b/stubs/cpus-virtual-clock.c @@ -6,3 +6,8 @@ int64_t cpus_get_virtual_clock(void) { return cpu_get_clock(); } + +void cpus_set_virtual_clock(int64_t new_time) +{ +/* do nothing */ +} diff --git a/system/cpus.c b/system/cpus.c index f8fa78f33d4..d3640c95030 100644 --- a/system/cpus.c +++ b/system/cpus.c @@ -229,6 +229,17 @@ int64_t cpus_get_virtual_clock(void) return cpu_get_clock(); } +/* + * Signal the new virtual time to the accelerator. This is only needed + * by accelerators that need to track the changes as we warp time. + */ +void cpus_set_virtual_clock(int64_t new_time) +{ +if (cpus_accel && cpus_accel->set_virtual_clock) { +cpus_accel->set_virtual_clock(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 diff --git a/stubs/meson.build b/stubs/meson.build index 3b9d42023cb..a1deafde08c 100644 --- a/stubs/meson.build +++ b/stubs/meson.build @@ -28,7 +28,7 @@ endif if have_block or have_ga stub_ss.add(files('replay-tools.c')) # stubs for hooks in util/main-loop.c, util/async.c etc. - stub_ss.add(files('cpus-get-virtual-clock.c')) + stub_ss.add(files('cpus-virtual-clock.c')) stub_ss.add(files('icount.c')) stub_ss.add(files('graph-lock.c')) if linux_io_uring.found() -- 2.39.2
Re: [PATCH v2 0/6] Implement icount=auto using TCG Plugins
A v3 was just sent, with a fix to the algorithm used. On 5/30/24 10:49, Pierrick Bouvier wrote: The goal here is to be able to scale temporally execution of qemu-user/system, using a given number of instructions per second. We define a virtual clock, that can be late or in advance compared to real time. When we are in advance, we slow execution (by sleeping) until catching real time. Finally, we should be able to cleanup icount=auto mode completely, and keep icount usage for determistic purposes only. It is built upon new TCG Plugins inline ops (store + conditional callbacks), now merged on master. Example in user-mode: - Retrieve number of instructions to execute /bin/true $ ./build/qemu-x86_64 -plugin ./build/tests/plugin/libinsn.so -d plugin /bin/true cpu 0 insns: 120546 total insns: 120546 - Slow execution to match 5 seconds $ time ./build/qemu-x86_64 -plugin ./build/contrib/plugins/libips,ips=$((120546/5)) /bin/true real0m4.985s v2 -- - Added missing personal Signed-off-by for commits from Alex - Fix bad rebase in stubs/meson.build - move qtest_{get,set}_virtual_clock to accel/qtest/qtest.c - A race condition was identified for plugins init/idle/resume, but is not related to this series, and will be fixed in another one: https://lore.kernel.org/qemu-devel/20240529152219.825680-1-alex.ben...@linaro.org/ Alex Bennée (4): sysemu: add set_virtual_time to accel ops qtest: use cpu interface in qtest_clock_warp sysemu: generalise qtest_warp_clock as qemu_clock_advance_virtual_time plugins: add time control API Pierrick Bouvier (2): qtest: move qtest_{get,set}_virtual_clock to accel/qtest/qtest.c contrib/plugins: add ips plugin example for cost modeling include/qemu/qemu-plugin.h| 23 ++ include/qemu/timer.h | 15 ++ include/sysemu/accel-ops.h| 18 +- include/sysemu/cpu-timers.h | 3 +- include/sysemu/qtest.h| 2 - accel/qtest/qtest.c | 13 + contrib/plugins/ips.c | 239 ++ plugins/api.c | 31 +++ ...t-virtual-clock.c => cpus-virtual-clock.c} | 5 + system/cpus.c | 11 + system/qtest.c| 37 +-- util/qemu-timer.c | 26 ++ contrib/plugins/Makefile | 1 + plugins/qemu-plugins.symbols | 2 + stubs/meson.build | 2 +- 15 files changed, 389 insertions(+), 39 deletions(-) create mode 100644 contrib/plugins/ips.c rename stubs/{cpus-get-virtual-clock.c => cpus-virtual-clock.c} (68%)
[PATCH v3 0/6] Implement icount=auto using TCG Plugins
The goal here is to be able to scale temporally execution of qemu-user/system, using a given number of instructions per second. We define a virtual clock, that can be late or in advance compared to real time. When we are in advance, we slow execution (by sleeping) until catching real time. Finally, we should be able to cleanup icount=auto mode completely, and keep icount usage for determistic purposes only. It is built upon new TCG Plugins inline ops (store + conditional callbacks), now merged on master. Example in user-mode: - Retrieve number of instructions to execute /bin/true $ ./build/qemu-x86_64 -plugin ./build/tests/plugin/libinsn.so -d plugin /bin/true cpu 0 insns: 120546 total insns: 120546 - Slow execution to match 5 seconds $ time ./build/qemu-x86_64 -plugin ./build/contrib/plugins/libips,ips=$((120546/5)) /bin/true real0m4.985s Tested in system mode by booting a full debian system, and using: $ sysbench cpu run Performance decrease linearly with the given number of ips. v2 -- - Added missing personal Signed-off-by for commits from Alex - Fix bad rebase in stubs/meson.build - move qtest_{get,set}_virtual_clock to accel/qtest/qtest.c - A race condition was identified for plugins init/idle/resume, but is not related to this series, and will be fixed in another one: https://lore.kernel.org/qemu-devel/20240529152219.825680-1-alex.ben...@linaro.org/ v3 -- - remove precise execution (per insn, per tb is enough) - fixed algorithm used. Instead of comparing from start time of the system, we just check on a given quantum of time that we didn't run too fast. It is more simple, there is no need to track idle/resume events, and a vcpu can sleep correctly. - use "sysbench cpu run" in system mode to check execution is slowed as expected. - do not use idle/resume callback Alex Bennée (4): sysemu: add set_virtual_time to accel ops qtest: use cpu interface in qtest_clock_warp sysemu: generalise qtest_warp_clock as qemu_clock_advance_virtual_time plugins: add time control API Pierrick Bouvier (2): qtest: move qtest_{get,set}_virtual_clock to accel/qtest/qtest.c contrib/plugins: add ips plugin example for cost modeling include/qemu/qemu-plugin.h| 23 +++ include/qemu/timer.h | 15 ++ include/sysemu/accel-ops.h| 18 +- include/sysemu/cpu-timers.h | 3 +- include/sysemu/qtest.h| 2 - accel/qtest/qtest.c | 13 ++ contrib/plugins/ips.c | 164 ++ plugins/api.c | 31 ...t-virtual-clock.c => cpus-virtual-clock.c} | 5 + system/cpus.c | 11 ++ system/qtest.c| 37 +--- util/qemu-timer.c | 26 +++ contrib/plugins/Makefile | 1 + plugins/qemu-plugins.symbols | 2 + stubs/meson.build | 2 +- 15 files changed, 314 insertions(+), 39 deletions(-) create mode 100644 contrib/plugins/ips.c rename stubs/{cpus-get-virtual-clock.c => cpus-virtual-clock.c} (68%) -- 2.39.2
[PATCH v3 6/6] contrib/plugins: add ips plugin example for cost modeling
This plugin uses the new time control interface to make decisions about the state of time during the emulation. The algorithm is currently very simple. The user specifies an ips rate which applies per core. If the core runs ahead of its allocated execution time the plugin sleeps for a bit to let real time catch up. Either way time is updated for the emulation as a function of total executed instructions with some adjustments for cores that idle. Examples Slow down execution of /bin/true: $ num_insn=$(./build/qemu-x86_64 -plugin ./build/tests/plugin/libinsn.so -d plugin /bin/true |& grep total | sed -e 's/.*: //') $ time ./build/qemu-x86_64 -plugin ./build/contrib/plugins/libips.so,ips=$(($num_insn/4)) /bin/true real 4.000s Boot a Linux kernel simulating a 250MHz cpu: $ /build/qemu-system-x86_64 -kernel /boot/vmlinuz-6.1.0-21-amd64 -append "console=ttyS0" -plugin ./build/contrib/plugins/libips.so,ips=$((250*1000*1000)) -smp 1 -m 512 check time until kernel panic on serial0 Tested in system mode by booting a full debian system, and using: $ sysbench cpu run Performance decrease linearly with the given number of ips. Signed-off-by: Pierrick Bouvier --- contrib/plugins/ips.c| 164 +++ contrib/plugins/Makefile | 1 + 2 files changed, 165 insertions(+) create mode 100644 contrib/plugins/ips.c diff --git a/contrib/plugins/ips.c b/contrib/plugins/ips.c new file mode 100644 index 000..db77729264b --- /dev/null +++ b/contrib/plugins/ips.c @@ -0,0 +1,164 @@ +/* + * ips rate limiting plugin. + * + * This plugin can be used to restrict the execution of a system to a + * particular number of Instructions Per Second (ips). This controls + * time as seen by the guest so while wall-clock time may be longer + * from the guests point of view time will pass at the normal rate. + * + * This uses the new plugin API which allows the plugin to control + * system time. + * + * Copyright (c) 2023 Linaro Ltd + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include + +QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; + +/* how many times do we update time per sec */ +#define NUM_TIME_UPDATE_PER_SEC 10 +#define NSEC_IN_ONE_SEC (1000 * 1000 * 1000) + +static GMutex global_state_lock; + +static uint64_t max_insn_per_second = 1000 * 1000 * 1000; /* ips per core, per second */ +static uint64_t max_insn_per_quantum; /* trap every N instructions */ +static int64_t virtual_time_ns; /* last set virtual time */ + +static const void *time_handle; + +typedef struct { +uint64_t total_insn; +uint64_t quantum_insn; /* insn in last quantum */ +int64_t last_quantum_time; /* time when last quantum started */ +} vCPUTime; + +struct qemu_plugin_scoreboard *vcpus; + +/* return epoch time in ns */ +static int64_t now_ns(void) +{ +return g_get_real_time() * 1000; +} + +static uint64_t num_insn_during(int64_t elapsed_ns) +{ +double num_secs = elapsed_ns / (double) NSEC_IN_ONE_SEC; +return num_secs * (double) max_insn_per_second; +} + +static int64_t time_for_insn(uint64_t num_insn) +{ +double num_secs = (double) num_insn / (double) max_insn_per_second; +return num_secs * (double) NSEC_IN_ONE_SEC; +} + +static void update_system_time(vCPUTime *vcpu) +{ +int64_t elapsed_ns = now_ns() - vcpu->last_quantum_time; +uint64_t max_insn = num_insn_during(elapsed_ns); + +if (vcpu->quantum_insn >= max_insn) { +/* this vcpu ran faster than expected, so it has to sleep */ +uint64_t insn_advance = vcpu->quantum_insn - max_insn; +uint64_t time_advance_ns = time_for_insn(insn_advance); +int64_t sleep_us = time_advance_ns / 1000; +g_usleep(sleep_us); +} + +vcpu->total_insn += vcpu->quantum_insn; +vcpu->quantum_insn = 0; +vcpu->last_quantum_time = now_ns(); + +/* based on total number of instructions, what should be the new time? */ +int64_t new_virtual_time = time_for_insn(vcpu->total_insn); + +g_mutex_lock(_state_lock); + +/* Time only moves forward. Another vcpu might have updated it already. */ +if (new_virtual_time > virtual_time_ns) { +qemu_plugin_update_ns(time_handle, new_virtual_time); +virtual_time_ns = new_virtual_time; +} + +g_mutex_unlock(_state_lock); +} + +static void vcpu_init(qemu_plugin_id_t id, unsigned int cpu_index) +{ +vCPUTime *vcpu = qemu_plugin_scoreboard_find(vcpus, cpu_index); +vcpu->total_insn = 0; +vcpu->quantum_insn = 0; +vcpu->last_quantum_time = now_ns(); +} + +static void vcpu_exit(qemu_plugin_id_t id, unsigned int cpu_index) +{ +vCPUTime *vcpu = qemu_plugin_scoreboard_find(vcpus, cpu_index); +update_system_time(vcpu); +} + +static void every_quantum_insn(unsigned int cpu_index, void *udata) +{ +vCPUTime *vcpu = qemu_plugin_scoreboard_find(vcpus, cpu_index); +g_assert(vcpu->quantum_ins
[PATCH v3 5/6] plugins: add time control API
From: Alex Bennée Expose the ability to control time through the plugin API. Only one plugin can control time so it has to request control when loaded. There are probably more corner cases to catch here. From: Alex Bennée Signed-off-by: Alex Bennée Signed-off-by: Pierrick Bouvier --- include/qemu/qemu-plugin.h | 23 +++ plugins/api.c| 31 +++ plugins/qemu-plugins.symbols | 2 ++ 3 files changed, 56 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 95703d8fec1..80b1637cede 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -661,6 +661,29 @@ void qemu_plugin_register_vcpu_mem_inline_per_vcpu( qemu_plugin_u64 entry, uint64_t imm); +/** + * qemu_plugin_request_time_control() - request the ability to control time + * + * This grants the plugin the ability to control system time. Only one + * plugin can control time so if multiple plugins request the ability + * all but the first will fail. + * + * Returns an opaque handle or NULL if fails + */ +const void *qemu_plugin_request_time_control(void); + +/** + * qemu_plugin_update_ns() - update system emulation time + * @handle: opaque handle returned by qemu_plugin_request_time_control() + * @time: time in nanoseconds + * + * This allows an appropriately authorised plugin (i.e. holding the + * time control handle) to move system time forward to @time. + * + * Start time is 0. + */ +void qemu_plugin_update_ns(const void *handle, int64_t time); + typedef void (*qemu_plugin_vcpu_syscall_cb_t)(qemu_plugin_id_t id, unsigned int vcpu_index, int64_t num, uint64_t a1, uint64_t a2, diff --git a/plugins/api.c b/plugins/api.c index 5a0a7f8c712..26822b69ea2 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -39,6 +39,7 @@ #include "qemu/main-loop.h" #include "qemu/plugin.h" #include "qemu/log.h" +#include "qemu/timer.h" #include "tcg/tcg.h" #include "exec/exec-all.h" #include "exec/gdbstub.h" @@ -583,3 +584,33 @@ uint64_t qemu_plugin_u64_sum(qemu_plugin_u64 entry) } return total; } + +/* + * Time control + */ +static bool has_control; + +const void *qemu_plugin_request_time_control(void) +{ +if (!has_control) { +has_control = true; +return _control; +} +return NULL; +} + +static void advance_virtual_time__async(CPUState *cpu, run_on_cpu_data data) +{ +int64_t new_time = data.host_ulong; +qemu_clock_advance_virtual_time(new_time); +} + +void qemu_plugin_update_ns(const void *handle, int64_t new_time) +{ +if (handle == _control) { +/* Need to execute out of cpu_exec, so bql can be locked. */ +async_run_on_cpu(current_cpu, + advance_virtual_time__async, + RUN_ON_CPU_HOST_ULONG(new_time)); +} +} diff --git a/plugins/qemu-plugins.symbols b/plugins/qemu-plugins.symbols index aa0a77a319f..ca773d8d9fe 100644 --- a/plugins/qemu-plugins.symbols +++ b/plugins/qemu-plugins.symbols @@ -38,6 +38,7 @@ qemu_plugin_register_vcpu_tb_exec_cond_cb; qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu; qemu_plugin_register_vcpu_tb_trans_cb; + qemu_plugin_request_time_control; qemu_plugin_reset; qemu_plugin_scoreboard_free; qemu_plugin_scoreboard_find; @@ -51,5 +52,6 @@ qemu_plugin_u64_set; qemu_plugin_u64_sum; qemu_plugin_uninstall; + qemu_plugin_update_ns; qemu_plugin_vcpu_for_each; }; -- 2.39.2
[PATCH v3 2/6] qtest: use cpu interface in qtest_clock_warp
From: Alex Bennée This generalises the qtest_clock_warp code to use the AccelOps handlers for updating its own sense of time. This will make the next patch which moves the warp code closer to pure code motion. From: Alex Bennée Signed-off-by: Alex Bennée Acked-by: Thomas Huth Signed-off-by: Pierrick Bouvier --- include/sysemu/qtest.h | 1 + accel/qtest/qtest.c| 1 + system/qtest.c | 6 +++--- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/sysemu/qtest.h b/include/sysemu/qtest.h index b5d5fd34637..45f3b7e1df5 100644 --- a/include/sysemu/qtest.h +++ b/include/sysemu/qtest.h @@ -36,6 +36,7 @@ void qtest_server_set_send_handler(void (*send)(void *, const char *), void qtest_server_inproc_recv(void *opaque, const char *buf); int64_t qtest_get_virtual_clock(void); +void qtest_set_virtual_clock(int64_t count); #endif #endif diff --git a/accel/qtest/qtest.c b/accel/qtest/qtest.c index f6056ac8361..53182e6c2ae 100644 --- a/accel/qtest/qtest.c +++ b/accel/qtest/qtest.c @@ -52,6 +52,7 @@ static void qtest_accel_ops_class_init(ObjectClass *oc, void *data) ops->create_vcpu_thread = dummy_start_vcpu_thread; ops->get_virtual_clock = qtest_get_virtual_clock; +ops->set_virtual_clock = qtest_set_virtual_clock; }; static const TypeInfo qtest_accel_ops_type = { diff --git a/system/qtest.c b/system/qtest.c index 6da58b3874e..ee8b139e982 100644 --- a/system/qtest.c +++ b/system/qtest.c @@ -332,14 +332,14 @@ int64_t qtest_get_virtual_clock(void) return qatomic_read_i64(_clock_counter); } -static void qtest_set_virtual_clock(int64_t count) +void qtest_set_virtual_clock(int64_t count) { qatomic_set_i64(_clock_counter, count); } static void qtest_clock_warp(int64_t dest) { -int64_t clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +int64_t clock = cpus_get_virtual_clock(); AioContext *aio_context; assert(qtest_enabled()); aio_context = qemu_get_aio_context(); @@ -348,7 +348,7 @@ static void qtest_clock_warp(int64_t dest) QEMU_TIMER_ATTR_ALL); int64_t warp = qemu_soonest_timeout(dest - clock, deadline); -qtest_set_virtual_clock(qtest_get_virtual_clock() + warp); +cpus_set_virtual_clock(cpus_get_virtual_clock() + warp); qemu_clock_run_timers(QEMU_CLOCK_VIRTUAL); timerlist_run_timers(aio_context->tlg.tl[QEMU_CLOCK_VIRTUAL]); -- 2.39.2
[PATCH v2 6/6] contrib/plugins: add ips plugin example for cost modeling
This plugin uses the new time control interface to make decisions about the state of time during the emulation. The algorithm is currently very simple. The user specifies an ips rate which applies per core. If the core runs ahead of its allocated execution time the plugin sleeps for a bit to let real time catch up. Either way time is updated for the emulation as a function of total executed instructions with some adjustments for cores that idle. Examples Slow down execution of /bin/true: $ num_insn=$(./build/qemu-x86_64 -plugin ./build/tests/plugin/libinsn.so -d plugin /bin/true |& grep total | sed -e 's/.*: //') $ time ./build/qemu-x86_64 -plugin ./build/contrib/plugins/libips.so,ips=$(($num_insn/4)) /bin/true real 4.000s Boot a Linux kernel simulating a 250MHz cpu: $ /build/qemu-system-x86_64 -kernel /boot/vmlinuz-6.1.0-21-amd64 -append "console=ttyS0" -plugin ./build/contrib/plugins/libips.so,ips=$((250*1000*1000)) -smp 1 -m 512 check time until kernel panic on serial0 Signed-off-by: Pierrick Bouvier --- contrib/plugins/ips.c| 239 +++ contrib/plugins/Makefile | 1 + 2 files changed, 240 insertions(+) create mode 100644 contrib/plugins/ips.c diff --git a/contrib/plugins/ips.c b/contrib/plugins/ips.c new file mode 100644 index 000..cf3159df391 --- /dev/null +++ b/contrib/plugins/ips.c @@ -0,0 +1,239 @@ +/* + * ips rate limiting plugin. + * + * This plugin can be used to restrict the execution of a system to a + * particular number of Instructions Per Second (ips). This controls + * time as seen by the guest so while wall-clock time may be longer + * from the guests point of view time will pass at the normal rate. + * + * This uses the new plugin API which allows the plugin to control + * system time. + * + * Copyright (c) 2023 Linaro Ltd + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include + +QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; + +/* how many times do we update time per sec */ +#define NUM_TIME_UPDATE_PER_SEC 10 +#define NSEC_IN_ONE_SEC (1000 * 1000 * 1000) + +static GMutex global_state_lock; + +static uint64_t insn_per_second = 1000 * 1000; /* ips per core, per second */ +static uint64_t insn_quantum; /* trap every N instructions */ +static bool precise_execution; /* count every instruction */ +static int64_t start_time_ns; /* time (ns since epoch) first vCPU started */ +static int64_t virtual_time_ns; /* last set virtual time */ + +static const void *time_handle; + +typedef enum { +UNKNOWN = 0, +EXECUTING, +IDLE, +FINISHED +} vCPUState; + +typedef struct { +uint64_t counter; +uint64_t track_insn; +vCPUState state; +/* timestamp when vCPU entered state */ +int64_t last_state_time; +} vCPUTime; + +struct qemu_plugin_scoreboard *vcpus; + +/* return epoch time in ns */ +static int64_t now_ns(void) +{ +return g_get_real_time() * 1000; +} + +static uint64_t num_insn_during(int64_t elapsed_ns) +{ +double num_secs = elapsed_ns / (double) NSEC_IN_ONE_SEC; +return num_secs * (double) insn_per_second; +} + +static int64_t time_for_insn(uint64_t num_insn) +{ +double num_secs = (double) num_insn / (double) insn_per_second; +return num_secs * (double) NSEC_IN_ONE_SEC; +} + +static int64_t uptime_ns(void) +{ +int64_t now = now_ns(); +g_assert(now >= start_time_ns); +return now - start_time_ns; +} + +static void vcpu_set_state(vCPUTime *vcpu, vCPUState new_state) +{ +vcpu->last_state_time = now_ns(); +vcpu->state = new_state; +} + +static void update_system_time(vCPUTime *vcpu) +{ +/* flush remaining instructions */ +vcpu->counter += vcpu->track_insn; +vcpu->track_insn = 0; + +int64_t uptime = uptime_ns(); +uint64_t expected_insn = num_insn_during(uptime); + +if (vcpu->counter >= expected_insn) { +/* this vcpu ran faster than expected, so it has to sleep */ +uint64_t insn_advance = vcpu->counter - expected_insn; +uint64_t time_advance_ns = time_for_insn(insn_advance); +int64_t sleep_us = time_advance_ns / 1000; +g_usleep(sleep_us); +} + +/* based on number of instructions, what should be the new time? */ +int64_t new_virtual_time = time_for_insn(vcpu->counter); + +g_mutex_lock(_state_lock); + +/* Time only moves forward. Another vcpu might have updated it already. */ +if (new_virtual_time > virtual_time_ns) { +qemu_plugin_update_ns(time_handle, new_virtual_time); +virtual_time_ns = new_virtual_time; +} + +g_mutex_unlock(_state_lock); +} + +static void set_start_time() +{ +g_mutex_lock(_state_lock); +if (!start_time_ns) { +start_time_ns = now_ns(); +} +g_mutex_unlock(_state_lock); +} + +static void vcpu_init(qemu_plugin_id_t id, unsigned int cpu_index) +{ +vCPUTime *vcpu = qemu_plugin_scoreboard_find(vcpus, cpu
[PATCH v2 5/6] plugins: add time control API
From: Alex Bennée Expose the ability to control time through the plugin API. Only one plugin can control time so it has to request control when loaded. There are probably more corner cases to catch here. From: Alex Bennée Signed-off-by: Alex Bennée Signed-off-by: Pierrick Bouvier --- include/qemu/qemu-plugin.h | 23 +++ plugins/api.c| 31 +++ plugins/qemu-plugins.symbols | 2 ++ 3 files changed, 56 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 95703d8fec1..80b1637cede 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -661,6 +661,29 @@ void qemu_plugin_register_vcpu_mem_inline_per_vcpu( qemu_plugin_u64 entry, uint64_t imm); +/** + * qemu_plugin_request_time_control() - request the ability to control time + * + * This grants the plugin the ability to control system time. Only one + * plugin can control time so if multiple plugins request the ability + * all but the first will fail. + * + * Returns an opaque handle or NULL if fails + */ +const void *qemu_plugin_request_time_control(void); + +/** + * qemu_plugin_update_ns() - update system emulation time + * @handle: opaque handle returned by qemu_plugin_request_time_control() + * @time: time in nanoseconds + * + * This allows an appropriately authorised plugin (i.e. holding the + * time control handle) to move system time forward to @time. + * + * Start time is 0. + */ +void qemu_plugin_update_ns(const void *handle, int64_t time); + typedef void (*qemu_plugin_vcpu_syscall_cb_t)(qemu_plugin_id_t id, unsigned int vcpu_index, int64_t num, uint64_t a1, uint64_t a2, diff --git a/plugins/api.c b/plugins/api.c index 5a0a7f8c712..26822b69ea2 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -39,6 +39,7 @@ #include "qemu/main-loop.h" #include "qemu/plugin.h" #include "qemu/log.h" +#include "qemu/timer.h" #include "tcg/tcg.h" #include "exec/exec-all.h" #include "exec/gdbstub.h" @@ -583,3 +584,33 @@ uint64_t qemu_plugin_u64_sum(qemu_plugin_u64 entry) } return total; } + +/* + * Time control + */ +static bool has_control; + +const void *qemu_plugin_request_time_control(void) +{ +if (!has_control) { +has_control = true; +return _control; +} +return NULL; +} + +static void advance_virtual_time__async(CPUState *cpu, run_on_cpu_data data) +{ +int64_t new_time = data.host_ulong; +qemu_clock_advance_virtual_time(new_time); +} + +void qemu_plugin_update_ns(const void *handle, int64_t new_time) +{ +if (handle == _control) { +/* Need to execute out of cpu_exec, so bql can be locked. */ +async_run_on_cpu(current_cpu, + advance_virtual_time__async, + RUN_ON_CPU_HOST_ULONG(new_time)); +} +} diff --git a/plugins/qemu-plugins.symbols b/plugins/qemu-plugins.symbols index aa0a77a319f..ca773d8d9fe 100644 --- a/plugins/qemu-plugins.symbols +++ b/plugins/qemu-plugins.symbols @@ -38,6 +38,7 @@ qemu_plugin_register_vcpu_tb_exec_cond_cb; qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu; qemu_plugin_register_vcpu_tb_trans_cb; + qemu_plugin_request_time_control; qemu_plugin_reset; qemu_plugin_scoreboard_free; qemu_plugin_scoreboard_find; @@ -51,5 +52,6 @@ qemu_plugin_u64_set; qemu_plugin_u64_sum; qemu_plugin_uninstall; + qemu_plugin_update_ns; qemu_plugin_vcpu_for_each; }; -- 2.39.2
[PATCH v2 2/6] qtest: use cpu interface in qtest_clock_warp
From: Alex Bennée This generalises the qtest_clock_warp code to use the AccelOps handlers for updating its own sense of time. This will make the next patch which moves the warp code closer to pure code motion. From: Alex Bennée Signed-off-by: Alex Bennée Acked-by: Thomas Huth Signed-off-by: Pierrick Bouvier --- include/sysemu/qtest.h | 1 + accel/qtest/qtest.c| 1 + system/qtest.c | 6 +++--- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/sysemu/qtest.h b/include/sysemu/qtest.h index b5d5fd34637..45f3b7e1df5 100644 --- a/include/sysemu/qtest.h +++ b/include/sysemu/qtest.h @@ -36,6 +36,7 @@ void qtest_server_set_send_handler(void (*send)(void *, const char *), void qtest_server_inproc_recv(void *opaque, const char *buf); int64_t qtest_get_virtual_clock(void); +void qtest_set_virtual_clock(int64_t count); #endif #endif diff --git a/accel/qtest/qtest.c b/accel/qtest/qtest.c index f6056ac8361..53182e6c2ae 100644 --- a/accel/qtest/qtest.c +++ b/accel/qtest/qtest.c @@ -52,6 +52,7 @@ static void qtest_accel_ops_class_init(ObjectClass *oc, void *data) ops->create_vcpu_thread = dummy_start_vcpu_thread; ops->get_virtual_clock = qtest_get_virtual_clock; +ops->set_virtual_clock = qtest_set_virtual_clock; }; static const TypeInfo qtest_accel_ops_type = { diff --git a/system/qtest.c b/system/qtest.c index 6da58b3874e..ee8b139e982 100644 --- a/system/qtest.c +++ b/system/qtest.c @@ -332,14 +332,14 @@ int64_t qtest_get_virtual_clock(void) return qatomic_read_i64(_clock_counter); } -static void qtest_set_virtual_clock(int64_t count) +void qtest_set_virtual_clock(int64_t count) { qatomic_set_i64(_clock_counter, count); } static void qtest_clock_warp(int64_t dest) { -int64_t clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +int64_t clock = cpus_get_virtual_clock(); AioContext *aio_context; assert(qtest_enabled()); aio_context = qemu_get_aio_context(); @@ -348,7 +348,7 @@ static void qtest_clock_warp(int64_t dest) QEMU_TIMER_ATTR_ALL); int64_t warp = qemu_soonest_timeout(dest - clock, deadline); -qtest_set_virtual_clock(qtest_get_virtual_clock() + warp); +cpus_set_virtual_clock(cpus_get_virtual_clock() + warp); qemu_clock_run_timers(QEMU_CLOCK_VIRTUAL); timerlist_run_timers(aio_context->tlg.tl[QEMU_CLOCK_VIRTUAL]); -- 2.39.2
[PATCH v2 3/6] sysemu: generalise qtest_warp_clock as qemu_clock_advance_virtual_time
From: Alex Bennée Move the key functionality of moving time forward into the clock sub-system itself. This will allow us to plumb in time control into plugins. From: Alex Bennée Signed-off-by: Alex Bennée Signed-off-by: Pierrick Bouvier --- include/qemu/timer.h | 15 +++ system/qtest.c | 25 +++-- util/qemu-timer.c| 26 ++ 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/include/qemu/timer.h b/include/qemu/timer.h index 9a366e551fb..910587d8293 100644 --- a/include/qemu/timer.h +++ b/include/qemu/timer.h @@ -245,6 +245,21 @@ bool qemu_clock_run_timers(QEMUClockType type); */ bool qemu_clock_run_all_timers(void); +/** + * qemu_clock_advance_virtual_time(): advance the virtual time tick + * @target: target time in nanoseconds + * + * This function is used where the control of the flow of time has + * been delegated to outside the clock subsystem (be it qtest, icount + * or some other external source). You can ask the clock system to + * return @early at the first expired timer. + * + * Time can only move forward, attempts to reverse time would lead to + * an error. + * + * Returns: new virtual time. + */ +int64_t qemu_clock_advance_virtual_time(int64_t dest); /* * QEMUTimerList diff --git a/system/qtest.c b/system/qtest.c index ee8b139e982..e6f6b4e62d5 100644 --- a/system/qtest.c +++ b/system/qtest.c @@ -337,26 +337,6 @@ void qtest_set_virtual_clock(int64_t count) qatomic_set_i64(_clock_counter, count); } -static void qtest_clock_warp(int64_t dest) -{ -int64_t clock = cpus_get_virtual_clock(); -AioContext *aio_context; -assert(qtest_enabled()); -aio_context = qemu_get_aio_context(); -while (clock < dest) { -int64_t deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL, - QEMU_TIMER_ATTR_ALL); -int64_t warp = qemu_soonest_timeout(dest - clock, deadline); - -cpus_set_virtual_clock(cpus_get_virtual_clock() + warp); - -qemu_clock_run_timers(QEMU_CLOCK_VIRTUAL); -timerlist_run_timers(aio_context->tlg.tl[QEMU_CLOCK_VIRTUAL]); -clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); -} -qemu_clock_notify(QEMU_CLOCK_VIRTUAL); -} - static bool (*process_command_cb)(CharBackend *chr, gchar **words); void qtest_set_command_cb(bool (*pc_cb)(CharBackend *chr, gchar **words)) @@ -755,7 +735,8 @@ static void qtest_process_command(CharBackend *chr, gchar **words) ns = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL, QEMU_TIMER_ATTR_ALL); } -qtest_clock_warp(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns); +qemu_clock_advance_virtual_time( +qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns); qtest_send_prefix(chr); qtest_sendf(chr, "OK %"PRIi64"\n", (int64_t)qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)); @@ -781,7 +762,7 @@ static void qtest_process_command(CharBackend *chr, gchar **words) g_assert(words[1]); ret = qemu_strtoi64(words[1], NULL, 0, ); g_assert(ret == 0); -qtest_clock_warp(ns); +qemu_clock_advance_virtual_time(ns); qtest_send_prefix(chr); qtest_sendf(chr, "OK %"PRIi64"\n", (int64_t)qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)); diff --git a/util/qemu-timer.c b/util/qemu-timer.c index 6a0de33dd2b..213114be68c 100644 --- a/util/qemu-timer.c +++ b/util/qemu-timer.c @@ -645,6 +645,11 @@ int64_t qemu_clock_get_ns(QEMUClockType type) } } +static void qemu_virtual_clock_set_ns(int64_t time) +{ +return cpus_set_virtual_clock(time); +} + void init_clocks(QEMUTimerListNotifyCB *notify_cb) { QEMUClockType type; @@ -675,3 +680,24 @@ bool qemu_clock_run_all_timers(void) return progress; } + +int64_t qemu_clock_advance_virtual_time(int64_t dest) +{ +int64_t clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +AioContext *aio_context; +aio_context = qemu_get_aio_context(); +while (clock < dest) { +int64_t deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL, + QEMU_TIMER_ATTR_ALL); +int64_t warp = qemu_soonest_timeout(dest - clock, deadline); + +qemu_virtual_clock_set_ns(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + warp); + +qemu_clock_run_timers(QEMU_CLOCK_VIRTUAL); +timerlist_run_timers(aio_context->tlg.tl[QEMU_CLOCK_VIRTUAL]); +clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +} +qemu_clock_notify(QEMU_CLOCK_VIRTUAL); + +return clock; +} -- 2.39.2
[PATCH v2 4/6] qtest: move qtest_{get, set}_virtual_clock to accel/qtest/qtest.c
Signed-off-by: Pierrick Bouvier --- include/sysemu/qtest.h | 3 --- accel/qtest/qtest.c| 12 system/qtest.c | 12 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/include/sysemu/qtest.h b/include/sysemu/qtest.h index 45f3b7e1df5..c161d751650 100644 --- a/include/sysemu/qtest.h +++ b/include/sysemu/qtest.h @@ -34,9 +34,6 @@ void qtest_server_init(const char *qtest_chrdev, const char *qtest_log, Error ** void qtest_server_set_send_handler(void (*send)(void *, const char *), void *opaque); void qtest_server_inproc_recv(void *opaque, const char *buf); - -int64_t qtest_get_virtual_clock(void); -void qtest_set_virtual_clock(int64_t count); #endif #endif diff --git a/accel/qtest/qtest.c b/accel/qtest/qtest.c index 53182e6c2ae..bf14032d294 100644 --- a/accel/qtest/qtest.c +++ b/accel/qtest/qtest.c @@ -24,6 +24,18 @@ #include "qemu/main-loop.h" #include "hw/core/cpu.h" +static int64_t qtest_clock_counter; + +static int64_t qtest_get_virtual_clock(void) +{ +return qatomic_read_i64(_clock_counter); +} + +static void qtest_set_virtual_clock(int64_t count) +{ +qatomic_set_i64(_clock_counter, count); +} + static int qtest_init_accel(MachineState *ms) { return 0; diff --git a/system/qtest.c b/system/qtest.c index e6f6b4e62d5..ba210780ec0 100644 --- a/system/qtest.c +++ b/system/qtest.c @@ -325,18 +325,6 @@ static void qtest_irq_handler(void *opaque, int n, int level) } } -static int64_t qtest_clock_counter; - -int64_t qtest_get_virtual_clock(void) -{ -return qatomic_read_i64(_clock_counter); -} - -void qtest_set_virtual_clock(int64_t count) -{ -qatomic_set_i64(_clock_counter, count); -} - static bool (*process_command_cb)(CharBackend *chr, gchar **words); void qtest_set_command_cb(bool (*pc_cb)(CharBackend *chr, gchar **words)) -- 2.39.2
[PATCH v2 1/6] sysemu: add set_virtual_time to accel ops
From: Alex Bennée We are about to remove direct calls to individual accelerators for this information and will need a central point for plugins to hook into time changes. From: Alex Bennée Signed-off-by: Alex Bennée Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Pierrick Bouvier --- include/sysemu/accel-ops.h | 18 +- include/sysemu/cpu-timers.h| 3 ++- ...et-virtual-clock.c => cpus-virtual-clock.c} | 5 + system/cpus.c | 11 +++ stubs/meson.build | 2 +- 5 files changed, 36 insertions(+), 3 deletions(-) rename stubs/{cpus-get-virtual-clock.c => cpus-virtual-clock.c} (68%) diff --git a/include/sysemu/accel-ops.h b/include/sysemu/accel-ops.h index ef91fc28bbd..a0886722305 100644 --- a/include/sysemu/accel-ops.h +++ b/include/sysemu/accel-ops.h @@ -20,7 +20,12 @@ typedef struct AccelOpsClass AccelOpsClass; DECLARE_CLASS_CHECKERS(AccelOpsClass, ACCEL_OPS, TYPE_ACCEL_OPS) -/* cpus.c operations interface */ +/** + * struct AccelOpsClass - accelerator interfaces + * + * This structure is used to abstract accelerator differences from the + * core CPU code. Not all have to be implemented. + */ struct AccelOpsClass { /*< private >*/ ObjectClass parent_class; @@ -44,7 +49,18 @@ struct AccelOpsClass { void (*handle_interrupt)(CPUState *cpu, int mask); +/** + * @get_virtual_clock: fetch virtual clock + * @set_virtual_clock: set virtual clock + * + * These allow the timer subsystem to defer to the accelerator to + * fetch time. The set function is needed if the accelerator wants + * to track the changes to time as the timer is warped through + * various timer events. + */ int64_t (*get_virtual_clock)(void); +void (*set_virtual_clock)(int64_t time); + int64_t (*get_elapsed_ticks)(void); /* gdbstub hooks */ diff --git a/include/sysemu/cpu-timers.h b/include/sysemu/cpu-timers.h index d86738a378d..7bfa960fbd6 100644 --- a/include/sysemu/cpu-timers.h +++ b/include/sysemu/cpu-timers.h @@ -96,8 +96,9 @@ int64_t cpu_get_clock(void); void qemu_timer_notify_cb(void *opaque, QEMUClockType type); -/* get the VIRTUAL clock and VM elapsed ticks via the cpus accel interface */ +/* get/set VIRTUAL clock and VM elapsed ticks via the cpus accel interface */ int64_t cpus_get_virtual_clock(void); +void cpus_set_virtual_clock(int64_t new_time); int64_t cpus_get_elapsed_ticks(void); #endif /* SYSEMU_CPU_TIMERS_H */ diff --git a/stubs/cpus-get-virtual-clock.c b/stubs/cpus-virtual-clock.c similarity index 68% rename from stubs/cpus-get-virtual-clock.c rename to stubs/cpus-virtual-clock.c index fd447d53f3c..af7c1a1d403 100644 --- a/stubs/cpus-get-virtual-clock.c +++ b/stubs/cpus-virtual-clock.c @@ -6,3 +6,8 @@ int64_t cpus_get_virtual_clock(void) { return cpu_get_clock(); } + +void cpus_set_virtual_clock(int64_t new_time) +{ +/* do nothing */ +} diff --git a/system/cpus.c b/system/cpus.c index f8fa78f33d4..d3640c95030 100644 --- a/system/cpus.c +++ b/system/cpus.c @@ -229,6 +229,17 @@ int64_t cpus_get_virtual_clock(void) return cpu_get_clock(); } +/* + * Signal the new virtual time to the accelerator. This is only needed + * by accelerators that need to track the changes as we warp time. + */ +void cpus_set_virtual_clock(int64_t new_time) +{ +if (cpus_accel && cpus_accel->set_virtual_clock) { +cpus_accel->set_virtual_clock(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 diff --git a/stubs/meson.build b/stubs/meson.build index 3b9d42023cb..a1deafde08c 100644 --- a/stubs/meson.build +++ b/stubs/meson.build @@ -28,7 +28,7 @@ endif if have_block or have_ga stub_ss.add(files('replay-tools.c')) # stubs for hooks in util/main-loop.c, util/async.c etc. - stub_ss.add(files('cpus-get-virtual-clock.c')) + stub_ss.add(files('cpus-virtual-clock.c')) stub_ss.add(files('icount.c')) stub_ss.add(files('graph-lock.c')) if linux_io_uring.found() -- 2.39.2
[PATCH v2 0/6] Implement icount=auto using TCG Plugins
The goal here is to be able to scale temporally execution of qemu-user/system, using a given number of instructions per second. We define a virtual clock, that can be late or in advance compared to real time. When we are in advance, we slow execution (by sleeping) until catching real time. Finally, we should be able to cleanup icount=auto mode completely, and keep icount usage for determistic purposes only. It is built upon new TCG Plugins inline ops (store + conditional callbacks), now merged on master. Example in user-mode: - Retrieve number of instructions to execute /bin/true $ ./build/qemu-x86_64 -plugin ./build/tests/plugin/libinsn.so -d plugin /bin/true cpu 0 insns: 120546 total insns: 120546 - Slow execution to match 5 seconds $ time ./build/qemu-x86_64 -plugin ./build/contrib/plugins/libips,ips=$((120546/5)) /bin/true real0m4.985s v2 -- - Added missing personal Signed-off-by for commits from Alex - Fix bad rebase in stubs/meson.build - move qtest_{get,set}_virtual_clock to accel/qtest/qtest.c - A race condition was identified for plugins init/idle/resume, but is not related to this series, and will be fixed in another one: https://lore.kernel.org/qemu-devel/20240529152219.825680-1-alex.ben...@linaro.org/ Alex Bennée (4): sysemu: add set_virtual_time to accel ops qtest: use cpu interface in qtest_clock_warp sysemu: generalise qtest_warp_clock as qemu_clock_advance_virtual_time plugins: add time control API Pierrick Bouvier (2): qtest: move qtest_{get,set}_virtual_clock to accel/qtest/qtest.c contrib/plugins: add ips plugin example for cost modeling include/qemu/qemu-plugin.h| 23 ++ include/qemu/timer.h | 15 ++ include/sysemu/accel-ops.h| 18 +- include/sysemu/cpu-timers.h | 3 +- include/sysemu/qtest.h| 2 - accel/qtest/qtest.c | 13 + contrib/plugins/ips.c | 239 ++ plugins/api.c | 31 +++ ...t-virtual-clock.c => cpus-virtual-clock.c} | 5 + system/cpus.c | 11 + system/qtest.c| 37 +-- util/qemu-timer.c | 26 ++ contrib/plugins/Makefile | 1 + plugins/qemu-plugins.symbols | 2 + stubs/meson.build | 2 +- 15 files changed, 389 insertions(+), 39 deletions(-) create mode 100644 contrib/plugins/ips.c rename stubs/{cpus-get-virtual-clock.c => cpus-virtual-clock.c} (68%) -- 2.39.2
Re: [PATCH 2/5] qtest: use cpu interface in qtest_clock_warp
On 5/29/24 23:32, Paolo Bonzini wrote: On Fri, May 17, 2024 at 12:21 AM Pierrick Bouvier wrote: From: Alex Bennée This generalises the qtest_clock_warp code to use the AccelOps handlers for updating its own sense of time. This will make the next patch which moves the warp code closer to pure code motion. From: Alex Bennée Signed-off-by: Alex Bennée Acked-by: Thomas Huth --- include/sysemu/qtest.h | 1 + accel/qtest/qtest.c| 1 + system/qtest.c | 6 +++--- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/sysemu/qtest.h b/include/sysemu/qtest.h index b5d5fd34637..45f3b7e1df5 100644 --- a/include/sysemu/qtest.h +++ b/include/sysemu/qtest.h @@ -36,6 +36,7 @@ void qtest_server_set_send_handler(void (*send)(void *, const char *), void qtest_server_inproc_recv(void *opaque, const char *buf); int64_t qtest_get_virtual_clock(void); +void qtest_set_virtual_clock(int64_t count); You can move qtest_get_virtual_clock/qtest_set_virtual_clock to accel/qtest/qtest.c instead, and make them static. They are not used anymore in system/qtest.c, and it actually makes a lot more sense that they aren't. Changed for next revision, thanks Paolo #endif #endif diff --git a/accel/qtest/qtest.c b/accel/qtest/qtest.c index f6056ac8361..53182e6c2ae 100644 --- a/accel/qtest/qtest.c +++ b/accel/qtest/qtest.c @@ -52,6 +52,7 @@ static void qtest_accel_ops_class_init(ObjectClass *oc, void *data) ops->create_vcpu_thread = dummy_start_vcpu_thread; ops->get_virtual_clock = qtest_get_virtual_clock; +ops->set_virtual_clock = qtest_set_virtual_clock; }; static const TypeInfo qtest_accel_ops_type = { diff --git a/system/qtest.c b/system/qtest.c index 6da58b3874e..ee8b139e982 100644 --- a/system/qtest.c +++ b/system/qtest.c @@ -332,14 +332,14 @@ int64_t qtest_get_virtual_clock(void) return qatomic_read_i64(_clock_counter); } -static void qtest_set_virtual_clock(int64_t count) +void qtest_set_virtual_clock(int64_t count) { qatomic_set_i64(_clock_counter, count); } static void qtest_clock_warp(int64_t dest) { -int64_t clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +int64_t clock = cpus_get_virtual_clock(); AioContext *aio_context; assert(qtest_enabled()); aio_context = qemu_get_aio_context(); @@ -348,7 +348,7 @@ static void qtest_clock_warp(int64_t dest) QEMU_TIMER_ATTR_ALL); int64_t warp = qemu_soonest_timeout(dest - clock, deadline); -qtest_set_virtual_clock(qtest_get_virtual_clock() + warp); +cpus_set_virtual_clock(cpus_get_virtual_clock() + warp); qemu_clock_run_timers(QEMU_CLOCK_VIRTUAL); timerlist_run_timers(aio_context->tlg.tl[QEMU_CLOCK_VIRTUAL]); -- 2.39.2
Re: [RFC PATCH] cpus: split qemu_init_vcpu and delay vCPU thread creation
On 5/29/24 08:22, Alex Bennée wrote: This ensures we don't start the thread until cpu_common_realizefn has finished. This ensures that plugins will always run qemu_plugin_vcpu_init__async first before any other states. It doesn't totally eliminate the race that plugin_cpu_update__locked has to work around though. I found this while reviewing the ips plugin which makes heavy use of the vcpu phase callbacks. An alternative might be to move the explicit creation of vCPU threads to qdev_machine_creation_done()? It doesn't affect user-mode which already has a thread to execute in and ensures the QOM object has completed creation in cpu_create() before continuing. Signed-off-by: Alex Bennée Cc: Pierrick Bouvier Cc: Philippe Mathieu-Daudé --- include/hw/core/cpu.h | 8 accel/tcg/user-exec-stub.c | 5 + hw/core/cpu-common.c | 7 ++- plugins/core.c | 5 + system/cpus.c | 15 ++- 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h index bb398e8237..6920699585 100644 --- a/include/hw/core/cpu.h +++ b/include/hw/core/cpu.h @@ -1041,6 +1041,14 @@ void end_exclusive(void); */ void qemu_init_vcpu(CPUState *cpu); +/** + * qemu_start_vcpu: + * @cpu: The vCPU to start. + * + * Create the vCPU thread and start it running. + */ +void qemu_start_vcpu(CPUState *cpu); + #define SSTEP_ENABLE 0x1 /* Enable simulated HW single stepping */ #define SSTEP_NOIRQ 0x2 /* Do not use IRQ while single stepping */ #define SSTEP_NOTIMER 0x4 /* Do not Timers while single stepping */ diff --git a/accel/tcg/user-exec-stub.c b/accel/tcg/user-exec-stub.c index 4fbe2dbdc8..162bb72bbe 100644 --- a/accel/tcg/user-exec-stub.c +++ b/accel/tcg/user-exec-stub.c @@ -18,6 +18,11 @@ void cpu_exec_reset_hold(CPUState *cpu) { } +void qemu_start_vcpu(CPUState *cpu) +{ +/* NOP for user-mode, we already have a thread */ +} + /* User mode emulation does not support record/replay yet. */ bool replay_exception(void) diff --git a/hw/core/cpu-common.c b/hw/core/cpu-common.c index 0f0a247f56..68895ddd59 100644 --- a/hw/core/cpu-common.c +++ b/hw/core/cpu-common.c @@ -230,7 +230,12 @@ static void cpu_common_realizefn(DeviceState *dev, Error **errp) } #endif -/* NOTE: latest generic point where the cpu is fully realized */ +/* + * With everything set up we can finally start the vCPU thread. + * This is a NOP for linux-user. + * NOTE: latest generic point where the cpu is fully realized + */ +qemu_start_vcpu(cpu); } static void cpu_common_unrealizefn(DeviceState *dev) diff --git a/plugins/core.c b/plugins/core.c index 0726bc7f25..1e5da7853b 100644 --- a/plugins/core.c +++ b/plugins/core.c @@ -65,6 +65,11 @@ static void plugin_cpu_update__locked(gpointer k, gpointer v, gpointer udata) CPUState *cpu = container_of(k, CPUState, cpu_index); run_on_cpu_data mask = RUN_ON_CPU_HOST_ULONG(*plugin.mask); +/* + * There is a race condition between the starting of the vCPU + * thread at the end of cpu_common_realizefn and when realized is + * finally set. + */ Could we simply have an active wait here? while (!DEVICE(cpu)->realized) {} We have a guarantee it will be realized shortly, and if it's too hard to have a proper synchronization mechanism (introduce a realize_cond?), then waiting for the proper state does not seem too bad. It's a bit strange for me to document an existing race condition, instead of finding a solution. if (DEVICE(cpu)->realized) { async_run_on_cpu(cpu, plugin_cpu_update__async, mask); } else { diff --git a/system/cpus.c b/system/cpus.c index d3640c9503..7dd8464c5e 100644 --- a/system/cpus.c +++ b/system/cpus.c @@ -488,11 +488,13 @@ void cpus_kick_thread(CPUState *cpu) void qemu_cpu_kick(CPUState *cpu) { -qemu_cond_broadcast(cpu->halt_cond); -if (cpus_accel->kick_vcpu_thread) { -cpus_accel->kick_vcpu_thread(cpu); -} else { /* default */ -cpus_kick_thread(cpu); +if (cpu->halt_cond) { +qemu_cond_broadcast(cpu->halt_cond); +if (cpus_accel->kick_vcpu_thread) { +cpus_accel->kick_vcpu_thread(cpu); +} else { /* default */ +cpus_kick_thread(cpu); +} } } @@ -674,7 +676,10 @@ void qemu_init_vcpu(CPUState *cpu) cpu->num_ases = 1; cpu_address_space_init(cpu, 0, "cpu-memory", cpu->memory); } +} +void qemu_start_vcpu(CPUState *cpu) +{ /* accelerators all implement the AccelOpsClass */ g_assert(cpus_accel != NULL && cpus_accel->create_vcpu_thread != NULL); cpus_accel->create_vcpu_thread(cpu);
Re: [PATCH 1/5] sysemu: add set_virtual_time to accel ops
On 5/29/24 23:30, Paolo Bonzini wrote: On Fri, May 17, 2024 at 12:21 AM Pierrick Bouvier wrote: diff --git a/stubs/meson.build b/stubs/meson.build index 3b9d42023cb..672213b7482 100644 --- a/stubs/meson.build +++ b/stubs/meson.build @@ -3,6 +3,11 @@ # below, so that it is clear who needs the stubbed functionality. stub_ss.add(files('cpu-get-clock.c')) +stub_ss.add(files('cpus-virtual-clock.c')) +stub_ss.add(files('qemu-timer-notify-cb.c')) +stub_ss.add(files('icount.c')) +stub_ss.add(files('dump.c')) +stub_ss.add(files('error-printf.c')) Was there a problem when rebasing? Paolo Hi Paolo, Yes, Philippe made the same comment. I'll fix it in next revision. Thanks! stub_ss.add(files('fdset.c')) stub_ss.add(files('iothread-lock.c')) stub_ss.add(files('is-daemonized.c')) @@ -28,7 +33,6 @@ endif if have_block or have_ga stub_ss.add(files('replay-tools.c')) # stubs for hooks in util/main-loop.c, util/async.c etc. - stub_ss.add(files('cpus-get-virtual-clock.c')) stub_ss.add(files('icount.c')) stub_ss.add(files('graph-lock.c')) if linux_io_uring.found() -- 2.39.2
Re: [PATCH 1/5] sysemu: add set_virtual_time to accel ops
On 5/29/24 05:29, Philippe Mathieu-Daudé wrote: On 17/5/24 00:20, Pierrick Bouvier wrote: From: Alex Bennée We are about to remove direct calls to individual accelerators for this information and will need a central point for plugins to hook into time changes. From: Alex Bennée Signed-off-by: Alex Bennée Reviewed-by: Philippe Mathieu-Daudé --- include/sysemu/accel-ops.h | 18 +- include/sysemu/cpu-timers.h| 3 ++- ...et-virtual-clock.c => cpus-virtual-clock.c} | 5 + system/cpus.c | 11 +++ stubs/meson.build | 6 +- 5 files changed, 40 insertions(+), 3 deletions(-) diff --git a/stubs/meson.build b/stubs/meson.build index 3b9d42023cb..672213b7482 100644 --- a/stubs/meson.build +++ b/stubs/meson.build @@ -3,6 +3,11 @@ # below, so that it is clear who needs the stubbed functionality. stub_ss.add(files('cpu-get-clock.c')) +stub_ss.add(files('cpus-virtual-clock.c')) Are these lines <... +stub_ss.add(files('qemu-timer-notify-cb.c')) +stub_ss.add(files('icount.c')) +stub_ss.add(files('dump.c')) +stub_ss.add(files('error-printf.c')) ...> due to a failed git-rebase? You're right, fixed this! stub_ss.add(files('fdset.c')) stub_ss.add(files('iothread-lock.c')) stub_ss.add(files('is-daemonized.c')) @@ -28,7 +33,6 @@ endif if have_block or have_ga stub_ss.add(files('replay-tools.c')) # stubs for hooks in util/main-loop.c, util/async.c etc. - stub_ss.add(files('cpus-get-virtual-clock.c')) stub_ss.add(files('icount.c')) stub_ss.add(files('graph-lock.c')) if linux_io_uring.found()
Re: [PATCH 5/5] contrib/plugins: add ips plugin example for cost modeling
On 5/29/24 05:13, Alex Bennée wrote: Pierrick Bouvier writes: (Added Philip to CC) On 5/28/24 12:57, Alex Bennée wrote: Pierrick Bouvier writes: On 5/28/24 12:14, Alex Bennée wrote: Pierrick Bouvier writes: This plugin uses the new time control interface to make decisions about the state of time during the emulation. The algorithm is currently very simple. The user specifies an ips rate which applies per core. +static void vcpu_init(qemu_plugin_id_t id, unsigned int cpu_index) +{ +vCPUTime *vcpu = qemu_plugin_scoreboard_find(vcpus, cpu_index); +/* ensure start time is set first */ +set_start_time(); +/* start counter from absolute time reference */ +vcpu->counter = num_insn_during(uptime_ns()); +vcpu_set_state(vcpu, EXECUTING); +} + +static void vcpu_idle(qemu_plugin_id_t id, unsigned int cpu_index) +{ +vCPUTime *vcpu = qemu_plugin_scoreboard_find(vcpus, cpu_index); +vcpu_set_state(vcpu, IDLE); +} + +static void vcpu_resume(qemu_plugin_id_t id, unsigned int cpu_index) +{ +vCPUTime *vcpu = qemu_plugin_scoreboard_find(vcpus, cpu_index); +g_assert(vcpu->state == IDLE); I'm triggering a weird race here: (gdb) b vcpu_init Breakpoint 1 at 0x77fa15f7: file /home/alex/lsrc/qemu.git/contrib/plugins/ips.c, line 127. (gdb) r The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/alex/lsrc/qemu.git/builds/arm.debug/qemu-system-aarch64 -machine type=virt,virtualization=on,pflash0=rom,pflash1=efivars -cpu cortex-a57 -smp 32 -accel tcg -device virtio-net-pci,netdev=unet -device virtio-scsi-pci -device scsi-hd,drive=hd -netdev user,id=unet,hostfwd=tcp::-:22 -blockdev driver=raw,node-name=hd,file.driver=host_device,file.filename=/dev/zen-ssd2/trixie-arm64,discard=unmap -serial mon:stdio -blockdev node-name=rom,driver=file,filename=/home/alex/lsrc/qemu.git/builds/arm.debug/pc-bios/edk2-aarch64-code.fd,read-only=true -blockdev node-name=efivars,driver=file,filename=/home/alex/images/qemu-arm64-efivars -m 8192 -object memory-backend-memfd,id=mem,size=8G,share=on -kernel /home/alex/lsrc/linux.git/builds/arm64/arch/arm64/boot/Image -append root=/dev/sda2\ console=ttyAMA0 -plugin contrib/plugins/libips.so,ips=10 [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [New Thread 0x7fffe72006c0 (LWP 360538)] [New Thread 0x7fffe68006c0 (LWP 360540)] [New Thread 0x7fffe5e006c0 (LWP 360541)] [New Thread 0x7fffe54006c0 (LWP 360542)] [New Thread 0x7fffe4a006c0 (LWP 360543)] [New Thread 0x7fffdfe006c0 (LWP 360544)] [New Thread 0x7fffdf4006c0 (LWP 360545)] [New Thread 0x7fffdea006c0 (LWP 360546)] [Switching to Thread 0x7fffdf4006c0 (LWP 360545)] Thread 8 "qemu-system-aar" hit Breakpoint 1, vcpu_init (id=10457908569352202058, cpu_index=0) at /home/alex/lsrc/qemu.git/contrib/plugins/ips.c:127 127 vCPUTime *vcpu = qemu_plugin_scoreboard_find(vcpus, cpu_index); (gdb) c Continuing. [New Thread 0x7fffde0006c0 (LWP 360548)] [Switching to Thread 0x7fffdea006c0 (LWP 360546)] Thread 9 "qemu-system-aar" hit Breakpoint 1, vcpu_init (id=10457908569352202058, cpu_index=1) at /home/alex/lsrc/qemu.git/contrib/plugins/ips.c:127 127 vCPUTime *vcpu = qemu_plugin_scoreboard_find(vcpus, cpu_index); (gdb) Continuing. [New Thread 0x7fffdd6006c0 (LWP 360549)] [Switching to Thread 0x7fffde0006c0 (LWP 360548)] Thread 10 "qemu-system-aar" hit Breakpoint 1, vcpu_init (id=10457908569352202058, cpu_index=2) at /home/alex/lsrc/qemu.git/contrib/plugins/ips.c:127 127 vCPUTime *vcpu = qemu_plugin_scoreboard_find(vcpus, cpu_index); (gdb) Continuing. [New Thread 0x7fffdcc006c0 (LWP 360550)] [Switching to Thread 0x7fffdd6006c0 (LWP 360549)] Thread 11 "qemu-system-aar" hit Breakpoint 1, vcpu_init (id=10457908569352202058, cpu_index=3) at /home/alex/lsrc/qemu.git/contrib/plugins/ips.c:127 127 vCPUTime *vcpu = qemu_plugin_scoreboard_find(vcpus, cpu_index); (gdb) Continuing. [New Thread 0x7fffd3e006c0 (LWP 360551)] [Switching to Thread 0x7fffdcc006c0 (LWP 360550)] Thread 12 "qemu-system-aar" hit Breakpoint 1, vcpu_init (id=10457908569352202058, cpu_index=4) at /home/alex/lsrc/qemu.git/contrib/plugins/ips.c:127 127 vCPUTime *vcpu = qemu_plugin_scoreboard_find(vcpus, cpu_index); (gdb) n 129 set_start_time(); (gdb) 131 vcpu->counter = num_insn_during(uptime_ns()); (gdb) 132 vcpu_set_state(vcpu, EXECUTING); (gdb) 133 } (gdb) p vcpu->state $1 = EXECUTING (gdb) p >state $2 = (vCPUState *) 0x57c6b5d0 (gdb) watch *(vCPUState *) 0x57c6b5d0 Hardwa
Re: [PATCH 5/5] contrib/plugins: add ips plugin example for cost modeling
On 5/28/24 12:57, Alex Bennée wrote: Pierrick Bouvier writes: On 5/28/24 12:14, Alex Bennée wrote: Pierrick Bouvier writes: This plugin uses the new time control interface to make decisions about the state of time during the emulation. The algorithm is currently very simple. The user specifies an ips rate which applies per core. If the core runs ahead of its allocated execution time the plugin sleeps for a bit to let real time catch up. Either way time is updated for the emulation as a function of total executed instructions with some adjustments for cores that idle. Examples Slow down execution of /bin/true: $ num_insn=$(./build/qemu-x86_64 -plugin ./build/tests/plugin/libinsn.so -d plugin /bin/true |& grep total | sed -e 's/.*: //') $ time ./build/qemu-x86_64 -plugin ./build/contrib/plugins/libips.so,ips=$(($num_insn/4)) /bin/true real 4.000s Boot a Linux kernel simulating a 250MHz cpu: $ /build/qemu-system-x86_64 -kernel /boot/vmlinuz-6.1.0-21-amd64 -append "console=ttyS0" -plugin ./build/contrib/plugins/libips.so,ips=$((250*1000*1000)) -smp 1 -m 512 check time until kernel panic on serial0 Signed-off-by: Pierrick Bouvier --- contrib/plugins/ips.c| 239 +++ contrib/plugins/Makefile | 1 + 2 files changed, 240 insertions(+) create mode 100644 contrib/plugins/ips.c diff --git a/contrib/plugins/ips.c b/contrib/plugins/ips.c new file mode 100644 index 000..cf3159df391 --- /dev/null +++ b/contrib/plugins/ips.c @@ -0,0 +1,239 @@ +/* + * ips rate limiting plugin. + * + * This plugin can be used to restrict the execution of a system to a + * particular number of Instructions Per Second (ips). This controls + * time as seen by the guest so while wall-clock time may be longer + * from the guests point of view time will pass at the normal rate. + * + * This uses the new plugin API which allows the plugin to control + * system time. + * + * Copyright (c) 2023 Linaro Ltd + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include + +QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; + +/* how many times do we update time per sec */ +#define NUM_TIME_UPDATE_PER_SEC 10 +#define NSEC_IN_ONE_SEC (1000 * 1000 * 1000) + +static GMutex global_state_lock; + +static uint64_t insn_per_second = 1000 * 1000; /* ips per core, per second */ +static uint64_t insn_quantum; /* trap every N instructions */ +static bool precise_execution; /* count every instruction */ +static int64_t start_time_ns; /* time (ns since epoch) first vCPU started */ +static int64_t virtual_time_ns; /* last set virtual time */ + +static const void *time_handle; + +typedef enum { +UNKNOWN = 0, +EXECUTING, +IDLE, +FINISHED +} vCPUState; + +typedef struct { +uint64_t counter; +uint64_t track_insn; +vCPUState state; +/* timestamp when vCPU entered state */ +int64_t last_state_time; +} vCPUTime; + +struct qemu_plugin_scoreboard *vcpus; + +/* return epoch time in ns */ +static int64_t now_ns(void) +{ +return g_get_real_time() * 1000; +} + +static uint64_t num_insn_during(int64_t elapsed_ns) +{ +double num_secs = elapsed_ns / (double) NSEC_IN_ONE_SEC; +return num_secs * (double) insn_per_second; +} + +static int64_t time_for_insn(uint64_t num_insn) +{ +double num_secs = (double) num_insn / (double) insn_per_second; +return num_secs * (double) NSEC_IN_ONE_SEC; +} + +static int64_t uptime_ns(void) +{ +int64_t now = now_ns(); +g_assert(now >= start_time_ns); +return now - start_time_ns; +} + +static void vcpu_set_state(vCPUTime *vcpu, vCPUState new_state) +{ +vcpu->last_state_time = now_ns(); +vcpu->state = new_state; +} + +static void update_system_time(vCPUTime *vcpu) +{ +/* flush remaining instructions */ +vcpu->counter += vcpu->track_insn; +vcpu->track_insn = 0; + +int64_t uptime = uptime_ns(); +uint64_t expected_insn = num_insn_during(uptime); + +if (vcpu->counter >= expected_insn) { +/* this vcpu ran faster than expected, so it has to sleep */ +uint64_t insn_advance = vcpu->counter - expected_insn; +uint64_t time_advance_ns = time_for_insn(insn_advance); +int64_t sleep_us = time_advance_ns / 1000; +g_usleep(sleep_us); +} + +/* based on number of instructions, what should be the new time? */ +int64_t new_virtual_time = time_for_insn(vcpu->counter); + +g_mutex_lock(_state_lock); + +/* Time only moves forward. Another vcpu might have updated it already. */ +if (new_virtual_time > virtual_time_ns) { +qemu_plugin_update_ns(time_handle, new_virtual_time); +virtual_time_ns = new_virtual_time; +} + +g_mutex_unlock(_state_lock); +} + +static void set_start_time() +{ +g_mutex_lock(_state_lock); +if (!start_time_ns) { +start_time_ns = now_ns(); +} +g_mutex_unlock(_state_lock);
Re: [PATCH 5/5] contrib/plugins: add ips plugin example for cost modeling
On 5/28/24 12:14, Alex Bennée wrote: Pierrick Bouvier writes: This plugin uses the new time control interface to make decisions about the state of time during the emulation. The algorithm is currently very simple. The user specifies an ips rate which applies per core. If the core runs ahead of its allocated execution time the plugin sleeps for a bit to let real time catch up. Either way time is updated for the emulation as a function of total executed instructions with some adjustments for cores that idle. Examples Slow down execution of /bin/true: $ num_insn=$(./build/qemu-x86_64 -plugin ./build/tests/plugin/libinsn.so -d plugin /bin/true |& grep total | sed -e 's/.*: //') $ time ./build/qemu-x86_64 -plugin ./build/contrib/plugins/libips.so,ips=$(($num_insn/4)) /bin/true real 4.000s Boot a Linux kernel simulating a 250MHz cpu: $ /build/qemu-system-x86_64 -kernel /boot/vmlinuz-6.1.0-21-amd64 -append "console=ttyS0" -plugin ./build/contrib/plugins/libips.so,ips=$((250*1000*1000)) -smp 1 -m 512 check time until kernel panic on serial0 Signed-off-by: Pierrick Bouvier --- contrib/plugins/ips.c| 239 +++ contrib/plugins/Makefile | 1 + 2 files changed, 240 insertions(+) create mode 100644 contrib/plugins/ips.c diff --git a/contrib/plugins/ips.c b/contrib/plugins/ips.c new file mode 100644 index 000..cf3159df391 --- /dev/null +++ b/contrib/plugins/ips.c @@ -0,0 +1,239 @@ +/* + * ips rate limiting plugin. + * + * This plugin can be used to restrict the execution of a system to a + * particular number of Instructions Per Second (ips). This controls + * time as seen by the guest so while wall-clock time may be longer + * from the guests point of view time will pass at the normal rate. + * + * This uses the new plugin API which allows the plugin to control + * system time. + * + * Copyright (c) 2023 Linaro Ltd + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include + +QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; + +/* how many times do we update time per sec */ +#define NUM_TIME_UPDATE_PER_SEC 10 +#define NSEC_IN_ONE_SEC (1000 * 1000 * 1000) + +static GMutex global_state_lock; + +static uint64_t insn_per_second = 1000 * 1000; /* ips per core, per second */ +static uint64_t insn_quantum; /* trap every N instructions */ +static bool precise_execution; /* count every instruction */ +static int64_t start_time_ns; /* time (ns since epoch) first vCPU started */ +static int64_t virtual_time_ns; /* last set virtual time */ + +static const void *time_handle; + +typedef enum { +UNKNOWN = 0, +EXECUTING, +IDLE, +FINISHED +} vCPUState; + +typedef struct { +uint64_t counter; +uint64_t track_insn; +vCPUState state; +/* timestamp when vCPU entered state */ +int64_t last_state_time; +} vCPUTime; + +struct qemu_plugin_scoreboard *vcpus; + +/* return epoch time in ns */ +static int64_t now_ns(void) +{ +return g_get_real_time() * 1000; +} + +static uint64_t num_insn_during(int64_t elapsed_ns) +{ +double num_secs = elapsed_ns / (double) NSEC_IN_ONE_SEC; +return num_secs * (double) insn_per_second; +} + +static int64_t time_for_insn(uint64_t num_insn) +{ +double num_secs = (double) num_insn / (double) insn_per_second; +return num_secs * (double) NSEC_IN_ONE_SEC; +} + +static int64_t uptime_ns(void) +{ +int64_t now = now_ns(); +g_assert(now >= start_time_ns); +return now - start_time_ns; +} + +static void vcpu_set_state(vCPUTime *vcpu, vCPUState new_state) +{ +vcpu->last_state_time = now_ns(); +vcpu->state = new_state; +} + +static void update_system_time(vCPUTime *vcpu) +{ +/* flush remaining instructions */ +vcpu->counter += vcpu->track_insn; +vcpu->track_insn = 0; + +int64_t uptime = uptime_ns(); +uint64_t expected_insn = num_insn_during(uptime); + +if (vcpu->counter >= expected_insn) { +/* this vcpu ran faster than expected, so it has to sleep */ +uint64_t insn_advance = vcpu->counter - expected_insn; +uint64_t time_advance_ns = time_for_insn(insn_advance); +int64_t sleep_us = time_advance_ns / 1000; +g_usleep(sleep_us); +} + +/* based on number of instructions, what should be the new time? */ +int64_t new_virtual_time = time_for_insn(vcpu->counter); + +g_mutex_lock(_state_lock); + +/* Time only moves forward. Another vcpu might have updated it already. */ +if (new_virtual_time > virtual_time_ns) { +qemu_plugin_update_ns(time_handle, new_virtual_time); +virtual_time_ns = new_virtual_time; +} + +g_mutex_unlock(_state_lock); +} + +static void set_start_time() +{ +g_mutex_lock(_state_lock); +if (!start_time_ns) { +start_time_ns = now_ns(); +} +g_mutex_unlock(_state_lock); +} + +static void vcpu_init(qemu_plugin_id_t id, unsigned int cpu_index) +{ +
Re: [PATCH 1/5] sysemu: add set_virtual_time to accel ops
On 5/28/24 10:11, Alex Bennée wrote: Pierrick Bouvier writes: From: Alex Bennée We are about to remove direct calls to individual accelerators for this information and will need a central point for plugins to hook into time changes. From: Alex Bennée Signed-off-by: Alex Bennée Reviewed-by: Philippe Mathieu-Daudé Just a note, when patches written by other people come via your tree you should add your s-o-b tag to indicate: "I'm legally okay to contribute this and happy for it to go into QEMU" Thanks for clarifying, I didn't know it was needed as a committer (vs author), and checkpatch.pl does not seem to check for this. I'll add this when reposting the series, if we have some comments.
Re: [PATCH 0/6] accel: Restrict TCG plugin (un)registration to TCG accel
On 5/28/24 07:59, Philippe Mathieu-Daudé wrote: > Philippe Mathieu-Daudé (6): system/runstate: Remove unused 'qemu/plugin.h' header accel/tcg: Move common declarations to 'internal-common.h' accel: Clarify accel_cpu_common_[un]realize() use unassigned vCPU accel: Introduce accel_cpu_common_[un]realize_assigned() handlers accel: Restrict TCG plugin (un)registration to TCG accel accel/tcg: Move qemu_plugin_vcpu_init__async() to plugins/ Reviewed-by: Pierrick Bouvier
Re: [PATCH] accel/tcg: Init tb size and icount before plugin_gen_tb_end
On 5/21/24 14:06, Richard Henderson wrote: When passing disassembly data to plugin callbacks, translator_st_len relies on db->tb->size having been set. Fixes: 4c833c60e047 ("disas: Use translator_st to get disassembly data") Reported-by: Bernhard Beschow Signed-off-by: Richard Henderson --- accel/tcg/translator.c | 8 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/accel/tcg/translator.c b/accel/tcg/translator.c index c56967eecd..113edcffe3 100644 --- a/accel/tcg/translator.c +++ b/accel/tcg/translator.c @@ -214,14 +214,14 @@ void translator_loop(CPUState *cpu, TranslationBlock *tb, int *max_insns, set_can_do_io(db, true); tcg_ctx->emit_before_op = NULL; +/* May be used by disas_log or plugin callbacks. */ +tb->size = db->pc_next - db->pc_first; +tb->icount = db->num_insns; + if (plugin_enabled) { plugin_gen_tb_end(cpu, db->num_insns); } -/* The disas_log hook may use these values rather than recompute. */ -tb->size = db->pc_next - db->pc_first; -tb->icount = db->num_insns; - if (qemu_loglevel_mask(CPU_LOG_TB_IN_ASM) && qemu_log_in_addr_range(db->pc_first)) { FILE *logfile = qemu_log_trylock(); Reviewed-by: Pierrick Bouvier
[PATCH 5/5] contrib/plugins: add ips plugin example for cost modeling
This plugin uses the new time control interface to make decisions about the state of time during the emulation. The algorithm is currently very simple. The user specifies an ips rate which applies per core. If the core runs ahead of its allocated execution time the plugin sleeps for a bit to let real time catch up. Either way time is updated for the emulation as a function of total executed instructions with some adjustments for cores that idle. Examples Slow down execution of /bin/true: $ num_insn=$(./build/qemu-x86_64 -plugin ./build/tests/plugin/libinsn.so -d plugin /bin/true |& grep total | sed -e 's/.*: //') $ time ./build/qemu-x86_64 -plugin ./build/contrib/plugins/libips.so,ips=$(($num_insn/4)) /bin/true real 4.000s Boot a Linux kernel simulating a 250MHz cpu: $ /build/qemu-system-x86_64 -kernel /boot/vmlinuz-6.1.0-21-amd64 -append "console=ttyS0" -plugin ./build/contrib/plugins/libips.so,ips=$((250*1000*1000)) -smp 1 -m 512 check time until kernel panic on serial0 Signed-off-by: Pierrick Bouvier --- contrib/plugins/ips.c| 239 +++ contrib/plugins/Makefile | 1 + 2 files changed, 240 insertions(+) create mode 100644 contrib/plugins/ips.c diff --git a/contrib/plugins/ips.c b/contrib/plugins/ips.c new file mode 100644 index 000..cf3159df391 --- /dev/null +++ b/contrib/plugins/ips.c @@ -0,0 +1,239 @@ +/* + * ips rate limiting plugin. + * + * This plugin can be used to restrict the execution of a system to a + * particular number of Instructions Per Second (ips). This controls + * time as seen by the guest so while wall-clock time may be longer + * from the guests point of view time will pass at the normal rate. + * + * This uses the new plugin API which allows the plugin to control + * system time. + * + * Copyright (c) 2023 Linaro Ltd + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include + +QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; + +/* how many times do we update time per sec */ +#define NUM_TIME_UPDATE_PER_SEC 10 +#define NSEC_IN_ONE_SEC (1000 * 1000 * 1000) + +static GMutex global_state_lock; + +static uint64_t insn_per_second = 1000 * 1000; /* ips per core, per second */ +static uint64_t insn_quantum; /* trap every N instructions */ +static bool precise_execution; /* count every instruction */ +static int64_t start_time_ns; /* time (ns since epoch) first vCPU started */ +static int64_t virtual_time_ns; /* last set virtual time */ + +static const void *time_handle; + +typedef enum { +UNKNOWN = 0, +EXECUTING, +IDLE, +FINISHED +} vCPUState; + +typedef struct { +uint64_t counter; +uint64_t track_insn; +vCPUState state; +/* timestamp when vCPU entered state */ +int64_t last_state_time; +} vCPUTime; + +struct qemu_plugin_scoreboard *vcpus; + +/* return epoch time in ns */ +static int64_t now_ns(void) +{ +return g_get_real_time() * 1000; +} + +static uint64_t num_insn_during(int64_t elapsed_ns) +{ +double num_secs = elapsed_ns / (double) NSEC_IN_ONE_SEC; +return num_secs * (double) insn_per_second; +} + +static int64_t time_for_insn(uint64_t num_insn) +{ +double num_secs = (double) num_insn / (double) insn_per_second; +return num_secs * (double) NSEC_IN_ONE_SEC; +} + +static int64_t uptime_ns(void) +{ +int64_t now = now_ns(); +g_assert(now >= start_time_ns); +return now - start_time_ns; +} + +static void vcpu_set_state(vCPUTime *vcpu, vCPUState new_state) +{ +vcpu->last_state_time = now_ns(); +vcpu->state = new_state; +} + +static void update_system_time(vCPUTime *vcpu) +{ +/* flush remaining instructions */ +vcpu->counter += vcpu->track_insn; +vcpu->track_insn = 0; + +int64_t uptime = uptime_ns(); +uint64_t expected_insn = num_insn_during(uptime); + +if (vcpu->counter >= expected_insn) { +/* this vcpu ran faster than expected, so it has to sleep */ +uint64_t insn_advance = vcpu->counter - expected_insn; +uint64_t time_advance_ns = time_for_insn(insn_advance); +int64_t sleep_us = time_advance_ns / 1000; +g_usleep(sleep_us); +} + +/* based on number of instructions, what should be the new time? */ +int64_t new_virtual_time = time_for_insn(vcpu->counter); + +g_mutex_lock(_state_lock); + +/* Time only moves forward. Another vcpu might have updated it already. */ +if (new_virtual_time > virtual_time_ns) { +qemu_plugin_update_ns(time_handle, new_virtual_time); +virtual_time_ns = new_virtual_time; +} + +g_mutex_unlock(_state_lock); +} + +static void set_start_time() +{ +g_mutex_lock(_state_lock); +if (!start_time_ns) { +start_time_ns = now_ns(); +} +g_mutex_unlock(_state_lock); +} + +static void vcpu_init(qemu_plugin_id_t id, unsigned int cpu_index) +{ +vCPUTime *vcpu = qemu_plugin_scoreboard_find(vcpus, cpu
[PATCH 4/5] plugins: add time control API
From: Alex Bennée Expose the ability to control time through the plugin API. Only one plugin can control time so it has to request control when loaded. There are probably more corner cases to catch here. From: Alex Bennée Signed-off-by: Alex Bennée --- include/qemu/qemu-plugin.h | 23 +++ plugins/api.c| 31 +++ plugins/qemu-plugins.symbols | 2 ++ 3 files changed, 56 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 95703d8fec1..80b1637cede 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -661,6 +661,29 @@ void qemu_plugin_register_vcpu_mem_inline_per_vcpu( qemu_plugin_u64 entry, uint64_t imm); +/** + * qemu_plugin_request_time_control() - request the ability to control time + * + * This grants the plugin the ability to control system time. Only one + * plugin can control time so if multiple plugins request the ability + * all but the first will fail. + * + * Returns an opaque handle or NULL if fails + */ +const void *qemu_plugin_request_time_control(void); + +/** + * qemu_plugin_update_ns() - update system emulation time + * @handle: opaque handle returned by qemu_plugin_request_time_control() + * @time: time in nanoseconds + * + * This allows an appropriately authorised plugin (i.e. holding the + * time control handle) to move system time forward to @time. + * + * Start time is 0. + */ +void qemu_plugin_update_ns(const void *handle, int64_t time); + typedef void (*qemu_plugin_vcpu_syscall_cb_t)(qemu_plugin_id_t id, unsigned int vcpu_index, int64_t num, uint64_t a1, uint64_t a2, diff --git a/plugins/api.c b/plugins/api.c index 5a0a7f8c712..26822b69ea2 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -39,6 +39,7 @@ #include "qemu/main-loop.h" #include "qemu/plugin.h" #include "qemu/log.h" +#include "qemu/timer.h" #include "tcg/tcg.h" #include "exec/exec-all.h" #include "exec/gdbstub.h" @@ -583,3 +584,33 @@ uint64_t qemu_plugin_u64_sum(qemu_plugin_u64 entry) } return total; } + +/* + * Time control + */ +static bool has_control; + +const void *qemu_plugin_request_time_control(void) +{ +if (!has_control) { +has_control = true; +return _control; +} +return NULL; +} + +static void advance_virtual_time__async(CPUState *cpu, run_on_cpu_data data) +{ +int64_t new_time = data.host_ulong; +qemu_clock_advance_virtual_time(new_time); +} + +void qemu_plugin_update_ns(const void *handle, int64_t new_time) +{ +if (handle == _control) { +/* Need to execute out of cpu_exec, so bql can be locked. */ +async_run_on_cpu(current_cpu, + advance_virtual_time__async, + RUN_ON_CPU_HOST_ULONG(new_time)); +} +} diff --git a/plugins/qemu-plugins.symbols b/plugins/qemu-plugins.symbols index aa0a77a319f..ca773d8d9fe 100644 --- a/plugins/qemu-plugins.symbols +++ b/plugins/qemu-plugins.symbols @@ -38,6 +38,7 @@ qemu_plugin_register_vcpu_tb_exec_cond_cb; qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu; qemu_plugin_register_vcpu_tb_trans_cb; + qemu_plugin_request_time_control; qemu_plugin_reset; qemu_plugin_scoreboard_free; qemu_plugin_scoreboard_find; @@ -51,5 +52,6 @@ qemu_plugin_u64_set; qemu_plugin_u64_sum; qemu_plugin_uninstall; + qemu_plugin_update_ns; qemu_plugin_vcpu_for_each; }; -- 2.39.2
[PATCH 1/5] sysemu: add set_virtual_time to accel ops
From: Alex Bennée We are about to remove direct calls to individual accelerators for this information and will need a central point for plugins to hook into time changes. From: Alex Bennée Signed-off-by: Alex Bennée Reviewed-by: Philippe Mathieu-Daudé --- include/sysemu/accel-ops.h | 18 +- include/sysemu/cpu-timers.h| 3 ++- ...et-virtual-clock.c => cpus-virtual-clock.c} | 5 + system/cpus.c | 11 +++ stubs/meson.build | 6 +- 5 files changed, 40 insertions(+), 3 deletions(-) rename stubs/{cpus-get-virtual-clock.c => cpus-virtual-clock.c} (68%) diff --git a/include/sysemu/accel-ops.h b/include/sysemu/accel-ops.h index ef91fc28bbd..a0886722305 100644 --- a/include/sysemu/accel-ops.h +++ b/include/sysemu/accel-ops.h @@ -20,7 +20,12 @@ typedef struct AccelOpsClass AccelOpsClass; DECLARE_CLASS_CHECKERS(AccelOpsClass, ACCEL_OPS, TYPE_ACCEL_OPS) -/* cpus.c operations interface */ +/** + * struct AccelOpsClass - accelerator interfaces + * + * This structure is used to abstract accelerator differences from the + * core CPU code. Not all have to be implemented. + */ struct AccelOpsClass { /*< private >*/ ObjectClass parent_class; @@ -44,7 +49,18 @@ struct AccelOpsClass { void (*handle_interrupt)(CPUState *cpu, int mask); +/** + * @get_virtual_clock: fetch virtual clock + * @set_virtual_clock: set virtual clock + * + * These allow the timer subsystem to defer to the accelerator to + * fetch time. The set function is needed if the accelerator wants + * to track the changes to time as the timer is warped through + * various timer events. + */ int64_t (*get_virtual_clock)(void); +void (*set_virtual_clock)(int64_t time); + int64_t (*get_elapsed_ticks)(void); /* gdbstub hooks */ diff --git a/include/sysemu/cpu-timers.h b/include/sysemu/cpu-timers.h index d86738a378d..7bfa960fbd6 100644 --- a/include/sysemu/cpu-timers.h +++ b/include/sysemu/cpu-timers.h @@ -96,8 +96,9 @@ int64_t cpu_get_clock(void); void qemu_timer_notify_cb(void *opaque, QEMUClockType type); -/* get the VIRTUAL clock and VM elapsed ticks via the cpus accel interface */ +/* get/set VIRTUAL clock and VM elapsed ticks via the cpus accel interface */ int64_t cpus_get_virtual_clock(void); +void cpus_set_virtual_clock(int64_t new_time); int64_t cpus_get_elapsed_ticks(void); #endif /* SYSEMU_CPU_TIMERS_H */ diff --git a/stubs/cpus-get-virtual-clock.c b/stubs/cpus-virtual-clock.c similarity index 68% rename from stubs/cpus-get-virtual-clock.c rename to stubs/cpus-virtual-clock.c index fd447d53f3c..af7c1a1d403 100644 --- a/stubs/cpus-get-virtual-clock.c +++ b/stubs/cpus-virtual-clock.c @@ -6,3 +6,8 @@ int64_t cpus_get_virtual_clock(void) { return cpu_get_clock(); } + +void cpus_set_virtual_clock(int64_t new_time) +{ +/* do nothing */ +} diff --git a/system/cpus.c b/system/cpus.c index 68d161d96b7..03ba026667c 100644 --- a/system/cpus.c +++ b/system/cpus.c @@ -229,6 +229,17 @@ int64_t cpus_get_virtual_clock(void) return cpu_get_clock(); } +/* + * Signal the new virtual time to the accelerator. This is only needed + * by accelerators that need to track the changes as we warp time. + */ +void cpus_set_virtual_clock(int64_t new_time) +{ +if (cpus_accel && cpus_accel->set_virtual_clock) { +cpus_accel->set_virtual_clock(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 diff --git a/stubs/meson.build b/stubs/meson.build index 3b9d42023cb..672213b7482 100644 --- a/stubs/meson.build +++ b/stubs/meson.build @@ -3,6 +3,11 @@ # below, so that it is clear who needs the stubbed functionality. stub_ss.add(files('cpu-get-clock.c')) +stub_ss.add(files('cpus-virtual-clock.c')) +stub_ss.add(files('qemu-timer-notify-cb.c')) +stub_ss.add(files('icount.c')) +stub_ss.add(files('dump.c')) +stub_ss.add(files('error-printf.c')) stub_ss.add(files('fdset.c')) stub_ss.add(files('iothread-lock.c')) stub_ss.add(files('is-daemonized.c')) @@ -28,7 +33,6 @@ endif if have_block or have_ga stub_ss.add(files('replay-tools.c')) # stubs for hooks in util/main-loop.c, util/async.c etc. - stub_ss.add(files('cpus-get-virtual-clock.c')) stub_ss.add(files('icount.c')) stub_ss.add(files('graph-lock.c')) if linux_io_uring.found() -- 2.39.2
[PATCH 2/5] qtest: use cpu interface in qtest_clock_warp
From: Alex Bennée This generalises the qtest_clock_warp code to use the AccelOps handlers for updating its own sense of time. This will make the next patch which moves the warp code closer to pure code motion. From: Alex Bennée Signed-off-by: Alex Bennée Acked-by: Thomas Huth --- include/sysemu/qtest.h | 1 + accel/qtest/qtest.c| 1 + system/qtest.c | 6 +++--- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/sysemu/qtest.h b/include/sysemu/qtest.h index b5d5fd34637..45f3b7e1df5 100644 --- a/include/sysemu/qtest.h +++ b/include/sysemu/qtest.h @@ -36,6 +36,7 @@ void qtest_server_set_send_handler(void (*send)(void *, const char *), void qtest_server_inproc_recv(void *opaque, const char *buf); int64_t qtest_get_virtual_clock(void); +void qtest_set_virtual_clock(int64_t count); #endif #endif diff --git a/accel/qtest/qtest.c b/accel/qtest/qtest.c index f6056ac8361..53182e6c2ae 100644 --- a/accel/qtest/qtest.c +++ b/accel/qtest/qtest.c @@ -52,6 +52,7 @@ static void qtest_accel_ops_class_init(ObjectClass *oc, void *data) ops->create_vcpu_thread = dummy_start_vcpu_thread; ops->get_virtual_clock = qtest_get_virtual_clock; +ops->set_virtual_clock = qtest_set_virtual_clock; }; static const TypeInfo qtest_accel_ops_type = { diff --git a/system/qtest.c b/system/qtest.c index 6da58b3874e..ee8b139e982 100644 --- a/system/qtest.c +++ b/system/qtest.c @@ -332,14 +332,14 @@ int64_t qtest_get_virtual_clock(void) return qatomic_read_i64(_clock_counter); } -static void qtest_set_virtual_clock(int64_t count) +void qtest_set_virtual_clock(int64_t count) { qatomic_set_i64(_clock_counter, count); } static void qtest_clock_warp(int64_t dest) { -int64_t clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +int64_t clock = cpus_get_virtual_clock(); AioContext *aio_context; assert(qtest_enabled()); aio_context = qemu_get_aio_context(); @@ -348,7 +348,7 @@ static void qtest_clock_warp(int64_t dest) QEMU_TIMER_ATTR_ALL); int64_t warp = qemu_soonest_timeout(dest - clock, deadline); -qtest_set_virtual_clock(qtest_get_virtual_clock() + warp); +cpus_set_virtual_clock(cpus_get_virtual_clock() + warp); qemu_clock_run_timers(QEMU_CLOCK_VIRTUAL); timerlist_run_timers(aio_context->tlg.tl[QEMU_CLOCK_VIRTUAL]); -- 2.39.2
[PATCH 0/5] Implement icount=auto using TCG Plugins
The goal here is to be able to scale temporally execution of qemu-user/system, using a given number of instructions per second. We define a virtual clock, that can be late or in advance compared to real time. When we are in advance, we slow execution (by sleeping) until catching real time. Finally, we should be able to cleanup icount=auto mode completely, and keep icount usage for determistic purposes only. It is built upon new TCG Plugins inline ops (store + conditional callbacks), now merged on master. Example in user-mode: - Retrieve number of instructions to execute /bin/true $ ./build/qemu-x86_64 -plugin ./build/tests/plugin/libinsn.so -d plugin /bin/true cpu 0 insns: 120546 total insns: 120546 - Slow execution to match 5 seconds $ time ./build/qemu-x86_64 -plugin ./build/contrib/plugins/libips,ips=$((120546/5)) /bin/true real0m4.985s Alex Bennée (4): sysemu: add set_virtual_time to accel ops qtest: use cpu interface in qtest_clock_warp sysemu: generalise qtest_warp_clock as qemu_clock_advance_virtual_time plugins: add time control API Pierrick Bouvier (1): contrib/plugins: add ips plugin example for cost modeling include/qemu/qemu-plugin.h| 23 ++ include/qemu/timer.h | 15 ++ include/sysemu/accel-ops.h| 18 +- include/sysemu/cpu-timers.h | 3 +- include/sysemu/qtest.h| 1 + accel/qtest/qtest.c | 1 + contrib/plugins/ips.c | 239 ++ plugins/api.c | 31 +++ ...t-virtual-clock.c => cpus-virtual-clock.c} | 5 + system/cpus.c | 11 + system/qtest.c| 27 +- util/qemu-timer.c | 26 ++ contrib/plugins/Makefile | 1 + plugins/qemu-plugins.symbols | 2 + stubs/meson.build | 6 +- 15 files changed, 383 insertions(+), 26 deletions(-) create mode 100644 contrib/plugins/ips.c rename stubs/{cpus-get-virtual-clock.c => cpus-virtual-clock.c} (68%) -- 2.39.2
[PATCH 3/5] sysemu: generalise qtest_warp_clock as qemu_clock_advance_virtual_time
From: Alex Bennée Move the key functionality of moving time forward into the clock sub-system itself. This will allow us to plumb in time control into plugins. From: Alex Bennée Signed-off-by: Alex Bennée --- include/qemu/timer.h | 15 +++ system/qtest.c | 25 +++-- util/qemu-timer.c| 26 ++ 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/include/qemu/timer.h b/include/qemu/timer.h index 9a366e551fb..910587d8293 100644 --- a/include/qemu/timer.h +++ b/include/qemu/timer.h @@ -245,6 +245,21 @@ bool qemu_clock_run_timers(QEMUClockType type); */ bool qemu_clock_run_all_timers(void); +/** + * qemu_clock_advance_virtual_time(): advance the virtual time tick + * @target: target time in nanoseconds + * + * This function is used where the control of the flow of time has + * been delegated to outside the clock subsystem (be it qtest, icount + * or some other external source). You can ask the clock system to + * return @early at the first expired timer. + * + * Time can only move forward, attempts to reverse time would lead to + * an error. + * + * Returns: new virtual time. + */ +int64_t qemu_clock_advance_virtual_time(int64_t dest); /* * QEMUTimerList diff --git a/system/qtest.c b/system/qtest.c index ee8b139e982..e6f6b4e62d5 100644 --- a/system/qtest.c +++ b/system/qtest.c @@ -337,26 +337,6 @@ void qtest_set_virtual_clock(int64_t count) qatomic_set_i64(_clock_counter, count); } -static void qtest_clock_warp(int64_t dest) -{ -int64_t clock = cpus_get_virtual_clock(); -AioContext *aio_context; -assert(qtest_enabled()); -aio_context = qemu_get_aio_context(); -while (clock < dest) { -int64_t deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL, - QEMU_TIMER_ATTR_ALL); -int64_t warp = qemu_soonest_timeout(dest - clock, deadline); - -cpus_set_virtual_clock(cpus_get_virtual_clock() + warp); - -qemu_clock_run_timers(QEMU_CLOCK_VIRTUAL); -timerlist_run_timers(aio_context->tlg.tl[QEMU_CLOCK_VIRTUAL]); -clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); -} -qemu_clock_notify(QEMU_CLOCK_VIRTUAL); -} - static bool (*process_command_cb)(CharBackend *chr, gchar **words); void qtest_set_command_cb(bool (*pc_cb)(CharBackend *chr, gchar **words)) @@ -755,7 +735,8 @@ static void qtest_process_command(CharBackend *chr, gchar **words) ns = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL, QEMU_TIMER_ATTR_ALL); } -qtest_clock_warp(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns); +qemu_clock_advance_virtual_time( +qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns); qtest_send_prefix(chr); qtest_sendf(chr, "OK %"PRIi64"\n", (int64_t)qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)); @@ -781,7 +762,7 @@ static void qtest_process_command(CharBackend *chr, gchar **words) g_assert(words[1]); ret = qemu_strtoi64(words[1], NULL, 0, ); g_assert(ret == 0); -qtest_clock_warp(ns); +qemu_clock_advance_virtual_time(ns); qtest_send_prefix(chr); qtest_sendf(chr, "OK %"PRIi64"\n", (int64_t)qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)); diff --git a/util/qemu-timer.c b/util/qemu-timer.c index 6a0de33dd2b..213114be68c 100644 --- a/util/qemu-timer.c +++ b/util/qemu-timer.c @@ -645,6 +645,11 @@ int64_t qemu_clock_get_ns(QEMUClockType type) } } +static void qemu_virtual_clock_set_ns(int64_t time) +{ +return cpus_set_virtual_clock(time); +} + void init_clocks(QEMUTimerListNotifyCB *notify_cb) { QEMUClockType type; @@ -675,3 +680,24 @@ bool qemu_clock_run_all_timers(void) return progress; } + +int64_t qemu_clock_advance_virtual_time(int64_t dest) +{ +int64_t clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +AioContext *aio_context; +aio_context = qemu_get_aio_context(); +while (clock < dest) { +int64_t deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL, + QEMU_TIMER_ATTR_ALL); +int64_t warp = qemu_soonest_timeout(dest - clock, deadline); + +qemu_virtual_clock_set_ns(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + warp); + +qemu_clock_run_timers(QEMU_CLOCK_VIRTUAL); +timerlist_run_timers(aio_context->tlg.tl[QEMU_CLOCK_VIRTUAL]); +clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +} +qemu_clock_notify(QEMU_CLOCK_VIRTUAL); + +return clock; +} -- 2.39.2
Re: [PATCH 02/11] scripts/update-linux-header.sh: be more src tree friendly
ot;$tmpdir/include/asm/unistd_64.h" "$output/linux-headers/asm-x86/" -cp_portable "$tmpdir/include/asm/kvm_para.h" "$output/include/standard-headers/asm-$arch" +cp "$hdrdir/include/asm/unistd_32.h" "$output/linux-headers/asm-x86/" +cp "$hdrdir/include/asm/unistd_x32.h" "$output/linux-headers/asm-x86/" +cp "$hdrdir/include/asm/unistd_64.h" "$output/linux-headers/asm-x86/" +cp_portable "$hdrdir/include/asm/kvm_para.h" "$output/include/standard-headers/asm-$arch" # Remove everything except the macros from bootparam.h avoiding the # unnecessary import of several video/ist/etc headers sed -e '/__ASSEMBLY__/,/__ASSEMBLY__/d' \ - "$tmpdir/include/asm/bootparam.h" > "$tmpdir/bootparam.h" -cp_portable "$tmpdir/bootparam.h" \ + "$hdrdir/include/asm/bootparam.h" > "$hdrdir/bootparam.h" +cp_portable "$hdrdir/bootparam.h" \ "$output/include/standard-headers/asm-$arch" -cp_portable "$tmpdir/include/asm/setup_data.h" \ +cp_portable "$hdrdir/include/asm/setup_data.h" \ "$output/standard-headers/asm-x86" fi if [ $arch = riscv ]; then -cp "$tmpdir/include/asm/ptrace.h" "$output/linux-headers/asm-riscv/" +cp "$hdrdir/include/asm/ptrace.h" "$output/linux-headers/asm-riscv/" fi done arch= @@ -169,13 +171,13 @@ mkdir -p "$output/linux-headers/linux" for header in const.h stddef.h kvm.h vfio.h vfio_ccw.h vfio_zdev.h vhost.h \ psci.h psp-sev.h userfaultfd.h memfd.h mman.h nvme_ioctl.h \ vduse.h iommufd.h bits.h; do -cp "$tmpdir/include/linux/$header" "$output/linux-headers/linux" +cp "$hdrdir/include/linux/$header" "$output/linux-headers/linux" done rm -rf "$output/linux-headers/asm-generic" mkdir -p "$output/linux-headers/asm-generic" for header in unistd.h bitsperlong.h mman-common.h mman.h hugetlb_encode.h; do -cp "$tmpdir/include/asm-generic/$header" "$output/linux-headers/asm-generic" +cp "$hdrdir/include/asm-generic/$header" "$output/linux-headers/asm-generic" done if [ -L "$linux/source" ]; then @@ -210,23 +212,23 @@ EOF rm -rf "$output/include/standard-headers/linux" mkdir -p "$output/include/standard-headers/linux" -for i in "$tmpdir"/include/linux/*virtio*.h \ - "$tmpdir/include/linux/qemu_fw_cfg.h" \ - "$tmpdir/include/linux/fuse.h" \ - "$tmpdir/include/linux/input.h" \ - "$tmpdir/include/linux/input-event-codes.h" \ - "$tmpdir/include/linux/udmabuf.h" \ - "$tmpdir/include/linux/pci_regs.h" \ - "$tmpdir/include/linux/ethtool.h" \ - "$tmpdir/include/linux/const.h" \ - "$tmpdir/include/linux/kernel.h" \ - "$tmpdir/include/linux/vhost_types.h" \ - "$tmpdir/include/linux/sysinfo.h" \ - "$tmpdir/include/misc/pvpanic.h"; do +for i in "$hdrdir"/include/linux/*virtio*.h \ + "$hdrdir/include/linux/qemu_fw_cfg.h" \ + "$hdrdir/include/linux/fuse.h" \ + "$hdrdir/include/linux/input.h" \ + "$hdrdir/include/linux/input-event-codes.h" \ + "$hdrdir/include/linux/udmabuf.h" \ + "$hdrdir/include/linux/pci_regs.h" \ + "$hdrdir/include/linux/ethtool.h" \ + "$hdrdir/include/linux/const.h" \ + "$hdrdir/include/linux/kernel.h" \ + "$hdrdir/include/linux/vhost_types.h" \ + "$hdrdir/include/linux/sysinfo.h" \ + "$hdrdir/include/misc/pvpanic.h"; do cp_portable "$i" "$output/include/standard-headers/linux" done mkdir -p "$output/include/standard-headers/drm" -cp_portable "$tmpdir/include/drm/drm_fourcc.h" \ +cp_portable "$hdrdir/include/drm/drm_fourcc.h" \ "$output/include/standard-headers/drm" cat <$output/include/standard-headers/linux/types.h Reviewed-by: Pierrick Bouvier
Re: [PATCH 01/11] tests/tcg: don't append QEMU_OPTS for armv6m-undef test
On 5/14/24 10:42, Alex Bennée wrote: We don't want to build on the default machine setup here but define a custom one for the microbit. Signed-off-by: Alex Bennée --- tests/tcg/arm/Makefile.softmmu-target | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tcg/arm/Makefile.softmmu-target b/tests/tcg/arm/Makefile.softmmu-target index 4c9264057f..39e01ce49d 100644 --- a/tests/tcg/arm/Makefile.softmmu-target +++ b/tests/tcg/arm/Makefile.softmmu-target @@ -16,7 +16,7 @@ test-armv6m-undef: test-armv6m-undef.S $< -o $@ -nostdlib -N -static \ -T $(ARM_SRC)/$@.ld -run-test-armv6m-undef: QEMU_OPTS+=-semihosting -M microbit -kernel +run-test-armv6m-undef: QEMU_OPTS=-semihosting-config enable=on,target=native,chardev=output -M microbit -kernel ARM_TESTS+=test-armv6m-undef Reviewed-by: Pierrick Bouvier
Re: [PATCH v2 13/33] plugins: Use DisasContextBase for qemu_plugin_insn_haddr
On 4/24/24 16:31, Richard Henderson wrote: We can delay the computation of haddr until the plugin actually requests it. Signed-off-by: Richard Henderson --- include/qemu/plugin.h | 4 accel/tcg/plugin-gen.c | 20 plugins/api.c | 25 - 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index 03081be543..3db0e75d16 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -98,7 +98,6 @@ struct qemu_plugin_dyn_cb { /* Internal context for instrumenting an instruction */ struct qemu_plugin_insn { uint64_t vaddr; -void *haddr; GArray *insn_cbs; GArray *mem_cbs; uint8_t len; @@ -119,9 +118,6 @@ struct qemu_plugin_tb { GPtrArray *insns; size_t n; uint64_t vaddr; -uint64_t vaddr2; -void *haddr1; -void *haddr2; /* if set, the TB calls helpers that might access guest memory */ bool mem_helper; diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index a4656859c6..b036773d3c 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -319,9 +319,6 @@ bool plugin_gen_tb_start(CPUState *cpu, const DisasContextBase *db) ret = true; ptb->vaddr = db->pc_first; -ptb->vaddr2 = -1; -ptb->haddr1 = db->host_addr[0]; -ptb->haddr2 = NULL; ptb->mem_helper = false; tcg_gen_plugin_cb(PLUGIN_GEN_FROM_TB); @@ -363,23 +360,6 @@ void plugin_gen_insn_start(CPUState *cpu, const DisasContextBase *db) pc = db->pc_next; insn->vaddr = pc; -/* - * Detect page crossing to get the new host address. - * Note that we skip this when haddr1 == NULL, e.g. when we're - * fetching instructions from a region not backed by RAM. - */ -if (ptb->haddr1 == NULL) { -insn->haddr = NULL; -} else if (is_same_page(db, db->pc_next)) { -insn->haddr = ptb->haddr1 + pc - ptb->vaddr; -} else { -if (ptb->vaddr2 == -1) { -ptb->vaddr2 = TARGET_PAGE_ALIGN(db->pc_first); -get_page_addr_code_hostp(cpu_env(cpu), ptb->vaddr2, >haddr2); -} -insn->haddr = ptb->haddr2 + pc - ptb->vaddr2; -} - tcg_gen_plugin_cb(PLUGIN_GEN_FROM_INSN); } diff --git a/plugins/api.c b/plugins/api.c index 39895a1cb1..4b6690c7d6 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -242,7 +242,30 @@ uint64_t qemu_plugin_insn_vaddr(const struct qemu_plugin_insn *insn) void *qemu_plugin_insn_haddr(const struct qemu_plugin_insn *insn) { -return insn->haddr; +const DisasContextBase *db = tcg_ctx->plugin_db; +vaddr page0_last = db->pc_first | ~TARGET_PAGE_MASK; + +if (db->fake_insn) { +return NULL; +} + +/* + * ??? The return value is not intended for use of host memory, + * but as a proxy for address space and physical address. + * Thus we are only interested in the first byte and do not + * care about spanning pages. + */ +if (insn->vaddr <= page0_last) { +if (db->host_addr[0] == NULL) { +return NULL; +} +return db->host_addr[0] + insn->vaddr - db->pc_first; +} else { +if (db->host_addr[1] == NULL) { +return NULL; +} +return db->host_addr[1] + insn->vaddr - (page0_last + 1); +} } char *qemu_plugin_insn_disas(const struct qemu_plugin_insn *insn) Reviewed-by: Pierrick Bouvier
Re: [PATCH v6 0/9] TCG plugins new inline operations
On 5/7/24 09:07, Alex Bennée wrote: Pierrick Bouvier writes: This series implement two new operations for plugins: - Store inline allows to write a specific value to a scoreboard. - Conditional callback executes a callback only when a given condition is true. The condition is evaluated inline. It's possible to mix various inline operations (add, store) with conditional callbacks, allowing efficient "trap" based counters. It builds on top of new scoreboard API, introduced in the previous series. Queued to plugins/next, thanks. Thanks Alex.
[PATCH v6 3/9] plugins: add new inline op STORE_U64
This new operation can store an immediate u64 value to a given scoreboard. Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- include/qemu/plugin.h | 1 + include/qemu/qemu-plugin.h | 4 ++-- accel/tcg/plugin-gen.c | 13 + plugins/core.c | 6 ++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index bee1647cfc4..0c5df7fa90a 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -70,6 +70,7 @@ enum plugin_dyn_cb_type { PLUGIN_CB_REGULAR, PLUGIN_CB_MEM_REGULAR, PLUGIN_CB_INLINE_ADD_U64, +PLUGIN_CB_INLINE_STORE_U64, }; /* diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 4fc6c3739b2..c5cac897a0b 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -305,12 +305,12 @@ void qemu_plugin_register_vcpu_tb_exec_cb(struct qemu_plugin_tb *tb, * enum qemu_plugin_op - describes an inline op * * @QEMU_PLUGIN_INLINE_ADD_U64: add an immediate value uint64_t - * - * Note: currently only a single inline op is supported. + * @QEMU_PLUGIN_INLINE_STORE_U64: store an immediate value uint64_t */ enum qemu_plugin_op { QEMU_PLUGIN_INLINE_ADD_U64, +QEMU_PLUGIN_INLINE_STORE_U64, }; /** diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index 8e2c3ef94f6..a5313cbbb2f 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -145,6 +145,16 @@ static void gen_inline_add_u64_cb(struct qemu_plugin_dyn_cb *cb) tcg_temp_free_ptr(ptr); } +static void gen_inline_store_u64_cb(struct qemu_plugin_dyn_cb *cb) +{ +TCGv_ptr ptr = gen_plugin_u64_ptr(cb->inline_insn.entry); +TCGv_i64 val = tcg_constant_i64(cb->inline_insn.imm); + +tcg_gen_st_i64(val, ptr, 0); + +tcg_temp_free_ptr(ptr); +} + static void gen_mem_cb(struct qemu_plugin_dyn_cb *cb, qemu_plugin_meminfo_t meminfo, TCGv_i64 addr) { @@ -170,6 +180,9 @@ static void inject_cb(struct qemu_plugin_dyn_cb *cb) case PLUGIN_CB_INLINE_ADD_U64: gen_inline_add_u64_cb(cb); break; +case PLUGIN_CB_INLINE_STORE_U64: +gen_inline_store_u64_cb(cb); +break; default: g_assert_not_reached(); } diff --git a/plugins/core.c b/plugins/core.c index a8557b54ff7..e1bf0dc3717 100644 --- a/plugins/core.c +++ b/plugins/core.c @@ -321,6 +321,8 @@ static enum plugin_dyn_cb_type op_to_cb_type(enum qemu_plugin_op op) switch (op) { case QEMU_PLUGIN_INLINE_ADD_U64: return PLUGIN_CB_INLINE_ADD_U64; +case QEMU_PLUGIN_INLINE_STORE_U64: +return PLUGIN_CB_INLINE_STORE_U64; default: g_assert_not_reached(); } @@ -535,6 +537,9 @@ void exec_inline_op(struct qemu_plugin_dyn_cb *cb, int cpu_index) case QEMU_PLUGIN_INLINE_ADD_U64: *val += cb->inline_insn.imm; break; +case QEMU_PLUGIN_INLINE_STORE_U64: +*val = cb->inline_insn.imm; +break; default: g_assert_not_reached(); } @@ -562,6 +567,7 @@ void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr, vaddr, cb->userp); break; case PLUGIN_CB_INLINE_ADD_U64: +case PLUGIN_CB_INLINE_STORE_U64: exec_inline_op(cb, cpu->cpu_index); break; default: -- 2.39.2
[PATCH v6 6/9] tests/plugin/inline: add test for conditional callback
Count number of tb and insn executed using a conditional callback. We ensure the callback has been called expected number of time (per vcpu). Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- tests/plugin/inline.c | 89 +-- 1 file changed, 86 insertions(+), 3 deletions(-) diff --git a/tests/plugin/inline.c b/tests/plugin/inline.c index 103c3a22f6e..cd63827b7d8 100644 --- a/tests/plugin/inline.c +++ b/tests/plugin/inline.c @@ -20,8 +20,14 @@ typedef struct { uint64_t count_insn_inline; uint64_t count_mem; uint64_t count_mem_inline; +uint64_t tb_cond_num_trigger; +uint64_t tb_cond_track_count; +uint64_t insn_cond_num_trigger; +uint64_t insn_cond_track_count; } CPUCount; +static const uint64_t cond_trigger_limit = 100; + typedef struct { uint64_t data_insn; uint64_t data_tb; @@ -35,6 +41,10 @@ static qemu_plugin_u64 count_insn; static qemu_plugin_u64 count_insn_inline; static qemu_plugin_u64 count_mem; static qemu_plugin_u64 count_mem_inline; +static qemu_plugin_u64 tb_cond_num_trigger; +static qemu_plugin_u64 tb_cond_track_count; +static qemu_plugin_u64 insn_cond_num_trigger; +static qemu_plugin_u64 insn_cond_track_count; static struct qemu_plugin_scoreboard *data; static qemu_plugin_u64 data_insn; static qemu_plugin_u64 data_tb; @@ -56,12 +66,19 @@ static void stats_insn(void) const uint64_t per_vcpu = qemu_plugin_u64_sum(count_insn); const uint64_t inl_per_vcpu = qemu_plugin_u64_sum(count_insn_inline); +const uint64_t cond_num_trigger = +qemu_plugin_u64_sum(insn_cond_num_trigger); +const uint64_t cond_track_left = qemu_plugin_u64_sum(insn_cond_track_count); +const uint64_t conditional = +cond_num_trigger * cond_trigger_limit + cond_track_left; printf("insn: %" PRIu64 "\n", expected); printf("insn: %" PRIu64 " (per vcpu)\n", per_vcpu); printf("insn: %" PRIu64 " (per vcpu inline)\n", inl_per_vcpu); +printf("insn: %" PRIu64 " (cond cb)\n", conditional); g_assert(expected > 0); g_assert(per_vcpu == expected); g_assert(inl_per_vcpu == expected); +g_assert(conditional == expected); } static void stats_tb(void) @@ -70,12 +87,18 @@ static void stats_tb(void) const uint64_t per_vcpu = qemu_plugin_u64_sum(count_tb); const uint64_t inl_per_vcpu = qemu_plugin_u64_sum(count_tb_inline); +const uint64_t cond_num_trigger = qemu_plugin_u64_sum(tb_cond_num_trigger); +const uint64_t cond_track_left = qemu_plugin_u64_sum(tb_cond_track_count); +const uint64_t conditional = +cond_num_trigger * cond_trigger_limit + cond_track_left; printf("tb: %" PRIu64 "\n", expected); printf("tb: %" PRIu64 " (per vcpu)\n", per_vcpu); printf("tb: %" PRIu64 " (per vcpu inline)\n", inl_per_vcpu); +printf("tb: %" PRIu64 " (conditional cb)\n", conditional); g_assert(expected > 0); g_assert(per_vcpu == expected); g_assert(inl_per_vcpu == expected); +g_assert(conditional == expected); } static void stats_mem(void) @@ -104,14 +127,35 @@ static void plugin_exit(qemu_plugin_id_t id, void *udata) const uint64_t insn_inline = qemu_plugin_u64_get(count_insn_inline, i); const uint64_t mem = qemu_plugin_u64_get(count_mem, i); const uint64_t mem_inline = qemu_plugin_u64_get(count_mem_inline, i); -printf("cpu %d: tb (%" PRIu64 ", %" PRIu64 ") | " - "insn (%" PRIu64 ", %" PRIu64 ") | " +const uint64_t tb_cond_trigger = +qemu_plugin_u64_get(tb_cond_num_trigger, i); +const uint64_t tb_cond_left = +qemu_plugin_u64_get(tb_cond_track_count, i); +const uint64_t insn_cond_trigger = +qemu_plugin_u64_get(insn_cond_num_trigger, i); +const uint64_t insn_cond_left = +qemu_plugin_u64_get(insn_cond_track_count, i); +printf("cpu %d: tb (%" PRIu64 ", %" PRIu64 + ", %" PRIu64 " * %" PRIu64 " + %" PRIu64 + ") | " + "insn (%" PRIu64 ", %" PRIu64 + ", %" PRIu64 " * %" PRIu64 " + %" PRIu64 + ") | " "mem (%" PRIu64 ", %" PRIu64 ")" "\n", - i, tb, tb_inline, insn, insn_inline, mem, mem_inline); + i, + tb, tb_inline, + tb_cond_trigger, cond_trigger_limit, tb_cond_left, + insn, insn_inline, + insn_cond_trigger, cond_trigger_limit, insn_cond_left, + mem, mem_inline); g_assert(tb == t
[PATCH v6 2/9] plugins: extract generate ptr for qemu_plugin_u64
Plugin operations can access a scoreboard. This function factorizes code generation for accessing entry associated to a given vcpu. Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- accel/tcg/plugin-gen.c | 27 ++- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index 8028ae76c3a..8e2c3ef94f6 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -113,24 +113,33 @@ static void gen_udata_cb(struct qemu_plugin_dyn_cb *cb) tcg_temp_free_i32(cpu_index); } -static void gen_inline_add_u64_cb(struct qemu_plugin_dyn_cb *cb) +static TCGv_ptr gen_plugin_u64_ptr(qemu_plugin_u64 entry) { -GArray *arr = cb->inline_insn.entry.score->data; -size_t offset = cb->inline_insn.entry.offset; -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); -TCGv_i64 val = tcg_temp_ebb_new_i64(); TCGv_ptr ptr = tcg_temp_ebb_new_ptr(); +GArray *arr = entry.score->data; +char *base_ptr = arr->data + entry.offset; +size_t entry_size = g_array_get_element_size(arr); + +TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); tcg_gen_ld_i32(cpu_index, tcg_env, -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); -tcg_gen_muli_i32(cpu_index, cpu_index, g_array_get_element_size(arr)); +tcg_gen_muli_i32(cpu_index, cpu_index, entry_size); tcg_gen_ext_i32_ptr(ptr, cpu_index); tcg_temp_free_i32(cpu_index); +tcg_gen_addi_ptr(ptr, ptr, (intptr_t) base_ptr); -tcg_gen_addi_ptr(ptr, ptr, (intptr_t)arr->data); -tcg_gen_ld_i64(val, ptr, offset); +return ptr; +} + +static void gen_inline_add_u64_cb(struct qemu_plugin_dyn_cb *cb) +{ +TCGv_ptr ptr = gen_plugin_u64_ptr(cb->inline_insn.entry); +TCGv_i64 val = tcg_temp_ebb_new_i64(); + +tcg_gen_ld_i64(val, ptr, 0); tcg_gen_addi_i64(val, val, cb->inline_insn.imm); -tcg_gen_st_i64(val, ptr, offset); +tcg_gen_st_i64(val, ptr, 0); tcg_temp_free_i64(val); tcg_temp_free_ptr(ptr); -- 2.39.2
[PATCH v6 4/9] tests/plugin/inline: add test for STORE_U64 inline op
Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- tests/plugin/inline.c | 41 + 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/tests/plugin/inline.c b/tests/plugin/inline.c index 0163e9b51c5..103c3a22f6e 100644 --- a/tests/plugin/inline.c +++ b/tests/plugin/inline.c @@ -22,6 +22,12 @@ typedef struct { uint64_t count_mem_inline; } CPUCount; +typedef struct { +uint64_t data_insn; +uint64_t data_tb; +uint64_t data_mem; +} CPUData; + static struct qemu_plugin_scoreboard *counts; static qemu_plugin_u64 count_tb; static qemu_plugin_u64 count_tb_inline; @@ -29,6 +35,10 @@ static qemu_plugin_u64 count_insn; static qemu_plugin_u64 count_insn_inline; static qemu_plugin_u64 count_mem; static qemu_plugin_u64 count_mem_inline; +static struct qemu_plugin_scoreboard *data; +static qemu_plugin_u64 data_insn; +static qemu_plugin_u64 data_tb; +static qemu_plugin_u64 data_mem; static uint64_t global_count_tb; static uint64_t global_count_insn; @@ -109,11 +119,13 @@ static void plugin_exit(qemu_plugin_id_t id, void *udata) stats_mem(); qemu_plugin_scoreboard_free(counts); +qemu_plugin_scoreboard_free(data); } static void vcpu_tb_exec(unsigned int cpu_index, void *udata) { qemu_plugin_u64_add(count_tb, cpu_index, 1); +g_assert(qemu_plugin_u64_get(data_tb, cpu_index) == (uintptr_t) udata); g_mutex_lock(_lock); max_cpu_index = MAX(max_cpu_index, cpu_index); global_count_tb++; @@ -123,6 +135,7 @@ static void vcpu_tb_exec(unsigned int cpu_index, void *udata) static void vcpu_insn_exec(unsigned int cpu_index, void *udata) { qemu_plugin_u64_add(count_insn, cpu_index, 1); +g_assert(qemu_plugin_u64_get(data_insn, cpu_index) == (uintptr_t) udata); g_mutex_lock(_lock); global_count_insn++; g_mutex_unlock(_lock); @@ -131,9 +144,10 @@ static void vcpu_insn_exec(unsigned int cpu_index, void *udata) static void vcpu_mem_access(unsigned int cpu_index, qemu_plugin_meminfo_t info, uint64_t vaddr, -void *userdata) +void *udata) { qemu_plugin_u64_add(count_mem, cpu_index, 1); +g_assert(qemu_plugin_u64_get(data_mem, cpu_index) == (uintptr_t) udata); g_mutex_lock(_lock); global_count_mem++; g_mutex_unlock(_lock); @@ -141,20 +155,34 @@ static void vcpu_mem_access(unsigned int cpu_index, static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb) { +void *tb_store = tb; +qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu( +tb, QEMU_PLUGIN_INLINE_STORE_U64, data_tb, (uintptr_t) tb_store); qemu_plugin_register_vcpu_tb_exec_cb( -tb, vcpu_tb_exec, QEMU_PLUGIN_CB_NO_REGS, 0); +tb, vcpu_tb_exec, QEMU_PLUGIN_CB_NO_REGS, tb_store); qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu( tb, QEMU_PLUGIN_INLINE_ADD_U64, count_tb_inline, 1); for (int idx = 0; idx < qemu_plugin_tb_n_insns(tb); ++idx) { struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, idx); +void *insn_store = insn; +void *mem_store = (char *)insn_store + 0xff; + +qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu( +insn, QEMU_PLUGIN_INLINE_STORE_U64, data_insn, +(uintptr_t) insn_store); qemu_plugin_register_vcpu_insn_exec_cb( -insn, vcpu_insn_exec, QEMU_PLUGIN_CB_NO_REGS, 0); +insn, vcpu_insn_exec, QEMU_PLUGIN_CB_NO_REGS, insn_store); qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu( insn, QEMU_PLUGIN_INLINE_ADD_U64, count_insn_inline, 1); + +qemu_plugin_register_vcpu_mem_inline_per_vcpu( +insn, QEMU_PLUGIN_MEM_RW, +QEMU_PLUGIN_INLINE_STORE_U64, +data_mem, (uintptr_t) mem_store); qemu_plugin_register_vcpu_mem_cb(insn, _mem_access, QEMU_PLUGIN_CB_NO_REGS, - QEMU_PLUGIN_MEM_RW, 0); + QEMU_PLUGIN_MEM_RW, mem_store); qemu_plugin_register_vcpu_mem_inline_per_vcpu( insn, QEMU_PLUGIN_MEM_RW, QEMU_PLUGIN_INLINE_ADD_U64, @@ -179,6 +207,11 @@ int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info, counts, CPUCount, count_insn_inline); count_mem_inline = qemu_plugin_scoreboard_u64_in_struct( counts, CPUCount, count_mem_inline); +data = qemu_plugin_scoreboard_new(sizeof(CPUData)); +data_insn = qemu_plugin_scoreboard_u64_in_struct(data, CPUData, data_insn); +data_tb = qemu_plugin_scoreboard_u64_in_struct(data, CPUData, data_tb); +data_mem = qemu_plugin_scoreboard_u64_in_struct(data, CPUData, data_mem); + qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans); qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); -- 2.39.2
[PATCH v6 5/9] plugins: conditional callbacks
Extend plugins API to support callback called with a given criteria (evaluated inline). Added functions: - qemu_plugin_register_vcpu_tb_exec_cond_cb - qemu_plugin_register_vcpu_insn_exec_cond_cb They expect as parameter a condition, a qemu_plugin_u64_t (op1) and an immediate (op2). Callback is called if op1 |cond| op2 is true. Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- include/qemu/plugin.h| 8 include/qemu/qemu-plugin.h | 76 plugins/plugin.h | 8 accel/tcg/plugin-gen.c | 48 +++ plugins/api.c| 39 ++ plugins/core.c | 32 +++ plugins/qemu-plugins.symbols | 2 + 7 files changed, 213 insertions(+) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index 0c5df7fa90a..0c0aae09e6f 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -68,6 +68,7 @@ union qemu_plugin_cb_sig { enum plugin_dyn_cb_type { PLUGIN_CB_REGULAR, +PLUGIN_CB_COND, PLUGIN_CB_MEM_REGULAR, PLUGIN_CB_INLINE_ADD_U64, PLUGIN_CB_INLINE_STORE_U64, @@ -89,6 +90,13 @@ struct qemu_plugin_dyn_cb { union qemu_plugin_cb_sig f; TCGHelperInfo *info; } regular; +struct { +union qemu_plugin_cb_sig f; +TCGHelperInfo *info; +qemu_plugin_u64 entry; +enum qemu_plugin_cond cond; +uint64_t imm; +} cond; struct { qemu_plugin_u64 entry; enum qemu_plugin_op op; diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index c5cac897a0b..337de25ece7 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -262,6 +262,29 @@ enum qemu_plugin_mem_rw { QEMU_PLUGIN_MEM_RW, }; +/** + * enum qemu_plugin_cond - condition to enable callback + * + * @QEMU_PLUGIN_COND_NEVER: false + * @QEMU_PLUGIN_COND_ALWAYS: true + * @QEMU_PLUGIN_COND_EQ: is equal? + * @QEMU_PLUGIN_COND_NE: is not equal? + * @QEMU_PLUGIN_COND_LT: is less than? + * @QEMU_PLUGIN_COND_LE: is less than or equal? + * @QEMU_PLUGIN_COND_GT: is greater than? + * @QEMU_PLUGIN_COND_GE: is greater than or equal? + */ +enum qemu_plugin_cond { +QEMU_PLUGIN_COND_NEVER, +QEMU_PLUGIN_COND_ALWAYS, +QEMU_PLUGIN_COND_EQ, +QEMU_PLUGIN_COND_NE, +QEMU_PLUGIN_COND_LT, +QEMU_PLUGIN_COND_LE, +QEMU_PLUGIN_COND_GT, +QEMU_PLUGIN_COND_GE, +}; + /** * typedef qemu_plugin_vcpu_tb_trans_cb_t - translation callback * @id: unique plugin id @@ -301,6 +324,32 @@ void qemu_plugin_register_vcpu_tb_exec_cb(struct qemu_plugin_tb *tb, enum qemu_plugin_cb_flags flags, void *userdata); +/** + * qemu_plugin_register_vcpu_tb_exec_cond_cb() - register conditional callback + * @tb: the opaque qemu_plugin_tb handle for the translation + * @cb: callback function + * @cond: condition to enable callback + * @entry: first operand for condition + * @imm: second operand for condition + * @flags: does the plugin read or write the CPU's registers? + * @userdata: any plugin data to pass to the @cb? + * + * The @cb function is called when a translated unit executes if + * entry @cond imm is true. + * If condition is QEMU_PLUGIN_COND_ALWAYS, condition is never interpreted and + * this function is equivalent to qemu_plugin_register_vcpu_tb_exec_cb. + * If condition QEMU_PLUGIN_COND_NEVER, condition is never interpreted and + * callback is never installed. + */ +QEMU_PLUGIN_API +void qemu_plugin_register_vcpu_tb_exec_cond_cb(struct qemu_plugin_tb *tb, + qemu_plugin_vcpu_udata_cb_t cb, + enum qemu_plugin_cb_flags flags, + enum qemu_plugin_cond cond, + qemu_plugin_u64 entry, + uint64_t imm, + void *userdata); + /** * enum qemu_plugin_op - describes an inline op * @@ -344,6 +393,33 @@ void qemu_plugin_register_vcpu_insn_exec_cb(struct qemu_plugin_insn *insn, enum qemu_plugin_cb_flags flags, void *userdata); +/** + * qemu_plugin_register_vcpu_insn_exec_cond_cb() - conditional insn execution cb + * @insn: the opaque qemu_plugin_insn handle for an instruction + * @cb: callback function + * @flags: does the plugin read or write the CPU's registers? + * @cond: condition to enable callback + * @entry: first operand for condition + * @imm: second operand for condition + * @userdata: any plugin data to pass to the @cb? + * + * The @cb function is called when an instruction executes if + * entry @cond imm is true. + * If condition is QEMU_PLUGIN_COND_ALWAYS, condition is never
[PATCH v6 8/9] plugins: extract cpu_index generate
Factorizes function to access current cpu index for a given vcpu. Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- accel/tcg/plugin-gen.c | 28 +--- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index b829a959398..7b73520e788 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -101,12 +101,17 @@ static void gen_disable_mem_helper(void) offsetof(ArchCPU, env)); } +static TCGv_i32 gen_cpu_index(void) +{ +TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); +tcg_gen_ld_i32(cpu_index, tcg_env, + -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +return cpu_index; +} + static void gen_udata_cb(struct qemu_plugin_regular_cb *cb) { -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); - -tcg_gen_ld_i32(cpu_index, tcg_env, - -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +TCGv_i32 cpu_index = gen_cpu_index(); tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL, tcgv_i32_temp(cpu_index), tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); @@ -121,9 +126,7 @@ static TCGv_ptr gen_plugin_u64_ptr(qemu_plugin_u64 entry) char *base_ptr = arr->data + entry.offset; size_t entry_size = g_array_get_element_size(arr); -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); -tcg_gen_ld_i32(cpu_index, tcg_env, - -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +TCGv_i32 cpu_index = gen_cpu_index(); tcg_gen_muli_i32(cpu_index, cpu_index, entry_size); tcg_gen_ext_i32_ptr(ptr, cpu_index); tcg_temp_free_i32(cpu_index); @@ -156,7 +159,6 @@ static TCGCond plugin_cond_to_tcgcond(enum qemu_plugin_cond cond) static void gen_udata_cond_cb(struct qemu_plugin_conditional_cb *cb) { TCGv_ptr ptr = gen_plugin_u64_ptr(cb->entry); -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); TCGv_i64 val = tcg_temp_ebb_new_i64(); TCGLabel *after_cb = gen_new_label(); @@ -165,15 +167,14 @@ static void gen_udata_cond_cb(struct qemu_plugin_conditional_cb *cb) tcg_gen_ld_i64(val, ptr, 0); tcg_gen_brcondi_i64(cond, val, cb->imm, after_cb); -tcg_gen_ld_i32(cpu_index, tcg_env, - -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +TCGv_i32 cpu_index = gen_cpu_index(); tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL, tcgv_i32_temp(cpu_index), tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); +tcg_temp_free_i32(cpu_index); gen_set_label(after_cb); tcg_temp_free_i64(val); -tcg_temp_free_i32(cpu_index); tcg_temp_free_ptr(ptr); } @@ -203,10 +204,7 @@ static void gen_inline_store_u64_cb(struct qemu_plugin_inline_cb *cb) static void gen_mem_cb(struct qemu_plugin_regular_cb *cb, qemu_plugin_meminfo_t meminfo, TCGv_i64 addr) { -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); - -tcg_gen_ld_i32(cpu_index, tcg_env, - -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +TCGv_i32 cpu_index = gen_cpu_index(); tcg_gen_call4(cb->f.vcpu_mem, cb->info, NULL, tcgv_i32_temp(cpu_index), tcgv_i32_temp(tcg_constant_i32(meminfo)), -- 2.39.2
Re: [PATCH v5 0/9] TCG plugins new inline operations
On 5/2/24 13:48, Gustavo Romero wrote: Hi Pierrick, On 5/2/24 5:09 PM, Gustavo Romero wrote: On 5/2/24 4:45 PM, Pierrick Bouvier wrote: Hi Gustavo, On 5/2/24 12:16, Gustavo Romero wrote: Hi Pierrick, On 5/2/24 3:08 PM, Pierrick Bouvier wrote: This series implement two new operations for plugins: - Store inline allows to write a specific value to a scoreboard. - Conditional callback executes a callback only when a given condition is true. The condition is evaluated inline. It's possible to mix various inline operations (add, store) with conditional callbacks, allowing efficient "trap" based counters. It builds on top of new scoreboard API, introduced in the previous series. NOTE: Two patches still need review v2 -- - fixed issue with udata not being passed to conditional callback - added specific test for this in tests/plugin/inline.c (udata was NULL before). v3 -- - rebased on top of "plugins: Rewrite plugin code generation": 20240316015720.3661236-1-richard.hender...@linaro.org - single pass code generation - small cleanups for code generation v4 -- - remove op field from qemu_plugin_inline_cb - use tcg_constant_i64 to load immediate value to store v5 -- - rebase on top of master now that Richard's series was merged Pierrick Bouvier (9): plugins: prepare introduction of new inline ops plugins: extract generate ptr for qemu_plugin_u64 plugins: add new inline op STORE_U64 tests/plugin/inline: add test for STORE_U64 inline op plugins: conditional callbacks tests/plugin/inline: add test for conditional callback plugins: distinct types for callbacks plugins: extract cpu_index generate plugins: remove op from qemu_plugin_inline_cb include/qemu/plugin.h | 42 +++ include/qemu/qemu-plugin.h | 80 - plugins/plugin.h | 12 +++- accel/tcg/plugin-gen.c | 136 +++ plugins/api.c | 39 ++ plugins/core.c | 109 tests/plugin/inline.c | 130 +++-- plugins/qemu-plugins.symbols | 2 + 8 files changed, 466 insertions(+), 84 deletions(-) The description in the commit message of patches 1/9, 2/9, 6/9, 7/9, and 8/9 is missing. Is this intentional? Do you mean there is no multiline commit message for those changes? Indeed, for some of those patches, the change is a single line commit message. I just see a commit title and the Signed-off-by. For example, in 8/9 I see the following on git log: commit f518898aa09b42e317b887237bb75a432b477c6d Author: Pierrick Bouvier Date: Thu May 2 11:08:46 2024 -0700 plugins: extract cpu_index generate Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier It has only the title: "plugins: extract cpu_index generate" and R-b and S-b, so no description about the changes. The description (the body of the commit message) is mentioned here: https://www.qemu.org/docs/master/devel/submitting-a-patch.html#write-a-meaningful-commit-message After quickly looking at the committed logs, I do notice some commits missing it, but I really believe it's important to have it for the reasons outlined in these guidelines. You're right, I just sent a v6 with more elaborated commit messages. Thanks, Pierrick Cheers, Gustavo
[PATCH v6 0/9] TCG plugins new inline operations
This series implement two new operations for plugins: - Store inline allows to write a specific value to a scoreboard. - Conditional callback executes a callback only when a given condition is true. The condition is evaluated inline. It's possible to mix various inline operations (add, store) with conditional callbacks, allowing efficient "trap" based counters. It builds on top of new scoreboard API, introduced in the previous series. v2 -- - fixed issue with udata not being passed to conditional callback - added specific test for this in tests/plugin/inline.c (udata was NULL before). v3 -- - rebased on top of "plugins: Rewrite plugin code generation": 20240316015720.3661236-1-richard.hender...@linaro.org - single pass code generation - small cleanups for code generation v4 -- - remove op field from qemu_plugin_inline_cb - use tcg_constant_i64 to load immediate value to store v5 -- - rebase on top of master now that Richard's series was merged v6 -- - more elaborated commit messages Pierrick Bouvier (9): plugins: prepare introduction of new inline ops plugins: extract generate ptr for qemu_plugin_u64 plugins: add new inline op STORE_U64 tests/plugin/inline: add test for STORE_U64 inline op plugins: conditional callbacks tests/plugin/inline: add test for conditional callback plugins: distinct types for callbacks plugins: extract cpu_index generate plugins: remove op from qemu_plugin_inline_cb include/qemu/plugin.h| 42 +++ include/qemu/qemu-plugin.h | 80 - plugins/plugin.h | 12 +++- accel/tcg/plugin-gen.c | 136 +++ plugins/api.c| 39 ++ plugins/core.c | 109 tests/plugin/inline.c| 130 +++-- plugins/qemu-plugins.symbols | 2 + 8 files changed, 466 insertions(+), 84 deletions(-) -- 2.39.2
[PATCH v6 1/9] plugins: prepare introduction of new inline ops
Until now, only add_u64 was available, and all functions assumed this or were named uniquely. Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- include/qemu/plugin.h | 2 +- accel/tcg/plugin-gen.c | 6 +++--- plugins/core.c | 14 -- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index 18062528c17..bee1647cfc4 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -69,7 +69,7 @@ union qemu_plugin_cb_sig { enum plugin_dyn_cb_type { PLUGIN_CB_REGULAR, PLUGIN_CB_MEM_REGULAR, -PLUGIN_CB_INLINE, +PLUGIN_CB_INLINE_ADD_U64, }; /* diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index 3db74ae9bfe..8028ae76c3a 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -113,7 +113,7 @@ static void gen_udata_cb(struct qemu_plugin_dyn_cb *cb) tcg_temp_free_i32(cpu_index); } -static void gen_inline_cb(struct qemu_plugin_dyn_cb *cb) +static void gen_inline_add_u64_cb(struct qemu_plugin_dyn_cb *cb) { GArray *arr = cb->inline_insn.entry.score->data; size_t offset = cb->inline_insn.entry.offset; @@ -158,8 +158,8 @@ static void inject_cb(struct qemu_plugin_dyn_cb *cb) case PLUGIN_CB_REGULAR: gen_udata_cb(cb); break; -case PLUGIN_CB_INLINE: -gen_inline_cb(cb); +case PLUGIN_CB_INLINE_ADD_U64: +gen_inline_add_u64_cb(cb); break; default: g_assert_not_reached(); diff --git a/plugins/core.c b/plugins/core.c index 0213513ec65..a8557b54ff7 100644 --- a/plugins/core.c +++ b/plugins/core.c @@ -316,6 +316,16 @@ static struct qemu_plugin_dyn_cb *plugin_get_dyn_cb(GArray **arr) return _array_index(cbs, struct qemu_plugin_dyn_cb, cbs->len - 1); } +static enum plugin_dyn_cb_type op_to_cb_type(enum qemu_plugin_op op) +{ +switch (op) { +case QEMU_PLUGIN_INLINE_ADD_U64: +return PLUGIN_CB_INLINE_ADD_U64; +default: +g_assert_not_reached(); +} +} + void plugin_register_inline_op_on_entry(GArray **arr, enum qemu_plugin_mem_rw rw, enum qemu_plugin_op op, @@ -326,7 +336,7 @@ void plugin_register_inline_op_on_entry(GArray **arr, dyn_cb = plugin_get_dyn_cb(arr); dyn_cb->userp = NULL; -dyn_cb->type = PLUGIN_CB_INLINE; +dyn_cb->type = op_to_cb_type(op); dyn_cb->rw = rw; dyn_cb->inline_insn.entry = entry; dyn_cb->inline_insn.op = op; @@ -551,7 +561,7 @@ void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr, cb->regular.f.vcpu_mem(cpu->cpu_index, make_plugin_meminfo(oi, rw), vaddr, cb->userp); break; -case PLUGIN_CB_INLINE: +case PLUGIN_CB_INLINE_ADD_U64: exec_inline_op(cb, cpu->cpu_index); break; default: -- 2.39.2
[PATCH v6 7/9] plugins: distinct types for callbacks
To prevent errors when writing new types of callbacks or inline operations, we split callbacks data to distinct types. Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- include/qemu/plugin.h | 46 ++--- plugins/plugin.h | 2 +- accel/tcg/plugin-gen.c | 58 +--- plugins/core.c | 76 ++ 4 files changed, 98 insertions(+), 84 deletions(-) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index 0c0aae09e6f..313b7c72684 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -74,34 +74,40 @@ enum plugin_dyn_cb_type { PLUGIN_CB_INLINE_STORE_U64, }; +struct qemu_plugin_regular_cb { +union qemu_plugin_cb_sig f; +TCGHelperInfo *info; +void *userp; +enum qemu_plugin_mem_rw rw; +}; + +struct qemu_plugin_inline_cb { +qemu_plugin_u64 entry; +enum qemu_plugin_op op; +uint64_t imm; +enum qemu_plugin_mem_rw rw; +}; + +struct qemu_plugin_conditional_cb { +union qemu_plugin_cb_sig f; +TCGHelperInfo *info; +void *userp; +qemu_plugin_u64 entry; +enum qemu_plugin_cond cond; +uint64_t imm; +}; + /* * A dynamic callback has an insertion point that is determined at run-time. * Usually the insertion point is somewhere in the code cache; think for * instance of a callback to be called upon the execution of a particular TB. */ struct qemu_plugin_dyn_cb { -void *userp; enum plugin_dyn_cb_type type; -/* @rw applies to mem callbacks only (both regular and inline) */ -enum qemu_plugin_mem_rw rw; -/* fields specific to each dyn_cb type go here */ union { -struct { -union qemu_plugin_cb_sig f; -TCGHelperInfo *info; -} regular; -struct { -union qemu_plugin_cb_sig f; -TCGHelperInfo *info; -qemu_plugin_u64 entry; -enum qemu_plugin_cond cond; -uint64_t imm; -} cond; -struct { -qemu_plugin_u64 entry; -enum qemu_plugin_op op; -uint64_t imm; -} inline_insn; +struct qemu_plugin_regular_cb regular; +struct qemu_plugin_conditional_cb cond; +struct qemu_plugin_inline_cb inline_insn; }; }; diff --git a/plugins/plugin.h b/plugins/plugin.h index 7d4b4e21f7c..80d5daa9171 100644 --- a/plugins/plugin.h +++ b/plugins/plugin.h @@ -108,7 +108,7 @@ void plugin_register_vcpu_mem_cb(GArray **arr, enum qemu_plugin_mem_rw rw, void *udata); -void exec_inline_op(struct qemu_plugin_dyn_cb *cb, int cpu_index); +void exec_inline_op(struct qemu_plugin_inline_cb *cb, int cpu_index); int plugin_num_vcpus(void); diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index 9deddd74c42..b829a959398 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -101,13 +101,13 @@ static void gen_disable_mem_helper(void) offsetof(ArchCPU, env)); } -static void gen_udata_cb(struct qemu_plugin_dyn_cb *cb) +static void gen_udata_cb(struct qemu_plugin_regular_cb *cb) { TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); tcg_gen_ld_i32(cpu_index, tcg_env, -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); -tcg_gen_call2(cb->regular.f.vcpu_udata, cb->regular.info, NULL, +tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL, tcgv_i32_temp(cpu_index), tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); tcg_temp_free_i32(cpu_index); @@ -153,21 +153,21 @@ static TCGCond plugin_cond_to_tcgcond(enum qemu_plugin_cond cond) } } -static void gen_udata_cond_cb(struct qemu_plugin_dyn_cb *cb) +static void gen_udata_cond_cb(struct qemu_plugin_conditional_cb *cb) { -TCGv_ptr ptr = gen_plugin_u64_ptr(cb->cond.entry); +TCGv_ptr ptr = gen_plugin_u64_ptr(cb->entry); TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); TCGv_i64 val = tcg_temp_ebb_new_i64(); TCGLabel *after_cb = gen_new_label(); /* Condition should be negated, as calling the cb is the "else" path */ -TCGCond cond = tcg_invert_cond(plugin_cond_to_tcgcond(cb->cond.cond)); +TCGCond cond = tcg_invert_cond(plugin_cond_to_tcgcond(cb->cond)); tcg_gen_ld_i64(val, ptr, 0); -tcg_gen_brcondi_i64(cond, val, cb->cond.imm, after_cb); +tcg_gen_brcondi_i64(cond, val, cb->imm, after_cb); tcg_gen_ld_i32(cpu_index, tcg_env, -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); -tcg_gen_call2(cb->cond.f.vcpu_udata, cb->cond.info, NULL, +tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL, tcgv_i32_temp(cpu_index), tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); gen_set_label(after_cb); @@ -177,37 +177,37 @@ static void gen_udata_cond_cb(struct qemu_plugin_d
[PATCH v6 9/9] plugins: remove op from qemu_plugin_inline_cb
This field is not needed as the callback type already holds this information. Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- include/qemu/plugin.h | 1 - plugins/plugin.h | 4 +++- plugins/core.c| 13 +++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index 313b7c72684..1ad8a59209b 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -83,7 +83,6 @@ struct qemu_plugin_regular_cb { struct qemu_plugin_inline_cb { qemu_plugin_u64 entry; -enum qemu_plugin_op op; uint64_t imm; enum qemu_plugin_mem_rw rw; }; diff --git a/plugins/plugin.h b/plugins/plugin.h index 80d5daa9171..30e2299a54d 100644 --- a/plugins/plugin.h +++ b/plugins/plugin.h @@ -108,7 +108,9 @@ void plugin_register_vcpu_mem_cb(GArray **arr, enum qemu_plugin_mem_rw rw, void *udata); -void exec_inline_op(struct qemu_plugin_inline_cb *cb, int cpu_index); +void exec_inline_op(enum plugin_dyn_cb_type type, +struct qemu_plugin_inline_cb *cb, +int cpu_index); int plugin_num_vcpus(void); diff --git a/plugins/core.c b/plugins/core.c index 7ea2ee208db..a9f19e197aa 100644 --- a/plugins/core.c +++ b/plugins/core.c @@ -338,7 +338,6 @@ void plugin_register_inline_op_on_entry(GArray **arr, struct qemu_plugin_inline_cb inline_cb = { .rw = rw, .entry = entry, - .op = op, .imm = imm }; dyn_cb = plugin_get_dyn_cb(arr); dyn_cb->type = op_to_cb_type(op); @@ -557,7 +556,9 @@ void qemu_plugin_flush_cb(void) plugin_cb__simple(QEMU_PLUGIN_EV_FLUSH); } -void exec_inline_op(struct qemu_plugin_inline_cb *cb, int cpu_index) +void exec_inline_op(enum plugin_dyn_cb_type type, +struct qemu_plugin_inline_cb *cb, +int cpu_index) { char *ptr = cb->entry.score->data->data; size_t elem_size = g_array_get_element_size( @@ -565,11 +566,11 @@ void exec_inline_op(struct qemu_plugin_inline_cb *cb, int cpu_index) size_t offset = cb->entry.offset; uint64_t *val = (uint64_t *)(ptr + offset + cpu_index * elem_size); -switch (cb->op) { -case QEMU_PLUGIN_INLINE_ADD_U64: +switch (type) { +case PLUGIN_CB_INLINE_ADD_U64: *val += cb->imm; break; -case QEMU_PLUGIN_INLINE_STORE_U64: +case PLUGIN_CB_INLINE_STORE_U64: *val = cb->imm; break; default: @@ -601,7 +602,7 @@ void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr, case PLUGIN_CB_INLINE_ADD_U64: case PLUGIN_CB_INLINE_STORE_U64: if (rw && cb->inline_insn.rw) { -exec_inline_op(>inline_insn, cpu->cpu_index); +exec_inline_op(cb->type, >inline_insn, cpu->cpu_index); } break; default: -- 2.39.2
Re: [PATCH v5 0/9] TCG plugins new inline operations
Hi Gustavo, On 5/2/24 12:16, Gustavo Romero wrote: Hi Pierrick, On 5/2/24 3:08 PM, Pierrick Bouvier wrote: This series implement two new operations for plugins: - Store inline allows to write a specific value to a scoreboard. - Conditional callback executes a callback only when a given condition is true. The condition is evaluated inline. It's possible to mix various inline operations (add, store) with conditional callbacks, allowing efficient "trap" based counters. It builds on top of new scoreboard API, introduced in the previous series. NOTE: Two patches still need review v2 -- - fixed issue with udata not being passed to conditional callback - added specific test for this in tests/plugin/inline.c (udata was NULL before). v3 -- - rebased on top of "plugins: Rewrite plugin code generation": 20240316015720.3661236-1-richard.hender...@linaro.org - single pass code generation - small cleanups for code generation v4 -- - remove op field from qemu_plugin_inline_cb - use tcg_constant_i64 to load immediate value to store v5 -- - rebase on top of master now that Richard's series was merged Pierrick Bouvier (9): plugins: prepare introduction of new inline ops plugins: extract generate ptr for qemu_plugin_u64 plugins: add new inline op STORE_U64 tests/plugin/inline: add test for STORE_U64 inline op plugins: conditional callbacks tests/plugin/inline: add test for conditional callback plugins: distinct types for callbacks plugins: extract cpu_index generate plugins: remove op from qemu_plugin_inline_cb include/qemu/plugin.h| 42 +++ include/qemu/qemu-plugin.h | 80 - plugins/plugin.h | 12 +++- accel/tcg/plugin-gen.c | 136 +++ plugins/api.c| 39 ++ plugins/core.c | 109 tests/plugin/inline.c| 130 +++-- plugins/qemu-plugins.symbols | 2 + 8 files changed, 466 insertions(+), 84 deletions(-) The description in the commit message of patches 1/9, 2/9, 6/9, 7/9, and 8/9 is missing. Is this intentional? Do you mean there is no multiline commit message for those changes? Indeed, for some of those patches, the change is a single line commit message. Cheers, Gustavo
Re: [PATCH v5 0/9] TCG plugins new inline operations
Contrary to what the cover letter says, all patches have been reviewed in the series since v4. On 5/2/24 11:08, Pierrick Bouvier wrote: This series implement two new operations for plugins: - Store inline allows to write a specific value to a scoreboard. - Conditional callback executes a callback only when a given condition is true. The condition is evaluated inline. It's possible to mix various inline operations (add, store) with conditional callbacks, allowing efficient "trap" based counters. It builds on top of new scoreboard API, introduced in the previous series. NOTE: Two patches still need review v2 -- - fixed issue with udata not being passed to conditional callback - added specific test for this in tests/plugin/inline.c (udata was NULL before). v3 -- - rebased on top of "plugins: Rewrite plugin code generation": 20240316015720.3661236-1-richard.hender...@linaro.org - single pass code generation - small cleanups for code generation v4 -- - remove op field from qemu_plugin_inline_cb - use tcg_constant_i64 to load immediate value to store v5 -- - rebase on top of master now that Richard's series was merged Pierrick Bouvier (9): plugins: prepare introduction of new inline ops plugins: extract generate ptr for qemu_plugin_u64 plugins: add new inline op STORE_U64 tests/plugin/inline: add test for STORE_U64 inline op plugins: conditional callbacks tests/plugin/inline: add test for conditional callback plugins: distinct types for callbacks plugins: extract cpu_index generate plugins: remove op from qemu_plugin_inline_cb include/qemu/plugin.h| 42 +++ include/qemu/qemu-plugin.h | 80 - plugins/plugin.h | 12 +++- accel/tcg/plugin-gen.c | 136 +++ plugins/api.c| 39 ++ plugins/core.c | 109 tests/plugin/inline.c| 130 +++-- plugins/qemu-plugins.symbols | 2 + 8 files changed, 466 insertions(+), 84 deletions(-)
[PATCH v5 2/9] plugins: extract generate ptr for qemu_plugin_u64
Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- accel/tcg/plugin-gen.c | 27 ++- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index 8028ae76c3a..8e2c3ef94f6 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -113,24 +113,33 @@ static void gen_udata_cb(struct qemu_plugin_dyn_cb *cb) tcg_temp_free_i32(cpu_index); } -static void gen_inline_add_u64_cb(struct qemu_plugin_dyn_cb *cb) +static TCGv_ptr gen_plugin_u64_ptr(qemu_plugin_u64 entry) { -GArray *arr = cb->inline_insn.entry.score->data; -size_t offset = cb->inline_insn.entry.offset; -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); -TCGv_i64 val = tcg_temp_ebb_new_i64(); TCGv_ptr ptr = tcg_temp_ebb_new_ptr(); +GArray *arr = entry.score->data; +char *base_ptr = arr->data + entry.offset; +size_t entry_size = g_array_get_element_size(arr); + +TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); tcg_gen_ld_i32(cpu_index, tcg_env, -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); -tcg_gen_muli_i32(cpu_index, cpu_index, g_array_get_element_size(arr)); +tcg_gen_muli_i32(cpu_index, cpu_index, entry_size); tcg_gen_ext_i32_ptr(ptr, cpu_index); tcg_temp_free_i32(cpu_index); +tcg_gen_addi_ptr(ptr, ptr, (intptr_t) base_ptr); -tcg_gen_addi_ptr(ptr, ptr, (intptr_t)arr->data); -tcg_gen_ld_i64(val, ptr, offset); +return ptr; +} + +static void gen_inline_add_u64_cb(struct qemu_plugin_dyn_cb *cb) +{ +TCGv_ptr ptr = gen_plugin_u64_ptr(cb->inline_insn.entry); +TCGv_i64 val = tcg_temp_ebb_new_i64(); + +tcg_gen_ld_i64(val, ptr, 0); tcg_gen_addi_i64(val, val, cb->inline_insn.imm); -tcg_gen_st_i64(val, ptr, offset); +tcg_gen_st_i64(val, ptr, 0); tcg_temp_free_i64(val); tcg_temp_free_ptr(ptr); -- 2.39.2
[PATCH v5 4/9] tests/plugin/inline: add test for STORE_U64 inline op
Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- tests/plugin/inline.c | 41 + 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/tests/plugin/inline.c b/tests/plugin/inline.c index 0163e9b51c5..103c3a22f6e 100644 --- a/tests/plugin/inline.c +++ b/tests/plugin/inline.c @@ -22,6 +22,12 @@ typedef struct { uint64_t count_mem_inline; } CPUCount; +typedef struct { +uint64_t data_insn; +uint64_t data_tb; +uint64_t data_mem; +} CPUData; + static struct qemu_plugin_scoreboard *counts; static qemu_plugin_u64 count_tb; static qemu_plugin_u64 count_tb_inline; @@ -29,6 +35,10 @@ static qemu_plugin_u64 count_insn; static qemu_plugin_u64 count_insn_inline; static qemu_plugin_u64 count_mem; static qemu_plugin_u64 count_mem_inline; +static struct qemu_plugin_scoreboard *data; +static qemu_plugin_u64 data_insn; +static qemu_plugin_u64 data_tb; +static qemu_plugin_u64 data_mem; static uint64_t global_count_tb; static uint64_t global_count_insn; @@ -109,11 +119,13 @@ static void plugin_exit(qemu_plugin_id_t id, void *udata) stats_mem(); qemu_plugin_scoreboard_free(counts); +qemu_plugin_scoreboard_free(data); } static void vcpu_tb_exec(unsigned int cpu_index, void *udata) { qemu_plugin_u64_add(count_tb, cpu_index, 1); +g_assert(qemu_plugin_u64_get(data_tb, cpu_index) == (uintptr_t) udata); g_mutex_lock(_lock); max_cpu_index = MAX(max_cpu_index, cpu_index); global_count_tb++; @@ -123,6 +135,7 @@ static void vcpu_tb_exec(unsigned int cpu_index, void *udata) static void vcpu_insn_exec(unsigned int cpu_index, void *udata) { qemu_plugin_u64_add(count_insn, cpu_index, 1); +g_assert(qemu_plugin_u64_get(data_insn, cpu_index) == (uintptr_t) udata); g_mutex_lock(_lock); global_count_insn++; g_mutex_unlock(_lock); @@ -131,9 +144,10 @@ static void vcpu_insn_exec(unsigned int cpu_index, void *udata) static void vcpu_mem_access(unsigned int cpu_index, qemu_plugin_meminfo_t info, uint64_t vaddr, -void *userdata) +void *udata) { qemu_plugin_u64_add(count_mem, cpu_index, 1); +g_assert(qemu_plugin_u64_get(data_mem, cpu_index) == (uintptr_t) udata); g_mutex_lock(_lock); global_count_mem++; g_mutex_unlock(_lock); @@ -141,20 +155,34 @@ static void vcpu_mem_access(unsigned int cpu_index, static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb) { +void *tb_store = tb; +qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu( +tb, QEMU_PLUGIN_INLINE_STORE_U64, data_tb, (uintptr_t) tb_store); qemu_plugin_register_vcpu_tb_exec_cb( -tb, vcpu_tb_exec, QEMU_PLUGIN_CB_NO_REGS, 0); +tb, vcpu_tb_exec, QEMU_PLUGIN_CB_NO_REGS, tb_store); qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu( tb, QEMU_PLUGIN_INLINE_ADD_U64, count_tb_inline, 1); for (int idx = 0; idx < qemu_plugin_tb_n_insns(tb); ++idx) { struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, idx); +void *insn_store = insn; +void *mem_store = (char *)insn_store + 0xff; + +qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu( +insn, QEMU_PLUGIN_INLINE_STORE_U64, data_insn, +(uintptr_t) insn_store); qemu_plugin_register_vcpu_insn_exec_cb( -insn, vcpu_insn_exec, QEMU_PLUGIN_CB_NO_REGS, 0); +insn, vcpu_insn_exec, QEMU_PLUGIN_CB_NO_REGS, insn_store); qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu( insn, QEMU_PLUGIN_INLINE_ADD_U64, count_insn_inline, 1); + +qemu_plugin_register_vcpu_mem_inline_per_vcpu( +insn, QEMU_PLUGIN_MEM_RW, +QEMU_PLUGIN_INLINE_STORE_U64, +data_mem, (uintptr_t) mem_store); qemu_plugin_register_vcpu_mem_cb(insn, _mem_access, QEMU_PLUGIN_CB_NO_REGS, - QEMU_PLUGIN_MEM_RW, 0); + QEMU_PLUGIN_MEM_RW, mem_store); qemu_plugin_register_vcpu_mem_inline_per_vcpu( insn, QEMU_PLUGIN_MEM_RW, QEMU_PLUGIN_INLINE_ADD_U64, @@ -179,6 +207,11 @@ int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info, counts, CPUCount, count_insn_inline); count_mem_inline = qemu_plugin_scoreboard_u64_in_struct( counts, CPUCount, count_mem_inline); +data = qemu_plugin_scoreboard_new(sizeof(CPUData)); +data_insn = qemu_plugin_scoreboard_u64_in_struct(data, CPUData, data_insn); +data_tb = qemu_plugin_scoreboard_u64_in_struct(data, CPUData, data_tb); +data_mem = qemu_plugin_scoreboard_u64_in_struct(data, CPUData, data_mem); + qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans); qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); -- 2.39.2
[PATCH v5 6/9] tests/plugin/inline: add test for conditional callback
Count number of tb and insn executed using a conditional callback. We ensure the callback has been called expected number of time (per vcpu). Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- tests/plugin/inline.c | 89 +-- 1 file changed, 86 insertions(+), 3 deletions(-) diff --git a/tests/plugin/inline.c b/tests/plugin/inline.c index 103c3a22f6e..cd63827b7d8 100644 --- a/tests/plugin/inline.c +++ b/tests/plugin/inline.c @@ -20,8 +20,14 @@ typedef struct { uint64_t count_insn_inline; uint64_t count_mem; uint64_t count_mem_inline; +uint64_t tb_cond_num_trigger; +uint64_t tb_cond_track_count; +uint64_t insn_cond_num_trigger; +uint64_t insn_cond_track_count; } CPUCount; +static const uint64_t cond_trigger_limit = 100; + typedef struct { uint64_t data_insn; uint64_t data_tb; @@ -35,6 +41,10 @@ static qemu_plugin_u64 count_insn; static qemu_plugin_u64 count_insn_inline; static qemu_plugin_u64 count_mem; static qemu_plugin_u64 count_mem_inline; +static qemu_plugin_u64 tb_cond_num_trigger; +static qemu_plugin_u64 tb_cond_track_count; +static qemu_plugin_u64 insn_cond_num_trigger; +static qemu_plugin_u64 insn_cond_track_count; static struct qemu_plugin_scoreboard *data; static qemu_plugin_u64 data_insn; static qemu_plugin_u64 data_tb; @@ -56,12 +66,19 @@ static void stats_insn(void) const uint64_t per_vcpu = qemu_plugin_u64_sum(count_insn); const uint64_t inl_per_vcpu = qemu_plugin_u64_sum(count_insn_inline); +const uint64_t cond_num_trigger = +qemu_plugin_u64_sum(insn_cond_num_trigger); +const uint64_t cond_track_left = qemu_plugin_u64_sum(insn_cond_track_count); +const uint64_t conditional = +cond_num_trigger * cond_trigger_limit + cond_track_left; printf("insn: %" PRIu64 "\n", expected); printf("insn: %" PRIu64 " (per vcpu)\n", per_vcpu); printf("insn: %" PRIu64 " (per vcpu inline)\n", inl_per_vcpu); +printf("insn: %" PRIu64 " (cond cb)\n", conditional); g_assert(expected > 0); g_assert(per_vcpu == expected); g_assert(inl_per_vcpu == expected); +g_assert(conditional == expected); } static void stats_tb(void) @@ -70,12 +87,18 @@ static void stats_tb(void) const uint64_t per_vcpu = qemu_plugin_u64_sum(count_tb); const uint64_t inl_per_vcpu = qemu_plugin_u64_sum(count_tb_inline); +const uint64_t cond_num_trigger = qemu_plugin_u64_sum(tb_cond_num_trigger); +const uint64_t cond_track_left = qemu_plugin_u64_sum(tb_cond_track_count); +const uint64_t conditional = +cond_num_trigger * cond_trigger_limit + cond_track_left; printf("tb: %" PRIu64 "\n", expected); printf("tb: %" PRIu64 " (per vcpu)\n", per_vcpu); printf("tb: %" PRIu64 " (per vcpu inline)\n", inl_per_vcpu); +printf("tb: %" PRIu64 " (conditional cb)\n", conditional); g_assert(expected > 0); g_assert(per_vcpu == expected); g_assert(inl_per_vcpu == expected); +g_assert(conditional == expected); } static void stats_mem(void) @@ -104,14 +127,35 @@ static void plugin_exit(qemu_plugin_id_t id, void *udata) const uint64_t insn_inline = qemu_plugin_u64_get(count_insn_inline, i); const uint64_t mem = qemu_plugin_u64_get(count_mem, i); const uint64_t mem_inline = qemu_plugin_u64_get(count_mem_inline, i); -printf("cpu %d: tb (%" PRIu64 ", %" PRIu64 ") | " - "insn (%" PRIu64 ", %" PRIu64 ") | " +const uint64_t tb_cond_trigger = +qemu_plugin_u64_get(tb_cond_num_trigger, i); +const uint64_t tb_cond_left = +qemu_plugin_u64_get(tb_cond_track_count, i); +const uint64_t insn_cond_trigger = +qemu_plugin_u64_get(insn_cond_num_trigger, i); +const uint64_t insn_cond_left = +qemu_plugin_u64_get(insn_cond_track_count, i); +printf("cpu %d: tb (%" PRIu64 ", %" PRIu64 + ", %" PRIu64 " * %" PRIu64 " + %" PRIu64 + ") | " + "insn (%" PRIu64 ", %" PRIu64 + ", %" PRIu64 " * %" PRIu64 " + %" PRIu64 + ") | " "mem (%" PRIu64 ", %" PRIu64 ")" "\n", - i, tb, tb_inline, insn, insn_inline, mem, mem_inline); + i, + tb, tb_inline, + tb_cond_trigger, cond_trigger_limit, tb_cond_left, + insn, insn_inline, + insn_cond_trigger, cond_trigger_limit, insn_cond_left, + mem, mem_inline); g_assert(tb == t
[PATCH v5 7/9] plugins: distinct types for callbacks
To prevent errors when writing new types of callbacks or inline operations, we split callbacks data to distinct types. Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- include/qemu/plugin.h | 46 ++--- plugins/plugin.h | 2 +- accel/tcg/plugin-gen.c | 58 +--- plugins/core.c | 76 ++ 4 files changed, 98 insertions(+), 84 deletions(-) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index 0c0aae09e6f..313b7c72684 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -74,34 +74,40 @@ enum plugin_dyn_cb_type { PLUGIN_CB_INLINE_STORE_U64, }; +struct qemu_plugin_regular_cb { +union qemu_plugin_cb_sig f; +TCGHelperInfo *info; +void *userp; +enum qemu_plugin_mem_rw rw; +}; + +struct qemu_plugin_inline_cb { +qemu_plugin_u64 entry; +enum qemu_plugin_op op; +uint64_t imm; +enum qemu_plugin_mem_rw rw; +}; + +struct qemu_plugin_conditional_cb { +union qemu_plugin_cb_sig f; +TCGHelperInfo *info; +void *userp; +qemu_plugin_u64 entry; +enum qemu_plugin_cond cond; +uint64_t imm; +}; + /* * A dynamic callback has an insertion point that is determined at run-time. * Usually the insertion point is somewhere in the code cache; think for * instance of a callback to be called upon the execution of a particular TB. */ struct qemu_plugin_dyn_cb { -void *userp; enum plugin_dyn_cb_type type; -/* @rw applies to mem callbacks only (both regular and inline) */ -enum qemu_plugin_mem_rw rw; -/* fields specific to each dyn_cb type go here */ union { -struct { -union qemu_plugin_cb_sig f; -TCGHelperInfo *info; -} regular; -struct { -union qemu_plugin_cb_sig f; -TCGHelperInfo *info; -qemu_plugin_u64 entry; -enum qemu_plugin_cond cond; -uint64_t imm; -} cond; -struct { -qemu_plugin_u64 entry; -enum qemu_plugin_op op; -uint64_t imm; -} inline_insn; +struct qemu_plugin_regular_cb regular; +struct qemu_plugin_conditional_cb cond; +struct qemu_plugin_inline_cb inline_insn; }; }; diff --git a/plugins/plugin.h b/plugins/plugin.h index 7d4b4e21f7c..80d5daa9171 100644 --- a/plugins/plugin.h +++ b/plugins/plugin.h @@ -108,7 +108,7 @@ void plugin_register_vcpu_mem_cb(GArray **arr, enum qemu_plugin_mem_rw rw, void *udata); -void exec_inline_op(struct qemu_plugin_dyn_cb *cb, int cpu_index); +void exec_inline_op(struct qemu_plugin_inline_cb *cb, int cpu_index); int plugin_num_vcpus(void); diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index 9deddd74c42..b829a959398 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -101,13 +101,13 @@ static void gen_disable_mem_helper(void) offsetof(ArchCPU, env)); } -static void gen_udata_cb(struct qemu_plugin_dyn_cb *cb) +static void gen_udata_cb(struct qemu_plugin_regular_cb *cb) { TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); tcg_gen_ld_i32(cpu_index, tcg_env, -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); -tcg_gen_call2(cb->regular.f.vcpu_udata, cb->regular.info, NULL, +tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL, tcgv_i32_temp(cpu_index), tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); tcg_temp_free_i32(cpu_index); @@ -153,21 +153,21 @@ static TCGCond plugin_cond_to_tcgcond(enum qemu_plugin_cond cond) } } -static void gen_udata_cond_cb(struct qemu_plugin_dyn_cb *cb) +static void gen_udata_cond_cb(struct qemu_plugin_conditional_cb *cb) { -TCGv_ptr ptr = gen_plugin_u64_ptr(cb->cond.entry); +TCGv_ptr ptr = gen_plugin_u64_ptr(cb->entry); TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); TCGv_i64 val = tcg_temp_ebb_new_i64(); TCGLabel *after_cb = gen_new_label(); /* Condition should be negated, as calling the cb is the "else" path */ -TCGCond cond = tcg_invert_cond(plugin_cond_to_tcgcond(cb->cond.cond)); +TCGCond cond = tcg_invert_cond(plugin_cond_to_tcgcond(cb->cond)); tcg_gen_ld_i64(val, ptr, 0); -tcg_gen_brcondi_i64(cond, val, cb->cond.imm, after_cb); +tcg_gen_brcondi_i64(cond, val, cb->imm, after_cb); tcg_gen_ld_i32(cpu_index, tcg_env, -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); -tcg_gen_call2(cb->cond.f.vcpu_udata, cb->cond.info, NULL, +tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL, tcgv_i32_temp(cpu_index), tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); gen_set_label(after_cb); @@ -177,37 +177,37 @@ static void gen_udata_cond_cb(struct qemu_plugin_d
[PATCH v5 5/9] plugins: conditional callbacks
Extend plugins API to support callback called with a given criteria (evaluated inline). Added functions: - qemu_plugin_register_vcpu_tb_exec_cond_cb - qemu_plugin_register_vcpu_insn_exec_cond_cb They expect as parameter a condition, a qemu_plugin_u64_t (op1) and an immediate (op2). Callback is called if op1 |cond| op2 is true. Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- include/qemu/plugin.h| 8 include/qemu/qemu-plugin.h | 76 plugins/plugin.h | 8 accel/tcg/plugin-gen.c | 48 +++ plugins/api.c| 39 ++ plugins/core.c | 32 +++ plugins/qemu-plugins.symbols | 2 + 7 files changed, 213 insertions(+) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index 0c5df7fa90a..0c0aae09e6f 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -68,6 +68,7 @@ union qemu_plugin_cb_sig { enum plugin_dyn_cb_type { PLUGIN_CB_REGULAR, +PLUGIN_CB_COND, PLUGIN_CB_MEM_REGULAR, PLUGIN_CB_INLINE_ADD_U64, PLUGIN_CB_INLINE_STORE_U64, @@ -89,6 +90,13 @@ struct qemu_plugin_dyn_cb { union qemu_plugin_cb_sig f; TCGHelperInfo *info; } regular; +struct { +union qemu_plugin_cb_sig f; +TCGHelperInfo *info; +qemu_plugin_u64 entry; +enum qemu_plugin_cond cond; +uint64_t imm; +} cond; struct { qemu_plugin_u64 entry; enum qemu_plugin_op op; diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index c5cac897a0b..337de25ece7 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -262,6 +262,29 @@ enum qemu_plugin_mem_rw { QEMU_PLUGIN_MEM_RW, }; +/** + * enum qemu_plugin_cond - condition to enable callback + * + * @QEMU_PLUGIN_COND_NEVER: false + * @QEMU_PLUGIN_COND_ALWAYS: true + * @QEMU_PLUGIN_COND_EQ: is equal? + * @QEMU_PLUGIN_COND_NE: is not equal? + * @QEMU_PLUGIN_COND_LT: is less than? + * @QEMU_PLUGIN_COND_LE: is less than or equal? + * @QEMU_PLUGIN_COND_GT: is greater than? + * @QEMU_PLUGIN_COND_GE: is greater than or equal? + */ +enum qemu_plugin_cond { +QEMU_PLUGIN_COND_NEVER, +QEMU_PLUGIN_COND_ALWAYS, +QEMU_PLUGIN_COND_EQ, +QEMU_PLUGIN_COND_NE, +QEMU_PLUGIN_COND_LT, +QEMU_PLUGIN_COND_LE, +QEMU_PLUGIN_COND_GT, +QEMU_PLUGIN_COND_GE, +}; + /** * typedef qemu_plugin_vcpu_tb_trans_cb_t - translation callback * @id: unique plugin id @@ -301,6 +324,32 @@ void qemu_plugin_register_vcpu_tb_exec_cb(struct qemu_plugin_tb *tb, enum qemu_plugin_cb_flags flags, void *userdata); +/** + * qemu_plugin_register_vcpu_tb_exec_cond_cb() - register conditional callback + * @tb: the opaque qemu_plugin_tb handle for the translation + * @cb: callback function + * @cond: condition to enable callback + * @entry: first operand for condition + * @imm: second operand for condition + * @flags: does the plugin read or write the CPU's registers? + * @userdata: any plugin data to pass to the @cb? + * + * The @cb function is called when a translated unit executes if + * entry @cond imm is true. + * If condition is QEMU_PLUGIN_COND_ALWAYS, condition is never interpreted and + * this function is equivalent to qemu_plugin_register_vcpu_tb_exec_cb. + * If condition QEMU_PLUGIN_COND_NEVER, condition is never interpreted and + * callback is never installed. + */ +QEMU_PLUGIN_API +void qemu_plugin_register_vcpu_tb_exec_cond_cb(struct qemu_plugin_tb *tb, + qemu_plugin_vcpu_udata_cb_t cb, + enum qemu_plugin_cb_flags flags, + enum qemu_plugin_cond cond, + qemu_plugin_u64 entry, + uint64_t imm, + void *userdata); + /** * enum qemu_plugin_op - describes an inline op * @@ -344,6 +393,33 @@ void qemu_plugin_register_vcpu_insn_exec_cb(struct qemu_plugin_insn *insn, enum qemu_plugin_cb_flags flags, void *userdata); +/** + * qemu_plugin_register_vcpu_insn_exec_cond_cb() - conditional insn execution cb + * @insn: the opaque qemu_plugin_insn handle for an instruction + * @cb: callback function + * @flags: does the plugin read or write the CPU's registers? + * @cond: condition to enable callback + * @entry: first operand for condition + * @imm: second operand for condition + * @userdata: any plugin data to pass to the @cb? + * + * The @cb function is called when an instruction executes if + * entry @cond imm is true. + * If condition is QEMU_PLUGIN_COND_ALWAYS, condition is never
[PATCH v5 8/9] plugins: extract cpu_index generate
Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- accel/tcg/plugin-gen.c | 28 +--- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index b829a959398..7b73520e788 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -101,12 +101,17 @@ static void gen_disable_mem_helper(void) offsetof(ArchCPU, env)); } +static TCGv_i32 gen_cpu_index(void) +{ +TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); +tcg_gen_ld_i32(cpu_index, tcg_env, + -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +return cpu_index; +} + static void gen_udata_cb(struct qemu_plugin_regular_cb *cb) { -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); - -tcg_gen_ld_i32(cpu_index, tcg_env, - -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +TCGv_i32 cpu_index = gen_cpu_index(); tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL, tcgv_i32_temp(cpu_index), tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); @@ -121,9 +126,7 @@ static TCGv_ptr gen_plugin_u64_ptr(qemu_plugin_u64 entry) char *base_ptr = arr->data + entry.offset; size_t entry_size = g_array_get_element_size(arr); -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); -tcg_gen_ld_i32(cpu_index, tcg_env, - -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +TCGv_i32 cpu_index = gen_cpu_index(); tcg_gen_muli_i32(cpu_index, cpu_index, entry_size); tcg_gen_ext_i32_ptr(ptr, cpu_index); tcg_temp_free_i32(cpu_index); @@ -156,7 +159,6 @@ static TCGCond plugin_cond_to_tcgcond(enum qemu_plugin_cond cond) static void gen_udata_cond_cb(struct qemu_plugin_conditional_cb *cb) { TCGv_ptr ptr = gen_plugin_u64_ptr(cb->entry); -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); TCGv_i64 val = tcg_temp_ebb_new_i64(); TCGLabel *after_cb = gen_new_label(); @@ -165,15 +167,14 @@ static void gen_udata_cond_cb(struct qemu_plugin_conditional_cb *cb) tcg_gen_ld_i64(val, ptr, 0); tcg_gen_brcondi_i64(cond, val, cb->imm, after_cb); -tcg_gen_ld_i32(cpu_index, tcg_env, - -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +TCGv_i32 cpu_index = gen_cpu_index(); tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL, tcgv_i32_temp(cpu_index), tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); +tcg_temp_free_i32(cpu_index); gen_set_label(after_cb); tcg_temp_free_i64(val); -tcg_temp_free_i32(cpu_index); tcg_temp_free_ptr(ptr); } @@ -203,10 +204,7 @@ static void gen_inline_store_u64_cb(struct qemu_plugin_inline_cb *cb) static void gen_mem_cb(struct qemu_plugin_regular_cb *cb, qemu_plugin_meminfo_t meminfo, TCGv_i64 addr) { -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); - -tcg_gen_ld_i32(cpu_index, tcg_env, - -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +TCGv_i32 cpu_index = gen_cpu_index(); tcg_gen_call4(cb->f.vcpu_mem, cb->info, NULL, tcgv_i32_temp(cpu_index), tcgv_i32_temp(tcg_constant_i32(meminfo)), -- 2.39.2
[PATCH v5 3/9] plugins: add new inline op STORE_U64
Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- include/qemu/plugin.h | 1 + include/qemu/qemu-plugin.h | 4 ++-- accel/tcg/plugin-gen.c | 13 + plugins/core.c | 6 ++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index bee1647cfc4..0c5df7fa90a 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -70,6 +70,7 @@ enum plugin_dyn_cb_type { PLUGIN_CB_REGULAR, PLUGIN_CB_MEM_REGULAR, PLUGIN_CB_INLINE_ADD_U64, +PLUGIN_CB_INLINE_STORE_U64, }; /* diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 4fc6c3739b2..c5cac897a0b 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -305,12 +305,12 @@ void qemu_plugin_register_vcpu_tb_exec_cb(struct qemu_plugin_tb *tb, * enum qemu_plugin_op - describes an inline op * * @QEMU_PLUGIN_INLINE_ADD_U64: add an immediate value uint64_t - * - * Note: currently only a single inline op is supported. + * @QEMU_PLUGIN_INLINE_STORE_U64: store an immediate value uint64_t */ enum qemu_plugin_op { QEMU_PLUGIN_INLINE_ADD_U64, +QEMU_PLUGIN_INLINE_STORE_U64, }; /** diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index 8e2c3ef94f6..a5313cbbb2f 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -145,6 +145,16 @@ static void gen_inline_add_u64_cb(struct qemu_plugin_dyn_cb *cb) tcg_temp_free_ptr(ptr); } +static void gen_inline_store_u64_cb(struct qemu_plugin_dyn_cb *cb) +{ +TCGv_ptr ptr = gen_plugin_u64_ptr(cb->inline_insn.entry); +TCGv_i64 val = tcg_constant_i64(cb->inline_insn.imm); + +tcg_gen_st_i64(val, ptr, 0); + +tcg_temp_free_ptr(ptr); +} + static void gen_mem_cb(struct qemu_plugin_dyn_cb *cb, qemu_plugin_meminfo_t meminfo, TCGv_i64 addr) { @@ -170,6 +180,9 @@ static void inject_cb(struct qemu_plugin_dyn_cb *cb) case PLUGIN_CB_INLINE_ADD_U64: gen_inline_add_u64_cb(cb); break; +case PLUGIN_CB_INLINE_STORE_U64: +gen_inline_store_u64_cb(cb); +break; default: g_assert_not_reached(); } diff --git a/plugins/core.c b/plugins/core.c index a8557b54ff7..e1bf0dc3717 100644 --- a/plugins/core.c +++ b/plugins/core.c @@ -321,6 +321,8 @@ static enum plugin_dyn_cb_type op_to_cb_type(enum qemu_plugin_op op) switch (op) { case QEMU_PLUGIN_INLINE_ADD_U64: return PLUGIN_CB_INLINE_ADD_U64; +case QEMU_PLUGIN_INLINE_STORE_U64: +return PLUGIN_CB_INLINE_STORE_U64; default: g_assert_not_reached(); } @@ -535,6 +537,9 @@ void exec_inline_op(struct qemu_plugin_dyn_cb *cb, int cpu_index) case QEMU_PLUGIN_INLINE_ADD_U64: *val += cb->inline_insn.imm; break; +case QEMU_PLUGIN_INLINE_STORE_U64: +*val = cb->inline_insn.imm; +break; default: g_assert_not_reached(); } @@ -562,6 +567,7 @@ void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr, vaddr, cb->userp); break; case PLUGIN_CB_INLINE_ADD_U64: +case PLUGIN_CB_INLINE_STORE_U64: exec_inline_op(cb, cpu->cpu_index); break; default: -- 2.39.2
[PATCH v5 1/9] plugins: prepare introduction of new inline ops
Until now, only add_u64 was available, and all functions assumed this or were named uniquely. Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- include/qemu/plugin.h | 2 +- accel/tcg/plugin-gen.c | 6 +++--- plugins/core.c | 14 -- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index 18062528c17..bee1647cfc4 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -69,7 +69,7 @@ union qemu_plugin_cb_sig { enum plugin_dyn_cb_type { PLUGIN_CB_REGULAR, PLUGIN_CB_MEM_REGULAR, -PLUGIN_CB_INLINE, +PLUGIN_CB_INLINE_ADD_U64, }; /* diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index 3db74ae9bfe..8028ae76c3a 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -113,7 +113,7 @@ static void gen_udata_cb(struct qemu_plugin_dyn_cb *cb) tcg_temp_free_i32(cpu_index); } -static void gen_inline_cb(struct qemu_plugin_dyn_cb *cb) +static void gen_inline_add_u64_cb(struct qemu_plugin_dyn_cb *cb) { GArray *arr = cb->inline_insn.entry.score->data; size_t offset = cb->inline_insn.entry.offset; @@ -158,8 +158,8 @@ static void inject_cb(struct qemu_plugin_dyn_cb *cb) case PLUGIN_CB_REGULAR: gen_udata_cb(cb); break; -case PLUGIN_CB_INLINE: -gen_inline_cb(cb); +case PLUGIN_CB_INLINE_ADD_U64: +gen_inline_add_u64_cb(cb); break; default: g_assert_not_reached(); diff --git a/plugins/core.c b/plugins/core.c index 0213513ec65..a8557b54ff7 100644 --- a/plugins/core.c +++ b/plugins/core.c @@ -316,6 +316,16 @@ static struct qemu_plugin_dyn_cb *plugin_get_dyn_cb(GArray **arr) return _array_index(cbs, struct qemu_plugin_dyn_cb, cbs->len - 1); } +static enum plugin_dyn_cb_type op_to_cb_type(enum qemu_plugin_op op) +{ +switch (op) { +case QEMU_PLUGIN_INLINE_ADD_U64: +return PLUGIN_CB_INLINE_ADD_U64; +default: +g_assert_not_reached(); +} +} + void plugin_register_inline_op_on_entry(GArray **arr, enum qemu_plugin_mem_rw rw, enum qemu_plugin_op op, @@ -326,7 +336,7 @@ void plugin_register_inline_op_on_entry(GArray **arr, dyn_cb = plugin_get_dyn_cb(arr); dyn_cb->userp = NULL; -dyn_cb->type = PLUGIN_CB_INLINE; +dyn_cb->type = op_to_cb_type(op); dyn_cb->rw = rw; dyn_cb->inline_insn.entry = entry; dyn_cb->inline_insn.op = op; @@ -551,7 +561,7 @@ void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr, cb->regular.f.vcpu_mem(cpu->cpu_index, make_plugin_meminfo(oi, rw), vaddr, cb->userp); break; -case PLUGIN_CB_INLINE: +case PLUGIN_CB_INLINE_ADD_U64: exec_inline_op(cb, cpu->cpu_index); break; default: -- 2.39.2
[PATCH v5 9/9] plugins: remove op from qemu_plugin_inline_cb
Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- include/qemu/plugin.h | 1 - plugins/plugin.h | 4 +++- plugins/core.c| 13 +++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index 313b7c72684..1ad8a59209b 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -83,7 +83,6 @@ struct qemu_plugin_regular_cb { struct qemu_plugin_inline_cb { qemu_plugin_u64 entry; -enum qemu_plugin_op op; uint64_t imm; enum qemu_plugin_mem_rw rw; }; diff --git a/plugins/plugin.h b/plugins/plugin.h index 80d5daa9171..30e2299a54d 100644 --- a/plugins/plugin.h +++ b/plugins/plugin.h @@ -108,7 +108,9 @@ void plugin_register_vcpu_mem_cb(GArray **arr, enum qemu_plugin_mem_rw rw, void *udata); -void exec_inline_op(struct qemu_plugin_inline_cb *cb, int cpu_index); +void exec_inline_op(enum plugin_dyn_cb_type type, +struct qemu_plugin_inline_cb *cb, +int cpu_index); int plugin_num_vcpus(void); diff --git a/plugins/core.c b/plugins/core.c index 7ea2ee208db..a9f19e197aa 100644 --- a/plugins/core.c +++ b/plugins/core.c @@ -338,7 +338,6 @@ void plugin_register_inline_op_on_entry(GArray **arr, struct qemu_plugin_inline_cb inline_cb = { .rw = rw, .entry = entry, - .op = op, .imm = imm }; dyn_cb = plugin_get_dyn_cb(arr); dyn_cb->type = op_to_cb_type(op); @@ -557,7 +556,9 @@ void qemu_plugin_flush_cb(void) plugin_cb__simple(QEMU_PLUGIN_EV_FLUSH); } -void exec_inline_op(struct qemu_plugin_inline_cb *cb, int cpu_index) +void exec_inline_op(enum plugin_dyn_cb_type type, +struct qemu_plugin_inline_cb *cb, +int cpu_index) { char *ptr = cb->entry.score->data->data; size_t elem_size = g_array_get_element_size( @@ -565,11 +566,11 @@ void exec_inline_op(struct qemu_plugin_inline_cb *cb, int cpu_index) size_t offset = cb->entry.offset; uint64_t *val = (uint64_t *)(ptr + offset + cpu_index * elem_size); -switch (cb->op) { -case QEMU_PLUGIN_INLINE_ADD_U64: +switch (type) { +case PLUGIN_CB_INLINE_ADD_U64: *val += cb->imm; break; -case QEMU_PLUGIN_INLINE_STORE_U64: +case PLUGIN_CB_INLINE_STORE_U64: *val = cb->imm; break; default: @@ -601,7 +602,7 @@ void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr, case PLUGIN_CB_INLINE_ADD_U64: case PLUGIN_CB_INLINE_STORE_U64: if (rw && cb->inline_insn.rw) { -exec_inline_op(>inline_insn, cpu->cpu_index); +exec_inline_op(cb->type, >inline_insn, cpu->cpu_index); } break; default: -- 2.39.2
[PATCH v5 0/9] TCG plugins new inline operations
This series implement two new operations for plugins: - Store inline allows to write a specific value to a scoreboard. - Conditional callback executes a callback only when a given condition is true. The condition is evaluated inline. It's possible to mix various inline operations (add, store) with conditional callbacks, allowing efficient "trap" based counters. It builds on top of new scoreboard API, introduced in the previous series. NOTE: Two patches still need review v2 -- - fixed issue with udata not being passed to conditional callback - added specific test for this in tests/plugin/inline.c (udata was NULL before). v3 -- - rebased on top of "plugins: Rewrite plugin code generation": 20240316015720.3661236-1-richard.hender...@linaro.org - single pass code generation - small cleanups for code generation v4 -- - remove op field from qemu_plugin_inline_cb - use tcg_constant_i64 to load immediate value to store v5 -- - rebase on top of master now that Richard's series was merged Pierrick Bouvier (9): plugins: prepare introduction of new inline ops plugins: extract generate ptr for qemu_plugin_u64 plugins: add new inline op STORE_U64 tests/plugin/inline: add test for STORE_U64 inline op plugins: conditional callbacks tests/plugin/inline: add test for conditional callback plugins: distinct types for callbacks plugins: extract cpu_index generate plugins: remove op from qemu_plugin_inline_cb include/qemu/plugin.h| 42 +++ include/qemu/qemu-plugin.h | 80 - plugins/plugin.h | 12 +++- accel/tcg/plugin-gen.c | 136 +++ plugins/api.c| 39 ++ plugins/core.c | 109 tests/plugin/inline.c| 130 +++-- plugins/qemu-plugins.symbols | 2 + 8 files changed, 466 insertions(+), 84 deletions(-) -- 2.39.2
Re: [PATCH v3 07/20] plugins: Use emit_before_op for PLUGIN_GEN_FROM_TB
} +break; + default: g_assert_not_reached(); } @@ -807,25 +844,6 @@ static void plugin_gen_inject(struct qemu_plugin_tb *plugin_tb) enum plugin_gen_cb type = op->args[1]; switch (from) { -case PLUGIN_GEN_FROM_TB: -{ -g_assert(insn_idx == -1); - -switch (type) { -case PLUGIN_GEN_CB_UDATA: -plugin_gen_tb_udata(plugin_tb, op); -break; -case PLUGIN_GEN_CB_UDATA_R: -plugin_gen_tb_udata_r(plugin_tb, op); -break; -case PLUGIN_GEN_CB_INLINE: -plugin_gen_tb_inline(plugin_tb, op); -break; -default: -g_assert_not_reached(); -} -break; -} case PLUGIN_GEN_FROM_INSN: { g_assert(insn_idx >= 0); diff --git a/plugins/api.c b/plugins/api.c index 8fa5a600ac..5d119e8049 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -92,11 +92,7 @@ void qemu_plugin_register_vcpu_tb_exec_cb(struct qemu_plugin_tb *tb, void *udata) { if (!tb->mem_only) { -int index = flags == QEMU_PLUGIN_CB_R_REGS || -flags == QEMU_PLUGIN_CB_RW_REGS ? -PLUGIN_CB_REGULAR_R : PLUGIN_CB_REGULAR; - -plugin_register_dyn_cb__udata(>cbs[index], +plugin_register_dyn_cb__udata(>cbs[PLUGIN_CB_REGULAR], cb, flags, udata); } } Reviewed-by: Pierrick Bouvier
Re: [PATCH v3 06/20] plugins: Use emit_before_op for PLUGIN_GEN_AFTER_INSN
} -case PLUGIN_GEN_AFTER_INSN: -{ -g_assert(insn_idx >= 0); - -switch (type) { -case PLUGIN_GEN_DISABLE_MEM_HELPER: -plugin_gen_disable_mem_helper(plugin_tb, op, insn_idx); -break; -default: -g_assert_not_reached(); -} -break; -} default: g_assert_not_reached(); } diff --git a/tcg/tcg-op.c b/tcg/tcg-op.c index aa6bc6f57d..0f2026c91c 100644 --- a/tcg/tcg-op.c +++ b/tcg/tcg-op.c @@ -312,6 +312,11 @@ void tcg_gen_mb(TCGBar mb_type) } } +void tcg_gen_plugin_cb(unsigned from) +{ +tcg_gen_op1(INDEX_op_plugin_cb, from); +} + void tcg_gen_plugin_cb_start(unsigned from, unsigned type, unsigned wr) { tcg_gen_op3(INDEX_op_plugin_cb_start, from, type, wr); Reviewed-by: Pierrick Bouvier
Re: [PATCH 18/24] plugins: Include missing 'qemu/bitmap.h' header
On 4/18/24 12:25, Philippe Mathieu-Daudé wrote: "qemu/plugin.h" uses DECLARE_BITMAP(), which is declared in "qemu/bitmap.h". Signed-off-by: Philippe Mathieu-Daudé --- include/qemu/plugin.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index 12a96cea2a..41db748eda 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -13,6 +13,7 @@ #include "qemu/queue.h" #include "qemu/option.h" #include "qemu/plugin-event.h" +#include "qemu/bitmap.h" #include "exec/memopidx.h" #include "hw/core/cpu.h" Reviewed-by: Pierrick Bouvier
Re: [PATCH 7/7] target/i386: Implement TCGCPUOps for plugin register reads
On 4/15/24 21:06, Richard Henderson wrote: Signed-off-by: Richard Henderson --- target/i386/tcg/tcg-cpu.c | 72 ++- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/target/i386/tcg/tcg-cpu.c b/target/i386/tcg/tcg-cpu.c index cca19cd40e..2370053df2 100644 --- a/target/i386/tcg/tcg-cpu.c +++ b/target/i386/tcg/tcg-cpu.c @@ -22,9 +22,11 @@ #include "helper-tcg.h" #include "qemu/accel.h" #include "hw/core/accel-cpu.h" - +#include "gdbstub/helpers.h" +#include "gdb-internal.h" #include "tcg-cpu.h" + /* Frob eflags into and out of the CPU temporary format. */ static void x86_cpu_exec_enter(CPUState *cs) @@ -61,38 +63,74 @@ static void x86_cpu_synchronize_from_tb(CPUState *cs, } } -static void x86_restore_state_to_opc(CPUState *cs, - const TranslationBlock *tb, - const uint64_t *data) +static uint64_t eip_from_unwind(CPUX86State *env, const TranslationBlock *tb, +uint64_t data0) { -X86CPU *cpu = X86_CPU(cs); -CPUX86State *env = >env; -int cc_op = data[1]; uint64_t new_pc; if (tb_cflags(tb) & CF_PCREL) { /* - * data[0] in PC-relative TBs is also a linear address, i.e. an address with - * the CS base added, because it is not guaranteed that EIP bits 12 and higher - * stay the same across the translation block. Add the CS base back before - * replacing the low bits, and subtract it below just like for !CF_PCREL. + * data[0] in PC-relative TBs is also a linear address, + * i.e. an address with the CS base added, because it is + * not guaranteed that EIP bits 12 and higher stay the + * same across the translation block. Add the CS base + * back before replacing the low bits, and subtract it + * below just like for !CF_PCREL. */ uint64_t pc = env->eip + tb->cs_base; -new_pc = (pc & TARGET_PAGE_MASK) | data[0]; +new_pc = (pc & TARGET_PAGE_MASK) | data0; } else { -new_pc = data[0]; +new_pc = data0; } if (tb->flags & HF_CS64_MASK) { -env->eip = new_pc; -} else { -env->eip = (uint32_t)(new_pc - tb->cs_base); +return new_pc; } +return (uint32_t)(new_pc - tb->cs_base); +} +static void x86_restore_state_to_opc(CPUState *cs, + const TranslationBlock *tb, + const uint64_t *data) +{ +CPUX86State *env = cpu_env(cs); +CCOp cc_op; + +env->eip = eip_from_unwind(env, tb, data[0]); + +cc_op = data[1]; if (cc_op != CC_OP_DYNAMIC) { env->cc_op = cc_op; } } +static bool x86_plugin_need_unwind_for_reg(CPUState *cs, int reg) +{ +return reg == IDX_IP_REG || reg == IDX_FLAGS_REG; +} + +static int x86_plugin_unwind_read_reg(CPUState *cs, GByteArray *buf, int reg, + const TranslationBlock *tb, + const uint64_t *data) +{ +CPUX86State *env = cpu_env(cs); +CCOp cc_op; + +switch (reg) { +case IDX_IP_REG: +return gdb_get_regl(buf, eip_from_unwind(env, tb, data[0])); + +case IDX_FLAGS_REG: +cc_op = data[1]; +if (cc_op == CC_OP_DYNAMIC) { +cc_op = env->cc_op; +} +return gdb_get_reg32(buf, cpu_compute_eflags_ccop(env, cc_op)); + +default: +g_assert_not_reached(); +} +} + #ifndef CONFIG_USER_ONLY static bool x86_debug_check_breakpoint(CPUState *cs) { @@ -110,6 +148,8 @@ static const TCGCPUOps x86_tcg_ops = { .initialize = tcg_x86_init, .synchronize_from_tb = x86_cpu_synchronize_from_tb, .restore_state_to_opc = x86_restore_state_to_opc, +.plugin_need_unwind_for_reg = x86_plugin_need_unwind_for_reg, +.plugin_unwind_read_reg = x86_plugin_unwind_read_reg, .cpu_exec_enter = x86_cpu_exec_enter, .cpu_exec_exit = x86_cpu_exec_exit, #ifdef CONFIG_USER_ONLY Reviewed-by: Pierrick Bouvier
Re: [PATCH 6/7] target/i386: Introduce cpu_compute_eflags_ccop
On 4/15/24 21:06, Richard Henderson wrote: This is a generalization of cpu_compute_eflags, with a dynamic value of cc_op, and is thus tcg specific. Signed-off-by: Richard Henderson --- target/i386/cpu.h | 2 ++ target/i386/tcg/cc_helper.c | 10 ++ 2 files changed, 12 insertions(+) diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 6b05738079..285f26d99d 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -2379,6 +2379,8 @@ void cpu_x86_inject_mce(Monitor *mon, X86CPU *cpu, int bank, uint32_t cpu_cc_compute_all(CPUX86State *env1); +uint32_t cpu_compute_eflags_ccop(CPUX86State *env, CCOp op); + static inline uint32_t cpu_compute_eflags(CPUX86State *env) { uint32_t eflags = env->eflags; diff --git a/target/i386/tcg/cc_helper.c b/target/i386/tcg/cc_helper.c index f76e9cb8cf..8203682ca8 100644 --- a/target/i386/tcg/cc_helper.c +++ b/target/i386/tcg/cc_helper.c @@ -225,6 +225,16 @@ uint32_t cpu_cc_compute_all(CPUX86State *env) return helper_cc_compute_all(CC_DST, CC_SRC, CC_SRC2, CC_OP); } +uint32_t cpu_compute_eflags_ccop(CPUX86State *env, CCOp op) +{ +uint32_t eflags; + +eflags = helper_cc_compute_all(CC_DST, CC_SRC, CC_SRC2, op); +eflags |= env->df & DF_MASK; +eflags |= env->eflags & ~(VM_MASK | RF_MASK); +return eflags; +} + target_ulong helper_cc_compute_c(target_ulong dst, target_ulong src1, target_ulong src2, int op) { Reviewed-by: Pierrick Bouvier
Re: [PATCH 5/7] target/i386: Split out gdb-internal.h
On 4/15/24 21:06, Richard Henderson wrote: Signed-off-by: Richard Henderson --- target/i386/gdb-internal.h | 65 ++ target/i386/gdbstub.c | 1 + 2 files changed, 66 insertions(+) create mode 100644 target/i386/gdb-internal.h diff --git a/target/i386/gdb-internal.h b/target/i386/gdb-internal.h new file mode 100644 index 00..7cf4c1a656 --- /dev/null +++ b/target/i386/gdb-internal.h @@ -0,0 +1,65 @@ +/* + * x86 gdb server stub + * + * Copyright (c) 2003-2005 Fabrice Bellard + * Copyright (c) 2013 SUSE LINUX Products GmbH + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef I386_GDB_INTERNAL_H +#define I386_GDB_INTERNAL_H + +/* + * Keep these in sync with assignment to + * gdb_num_core_regs in target/i386/cpu.c + * and with the machine description + */ + +/* + * SEG: 6 segments, plus fs_base, gs_base, kernel_gs_base + */ + +/* + * general regs -> 8 or 16 + */ +#define IDX_NB_IP 1 +#define IDX_NB_FLAGS1 +#define IDX_NB_SEG (6 + 3) +#define IDX_NB_CTL 6 +#define IDX_NB_FP 16 +/* + * fpu regs --> 8 or 16 + */ +#define IDX_NB_MXCSR1 +/* + * total > 8+1+1+9+6+16+8+1=50 or 16+1+1+9+6+16+16+1=66 + */ + +#define IDX_IP_REG CPU_NB_REGS +#define IDX_FLAGS_REG (IDX_IP_REG + IDX_NB_IP) +#define IDX_SEG_REGS(IDX_FLAGS_REG + IDX_NB_FLAGS) +#define IDX_CTL_REGS(IDX_SEG_REGS + IDX_NB_SEG) +#define IDX_FP_REGS (IDX_CTL_REGS + IDX_NB_CTL) +#define IDX_XMM_REGS(IDX_FP_REGS + IDX_NB_FP) +#define IDX_MXCSR_REG (IDX_XMM_REGS + CPU_NB_REGS) + +#define IDX_CTL_CR0_REG (IDX_CTL_REGS + 0) +#define IDX_CTL_CR2_REG (IDX_CTL_REGS + 1) +#define IDX_CTL_CR3_REG (IDX_CTL_REGS + 2) +#define IDX_CTL_CR4_REG (IDX_CTL_REGS + 3) +#define IDX_CTL_CR8_REG (IDX_CTL_REGS + 4) +#define IDX_CTL_EFER_REG(IDX_CTL_REGS + 5) + +#endif diff --git a/target/i386/gdbstub.c b/target/i386/gdbstub.c index ebb000df6a..9662509b82 100644 --- a/target/i386/gdbstub.c +++ b/target/i386/gdbstub.c @@ -20,6 +20,7 @@ #include "qemu/osdep.h" #include "cpu.h" #include "include/gdbstub/helpers.h" +#include "gdb-internal.h" #ifdef TARGET_X86_64 static const int gpr_map[16] = { Reviewed-by: Pierrick Bouvier
Re: [PATCH 4/7] plugins: Introduce TCGCPUOps callbacks for mid-tb register reads
On 4/15/24 21:06, Richard Henderson wrote: Certain target registers are not updated continuously within the translation block. For normal exception handling we use unwind info to re-generate the correct value when required. Leverage that same info for reading those registers for plugins. All targets will need updating for these new callbacks. Signed-off-by: Richard Henderson --- include/hw/core/tcg-cpu-ops.h | 13 + plugins/api.c | 36 +-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/include/hw/core/tcg-cpu-ops.h b/include/hw/core/tcg-cpu-ops.h index bf8ff8e3ee..e954d83edf 100644 --- a/include/hw/core/tcg-cpu-ops.h +++ b/include/hw/core/tcg-cpu-ops.h @@ -49,6 +49,19 @@ struct TCGCPUOps { /** @debug_excp_handler: Callback for handling debug exceptions */ void (*debug_excp_handler)(CPUState *cpu); +/** + * @plugin_need_unwind_for_reg: + * True if unwind info needed for reading reg. + */ +bool (*plugin_need_unwind_for_reg)(CPUState *cpu, int reg); +/** + * @plugin_unwind_read_reg: + * Like CPUClass.gdb_read_register, but for registers that require + * regeneration using unwind info, like in @restore_state_to_opc. + */ +int (*plugin_unwind_read_reg)(CPUState *cpu, GByteArray *buf, int reg, + const TranslationBlock *tb, + const uint64_t *data); #ifdef NEED_CPU_H #ifdef CONFIG_USER_ONLY /** diff --git a/plugins/api.c b/plugins/api.c index 3912c9cc8f..3543647a89 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -40,10 +40,12 @@ #include "qemu/plugin.h" #include "qemu/log.h" #include "tcg/tcg.h" +#include "tcg/insn-start-words.h" #include "exec/exec-all.h" #include "exec/gdbstub.h" #include "exec/ram_addr.h" #include "disas/disas.h" +#include "hw/core/tcg-cpu-ops.h" #include "plugin.h" #ifndef CONFIG_USER_ONLY #include "qemu/plugin-memory.h" @@ -454,9 +456,39 @@ GArray *qemu_plugin_get_registers(void) int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) { -g_assert(current_cpu); +CPUState *cs; +uintptr_t ra; +int regno; -return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg)); +assert(current_cpu); +cs = current_cpu; +ra = cs->neg.plugin_ra; +regno = GPOINTER_TO_INT(reg); + +/* + * When plugin_ra is 0, we have no unwind info. This will be true for + * TB callbacks that happen before any insns of the TB have started. + */ +if (ra) { +const TCGCPUOps *tcg_ops = cs->cc->tcg_ops; + +/* + * For plugins in the middle of the TB, we may need to locate + * and use unwind data to reconstruct a register value. + * Usually this required for the PC, but there may be others. + */ +if (tcg_ops->plugin_need_unwind_for_reg && +tcg_ops->plugin_need_unwind_for_reg(cs, regno)) { +uint64_t data[TARGET_INSN_START_WORDS]; +const TranslationBlock *tb; + +tb = cpu_unwind_state_data(cs, ra, data); +assert(tb); +return tcg_ops->plugin_unwind_read_reg(cs, buf, regno, tb, data); +} +} + +return gdb_read_register(cs, buf, regno); } struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) Reviewed-by: Pierrick Bouvier
Re: [PATCH 3/7] accel/tcg: Return the TranslationBlock from cpu_unwind_state_data
On 4/15/24 21:06, Richard Henderson wrote: Fix the i386 get_memio_eip function to use tb->cflags instead of cs->tcg_cflags. Signed-off-by: Richard Henderson --- include/exec/cpu-common.h | 9 + accel/tcg/translate-all.c | 9 + target/i386/helper.c | 6 -- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/include/exec/cpu-common.h b/include/exec/cpu-common.h index 6346df17ce..f056132cab 100644 --- a/include/exec/cpu-common.h +++ b/include/exec/cpu-common.h @@ -176,12 +176,13 @@ void list_cpus(void); * @host_pc: the host pc within the translation * @data: output data * - * Attempt to load the the unwind state for a host pc occurring in - * translated code. If @host_pc is not in translated code, the - * function returns false; otherwise @data is loaded. + * Attempt to load the the unwind state for a host pc occurring in translated + * code. If @host_pc is not in translated code, the function returns NULL; + * otherwise @data is loaded and the TranslationBlock is returned. * This is the same unwind info as given to restore_state_to_opc. */ -bool cpu_unwind_state_data(CPUState *cpu, uintptr_t host_pc, uint64_t *data); +const TranslationBlock *cpu_unwind_state_data(CPUState *cpu, uintptr_t host_pc, + uint64_t *data); /** * cpu_restore_state: diff --git a/accel/tcg/translate-all.c b/accel/tcg/translate-all.c index 83cc14fbde..c745bc5b6c 100644 --- a/accel/tcg/translate-all.c +++ b/accel/tcg/translate-all.c @@ -243,15 +243,16 @@ bool cpu_restore_state(CPUState *cpu, uintptr_t host_pc) return false; } -bool cpu_unwind_state_data(CPUState *cpu, uintptr_t host_pc, uint64_t *data) +const TranslationBlock * +cpu_unwind_state_data(CPUState *cpu, uintptr_t host_pc, uint64_t *data) { if (in_code_gen_buffer((const void *)(host_pc - tcg_splitwx_diff))) { TranslationBlock *tb = tcg_tb_lookup(host_pc); -if (tb) { -return cpu_unwind_data_from_tb(tb, host_pc, data) >= 0; +if (tb && cpu_unwind_data_from_tb(tb, host_pc, data) >= 0) { +return tb; } } -return false; +return NULL; } void page_init(void) diff --git a/target/i386/helper.c b/target/i386/helper.c index 23ccb23a5b..eaa691a851 100644 --- a/target/i386/helper.c +++ b/target/i386/helper.c @@ -517,13 +517,15 @@ static inline target_ulong get_memio_eip(CPUX86State *env) #ifdef CONFIG_TCG uint64_t data[TARGET_INSN_START_WORDS]; CPUState *cs = env_cpu(env); +const TranslationBlock *tb; -if (!cpu_unwind_state_data(cs, cs->mem_io_pc, data)) { +tb = cpu_unwind_state_data(cs, cs->mem_io_pc, data); +if (!tb) { return env->eip; } /* Per x86_restore_state_to_opc. */ -if (cs->tcg_cflags & CF_PCREL) { +if (tb->cflags & CF_PCREL) { return (env->eip & TARGET_PAGE_MASK) | data[0]; } else { return data[0] - env->segs[R_CS].base; Reviewed-by: Pierrick Bouvier
Re: [PATCH 2/7] accel/tcg: Set CPUState.plugin_ra before all plugin callbacks
On 4/15/24 21:06, Richard Henderson wrote: Signed-off-by: Richard Henderson --- include/hw/core/cpu.h | 1 + accel/tcg/plugin-gen.c | 50 +- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h index 10cd492aff..f4af37c13d 100644 --- a/include/hw/core/cpu.h +++ b/include/hw/core/cpu.h @@ -350,6 +350,7 @@ typedef union IcountDecr { typedef struct CPUNegativeOffsetState { CPUTLB tlb; #ifdef CONFIG_PLUGIN +uintptr_t plugin_ra; GArray *plugin_mem_cbs; #endif IcountDecr icount_decr; diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index 36e9134a5d..f96b49cce6 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -37,6 +37,12 @@ enum plugin_gen_from { PLUGIN_GEN_AFTER_TB, }; +enum plugin_gen_ra { +GEN_RA_DONE, +GEN_RA_FROM_TB, +GEN_RA_FROM_INSN, +}; + /* called before finishing a TB with exit_tb, goto_tb or goto_ptr */ void plugin_gen_disable_mem_helpers(void) { @@ -151,11 +157,38 @@ static void gen_mem_cb(struct qemu_plugin_dyn_cb *cb, tcg_temp_free_i32(cpu_index); } -static void inject_cb(struct qemu_plugin_dyn_cb *cb) +static void inject_ra(enum plugin_gen_ra *gen_ra) +{ +TCGv_ptr ra; + +switch (*gen_ra) { +case GEN_RA_DONE: +return; +case GEN_RA_FROM_TB: +ra = tcg_constant_ptr(NULL); +break; +case GEN_RA_FROM_INSN: +ra = tcg_temp_ebb_new_ptr(); +tcg_gen_plugin_pc(ra); +break; +default: +g_assert_not_reached(); +} + +tcg_gen_st_ptr(ra, tcg_env, + offsetof(CPUState, neg.plugin_ra) - + offsetof(ArchCPU, env)); +tcg_temp_free_ptr(ra); +*gen_ra = GEN_RA_DONE; +} + +static void inject_cb(struct qemu_plugin_dyn_cb *cb, + enum plugin_gen_ra *gen_ra) { switch (cb->type) { case PLUGIN_CB_REGULAR: +inject_ra(gen_ra); gen_udata_cb(cb); break; case PLUGIN_CB_INLINE: @@ -167,16 +200,18 @@ static void inject_cb(struct qemu_plugin_dyn_cb *cb) } static void inject_mem_cb(struct qemu_plugin_dyn_cb *cb, + enum plugin_gen_ra *gen_ra, enum qemu_plugin_mem_rw rw, qemu_plugin_meminfo_t meminfo, TCGv_i64 addr) { if (cb->rw & rw) { switch (cb->type) { case PLUGIN_CB_MEM_REGULAR: +inject_ra(gen_ra); gen_mem_cb(cb, meminfo, addr); break; default: -inject_cb(cb); +inject_cb(cb, gen_ra); break; } } @@ -186,6 +221,7 @@ static void plugin_gen_inject(struct qemu_plugin_tb *plugin_tb) { TCGOp *op, *next; int insn_idx = -1; +enum plugin_gen_ra gen_ra; if (unlikely(qemu_loglevel_mask(LOG_TB_OP_PLUGIN) && qemu_log_in_addr_range(plugin_tb->vaddr))) { @@ -205,10 +241,12 @@ static void plugin_gen_inject(struct qemu_plugin_tb *plugin_tb) */ memset(tcg_ctx->free_temps, 0, sizeof(tcg_ctx->free_temps)); +gen_ra = GEN_RA_FROM_TB; QTAILQ_FOREACH_SAFE(op, _ctx->ops, link, next) { switch (op->opc) { case INDEX_op_insn_start: insn_idx++; +gen_ra = GEN_RA_FROM_INSN; break; case INDEX_op_plugin_cb: @@ -244,7 +282,8 @@ static void plugin_gen_inject(struct qemu_plugin_tb *plugin_tb) cbs = plugin_tb->cbs; for (i = 0, n = (cbs ? cbs->len : 0); i < n; i++) { inject_cb( -_array_index(cbs, struct qemu_plugin_dyn_cb, i)); +_array_index(cbs, struct qemu_plugin_dyn_cb, i), +_ra); } break; @@ -256,7 +295,8 @@ static void plugin_gen_inject(struct qemu_plugin_tb *plugin_tb) cbs = insn->insn_cbs; for (i = 0, n = (cbs ? cbs->len : 0); i < n; i++) { inject_cb( -_array_index(cbs, struct qemu_plugin_dyn_cb, i)); +_array_index(cbs, struct qemu_plugin_dyn_cb, i), +_ra); } break; @@ -288,7 +328,7 @@ static void plugin_gen_inject(struct qemu_plugin_tb *plugin_tb) cbs = insn->mem_cbs; for (i = 0, n = (cbs ? cbs->len : 0); i < n; i++) { inject_mem_cb(_array_index(cbs, struct qemu_plugin_dyn_cb, i), - rw, meminfo, addr); + _ra, rw, meminfo, addr); } tcg_ctx->emit_before_op = NULL; Reviewed-by: Pierrick Bouvier
Re: [PATCH 1/7] tcg: Introduce INDEX_op_plugin_pc
On 4/15/24 21:06, Richard Henderson wrote: Add an opcode to find a code address within the current insn, for later use with unwinding. Generate the code generically using tcg_reg_alloc_do_movi. Signed-off-by: Richard Henderson --- include/tcg/tcg-op-common.h | 1 + include/tcg/tcg-opc.h | 1 + tcg/tcg-op.c| 5 + tcg/tcg.c | 10 ++ 4 files changed, 17 insertions(+) diff --git a/include/tcg/tcg-op-common.h b/include/tcg/tcg-op-common.h index 009e2778c5..a32c88a182 100644 --- a/include/tcg/tcg-op-common.h +++ b/include/tcg/tcg-op-common.h @@ -76,6 +76,7 @@ void tcg_gen_lookup_and_goto_ptr(void); void tcg_gen_plugin_cb(unsigned from); void tcg_gen_plugin_mem_cb(TCGv_i64 addr, unsigned meminfo); +void tcg_gen_plugin_pc(TCGv_ptr); /* 32 bit ops */ diff --git a/include/tcg/tcg-opc.h b/include/tcg/tcg-opc.h index 546eb49c11..087d1b82da 100644 --- a/include/tcg/tcg-opc.h +++ b/include/tcg/tcg-opc.h @@ -199,6 +199,7 @@ DEF(goto_ptr, 0, 1, 0, TCG_OPF_BB_EXIT | TCG_OPF_BB_END) DEF(plugin_cb, 0, 0, 1, TCG_OPF_NOT_PRESENT) DEF(plugin_mem_cb, 0, 1, 1, TCG_OPF_NOT_PRESENT) +DEF(plugin_pc, 1, 0, 0, TCG_OPF_NOT_PRESENT) /* Replicate ld/st ops for 32 and 64-bit guest addresses. */ DEF(qemu_ld_a32_i32, 1, 1, 1, diff --git a/tcg/tcg-op.c b/tcg/tcg-op.c index eff3728622..b8ca78cbe4 100644 --- a/tcg/tcg-op.c +++ b/tcg/tcg-op.c @@ -322,6 +322,11 @@ void tcg_gen_plugin_mem_cb(TCGv_i64 addr, unsigned meminfo) tcg_gen_op2(INDEX_op_plugin_mem_cb, tcgv_i64_arg(addr), meminfo); } +void tcg_gen_plugin_pc(TCGv_ptr arg) +{ +tcg_gen_op1(INDEX_op_plugin_pc, tcgv_ptr_arg(arg)); +} + /* 32 bit ops */ void tcg_gen_discard_i32(TCGv_i32 arg) diff --git a/tcg/tcg.c b/tcg/tcg.c index d248c52e96..42e2b53729 100644 --- a/tcg/tcg.c +++ b/tcg/tcg.c @@ -4701,6 +4701,13 @@ static void tcg_reg_alloc_mov(TCGContext *s, const TCGOp *op) } } +static void tcg_reg_alloc_plugin_pc(TCGContext *s, const TCGOp *op) +{ +tcg_reg_alloc_do_movi(s, arg_temp(op->args[0]), + (uintptr_t)tcg_splitwx_to_rx(s->code_ptr), + op->life, output_pref(op, 0)); +} + /* * Specialized code generation for INDEX_op_dup_vec. */ @@ -6208,6 +6215,9 @@ int tcg_gen_code(TCGContext *s, TranslationBlock *tb, uint64_t pc_start) case INDEX_op_mov_vec: tcg_reg_alloc_mov(s, op); break; +case INDEX_op_plugin_pc: +tcg_reg_alloc_plugin_pc(s, op); +break; case INDEX_op_dup_vec: tcg_reg_alloc_dup(s, op); break; Reviewed-by: Pierrick Bouvier
Re: [PATCH 0/7] plugins: Use unwind info for special gdb registers
On 4/16/24 19:40, Richard Henderson wrote: On 4/16/24 17:35, Pierrick Bouvier wrote: On 4/15/24 21:06, Richard Henderson wrote: Based-on: 20240404230611.21231-1-richard.hender...@linaro.org ("[PATCH v2 00/21] Rewrite plugin code generation") This is an attempt to fix https://gitlab.com/qemu-project/qemu/-/issues/2208 ("PC is not updated for each instruction in TCG plugins") I have only updated target/i386 so far, but basically all targets need updating for the new callbacks. Extra points to anyone who sees how to avoid the extra code duplication. :-) Thanks for the series Richard. It looks good to me. Besides reviewing individual commits, I have a more general question. From some discussions we had, it seems like that previously gdb stub was correctly updating all register values, and that it has been dropped at some point. Normally gdbstub does not run in the middle of a TB -- we end normally (single-step, breakpoint) or raise an exception (watchpoint). Only then, after TCG state has been made consistent, does gdbstub have access to the CPUState. That makes sense. > Was it for performance reasons, or an architectural change in QEMU? Is gdb stub the right way to poke register values for plugins? I don't know exactly why some registers are not updated correctly in this context, but it seems like we are trying to fix this afterward, instead of identifying root cause. The one or two registers are not updated continuously for performance reasons. And because they are not updated during initial code generation, it's not easy to do so later with plugin injection. But recovering that data is what the unwind info is for -- a bit expensive to access that way, but overall less, with the expectation that it is rare. Thanks for the description, I understand better the approach you picked for that issue. r~
Re: [PATCH 0/7] plugins: Use unwind info for special gdb registers
On 4/15/24 21:06, Richard Henderson wrote: Based-on: 20240404230611.21231-1-richard.hender...@linaro.org ("[PATCH v2 00/21] Rewrite plugin code generation") This is an attempt to fix https://gitlab.com/qemu-project/qemu/-/issues/2208 ("PC is not updated for each instruction in TCG plugins") I have only updated target/i386 so far, but basically all targets need updating for the new callbacks. Extra points to anyone who sees how to avoid the extra code duplication. :-) Thanks for the series Richard. It looks good to me. Besides reviewing individual commits, I have a more general question. From some discussions we had, it seems like that previously gdb stub was correctly updating all register values, and that it has been dropped at some point. Was it for performance reasons, or an architectural change in QEMU? Is gdb stub the right way to poke register values for plugins? I don't know exactly why some registers are not updated correctly in this context, but it seems like we are trying to fix this afterward, instead of identifying root cause. Sorry if my question is irrelevant, I'm trying to understand the full context here. Thanks, Pierrick r~ Richard Henderson (7): tcg: Introduce INDEX_op_plugin_pc accel/tcg: Set CPUState.plugin_ra before all plugin callbacks accel/tcg: Return the TranslationBlock from cpu_unwind_state_data plugins: Introduce TCGCPUOps callbacks for mid-tb register reads target/i386: Split out gdb-internal.h target/i386: Introduce cpu_compute_eflags_ccop target/i386: Implement TCGCPUOps for plugin register reads include/exec/cpu-common.h | 9 +++-- include/hw/core/cpu.h | 1 + include/hw/core/tcg-cpu-ops.h | 13 +++ include/tcg/tcg-op-common.h | 1 + include/tcg/tcg-opc.h | 1 + target/i386/cpu.h | 2 + target/i386/gdb-internal.h| 65 +++ accel/tcg/plugin-gen.c| 50 +--- accel/tcg/translate-all.c | 9 +++-- plugins/api.c | 36 +- target/i386/gdbstub.c | 1 + target/i386/helper.c | 6 ++- target/i386/tcg/cc_helper.c | 10 + target/i386/tcg/tcg-cpu.c | 72 +++ tcg/tcg-op.c | 5 +++ tcg/tcg.c | 10 + 16 files changed, 258 insertions(+), 33 deletions(-) create mode 100644 target/i386/gdb-internal.h
Re: [PATCH 14/19] plugins: fix -Werror=maybe-uninitialized false-positive
On 3/28/24 14:20, marcandre.lur...@redhat.com wrote: From: Marc-André Lureau ../plugins/loader.c:405:15: error: ‘ctx’ may be used uninitialized [-Werror=maybe-uninitialized] Signed-off-by: Marc-André Lureau --- plugins/loader.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/loader.c b/plugins/loader.c index 9768b78eb6..513a429c57 100644 --- a/plugins/loader.c +++ b/plugins/loader.c @@ -390,7 +390,7 @@ void plugin_reset_uninstall(qemu_plugin_id_t id, bool reset) { struct qemu_plugin_reset_data *data; -struct qemu_plugin_ctx *ctx; +struct qemu_plugin_ctx *ctx = NULL; WITH_QEMU_LOCK_GUARD() { ctx = plugin_id_to_ctx_locked(id); Reviewed-by: Pierrick Bouvier
Re: [PATCH v3] contrib/plugins/execlog: Fix compiler warning
On 3/26/24 13:54, Philippe Mathieu-Daudé wrote: I will post a small PR later today, so until Peter has something else planned, I can take it, since the patch LGTM now. Thanks Philippe :)
[PATCH v4 6/9] tests/plugin/inline: add test for conditional callback
Count number of tb and insn executed using a conditional callback. We ensure the callback has been called expected number of time (per vcpu). Signed-off-by: Pierrick Bouvier --- tests/plugin/inline.c | 89 +-- 1 file changed, 86 insertions(+), 3 deletions(-) diff --git a/tests/plugin/inline.c b/tests/plugin/inline.c index 103c3a22f6e..cd63827b7d8 100644 --- a/tests/plugin/inline.c +++ b/tests/plugin/inline.c @@ -20,8 +20,14 @@ typedef struct { uint64_t count_insn_inline; uint64_t count_mem; uint64_t count_mem_inline; +uint64_t tb_cond_num_trigger; +uint64_t tb_cond_track_count; +uint64_t insn_cond_num_trigger; +uint64_t insn_cond_track_count; } CPUCount; +static const uint64_t cond_trigger_limit = 100; + typedef struct { uint64_t data_insn; uint64_t data_tb; @@ -35,6 +41,10 @@ static qemu_plugin_u64 count_insn; static qemu_plugin_u64 count_insn_inline; static qemu_plugin_u64 count_mem; static qemu_plugin_u64 count_mem_inline; +static qemu_plugin_u64 tb_cond_num_trigger; +static qemu_plugin_u64 tb_cond_track_count; +static qemu_plugin_u64 insn_cond_num_trigger; +static qemu_plugin_u64 insn_cond_track_count; static struct qemu_plugin_scoreboard *data; static qemu_plugin_u64 data_insn; static qemu_plugin_u64 data_tb; @@ -56,12 +66,19 @@ static void stats_insn(void) const uint64_t per_vcpu = qemu_plugin_u64_sum(count_insn); const uint64_t inl_per_vcpu = qemu_plugin_u64_sum(count_insn_inline); +const uint64_t cond_num_trigger = +qemu_plugin_u64_sum(insn_cond_num_trigger); +const uint64_t cond_track_left = qemu_plugin_u64_sum(insn_cond_track_count); +const uint64_t conditional = +cond_num_trigger * cond_trigger_limit + cond_track_left; printf("insn: %" PRIu64 "\n", expected); printf("insn: %" PRIu64 " (per vcpu)\n", per_vcpu); printf("insn: %" PRIu64 " (per vcpu inline)\n", inl_per_vcpu); +printf("insn: %" PRIu64 " (cond cb)\n", conditional); g_assert(expected > 0); g_assert(per_vcpu == expected); g_assert(inl_per_vcpu == expected); +g_assert(conditional == expected); } static void stats_tb(void) @@ -70,12 +87,18 @@ static void stats_tb(void) const uint64_t per_vcpu = qemu_plugin_u64_sum(count_tb); const uint64_t inl_per_vcpu = qemu_plugin_u64_sum(count_tb_inline); +const uint64_t cond_num_trigger = qemu_plugin_u64_sum(tb_cond_num_trigger); +const uint64_t cond_track_left = qemu_plugin_u64_sum(tb_cond_track_count); +const uint64_t conditional = +cond_num_trigger * cond_trigger_limit + cond_track_left; printf("tb: %" PRIu64 "\n", expected); printf("tb: %" PRIu64 " (per vcpu)\n", per_vcpu); printf("tb: %" PRIu64 " (per vcpu inline)\n", inl_per_vcpu); +printf("tb: %" PRIu64 " (conditional cb)\n", conditional); g_assert(expected > 0); g_assert(per_vcpu == expected); g_assert(inl_per_vcpu == expected); +g_assert(conditional == expected); } static void stats_mem(void) @@ -104,14 +127,35 @@ static void plugin_exit(qemu_plugin_id_t id, void *udata) const uint64_t insn_inline = qemu_plugin_u64_get(count_insn_inline, i); const uint64_t mem = qemu_plugin_u64_get(count_mem, i); const uint64_t mem_inline = qemu_plugin_u64_get(count_mem_inline, i); -printf("cpu %d: tb (%" PRIu64 ", %" PRIu64 ") | " - "insn (%" PRIu64 ", %" PRIu64 ") | " +const uint64_t tb_cond_trigger = +qemu_plugin_u64_get(tb_cond_num_trigger, i); +const uint64_t tb_cond_left = +qemu_plugin_u64_get(tb_cond_track_count, i); +const uint64_t insn_cond_trigger = +qemu_plugin_u64_get(insn_cond_num_trigger, i); +const uint64_t insn_cond_left = +qemu_plugin_u64_get(insn_cond_track_count, i); +printf("cpu %d: tb (%" PRIu64 ", %" PRIu64 + ", %" PRIu64 " * %" PRIu64 " + %" PRIu64 + ") | " + "insn (%" PRIu64 ", %" PRIu64 + ", %" PRIu64 " * %" PRIu64 " + %" PRIu64 + ") | " "mem (%" PRIu64 ", %" PRIu64 ")" "\n", - i, tb, tb_inline, insn, insn_inline, mem, mem_inline); + i, + tb, tb_inline, + tb_cond_trigger, cond_trigger_limit, tb_cond_left, + insn, insn_inline, + insn_cond_trigger, cond_trigger_limit, insn_cond_left, + mem, mem_inline); g_assert(tb == tb_inline); g_assert(insn ==
[PATCH v4 8/9] plugins: extract cpu_index generate
Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- accel/tcg/plugin-gen.c | 28 +--- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index d00a2b3fd67..eaa0160cab1 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -108,12 +108,17 @@ static void gen_disable_mem_helper(void) offsetof(ArchCPU, env)); } +static TCGv_i32 gen_cpu_index(void) +{ +TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); +tcg_gen_ld_i32(cpu_index, tcg_env, + -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +return cpu_index; +} + static void gen_udata_cb(struct qemu_plugin_regular_cb *cb) { -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); - -tcg_gen_ld_i32(cpu_index, tcg_env, - -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +TCGv_i32 cpu_index = gen_cpu_index(); tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL, tcgv_i32_temp(cpu_index), tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); @@ -128,9 +133,7 @@ static TCGv_ptr gen_plugin_u64_ptr(qemu_plugin_u64 entry) char *base_ptr = arr->data + entry.offset; size_t entry_size = g_array_get_element_size(arr); -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); -tcg_gen_ld_i32(cpu_index, tcg_env, - -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +TCGv_i32 cpu_index = gen_cpu_index(); tcg_gen_muli_i32(cpu_index, cpu_index, entry_size); tcg_gen_ext_i32_ptr(ptr, cpu_index); tcg_temp_free_i32(cpu_index); @@ -163,7 +166,6 @@ static TCGCond plugin_cond_to_tcgcond(enum qemu_plugin_cond cond) static void gen_udata_cond_cb(struct qemu_plugin_conditional_cb *cb) { TCGv_ptr ptr = gen_plugin_u64_ptr(cb->entry); -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); TCGv_i64 val = tcg_temp_ebb_new_i64(); TCGLabel *after_cb = gen_new_label(); @@ -172,15 +174,14 @@ static void gen_udata_cond_cb(struct qemu_plugin_conditional_cb *cb) tcg_gen_ld_i64(val, ptr, 0); tcg_gen_brcondi_i64(cond, val, cb->imm, after_cb); -tcg_gen_ld_i32(cpu_index, tcg_env, - -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +TCGv_i32 cpu_index = gen_cpu_index(); tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL, tcgv_i32_temp(cpu_index), tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); +tcg_temp_free_i32(cpu_index); gen_set_label(after_cb); tcg_temp_free_i64(val); -tcg_temp_free_i32(cpu_index); tcg_temp_free_ptr(ptr); } @@ -210,10 +211,7 @@ static void gen_inline_store_u64_cb(struct qemu_plugin_inline_cb *cb) static void gen_mem_cb(struct qemu_plugin_regular_cb *cb, qemu_plugin_meminfo_t meminfo, TCGv_i64 addr) { -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); - -tcg_gen_ld_i32(cpu_index, tcg_env, - -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +TCGv_i32 cpu_index = gen_cpu_index(); tcg_gen_call4(cb->f.vcpu_mem, cb->info, NULL, tcgv_i32_temp(cpu_index), tcgv_i32_temp(tcg_constant_i32(meminfo)), -- 2.39.2
[PATCH v4 1/9] plugins: prepare introduction of new inline ops
Until now, only add_u64 was available, and all functions assumed this or were named uniquely. Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- include/qemu/plugin.h | 2 +- accel/tcg/plugin-gen.c | 6 +++--- plugins/core.c | 14 -- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index 201889cbeec..23271fbe36a 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -68,7 +68,7 @@ union qemu_plugin_cb_sig { enum plugin_dyn_cb_type { PLUGIN_CB_REGULAR, PLUGIN_CB_MEM_REGULAR, -PLUGIN_CB_INLINE, +PLUGIN_CB_INLINE_ADD_U64, }; /* diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index c3548257798..41d4d83f547 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -120,7 +120,7 @@ static void gen_udata_cb(struct qemu_plugin_dyn_cb *cb) tcg_temp_free_i32(cpu_index); } -static void gen_inline_cb(struct qemu_plugin_dyn_cb *cb) +static void gen_inline_add_u64_cb(struct qemu_plugin_dyn_cb *cb) { GArray *arr = cb->inline_insn.entry.score->data; size_t offset = cb->inline_insn.entry.offset; @@ -165,8 +165,8 @@ static void inject_cb(struct qemu_plugin_dyn_cb *cb) case PLUGIN_CB_REGULAR: gen_udata_cb(cb); break; -case PLUGIN_CB_INLINE: -gen_inline_cb(cb); +case PLUGIN_CB_INLINE_ADD_U64: +gen_inline_add_u64_cb(cb); break; default: g_assert_not_reached(); diff --git a/plugins/core.c b/plugins/core.c index 0213513ec65..a8557b54ff7 100644 --- a/plugins/core.c +++ b/plugins/core.c @@ -316,6 +316,16 @@ static struct qemu_plugin_dyn_cb *plugin_get_dyn_cb(GArray **arr) return _array_index(cbs, struct qemu_plugin_dyn_cb, cbs->len - 1); } +static enum plugin_dyn_cb_type op_to_cb_type(enum qemu_plugin_op op) +{ +switch (op) { +case QEMU_PLUGIN_INLINE_ADD_U64: +return PLUGIN_CB_INLINE_ADD_U64; +default: +g_assert_not_reached(); +} +} + void plugin_register_inline_op_on_entry(GArray **arr, enum qemu_plugin_mem_rw rw, enum qemu_plugin_op op, @@ -326,7 +336,7 @@ void plugin_register_inline_op_on_entry(GArray **arr, dyn_cb = plugin_get_dyn_cb(arr); dyn_cb->userp = NULL; -dyn_cb->type = PLUGIN_CB_INLINE; +dyn_cb->type = op_to_cb_type(op); dyn_cb->rw = rw; dyn_cb->inline_insn.entry = entry; dyn_cb->inline_insn.op = op; @@ -551,7 +561,7 @@ void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr, cb->regular.f.vcpu_mem(cpu->cpu_index, make_plugin_meminfo(oi, rw), vaddr, cb->userp); break; -case PLUGIN_CB_INLINE: +case PLUGIN_CB_INLINE_ADD_U64: exec_inline_op(cb, cpu->cpu_index); break; default: -- 2.39.2
[PATCH v4 9/9] plugins: remove op from qemu_plugin_inline_cb
Signed-off-by: Pierrick Bouvier --- include/qemu/plugin.h | 1 - plugins/plugin.h | 4 +++- plugins/core.c| 13 +++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index a078229942f..7f7b915e495 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -82,7 +82,6 @@ struct qemu_plugin_regular_cb { struct qemu_plugin_inline_cb { qemu_plugin_u64 entry; -enum qemu_plugin_op op; uint64_t imm; enum qemu_plugin_mem_rw rw; }; diff --git a/plugins/plugin.h b/plugins/plugin.h index 80d5daa9171..30e2299a54d 100644 --- a/plugins/plugin.h +++ b/plugins/plugin.h @@ -108,7 +108,9 @@ void plugin_register_vcpu_mem_cb(GArray **arr, enum qemu_plugin_mem_rw rw, void *udata); -void exec_inline_op(struct qemu_plugin_inline_cb *cb, int cpu_index); +void exec_inline_op(enum plugin_dyn_cb_type type, +struct qemu_plugin_inline_cb *cb, +int cpu_index); int plugin_num_vcpus(void); diff --git a/plugins/core.c b/plugins/core.c index 7ea2ee208db..a9f19e197aa 100644 --- a/plugins/core.c +++ b/plugins/core.c @@ -338,7 +338,6 @@ void plugin_register_inline_op_on_entry(GArray **arr, struct qemu_plugin_inline_cb inline_cb = { .rw = rw, .entry = entry, - .op = op, .imm = imm }; dyn_cb = plugin_get_dyn_cb(arr); dyn_cb->type = op_to_cb_type(op); @@ -557,7 +556,9 @@ void qemu_plugin_flush_cb(void) plugin_cb__simple(QEMU_PLUGIN_EV_FLUSH); } -void exec_inline_op(struct qemu_plugin_inline_cb *cb, int cpu_index) +void exec_inline_op(enum plugin_dyn_cb_type type, +struct qemu_plugin_inline_cb *cb, +int cpu_index) { char *ptr = cb->entry.score->data->data; size_t elem_size = g_array_get_element_size( @@ -565,11 +566,11 @@ void exec_inline_op(struct qemu_plugin_inline_cb *cb, int cpu_index) size_t offset = cb->entry.offset; uint64_t *val = (uint64_t *)(ptr + offset + cpu_index * elem_size); -switch (cb->op) { -case QEMU_PLUGIN_INLINE_ADD_U64: +switch (type) { +case PLUGIN_CB_INLINE_ADD_U64: *val += cb->imm; break; -case QEMU_PLUGIN_INLINE_STORE_U64: +case PLUGIN_CB_INLINE_STORE_U64: *val = cb->imm; break; default: @@ -601,7 +602,7 @@ void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr, case PLUGIN_CB_INLINE_ADD_U64: case PLUGIN_CB_INLINE_STORE_U64: if (rw && cb->inline_insn.rw) { -exec_inline_op(>inline_insn, cpu->cpu_index); +exec_inline_op(cb->type, >inline_insn, cpu->cpu_index); } break; default: -- 2.39.2
[PATCH v4 3/9] plugins: add new inline op STORE_U64
Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- include/qemu/plugin.h | 1 + include/qemu/qemu-plugin.h | 4 ++-- accel/tcg/plugin-gen.c | 13 + plugins/core.c | 6 ++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index 23271fbe36a..d1d9b4490df 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -69,6 +69,7 @@ enum plugin_dyn_cb_type { PLUGIN_CB_REGULAR, PLUGIN_CB_MEM_REGULAR, PLUGIN_CB_INLINE_ADD_U64, +PLUGIN_CB_INLINE_STORE_U64, }; /* diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 4fc6c3739b2..c5cac897a0b 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -305,12 +305,12 @@ void qemu_plugin_register_vcpu_tb_exec_cb(struct qemu_plugin_tb *tb, * enum qemu_plugin_op - describes an inline op * * @QEMU_PLUGIN_INLINE_ADD_U64: add an immediate value uint64_t - * - * Note: currently only a single inline op is supported. + * @QEMU_PLUGIN_INLINE_STORE_U64: store an immediate value uint64_t */ enum qemu_plugin_op { QEMU_PLUGIN_INLINE_ADD_U64, +QEMU_PLUGIN_INLINE_STORE_U64, }; /** diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index d3667203546..1cfd7908df1 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -152,6 +152,16 @@ static void gen_inline_add_u64_cb(struct qemu_plugin_dyn_cb *cb) tcg_temp_free_ptr(ptr); } +static void gen_inline_store_u64_cb(struct qemu_plugin_dyn_cb *cb) +{ +TCGv_ptr ptr = gen_plugin_u64_ptr(cb->inline_insn.entry); +TCGv_i64 val = tcg_constant_i64(cb->inline_insn.imm); + +tcg_gen_st_i64(val, ptr, 0); + +tcg_temp_free_ptr(ptr); +} + static void gen_mem_cb(struct qemu_plugin_dyn_cb *cb, qemu_plugin_meminfo_t meminfo, TCGv_i64 addr) { @@ -177,6 +187,9 @@ static void inject_cb(struct qemu_plugin_dyn_cb *cb) case PLUGIN_CB_INLINE_ADD_U64: gen_inline_add_u64_cb(cb); break; +case PLUGIN_CB_INLINE_STORE_U64: +gen_inline_store_u64_cb(cb); +break; default: g_assert_not_reached(); } diff --git a/plugins/core.c b/plugins/core.c index a8557b54ff7..e1bf0dc3717 100644 --- a/plugins/core.c +++ b/plugins/core.c @@ -321,6 +321,8 @@ static enum plugin_dyn_cb_type op_to_cb_type(enum qemu_plugin_op op) switch (op) { case QEMU_PLUGIN_INLINE_ADD_U64: return PLUGIN_CB_INLINE_ADD_U64; +case QEMU_PLUGIN_INLINE_STORE_U64: +return PLUGIN_CB_INLINE_STORE_U64; default: g_assert_not_reached(); } @@ -535,6 +537,9 @@ void exec_inline_op(struct qemu_plugin_dyn_cb *cb, int cpu_index) case QEMU_PLUGIN_INLINE_ADD_U64: *val += cb->inline_insn.imm; break; +case QEMU_PLUGIN_INLINE_STORE_U64: +*val = cb->inline_insn.imm; +break; default: g_assert_not_reached(); } @@ -562,6 +567,7 @@ void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr, vaddr, cb->userp); break; case PLUGIN_CB_INLINE_ADD_U64: +case PLUGIN_CB_INLINE_STORE_U64: exec_inline_op(cb, cpu->cpu_index); break; default: -- 2.39.2
[PATCH v4 4/9] tests/plugin/inline: add test for STORE_U64 inline op
Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- tests/plugin/inline.c | 41 + 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/tests/plugin/inline.c b/tests/plugin/inline.c index 0163e9b51c5..103c3a22f6e 100644 --- a/tests/plugin/inline.c +++ b/tests/plugin/inline.c @@ -22,6 +22,12 @@ typedef struct { uint64_t count_mem_inline; } CPUCount; +typedef struct { +uint64_t data_insn; +uint64_t data_tb; +uint64_t data_mem; +} CPUData; + static struct qemu_plugin_scoreboard *counts; static qemu_plugin_u64 count_tb; static qemu_plugin_u64 count_tb_inline; @@ -29,6 +35,10 @@ static qemu_plugin_u64 count_insn; static qemu_plugin_u64 count_insn_inline; static qemu_plugin_u64 count_mem; static qemu_plugin_u64 count_mem_inline; +static struct qemu_plugin_scoreboard *data; +static qemu_plugin_u64 data_insn; +static qemu_plugin_u64 data_tb; +static qemu_plugin_u64 data_mem; static uint64_t global_count_tb; static uint64_t global_count_insn; @@ -109,11 +119,13 @@ static void plugin_exit(qemu_plugin_id_t id, void *udata) stats_mem(); qemu_plugin_scoreboard_free(counts); +qemu_plugin_scoreboard_free(data); } static void vcpu_tb_exec(unsigned int cpu_index, void *udata) { qemu_plugin_u64_add(count_tb, cpu_index, 1); +g_assert(qemu_plugin_u64_get(data_tb, cpu_index) == (uintptr_t) udata); g_mutex_lock(_lock); max_cpu_index = MAX(max_cpu_index, cpu_index); global_count_tb++; @@ -123,6 +135,7 @@ static void vcpu_tb_exec(unsigned int cpu_index, void *udata) static void vcpu_insn_exec(unsigned int cpu_index, void *udata) { qemu_plugin_u64_add(count_insn, cpu_index, 1); +g_assert(qemu_plugin_u64_get(data_insn, cpu_index) == (uintptr_t) udata); g_mutex_lock(_lock); global_count_insn++; g_mutex_unlock(_lock); @@ -131,9 +144,10 @@ static void vcpu_insn_exec(unsigned int cpu_index, void *udata) static void vcpu_mem_access(unsigned int cpu_index, qemu_plugin_meminfo_t info, uint64_t vaddr, -void *userdata) +void *udata) { qemu_plugin_u64_add(count_mem, cpu_index, 1); +g_assert(qemu_plugin_u64_get(data_mem, cpu_index) == (uintptr_t) udata); g_mutex_lock(_lock); global_count_mem++; g_mutex_unlock(_lock); @@ -141,20 +155,34 @@ static void vcpu_mem_access(unsigned int cpu_index, static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb) { +void *tb_store = tb; +qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu( +tb, QEMU_PLUGIN_INLINE_STORE_U64, data_tb, (uintptr_t) tb_store); qemu_plugin_register_vcpu_tb_exec_cb( -tb, vcpu_tb_exec, QEMU_PLUGIN_CB_NO_REGS, 0); +tb, vcpu_tb_exec, QEMU_PLUGIN_CB_NO_REGS, tb_store); qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu( tb, QEMU_PLUGIN_INLINE_ADD_U64, count_tb_inline, 1); for (int idx = 0; idx < qemu_plugin_tb_n_insns(tb); ++idx) { struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, idx); +void *insn_store = insn; +void *mem_store = (char *)insn_store + 0xff; + +qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu( +insn, QEMU_PLUGIN_INLINE_STORE_U64, data_insn, +(uintptr_t) insn_store); qemu_plugin_register_vcpu_insn_exec_cb( -insn, vcpu_insn_exec, QEMU_PLUGIN_CB_NO_REGS, 0); +insn, vcpu_insn_exec, QEMU_PLUGIN_CB_NO_REGS, insn_store); qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu( insn, QEMU_PLUGIN_INLINE_ADD_U64, count_insn_inline, 1); + +qemu_plugin_register_vcpu_mem_inline_per_vcpu( +insn, QEMU_PLUGIN_MEM_RW, +QEMU_PLUGIN_INLINE_STORE_U64, +data_mem, (uintptr_t) mem_store); qemu_plugin_register_vcpu_mem_cb(insn, _mem_access, QEMU_PLUGIN_CB_NO_REGS, - QEMU_PLUGIN_MEM_RW, 0); + QEMU_PLUGIN_MEM_RW, mem_store); qemu_plugin_register_vcpu_mem_inline_per_vcpu( insn, QEMU_PLUGIN_MEM_RW, QEMU_PLUGIN_INLINE_ADD_U64, @@ -179,6 +207,11 @@ int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info, counts, CPUCount, count_insn_inline); count_mem_inline = qemu_plugin_scoreboard_u64_in_struct( counts, CPUCount, count_mem_inline); +data = qemu_plugin_scoreboard_new(sizeof(CPUData)); +data_insn = qemu_plugin_scoreboard_u64_in_struct(data, CPUData, data_insn); +data_tb = qemu_plugin_scoreboard_u64_in_struct(data, CPUData, data_tb); +data_mem = qemu_plugin_scoreboard_u64_in_struct(data, CPUData, data_mem); + qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans); qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); -- 2.39.2
[PATCH v4 2/9] plugins: extract generate ptr for qemu_plugin_u64
Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- accel/tcg/plugin-gen.c | 27 ++- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index 41d4d83f547..d3667203546 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -120,24 +120,33 @@ static void gen_udata_cb(struct qemu_plugin_dyn_cb *cb) tcg_temp_free_i32(cpu_index); } -static void gen_inline_add_u64_cb(struct qemu_plugin_dyn_cb *cb) +static TCGv_ptr gen_plugin_u64_ptr(qemu_plugin_u64 entry) { -GArray *arr = cb->inline_insn.entry.score->data; -size_t offset = cb->inline_insn.entry.offset; -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); -TCGv_i64 val = tcg_temp_ebb_new_i64(); TCGv_ptr ptr = tcg_temp_ebb_new_ptr(); +GArray *arr = entry.score->data; +char *base_ptr = arr->data + entry.offset; +size_t entry_size = g_array_get_element_size(arr); + +TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); tcg_gen_ld_i32(cpu_index, tcg_env, -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); -tcg_gen_muli_i32(cpu_index, cpu_index, g_array_get_element_size(arr)); +tcg_gen_muli_i32(cpu_index, cpu_index, entry_size); tcg_gen_ext_i32_ptr(ptr, cpu_index); tcg_temp_free_i32(cpu_index); +tcg_gen_addi_ptr(ptr, ptr, (intptr_t) base_ptr); -tcg_gen_addi_ptr(ptr, ptr, (intptr_t)arr->data); -tcg_gen_ld_i64(val, ptr, offset); +return ptr; +} + +static void gen_inline_add_u64_cb(struct qemu_plugin_dyn_cb *cb) +{ +TCGv_ptr ptr = gen_plugin_u64_ptr(cb->inline_insn.entry); +TCGv_i64 val = tcg_temp_ebb_new_i64(); + +tcg_gen_ld_i64(val, ptr, 0); tcg_gen_addi_i64(val, val, cb->inline_insn.imm); -tcg_gen_st_i64(val, ptr, offset); +tcg_gen_st_i64(val, ptr, 0); tcg_temp_free_i64(val); tcg_temp_free_ptr(ptr); -- 2.39.2
[PATCH v4 0/9] TCG plugins new inline operations
This series implement two new operations for plugins: - Store inline allows to write a specific value to a scoreboard. - Conditional callback executes a callback only when a given condition is true. The condition is evaluated inline. It's possible to mix various inline operations (add, store) with conditional callbacks, allowing efficient "trap" based counters. It builds on top of new scoreboard API, introduced in the previous series. NOTE: Two patches still need review v2 -- - fixed issue with udata not being passed to conditional callback - added specific test for this in tests/plugin/inline.c (udata was NULL before). v3 -- - rebased on top of "plugins: Rewrite plugin code generation": 20240316015720.3661236-1-richard.hender...@linaro.org - single pass code generation - small cleanups for code generation v4 -- - remove op field from qemu_plugin_inline_cb - use tcg_constant_i64 to load immediate value to store Pierrick Bouvier (9): plugins: prepare introduction of new inline ops plugins: extract generate ptr for qemu_plugin_u64 plugins: add new inline op STORE_U64 tests/plugin/inline: add test for STORE_U64 inline op plugins: conditional callbacks tests/plugin/inline: add test for conditional callback plugins: distinct types for callbacks plugins: extract cpu_index generate plugins: remove op from qemu_plugin_inline_cb include/qemu/plugin.h| 42 +++ include/qemu/qemu-plugin.h | 80 - plugins/plugin.h | 12 +++- accel/tcg/plugin-gen.c | 136 +++ plugins/api.c| 39 ++ plugins/core.c | 109 tests/plugin/inline.c| 130 +++-- plugins/qemu-plugins.symbols | 2 + 8 files changed, 466 insertions(+), 84 deletions(-) -- 2.39.2
Re: [PATCH v3 7/8] plugins: distinct types for callbacks
On 3/25/24 23:23, Richard Henderson wrote: On 3/25/24 02:41, Pierrick Bouvier wrote: To prevent errors when writing new types of callbacks or inline operations, we split callbacks data to distinct types. Signed-off-by: Pierrick Bouvier --- include/qemu/plugin.h | 46 ++--- plugins/plugin.h | 2 +- accel/tcg/plugin-gen.c | 58 +--- plugins/core.c | 76 ++ 4 files changed, 98 insertions(+), 84 deletions(-) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index bb224b8e4c7..a078229942f 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -73,34 +73,40 @@ enum plugin_dyn_cb_type { PLUGIN_CB_INLINE_STORE_U64, }; +struct qemu_plugin_regular_cb { +union qemu_plugin_cb_sig f; +TCGHelperInfo *info; +void *userp; +enum qemu_plugin_mem_rw rw; +}; + +struct qemu_plugin_inline_cb { +qemu_plugin_u64 entry; +enum qemu_plugin_op op; +uint64_t imm; +enum qemu_plugin_mem_rw rw; +}; Do you still need 'op' anymore here? It seems redundant with 'type'. You're right, removed it in a new commit, will post new series. Otherwise, Reviewed-by: Richard Henderson r~
Re: [PATCH v3] contrib/plugins/execlog: Fix compiler warning
On 3/26/24 05:52, Yao Xingtao wrote: 1. The g_pattern_match_string() is deprecated when glib2 version >= 2.70. Use g_pattern_spec_match_string() instead to avoid this problem. 2. The type of second parameter in g_ptr_array_add() is 'gpointer' {aka 'void *'}, but the type of reg->name is 'const char*'. Cast the type of reg->name to 'gpointer' to avoid this problem. compiler warning message: /root/qemu/contrib/plugins/execlog.c:330:17: warning: ‘g_pattern_match_string’ is deprecated: Use 'g_pattern_spec_match_string' instead [-Wdeprecated-declarations] 330 | if (g_pattern_match_string(pat, rd->name) || | ^~ In file included from /usr/include/glib-2.0/glib.h:67, from /root/qemu/contrib/plugins/execlog.c:9: /usr/include/glib-2.0/glib/gpattern.h:57:15: note: declared here 57 | gboolean g_pattern_match_string (GPatternSpec *pspec, | ^~ /root/qemu/contrib/plugins/execlog.c:331:21: warning: ‘g_pattern_match_string’ is deprecated: Use 'g_pattern_spec_match_string' instead [-Wdeprecated-declarations] 331 | g_pattern_match_string(pat, rd_lower)) { | ^~ /usr/include/glib-2.0/glib/gpattern.h:57:15: note: declared here 57 | gboolean g_pattern_match_string (GPatternSpec *pspec, | ^~ /root/qemu/contrib/plugins/execlog.c:339:63: warning: passing argument 2 of ‘g_ptr_array_add’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers] 339 | g_ptr_array_add(all_reg_names, reg->name); |~~~^~ In file included from /usr/include/glib-2.0/glib.h:33: /usr/include/glib-2.0/glib/garray.h:198:62: note: expected ‘gpointer’ {aka ‘void *’} but argument is of type ‘const char *’ 198 |gpointer data); |~~^~~~ Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2210 Signed-off-by: Yao Xingtao --- contrib/plugins/execlog.c | 24 +--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/contrib/plugins/execlog.c b/contrib/plugins/execlog.c index a1dfd59ab7..fab18113d4 100644 --- a/contrib/plugins/execlog.c +++ b/contrib/plugins/execlog.c @@ -311,6 +311,24 @@ static Register *init_vcpu_register(qemu_plugin_reg_descriptor *desc) return reg; } +/* + * g_pattern_match_string has been deprecated in Glib since 2.70 and + * will complain about it if you try to use it. Fortunately the + * signature of both functions is the same making it easy to work + * around. + */ +static inline +gboolean g_pattern_spec_match_string_qemu(GPatternSpec *pspec, + const gchar *string) +{ +#if GLIB_CHECK_VERSION(2, 70, 0) +return g_pattern_spec_match_string(pspec, string); +#else +return g_pattern_match_string(pspec, string); +#endif +}; +#define g_pattern_spec_match_string(p, s) g_pattern_spec_match_string_qemu(p, s) + static GPtrArray *registers_init(int vcpu_index) { g_autoptr(GPtrArray) registers = g_ptr_array_new(); @@ -327,8 +345,8 @@ static GPtrArray *registers_init(int vcpu_index) for (int p = 0; p < rmatches->len; p++) { g_autoptr(GPatternSpec) pat = g_pattern_spec_new(rmatches->pdata[p]); g_autofree gchar *rd_lower = g_utf8_strdown(rd->name, -1); -if (g_pattern_match_string(pat, rd->name) || -g_pattern_match_string(pat, rd_lower)) { +if (g_pattern_spec_match_string(pat, rd->name) || +g_pattern_spec_match_string(pat, rd_lower)) { Register *reg = init_vcpu_register(rd); g_ptr_array_add(registers, reg); @@ -336,7 +354,7 @@ static GPtrArray *registers_init(int vcpu_index) if (disas_assist) { g_mutex_lock(_reg_name_lock); if (!g_ptr_array_find(all_reg_names, reg->name, NULL)) { -g_ptr_array_add(all_reg_names, reg->name); +g_ptr_array_add(all_reg_names, (gpointer)reg->name); } g_mutex_unlock(_reg_name_lock); } Would be nice if it's still possible to merge this in 9.0 Peter. Reviewed-by: Pierrick Bouvier
[PATCH v3 8/8] plugins: extract cpu_index generate
Signed-off-by: Pierrick Bouvier --- accel/tcg/plugin-gen.c | 28 +--- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index 16618adf1bc..e6fd6fdfae5 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -108,12 +108,17 @@ static void gen_disable_mem_helper(void) offsetof(ArchCPU, env)); } +static TCGv_i32 gen_cpu_index(void) +{ +TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); +tcg_gen_ld_i32(cpu_index, tcg_env, + -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +return cpu_index; +} + static void gen_udata_cb(struct qemu_plugin_regular_cb *cb) { -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); - -tcg_gen_ld_i32(cpu_index, tcg_env, - -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +TCGv_i32 cpu_index = gen_cpu_index(); tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL, tcgv_i32_temp(cpu_index), tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); @@ -128,9 +133,7 @@ static TCGv_ptr gen_plugin_u64_ptr(qemu_plugin_u64 entry) char *base_ptr = arr->data + entry.offset; size_t entry_size = g_array_get_element_size(arr); -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); -tcg_gen_ld_i32(cpu_index, tcg_env, - -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +TCGv_i32 cpu_index = gen_cpu_index(); tcg_gen_muli_i32(cpu_index, cpu_index, entry_size); tcg_gen_ext_i32_ptr(ptr, cpu_index); tcg_temp_free_i32(cpu_index); @@ -163,7 +166,6 @@ static TCGCond plugin_cond_to_tcgcond(enum qemu_plugin_cond cond) static void gen_udata_cond_cb(struct qemu_plugin_conditional_cb *cb) { TCGv_ptr ptr = gen_plugin_u64_ptr(cb->entry); -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); TCGv_i64 val = tcg_temp_ebb_new_i64(); TCGLabel *after_cb = gen_new_label(); @@ -172,15 +174,14 @@ static void gen_udata_cond_cb(struct qemu_plugin_conditional_cb *cb) tcg_gen_ld_i64(val, ptr, 0); tcg_gen_brcondi_i64(cond, val, cb->imm, after_cb); -tcg_gen_ld_i32(cpu_index, tcg_env, - -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +TCGv_i32 cpu_index = gen_cpu_index(); tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL, tcgv_i32_temp(cpu_index), tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); +tcg_temp_free_i32(cpu_index); gen_set_label(after_cb); tcg_temp_free_i64(val); -tcg_temp_free_i32(cpu_index); tcg_temp_free_ptr(ptr); } @@ -212,10 +213,7 @@ static void gen_inline_store_u64_cb(struct qemu_plugin_inline_cb *cb) static void gen_mem_cb(struct qemu_plugin_regular_cb *cb, qemu_plugin_meminfo_t meminfo, TCGv_i64 addr) { -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); - -tcg_gen_ld_i32(cpu_index, tcg_env, - -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); +TCGv_i32 cpu_index = gen_cpu_index(); tcg_gen_call4(cb->f.vcpu_mem, cb->info, NULL, tcgv_i32_temp(cpu_index), tcgv_i32_temp(tcg_constant_i32(meminfo)), -- 2.39.2
[PATCH v3 1/8] plugins: prepare introduction of new inline ops
Until now, only add_u64 was available, and all functions assumed this or were named uniquely. Reviewed-by: Richard Henderson Signed-off-by: Pierrick Bouvier --- include/qemu/plugin.h | 2 +- accel/tcg/plugin-gen.c | 6 +++--- plugins/core.c | 14 -- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index 201889cbeec..23271fbe36a 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -68,7 +68,7 @@ union qemu_plugin_cb_sig { enum plugin_dyn_cb_type { PLUGIN_CB_REGULAR, PLUGIN_CB_MEM_REGULAR, -PLUGIN_CB_INLINE, +PLUGIN_CB_INLINE_ADD_U64, }; /* diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index c3548257798..41d4d83f547 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -120,7 +120,7 @@ static void gen_udata_cb(struct qemu_plugin_dyn_cb *cb) tcg_temp_free_i32(cpu_index); } -static void gen_inline_cb(struct qemu_plugin_dyn_cb *cb) +static void gen_inline_add_u64_cb(struct qemu_plugin_dyn_cb *cb) { GArray *arr = cb->inline_insn.entry.score->data; size_t offset = cb->inline_insn.entry.offset; @@ -165,8 +165,8 @@ static void inject_cb(struct qemu_plugin_dyn_cb *cb) case PLUGIN_CB_REGULAR: gen_udata_cb(cb); break; -case PLUGIN_CB_INLINE: -gen_inline_cb(cb); +case PLUGIN_CB_INLINE_ADD_U64: +gen_inline_add_u64_cb(cb); break; default: g_assert_not_reached(); diff --git a/plugins/core.c b/plugins/core.c index 0213513ec65..a8557b54ff7 100644 --- a/plugins/core.c +++ b/plugins/core.c @@ -316,6 +316,16 @@ static struct qemu_plugin_dyn_cb *plugin_get_dyn_cb(GArray **arr) return _array_index(cbs, struct qemu_plugin_dyn_cb, cbs->len - 1); } +static enum plugin_dyn_cb_type op_to_cb_type(enum qemu_plugin_op op) +{ +switch (op) { +case QEMU_PLUGIN_INLINE_ADD_U64: +return PLUGIN_CB_INLINE_ADD_U64; +default: +g_assert_not_reached(); +} +} + void plugin_register_inline_op_on_entry(GArray **arr, enum qemu_plugin_mem_rw rw, enum qemu_plugin_op op, @@ -326,7 +336,7 @@ void plugin_register_inline_op_on_entry(GArray **arr, dyn_cb = plugin_get_dyn_cb(arr); dyn_cb->userp = NULL; -dyn_cb->type = PLUGIN_CB_INLINE; +dyn_cb->type = op_to_cb_type(op); dyn_cb->rw = rw; dyn_cb->inline_insn.entry = entry; dyn_cb->inline_insn.op = op; @@ -551,7 +561,7 @@ void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr, cb->regular.f.vcpu_mem(cpu->cpu_index, make_plugin_meminfo(oi, rw), vaddr, cb->userp); break; -case PLUGIN_CB_INLINE: +case PLUGIN_CB_INLINE_ADD_U64: exec_inline_op(cb, cpu->cpu_index); break; default: -- 2.39.2
[PATCH v3 5/8] plugins: conditional callbacks
Extend plugins API to support callback called with a given criteria (evaluated inline). Added functions: - qemu_plugin_register_vcpu_tb_exec_cond_cb - qemu_plugin_register_vcpu_insn_exec_cond_cb They expect as parameter a condition, a qemu_plugin_u64_t (op1) and an immediate (op2). Callback is called if op1 |cond| op2 is true. Signed-off-by: Pierrick Bouvier --- include/qemu/plugin.h| 8 include/qemu/qemu-plugin.h | 76 plugins/plugin.h | 8 accel/tcg/plugin-gen.c | 48 +++ plugins/api.c| 39 ++ plugins/core.c | 32 +++ plugins/qemu-plugins.symbols | 2 + 7 files changed, 213 insertions(+) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index d1d9b4490df..bb224b8e4c7 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -67,6 +67,7 @@ union qemu_plugin_cb_sig { enum plugin_dyn_cb_type { PLUGIN_CB_REGULAR, +PLUGIN_CB_COND, PLUGIN_CB_MEM_REGULAR, PLUGIN_CB_INLINE_ADD_U64, PLUGIN_CB_INLINE_STORE_U64, @@ -88,6 +89,13 @@ struct qemu_plugin_dyn_cb { union qemu_plugin_cb_sig f; TCGHelperInfo *info; } regular; +struct { +union qemu_plugin_cb_sig f; +TCGHelperInfo *info; +qemu_plugin_u64 entry; +enum qemu_plugin_cond cond; +uint64_t imm; +} cond; struct { qemu_plugin_u64 entry; enum qemu_plugin_op op; diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index c5cac897a0b..337de25ece7 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -262,6 +262,29 @@ enum qemu_plugin_mem_rw { QEMU_PLUGIN_MEM_RW, }; +/** + * enum qemu_plugin_cond - condition to enable callback + * + * @QEMU_PLUGIN_COND_NEVER: false + * @QEMU_PLUGIN_COND_ALWAYS: true + * @QEMU_PLUGIN_COND_EQ: is equal? + * @QEMU_PLUGIN_COND_NE: is not equal? + * @QEMU_PLUGIN_COND_LT: is less than? + * @QEMU_PLUGIN_COND_LE: is less than or equal? + * @QEMU_PLUGIN_COND_GT: is greater than? + * @QEMU_PLUGIN_COND_GE: is greater than or equal? + */ +enum qemu_plugin_cond { +QEMU_PLUGIN_COND_NEVER, +QEMU_PLUGIN_COND_ALWAYS, +QEMU_PLUGIN_COND_EQ, +QEMU_PLUGIN_COND_NE, +QEMU_PLUGIN_COND_LT, +QEMU_PLUGIN_COND_LE, +QEMU_PLUGIN_COND_GT, +QEMU_PLUGIN_COND_GE, +}; + /** * typedef qemu_plugin_vcpu_tb_trans_cb_t - translation callback * @id: unique plugin id @@ -301,6 +324,32 @@ void qemu_plugin_register_vcpu_tb_exec_cb(struct qemu_plugin_tb *tb, enum qemu_plugin_cb_flags flags, void *userdata); +/** + * qemu_plugin_register_vcpu_tb_exec_cond_cb() - register conditional callback + * @tb: the opaque qemu_plugin_tb handle for the translation + * @cb: callback function + * @cond: condition to enable callback + * @entry: first operand for condition + * @imm: second operand for condition + * @flags: does the plugin read or write the CPU's registers? + * @userdata: any plugin data to pass to the @cb? + * + * The @cb function is called when a translated unit executes if + * entry @cond imm is true. + * If condition is QEMU_PLUGIN_COND_ALWAYS, condition is never interpreted and + * this function is equivalent to qemu_plugin_register_vcpu_tb_exec_cb. + * If condition QEMU_PLUGIN_COND_NEVER, condition is never interpreted and + * callback is never installed. + */ +QEMU_PLUGIN_API +void qemu_plugin_register_vcpu_tb_exec_cond_cb(struct qemu_plugin_tb *tb, + qemu_plugin_vcpu_udata_cb_t cb, + enum qemu_plugin_cb_flags flags, + enum qemu_plugin_cond cond, + qemu_plugin_u64 entry, + uint64_t imm, + void *userdata); + /** * enum qemu_plugin_op - describes an inline op * @@ -344,6 +393,33 @@ void qemu_plugin_register_vcpu_insn_exec_cb(struct qemu_plugin_insn *insn, enum qemu_plugin_cb_flags flags, void *userdata); +/** + * qemu_plugin_register_vcpu_insn_exec_cond_cb() - conditional insn execution cb + * @insn: the opaque qemu_plugin_insn handle for an instruction + * @cb: callback function + * @flags: does the plugin read or write the CPU's registers? + * @cond: condition to enable callback + * @entry: first operand for condition + * @imm: second operand for condition + * @userdata: any plugin data to pass to the @cb? + * + * The @cb function is called when an instruction executes if + * entry @cond imm is true. + * If condition is QEMU_PLUGIN_COND_ALWAYS, condition is never interpreted
[PATCH v3 2/8] plugins: extract generate ptr for qemu_plugin_u64
Signed-off-by: Pierrick Bouvier --- accel/tcg/plugin-gen.c | 27 ++- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index 41d4d83f547..d3667203546 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -120,24 +120,33 @@ static void gen_udata_cb(struct qemu_plugin_dyn_cb *cb) tcg_temp_free_i32(cpu_index); } -static void gen_inline_add_u64_cb(struct qemu_plugin_dyn_cb *cb) +static TCGv_ptr gen_plugin_u64_ptr(qemu_plugin_u64 entry) { -GArray *arr = cb->inline_insn.entry.score->data; -size_t offset = cb->inline_insn.entry.offset; -TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); -TCGv_i64 val = tcg_temp_ebb_new_i64(); TCGv_ptr ptr = tcg_temp_ebb_new_ptr(); +GArray *arr = entry.score->data; +char *base_ptr = arr->data + entry.offset; +size_t entry_size = g_array_get_element_size(arr); + +TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); tcg_gen_ld_i32(cpu_index, tcg_env, -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); -tcg_gen_muli_i32(cpu_index, cpu_index, g_array_get_element_size(arr)); +tcg_gen_muli_i32(cpu_index, cpu_index, entry_size); tcg_gen_ext_i32_ptr(ptr, cpu_index); tcg_temp_free_i32(cpu_index); +tcg_gen_addi_ptr(ptr, ptr, (intptr_t) base_ptr); -tcg_gen_addi_ptr(ptr, ptr, (intptr_t)arr->data); -tcg_gen_ld_i64(val, ptr, offset); +return ptr; +} + +static void gen_inline_add_u64_cb(struct qemu_plugin_dyn_cb *cb) +{ +TCGv_ptr ptr = gen_plugin_u64_ptr(cb->inline_insn.entry); +TCGv_i64 val = tcg_temp_ebb_new_i64(); + +tcg_gen_ld_i64(val, ptr, 0); tcg_gen_addi_i64(val, val, cb->inline_insn.imm); -tcg_gen_st_i64(val, ptr, offset); +tcg_gen_st_i64(val, ptr, 0); tcg_temp_free_i64(val); tcg_temp_free_ptr(ptr); -- 2.39.2
[PATCH v3 7/8] plugins: distinct types for callbacks
To prevent errors when writing new types of callbacks or inline operations, we split callbacks data to distinct types. Signed-off-by: Pierrick Bouvier --- include/qemu/plugin.h | 46 ++--- plugins/plugin.h | 2 +- accel/tcg/plugin-gen.c | 58 +--- plugins/core.c | 76 ++ 4 files changed, 98 insertions(+), 84 deletions(-) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index bb224b8e4c7..a078229942f 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -73,34 +73,40 @@ enum plugin_dyn_cb_type { PLUGIN_CB_INLINE_STORE_U64, }; +struct qemu_plugin_regular_cb { +union qemu_plugin_cb_sig f; +TCGHelperInfo *info; +void *userp; +enum qemu_plugin_mem_rw rw; +}; + +struct qemu_plugin_inline_cb { +qemu_plugin_u64 entry; +enum qemu_plugin_op op; +uint64_t imm; +enum qemu_plugin_mem_rw rw; +}; + +struct qemu_plugin_conditional_cb { +union qemu_plugin_cb_sig f; +TCGHelperInfo *info; +void *userp; +qemu_plugin_u64 entry; +enum qemu_plugin_cond cond; +uint64_t imm; +}; + /* * A dynamic callback has an insertion point that is determined at run-time. * Usually the insertion point is somewhere in the code cache; think for * instance of a callback to be called upon the execution of a particular TB. */ struct qemu_plugin_dyn_cb { -void *userp; enum plugin_dyn_cb_type type; -/* @rw applies to mem callbacks only (both regular and inline) */ -enum qemu_plugin_mem_rw rw; -/* fields specific to each dyn_cb type go here */ union { -struct { -union qemu_plugin_cb_sig f; -TCGHelperInfo *info; -} regular; -struct { -union qemu_plugin_cb_sig f; -TCGHelperInfo *info; -qemu_plugin_u64 entry; -enum qemu_plugin_cond cond; -uint64_t imm; -} cond; -struct { -qemu_plugin_u64 entry; -enum qemu_plugin_op op; -uint64_t imm; -} inline_insn; +struct qemu_plugin_regular_cb regular; +struct qemu_plugin_conditional_cb cond; +struct qemu_plugin_inline_cb inline_insn; }; }; diff --git a/plugins/plugin.h b/plugins/plugin.h index 7d4b4e21f7c..80d5daa9171 100644 --- a/plugins/plugin.h +++ b/plugins/plugin.h @@ -108,7 +108,7 @@ void plugin_register_vcpu_mem_cb(GArray **arr, enum qemu_plugin_mem_rw rw, void *udata); -void exec_inline_op(struct qemu_plugin_dyn_cb *cb, int cpu_index); +void exec_inline_op(struct qemu_plugin_inline_cb *cb, int cpu_index); int plugin_num_vcpus(void); diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index 7ecaf670d93..16618adf1bc 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -108,13 +108,13 @@ static void gen_disable_mem_helper(void) offsetof(ArchCPU, env)); } -static void gen_udata_cb(struct qemu_plugin_dyn_cb *cb) +static void gen_udata_cb(struct qemu_plugin_regular_cb *cb) { TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); tcg_gen_ld_i32(cpu_index, tcg_env, -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); -tcg_gen_call2(cb->regular.f.vcpu_udata, cb->regular.info, NULL, +tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL, tcgv_i32_temp(cpu_index), tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); tcg_temp_free_i32(cpu_index); @@ -160,21 +160,21 @@ static TCGCond plugin_cond_to_tcgcond(enum qemu_plugin_cond cond) } } -static void gen_udata_cond_cb(struct qemu_plugin_dyn_cb *cb) +static void gen_udata_cond_cb(struct qemu_plugin_conditional_cb *cb) { -TCGv_ptr ptr = gen_plugin_u64_ptr(cb->cond.entry); +TCGv_ptr ptr = gen_plugin_u64_ptr(cb->entry); TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); TCGv_i64 val = tcg_temp_ebb_new_i64(); TCGLabel *after_cb = gen_new_label(); /* Condition should be negated, as calling the cb is the "else" path */ -TCGCond cond = tcg_invert_cond(plugin_cond_to_tcgcond(cb->cond.cond)); +TCGCond cond = tcg_invert_cond(plugin_cond_to_tcgcond(cb->cond)); tcg_gen_ld_i64(val, ptr, 0); -tcg_gen_brcondi_i64(cond, val, cb->cond.imm, after_cb); +tcg_gen_brcondi_i64(cond, val, cb->imm, after_cb); tcg_gen_ld_i32(cpu_index, tcg_env, -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); -tcg_gen_call2(cb->cond.f.vcpu_udata, cb->cond.info, NULL, +tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL, tcgv_i32_temp(cpu_index), tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); gen_set_label(after_cb); @@ -184,39 +184,39 @@ static void gen_udata_cond_cb(struct qemu_plugin_dyn_cb *cb) tcg_temp_fre
[PATCH v3 6/8] tests/plugin/inline: add test for conditional callback
Count number of tb and insn executed using a conditional callback. We ensure the callback has been called expected number of time (per vcpu). Signed-off-by: Pierrick Bouvier --- tests/plugin/inline.c | 89 +-- 1 file changed, 86 insertions(+), 3 deletions(-) diff --git a/tests/plugin/inline.c b/tests/plugin/inline.c index 103c3a22f6e..cd63827b7d8 100644 --- a/tests/plugin/inline.c +++ b/tests/plugin/inline.c @@ -20,8 +20,14 @@ typedef struct { uint64_t count_insn_inline; uint64_t count_mem; uint64_t count_mem_inline; +uint64_t tb_cond_num_trigger; +uint64_t tb_cond_track_count; +uint64_t insn_cond_num_trigger; +uint64_t insn_cond_track_count; } CPUCount; +static const uint64_t cond_trigger_limit = 100; + typedef struct { uint64_t data_insn; uint64_t data_tb; @@ -35,6 +41,10 @@ static qemu_plugin_u64 count_insn; static qemu_plugin_u64 count_insn_inline; static qemu_plugin_u64 count_mem; static qemu_plugin_u64 count_mem_inline; +static qemu_plugin_u64 tb_cond_num_trigger; +static qemu_plugin_u64 tb_cond_track_count; +static qemu_plugin_u64 insn_cond_num_trigger; +static qemu_plugin_u64 insn_cond_track_count; static struct qemu_plugin_scoreboard *data; static qemu_plugin_u64 data_insn; static qemu_plugin_u64 data_tb; @@ -56,12 +66,19 @@ static void stats_insn(void) const uint64_t per_vcpu = qemu_plugin_u64_sum(count_insn); const uint64_t inl_per_vcpu = qemu_plugin_u64_sum(count_insn_inline); +const uint64_t cond_num_trigger = +qemu_plugin_u64_sum(insn_cond_num_trigger); +const uint64_t cond_track_left = qemu_plugin_u64_sum(insn_cond_track_count); +const uint64_t conditional = +cond_num_trigger * cond_trigger_limit + cond_track_left; printf("insn: %" PRIu64 "\n", expected); printf("insn: %" PRIu64 " (per vcpu)\n", per_vcpu); printf("insn: %" PRIu64 " (per vcpu inline)\n", inl_per_vcpu); +printf("insn: %" PRIu64 " (cond cb)\n", conditional); g_assert(expected > 0); g_assert(per_vcpu == expected); g_assert(inl_per_vcpu == expected); +g_assert(conditional == expected); } static void stats_tb(void) @@ -70,12 +87,18 @@ static void stats_tb(void) const uint64_t per_vcpu = qemu_plugin_u64_sum(count_tb); const uint64_t inl_per_vcpu = qemu_plugin_u64_sum(count_tb_inline); +const uint64_t cond_num_trigger = qemu_plugin_u64_sum(tb_cond_num_trigger); +const uint64_t cond_track_left = qemu_plugin_u64_sum(tb_cond_track_count); +const uint64_t conditional = +cond_num_trigger * cond_trigger_limit + cond_track_left; printf("tb: %" PRIu64 "\n", expected); printf("tb: %" PRIu64 " (per vcpu)\n", per_vcpu); printf("tb: %" PRIu64 " (per vcpu inline)\n", inl_per_vcpu); +printf("tb: %" PRIu64 " (conditional cb)\n", conditional); g_assert(expected > 0); g_assert(per_vcpu == expected); g_assert(inl_per_vcpu == expected); +g_assert(conditional == expected); } static void stats_mem(void) @@ -104,14 +127,35 @@ static void plugin_exit(qemu_plugin_id_t id, void *udata) const uint64_t insn_inline = qemu_plugin_u64_get(count_insn_inline, i); const uint64_t mem = qemu_plugin_u64_get(count_mem, i); const uint64_t mem_inline = qemu_plugin_u64_get(count_mem_inline, i); -printf("cpu %d: tb (%" PRIu64 ", %" PRIu64 ") | " - "insn (%" PRIu64 ", %" PRIu64 ") | " +const uint64_t tb_cond_trigger = +qemu_plugin_u64_get(tb_cond_num_trigger, i); +const uint64_t tb_cond_left = +qemu_plugin_u64_get(tb_cond_track_count, i); +const uint64_t insn_cond_trigger = +qemu_plugin_u64_get(insn_cond_num_trigger, i); +const uint64_t insn_cond_left = +qemu_plugin_u64_get(insn_cond_track_count, i); +printf("cpu %d: tb (%" PRIu64 ", %" PRIu64 + ", %" PRIu64 " * %" PRIu64 " + %" PRIu64 + ") | " + "insn (%" PRIu64 ", %" PRIu64 + ", %" PRIu64 " * %" PRIu64 " + %" PRIu64 + ") | " "mem (%" PRIu64 ", %" PRIu64 ")" "\n", - i, tb, tb_inline, insn, insn_inline, mem, mem_inline); + i, + tb, tb_inline, + tb_cond_trigger, cond_trigger_limit, tb_cond_left, + insn, insn_inline, + insn_cond_trigger, cond_trigger_limit, insn_cond_left, + mem, mem_inline); g_assert(tb == tb_inline); g_assert(insn ==
[PATCH v3 4/8] tests/plugin/inline: add test for STORE_U64 inline op
Signed-off-by: Pierrick Bouvier --- tests/plugin/inline.c | 41 + 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/tests/plugin/inline.c b/tests/plugin/inline.c index 0163e9b51c5..103c3a22f6e 100644 --- a/tests/plugin/inline.c +++ b/tests/plugin/inline.c @@ -22,6 +22,12 @@ typedef struct { uint64_t count_mem_inline; } CPUCount; +typedef struct { +uint64_t data_insn; +uint64_t data_tb; +uint64_t data_mem; +} CPUData; + static struct qemu_plugin_scoreboard *counts; static qemu_plugin_u64 count_tb; static qemu_plugin_u64 count_tb_inline; @@ -29,6 +35,10 @@ static qemu_plugin_u64 count_insn; static qemu_plugin_u64 count_insn_inline; static qemu_plugin_u64 count_mem; static qemu_plugin_u64 count_mem_inline; +static struct qemu_plugin_scoreboard *data; +static qemu_plugin_u64 data_insn; +static qemu_plugin_u64 data_tb; +static qemu_plugin_u64 data_mem; static uint64_t global_count_tb; static uint64_t global_count_insn; @@ -109,11 +119,13 @@ static void plugin_exit(qemu_plugin_id_t id, void *udata) stats_mem(); qemu_plugin_scoreboard_free(counts); +qemu_plugin_scoreboard_free(data); } static void vcpu_tb_exec(unsigned int cpu_index, void *udata) { qemu_plugin_u64_add(count_tb, cpu_index, 1); +g_assert(qemu_plugin_u64_get(data_tb, cpu_index) == (uintptr_t) udata); g_mutex_lock(_lock); max_cpu_index = MAX(max_cpu_index, cpu_index); global_count_tb++; @@ -123,6 +135,7 @@ static void vcpu_tb_exec(unsigned int cpu_index, void *udata) static void vcpu_insn_exec(unsigned int cpu_index, void *udata) { qemu_plugin_u64_add(count_insn, cpu_index, 1); +g_assert(qemu_plugin_u64_get(data_insn, cpu_index) == (uintptr_t) udata); g_mutex_lock(_lock); global_count_insn++; g_mutex_unlock(_lock); @@ -131,9 +144,10 @@ static void vcpu_insn_exec(unsigned int cpu_index, void *udata) static void vcpu_mem_access(unsigned int cpu_index, qemu_plugin_meminfo_t info, uint64_t vaddr, -void *userdata) +void *udata) { qemu_plugin_u64_add(count_mem, cpu_index, 1); +g_assert(qemu_plugin_u64_get(data_mem, cpu_index) == (uintptr_t) udata); g_mutex_lock(_lock); global_count_mem++; g_mutex_unlock(_lock); @@ -141,20 +155,34 @@ static void vcpu_mem_access(unsigned int cpu_index, static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb) { +void *tb_store = tb; +qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu( +tb, QEMU_PLUGIN_INLINE_STORE_U64, data_tb, (uintptr_t) tb_store); qemu_plugin_register_vcpu_tb_exec_cb( -tb, vcpu_tb_exec, QEMU_PLUGIN_CB_NO_REGS, 0); +tb, vcpu_tb_exec, QEMU_PLUGIN_CB_NO_REGS, tb_store); qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu( tb, QEMU_PLUGIN_INLINE_ADD_U64, count_tb_inline, 1); for (int idx = 0; idx < qemu_plugin_tb_n_insns(tb); ++idx) { struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, idx); +void *insn_store = insn; +void *mem_store = (char *)insn_store + 0xff; + +qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu( +insn, QEMU_PLUGIN_INLINE_STORE_U64, data_insn, +(uintptr_t) insn_store); qemu_plugin_register_vcpu_insn_exec_cb( -insn, vcpu_insn_exec, QEMU_PLUGIN_CB_NO_REGS, 0); +insn, vcpu_insn_exec, QEMU_PLUGIN_CB_NO_REGS, insn_store); qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu( insn, QEMU_PLUGIN_INLINE_ADD_U64, count_insn_inline, 1); + +qemu_plugin_register_vcpu_mem_inline_per_vcpu( +insn, QEMU_PLUGIN_MEM_RW, +QEMU_PLUGIN_INLINE_STORE_U64, +data_mem, (uintptr_t) mem_store); qemu_plugin_register_vcpu_mem_cb(insn, _mem_access, QEMU_PLUGIN_CB_NO_REGS, - QEMU_PLUGIN_MEM_RW, 0); + QEMU_PLUGIN_MEM_RW, mem_store); qemu_plugin_register_vcpu_mem_inline_per_vcpu( insn, QEMU_PLUGIN_MEM_RW, QEMU_PLUGIN_INLINE_ADD_U64, @@ -179,6 +207,11 @@ int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info, counts, CPUCount, count_insn_inline); count_mem_inline = qemu_plugin_scoreboard_u64_in_struct( counts, CPUCount, count_mem_inline); +data = qemu_plugin_scoreboard_new(sizeof(CPUData)); +data_insn = qemu_plugin_scoreboard_u64_in_struct(data, CPUData, data_insn); +data_tb = qemu_plugin_scoreboard_u64_in_struct(data, CPUData, data_tb); +data_mem = qemu_plugin_scoreboard_u64_in_struct(data, CPUData, data_mem); + qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans); qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); -- 2.39.2
[PATCH v3 3/8] plugins: add new inline op STORE_U64
Signed-off-by: Pierrick Bouvier --- include/qemu/plugin.h | 1 + include/qemu/qemu-plugin.h | 4 ++-- accel/tcg/plugin-gen.c | 15 +++ plugins/core.c | 6 ++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index 23271fbe36a..d1d9b4490df 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -69,6 +69,7 @@ enum plugin_dyn_cb_type { PLUGIN_CB_REGULAR, PLUGIN_CB_MEM_REGULAR, PLUGIN_CB_INLINE_ADD_U64, +PLUGIN_CB_INLINE_STORE_U64, }; /* diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 4fc6c3739b2..c5cac897a0b 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -305,12 +305,12 @@ void qemu_plugin_register_vcpu_tb_exec_cb(struct qemu_plugin_tb *tb, * enum qemu_plugin_op - describes an inline op * * @QEMU_PLUGIN_INLINE_ADD_U64: add an immediate value uint64_t - * - * Note: currently only a single inline op is supported. + * @QEMU_PLUGIN_INLINE_STORE_U64: store an immediate value uint64_t */ enum qemu_plugin_op { QEMU_PLUGIN_INLINE_ADD_U64, +QEMU_PLUGIN_INLINE_STORE_U64, }; /** diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index d3667203546..45856a75cda 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -152,6 +152,18 @@ static void gen_inline_add_u64_cb(struct qemu_plugin_dyn_cb *cb) tcg_temp_free_ptr(ptr); } +static void gen_inline_store_u64_cb(struct qemu_plugin_dyn_cb *cb) +{ +TCGv_ptr ptr = gen_plugin_u64_ptr(cb->inline_insn.entry); +TCGv_i64 val = tcg_temp_ebb_new_i64(); + +tcg_gen_movi_i64(val, cb->inline_insn.imm); +tcg_gen_st_i64(val, ptr, 0); + +tcg_temp_free_i64(val); +tcg_temp_free_ptr(ptr); +} + static void gen_mem_cb(struct qemu_plugin_dyn_cb *cb, qemu_plugin_meminfo_t meminfo, TCGv_i64 addr) { @@ -177,6 +189,9 @@ static void inject_cb(struct qemu_plugin_dyn_cb *cb) case PLUGIN_CB_INLINE_ADD_U64: gen_inline_add_u64_cb(cb); break; +case PLUGIN_CB_INLINE_STORE_U64: +gen_inline_store_u64_cb(cb); +break; default: g_assert_not_reached(); } diff --git a/plugins/core.c b/plugins/core.c index a8557b54ff7..e1bf0dc3717 100644 --- a/plugins/core.c +++ b/plugins/core.c @@ -321,6 +321,8 @@ static enum plugin_dyn_cb_type op_to_cb_type(enum qemu_plugin_op op) switch (op) { case QEMU_PLUGIN_INLINE_ADD_U64: return PLUGIN_CB_INLINE_ADD_U64; +case QEMU_PLUGIN_INLINE_STORE_U64: +return PLUGIN_CB_INLINE_STORE_U64; default: g_assert_not_reached(); } @@ -535,6 +537,9 @@ void exec_inline_op(struct qemu_plugin_dyn_cb *cb, int cpu_index) case QEMU_PLUGIN_INLINE_ADD_U64: *val += cb->inline_insn.imm; break; +case QEMU_PLUGIN_INLINE_STORE_U64: +*val = cb->inline_insn.imm; +break; default: g_assert_not_reached(); } @@ -562,6 +567,7 @@ void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr, vaddr, cb->userp); break; case PLUGIN_CB_INLINE_ADD_U64: +case PLUGIN_CB_INLINE_STORE_U64: exec_inline_op(cb, cpu->cpu_index); break; default: -- 2.39.2
[PATCH v3 0/8] TCG plugins new inline operations
This series implement two new operations for plugins: - Store inline allows to write a specific value to a scoreboard. - Conditional callback executes a callback only when a given condition is true. The condition is evaluated inline. It's possible to mix various inline operations (add, store) with conditional callbacks, allowing efficient "trap" based counters. It builds on top of new scoreboard API, introduced in the previous series. v2 -- - fixed issue with udata not being passed to conditional callback - added specific test for this in tests/plugin/inline.c (udata was NULL before). v3 -- - rebased on top of "plugins: Rewrite plugin code generation": 20240316015720.3661236-1-richard.hender...@linaro.org - single pass code generation - small cleanups for code generation Pierrick Bouvier (8): plugins: prepare introduction of new inline ops plugins: extract generate ptr for qemu_plugin_u64 plugins: add new inline op STORE_U64 tests/plugin/inline: add test for STORE_U64 inline op plugins: conditional callbacks tests/plugin/inline: add test for conditional callback plugins: distinct types for callbacks plugins: extract cpu_index generate include/qemu/plugin.h| 43 +++ include/qemu/qemu-plugin.h | 80 +++- plugins/plugin.h | 10 ++- accel/tcg/plugin-gen.c | 138 +++ plugins/api.c| 39 ++ plugins/core.c | 106 --- tests/plugin/inline.c| 130 +++-- plugins/qemu-plugins.symbols | 2 + 8 files changed, 465 insertions(+), 83 deletions(-) -- 2.39.2
Re: [PATCH v2] contrib/plugins/execlog: Fix compiler warning
On 3/25/24 13:58, Peter Maydell wrote: On Mon, 25 Mar 2024 at 06:41, Pierrick Bouvier wrote: On 3/25/24 10:06, Yao Xingtao wrote: diff --git a/contrib/plugins/execlog.c b/contrib/plugins/execlog.c index a1dfd59ab7..09654910ee 100644 --- a/contrib/plugins/execlog.c +++ b/contrib/plugins/execlog.c @@ -327,8 +327,13 @@ static GPtrArray *registers_init(int vcpu_index) for (int p = 0; p < rmatches->len; p++) { g_autoptr(GPatternSpec) pat = g_pattern_spec_new(rmatches->pdata[p]); g_autofree gchar *rd_lower = g_utf8_strdown(rd->name, -1); +#if GLIB_CHECK_VERSION(2, 70, 0) +if (g_pattern_spec_match_string(pat, rd->name) || +g_pattern_spec_match_string(pat, rd_lower)) { +#else if (g_pattern_match_string(pat, rd->name) || g_pattern_match_string(pat, rd_lower)) { +#endif Register *reg = init_vcpu_register(rd); g_ptr_array_add(registers, reg); As suggested by Peter on previous version, you can declare a new function `g_pattern_match_string_qemu` in include/glib-compat.h which abstract this. We should have an abstraction function, but it should *not* be in glib-compat.h, but local to this plugin's .c file. This is because the plugins are deliberately standalone binaries which do not rely on any of QEMU's include files or build process (you'll see they don't use osdep.h, for example). Sorry, I misunderstood that, as it was discussed with Alex that maybe plugins should be able to access glib-compat.h. thanks -- PMM
Re: [PATCH v2] contrib/plugins/execlog: Fix compiler warning
On 3/25/24 10:06, Yao Xingtao wrote: 1. The g_pattern_match_string() is deprecated when glib2 version >= 2.70. Use g_pattern_spec_match_string() instead to avoid this problem. 2. The type of second parameter in g_ptr_array_add() is 'gpointer' {aka 'void *'}, but the type of reg->name is 'const char*'. Cast the type of reg->name to 'gpointer' to avoid this problem. compiler warning message: /root/qemu/contrib/plugins/execlog.c:330:17: warning: ‘g_pattern_match_string’ is deprecated: Use 'g_pattern_spec_match_string' instead [-Wdeprecated-declarations] 330 | if (g_pattern_match_string(pat, rd->name) || | ^~ In file included from /usr/include/glib-2.0/glib.h:67, from /root/qemu/contrib/plugins/execlog.c:9: /usr/include/glib-2.0/glib/gpattern.h:57:15: note: declared here 57 | gboolean g_pattern_match_string (GPatternSpec *pspec, | ^~ /root/qemu/contrib/plugins/execlog.c:331:21: warning: ‘g_pattern_match_string’ is deprecated: Use 'g_pattern_spec_match_string' instead [-Wdeprecated-declarations] 331 | g_pattern_match_string(pat, rd_lower)) { | ^~ /usr/include/glib-2.0/glib/gpattern.h:57:15: note: declared here 57 | gboolean g_pattern_match_string (GPatternSpec *pspec, | ^~ /root/qemu/contrib/plugins/execlog.c:339:63: warning: passing argument 2 of ‘g_ptr_array_add’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers] 339 | g_ptr_array_add(all_reg_names, reg->name); |~~~^~ In file included from /usr/include/glib-2.0/glib.h:33: /usr/include/glib-2.0/glib/garray.h:198:62: note: expected ‘gpointer’ {aka ‘void *’} but argument is of type ‘const char *’ 198 |gpointer data); |~~^~~~ Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2210 Signed-off-by: Yao Xingtao --- contrib/plugins/execlog.c | 7 ++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/contrib/plugins/execlog.c b/contrib/plugins/execlog.c index a1dfd59ab7..09654910ee 100644 --- a/contrib/plugins/execlog.c +++ b/contrib/plugins/execlog.c @@ -327,8 +327,13 @@ static GPtrArray *registers_init(int vcpu_index) for (int p = 0; p < rmatches->len; p++) { g_autoptr(GPatternSpec) pat = g_pattern_spec_new(rmatches->pdata[p]); g_autofree gchar *rd_lower = g_utf8_strdown(rd->name, -1); +#if GLIB_CHECK_VERSION(2, 70, 0) +if (g_pattern_spec_match_string(pat, rd->name) || +g_pattern_spec_match_string(pat, rd_lower)) { +#else if (g_pattern_match_string(pat, rd->name) || g_pattern_match_string(pat, rd_lower)) { +#endif Register *reg = init_vcpu_register(rd); g_ptr_array_add(registers, reg); As suggested by Peter on previous version, you can declare a new function `g_pattern_match_string_qemu` in include/glib-compat.h which abstract this. You'll need to add include/ to Makefile as well, so glib-compat.h will be accessible to contrib plugins too. @@ -336,7 +341,7 @@ static GPtrArray *registers_init(int vcpu_index) if (disas_assist) { g_mutex_lock(_reg_name_lock); if (!g_ptr_array_find(all_reg_names, reg->name, NULL)) { -g_ptr_array_add(all_reg_names, reg->name); +g_ptr_array_add(all_reg_names, (gpointer)reg->name); } g_mutex_unlock(_reg_name_lock); }
Re: [PATCH] contrib/plugins/execlog: Fix compiler warning
On 3/25/24 07:00, Xingtao Yao (Fujitsu) wrote: Pete: Thanks for your comment. I also find a similar patch written by Pierrick: https://lore.kernel.org/qemu-devel/20240118032400.3762658-15-pierrick.bouv...@linaro.org/ but for some reason, the patch was not merged yet. shall I need to continue tracking the fixes of this bug? Hi Xingtao, you're doing the right thing here. In my original patch, there was no consideration for backward compatibility, to the opposite of what you did. Alex will be out for several weeks, so it might take some time to get this merged, but I'm definitely voting for this . Pierrick -Original Message- From: Peter Maydell Sent: Friday, March 22, 2024 7:50 PM To: Yao, Xingtao/姚 幸涛 Cc: alex.ben...@linaro.org; erdn...@crans.org; ma.mando...@gmail.com; pierrick.bouv...@linaro.org; qemu-devel@nongnu.org Subject: Re: [PATCH] contrib/plugins/execlog: Fix compiler warning On Wed, 20 Mar 2024 at 02:05, Yao Xingtao via wrote: 1. The g_pattern_match_string() is deprecated when glib2 version >= 2.70. Use g_pattern_spec_match_string() instead to avoid this problem. 2. The type of second parameter in g_ptr_array_add() is 'gpointer' {aka 'void *'}, but the type of reg->name is 'const char*'. Cast the type of reg->name to 'gpointer' to avoid this problem. compiler warning message: /root/qemu/contrib/plugins/execlog.c:330:17: warning: ‘g_pattern_match_string’ is deprecated: Use 'g_pattern_spec_match_string' instead [-Wdeprecated-declarations] 330 | if (g_pattern_match_string(pat, rd->name) || | ^~ In file included from /usr/include/glib-2.0/glib.h:67, from /root/qemu/contrib/plugins/execlog.c:9: /usr/include/glib-2.0/glib/gpattern.h:57:15: note: declared here 57 | gboolean g_pattern_match_string (GPatternSpec *pspec, | ^~ /root/qemu/contrib/plugins/execlog.c:331:21: warning: ‘g_pattern_match_string’ is deprecated: Use 'g_pattern_spec_match_string' instead [-Wdeprecated-declarations] 331 | g_pattern_match_string(pat, rd_lower)) { | ^~ /usr/include/glib-2.0/glib/gpattern.h:57:15: note: declared here 57 | gboolean g_pattern_match_string (GPatternSpec *pspec, | ^~ /root/qemu/contrib/plugins/execlog.c:339:63: warning: passing argument 2 of ‘g_ptr_array_add’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers] 339 | g_ptr_array_add(all_reg_names, reg->name); | ~~~^~ In file included from /usr/include/glib-2.0/glib.h:33: /usr/include/glib-2.0/glib/garray.h:198:62: note: expected ‘gpointer’ {aka ‘void *’} but argument is of type ‘const char *’ 198 |gpointer data); | ~~^~~~ Hi; thanks for this patch. This fixes a bug reported in the bug tracker so we should put Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2210 in the commit message just above your signed-off-by tag. Signed-off-by: Yao Xingtao I will if needed. --- contrib/plugins/execlog.c | 7 ++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/contrib/plugins/execlog.c b/contrib/plugins/execlog.c index a1dfd59ab7..41f6774116 100644 --- a/contrib/plugins/execlog.c +++ b/contrib/plugins/execlog.c @@ -327,8 +327,13 @@ static GPtrArray *registers_init(int vcpu_index) for (int p = 0; p < rmatches->len; p++) { g_autoptr(GPatternSpec) pat = g_pattern_spec_new(rmatches->pdata[p]); g_autofree gchar *rd_lower = g_utf8_strdown(rd->name, -1); +#if GLIB_VERSION_MAX_ALLOWED < G_ENCODE_VERSION(2, 70) Elsewhere we do glib version checks with #if GLIB_CHECK_VERSION(2, 70, 0) code for 2.70.0 and up; #else code for older versions #endif so I think we should probably do that here too. if (g_pattern_match_string(pat, rd->name) || g_pattern_match_string(pat, rd_lower)) { +#else +if (g_pattern_spec_match_string(pat, rd->name) || +g_pattern_spec_match_string(pat, rd_lower)) { +#endif thanks, I got it. Rather than putting this ifdef in the middle of this function, I think it would be easier to read if we abstract out a function which does the pattern matching and whose body calls the right glib function depending on glib version. We generally call these functions the same as the glib function but with a _qemu suffix (compare the ones in include/glib-compat.h), so here that would be g_pattern_spec_match_string_qemu(). Register *reg = init_vcpu_register(rd); g_ptr_array_add(registers, reg); @@ -336,7 +341,7 @@ static GPtrArray *registers_init(int vcpu_index) if (disas_assist) {
Re: [PATCH 07/22] plugins: Use emit_before_op for PLUGIN_GEN_AFTER_INSN
On 3/19/24 23:56, Richard Henderson wrote: On 3/19/24 03:32, Pierrick Bouvier wrote: static void plugin_gen_inject(struct qemu_plugin_tb *plugin_tb) { - TCGOp *op; + TCGOp *op, *next; int insn_idx = -1; pr_ops(); - QTAILQ_FOREACH(op, _ctx->ops, link) { + /* + * While injecting code, we cannot afford to reuse any ebb temps + * that might be live within the existing opcode stream. + * The simplest solution is to release them all and create new. + */ + memset(tcg_ctx->free_temps, 0, sizeof(tcg_ctx->free_temps)); + Not an expert at this, but wouldn't that break an existing TB that already has some ops on those temps? No, this only affects allocation of new temps -- if free_temps is empty, a new temp will be allocated from tcg_ctx->nb_temps++. Zeroing free_temps here ensures that we *do not* reuse a temp that might already be live across any plugin insertion point. Between insertion points, we will free plugin temps and only reuse those. Thanks, by looking at tcg_temp_new_internal fn, and with your explaination, it makes more sense. r~