Commit 7ce106a9 authored by Eric Blake's avatar Eric Blake Committed by Markus Armbruster
Browse files

qapi: Emit implicit structs in generated C



We already have several places that want to visit all the members
of an implicit object within a larger context (simple union variant,
event with anonymous data, command with anonymous arguments struct);
and will be adding another one soon (the ability to declare an
anonymous base for a flat union).  Having a C struct declared for
these implicit types, along with a visit_type_FOO_members() helper
function, will make for fewer special cases in our generator.

We do not, however, need qapi_free_FOO() or visit_type_FOO()
functions for implicit types, because they should not be used
directly outside of the generated code.  This is done by adding a
conditional in visit_object_type() for both qapi-types.py and
qapi-visit.py based on the object name.  The comparison of
"name.startswith('q_')" is a bit hacky (it's basically duplicating
what .is_implicit() already uses), but beats changing the signature
of the visit_object_type() callback to pass a new 'implicit' flag.
The hack should be temporary: we are considering adding a future
patch that consolidates the narrow visit_object_type(..., base,
local_members, variants) and visit_object_type_flat(...,
all_members, variants) [where different sets of information are
already broken out, and the QAPISchemaObjectType is no longer
available] into a broader visit_object_type(obj_type) [where the
visitor can query the needed fields from obj_type directly].

Also, now that we WANT to output C code for implicits, we no longer
need the visit_needed() filter, leaving 'q_empty' as the only object
still needing a special case.  Remember, 'q_empty' is the only
built-in generated object, which means that without a special case
it would be emitted in multiple files (the main qapi-types.h and in
qga-qapi-types.h) causing compilation failure due to redefinition.
But since it has no members, it's easier to just avoid an attempt to
visit that particular type; since gen_object() is called recursively,
we also prime the objects_seen set to cover any recursion into the
empty type.

The patch relies on the changed naming of implicit types in the
previous patch.  It is a bit unfortunate that the generated struct
names and visit_type_FOO_members() don't match normal naming
conventions, but it's not too bad, since they will only be used in
generated code.

The generated code grows substantially in size: the implicit
'-wrapper' types must be emitted in qapi-types.h before any union
can include an unboxed member of that type.  Arguably, the '-args'
types could be emitted in a private header for just qapi-visit.c
and qmp-marshal.c, rather than polluting qapi-types.h; but adding
complexity to the generator to split the output location according
to role doesn't seem worth the maintenance costs.

Signed-off-by: default avatarEric Blake <eblake@redhat.com>
Message-Id: <1458254921-17042-6-git-send-email-eblake@redhat.com>
Signed-off-by: default avatarMarkus Armbruster <armbru@redhat.com>
parent 7599697c
Loading
Loading
Loading
Loading
+11 −8
Original line number Diff line number Diff line
@@ -61,8 +61,7 @@ def gen_object(name, base, members, variants):
    ret = ''
    if variants:
        for v in variants.variants:
            if (isinstance(v.type, QAPISchemaObjectType) and
                    not v.type.is_implicit()):
            if isinstance(v.type, QAPISchemaObjectType):
                ret += gen_object(v.type.name, v.type.base,
                                  v.type.local_members, v.type.variants)

@@ -180,6 +179,8 @@ class QAPISchemaGenTypeVisitor(QAPISchemaVisitor):
        self._btin = None

    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.decl = ''
        self.defn = ''
        self._fwdecl = ''
@@ -196,11 +197,6 @@ class QAPISchemaGenTypeVisitor(QAPISchemaVisitor):
        self.decl = self._btin + self.decl
        self._btin = None

    def visit_needed(self, entity):
        # Visit everything except implicit objects
        return not (entity.is_implicit() and
                    isinstance(entity, QAPISchemaObjectType))

    def _gen_type_cleanup(self, name):
        self.decl += gen_type_cleanup_decl(name)
        self.defn += gen_type_cleanup(name)
@@ -229,10 +225,17 @@ class QAPISchemaGenTypeVisitor(QAPISchemaVisitor):
            self._gen_type_cleanup(name)

    def visit_object_type(self, name, info, base, members, variants):
        # Nothing to do for the special empty builtin
        if name == 'q_empty':
            return
        self._fwdecl += gen_fwd_object_or_array(name)
        self.decl += gen_object(name, base, members, variants)
        if base:
            self.decl += gen_upcast(name, base)
        # TODO Worth changing the visitor signature, so we could
        # directly use rather than repeat type.is_implicit()?
        if not name.startswith('q_'):
            # implicit types won't be directly allocated/freed
            self._gen_type_cleanup(name)

    def visit_alternate_type(self, name, info, variants):
+11 −12
Original line number Diff line number Diff line
@@ -215,13 +215,11 @@ out:


def gen_visit_object(name, base, members, variants):
    ret = gen_visit_object_members(name, base, members, variants)

    # FIXME: if *obj is NULL on entry, and visit_start_struct() assigns to
    # *obj, but then visit_type_FOO_members() fails, we should clean up *obj
    # rather than leaving it non-NULL. As currently written, the caller must
    # call qapi_free_FOO() to avoid a memory leak of the partial FOO.
    ret += mcgen('''
    return mcgen('''

void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error **errp)
{
@@ -245,8 +243,6 @@ out:
''',
                 c_name=c_name(name))

    return ret


class QAPISchemaGenVisitVisitor(QAPISchemaVisitor):
    def __init__(self):
@@ -268,11 +264,6 @@ class QAPISchemaGenVisitVisitor(QAPISchemaVisitor):
        self.decl = self._btin + self.decl
        self._btin = None

    def visit_needed(self, entity):
        # Visit everything except implicit objects
        return not (entity.is_implicit() and
                    isinstance(entity, QAPISchemaObjectType))

    def visit_enum_type(self, name, info, values, prefix):
        # Special case for our lone builtin enum type
        # TODO use something cleaner than existence of info
@@ -296,7 +287,15 @@ class QAPISchemaGenVisitVisitor(QAPISchemaVisitor):
            self.defn += defn

    def visit_object_type(self, name, info, base, members, variants):
        # Nothing to do for the special empty builtin
        if name == 'q_empty':
            return
        self.decl += gen_visit_members_decl(name)
        self.defn += gen_visit_object_members(name, base, members, variants)
        # TODO Worth changing the visitor signature, so we could
        # directly use rather than repeat type.is_implicit()?
        if not name.startswith('q_'):
            # only explicit types need an allocating visit
            self.decl += gen_visit_decl(name)
            self.defn += gen_visit_object(name, base, members, variants)

+0 −2
Original line number Diff line number Diff line
@@ -1000,7 +1000,6 @@ class QAPISchemaObjectType(QAPISchemaType):
        return self.name.startswith('q_')

    def c_name(self):
        assert not self.is_implicit()
        return QAPISchemaType.c_name(self)

    def c_type(self):
@@ -1008,7 +1007,6 @@ class QAPISchemaObjectType(QAPISchemaType):
        return c_name(self.name) + pointer_suffix

    def c_unboxed_type(self):
        assert not self.is_implicit()
        return c_name(self.name)

    def json_type(self):