From patchwork Tue Jul 23 13:03:18 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kevin Wolf X-Patchwork-Id: 261089 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 47B752C008C for ; Tue, 23 Jul 2013 23:37:03 +1000 (EST) Received: from localhost ([::1]:52703 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1V1cKu-0007Iu-94 for incoming@patchwork.ozlabs.org; Tue, 23 Jul 2013 09:09:04 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:54471) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1V1cFz-0000QQ-VY for qemu-devel@nongnu.org; Tue, 23 Jul 2013 09:04:01 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1V1cFu-0007Tx-BY for qemu-devel@nongnu.org; Tue, 23 Jul 2013 09:03:59 -0400 Received: from mx1.redhat.com ([209.132.183.28]:14975) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1V1cFu-0007Tp-11 for qemu-devel@nongnu.org; Tue, 23 Jul 2013 09:03:54 -0400 Received: from int-mx02.intmail.prod.int.phx2.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by mx1.redhat.com (8.14.4/8.14.4) with ESMTP id r6ND3rjK005362 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK) for ; Tue, 23 Jul 2013 09:03:53 -0400 Received: from dhcp-200-207.str.redhat.com (ovpn-116-56.ams2.redhat.com [10.36.116.56]) by int-mx02.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id r6ND3U6I008625; Tue, 23 Jul 2013 09:03:51 -0400 From: Kevin Wolf To: qemu-devel@nongnu.org Date: Tue, 23 Jul 2013 15:03:18 +0200 Message-Id: <1374584606-5615-11-git-send-email-kwolf@redhat.com> In-Reply-To: <1374584606-5615-1-git-send-email-kwolf@redhat.com> References: <1374584606-5615-1-git-send-email-kwolf@redhat.com> X-Scanned-By: MIMEDefang 2.67 on 10.5.11.12 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 209.132.183.28 Cc: kwolf@redhat.com, armbru@redhat.com, stefanha@redhat.com, lcapitulino@redhat.com Subject: [Qemu-devel] [PATCH 10/18] qapi: Anonymous unions X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org The discriminator for anonymous unions is the data type. This allows to have a union type that allows both of these: { 'file': 'my_existing_block_device_id' } { 'file': { 'filename': '/tmp/mydisk.qcow2', 'read-only': true } } Unions like this are specified in the schema with an empty dict as discriminator. For this example you could take: { 'union': 'BlockRef', 'discriminator': {}, 'data': { 'definition': 'BlockOptions' 'reference': 'str' } } { 'type': 'ExampleObject', 'data: { 'file': 'BlockRef' } } Signed-off-by: Kevin Wolf Reviewed-by: Eric Blake --- docs/qapi-code-gen.txt | 25 ++++++++++++++++++++++++ include/qapi/qmp/qobject.h | 1 + include/qapi/visitor-impl.h | 2 ++ include/qapi/visitor.h | 3 +++ qapi/qapi-visit-core.c | 9 +++++++++ qapi/qmp-input-visitor.c | 14 ++++++++++++++ qobject/qjson.c | 2 ++ scripts/qapi-types.py | 42 ++++++++++++++++++++++++++++++++++++++++ scripts/qapi-visit.py | 47 +++++++++++++++++++++++++++++++++++++++++++++ scripts/qapi.py | 15 +++++++++++++++ 10 files changed, 160 insertions(+) diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt index c187fda..ee8a13e 100644 --- a/docs/qapi-code-gen.txt +++ b/docs/qapi-code-gen.txt @@ -125,6 +125,31 @@ Resulting in this JSON object: "lazy-refcounts": true } +A special type of unions are anonymous unions. They don't form a dictionary in +the wire format but allow the direct use of different types in their place. As +they aren't structured, they don't have any explicit discriminator but use +the (QObject) data type of their value as an implicit discriminator. This means +that they are restricted to using only one discriminator value per QObject +type. For example, you cannot have two different complex types in an anonymous +union, or two different integer types. + +Anonymous unions are declared using an empty dictionary as their discriminator. +The disriminator values never appear on the wire, they are only used in the +generated C code. Anonymous unions cannot have a base type. + + { 'union': 'BlockRef', + 'discriminator': {}, + 'data': { 'definition': 'BlockdevOptions' + 'reference': 'str' } } + +This example allows using both of the following example objects: + + { "file": "my_existing_block_device_id" } + { "file": { "driver": "file", + "readonly": false, + 'filename': "/tmp/mydisk.qcow2" } } + + === Commands === Commands are defined by using a list containing three members. The first diff --git a/include/qapi/qmp/qobject.h b/include/qapi/qmp/qobject.h index 9124649..d0bbc7c 100644 --- a/include/qapi/qmp/qobject.h +++ b/include/qapi/qmp/qobject.h @@ -44,6 +44,7 @@ typedef enum { QTYPE_QFLOAT, QTYPE_QBOOL, QTYPE_QERROR, + QTYPE_MAX, } qtype_code; struct QObject; diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h index 5c1297f..f3fa420 100644 --- a/include/qapi/visitor-impl.h +++ b/include/qapi/visitor-impl.h @@ -32,6 +32,8 @@ struct Visitor void (*type_enum)(Visitor *v, int *obj, const char *strings[], const char *kind, const char *name, Error **errp); + void (*get_next_type)(Visitor *v, int *kind, const int *qobjects, + const char *name, Error **errp); void (*type_int)(Visitor *v, int64_t *obj, const char *name, Error **errp); void (*type_bool)(Visitor *v, bool *obj, const char *name, Error **errp); diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h index bd24f85..48a2a2e 100644 --- a/include/qapi/visitor.h +++ b/include/qapi/visitor.h @@ -13,6 +13,7 @@ #ifndef QAPI_VISITOR_CORE_H #define QAPI_VISITOR_CORE_H +#include "qapi/qmp/qobject.h" #include "qapi/error.h" #include @@ -42,6 +43,8 @@ void visit_end_list(Visitor *v, Error **errp); void visit_start_optional(Visitor *v, bool *present, const char *name, Error **errp); void visit_end_optional(Visitor *v, Error **errp); +void visit_get_next_type(Visitor *v, int *obj, const int *qtypes, + const char *name, Error **errp); void visit_type_enum(Visitor *v, int *obj, const char *strings[], const char *kind, const char *name, Error **errp); void visit_type_int(Visitor *v, int64_t *obj, const char *name, Error **errp); diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c index 9b4d51b..d6a4012 100644 --- a/qapi/qapi-visit-core.c +++ b/qapi/qapi-visit-core.c @@ -12,6 +12,7 @@ */ #include "qemu-common.h" +#include "qapi/qmp/qobject.h" #include "qapi/qmp/qerror.h" #include "qapi/visitor.h" #include "qapi/visitor-impl.h" @@ -98,6 +99,14 @@ void visit_end_optional(Visitor *v, Error **errp) } } +void visit_get_next_type(Visitor *v, int *obj, const int *qtypes, + const char *name, Error **errp) +{ + if (!error_is_set(errp) && v->get_next_type) { + v->get_next_type(v, obj, qtypes, name, errp); + } +} + void visit_type_enum(Visitor *v, int *obj, const char *strings[], const char *kind, const char *name, Error **errp) { diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c index 70864a1..bf42c04 100644 --- a/qapi/qmp-input-visitor.c +++ b/qapi/qmp-input-visitor.c @@ -208,6 +208,19 @@ static void qmp_input_end_list(Visitor *v, Error **errp) qmp_input_pop(qiv, errp); } +static void qmp_input_get_next_type(Visitor *v, int *kind, const int *qobjects, + const char *name, Error **errp) +{ + QmpInputVisitor *qiv = to_qiv(v); + QObject *qobj = qmp_input_get_object(qiv, name, false); + + if (!qobj) { + error_set(errp, QERR_MISSING_PARAMETER, name ? name : "null"); + return; + } + *kind = qobjects[qobject_type(qobj)]; +} + static void qmp_input_type_int(Visitor *v, int64_t *obj, const char *name, Error **errp) { @@ -317,6 +330,7 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj) v->visitor.type_str = qmp_input_type_str; v->visitor.type_number = qmp_input_type_number; v->visitor.start_optional = qmp_input_start_optional; + v->visitor.get_next_type = qmp_input_get_next_type; qmp_input_push(v, obj, NULL); qobject_incref(obj); diff --git a/qobject/qjson.c b/qobject/qjson.c index 19085a1..6cf2511 100644 --- a/qobject/qjson.c +++ b/qobject/qjson.c @@ -260,6 +260,8 @@ static void to_json(const QObject *obj, QString *str, int pretty, int indent) /* XXX: should QError be emitted? */ case QTYPE_NONE: break; + case QTYPE_MAX: + abort(); } } diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py index db2f533..57dff53 100644 --- a/scripts/qapi-types.py +++ b/scripts/qapi-types.py @@ -157,6 +157,40 @@ typedef enum %(name)s return lookup_decl + enum_decl +def generate_anon_union_qtypes(expr): + + name = expr['union'] + members = expr['data'] + + ret = mcgen(''' +const int %(name)s_qtypes[QTYPE_MAX] = { +''', + name=name) + + for key in members: + qapi_type = members[key] + if builtin_type_qtypes.has_key(qapi_type): + qtype = builtin_type_qtypes[qapi_type] + elif find_struct(qapi_type): + qtype = "QTYPE_QDICT" + elif find_union(qapi_type): + qtype = "QTYPE_QDICT" + else: + assert False, "Invalid anonymous union member" + + ret += mcgen(''' + [ %(qtype)s ] = %(abbrev)s_KIND_%(enum)s, +''', + qtype = qtype, + abbrev = de_camel_case(name).upper(), + enum = c_fun(de_camel_case(key),False).upper()) + + ret += mcgen(''' +}; +''') + return ret + + def generate_union(expr): name = expr['union'] @@ -196,6 +230,12 @@ struct %(name)s ret += mcgen(''' }; ''') + if discriminator == {}: + ret += mcgen(''' +extern const int %(name)s_qtypes[]; +''', + name=name) + return ret @@ -348,6 +388,8 @@ for expr in exprs: ret += generate_fwd_struct(expr['union'], expr['data']) + "\n" ret += generate_enum('%sKind' % expr['union'], expr['data'].keys()) fdef.write(generate_enum_lookup('%sKind' % expr['union'], expr['data'].keys())) + if expr.get('discriminator') == {}: + fdef.write(generate_anon_union_qtypes(expr)) else: continue fdecl.write(ret) diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py index cd33e44..28539e2 100644 --- a/scripts/qapi-visit.py +++ b/scripts/qapi-visit.py @@ -172,6 +172,49 @@ void visit_type_%(name)s(Visitor *m, %(name)s * obj, const char *name, Error **e ''', name=name) +def generate_visit_anon_union(name, members): + ret = mcgen(''' + +void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **errp) +{ + Error *err = NULL; + + if (!error_is_set(errp)) { + visit_start_implicit_struct(m, (void**) obj, sizeof(%(name)s), &err); + visit_get_next_type(m, (int*) &(*obj)->kind, %(name)s_qtypes, name, &err); + switch ((*obj)->kind) { +''', + name=name) + + for key in members: + assert (members[key] in builtin_types + or find_struct(members[key]) + or find_union(members[key])), "Invalid anonymous union member" + + ret += mcgen(''' + case %(abbrev)s_KIND_%(enum)s: + visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, name, &err); + break; +''', + abbrev = de_camel_case(name).upper(), + enum = c_fun(de_camel_case(key),False).upper(), + c_type = type_name(members[key]), + c_name = c_fun(key)) + + ret += mcgen(''' + default: + abort(); + } + error_propagate(errp, err); + err = NULL; + visit_end_implicit_struct(m, &err); + } +} +''') + + return ret + + def generate_visit_union(expr): name = expr['union'] @@ -180,6 +223,10 @@ def generate_visit_union(expr): base = expr.get('base') discriminator = expr.get('discriminator') + if discriminator == {}: + assert not base + return generate_visit_anon_union(name, members) + ret = generate_visit_enum('%sKind' % name, members.keys()) if base: diff --git a/scripts/qapi.py b/scripts/qapi.py index 3a54c7f..38c808e 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -17,6 +17,21 @@ builtin_types = [ 'uint8', 'uint16', 'uint32', 'uint64' ] +builtin_type_qtypes = { + 'str': 'QTYPE_QSTRING', + 'int': 'QTYPE_QINT', + 'number': 'QTYPE_QFLOAT', + 'bool': 'QTYPE_QBOOL', + 'int8': 'QTYPE_QINT', + 'int16': 'QTYPE_QINT', + 'int32': 'QTYPE_QINT', + 'int64': 'QTYPE_QINT', + 'uint8': 'QTYPE_QINT', + 'uint16': 'QTYPE_QINT', + 'uint32': 'QTYPE_QINT', + 'uint64': 'QTYPE_QINT', +} + def tokenize(data): while len(data): ch = data[0]