qemu-devel
[Top][All Lists]
Advanced

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

Re: [Qemu-devel] [PATCH v5 02/20] qapi.py: add a simple #ifdef condition


From: Marc-André Lureau
Subject: Re: [Qemu-devel] [PATCH v5 02/20] qapi.py: add a simple #ifdef conditional
Date: Wed, 07 Sep 2016 14:23:42 +0000

Hi

On Wed, Sep 7, 2016 at 5:40 PM Markus Armbruster <address@hidden> wrote:

> Markus Armbruster <address@hidden> writes:
>
> > Marc-André Lureau <address@hidden> writes:
> >
> >> Hi
> >>
> >> On Tue, Sep 6, 2016 at 7:58 PM Markus Armbruster <address@hidden>
> wrote:
> >>
> >>> QAPI language design issues, copying Eric.
> >>>
> >>> Marc-André Lureau <address@hidden> writes:
> >>>
> >>> > Hi
> >>> >
> >>> > On Tue, Sep 6, 2016 at 5:00 PM Markus Armbruster <address@hidden>
> >>> wrote:
> [...]
> >>> >> Yet another option is to add 'ifdef' keys to the definitions
> >>> >> themselves, i.e.
> >>> >>
> >>> >>     { 'command': 'query-gic-capabilities', 'returns':
> ['GICCapability'],
> >>> >>       'ifdef': 'TARGET_ARM' }
> >>> >>
> >>> >
> >>> > That's the worst of all options imho, as it makes it hard to filter
> out
> >>> > unions/enums, ex:
> >>> >
> >>> >  @ -3446,8 +3466,10 @@
> >>> >                                         'testdev': 'ChardevCommon',
> >>> >                                         'stdio'  : 'ChardevStdio',
> >>> >                                         'console': 'ChardevCommon',
> >>> > +#ifdef CONFIG_SPICE
> >>> >                                         'spicevmc' :
> >>> 'ChardevSpiceChannel',
> >>> >                                         'spiceport' :
> 'ChardevSpicePort',
> >>> > +#endif
> >>> >                                         'vc'     : 'ChardevVC',
> >>> >                                         'ringbuf': 'ChardevRingbuf',
> >>>
> >>> Point taken.
> >>>
> >>> Fixable the same way as always when a definition needs to grow
> >>> additional properties, but the syntax only provides a single value:
> make
> >>> that single value an object, and the old non-object value syntactic
> >>> sugar for the equivalent object value.  We've previously discussed this
> >>> technique in the context of giving command arguments default values.
> >>> I'm not saying this is what we should do here, only pointing out it's
> >>> possible.
> >>>
> >>
> >> Ok, but let's find something, if possible simple and convenient, no?
> >
> > I agree it needs to be simple, both the interface (QAPI language) and
> > the implementation.  However, I don't like "first past the post".  I
> > prefer to explore the design space a bit.
> >
> > So let me explore the "add 'ifdef' keys to definitions" corner of the
> > QAPI language design space.
> >
> > Easily done for top-level definitions, because they're all JSON objects.
> > Could even add it to the include directive if we wanted to.
> >
> > Less easily done for enumeration, struct, union and alternate members.
> > Note that command and event arguments specified inline are a special
> > case of struct members.
> >
> > The "can't specify additional stuff for struct members" problem isn't
> > new.  We hacked around it to specify "optional": encode it into the
> > member name.  Doesn't scale.  We need to solve the problem to be able to
> > specify default values, and we already decided how: have an JSON object
> > instead of a mere JSON string, make the string syntax sugar for {
> > 'type': STRING }.  See commit 6b5abc7 and the discussion in qemu-devel
> > leading up to it.  For consistency, we'll do it for union and alternate
> > members, too.
> >
> > That leaves just enumeration members.  The same technique applies.
> >
> > If I remember correctly, we only need conditional commands right now, to
> > avoid regressing query-commands.  The more complicated member work could
> > be done later.
>
> To gauge whether this idea is practical, I implemented key 'if' for
> commands.  It's just a sketch, and has a number of issues, which I
> marked FIXME.
>

