On 2026/10/08 10:08, Pierrick Bouvier wrote:
On 10/6/2026 11:01 PM, Akihiko Odaki wrote:
A region of interest (ROI) often excludes the program startup and exit.
For example, a benchmark may parse a large input and then run a
computation kernel and the former may take longer than the latter;
basic block vectors generated for the whole execution will not properly
represent the latter.

Add options to specify the beginning and the end of the ROI to bound
basic block vector generation to the ROI.

Signed-off-by: Akihiko Odaki <[email protected]>
---
  docs/about/emulation.rst |  8 +++++
  contrib/plugins/bbv.c    | 80 ++++++++++++++++++++++++++++++++++++++++++++++--
  2 files changed, 86 insertions(+), 2 deletions(-)

diff --git a/docs/about/emulation.rst b/docs/about/emulation.rst
index c58149086cdd..0566171b517b 100644
--- a/docs/about/emulation.rst
+++ b/docs/about/emulation.rst
@@ -284,6 +284,14 @@ The bbv plugin allows you to generate basic block vectors 
for use with the
* - Option
      - Description
+  * - begin=A
+    - The virtual address of the instruction that starts basic block vector
+      generation. Generation starts from the beginning of the execution if
+      not specified.
+  * - end=A
+    - The virtual address of the instruction that ends basic block vector
+      generation. Generation continues until the endo of the execution if

small typo here: s/endo/end

Thanks for catching the typo.


+      not specified.
    * - interval=N
      - The interval to generate a basic block vector specified by the number of
        instructions (Default: N = 100000000)
diff --git a/contrib/plugins/bbv.c b/contrib/plugins/bbv.c
index e5caf5d9543b..a2a779f7639e 100644
--- a/contrib/plugins/bbv.c
+++ b/contrib/plugins/bbv.c
@@ -5,6 +5,8 @@
   * SPDX-License-Identifier: GPL-2.0-or-later
   */
+#include <stdbool.h>
+#include <stdint.h>
  #include <stdio.h>
  #include <glib.h>
@@ -26,6 +28,10 @@ static GHashTable *bbs;
  static GRWLock bbs_lock;
  static char *filename;
  static struct qemu_plugin_scoreboard *vcpus;
+static bool begin_present;
+static uint64_t begin;
+static bool end_present;
+static uint64_t end;
  static int64_t interval = 100000000;
static void plugin_exit(void *p)
@@ -66,6 +72,7 @@ static void vcpu_init(unsigned int vcpu_index, void *userdata)
      Vcpu *vcpu = qemu_plugin_scoreboard_find(vcpus, vcpu_index);
vcpu_filename = g_strdup_printf("%s.%u.bb", filename, vcpu_index);
+    vcpu->count = begin_present ? INT64_MIN : -interval;

Not sure which semantic we are trying to achieve with -interval here.

The original logic was to increment this count, until reaching interval,
after what we would reset it by using vcpu_count -= interval, to keep
the extra insn that went in after the threshold.

The counter now starts at -interval and counts up to zero. Subtracting interval after emitting a vector still preserves the extra instructions. For example, with interval=100:

    initial count:       -100
    after 130 insns:       30  -> emit a vector
    subtract interval:    -70  -> 70 insns until the next vector


      vcpu->file = fopen(vcpu_filename, "w");
  }
@@ -102,6 +109,22 @@ static void vcpu_interval_exec(unsigned int vcpu_index, void *udata)
      fputc('\n', vcpu->file);
  }
+static void vcpu_begin_insn_exec(unsigned int vcpu_index, void *userdata)
+{
+    GHashTableIter iter;
+    void *value;
+
+    g_rw_lock_reader_lock(&bbs_lock);
+    g_hash_table_iter_init(&iter, bbs);
+
+    while (g_hash_table_iter_next(&iter, NULL, &value)) {
+        qemu_plugin_u64_set(bb_count_u64(value), vcpu_index, 0);
+    }
+
+    g_rw_lock_reader_unlock(&bbs_lock);
+    qemu_plugin_u64_set(count_u64(), vcpu_index, -interval);
+}
+
  static void vcpu_tb_trans(struct qemu_plugin_tb *tb, void *userdata)
  {
      uint64_t n_insns = qemu_plugin_tb_n_insns(tb);
@@ -119,6 +142,19 @@ static void vcpu_tb_trans(struct qemu_plugin_tb *tb, void 
*userdata)
      }
      g_rw_lock_writer_unlock(&bbs_lock);
+ for (size_t idx = 0; idx < n_insns; idx++) {
+        struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, idx);
+        uint64_t insn_vaddr = qemu_plugin_insn_vaddr(insn);
+
+        if (begin_present && insn_vaddr == begin) {
+            qemu_plugin_register_vcpu_insn_exec_cb(
+                insn, vcpu_begin_insn_exec, QEMU_PLUGIN_CB_NO_REGS, NULL);
+        } else if (end_present && insn_vaddr == end) {
+            qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu(
+                insn, QEMU_PLUGIN_INLINE_STORE_U64, count_u64(), INT64_MIN);
+        }
+    }
+
      qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu(
          tb, QEMU_PLUGIN_INLINE_ADD_U64, count_u64(), n_insns);
@@ -127,7 +163,32 @@ static void vcpu_tb_trans(struct qemu_plugin_tb *tb, void *userdata) qemu_plugin_register_vcpu_tb_exec_cond_cb(
          tb, vcpu_interval_exec, QEMU_PLUGIN_CB_NO_REGS,
-        QEMU_PLUGIN_COND_GE, count_u64(), interval, NULL);
+        QEMU_PLUGIN_COND_LT, count_u64(), INT64_MIN, NULL);
+}

is INT64_MIN used as an arbitrary large value when interpreted as
unsigned immediate? If yes, that's confusing to the reader.

The unsigned comparison count < (uint64_t)INT64_MIN is equivalent with the signed comparison (int64_t)count >= 0. That is why this particular value is used.

Setting count to INT64_MIN suppresses output for the next 2^63 counted instructions, which is effectively disabled for this use. The begin callback clears the BB counters and sets count to -interval; end sets it back to INT64_MIN. The requested interval stays unchanged.

It would be better to choose something like:
UINT64_MAX - 1024, and assert than no tb has more than 1024 insn.

 From that and code above, it seems like we're trying to introduce a "run
forever" logic as an arbitrary big interval when using begin/end features.

Using UINT64_MAX - 1024 as the disabled counter value would wrap after 1025 instructions in total. Bounding the size of each TB would not prevent that.


Maybe it would be more obvious to use:
- leave count initialization to 0
- if using begin/end, set an arbitrary large interval (which will never
be "fullfilled" by normal execution), like UINT64_MAX - 1024.

Normal path is unmodified, and you can insert the begin/end logic
accordingly.
Using a large interval instead needs another consideration: the comparison threshold is embedded as an immediate in translated code. Changing the interval variable at begin/end would not update existing comparisons, and that variable is shared across vCPUs. Adjusting the per-vCPU counter lets the comparison stay fixed.


Maybe I missed the clever way it's supposed to work, but at this time of
the day, my brain can't see it :). Feel free to explain if I missed
anything obvious.
I agree this encoding deserves a comment; it is not obvious from the code alone.

Regards,
Akihiko Odaki

Reply via email to