Add tcg_gen_print() so translators can dump formatted state without
hand writing helpers. The helper builds a typed descriptor for up to
four arguments and emits a runtime call that prints via qemu_printf().

Example:

```
    tcg_gen_print("const value = 0x%lx\n"
                  TCG_PRINT_ARG_I64, tcg_constant_i64(0xdead),
                  TCG_PRINT_ARG_END);
```

Signed-off-by: Chao Liu <[email protected]>
---
 accel/tcg/tcg-runtime.c     | 187 ++++++++++++++++++++++++++++++++++++
 accel/tcg/tcg-runtime.h     |   1 +
 include/tcg/tcg-op-common.h |   2 +
 include/tcg/tcg-print.h     |  45 +++++++++
 tcg/tcg-op.c                |  89 +++++++++++++++++
 5 files changed, 324 insertions(+)
 create mode 100644 include/tcg/tcg-print.h

diff --git a/accel/tcg/tcg-runtime.c b/accel/tcg/tcg-runtime.c
index fa7ed9739c..e75c1d5702 100644
--- a/accel/tcg/tcg-runtime.c
+++ b/accel/tcg/tcg-runtime.c
@@ -23,9 +23,11 @@
  */
 #include "qemu/osdep.h"
 #include "qemu/host-utils.h"
+#include "qemu/qemu-print.h"
 #include "exec/cpu-common.h"
 #include "exec/helper-proto-common.h"
 #include "accel/tcg/getpc.h"
+#include "tcg/tcg-print.h"
 
 #define HELPER_H  "accel/tcg/tcg-runtime.h"
 #include "exec/helper-info.c.inc"
@@ -148,3 +150,188 @@ void HELPER(exit_atomic)(CPUArchState *env)
 {
     cpu_loop_exit_atomic(env_cpu(env), GETPC());
 }
