qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [PATCH v10 10/13] qapi: Don't box struct branch of alte


From: Eric Blake
Subject: Re: [Qemu-devel] [PATCH v10 10/13] qapi: Don't box struct branch of alternate
Date: Tue, 16 Feb 2016 12:53:13 -0700
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Thunderbird/38.5.0

On 02/16/2016 12:07 PM, Markus Armbruster wrote:
> Eric Blake <address@hidden> writes:
> 
>> 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; some testsuite coverage is added to avoid future
>> regressions.
>>
>> Ultimately, we want to do the same treatment for QAPI unions, but
>> as that touches a lot more client code, it is better as a separate
>> patch.  So in the meantime, I had to hack in a way to test if we
>> are visiting an alternate type, within qapi-types:gen_variants();
>> the hack is possible because an earlier patch guaranteed that all
>> alternates have at least two branches, with at most one object
>> branch; the hack will go away in a later patch.
> 
> Suggest:
> 
>   Ultimately, we want to do the same treatment for QAPI unions, but as
>   that touches a lot more client code, it is better as a separate patch.
>   The two share gen_variants(), and I had to hack in a way to test if we
>   are visiting an alternate type: look for a non-object branch.  This
>   works because alternates have at least two branches, with at most one
>   object branch, and unions have only object branches.  The hack will go
>   away in a later patch.

Nicer.

> 
>> The generated code difference to qapi-types.h is relatively small,
>> made possible by a new c_type(is_member) parameter in qapi.py:
> 
> Let's drop the "made possible" clause here.

I was trying to document the new is_member parameter somewhere in the
commit message, but I can agree that it could be its own paragraph
rather than a clause here.


> 
> This is in addition to visit_type_BlockdevOptions(), so we need another
> name.
> 
> I can't quite see how the function is tied to alternates, though.
> 

I'm open to naming suggestions.  Also, I noticed that we have
'visit_type_FOO_fields' and 'visit_type_implicit_FOO'; so I didn't know
whether to name it 'visit_type_alternate_FOO' or
'visit_type_FOO_alternate'; it gets more interesting later in the series
when I completely delete 'visit_type_implicit_FOO'.

>>
>> and use it like this:
>>
>> |     switch ((*obj)->type) {
>> |     case QTYPE_QDICT:
>> |-        visit_type_BlockdevOptions(v, name, &(*obj)->u.definition, &err);
>> |+        visit_type_alternate_BlockdevOptions(v, name, 
>> &(*obj)->u.definition, &err);
> 
> Let's compare the two functions.  First visit_type_BlockdevOptions():
> 

> 
> Now visit_type_alternate_BlockdevOptions(), with differences annotated
> with //:
> 
>     static void visit_type_alternate_BlockdevOptions(Visitor *v,
>                                     const char *name,
>                                     BlockdevOptions *obj, // one * less

Yep, because we no longer need to malloc a second object, so we no
longer need to propagate a change to obj back to the caller.

>                                     Error **errp)
>     {
>         Error *err = NULL;
> 
>         visit_start_struct(v, name, NULL, 0, &err); // NULL instead of &obj
>                                                     // suppresses malloc
>         if (err) {
>             goto out;
>         }
>         // null check dropped (obj can't be null)
>         visit_type_BlockdevOptions_fields(v, obj, &err);

Also, here we pass 'obj'; visit_type_FOO() had to pass '*obj' (again,
because we have one less level of indirection, and 7/13 reduced the
indirection required in visit_type_FOO_fields()).

>         // visit_start_union() + switch dropped
>         error_propagate(errp, err);
>         err = NULL;
>         visit_end_struct(v, &err);
>     out:
>         error_propagate(errp, err);
>     }
> 
> Why can we drop visit_start_union() + switch?

visit_start_union() is dropped because its only purpose was to determine
if the dealloc visitor needs to visit the default branch. When we had a
separate allocation, we did not want to visit the branch if the
discriminator was not successfully parsed, because otherwise we would
dereference NULL.  But now that we don't have a second pointer
allocation, we no longer have anything to dealloc, and we can no longer
dereference NULL. Explained better in 12/13, where I delete
visit_start_union() altogether.  But maybe I could keep it in this patch
in the meantime, to minimize confusion.

Dropped switch, on the other hand, looks to be a genuine bug.  Eeek.
That should absolutely be present, and it proves that our testsuite is
not strong enough for not catching me on it.

And now that you've made me think about it, maybe I have yet another
idea.  Right now, we've split the visit of local members into
visit_type_FOO_fields(), while leaving the variant members to be visited
in visit_type_FOO()

visit_type_FOO_fields() is static, so we can change it without impacting
the entire tree; I could add a bool parameter to that function, and write:

visit_type_FOO() {
  visit_start_struct(obj)
  visit_type_FOO_fields(, false)
  visit_end_struct()
}

visit_type_FOO_fields(, wrap) {
  if (wrap) {
    visit_start_struct(NULL)
  }
  visit local fields
  visit variant fields
  if (wrap) {
    visit_end_struct()
  }
}

and for the variant type that includes FOO as one of its options,

visit_type_VARIANT() {
  allocate (currently spelled visit_start_implicit_struct, but see 13/13)
  switch (type) {
  case QTYPE_QINT:
    visit_type_int(); break
  case QTYPE_QDICT:
    visit_type_FOO_fields(, true); break
  }
  visit_end
}

or not even create a new function, and just have:


visit_type_FOO() {
  visit_start_struct(obj)
  visit_type_FOO_fields()
  visit_end_struct()
}

visit_type_FOO_fields() {
  visit local fields
  visit variant fields
}

and for the variant type,

visit_type_VARIANT() {
  allocate (currently spelled visit_start_implicit_struct, but see 13/13)
  switch (type) {
  case QTYPE_QINT:
    visit_type_int(); break
  case QTYPE_QDICT:
    visit_start_struct(NULL)
    visit_type_FOO_fields()
    visit_end_struct()
    break
  }
  visit_end_implicit
}

where the logic gets a bit more complicated to do correct error handling.


>> @@ -984,8 +984,10 @@ 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_member=False):
>>          assert not self.is_implicit()
>> +        if is_member:
>> +            return c_name(self.name)
>>          return QAPISchemaType.c_type(self)
> 
> To understand this, you have to peel off OO abstraction:
> QAPISchemaType.c_type(self) returns c_name(self.name) + pointer_suffix.
> 
> I think we should keep it peeled off in the source code.

By this, you mean I should do:

def c_type(self, is_param=False, is_member=False):
    assert not self.is_implicit()
    if is_member:
        return c_name(self.name)
    return c_name(self.name) + pointer_suffix

?

or that I should hoist the suppression of pointer_suffix into the parent
class?

> 
>>
>>      def json_type(self):
> [tests/ diff looks good]

good for what they caught, but they didn't catch enough :)

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org

Attachment: signature.asc
Description: OpenPGP digital signature


reply via email to

[Prev in Thread] Current Thread [Next in Thread]