Commit 76b70dcb8f for qemu.org

commit 76b70dcb8f425363cf4c767cd84761e219349bdd
Author: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Date:   Mon Jun 15 12:35:25 2026 -0700

    plugins/cpp: register callbacks using captureless lambda

    We can now demonstrate what previous changes allow us to do. Since all
    callbacks have a userdata pointer, we can use that mechanism to move an
    object through all of them.

    In other words, we can now have stateful plugins without resorting to
    any global variable.

    As an example, we implement tb counting plugin with our cpp plugin. It
    produces an output similar to hotblocks, with same performance.

    Reviewed-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
    Link: https://lore.kernel.org/qemu-devel/20260615193526.2883349-27-pierrick.bouvier@oss.qualcomm.com
    Signed-off-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>

diff --git a/contrib/plugins/cpp.cpp b/contrib/plugins/cpp.cpp
index a0bb261fbe..006fa6de24 100644
--- a/contrib/plugins/cpp.cpp
+++ b/contrib/plugins/cpp.cpp
@@ -363,14 +363,98 @@

 QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;

-static void vcpu_tb_trans(struct qemu_plugin_tb *tb, void *userdata)
+class Plugin
 {
-}
+    public:
+    void tb_exec(std::atomic<uint64_t> &count)
+    {
+        count++;
+    }
+
+    void tb_trans(struct qemu_plugin_tb *tb)
+    {
+        auto vaddr = qemu_plugin_tb_vaddr(tb);
+
+        TbData *data = [&]() {
+            std::lock_guard l(tb_trans_lock);
+            auto [it, is_new] = tb_data.try_emplace(vaddr);
+            TbData &in = it->second;
+            if (is_new) {
+                in.first = 0;
+                in.second = this;
+            }
+            return &in;
+        } ();
+
+        qemu_plugin_register_vcpu_tb_exec_cb(
+            tb,
+            [](unsigned int cpu_index, void *udata) {
+                auto &[counter, p] = *static_cast<TbData*>(udata);
+                p->tb_exec(counter);
+            },
+            QEMU_PLUGIN_CB_NO_REGS,
+            data);
+    }
+
+    void at_exit()
+    {
+        std::vector<std::pair<Vaddr, uint64_t>> v;
+        for (auto &[vaddr, data] : tb_data) {
+            uint64_t count = data.first;
+            v.push_back({vaddr, count});
+        }
+        std::sort(v.begin(), v.end(),
+                  [](const auto &a, const auto &b) {
+                    return a.second > b.second;
+                  });
+        std::stringstream ss;
+        ss << "Top 10 executed TB are:\n";
+        size_t idx = 0;
+        for (auto &tb : v) {
+            if (idx >= 10) {
+                break;
+            }
+            ss << std::hex << "0x" << tb.first << std::dec << ": "
+               << tb.second << '\n';
+            ++idx;
+        }
+        qemu_plugin_outs(ss.str().c_str());
+    }
+
+    private:
+    using Vaddr = uint64_t;
+    using Counter = std::atomic<uint64_t>;
+    using TbData = std::pair<Counter, Plugin*>;
+    std::unordered_map<Vaddr, TbData> tb_data;
+    std::mutex tb_trans_lock;
+};

 QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
                                            const qemu_info_t *info,
                                            int argc, char **argv)
 {
-    qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans, NULL);
+    Plugin *plugin = new Plugin;
+
+    /*
+     * We can register a cb with any function, which may be a free
+     * function, a static class function, or a captureless lambda.
+     */
+    qemu_plugin_register_vcpu_tb_trans_cb(
+        id,
+        [](struct qemu_plugin_tb *tb, void *userdata) {
+            Plugin *p = static_cast<Plugin*>(userdata);
+            p->tb_trans(tb);
+        },
+        plugin);
+
+    qemu_plugin_register_atexit_cb(
+        id,
+        [](void *userdata) {
+            Plugin *p = static_cast<Plugin*>(userdata);
+            p->at_exit();
+            delete p;
+        },
+        plugin);
+
     return 0;
 }