+
+static void tcg_print_skip_format(const char **pfmt)
+{
+    const char *p = *pfmt;
+
+    while (*p) {
+        char c = *p;
+
+        if (c == 'l' || c == 'h' || c == 'z' || c == 't' ||
+            c == 'j' || c == '*') {
+            p++;
+            continue;
+        }
+        if (strchr("diuoxXp", c) || c == '%') {
+            p++;
+            break;
+        }
+        p++;
+    }
+    *pfmt = p;
+}
+
+static bool tcg_print_emit_arg(GString *out, const char **pfmt,
+                               TCGPrintArgType type, uint64_t value)
+{
+    const char *p = *pfmt;
+    char prefix[32];
+    size_t prelen = 0;
+
+    if (*p == '\0') {
+        tcg_print_skip_format(pfmt);
+        return false;
+    }
+
+    prefix[prelen++] = '%';
+    while (*p) {
+        char c = *p;
+
+        if (c == '*') {
+            tcg_print_skip_format(pfmt);
+            return false;
+        }
+        if (c == 'l' || c == 'h' || c == 'z' || c == 't' || c == 'j') {
+            p++;
+            continue;
+        }
+        if (strchr("diuoxXp", c)) {
+            char fmtbuf[64];
+            size_t len;
+
+            if (prelen >= sizeof(fmtbuf)) {
+                tcg_print_skip_format(pfmt);
+                return false;
+            }
+            memcpy(fmtbuf, prefix, prelen);
+            len = prelen;
+            if (c != 'p' &&
+                (type == TCG_PRINT_ARG_I64 ||
+                (type == TCG_PRINT_ARG_PTR &&
+                 sizeof(uintptr_t) == 8))) {
+                if (len + 2 >= sizeof(fmtbuf)) {
+                    tcg_print_skip_format(pfmt);
+                    return false;
+                }
+                fmtbuf[len++] = 'l';
+                fmtbuf[len++] = 'l';
+            }
+            if (len + 1 >= sizeof(fmtbuf)) {
+                tcg_print_skip_format(pfmt);
+                return false;
+            }
+            fmtbuf[len++] = c;
+            fmtbuf[len] = '\0';
+
+            char tmp[128];
+            bool ok = true;
+
+            switch (c) {
+            case 'd':
+            case 'i':
+                if (type == TCG_PRINT_ARG_I64 ||
+                    (type == TCG_PRINT_ARG_PTR &&
+                     sizeof(uintptr_t) == 8)) {
+                    g_snprintf(tmp, sizeof(tmp), fmtbuf,
+                               (long long)(int64_t)value);
+                } else if (type == TCG_PRINT_ARG_I32 ||
+                           (type == TCG_PRINT_ARG_PTR &&
+                            sizeof(uintptr_t) == 4)) {
+                    g_snprintf(tmp, sizeof(tmp), fmtbuf,
+                               (int)(int32_t)value);
+                } else {
+                    ok = false;
+                }
+                break;
+            case 'u':
+            case 'o':
+            case 'x':
+            case 'X':
+                if (type == TCG_PRINT_ARG_I64 ||
+                    (type == TCG_PRINT_ARG_PTR &&
+                     sizeof(uintptr_t) == 8)) {
+                    g_snprintf(tmp, sizeof(tmp), fmtbuf,
+                               (unsigned long long)value);
+                } else if (type == TCG_PRINT_ARG_I32 ||
+                           (type == TCG_PRINT_ARG_PTR &&
+                            sizeof(uintptr_t) == 4)) {
+                    g_snprintf(tmp, sizeof(tmp), fmtbuf,
+                               (unsigned int)(uint32_t)value);
+                } else {
+                    ok = false;
+                }
+                break;
+            case 'p':
+                g_snprintf(tmp, sizeof(tmp), fmtbuf,
+                           (void *)(uintptr_t)value);
+                break;
+            default:
+                ok = false;
+                break;
+            }
+
+            if (!ok) {
+                tcg_print_skip_format(pfmt);
+                return false;
+            }
+            g_string_append(out, tmp);
+            *pfmt = p + 1;
+            return true;
+        }
+        if (prelen + 1 >= sizeof(prefix)) {
+            tcg_print_skip_format(pfmt);
+            return false;
+        }
+        prefix[prelen++] = c;
+        p++;
+    }
+    tcg_print_skip_format(pfmt);
+    return false;
+}
+
+void HELPER(tcg_print)(void *fmt_ptr, uint32_t desc,
+                       uint64_t v0, uint64_t v1,
+                       uint64_t v2, uint64_t v3,
+                       uint64_t v4)
+{
+    const char *fmt = fmt_ptr;
+    uint64_t values[TCG_PRINT_MAX_ARGS] = { v0, v1, v2, v3, v4 };
+    TCGPrintArgType types[TCG_PRINT_MAX_ARGS];
+    GString *msg = g_string_new(NULL);
+    unsigned count = tcg_print_desc_count(desc);
+    unsigned i;
+    unsigned arg_index = 0;
+
+    g_assert(count <= TCG_PRINT_MAX_ARGS);
+
+    for (i = 0; i < count && i < TCG_PRINT_MAX_ARGS; i++) {
+        types[i] = tcg_print_desc_type(desc, i);
+    }
+
+    while (*fmt) {
+        if (*fmt != '%') {
+            g_string_append_c(msg, *fmt++);
+            continue;
+        }
+        fmt++;
+        if (*fmt == '%') {
+            g_string_append_c(msg, '%');
+            fmt++;
+            continue;
+        }
+        if (arg_index >= count) {
+            tcg_print_skip_format(&fmt);
+            g_string_append(msg, "<missing>");
+            continue;
+        }
+        if (!tcg_print_emit_arg(msg, &fmt, types[arg_index],
+                                 values[arg_index])) {
+            g_string_append(msg, "<fmt?>");
+        }
+        arg_index++;
+    }
+
+    qemu_printf("%s", msg->str);
+    g_string_free(msg, TRUE);
+}
diff --git a/accel/tcg/tcg-runtime.h b/accel/tcg/tcg-runtime.h
index 8436599b9f..6b1f0c0b13 100644
--- a/accel/tcg/tcg-runtime.h
+++ b/accel/tcg/tcg-runtime.h
@@ -27,6 +27,7 @@ DEF_HELPER_FLAGS_1(ctpop_i64, TCG_CALL_NO_RWG_SE, i64, i64)
 DEF_HELPER_FLAGS_1(lookup_tb_ptr, TCG_CALL_NO_WG_SE, cptr, env)
 
 DEF_HELPER_FLAGS_1(exit_atomic, TCG_CALL_NO_WG, noreturn, env)
+DEF_HELPER_FLAGS_7(tcg_print, 0, void, ptr, i32, i64, i64, i64, i64, i64)
 
 #ifndef IN_HELPER_PROTO
 /*
diff --git a/include/tcg/tcg-op-common.h b/include/tcg/tcg-op-common.h
index f752ef440b..858137cf41 100644
--- a/include/tcg/tcg-op-common.h
+++ b/include/tcg/tcg-op-common.h
@@ -9,6 +9,7 @@
 #define TCG_TCG_OP_COMMON_H
 
 #include "tcg/tcg.h"
+#include "tcg/tcg-print.h"
 #include "exec/helper-proto-common.h"
 #include "exec/helper-gen-common.h"
 
@@ -77,6 +78,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_print(const char *fmt, ...);
 
 /* 32 bit ops */
 
