All of lore.kernel.org
 help / color / mirror / Atom feed
From: marcandre.lureau@redhat.com
To: qemu-devel@nongnu.org
Cc: pbonzini@redhat.com, berrange@redhat.com, armbru@redhat.com,
	stefanha@redhat.com,
	"Marc-André Lureau" <marcandre.lureau@redhat.com>
Subject: [RFC v3 20/32] scripts/qapi: generate high-level Rust bindings
Date: Tue,  7 Sep 2021 16:19:31 +0400	[thread overview]
Message-ID: <20210907121943.3498701-21-marcandre.lureau@redhat.com> (raw)
In-Reply-To: <20210907121943.3498701-1-marcandre.lureau@redhat.com>

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Generate high-level idiomatic Rust code for the QAPI types, with to/from
translations for the C FFI.

- char* is mapped to String, scalars to there corresponding Rust types

- enums are simply aliased from FFI

- has_foo/foo members are mapped to Option<T>

- lists are represented as Vec<T>

- structures have Rust versions, with To/From FFI conversions

- alternate are represented as Rust enum

- unions are represented in a similar way as in C: a struct S with a "u"
  member (since S may have extra 'base' fields). However, the discriminant
  isn't a member of S, since Rust enum already include it.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 meson.build              |   1 +
 scripts/qapi/main.py     |   2 +
 scripts/qapi/rs.py       |  94 +++-
 scripts/qapi/rs_types.py | 966 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 1062 insertions(+), 1 deletion(-)
 create mode 100644 scripts/qapi/rs_types.py

diff --git a/meson.build b/meson.build
index 74e90059c2..8e12a4dd70 100644
--- a/meson.build
+++ b/meson.build
@@ -2017,6 +2017,7 @@ qapi_gen_depends = [ meson.source_root() / 'scripts/qapi/__init__.py',
                      meson.source_root() / 'scripts/qapi/common.py',
                      meson.source_root() / 'scripts/qapi/rs.py',
                      meson.source_root() / 'scripts/qapi/rs_ffi.py',
+                     meson.source_root() / 'scripts/qapi/rs_types.py',
                      meson.source_root() / 'scripts/qapi-gen.py',
 ]
 
diff --git a/scripts/qapi/main.py b/scripts/qapi/main.py
index deba72ee4e..9756c0c35d 100644
--- a/scripts/qapi/main.py
+++ b/scripts/qapi/main.py
@@ -17,6 +17,7 @@
 from .events import gen_events
 from .introspect import gen_introspect
 from .rs_ffi import gen_rs_ffitypes
+from .rs_types import gen_rs_types
 from .schema import QAPISchema
 from .types import gen_types
 from .visit import gen_visit