Tbh, I think it is scratching the surface doing it only for commands.
Furthermore, it's pushing the burdden of conditional generation at a
different level, at the C level, which is probably not so nice if some day
it targets a different language.

If the main issue is using #\w* for preprocessing, we could use something
else like #! (oh no someone else did it, there are many ex where comments
are not just comments), or .\w* (.ifdef / .endif etc).

As you said, the main goal of this patch was to disable commands from the
qapi-dispatch that are currently disabled with the middle mode.

Do you think it would be reasonable to accept this simple approach for now,
until we find a better more suitable solution? After all, this is internal,
we can iterate. Or should we drop this patch for this series? I don't like
seeing it delayed by weeks...


>
> I ported qmp-commands.hx's #if to qapi-schema.json.  The TARGET_FOO are
> poisoned, so I commented them out.  There's a CONFIG_SPICE left, which
> will do for testing.
>
> I also turned key 'gen': false into 'if': false.  Possibly a bad idea.
>
> Anyway, diffstat isn't bad:
>
>  docs/qapi-code-gen.txt                     | 14 ++++++-----
>  qapi-schema.json                           | 15 ++++++++---
>  qapi/introspect.json                       |  2 +-
>  scripts/qapi-commands.py                   | 12 +++++++--
>  scripts/qapi-introspect.py                 | 22 ++++++++++------
>  scripts/qapi.py                            | 40
> ++++++++++++++++++++++--------
>  tests/qapi-schema/type-bypass-bad-gen.err  |  2 +-
>  tests/qapi-schema/type-bypass-bad-gen.json |  4 +--
>  8 files changed, 77 insertions(+), 34 deletions(-)
>
> diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
> index de298dc..93e99d8 100644
> --- a/docs/qapi-code-gen.txt
> +++ b/docs/qapi-code-gen.txt
> @@ -423,7 +423,7 @@ part of a Client JSON Protocol command.  The 'data'
> member is optional
>  and defaults to {} (an empty dictionary).  If present, it must be the
>  string name of a complex type, or a dictionary that declares an
>  anonymous type with the same semantics as a 'struct' expression, with
> -one exception noted below when 'gen' is used.
> +one exception noted below when 'if': false is used.
>
>  The 'returns' member describes what will appear in the "return" member
>  of a Client JSON Protocol reply on successful completion of a command.
> @@ -431,8 +431,8 @@ The member is optional from the command declaration;
> if absent, the
>  "return" member will be an empty dictionary.  If 'returns' is present,
>  it must be the string name of a complex or built-in type, a
>  one-element array containing the name of a complex or built-in type,
> -with one exception noted below when 'gen' is used.  Although it is
> -permitted to have the 'returns' member name a built-in type or an
> +with one exception noted below when 'if':false is used.  Although it
> +is permitted to have the 'returns' member name a built-in type or an
>  array of built-in types, any command that does this cannot be extended
>  to return additional information in the future; thus, new commands
>  should strongly consider returning a dictionary-based type or an array
> @@ -475,16 +475,18 @@ arguments for the user's function out of an input
> QDict, calls the
>  user's function, and if it succeeded, builds an output QObject from
>  its return value.
>
> +FIXME document 'if'
> +
>  In rare cases, QAPI cannot express a type-safe representation of a
>  corresponding Client JSON Protocol command.  You then have to suppress
> -generation of a marshalling function by including a key 'gen' with
> +generation of a marshalling function by including a key 'if' with
>  boolean value false, and instead write your own function.  Please try
>  to avoid adding new commands that rely on this, and instead use
>  type-safe unions.  For an example of this usage:
>
>   { 'command': 'netdev_add',
> -   'data': {'type': 'str', 'id': 'str'},
> -   'gen': false }
> +   'if': false,
> +   'data': {'type': 'str', 'id': 'str'} }
>
>  Normally, the QAPI schema is used to describe synchronous exchanges,
>  where a response is expected.  But in some cases, the action of a
> diff --git a/qapi-schema.json b/qapi-schema.json
> index c4f3674..ad0559e 100644
> --- a/qapi-schema.json
> +++ b/qapi-schema.json
> @@ -1269,7 +1269,9 @@
>  #
>  # Since: 0.14.0
>  ##
> -{ 'command': 'query-spice', 'returns': 'SpiceInfo' }
> +{ 'command': 'query-spice',
> +  'if': 'defined(CONFIG_SPICE)',
> +  'returns': 'SpiceInfo' }
>
>  ##
>  # @BalloonInfo:
> @@ -2355,6 +2357,7 @@
>  # Since: 2.5
>  ##
>  { 'command': 'dump-skeys',
> +#  'if': 'defined(TARGET_S390X)',
>    'data': { 'filename': 'str' } }
>
>  ##
> @@ -2380,8 +2383,8 @@
>  #          If @type is not a valid network backend, DeviceNotFound
>  ##
>  { 'command': 'netdev_add',
> -  'data': {'type': 'str', 'id': 'str'},
> -  'gen': false }                # so we can get the additional arguments
> +  'if': false,                  # so we can get the additional arguments
> +  'data': {'type': 'str', 'id': 'str'} }
>
>  ##
>  # @netdev_del:
> @@ -4455,6 +4458,7 @@
>  # Since: 2.1
>  ##
>  { 'command': 'rtc-reset-reinjection' }
> +#  'if': 'defined(TARGET_I386)'
>
>  # Rocker ethernet network switch
>  { 'include': 'qapi/rocker.json' }
> @@ -4525,7 +4529,10 @@
>  #
>  # Since: 2.6
>  ##
> -{ 'command': 'query-gic-capabilities', 'returns': ['GICCapability'] }
> +{ 'command': 'query-gic-capabilities',
> +#  'if': 'defined(TARGET_ARM)',
> +  'returns': ['GICCapability']
> +}
>
>  ##
>  # CpuInstanceProperties
> diff --git a/qapi/introspect.json b/qapi/introspect.json
> index 3fd81fb..b8f421a 100644
> --- a/qapi/introspect.json
> +++ b/qapi/introspect.json
> @@ -46,7 +46,7 @@
>  ##
>  { 'command': 'query-qmp-schema',
>    'returns': [ 'SchemaInfo' ],
> -  'gen': false }                # just to simplify qmp_query_json()
> +  'if': false }                 # just to simplify qmp_query_json()
>
>  ##
>  # @SchemaMetaType
> diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py
> index a06a2c4..f34e4cc 100644
> --- a/scripts/qapi-commands.py
> +++ b/scripts/qapi-commands.py
> @@ -215,9 +215,13 @@ class QAPISchemaGenCommandVisitor(QAPISchemaVisitor):
>          self._visited_ret_types = None
>
>      def visit_command(self, name, info, arg_type, ret_type,
> -                      gen, success_response, boxed):
> -        if not gen:
> +                      genif, success_response, boxed):
> +        if genif is False:
>              return
> +        pp_if = gen_pp_if(genif)
> +        pp_endif = gen_pp_endif(genif)
> +        self.decl += pp_if
> +        self.defn += pp_if      # FIXME blank lines are off
>          self.decl += gen_command_decl(name, arg_type, boxed, ret_type)
>          if ret_type and ret_type not in self._visited_ret_types:
>              self._visited_ret_types.add(ret_type)
> @@ -226,7 +230,11 @@ class QAPISchemaGenCommandVisitor(QAPISchemaVisitor):
>              self.decl += gen_marshal_decl(name)
>          self.defn += gen_marshal(name, arg_type, boxed, ret_type)
>          if not middle_mode:
> +            self._regy += pp_if
>              self._regy += gen_register_command(name, success_response)
> +            self._regy += pp_endif
> +        self.decl += pp_endif
> +        self.defn += pp_endif
>
>
>  middle_mode = False
> diff --git a/scripts/qapi-introspect.py b/scripts/qapi-introspect.py
> index 541644e..0d8cec7 100644
> --- a/scripts/qapi-introspect.py
> +++ b/scripts/qapi-introspect.py
> @@ -30,8 +30,6 @@ def to_json(obj, level=0):
>          ret = '{' + ', '.join(elts) + '}'
>      else:
>          assert False                # not implemented
> -    if level == 1:
> -        ret = '\n' + ret
>      return ret
>
>
> @@ -69,8 +67,15 @@ class QAPISchemaGenIntrospectVisitor(QAPISchemaVisitor):
>  extern const char %(c_name)s[];
>  ''',
>                            c_name=c_name(name))
> -        lines = to_json(jsons).split('\n')
> -        c_string = '\n    '.join([to_c_string(line) for line in lines])
> +        c_string = '"["'
> +        for i in jsons:
> +            js, genif = i
> +            # FIXME blank lines are off
> +            c_string += gen_pp_if(genif or True)
> +            c_string += '\n    ' + to_c_string(to_json(js) + ', ')
> +            c_string += gen_pp_endif(genif or True)
> +        # FIXME trailing comma (JSON sucks)
> +        c_string += '\n    "]"'
>          self.defn = mcgen('''
>  const char %(c_name)s[] = %(c_string)s;
>  ''',
> @@ -111,12 +116,12 @@ const char %(c_name)s[] = %(c_string)s;
>              return '[' + self._use_type(typ.element_type) + ']'
>          return self._name(typ.name)
>
> -    def _gen_json(self, name, mtype, obj):
> +    def _gen_json(self, name, mtype, obj, genif=True):
>          if mtype not in ('command', 'event', 'builtin', 'array'):
>              name = self._name(name)
>          obj['name'] = name
>          obj['meta-type'] = mtype
> -        self._jsons.append(obj)
> +        self._jsons.append((obj, genif))
>
>      def _gen_member(self, member):
>          ret = {'name': member.name, 'type': self._use_type(member.type)}
> @@ -154,12 +159,13 @@ const char %(c_name)s[] = %(c_string)s;
>                                      for m in variants.variants]})
>
>      def visit_command(self, name, info, arg_type, ret_type,
> -                      gen, success_response, boxed):
> +                      genif, success_response, boxed):
>          arg_type = arg_type or self._schema.the_empty_object_type
>          ret_type = ret_type or self._schema.the_empty_object_type
>          self._gen_json(name, 'command',
>                         {'arg-type': self._use_type(arg_type),
> -                        'ret-type': self._use_type(ret_type)})
> +                        'ret-type': self._use_type(ret_type)},
> +                       genif)
>
>      def visit_event(self, name, info, arg_type, boxed):
>          arg_type = arg_type or self._schema.the_empty_object_type
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index 21bc32f..6c0cf9f 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
> @@ -698,7 +698,13 @@ def check_keys(expr_elem, meta, required,
> optional=[]):
>              raise QAPIExprError(info,
>                                  "Unknown key '%s' in %s '%s'"
>                                  % (key, meta, name))
> -        if (key == 'gen' or key == 'success-response') and value is not
> False:
> +        if (key == 'if'
> +            and value is not False and not isinstance(value, str)):
> +            # FIXME update error message
> +            raise QAPIExprError(info,
> +                                "'%s' of %s '%s' should only use false
> value"
> +                                % (key, meta, name))
> +        if (key == 'success-response') and value is not False:
>              raise QAPIExprError(info,
>                                  "'%s' of %s '%s' should only use false
> value"
>                                  % (key, meta, name))
> @@ -737,7 +743,7 @@ def check_exprs(exprs):
>              add_struct(expr, info)
>          elif 'command' in expr:
>              check_keys(expr_elem, 'command', [],
> -                       ['data', 'returns', 'gen', 'success-response',
> 'boxed'])
> +                       ['data', 'returns', 'if', 'success-response',
> 'boxed'])
>              add_name(expr['command'], info, 'command')
>          elif 'event' in expr:
>              check_keys(expr_elem, 'event', [], ['data', 'boxed'])
> @@ -838,7 +844,7 @@ class QAPISchemaVisitor(object):
>          pass
>
>      def visit_command(self, name, info, arg_type, ret_type,
> -                      gen, success_response, boxed):
> +                      genif, success_response, boxed):
>          pass
>
>      def visit_event(self, name, info, arg_type, boxed):
> @@ -1180,8 +1186,8 @@ class QAPISchemaAlternateType(QAPISchemaType):
>
>
>  class QAPISchemaCommand(QAPISchemaEntity):
> -    def __init__(self, name, info, arg_type, ret_type, gen,
> success_response,
> -                 boxed):
> +    def __init__(self, name, info, arg_type, ret_type,
> +                 genif, success_response, boxed):
>          QAPISchemaEntity.__init__(self, name, info)
>          assert not arg_type or isinstance(arg_type, str)
>          assert not ret_type or isinstance(ret_type, str)
> @@ -1189,7 +1195,7 @@ class QAPISchemaCommand(QAPISchemaEntity):
>          self.arg_type = None
>          self._ret_type_name = ret_type
>          self.ret_type = None
> -        self.gen = gen
> +        self.genif = genif
>          self.success_response = success_response
>          self.boxed = boxed
>
> @@ -1216,7 +1222,7 @@ class QAPISchemaCommand(QAPISchemaEntity):
>      def visit(self, visitor):
>          visitor.visit_command(self.name, self.info,
>                                self.arg_type, self.ret_type,
> -                              self.gen, self.success_response, self.boxed)
> +                              self.genif, self.success_response,
> self.boxed)
>
>
>  class QAPISchemaEvent(QAPISchemaEntity):
> @@ -1419,17 +1425,20 @@ class QAPISchema(object):
>          name = expr['command']
>          data = expr.get('data')
>          rets = expr.get('returns')
> -        gen = expr.get('gen', True)
> +        genif = expr.get('if', True)
>          success_response = expr.get('success-response', True)
>          boxed = expr.get('boxed', False)
>          if isinstance(data, OrderedDict):
> +            # TODO apply genif to the implicit object type
>              data = self._make_implicit_object_type(
>                  name, info, 'arg', self._make_members(data, info))
>          if isinstance(rets, list):
> +            # TODO apply genif to the implicit array type
> +            # TODO disjunction of all the genif
>              assert len(rets) == 1
>              rets = self._make_array_type(rets[0], info)
> -        self._def_entity(QAPISchemaCommand(name, info, data, rets, gen,
> -                                           success_response, boxed))
> +        self._def_entity(QAPISchemaCommand(name, info, data, rets,
> +                                           genif, success_response,
> boxed))
>
>      def _def_event(self, expr, info):
>          name = expr['event']
> @@ -1704,6 +1713,17 @@ def gen_params(arg_type, boxed, extra):
>      return ret
>
>
> +def gen_pp_if(cond):
> +    if cond is True:
> +        return ''
> +    return '\n#if ' + cond + '\n'
> +
> +
> +def gen_pp_endif(cond):
> +    if cond is True:
> +        return ''
> +    return '\n#endif  /* ' + cond + ' */\n'
> +
>  #
>  # Common command line parsing
>  #
> diff --git a/tests/qapi-schema/type-bypass-bad-gen.err
> b/tests/qapi-schema/type-bypass-bad-gen.err
> index a83c3c6..cca25f1 100644
> --- a/tests/qapi-schema/type-bypass-bad-gen.err
> +++ b/tests/qapi-schema/type-bypass-bad-gen.err
> @@ -1 +1 @@
> -tests/qapi-schema/type-bypass-bad-gen.json:2: 'gen' of command 'foo'
> should only use false value
> +tests/qapi-schema/type-bypass-bad-gen.json:2: 'if' of command 'foo'
> should only use false value
> diff --git a/tests/qapi-schema/type-bypass-bad-gen.json
> b/tests/qapi-schema/type-bypass-bad-gen.json
> index e8dec34..637b11f 100644
> --- a/tests/qapi-schema/type-bypass-bad-gen.json
> +++ b/tests/qapi-schema/type-bypass-bad-gen.json
> @@ -1,2 +1,2 @@
> -# 'gen' should only appear with value false
> -{ 'command': 'foo', 'gen': 'whatever' }
> +# 'if' should only appear with value false FIXME or str
> +{ 'command': 'foo', 'if': null }
>
-- 
Marc-André Lureau


reply via email to

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