qemu-devel
[Top][All Lists]
Advanced

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

Re: [PATCH 11/11] qdev: Rework array properties based on list visitor


From: Markus Armbruster
Subject: Re: [PATCH 11/11] qdev: Rework array properties based on list visitor
Date: Mon, 23 Oct 2023 13:41:38 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/28.2 (gnu/linux)

Kevin Wolf <kwolf@redhat.com> writes:

> Am 14.10.2023 um 08:36 hat Markus Armbruster geschrieben:
>> Kevin Wolf <kwolf@redhat.com> writes:
>> 
>> > Am 22.09.2023 um 17:05 hat Markus Armbruster geschrieben:
>> >> Kevin Wolf <kwolf@redhat.com> writes:
>> >> 
>> >> > Until now, array properties are actually implemented with a hack that
>> >> > uses multiple properties on the QOM level: a static "foo-len" property
>> >> > and after it is set, dynamically created "foo[i]" properties.
>> >> >
>> >> > In external interfaces (-device on the command line and device_add in
>> >> > QMP), this interface was broken by commit f3558b1b ('qdev: Base object
>> >> > creation on QDict rather than QemuOpts') because QDicts are unordered
>> >> > and therefore it could happen that QEMU tried to set the indexed
>> >> > properties before setting the length, which fails and effectively makes
>> >> > array properties inaccessible. In particular, this affects the 'ports'
>> >> > property of the 'rocker' device.
>> >> >
>> >> > This patch reworks the external interface so that instead of using a
>> >> > separate top-level property for the length and for each element, we use
>> >> > a single true array property that accepts a list value. In the external
>> >> > interfaces, this is naturally expressed as a JSON list and makes array
>> >> > properties accessible again.
>> >> >
>> >> > Creating an array property on the command line without using JSON format
>> >> > is currently not possible. This could be fixed by switching from
>> >> > QemuOpts to a keyval parser, which however requires consideration of the
>> >> > compatibility implications.
>> >> >
>> >> > All internal users of devices with array properties go through
>> >> > qdev_prop_set_array() at this point, so updating it takes care of all of
>> >> > them.
>> >> >
>> >> > Resolves: https://gitlab.com/qemu-project/qemu/-/issues/1090
>> >> > Fixes: f3558b1b763683bb877f7dd5b282469cdadc65c3
>> >> > Signed-off-by: Kevin Wolf <kwolf@redhat.com>
>
>> >> > +{
>> >> > +    return (Property) {
>> >> > +        .info = parent_prop->arrayinfo,
>> >> > +        .name = name,
>> >> > +        /*
>> >> > +         * This ugly piece of pointer arithmetic sets up the offset so
>> >> > +         * that when the underlying release hook calls 
>> >> > qdev_get_prop_ptr
>> >> > +         * they get the right answer despite the array element not 
>> >> > actually
>> >> > +         * being inside the device struct.
>> >> > +         */
>> >> > +        .offset = elem - (char *) obj,
>> >> 
>> >> Isn't this is undefined behavior?
>> >
>> > It should be at least less illegal than the old version of it, which did
>> > the calculation on void * and still worked in practice...
>> >
>> > But yes, strictly speaking, it's probably undefined behaviour. I can
>> > calculate on uintptr_t instead, and then it should be defined here.
>> >
>> > The QOM counterpart object_field_prop_ptr() is probably still undefined
>> > because it calculates on a pointer and I think the spec allows casting
>> > back to a pointer only after we've applied the offset so that we stay in
>> > the same object with pointer arithmetics.
>> 
>> We should not have to waste time on worrying about compilers using UB
>> fine print against us, but sadly we do.
>> 
>> I'm not objecting to your code, I'm merely pointing out a potential time
>> bomb.  In a programming environment that has embraced time bombing with
>> gusto.
>
> While I'm touching this part, I'll change it to uintptr_t. I'll leave
> the counterpart for later.
>
>> >> Delete the space between (char *) and obj.
>> >> 
>> >> > +    };
>> >> > +}
>> >> > +
>> >> > +/*
>> >> > + * Object property release callback for array properties: We call the 
>> >> > underlying
>> >> > + * element's property release hook for each element.
>> >> > + *
>> >> > + * Note that it is the responsibility of the individual device's 
>> >> > deinit to free
>> >> > + * the array proper.
>> >> 
>> >> What is a device's "deinit"?  Is it the unrealize() method?  The
>> >> instance_finalize() method?
>> >
>> > Who knows? I only moved this comment.
>> 
>> Opportunity to improve it.  Not a demand.
>
> If you have a better version of it? My guess is only a guess, so I'd
> avoid putting it in the code without understanding if there are reasons
> why it has to be a specific place, or why a specific place doesn't work.
>
> Leaving it vague is probably better than being specific, but potentially
> wrong.

Fair.

>> > My guess is that it doesn't really matter as long as _something_ frees
>> > the array when unplugging the device.
>> >
>> >> >   */
>> >> > -static void array_element_release(Object *obj, const char *name, void 
>> >> > *opaque)
>> >> > +static void release_prop_array(Object *obj, const char *name, void 
>> >> > *opaque)
>> >> >  {
>> >> > -    ArrayElementProperty *p = opaque;
>> >> > -    if (p->release) {
>> >> > -        p->release(obj, name, opaque);
>> >> > +    Property *prop = opaque;
>> >> > +    uint32_t *alenptr = object_field_prop_ptr(obj, prop);
>> >> > +    void **arrayptr = (void *)obj + prop->arrayoffset;
>> >> 
>> >> I'd call these @plen and @pelts, but that's clearly a matter of taste.
>> >
>> > I just kept the old names in set_prop_array() and used the same names in
>> > new functions to stay consistent. But to be honest, @plen and @pelts
>> > would be equally confusing to me.
>> >
>> > My own choice would probably have been something like array_len and
>> > array_data (if you want to know that it's a pointer, look at its type).
>> >
>> >> > +    char *elem = *arrayptr;
>> >> > +    int i;
>> >> > +
>> >> > +    for (i = 0; i < *alenptr; i++) {
>> >> > +        Property elem_prop = array_elem_prop(obj, prop, name, elem);
>> >> > +        prop->arrayinfo->release(obj, NULL, &elem_prop);
>> >> > +        elem += prop->arrayfieldsize;
>> >> >      }
>> >> > -    g_free(p->propname);
>> >> > -    g_free(p);
>> >> >  }
>> >> >  
>> >> > -static void set_prop_arraylen(Object *obj, Visitor *v, const char 
>> >> > *name,
>> >> > -                              void *opaque, Error **errp)
>> >> > +/*
>> >> > + * Setter for an array property. This sets both the array length 
>> >> > (which is
>> >> > + * technically the property field in the object) and the array itself 
>> >> > (a pointer
>> >> > + * to which is stored in the additional field described by 
>> >> > prop->arrayoffset).
>> >> > + */
>> >> > +static void set_prop_array(Object *obj, Visitor *v, const char *name,
>> >> > +                           void *opaque, Error **errp)
>> >> >  {
>> >> > -    /* Setter for the property which defines the length of a
>> >> > -     * variable-sized property array. As well as actually setting the
>> >> > -     * array-length field in the device struct, we have to create the
>> >> > -     * array itself and dynamically add the corresponding properties.
>> >> > -     */
>> >> > +    ERRP_GUARD();
>> >> > +
>> >> 
>> >> Drop the blank line.
>> >> 
>> >> >      Property *prop = opaque;
>> >> >      uint32_t *alenptr = object_field_prop_ptr(obj, prop);
>> >> >      void **arrayptr = (void *)obj + prop->arrayoffset;
>> >> > -    void *eltptr;
>> >> > -    const char *arrayname;
>> >> > -    int i;
>> >> > +    GenericList *list, *elem, *next;
>> >> > +    const size_t list_elem_size = sizeof(*list) + prop->arrayfieldsize;
>> >> 
>> >> This can be smaller than the size of the QAPI-generated list type, since
>> >> the compiler may add padding.  Does it matter?
>> >
>> > If it happens in practice, it does matter. Do we have any cleaner way to
>> > get the element size without knowing the content of the list?
>> >
>> > I expect that because GenericList only contains a single pointer, the
>> > rest should have natural alignment
>> 
>> Yes, GenericList's size and alignment should match a pointer's:
>> 
>>     typedef struct GenericList {
>>         struct GenericList *next;
>>         char padding[];
>>     } GenericList;
>> 
>> >                                    and therefore the compiler shouldn't
>> > have any reason to insert padding.
>> 
>> The actual list will look like
>> 
>>     struct FOOList {
>>         FOOList *next;
>>         FOOTYPE value;
>>     }
>> 
>> where FOOTYPE is some QAPI-generated type.  No padding as long as
>> FOOTYPE's alignment divides the pointer size.  I figure that's true for
>> our current targets and generated QAPI types (currently pointers,
>> double, bool, or integers up to 64 bits).
>
> I'm quite confused about the whole alignment stuff at the moment, but
> after looking some more at the code, I think I just realised one
> important thing: We're actually not dealing with QAPI-generated types
> here, but with custom visitors in QOM property setters.

Hmm.

The patch is about qdev array properties, which are special QOM
properties.

A QOM property encapsulates arbitray C data within a QOM object.  Its
get() method enables getting this data with an output visitor, and its
set() method enables setting it with an input visitor.

When the C data is a QAPI type T, set() and get() simply call
visit_type_T().  Example: property_get_uint8_ptr(),
property_set_uint8_ptr().

When it isn't, set() and get() do what qapi/visitor.h calls a "virtual
walk".  Example: the array property we're discussing here.

However, there's a catch: set_prop_array() doesn't visit the property's
C data (a heap-allocated array of ELEMENT-TYPE) directly.  Instead, it
visits a heap-allocated GenericList of ELEMENT-TYPE, which it later
copies to the array.

Here's the visit:

    /* Read the whole input into a temporary list */
    elem = list;
    while (elem) {
        Property elem_prop = array_elem_prop(obj, prop, name, elem->padding);
--->    prop->arrayinfo->set(obj, v, NULL, &elem_prop, errp);
        if (*errp) {
            ok = false;
            goto out_obj;
        }
        (*alenptr)++;
        elem = visit_next_list(v, elem, list_elem_size);
    }

The call marked ---> visits an element at address &elem->padding.

This address is pointer-aligned.  What if the element type requires more
alignment?  Unusual, but certainly not impossible.  For instance, SSE
and AVX vectors require 16 or even 32 byte alignment.

Or am I confused?

>                                                         The netdev one
> specifically (which is used in the array property of the 'rocker'
> device) uses NICPeers (not a pointer to NICPeers!), which is much
> larger.
>
> But now I'm wondering, FOOList doesn't even exist as a type. We're
> calculating list_elem_size only to pass it to visit_start_list(), which
> doesn't have FOOList either. What does the visitor do with it?
>
> It seems it doesn't do anything with it except using it as the size to
> malloc() elements. And then we (the array property code in this patch)
> use &elem->padding as the element pointer, directly from GenericList,
> not any specific FOOList. So I think this just means that we never
> insert any padding and what the compiler would do with a hypothetical
> FOOList, which doesn't even have to exist as a type, doesn't matter.
>
> Am I missing something or does this mean that the code is actually fine?
>
>> > If you think this is not enough and there is no other way to get the
>> > size of the list elements, we might have to generate packed structs for
>> > the QAPI list types (which are really only two pointers, so not much to
>> > lose when we do that).
>> 
>> Could we assert the element type's alignment divides GenericList's size?
>> Not here, obviously, but in DEFINE_PROP_ARRAY(), where we can use
>> __alignof__(_arraytype).
>
> A bit tricky because we're in the middle of a literal, but I suppose
> with the GCC extension we could change one random element of the struct
> to something like:
>
>     ({ QEMU_BUILD_BUG_ON(...); real_value; })
>
> Of course, if my thoughts above were right, this might not actually be
> needed.
>
>> We could also play with attribute aligned to ensure GenericList's size is
>> safe, but I doubt that's worthwhile.
>> 
>> >> > +    char *elemptr;
>> >> > +    bool ok = true;
>> >> >  
>> >> >      if (*alenptr) {
>> >> >          error_setg(errp, "array size property %s may not be set more 
>> >> > than once",
>> >> >                     name);
>> >> >          return;
>> >> >      }
>> >> > -    if (!visit_type_uint32(v, name, alenptr, errp)) {
>> >> > +
>> >> > +    if (!visit_start_list(v, name, &list, list_elem_size, errp)) {
>> >> >          return;
>> >> >      }
>> >> > -    if (!*alenptr) {
>> >> > +
>> >> > +    /* Read the whole input into a temporary list */
>> >> > +    elem = list;
>> >> > +    while (elem) {
>> >> > +        Property elem_prop = array_elem_prop(obj, prop, name, 
>> >> > elem->padding);
>> >> > +        prop->arrayinfo->set(obj, v, NULL, &elem_prop, errp);
>> >> > +        if (*errp) {
>> >> > +            ok = false;
>> >> > +            goto out_obj;
>> >> > +        }
>> >> > +        (*alenptr)++;
>> >> > +        elem = visit_next_list(v, elem, list_elem_size);
>> >> > +    }
>> >> > +
>> >> > +    ok = visit_check_list(v, errp);
>> >> > +out_obj:
>> >> > +    visit_end_list(v, (void**) &list);
>> >> > +
>> >> > +    if (!ok) {
>> >> > +        for (elem = list; elem; elem = next) {
>> >> > +            next = elem->next;
>> >> > +            g_free(elem);
>> >> > +        }
>> >> 
>> >> We consume the list even on error.  It's too late in my day for me to
>> >> see why that's proper.
>> >
>> > Who else would free it otherwise?
>> >
>> > This is pretty much the same as the generated list visitors do.
>> 
>> Help me out: point me to the precedence you have in mind.
>
> Any QAPI generated list visiting function should do. As an example, I'm
> looking at visit_type_Qcow2BitmapInfoList():
>
>     ...
>     ok = visit_check_list(v, errp);
> out_obj:
>     visit_end_list(v, (void **)obj);
>     if (!ok && visit_is_input(v)) {
>         qapi_free_Qcow2BitmapInfoList(*obj);
>         *obj = NULL;
>     }
>     return ok;
> }
>
> On failure, it calls qapi_free_Qcow2BitmapInfoList().
>
> What's probably wrong in my code is that to be a full equivalent, it
> also needs to free things behind pointers that are owned by elem, i.e.
> call prop->arrayinfo->release().
>
> In practice, it doesn't currently make a difference because none of the
> types used with array properties actually have a release callback.
> Should still be fixed, of course.

Glad I got sufficiently confused to ask ;)

>> >> >          return;
>> >> >      }
>> >> >  
>> >> > -    /* DEFINE_PROP_ARRAY guarantees that name should start with this 
>> >> > prefix;
>> >> > -     * strip it off so we can get the name of the array itself.
>> >> > +    /*
>> >> > +     * Now that we know how big the array has to be, move the data 
>> >> > over to a
>> >> > +     * linear array and free the temporary list.
>> >> >       */
>> >> > -    assert(strncmp(name, PROP_ARRAY_LEN_PREFIX,
>> >> > -                   strlen(PROP_ARRAY_LEN_PREFIX)) == 0);
>> >> > -    arrayname = name + strlen(PROP_ARRAY_LEN_PREFIX);
>> >> > +    *arrayptr = g_malloc_n(*alenptr, prop->arrayfieldsize);
>> >> > +    elemptr = *arrayptr;
>> >> > +    for (elem = list; elem; elem = next) {
>> >> > +        memcpy(elemptr, elem->padding, prop->arrayfieldsize);
>> >> > +        elemptr += prop->arrayfieldsize;
>> >> > +        next = elem->next;
>> >> > +        g_free(elem);
>> >> > +    }
>> >> > +}
>> >> >  
>> >> > -    /* Note that it is the responsibility of the individual device's 
>> >> > deinit
>> >> > -     * to free the array proper.
>> >> > -     */
>> >> > -    *arrayptr = eltptr = g_malloc0(*alenptr * prop->arrayfieldsize);
>> >> > -    for (i = 0; i < *alenptr; i++, eltptr += prop->arrayfieldsize) {
>> >> > -        char *propname = g_strdup_printf("%s[%d]", arrayname, i);
>> >> > -        ArrayElementProperty *arrayprop = g_new0(ArrayElementProperty, 
>> >> > 1);
>> >> > -        arrayprop->release = prop->arrayinfo->release;
>> >> > -        arrayprop->propname = propname;
>> >> > -        arrayprop->prop.info = prop->arrayinfo;
>> >> > -        arrayprop->prop.name = propname;
>> >> > -        /* This ugly piece of pointer arithmetic sets up the offset so
>> >> > -         * that when the underlying get/set hooks call 
>> >> > qdev_get_prop_ptr
>> >> > -         * they get the right answer despite the array element not 
>> >> > actually
>> >> > -         * being inside the device struct.
>> >> > -         */
>> >> > -        arrayprop->prop.offset = eltptr - (void *)obj;
>> >> > -        assert(object_field_prop_ptr(obj, &arrayprop->prop) == eltptr);
>> >> > -        object_property_add(obj, propname,
>> >> > -                            arrayprop->prop.info->name,
>> >> > -                            field_prop_getter(arrayprop->prop.info),
>> >> > -                            field_prop_setter(arrayprop->prop.info),
>> >> > -                            array_element_release,
>> >> > -                            arrayprop);
>> >> > +static void get_prop_array(Object *obj, Visitor *v, const char *name,
>> >> > +                           void *opaque, Error **errp)
>> >> > +{
>> >> > +    ERRP_GUARD();
>> >> > +
>> >> 
>> >> Drop the blank line.
>> >> 
>> >> > +    Property *prop = opaque;
>> >> > +    uint32_t *alenptr = object_field_prop_ptr(obj, prop);
>> >> > +    void **arrayptr = (void *)obj + prop->arrayoffset;
>> >> > +    char *elem = *arrayptr;
>> >> > +    GenericList *list;
>> >> > +    const size_t list_elem_size = sizeof(*list) + prop->arrayfieldsize;
>> >> > +    int i;
>> >> > +
>> >> > +    if (!visit_start_list(v, name, &list, list_elem_size, errp)) {
>> >> > +        return;
>> >> >      }
>> >> > +
>> >> > +    for (i = 0; i < *alenptr; i++) {
>> >> > +        Property elem_prop = array_elem_prop(obj, prop, name, elem);
>> >> > +        prop->arrayinfo->get(obj, v, NULL, &elem_prop, errp);
>> >> > +        if (*errp) {
>> >> > +            goto out_obj;
>> >> > +        }
>> >> > +        elem += prop->arrayfieldsize;
>> >> > +    }
>> >> > +
>> >> 
>> >> You neglect to call visit_check_list().
>> >
>> > It is documented to be intended for input visitors only. Do we need it
>> > with an output visitor?
>> 
>> Help me out: where is that documented?
>
> In struct Visitor:
>
>     /* Optional; intended for input visitors */
>     bool (*check_list)(Visitor *v, Error **errp);

I think this comment tries to say "this is intended to be used by input
visitors", which doesn't quite imply "only input visitors use this".

> And indeed, the existing output vistors don't implement it.

It's implemented by all the input visitors.

Fine print: the forward visitor is the same kind of visitor as the one
it wraps, either input or output.  It implements check_list()
unconditionally.

> Admittedly, the public visit_check_list() is less clear:
>
>     /*
>      * Prepare for completing a list visit.
>      *
>      * On failure, store an error through @errp.  Can happen only when @v
>      * is an input visitor.
>      *
>      * Return true on success, false on failure.
>      *
>      * Should be called prior to visit_end_list() if all other
>      * intermediate visit steps were successful, to allow the visitor one
>      * last chance to report errors.  May be skipped on a cleanup path,
>      * where there is no need to check for further errors.
>      */
>     bool visit_check_list(Visitor *v, Error **errp);
>
> If you prefer, I can add it here just to be sure. Instead of having real
> error handling, assert(ok) should be enough.

We either tighten the public visitor contract to make calling
visit_check_list() clearly optional when you know you're not using an
input visitor.

Or we call it here, too.  Probably simpler.

>> >> > +out_obj:
>> >> > +    visit_end_list(v, (void**) &list);
>> >> >  }
>
> Kevin




reply via email to

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