From: Marc-André Lureau <marcandre.lur...@redhat.com> Generate the C QAPI types in Rust, with a few common niceties to Debug/Clone/Copy the Rust type.
An important question that remains unsolved to be usable with the QEMU schema in this version, is the handling of the 'if' compilation conditions. Since the 'if' value is a C pre-processor condition, it is hard to evaluate from Rust (we could implement a minimalistic CPP evaluator, or invoke CPP and somehow parse the output...). The normal Rust way of handling conditional compilation is via #[cfg] features, which rely on feature arguments being passed to rustc from Cargo. This would require a long Rust feature list, and new 'if-cfg' conditions in the schema (an idea would be for Cargo to read features from meson in the future?) Signed-off-by: Marc-André Lureau <marcandre.lur...@redhat.com> --- meson.build | 4 +- scripts/qapi-gen.py | 16 ++- scripts/qapi/rs.py | 126 ++++++++++++++++++++ scripts/qapi/rs_sys.py | 254 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 394 insertions(+), 6 deletions(-) create mode 100644 scripts/qapi/rs.py create mode 100644 scripts/qapi/rs_sys.py diff --git a/meson.build b/meson.build index c30bb290c5..b6b8330b97 100644 --- a/meson.build +++ b/meson.build @@ -1147,7 +1147,9 @@ qapi_gen_depends = [ meson.source_root() / 'scripts/qapi/__init__.py', meson.source_root() / 'scripts/qapi/types.py', meson.source_root() / 'scripts/qapi/visit.py', meson.source_root() / 'scripts/qapi/common.py', - meson.source_root() / 'scripts/qapi-gen.py' + meson.source_root() / 'scripts/qapi/rs.py', + meson.source_root() / 'scripts/qapi/rs_sys.py', + meson.source_root() / 'scripts/qapi-gen.py', ] tracetool = [ diff --git a/scripts/qapi-gen.py b/scripts/qapi-gen.py index 541e8c1f55..5bfe9c8cd1 100644 --- a/scripts/qapi-gen.py +++ b/scripts/qapi-gen.py @@ -16,10 +16,13 @@ from qapi.schema import QAPIError, QAPISchema from qapi.types import gen_types from qapi.visit import gen_visit +from qapi.rs_sys import gen_rs_systypes def main(argv): parser = argparse.ArgumentParser( description='Generate code from a QAPI schema') + parser.add_argument('-r', '--rust', action='store_true', + help="generate Rust code") parser.add_argument('-b', '--builtins', action='store_true', help="generate code for built-in types") parser.add_argument('-o', '--output-dir', action='store', default='', @@ -45,11 +48,14 @@ def main(argv): print(err, file=sys.stderr) exit(1) - gen_types(schema, args.output_dir, args.prefix, args.builtins) - gen_visit(schema, args.output_dir, args.prefix, args.builtins) - gen_commands(schema, args.output_dir, args.prefix) - gen_events(schema, args.output_dir, args.prefix) - gen_introspect(schema, args.output_dir, args.prefix, args.unmask) + if args.rust: + gen_rs_systypes(schema, args.output_dir, args.prefix, args.builtins) + else: + gen_types(schema, args.output_dir, args.prefix, args.builtins) + gen_visit(schema, args.output_dir, args.prefix, args.builtins) + gen_commands(schema, args.output_dir, args.prefix) + gen_events(schema, args.output_dir, args.prefix) + gen_introspect(schema, args.output_dir, args.prefix, args.unmask) if __name__ == '__main__': diff --git a/scripts/qapi/rs.py b/scripts/qapi/rs.py new file mode 100644 index 0000000000..daa946580b --- /dev/null +++ b/scripts/qapi/rs.py @@ -0,0 +1,126 @@ +# This work is licensed under the terms of the GNU GPL, version 2. +# See the COPYING file in the top-level directory. +""" +QAPI Rust generator +""" + +import os +import subprocess + +from qapi.common import * +from qapi.gen import QAPIGen, QAPISchemaVisitor + + +rs_name_trans = str.maketrans('.-', '__') + +# Map @name to a valid Rust identifier. +# If @protect, avoid returning certain ticklish identifiers (like +# keywords) by prepending raw identifier prefix 'r#'. +def rs_name(name, protect=True): + name = name.translate(rs_name_trans) + if name[0].isnumeric(): + name = '_' + name + if not protect: + return name + # based from the list: + # https://doc.rust-lang.org/reference/keywords.html + if name in ('Self', 'abstract', 'as', 'async', + 'await','become', 'box', 'break', + 'const', 'continue', 'crate', 'do', + 'dyn', 'else', 'enum', 'extern', + 'false', 'final', 'fn', 'for', + 'if', 'impl', 'in', 'let', + 'loop', 'macro', 'match', 'mod', + 'move', 'mut', 'override', 'priv', + 'pub', 'ref', 'return', 'self', + 'static', 'struct', 'super', 'trait', + 'true', 'try', 'type', 'typeof', + 'union', 'unsafe', 'unsized', 'use', + 'virtual', 'where', 'while', 'yield', + ): + name = 'r#' + name + return name + + +def rs_ctype_parse(c_type): + is_pointer = False + if c_type.endswith(pointer_suffix): + is_pointer = True + c_type = c_type.rstrip(pointer_suffix).strip() + is_list = c_type.endswith('List') + is_const = False + if c_type.startswith('const '): + is_const = True + c_type = c_type[6:] + + return (is_pointer, is_const, is_list, c_type) + + +def rs_systype(c_type, sys_ns='qapi_sys::', list_as_newp=False): + (is_pointer, is_const, is_list, c_type) = rs_ctype_parse(c_type) + + to_rs = { + 'char': 'libc::c_char', + 'int8_t': 'i8', + 'uint8_t': 'u8', + 'int16_t': 'i16', + 'uint16_t': 'u16', + 'int32_t': 'i32', + 'uint32_t': 'u32', + 'int64_t': 'libc::c_longlong', + 'uint64_t': 'libc::c_ulonglong', + 'double': 'libc::c_double', + 'bool': 'bool', + } + + rs = '' + if is_const and is_pointer: + rs += '*const ' + elif is_pointer: + rs += '*mut ' + if c_type in to_rs: + rs += to_rs[c_type] + else: + rs += sys_ns + c_type + + if is_list and list_as_newp: + rs = 'NewPtr<{}>'.format(rs) + + return rs + + +def to_camel_case(value): + if value[0] == '_': + return value + raw_id = False + if value.startswith('r#'): + raw_id = True + value = value[2:] + value = ''.join(word.title() for word in filter(None, re.split("[-_]+", value))) + if raw_id: + return 'r#' + value + else: + return value + + +class QAPIGenRs(QAPIGen): + + def __init__(self, fname): + super().__init__(fname) + + +class QAPISchemaRsVisitor(QAPISchemaVisitor): + + def __init__(self, prefix, what): + self._prefix = prefix + self._what = what + self._gen = QAPIGenRs(self._prefix + self._what + '.rs') + + def write(self, output_dir): + self._gen.write(output_dir) + + pathname = os.path.join(output_dir, self._gen.fname) + try: + subprocess.check_call(['rustfmt', pathname]) + except FileNotFoundError: + pass diff --git a/scripts/qapi/rs_sys.py b/scripts/qapi/rs_sys.py new file mode 100644 index 0000000000..551a910c57 --- /dev/null +++ b/scripts/qapi/rs_sys.py @@ -0,0 +1,254 @@ +# This work is licensed under the terms of the GNU GPL, version 2. +# See the COPYING file in the top-level directory. +""" +QAPI Rust sys/ffi generator +""" + +from qapi.common import * +from qapi.rs import * +from qapi.schema import QAPISchemaEnumMember, QAPISchemaObjectType + + +objects_seen = set() + + +def gen_rs_sys_enum(name, ifcond, members, prefix=None): + if ifcond: + raise NotImplementedError("ifcond are not implemented") + # append automatically generated _max value + enum_members = members + [QAPISchemaEnumMember('_MAX', None)] + + ret = mcgen(''' + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(C)] +pub enum %(rs_name)s { +''', + rs_name=rs_name(name)) + + for m in enum_members: + if m.ifcond: + raise NotImplementedError("ifcond are not implemented") + ret += mcgen(''' + %(c_enum)s, +''', + c_enum=to_camel_case(rs_name(m.name, False))) + ret += mcgen(''' +} +''') + return ret + + +def gen_rs_sys_struct_members(members): + ret = '' + for memb in members: + if memb.ifcond: + raise NotImplementedError("ifcond are not implemented") + if memb.optional: + ret += mcgen(''' + pub has_%(rs_name)s: bool, +''', + rs_name=rs_name(memb.name, protect=False)) + ret += mcgen(''' + pub %(rs_name)s: %(rs_systype)s, +''', + rs_systype=rs_systype(memb.type.c_type(), ''), rs_name=rs_name(memb.name)) + return ret + + +def gen_rs_sys_free(ty): + return mcgen(''' + +extern "C" { + pub fn qapi_free_%(ty)s(obj: *mut %(ty)s); +} +''', ty=ty) + + +def gen_rs_sys_variants(name, variants): + ret = mcgen(''' + +#[repr(C)] +#[derive(Copy, Clone)] +pub union %(rs_name)s { /* union tag is @%(tag_name)s */ +''', + tag_name=rs_name(variants.tag_member.name), + rs_name=name) + + for var in variants.variants: + if var.ifcond: + raise NotImplementedError("ifcond are not implemented") + if var.type.name == 'q_empty': + continue + ret += mcgen(''' + pub %(rs_name)s: %(rs_systype)s, +''', + rs_systype=rs_systype(var.type.c_unboxed_type(), ''), + rs_name=rs_name(var.name)) + + ret += mcgen(''' +} + +impl ::std::fmt::Debug for %(rs_name)s { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + f.debug_struct(&format!("%(rs_name)s @ {:?}", self as *const _)) + .finish() + } +} +''', rs_name=name) + + return ret + + +def gen_rs_sys_object(name, ifcond, base, members, variants): + if ifcond: + raise NotImplementedError("ifcond are not implemented") + if name in objects_seen: + return '' + + ret = '' + objects_seen.add(name) + unionty = name + 'Union' + if variants: + for v in variants.variants: + if v.ifcond: + raise NotImplementedError("ifcond are not implemented") + if isinstance(v.type, QAPISchemaObjectType): + ret += gen_rs_sys_object(v.type.name, v.type.ifcond, v.type.base, + v.type.local_members, v.type.variants) + ret += gen_rs_sys_variants(unionty, variants) + + ret += gen_rs_sys_free(rs_name(name)) + ret += mcgen(''' + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct %(rs_name)s { +''', + rs_name=rs_name(name)) + + if base: + if not base.is_implicit(): + ret += mcgen(''' + // Members inherited: +''') + ret += gen_rs_sys_struct_members(base.members) + if not base.is_implicit(): + ret += mcgen(''' + // Own members: +''') + + ret += gen_rs_sys_struct_members(members) + if variants: + ret += mcgen(''' + pub u: %(unionty)s +''', unionty=unionty) + ret += mcgen(''' +} +''') + return ret + + +def gen_rs_sys_variant(name, ifcond, variants): + if ifcond: + raise NotImplementedError("ifcond are not implemented") + if name in objects_seen: + return '' + + objects_seen.add(name) + + vs = '' + for var in variants.variants: + if var.type.name == 'q_empty': + continue + vs += mcgen(''' + pub %(mem_name)s: %(rs_systype)s, +''', + rs_systype=rs_systype(var.type.c_unboxed_type(), ''), + mem_name=rs_name(var.name)) + + return mcgen(''' + +#[repr(C)] +#[derive(Copy,Clone)] +pub union %(rs_name)sUnion { + %(variants)s +} + +impl ::std::fmt::Debug for %(rs_name)sUnion { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + f.debug_struct(&format!("%(rs_name)sUnion @ {:?}", self as *const _)) + .finish() + } +} + +#[repr(C)] +#[derive(Copy,Clone,Debug)] +pub struct %(rs_name)s { + pub ty: QType, + pub u: %(rs_name)sUnion, +} +''', + rs_name=rs_name(name), variants=vs) + + +def gen_rs_sys_array(name, ifcond, element_type): + if ifcond: + raise NotImplementedError("ifcond are not implemented") + ret = mcgen(''' + +#[repr(C)] +#[derive(Copy,Clone)] +pub struct %(rs_name)s { + pub next: *mut %(rs_name)s, + pub value: %(rs_systype)s, +} + +impl ::std::fmt::Debug for %(rs_name)s { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + f.debug_struct(&format!("%(rs_name)s @ {:?}", self as *const _)) + .finish() + } +} +''', + rs_name=rs_name(name), rs_systype=rs_systype(element_type.c_type(), '')) + ret += gen_rs_sys_free(rs_name(name)) + return ret + + +class QAPISchemaGenRsSysTypeVisitor(QAPISchemaRsVisitor): + + def __init__(self, prefix): + super().__init__(prefix, 'qapi-sys-types') + + def visit_begin(self, schema): + # gen_object() is recursive, ensure it doesn't visit the empty type + objects_seen.add(schema.the_empty_object_type.name) + self._gen.preamble_add( + mcgen(''' +// generated by qapi-gen, DO NOT EDIT + +use common::sys::{QNull, QObject}; + +''')) + + def visit_enum_type(self, name, info, ifcond, features, members, prefix): + self._gen.add(gen_rs_sys_enum(name, ifcond, members, prefix)) + + def visit_array_type(self, name, info, ifcond, element_type): + self._gen.add(gen_rs_sys_array(name, ifcond, element_type)) + + def visit_object_type(self, name, info, ifcond, features, + base, members, variants): + if name.startswith('q_'): + return + self._gen.add(gen_rs_sys_object(name, ifcond, base, members, variants)) + + def visit_alternate_type(self, name, info, ifcond, features, variants): + self._gen.add(gen_rs_sys_variant(name, ifcond, variants)) + + +def gen_rs_systypes(schema, output_dir, prefix, opt_builtins): + vis = QAPISchemaGenRsSysTypeVisitor(prefix) + schema.visit(vis) + vis.write(output_dir) -- 2.28.0