@@ -52,6 +53,7 @@ def generate(schema_file: str,
     schema = QAPISchema(schema_file)
     if rust:
         gen_rs_ffitypes(schema, output_dir, prefix)
+        gen_rs_types(schema, output_dir, prefix)
     else:
         gen_types(schema, output_dir, prefix, builtins)
         gen_visit(schema, output_dir, prefix, builtins)
diff --git a/scripts/qapi/rs.py b/scripts/qapi/rs.py
index be42329fa4..b53930eab2 100644
--- a/scripts/qapi/rs.py
+++ b/scripts/qapi/rs.py
@@ -9,7 +9,7 @@
 import subprocess
 from typing import NamedTuple, Optional
 
-from .common import POINTER_SUFFIX
+from .common import POINTER_SUFFIX, mcgen
 from .gen import QAPIGen
 from .schema import QAPISchemaModule, QAPISchemaVisitor
 
@@ -53,6 +53,64 @@ def rs_name(name: str, protect: bool = True) -> str:
     return name
 
 
+def rs_type(c_type: str,
+            qapi_ns: Optional[str] = 'qapi::',
+            optional: Optional[bool] = False,
+            box: bool = False) -> str:
+    (is_pointer, _, is_list, c_type) = rs_ctype_parse(c_type)
+    # accepts QAPI types ('any', 'str', ...) as we translate
+    # qapiList to Rust FFI types here.
+    to_rs = {
+        'any': 'QObject',
+        'bool': 'bool',
+        'char': 'i8',
+        'double': 'f64',
+        'int': 'i64',
+        'int16': 'i16',
+        'int16_t': 'i16',
+        'int32': 'i32',
+        'int32_t': 'i32',
+        'int64': 'i64',
+        'int64_t': 'i64',
+        'int8': 'i8',
+        'int8_t': 'i8',
+        'null': 'QNull',
+        'number': 'f64',
+        'size': 'u64',
+        'str': 'String',
+        'uint16': 'u16',
+        'uint16_t': 'u16',
+        'uint32': 'u32',
+        'uint32_t': 'u32',
+        'uint64': 'u64',
+        'uint64_t': 'u64',
+        'uint8': 'u8',
+        'uint8_t': 'u8',
+        'String': 'QapiString',
+    }
+    if is_pointer:
+        to_rs.update({
+            'char': 'String',
+        })
+
+    if is_list:
+        c_type = c_type[:-4]
+
+    to_rs = to_rs.get(c_type)
+    if to_rs:
+        ret = to_rs
+    else:
+        ret = qapi_ns + c_type
+
+    if is_list:
+        ret = 'Vec<%s>' % ret
+    elif is_pointer and not to_rs and box:
+        ret = 'Box<%s>' % ret
+    if optional:
+        ret = 'Option<%s>' % ret
+    return ret
+
+
 class CType(NamedTuple):
     is_pointer: bool
     is_const: bool
@@ -140,6 +198,40 @@ def to_snake_case(value: str) -> str:
     return snake_case.sub(r'_\1', value).lower()
 
 
+def to_qemu_none(c_type: str, name: str) -> str:
+    (is_pointer, _, is_list, _) = rs_ctype_parse(c_type)
+
+    if is_pointer:
+        if c_type == 'char':
+            return mcgen('''
+    let %(name)s_ = CString::new(%(name)s).unwrap();
+    let %(name)s = %(name)s_.as_ptr();
+''', name=name)
+        if is_list:
+            return mcgen('''
+    let %(name)s_ = NewPtr(%(name)s).to_qemu_none();
+    let %(name)s = %(name)s_.0.0;
+''', name=name)
+        return mcgen('''
+    let %(name)s_ = %(name)s.to_qemu_none();
+    let %(name)s = %(name)s_.0;
+''', name=name)
+    return ''
+
+
+def from_qemu(var_name: str, c_type: str, full: Optional[bool] = False) -> str:
+    (is_pointer, _, is_list, c_type) = rs_ctype_parse(c_type)
+    ptr = '{} as *{} _'.format(var_name, 'mut' if full else 'const')
+    if is_list:
+        ptr = 'NewPtr({})'.format(ptr)
+    if is_pointer:
+        ret = 'from_qemu_{}({})'.format('full' if full else 'none', ptr)
+        if c_type != 'char' and not is_list:
+            ret = 'Box::new(%s)' % ret
+        return ret
+    return var_name
+
+
 class QAPIGenRs(QAPIGen):
     pass
 
diff --git a/scripts/qapi/rs_types.py b/scripts/qapi/rs_types.py
new file mode 100644
index 0000000000..eb9877a0de
--- /dev/null
+++ b/scripts/qapi/rs_types.py
@@ -0,0 +1,966 @@
+# This work is licensed under the terms of the GNU GPL, version 2.
+# See the COPYING file in the top-level directory.
+"""
+QAPI Rust types generator
+"""
+
+from typing import List, Optional
+
+from .common import POINTER_SUFFIX, mcgen
+from .rs import (
+    QAPISchemaRsVisitor,
+    from_qemu,
+    rs_ctype_parse,
+    rs_ffitype,
+    rs_name,
+    rs_type,
+    to_camel_case,
+    to_snake_case,
+)
+from .schema import (
+    QAPISchema,
+    QAPISchemaEnumMember,
+    QAPISchemaEnumType,
+    QAPISchemaFeature,
+    QAPISchemaIfCond,
+    QAPISchemaObjectType,
+    QAPISchemaObjectTypeMember,
+    QAPISchemaType,
+    QAPISchemaVariants,
+)
+from .source import QAPISourceInfo
+
+
+objects_seen = set()
+
+
+def gen_rs_variants_to_tag(name: str,
+                           ifcond: QAPISchemaIfCond,
+                           variants: Optional[QAPISchemaVariants]) -> str:
+    ret = mcgen('''
+
+%(cfg)s
+impl From<&%(rs_name)sVariant> for %(tag)s {
+    fn from(e: &%(rs_name)sVariant) -> Self {
+        match e {
+    ''',
+                cfg=ifcond.rsgen(),
+                rs_name=rs_name(name),
+                tag=rs_type(variants.tag_member.type.c_type(), ''))
+
+    for var in variants.variants:
+        type_name = var.type.name
+        var_name = to_camel_case(rs_name(var.name))
+        patt = '(_)'
+        if type_name == 'q_empty':
+            patt = ''
+        ret += mcgen('''
+    %(cfg)s
+    %(rs_name)sVariant::%(var_name)s%(patt)s => Self::%(var_name)s,
+''',
+                     cfg=var.ifcond.rsgen(),
+                     rs_name=rs_name(name),
+                     var_name=var_name,
+                     patt=patt)
+
+    ret += mcgen('''
+        }
+    }
+}
+''')
+    return ret
+
+
+def variants_to_qemu_inner(name: str,
+                           variants: Optional[QAPISchemaVariants]) -> str:
+    members = ''
+    none_arms = ''
+    full_arms = ''
+    lifetime = ''
+    for var in variants.variants:
+        var_name = to_camel_case(rs_name(var.name))
+        type_name = var.type.name
+        if type_name == 'q_empty':
+            members += mcgen('''
+    %(cfg)s
+    %(var_name)s,
+''',
+                             cfg=var.ifcond.rsgen(),
+                             var_name=var_name)
+            none_arms += mcgen('''
+    %(cfg)s
+    %(rs_name)sVariant::%(var_name)s => {
+        (std::ptr::null_mut(),
+         %(rs_name)sVariantStorage::%(var_name)s)
+    },
+''',
+                               cfg=var.ifcond.rsgen(),
+                               rs_name=rs_name(name),
+                               var_name=var_name)
+            full_arms += mcgen('''
+    %(cfg)s
+    %(rs_name)sVariant::%(var_name)s => {
+        std::ptr::null_mut()
+    }
+''',
+                               cfg=var.ifcond.rsgen(),
+                               rs_name=rs_name(name),
+                               var_name=var_name)
+            continue
+        c_type = var.type.c_unboxed_type()
+        if type_name.endswith('-wrapper'):
+            wrap = list(var.type.members)[0]
+            type_name = wrap.type.name
+            c_type = wrap.type.c_unboxed_type()
+
+        lifetime = "<'a>"
+        (_, _, is_list, ffitype) = rs_ctype_parse(c_type)
+        ffitype = rs_ffitype(ffitype)
+        ptr_ty = 'NewPtr<*mut %s>' % ffitype if is_list else '*mut ' + ffitype
+        stash_ty = ': Stash<%s, _>' % ptr_ty if is_list else ''
+
+        members += mcgen('''
+    %(cfg)s
+    %(var_name)s(<%(rs_type)s as ToQemuPtr<'a, %(ptr_ty)s>>::Storage),
+''',
+                         cfg=var.ifcond.rsgen(),
+                         var_name=var_name,
+                         rs_type=rs_type(c_type, ''),
+                         ptr_ty=ptr_ty)
+        none_arms += mcgen('''
+    %(cfg)s
+    %(rs_name)sVariant::%(var_name)s(v) => {
+        let stash_%(stash_ty)s = v.to_qemu_none();
+        (stash_.0.to() as *mut std::ffi::c_void,
+         %(rs_name)sVariantStorage::%(var_name)s(stash_.1))
+    },
+''',
+                           cfg=var.ifcond.rsgen(),
+                           rs_name=rs_name(name),
+                           var_name=var_name,
+                           stash_ty=stash_ty)
+        ptr_ty = ': %s' % ptr_ty if is_list else ''
+        full_arms += mcgen('''
+    %(cfg)s
+    %(rs_name)sVariant::%(var_name)s(v) => {
+        let ptr%(ptr_ty)s = v.to_qemu_full();
+        ptr.to() as *mut std::ffi::c_void
+    },
+''',
+                           cfg=var.ifcond.rsgen(),
+                           rs_name=rs_name(name),
+                           var_name=var_name,
+                           ptr_ty=ptr_ty)
+    return (members, none_arms, full_arms, lifetime)
+
+
+def gen_rs_variants_to_qemu(name: str,
+                            ifcond: QAPISchemaIfCond,
+                            variants: Optional[QAPISchemaVariants]) -> str:
+    (members, none_arms, full_arms, lifetime) = \
+        variants_to_qemu_inner(name, variants)
+    return mcgen('''
+
+%(cfg)s
+impl QemuPtrDefault for %(rs_name)sVariant {
+    type QemuType = *mut std::ffi::c_void;
+}
+
+%(cfg)s
+pub enum %(rs_name)sVariantStorage%(lt)s {
+    %(members)s
+}
+
+%(cfg)s
+impl<'a> ToQemuPtr<'a, *mut std::ffi::c_void> for %(rs_name)sVariant {
+    type Storage = %(rs_name)sVariantStorage%(lt)s;
+
+    #[inline]
+    fn to_qemu_none(&'a self)
+    -> Stash<'a, *mut std::ffi::c_void, %(rs_name)sVariant> {
+        let (ptr_, cenum_) = match self {
+             %(none_arms)s
+        };
+
+        Stash(ptr_, cenum_)
+    }
+
+    #[inline]
+    fn to_qemu_full(&self) -> *mut std::ffi::c_void {
+        match self {
+            %(full_arms)s
+        }
+    }
+}
+''',
+                 cfg=ifcond.rsgen(),
+                 rs_name=rs_name(name),
+                 lt=lifetime,
+                 members=members,
+                 none_arms=none_arms,
+                 full_arms=full_arms)
+
+
+def gen_rs_variants(name: str,
+                    ifcond: QAPISchemaIfCond,
+                    variants: Optional[QAPISchemaVariants]) -> str:
+    ret = mcgen('''
+
+%(cfg)s
+#[derive(Clone,Debug)]
+pub enum %(rs_name)sVariant {
+''',
+                cfg=ifcond.rsgen(),
+                rs_name=rs_name(name))
+
+    for var in variants.variants:
+        type_name = var.type.name
+        var_name = to_camel_case(rs_name(var.name, False))
+        if type_name == 'q_empty':
+            ret += mcgen('''
+    %(cfg)s
+    %(var_name)s,
+''',
+                         cfg=var.ifcond.rsgen(),
+                         var_name=var_name)
+        else:
+            c_type = var.type.c_unboxed_type()
+            if c_type.endswith('_wrapper'):
+                c_type = c_type[6:-8]  # remove q_obj*-wrapper
+            ret += mcgen('''
+    %(cfg)s
+    %(var_name)s(%(rs_type)s),
+''',
+                         cfg=var.ifcond.rsgen(),
+                         var_name=var_name,
+                         rs_type=rs_type(c_type, ''))
+
+    ret += mcgen('''
+}
+''')
+
+    ret += gen_rs_variants_to_tag(name, ifcond, variants)
+    # implement ToQemu trait for the storage handling
+    # no need for gen_rs_variants_from_qemu() however
+    ret += gen_rs_variants_to_qemu(name, ifcond, variants)
+
+    return ret
+
+
+def gen_rs_object_to_qemu(name: str,
+                          ifcond: QAPISchemaIfCond,
+                          base: Optional[QAPISchemaObjectType],
+                          members: List[QAPISchemaObjectTypeMember],
+                          variants: Optional[QAPISchemaVariants]) -> str:
+    storage = []
+    stash = []
+    ffi_memb = []
+    memb_none = ''
+    memb_full = ''
+    if base:
+        members = list(base.members) + members
+    for memb in members:
+        if variants and variants.tag_member.name == memb.name:
+            continue
+        memb_name = to_snake_case(rs_name(memb.name))
+        c_type = memb.type.c_type()
+        (is_pointer, _, is_list, _) = rs_ctype_parse(c_type)
+        if is_pointer:
+            if memb.ifcond.is_present():
+                raise NotImplementedError("missing support for condition here")
+            typ = rs_type(memb.type.c_type(),
+                          optional=memb.optional,
+                          qapi_ns='',
+                          box=True)
+            styp = rs_ffitype(memb.type.c_type(), list_as_newp=True)
+            storage.append("Stash<'a, %s, %s>" % (styp, typ))
+        if memb.optional:
+            has_memb_name = 'has_%s' % rs_name(memb.name, protect=False)
+            ffi_memb.append(f"{memb.ifcond.rsgen()} {has_memb_name}")
+            has_memb = mcgen('''
+    %(cfg)s
+    let %(has_memb_name)s = self.%(memb_name)s.is_some();
+''',
+                             cfg=memb.ifcond.rsgen(),
+                             memb_name=memb_name,
+                             has_memb_name=has_memb_name)
+            memb_none += has_memb
+            memb_full += has_memb
+
+        if is_pointer:
+            stash_name = '{}_stash_'.format(memb_name)
+            stash.append(stash_name)
+            var = 'NewPtr(%s)' % memb_name if is_list else memb_name
+            memb_none += mcgen('''
+    let %(stash_name)s = self.%(memb_name)s.to_qemu_none();
+    let %(var)s = %(stash_name)s.0;
+''', stash_name=stash_name, memb_name=memb_name, var=var)
+            memb_full += mcgen('''
+    let %(var)s = self.%(memb_name)s.to_qemu_full();
+''', memb_name=memb_name, var=var)
+        else:
+            unwrap = ''
+            if memb.optional:
+                unwrap = '.unwrap_or_default()'
+            assign = mcgen('''
+    %(cfg)s
+    let %(memb_name)s = self.%(memb_name)s%(unwrap)s;
+''',
+                           cfg=memb.ifcond.rsgen(),
+                           memb_name=memb_name,
+                           unwrap=unwrap)
+            memb_none += assign
+            memb_full += assign
+
+        ffi_memb.append(f"{memb.ifcond.rsgen()} {memb_name}")
+
+    if variants:
+        tag_name = rs_name(variants.tag_member.name)
+        ffi_memb.append(tag_name)
+        ffi_memb.append('u')
+        voidp = '*mut std::ffi::c_void'
+        storage.append("Stash<'a, %s, %sVariant>" % (voidp, rs_name(name)))
+        tag = mcgen('''
+    let %(tag_name)s = (&self.u).into();
+''', tag_name=tag_name)
+        memb_none += tag
+        memb_full += tag
+        arms_none = ''
+        arms_full = ''
+        for variant in variants.variants:
+            typ = variant.type
+            if typ.name == 'q_empty':
+                arms_none += mcgen('''
+    %(cfg)s
+    %(rs_name)sVariantStorage::%(kind)s => qapi_ffi::%(rs_name)sUnion {
+        qapi_dummy: qapi_ffi::QapiDummy,
+    },''',
+                                   cfg=variant.ifcond.rsgen(),
+                                   rs_name=rs_name(name),
+                                   kind=to_camel_case(rs_name(variant.name)))
+                arms_full += mcgen('''
+    %(cfg)s
+    %(rs_name)sVariant::%(kind)s => qapi_ffi::%(rs_name)sUnion {
+        qapi_dummy: qapi_ffi::QapiDummy,
+    },''',
+                                   cfg=variant.ifcond.rsgen(),
+                                   rs_name=rs_name(name),
+                                   kind=to_camel_case(rs_name(variant.name)))
+            else:
+                if typ.name.endswith('-wrapper'):
+                    wrap_ty = list(typ.members)[0].type.c_type()
+                    ptr = wrap_ty.endswith(POINTER_SUFFIX)
+                    val = (
+                        rs_ffitype(variant.type.c_unboxed_type()) +
+                        ' { data: u_stash_.0.to() as *mut _ }' if ptr else
+                        ' { data: unsafe { *(u_stash_.0.to() as *const _) } }'
+                    )
+                else:
+                    val = '*_s.0'
+                arms_none += mcgen('''
+    %(cfg)s
+    %(rs_name)sVariantStorage::%(kind)s(ref _s) => qapi_ffi::%(rs_name)sUnion {
+        %(var_name)s: %(val)s,
+    },''',
+                                   cfg=variant.ifcond.rsgen(),
+                                   rs_name=rs_name(name),
+                                   kind=to_camel_case(rs_name(variant.name)),
+                                   var_name=rs_name(variant.name),
+                                   val=val)
+                arms_full += mcgen('''
+    %(cfg)s
+    %(rs_name)sVariant::%(kind)s(_) => qapi_ffi::%(rs_name)sUnion {
+        %(var_name)s: *(u_ptr_.to() as *const _),
+    },''',
+                                   cfg=variant.ifcond.rsgen(),
+                                   rs_name=rs_name(name),
+                                   kind=to_camel_case(rs_name(variant.name)),
+                                   var_name=rs_name(variant.name))
+        memb_none += mcgen('''
+    let u_stash_ = self.u.to_qemu_none();
+    let u = match u_stash_.1 {
+        %(arms)s
+    };
+''', arms=arms_none)
+        stash.append('u_stash_')
+        memb_full += mcgen('''
+    let u_ptr_ = self.u.to_qemu_full();
+    let u = match self.u {
+        %(arms)s
+    };
+    ffi::g_free(u_ptr_);
+''', arms=arms_full)
+
+    if not ffi_memb:
+        ffi_memb = ['qapi_dummy_for_empty_struct: 0']
+
+    return mcgen('''
+
+%(cfg)s
+impl QemuPtrDefault for %(rs_name)s {
+    type QemuType = *mut qapi_ffi::%(rs_name)s;
+}
+
+%(cfg)s
+impl<'a> ToQemuPtr<'a, *mut qapi_ffi::%(rs_name)s> for %(rs_name)s {
+    type Storage = (Box<qapi_ffi::%(rs_name)s>, %(storage)s);
+
+    #[inline]
+    fn to_qemu_none(&'a self)
+    -> Stash<'a, *mut qapi_ffi::%(rs_name)s, %(rs_name)s> {
+        %(memb_none)s
+        let mut box_ = Box::new(qapi_ffi::%(rs_name)s { %(ffi_memb)s });
+
+        Stash(&mut *box_, (box_, %(stash)s))
+    }
+
+    #[inline]
+    fn to_qemu_full(&self) -> *mut qapi_ffi::%(rs_name)s {
+        unsafe {
+            %(memb_full)s
+            let ptr = ffi::g_malloc0(
+                std::mem::size_of::<%(rs_name)s>()) as *mut _;
+            *ptr = qapi_ffi::%(rs_name)s { %(ffi_memb)s };
+            ptr
+        }
+    }
+}
+''',
+                 cfg=ifcond.rsgen(),
+                 rs_name=rs_name(name),
+                 storage=', '.join(storage),
+                 ffi_memb=', '.join(ffi_memb),
+                 memb_none=memb_none,
+                 memb_full=memb_full,
+                 stash=', '.join(stash))
+
+
+def gen_rs_members(members: List[QAPISchemaObjectTypeMember],
+                   exclude: List[str] = None) -> str:
+    exclude = exclude or []
+    return [f"{m.ifcond.rsgen()} {to_snake_case(rs_name(m.name))}"
+            for m in members if m.name not in exclude]
+
+
+def gen_rs_object_from_qemu(name: str,
+                            ifcond: QAPISchemaIfCond,
+                            base: Optional[QAPISchemaObjectType],
+                            members: List[QAPISchemaObjectTypeMember],
+                            variants: Optional[QAPISchemaVariants]) -> str:
+    exclude = [variants.tag_member.name] if variants else []
+    memb_names = []
+    if base:
+        names = gen_rs_members(base.members, exclude)
+        memb_names.extend(names)
+    names = gen_rs_members(members, exclude)
+    memb_names.extend(names)
+
+    ret = mcgen('''
+
+%(cfg)s
+impl FromQemuPtrFull<*mut qapi_ffi::%(rs_name)s> for %(rs_name)s {
+    unsafe fn from_qemu_full(ffi: *mut qapi_ffi::%(rs_name)s) -> Self {
+        let ret = from_qemu_none(ffi as *const _);
+        qapi_ffi::qapi_free_%(name)s(ffi);
+        ret
+    }
+}
+
+%(cfg)s
+impl FromQemuPtrNone<*const qapi_ffi::%(rs_name)s> for %(rs_name)s {
+    unsafe fn from_qemu_none(ffi: *const qapi_ffi::%(rs_name)s) -> Self {
+        let _ffi = &*ffi;
+''',
+                cfg=ifcond.rsgen(),
+                name=rs_name(name, protect=False),
+                rs_name=rs_name(name))
+
+    if base:
+        members = list(base.members) + members
+
+    tag_member = variants.tag_member if variants else None
+    for memb in members:
+        if memb == tag_member:
+            continue
+        memb_name = rs_name(memb.name)
+        val = from_qemu('_ffi.' + to_snake_case(memb_name), memb.type.c_type())
+        if memb.optional:
+            val = mcgen('''{
+            if _ffi.has_%(memb_name)s {
+                Some(%(val)s)
+            } else {
+                None
+            }
+}''',
+                        memb_name=rs_name(memb.name, protect=False),
+                        val=val)
+
+        ret += mcgen('''
+        %(cfg)s
+        let %(snake_memb_name)s = %(val)s;
+''',
+                     cfg=memb.ifcond.rsgen(),
+                     snake_memb_name=to_snake_case(memb_name),
+                     memb_name=memb_name,
+                     val=val)
+
+    if variants:
+        arms = ''
+        assert isinstance(variants.tag_member.type, QAPISchemaEnumType)
+        for variant in variants.variants:
+            typ = variant.type
+            if typ.name == 'q_empty':
+                memb = ''
+            else:
+                ptr = True
+                is_list = False
+                memb = to_snake_case(rs_name(variant.name))
+                if typ.name.endswith('-wrapper'):
+                    memb = '_ffi.u.%s.data' % memb
+                    wrap_ty = list(typ.members)[0].type.c_type()
+                    (ptr, _, is_list, _) = rs_ctype_parse(wrap_ty)
+                else:
+                    memb = '&_ffi.u.%s' % memb
+                if ptr:
+                    memb = '%s as *const _' % memb
+                    if is_list:
+                        memb = 'NewPtr(%s)' % memb
+                    memb = 'from_qemu_none(%s)' % memb
+                memb = '(%s)' % memb
+            arms += mcgen('''
+%(cfg)s
+%(enum)s::%(variant)s => { %(rs_name)sVariant::%(variant)s%(memb)s },
+''',
+                          cfg=variant.ifcond.rsgen(),
+                          enum=rs_name(variants.tag_member.type.name),
+                          memb=memb,
+                          variant=to_camel_case(rs_name(variant.name)),
+                          rs_name=rs_name(name))
+        ret += mcgen('''
+        let u = match _ffi.%(tag)s {
+            %(arms)s
+            _ => panic!("Variant with invalid tag"),
+        };
+''',
+                     tag=rs_name(variants.tag_member.name),
+                     arms=arms)
+        memb_names.append('u')
+
+    ret += mcgen('''
+            Self { %(memb_names)s }
+        }
+}
+''',
+                 memb_names=', '.join(memb_names))
+    return ret
+
+
+def gen_struct_members(members: List[QAPISchemaObjectTypeMember]) -> str:
+    ret = ''
+    for memb in members:
+        typ = rs_type(memb.type.c_type(), '', optional=memb.optional, box=True)
+        ret += mcgen('''
+    %(cfg)s
+    pub %(rs_name)s: %(rs_type)s,
+''',
+                     cfg=memb.ifcond.rsgen(),
+                     rs_type=typ,
+                     rs_name=to_snake_case(rs_name(memb.name)))
+    return ret
+
+
+def gen_rs_object(name: str,
+                  ifcond: QAPISchemaIfCond,
+                  base: Optional[QAPISchemaObjectType],
+                  members: List[QAPISchemaObjectTypeMember],
+                  variants: Optional[QAPISchemaVariants]) -> str:
+    if name in objects_seen:
+        return ''
+
+    if variants:
+        members = [m for m in members
+                   if m.name != variants.tag_member.name]
+
+    ret = ''
+    objects_seen.add(name)
+
+    if variants:
+        ret += gen_rs_variants(name, ifcond, variants)
+
+    ret += mcgen('''
+
+%(cfg)s
+#[derive(Clone, Debug)]
+pub struct %(rs_name)s {
+''',
+                 cfg=ifcond.rsgen(),
+                 rs_name=rs_name(name))
+
+    if base:
+        if not base.is_implicit():
+            ret += mcgen('''
+    // Members inherited:
+''',
+                         c_name=base.c_name())
+        base_members = base.members
+        if variants:
+            base_members = [m for m in base.members
+                            if m.name != variants.tag_member.name]
+        ret += gen_struct_members(base_members)
+        if not base.is_implicit():
+            ret += mcgen('''
+    // Own members:
+''')
+
+    ret += gen_struct_members(members)
+
+    if variants:
+        ret += mcgen('''
+    pub u: %(rs_type)sVariant,
+''', rs_type=rs_name(name))
+    ret += mcgen('''
+}
+''')
+
+    ret += gen_rs_object_from_qemu(name, ifcond, base, members, variants)
+    ret += gen_rs_object_to_qemu(name, ifcond, base, members, variants)
+    return ret
+
+
+def gen_rs_alternate_from_qemu(name: str,
+                               ifcond: QAPISchemaIfCond,
+                               variants: Optional[QAPISchemaVariants]) -> str:
+    arms = ''
+    for var in variants.variants:
+        qtype = to_camel_case(var.type.alternate_qtype()[6:].lower())
+        ptr = var.type.c_unboxed_type().endswith(POINTER_SUFFIX)
+        memb = 'ffi.u.%s' % rs_name(var.name)
+        if not ptr:
+            memb = '&' + memb
+        arms += mcgen('''
+        %(cfg)s
+        QType::%(qtype)s => {
+            Self::%(kind)s(from_qemu_none(%(memb)s as *const _))
+        }
+''',
+                      qtype=qtype,
+                      cfg=var.ifcond.rsgen(),
+                      kind=to_camel_case(rs_name(var.name)),
+                      memb=memb)
+
+    ret = mcgen('''
+
+%(cfg)s
+impl FromQemuPtrFull<*mut qapi_ffi::%(rs_name)s> for %(rs_name)s {
+    unsafe fn from_qemu_full(ffi: *mut qapi_ffi::%(rs_name)s) -> Self {
+        let ret = from_qemu_none(ffi as *const _);
+        qapi_ffi::qapi_free_%(name)s(ffi);
+        ret
+    }
+}
+
+%(cfg)s
+impl FromQemuPtrNone<*const qapi_ffi::%(rs_name)s> for %(rs_name)s {
+    unsafe fn from_qemu_none(ffi: *const qapi_ffi::%(rs_name)s) -> Self {
+        let ffi = &*ffi;
+
+        match ffi.r#type {
+            %(arms)s
+            _ => panic!()
+        }
+    }
+}
+''',
+                cfg=ifcond.rsgen(),
+                name=rs_name(name, protect=False),
+                rs_name=rs_name(name),
+                arms=arms)
+    return ret
+
+
+def gen_rs_alternate_to_qemu(name: str,
+                             ifcond: QAPISchemaIfCond,
+                             variants: Optional[QAPISchemaVariants],
+                             lifetime: str) -> str:
+    arms_none = ''
+    arms_full = ''
+    for var in variants.variants:
+        if var.type.name == 'q_empty':
+            continue
+        ptr = var.type.c_unboxed_type().endswith(POINTER_SUFFIX)
+        val = 'val.0' if ptr else 'unsafe { *(val.0.to() as *const _) }'
+        stor = '(val.1)' if var.type.c_type().endswith(POINTER_SUFFIX) else ''
+        qtype = var.type.alternate_qtype()[6:].lower()
+        arms_none += mcgen('''
+        %(cfg)s
+        Self::%(memb_name)s(val) => {
+            let val = val.to_qemu_none();
+            (
+                QType::%(qtype)s,
+                qapi_ffi::%(rs_name)sUnion { %(ffi_memb_name)s: %(val)s },
+                %(rs_name)sStorage::%(memb_name)s%(stor)s
+            )
+        }
+''',
+                           rs_name=rs_name(name),
+                           cfg=var.ifcond.rsgen(),
+                           memb_name=to_camel_case(rs_name(var.name)),
+                           ffi_memb_name=rs_name(var.name),
+                           qtype=to_camel_case(qtype),
+                           val=val,
+                           stor=stor)
+        val = 'val' if ptr else '*val'
+        free = '' if ptr else 'ffi::g_free(val as *mut _);'
+        arms_full += mcgen('''
+        %(cfg)s
+        Self::%(memb_name)s(val) => {
+            let val = val.to_qemu_full();
+            let ret = (QType::%(qtype)s, qapi_ffi::%(rs_name)sUnion {
+                %(ffi_memb_name)s: %(val)s
+            } );
+            %(free)s
+            ret
+        }
+''',
+                           rs_name=rs_name(name),
+                           cfg=var.ifcond.rsgen(),
+                           memb_name=to_camel_case(rs_name(var.name)),
+                           ffi_memb_name=rs_name(var.name),
+                           qtype=to_camel_case(qtype),
+                           val=val,
+                           free=free)
+
+    memb_none = mcgen('''
+    let (r#type, u, stor) = match self {
+        %(arms_none)s
+    };
+''', arms_none=arms_none)
+    memb_full = mcgen('''
+    let (r#type, u) = match self {
+        %(arms_full)s
+    };
+''', arms_full=arms_full)
+    ffi_memb = ['r#type', 'u']
+    return mcgen('''
+
+%(cfg)s
+impl QemuPtrDefault for %(rs_name)s {
+    type QemuType = *mut qapi_ffi::%(rs_name)s;
+}
+
+%(cfg)s
+impl<'a> ToQemuPtr<'a, *mut qapi_ffi::%(rs_name)s> for %(rs_name)s {
+    // Additional boxing of storage needed due to recursive types
+    type Storage = (Box<qapi_ffi::%(rs_name)s>, Box<%(rs_name)sStorage%(lt)s>);
+
+    #[inline]
+    fn to_qemu_none(&'a self)
+    -> Stash<'a, *mut qapi_ffi::%(rs_name)s, %(rs_name)s> {
+        %(memb_none)s
+        let mut box_ = Box::new(qapi_ffi::%(rs_name)s { %(ffi_memb)s });
+
+        Stash(&mut *box_, (box_, Box::new(stor)))
+    }
+
+    #[inline]
+    fn to_qemu_full(&self) -> *mut qapi_ffi::%(rs_name)s {
+        unsafe {
+            %(memb_full)s
+            let ptr = ffi::g_malloc0(
+                std::mem::size_of::<%(rs_name)s>()) as *mut _;
+            *ptr = qapi_ffi::%(rs_name)s { %(ffi_memb)s };
+            ptr
+        }
+    }
+}
+''',
+                 cfg=ifcond.rsgen(),
+                 rs_name=rs_name(name),
+                 lt=lifetime,
+                 ffi_memb=', '.join(ffi_memb),
+                 memb_none=memb_none,
+                 memb_full=memb_full)
+
+
+def gen_rs_alternate(name: str,
+                     ifcond: QAPISchemaIfCond,
+                     variants: Optional[QAPISchemaVariants]) -> str:
+    if name in objects_seen:
+        return ''
+
+    ret = ''
+    objects_seen.add(name)
+
+    ret += mcgen('''
+%(cfg)s
+#[derive(Clone, Debug)]
+pub enum %(rs_name)s {
+''',
+                 cfg=ifcond.rsgen(),
+                 rs_name=rs_name(name))
+
+    for var in variants.variants:
+        if var.type.name == 'q_empty':
+            continue
+        ret += mcgen('''
+        %(cfg)s
+        %(mem_name)s(%(rs_type)s),
+''',
+                     cfg=var.ifcond.rsgen(),
+                     rs_type=rs_type(var.type.c_unboxed_type(), ''),
+                     mem_name=to_camel_case(rs_name(var.name)))
+
+    membs = ''
+    lifetime = ''
+    for var in variants.variants:
+        var_name = to_camel_case(rs_name(var.name))
+        type_name = var.type.name
+        if type_name == 'q_empty':
+            continue
+        if not var.type.c_type().endswith(POINTER_SUFFIX):
+            membs += mcgen('''
+    %(cfg)s
+    %(var_name)s,
+''',
+                           cfg=var.ifcond.rsgen(),
+                           var_name=var_name)
+        else:
+            lifetime = "<'a>"
+            c_type = var.type.c_type()
+            ptr_ty = rs_ffitype(c_type)
+            membs += mcgen('''
+    %(cfg)s
+    %(var_name)s(<%(rs_type)s as ToQemuPtr<'a, %(ptr_ty)s>>::Storage),
+''',
+                           cfg=var.ifcond.rsgen(),
+                           var_name=var_name,
+                           rs_type=rs_type(c_type, ''),
+                           ptr_ty=ptr_ty)
+    ret += mcgen('''
+}
+
+%(cfg)s
+pub enum %(rs_name)sStorage%(lt)s {
+    %(membs)s
+}
+''',
+                 cfg=ifcond.rsgen(),
+                 rs_name=rs_name(name),
+                 lt=lifetime,
+                 membs=membs)
+
+    ret += gen_rs_alternate_from_qemu(name, ifcond, variants)
+    ret += gen_rs_alternate_to_qemu(name, ifcond, variants, lifetime)
+
+    return ret
+
+
+def gen_rs_enum(name: str, ifcond: QAPISchemaIfCond) -> str:
+    return mcgen('''
+
+%(cfg)s
+pub type %(rs_name)s = qapi_ffi::%(ffi_name)s;
+
+%(cfg)s
+impl_to_qemu_scalar_boxed!(%(rs_name)s);
+
+%(cfg)s
+impl_from_qemu_none_scalar!(%(rs_name)s);
+''',
+                 cfg=ifcond.rsgen(),
+                 rs_name=rs_name(name),
+                 ffi_name=rs_name(name))
+
+
+class QAPISchemaGenRsTypeVisitor(QAPISchemaRsVisitor):
+
+    def __init__(self, prefix: str) -> None:
+        super().__init__(prefix, 'qapi-types')
+
+    def visit_begin(self, schema: QAPISchema) -> None:
+        # don'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::{QNull, QObject};
+use crate::qapi_ffi;
+
+'''))
+
+    def visit_array_type(self,
+                         name: str,
+                         info: Optional[QAPISourceInfo],
+                         ifcond: QAPISchemaIfCond,
+                         element_type: QAPISchemaType) -> None:
+        typ = rs_type(name, qapi_ns='')
+        scalar = False
+        if name[:-4] in {'number',
+                         'int',
+                         'int8',
+                         'int16',
+                         'int32',
+                         'int64',
+                         'uint8',
+                         'uint16',
+                         'uint32',
+                         'uint64',
+                         'size',
+                         'bool'}:
+            scalar = True
+        if isinstance(element_type, QAPISchemaEnumType):
+            scalar = True
+
+        self._gen.add(mcgen('''
+%(cfg)s
+mod %(mod)s_module {
+    use super::*;
+
+    vec_type!(%(rs)s, %(ffi)s, qapi_free_%(name)s, %(scalar)i);
+}
+
+%(cfg)s
+pub use %(mod)s_module::*;
+''',
+                            cfg=ifcond.rsgen(),
+                            name=rs_name(name, protect=False),
+                            mod=rs_name(name).lower(),
+                            ffi=rs_name(name),
+                            rs=typ,
+                            scalar=scalar))
+
+    def visit_object_type(self,
+                          name: str,
+                          info: Optional[QAPISourceInfo],
+                          ifcond: QAPISchemaIfCond,
+                          features: List[QAPISchemaFeature],
+                          base: Optional[QAPISchemaObjectType],
+                          members: List[QAPISchemaObjectTypeMember],
+                          variants: Optional[QAPISchemaVariants]) -> None:
+        if name.startswith('q_'):
+            return
+        self._gen.add(gen_rs_object(name, ifcond, base, members, variants))
+
+    def visit_enum_type(self,
+                        name: str,
+                        info: Optional[QAPISourceInfo],
+                        ifcond: QAPISchemaIfCond,
+                        features: List[QAPISchemaFeature],
+                        members: List[QAPISchemaEnumMember],
+                        prefix: Optional[str]) -> None:
+        self._gen.add(gen_rs_enum(name, ifcond))
+
+    def visit_alternate_type(self,
+                             name: str,
+                             info: QAPISourceInfo,
+                             ifcond: QAPISchemaIfCond,
+                             features: List[QAPISchemaFeature],
+                             variants: QAPISchemaVariants) -> None:
+        self._gen.add(gen_rs_alternate(name, ifcond, variants))
+
+
+def gen_rs_types(schema: QAPISchema, output_dir: str, prefix: str) -> None:
+    vis = QAPISchemaGenRsTypeVisitor(prefix)
+    schema.visit(vis)
+    vis.write(output_dir)
-- 
2.33.0.113.g6c40894d24



  parent reply	other threads:[~2021-09-07 12:56 UTC|newest]

Thread overview: 74+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-09-07 12:19 [RFC v3 00/32] Rust binding for QAPI and qemu-ga QMP handler examples marcandre.lureau
2021-09-07 12:19 ` [RFC v3 01/32] RFC: docs: add supported host CPUs section marcandre.lureau
2021-09-07 12:33   ` Peter Maydell
2021-09-13 11:32     ` Marc-André Lureau
2021-09-13 11:46       ` Peter Maydell
2021-09-07 12:19 ` [RFC v3 02/32] build-sys: add HAVE_IPPROTO_MPTCP marcandre.lureau
2021-09-08 12:01   ` Markus Armbruster
2021-09-13 13:02   ` Paolo Bonzini
2021-09-07 12:19 ` [RFC v3 03/32] scripts/qapi: teach c_param_type() to return const argument type marcandre.lureau
2021-09-08 12:10   ` Markus Armbruster
2021-09-08 14:33     ` Marc-André Lureau
2021-09-07 12:19 ` [RFC v3 04/32] glib-compat: add G_SIZEOF_MEMBER marcandre.lureau
2021-09-08 12:16   ` Markus Armbruster
2021-09-08 13:49     ` Marc-André Lureau
2021-09-07 12:19 ` [RFC v3 05/32] scripts/qapi: add QAPISchemaVisitor.visit_module_end marcandre.lureau
2021-09-08 12:26   ` Markus Armbruster
2021-09-07 12:19 ` [RFC v3 06/32] scripts/qapi: add a CABI module marcandre.lureau
2021-09-07 12:19 ` [RFC v3 07/32] scripts/qapi: generate CABI dump for C types marcandre.lureau
2021-09-07 12:19 ` [RFC v3 08/32] tests: build qapi-cabi (C ABI dump) marcandre.lureau
2021-09-07 12:19 ` [RFC v3 09/32] build-sys: add i686 cpu target marcandre.lureau
2021-09-08 13:45   ` Peter Maydell
2021-09-07 12:19 ` [RFC v3 10/32] build-sys: add --with-rust{-target} & basic build infrastructure marcandre.lureau
2021-09-08 14:00   ` Peter Maydell
2021-09-08 14:21     ` Marc-André Lureau
2021-09-07 12:19 ` [RFC v3 11/32] build-sys: add a cargo-wrapper script marcandre.lureau
2021-09-07 12:19 ` [RFC v3 12/32] rust: provide a common crate for QEMU marcandre.lureau
2021-09-10  1:18   ` Alistair Francis
2021-09-10  7:43     ` Marc-André Lureau
2021-09-13 17:11   ` Paolo Bonzini
2021-09-14 11:34     ` Marc-André Lureau
2021-09-07 12:19 ` [RFC v3 13/32] rust: use vendored-sources marcandre.lureau
2021-09-08 15:38   ` Ian Jackson
2021-09-08 15:47     ` Marc-André Lureau
2021-09-08 15:55       ` Ian Jackson
2021-09-08 16:15         ` Marc-André Lureau
2021-09-08 16:22           ` Peter Maydell
2021-09-08 16:22           ` Ian Jackson
2021-09-08 16:20     ` Marc-André Lureau
2021-09-08 16:29       ` Ian Jackson
2021-09-08 16:34         ` Marc-André Lureau
2021-09-08 16:50           ` Ian Jackson
2021-09-08 19:33             ` Marc-André Lureau
2021-09-09 16:02   ` Peter Maydell
2021-09-09 16:29     ` Marc-André Lureau
2021-09-09 16:53       ` Daniel P. Berrangé
2021-09-09 17:04         ` Ian Jackson
2021-09-13 14:21       ` Paolo Bonzini
2021-09-09 16:49     ` Daniel P. Berrangé
2021-09-09 17:02       ` Ian Jackson
2021-09-07 12:19 ` [RFC v3 14/32] scripts/qapi: add QAPISchemaIfCond.rsgen() marcandre.lureau
2021-09-08 12:33   ` Markus Armbruster
2021-09-08 14:06     ` Marc-André Lureau
2021-09-07 12:19 ` [RFC v3 15/32] scripts/qapi: strip trailing whitespaces marcandre.lureau
2021-09-07 12:19 ` [RFC v3 16/32] scripts/qapi: add Rust FFI bindings generation marcandre.lureau
2021-09-07 12:19 ` [RFC v3 17/32] scripts/qapi: learn to generate ABI dump for Rust FFI marcandre.lureau
2021-09-07 12:19 ` [RFC v3 18/32] tests: generate Rust bindings marcandre.lureau
2021-09-07 12:19 ` [RFC v3 19/32] tests: check Rust and C CABI diffs marcandre.lureau
2021-09-07 12:19 ` marcandre.lureau [this message]
2021-09-07 12:19 ` [RFC v3 21/32] tests/rust: build a common library, checking bindings compile marcandre.lureau
2021-09-07 12:19 ` [RFC v3 22/32] qga: build qapi-cabi binary (ABI from C) marcandre.lureau
2021-09-07 12:19 ` [RFC v3 23/32] qga/rust: build and link an empty static library marcandre.lureau
2021-09-07 12:19 ` [RFC v3 24/32] qga/rust: generate QGA QAPI types FFI bindings marcandre.lureau
2021-09-07 12:19 ` [RFC v3 25/32] qga/rust: build a qga-cabi-rs executable (ABI from Rust) marcandre.lureau
2021-09-07 12:19 ` [RFC v3 26/32] qga/rust: check the Rust C binding marcandre.lureau
2021-09-07 12:19 ` [RFC v3 27/32] qga/rust: build high-level Rust QAPI types marcandre.lureau
2021-09-07 12:19 ` [RFC v3 28/32] qga/rust: implement get-host-name in Rust (example) marcandre.lureau
2021-09-07 12:19 ` [RFC v3 29/32] qga/rust: implement {get,set}-vcpus " marcandre.lureau
2021-09-07 12:19 ` [RFC v3 30/32] tests/vm: add Rust to FreeBSD VM marcandre.lureau
2021-09-07 12:19 ` [RFC v3 31/32] tests/vm: bump fedora VM to f32 marcandre.lureau
2021-09-07 12:19 ` [RFC v3 32/32] tests/vm: add Rust to Fedora marcandre.lureau
2021-09-08 13:22 ` [RFC v3 00/32] Rust binding for QAPI and qemu-ga QMP handler examples Markus Armbruster
2021-09-08 13:55   ` Marc-André Lureau
2021-09-09 10:31     ` Markus Armbruster
2021-09-09 15:22       ` Marc-André Lureau

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210907121943.3498701-21-marcandre.lureau@redhat.com \
    --to=marcandre.lureau@redhat.com \
    --cc=armbru@redhat.com \
    --cc=berrange@redhat.com \
    --cc=pbonzini@redhat.com \
    --cc=qemu-devel@nongnu.org \
    --cc=stefanha@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.