qapi: Don't box struct branch of alternate

There's no reason to do two malloc's for an alternate type visiting
a QAPI struct; let's just inline the struct directly as the C union
branch of the struct.

Surprisingly, no clients were actually using the struct member prior
to this patch outside of the testsuite; an earlier patch in the series
added some testsuite coverage to make the effect of this patch more
obvious.

In qapi.py, c_type() gains a new is_unboxed flag to control when we
are emitting a C struct unboxed within the context of an outer
struct (different from our other two modes of usage with no flags
for normal local variable declarations, and with is_param for adding
'const' in a parameter list). I don't know if there is any more
pythonic way of collapsing the two flags into a single parameter,
as we never have a caller setting both flags at once.

Ultimately, we want to also unbox branches for QAPI unions, but as
that touches a lot more client code, it is better as separate
patches. But since unions and alternates share gen_variants(), I
had to hack in a way to test if we are visiting an alternate type
for setting the is_unboxed flag: look for a non-object branch.
This works because alternates have at least two branches, with at
most one object branch, while unions have only object branches.
The hack will go away in a later patch.

The generated code difference to qapi-types.h is relatively small:

| struct BlockdevRef {
| QType type;
| union { /* union tag is @type */
| void *data;
|- BlockdevOptions *definition;
|+ BlockdevOptions definition;
| char *reference;
| } u;
| };

The corresponding spot in qapi-visit.c calls visit_type_FOO(), which
first calls visit_start_struct() to allocate or deallocate the member
and handle a layer of {} from the JSON stream, then visits the
members. To peel off the indirection and the memory management that
comes with it, we inline this call, then suppress allocation /
deallocation by passing NULL to visit_start_struct(), and adjust the
member visit:

| switch ((*obj)->type) {
| case QTYPE_QDICT:
|- visit_type_BlockdevOptions(v, name, &(*obj)->u.definition, &err);
|+ visit_start_struct(v, name, NULL, 0, &err);
|+ if (err) {
|+ break;
|+ }
|+ visit_type_BlockdevOptions_fields(v, &(*obj)->u.definition, &err);
|+ error_propagate(errp, err);
|+ err = NULL;
|+ visit_end_struct(v, &err);
| break;
| case QTYPE_QSTRING:
| visit_type_str(v, name, &(*obj)->u.reference, &err);

The visit of non-object fields is unchanged.

Backports commit becceedc4d9bc1435099c90a0514945a89844d3a from qemu
This commit is contained in:
Eric Blake 2018-02-20 16:30:59 -05:00 committed by Lioncash
parent dc706426c9
commit 12dabf0640
No known key found for this signature in database
GPG key ID: 4E3C3CC1031BA9C7
3 changed files with 44 additions and 13 deletions

View file

@ -150,6 +150,14 @@ const int %(c_name)s_qtypes[QTYPE__MAX] = {
def gen_variants(variants):
# HACK: Determine if this is an alternate (at least one variant
# is not an object); unions have all branches as objects.
unboxed = False
for v in variants.variants:
if not isinstance(v.type, QAPISchemaObjectType):
unboxed = True
break
# FIXME: What purpose does data serve, besides preventing a union that
# has a branch named 'data'? We use it in qapi-visit.py to decide
# whether to bypass the switch statement if visiting the discriminator
@ -170,7 +178,7 @@ def gen_variants(variants):
ret += mcgen('''
%(c_type)s %(c_name)s;
''',
c_type=typ.c_type(),
c_type=typ.c_type(is_unboxed=unboxed),
c_name=c_name(var.name))
ret += mcgen('''

View file

@ -199,12 +199,15 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s *obj, Error
def gen_visit_alternate(name, variants):
ret = ''
promote_int = 'true'
for var in variants.variants:
if var.type.alternate_qtype() == 'QTYPE_QINT':
promote_int = 'false'
if isinstance(var.type, QAPISchemaObjectType):
ret += gen_visit_fields_decl(var.type)
ret = mcgen('''
ret += mcgen('''
void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error **errp)
{
@ -220,17 +223,35 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error
}
switch ((*obj)->type) {
''',
c_name=c_name(name), promote_int=promote_int)
c_name=c_name(name), promote_int=promote_int)
for var in variants.variants:
ret += mcgen('''
case %(case)s:
visit_type_%(c_type)s(v, name, &(*obj)->u.%(c_name)s, &err);
break;
''',
case=var.type.alternate_qtype(),
c_type=var.type.c_name(),
c_name=c_name(var.name))
case=var.type.alternate_qtype())
if isinstance(var.type, QAPISchemaObjectType):
ret += mcgen('''
visit_start_struct(v, name, NULL, 0, &err);
if (err) {
break;
}
visit_type_%(c_type)s_fields(v, &(*obj)->u.%(c_name)s, &err);
error_propagate(errp, err);
err = NULL;
visit_end_struct(v, &err);
''',
c_type=var.type.c_name(),
c_name=c_name(var.name))
else:
ret += mcgen('''
visit_type_%(c_type)s(v, name, &(*obj)->u.%(c_name)s, &err);
''',
c_type=var.type.c_name(),
c_name=c_name(var.name))
ret += mcgen('''
break;
''')
ret += mcgen('''
default:

View file

@ -827,7 +827,7 @@ class QAPISchemaVisitor(object):
class QAPISchemaType(QAPISchemaEntity):
def c_type(self, is_param=False):
def c_type(self, is_param=False, is_unboxed=False):
return c_name(self.name) + pointer_suffix
def c_null(self):
@ -860,7 +860,7 @@ class QAPISchemaBuiltinType(QAPISchemaType):
def c_name(self):
return self.name
def c_type(self, is_param=False):
def c_type(self, is_param=False, is_unboxed=False):
if is_param and self.name == 'str':
return 'const ' + self._c_type_name
return self._c_type_name
@ -894,7 +894,7 @@ class QAPISchemaEnumType(QAPISchemaType):
# See QAPISchema._make_implicit_enum_type()
return self.name.endswith('Kind')
def c_type(self, is_param=False):
def c_type(self, is_param=False, is_unboxed=False):
return c_name(self.name)
def member_names(self):
@ -991,9 +991,11 @@ class QAPISchemaObjectType(QAPISchemaType):
assert not self.is_implicit()
return QAPISchemaType.c_name(self)
def c_type(self, is_param=False):
def c_type(self, is_param=False, is_unboxed=False):
assert not self.is_implicit()
return QAPISchemaType.c_type(self)
if is_unboxed:
return c_name(self.name)
return c_name(self.name) + pointer_suffix
def json_type(self):
return 'object'