diff --git a/include/tcg/tcg-print.h b/include/tcg/tcg-print.h
new file mode 100644
index 0000000000..40a2dd9d40
--- /dev/null
+++ b/include/tcg/tcg-print.h
@@ -0,0 +1,45 @@
+/*
+ * Shared definitions for the TCG printf-style helper.
+ *
+ * Copyright (c) 2025 Chao Liu <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef TCG_TCG_PRINT_H
+#define TCG_TCG_PRINT_H
+
+#define TCG_PRINT_MAX_ARGS 5
+
+typedef enum TCGPrintArgType {
+    TCG_PRINT_ARG_END = 0,
+    TCG_PRINT_ARG_I32 = 1,
+    TCG_PRINT_ARG_I64 = 2,
+    TCG_PRINT_ARG_PTR = 3,
+} TCGPrintArgType;
+
+#define TCG_PRINT_DESC_COUNT_MASK 0xF
+#define TCG_PRINT_DESC_SHIFT 4
+#define TCG_PRINT_DESC_BITS_PER_ARG 3
+#define TCG_PRINT_DESC_TYPE_MASK ((1u << TCG_PRINT_DESC_BITS_PER_ARG) - 1)
+
+static inline unsigned tcg_print_desc_count(uint32_t desc)
+{
+    return desc & TCG_PRINT_DESC_COUNT_MASK;
+}
+
+static inline unsigned tcg_print_desc_type(uint32_t desc, unsigned index)
+{
+    return (desc >> (TCG_PRINT_DESC_SHIFT +
+                     index * TCG_PRINT_DESC_BITS_PER_ARG))
+        & TCG_PRINT_DESC_TYPE_MASK;
+}
+
+static inline uint32_t tcg_print_desc_add_type(uint32_t desc, unsigned index,
+                                               TCGPrintArgType type)
+{
+    return desc | ((uint32_t)type & TCG_PRINT_DESC_TYPE_MASK)
+        << (TCG_PRINT_DESC_SHIFT + index * TCG_PRINT_DESC_BITS_PER_ARG);
+}
+
+#endif /* TCG_TCG_PRINT_H */
diff --git a/tcg/tcg-op.c b/tcg/tcg-op.c
index ab7b409be6..44d50c84da 100644
--- a/tcg/tcg-op.c
+++ b/tcg/tcg-op.c
@@ -323,6 +323,95 @@ void tcg_gen_plugin_mem_cb(TCGv_i64 addr, unsigned meminfo)
     tcg_gen_op2(INDEX_op_plugin_mem_cb, 0, tcgv_i64_arg(addr), meminfo);
 }
 
+void tcg_gen_print(const char *fmt, ...)
+{
+    TCGTemp *temps[TCG_PRINT_MAX_ARGS];
+    TCGPrintArgType kinds[TCG_PRINT_MAX_ARGS];
+    TCGv_i64 values[TCG_PRINT_MAX_ARGS];
+    TCGv_i64 to_free[TCG_PRINT_MAX_ARGS];
+    int free_count = 0;
+    int count = 0;
+    va_list ap;
+    uint32_t desc;
+    TCGv_ptr fmt_ptr;
+    TCGv_i32 desc_arg;
+    TCGv_i64 zero;
+    int i;
+
+    QEMU_BUILD_BUG_ON(TCG_PRINT_MAX_ARGS != 5);
+
+    va_start(ap, fmt);
+    for (;;) {
+        int kind = va_arg(ap, int);
+
+        if (kind == TCG_PRINT_ARG_END) {
+            break;
+        }
+        g_assert(count < TCG_PRINT_MAX_ARGS);
+
+        switch (kind) {
+        case TCG_PRINT_ARG_I32:
+            temps[count] = tcgv_i32_temp(va_arg(ap, TCGv_i32));
+            break;
+        case TCG_PRINT_ARG_I64:
+            temps[count] = tcgv_i64_temp(va_arg(ap, TCGv_i64));
+            break;
+        case TCG_PRINT_ARG_PTR:
+            temps[count] = tcgv_ptr_temp(va_arg(ap, TCGv_ptr));
+            break;
+        default:
+            g_assert_not_reached();
+        }
+        kinds[count++] = kind;
+    }
+    va_end(ap);
+
+    desc = count;
+    for (i = 0; i < count; i++) {
+        desc = tcg_print_desc_add_type(desc, i, kinds[i]);
+    }
+
+    fmt_ptr = tcg_constant_ptr(fmt);
+    desc_arg = tcg_constant_i32(desc);
+    zero = tcg_constant_i64(0);
+
+    for (i = 0; i < count; i++) {
+        switch (kinds[i]) {
+        case TCG_PRINT_ARG_I32:
+            values[i] = tcg_temp_ebb_new_i64();
+            tcg_gen_extu_i32_i64(values[i], temp_tcgv_i32(temps[i]));
+            to_free[free_count++] = values[i];
+            break;
+        case TCG_PRINT_ARG_I64:
+            values[i] = temp_tcgv_i64(temps[i]);
+            break;
+        case TCG_PRINT_ARG_PTR:
+#if UINTPTR_MAX == UINT32_MAX
+            values[i] = tcg_temp_ebb_new_i64();
+            tcg_gen_extu_i32_i64(values[i],
+                                 (TCGv_i32)temp_tcgv_ptr(temps[i]));
+            to_free[free_count++] = values[i];
+#else
+            values[i] = temp_tcgv_i64(temps[i]);
+#endif
+            break;
+        default:
+            g_assert_not_reached();
+        }
+    }
+    for (; i < TCG_PRINT_MAX_ARGS; i++) {
+        values[i] = zero;
+    }
+
+    gen_helper_tcg_print(fmt_ptr, desc_arg,
+                         values[0], values[1], values[2], values[3],
+                         values[4]);
+
+    for (i = 0; i < free_count; i++) {
+        tcg_temp_free_i64(to_free[i]);
+    }
+}
+
 /* 32 bit ops */
 
 void tcg_gen_discard_i32(TCGv_i32 arg)
-- 
2.52.0


Reply via email to