tracetool: Add Rust format support

Generating .rs files makes it possible to support tracing in rust.
This support comprises a new format, and common code that converts
the C expressions in trace-events to Rust.  In particular, types
need to be converted, and PRI macros expanded.

As of this commit no backend generates Rust code, but it is already
possible to use tracetool to generate Rust sources; they are not
functional but they compile and contain tracepoint functions.

[Move Rust argument conversion from Event to Arguments; string
 support. - Paolo]

Signed-off-by: Tanish Desai <tanishdesai37@gmail.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Message-ID: <20250929154938.594389-9-pbonzini@redhat.com>
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Tanish Desai 2025-09-29 17:49:30 +02:00 committed by Stefan Hajnoczi
parent 494492c5c5
commit e2e182bef7
2 changed files with 219 additions and 0 deletions

View file

@ -30,6 +30,49 @@ def error(*lines):
error_write(*lines)
sys.exit(1)
FMT_TOKEN = re.compile(r'''(?:
" ( (?: [^"\\] | \\[\\"abfnrt] | # a string literal
\\x[0-9a-fA-F][0-9a-fA-F]) *? ) "
| ( PRI [duixX] (?:8|16|32|64|PTR|MAX) ) # a PRIxxx macro
| \s+ # spaces (ignored)
)''', re.X)
PRI_SIZE_MAP = {
'8': 'hh',
'16': 'h',
'32': '',
'64': 'll',
'PTR': 't',
'MAX': 'j',
}
def expand_format_string(c_fmt, prefix=""):
def pri_macro_to_fmt(pri_macro):
assert pri_macro.startswith("PRI")
fmt_type = pri_macro[3] # 'd', 'i', 'u', or 'x'
fmt_size = pri_macro[4:] # '8', '16', '32', '64', 'PTR', 'MAX'
size = PRI_SIZE_MAP.get(fmt_size, None)
if size is None:
raise Exception(f"unknown macro {pri_macro}")
return size + fmt_type
result = prefix
pos = 0
while pos < len(c_fmt):
m = FMT_TOKEN.match(c_fmt, pos)
if not m:
print("No match at position", pos, ":", repr(c_fmt[pos:]), file=sys.stderr)
raise Exception("syntax error in trace file")
if m[1]:
substr = m[1]
elif m[2]:
substr = pri_macro_to_fmt(m[2])
else:
substr = ""
result += substr
pos = m.end()
return result
out_lineno = 1
out_filename = '<none>'
@ -89,6 +132,49 @@ ALLOWED_TYPES = [
"ptrdiff_t",
]
C_TYPE_KEYWORDS = {"char", "int", "void", "short", "long", "signed", "unsigned"}
C_TO_RUST_TYPE_MAP = {
"int": "std::ffi::c_int",
"long": "std::ffi::c_long",
"long long": "std::ffi::c_longlong",
"short": "std::ffi::c_short",
"char": "std::ffi::c_char",
"bool": "bool",
"unsigned": "std::ffi::c_uint",
# multiple keywords, keep them sorted
"long unsigned": "std::ffi::c_long",
"long long unsigned": "std::ffi::c_ulonglong",
"short unsigned": "std::ffi::c_ushort",
"char unsigned": "u8",
"int8_t": "i8",
"uint8_t": "u8",
"int16_t": "i16",
"uint16_t": "u16",
"int32_t": "i32",
"uint32_t": "u32",
"int64_t": "i64",
"uint64_t": "u64",
"void": "()",
"size_t": "usize",
"ssize_t": "isize",
"uintptr_t": "usize",
"ptrdiff_t": "isize",
}
# Rust requires manual casting of <32-bit types when passing them to
# variable-argument functions.
RUST_VARARGS_SMALL_TYPES = {
"std::ffi::c_short",
"std::ffi::c_ushort",
"std::ffi::c_char",
"i8",
"u8",
"i16",
"u16",
"bool",
}
def validate_type(name):
bits = name.split(" ")
for bit in bits:
@ -104,6 +190,38 @@ def validate_type(name):
"other complex pointer types should be "
"declared as 'void *'" % name)
def c_type_to_rust(name):
ptr = False
const = False
name = name.rstrip()
if name[-1] == '*':
name = name[:-1].rstrip()
ptr = True
if name[-1] == '*':
# pointers to pointers are the same as void*
name = "void"
bits = name.split()
if "const" in bits:
const = True
bits.remove("const")
if bits[0] in C_TYPE_KEYWORDS:
if "signed" in bits:
bits.remove("signed")
if len(bits) > 1 and "int" in bits:
bits.remove("int")
bits.sort()
name = ' '.join(bits)
else:
if len(bits) > 1:
raise ValueError("Invalid type '%s'." % name)
name = bits[0]
ty = C_TO_RUST_TYPE_MAP[name.strip()]
if ptr:
ty = f'*{"const" if const else "mut"} {ty}'
return ty
class Arguments:
"""Event arguments description."""
@ -192,6 +310,43 @@ class Arguments:
"""List of argument names casted to their type."""
return ["(%s)%s" % (type_, name) for type_, name in self._args]
def rust_decl_extern(self):
"""Return a Rust argument list for an extern "C" function"""
return ", ".join((f"_{name}: {c_type_to_rust(type_)}"
for type_, name in self._args))
def rust_decl(self):
"""Return a Rust argument list for a tracepoint function"""
def decl_type(type_):
if type_ == "const char *":
return "&std::ffi::CStr"
return c_type_to_rust(type_)
return ", ".join((f"_{name}: {decl_type(type_)}"
for type_, name in self._args))
def rust_call_extern(self):
"""Return a Rust argument list for a call to an extern "C" function"""
def rust_cast(name, type_):
if type_ == "const char *":
return f"_{name}.as_ptr()"
return f"_{name}"
return ", ".join((rust_cast(name, type_) for type_, name in self._args))
def rust_call_varargs(self):
"""Return a Rust argument list for a call to a C varargs function"""
def rust_cast(name, type_):
if type_ == "const char *":
return f"_{name}.as_ptr()"
type_ = c_type_to_rust(type_)
if type_ in RUST_VARARGS_SMALL_TYPES:
return f"_{name} as std::ffi::c_int"
return f"_{name} /* as {type_} */"
return ", ".join((rust_cast(name, type_) for type_, name in self._args))
class Event(object):
"""Event description.

View file

@ -0,0 +1,64 @@
# SPDX-License-Identifier: GPL-2.0-or-later
"""
trace-DIR.rs
"""
__author__ = "Tanish Desai <tanishdesai37@gmail.com>"
__copyright__ = "Copyright 2025, Tanish Desai <tanishdesai37@gmail.com>"
__license__ = "GPL version 2 or (at your option) any later version"
__maintainer__ = "Stefan Hajnoczi"
__email__ = "stefanha@redhat.com"
from tracetool import out
def generate(events, backend, group):
out('// SPDX-License-Identifier: GPL-2.0-or-later',
'// This file is @generated by tracetool, do not edit.',
'',
'#[allow(unused_imports)]',
'use std::ffi::c_char;',
'#[allow(unused_imports)]',
'use util::bindings;',
'',
'#[inline(always)]',
'fn trace_event_state_is_enabled(dstate: u16) -> bool {',
' (unsafe { trace_events_enabled_count }) != 0 && dstate != 0',
'}',
'',
'extern "C" {',
' static mut trace_events_enabled_count: u32;',
'}',)
out('extern "C" {')
for e in events:
out(' static mut %s: u16;' % e.api(e.QEMU_DSTATE))
out('}')
backend.generate_begin(events, group)
for e in events:
out('',
'#[inline(always)]',
'#[allow(dead_code)]',
'pub fn %(api)s(%(args)s)',
'{',
api=e.api(e.QEMU_TRACE),
args=e.args.rust_decl())
if "disable" not in e.properties:
backend.generate(e, group, check_trace_event_get_state=False)
if backend.check_trace_event_get_state:
event_id = 'TRACE_' + e.name.upper()
out(' if trace_event_state_is_enabled(unsafe { _%(event_id)s_DSTATE}) {',
event_id = event_id,
api=e.api())
backend.generate(e, group, check_trace_event_get_state=True)
out(' }')
out('}')
backend.generate_end(events, group)