Patchwork [08/22] qapi: add code generator for qmp-types

login
register
mail settings
Submitter Anthony Liguori
Date March 7, 2011, 1:22 a.m.
Message ID <1299460984-15849-9-git-send-email-aliguori@us.ibm.com>
Download mbox | patch
Permalink /patch/85632/
State New
Headers show

Comments

Anthony Liguori - March 7, 2011, 1:22 a.m.
Only generate qmp-types.[ch].  These files contain the type definitions for
QMP along with the alloc/free functions for these types.  Functions to convert
enum values to integers and vice versa are also included.

qmp-types is used both within QEMU and within libqmp

Special alloc/free functions are provided to ensure that all structures are
padded when allocated.  This makes sure that libqmp can provide a forward
compatible interface since all additions to a structure will have a boolean
enable flag.

The free function is convenient since individual structures may have pointers
that also require freeing.

Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
Anthony Liguori - March 7, 2011, 1:57 a.m.
On 03/06/2011 07:22 PM, Anthony Liguori wrote:
> Only generate qmp-types.[ch].  These files contain the type definitions for
> QMP along with the alloc/free functions for these types.  Functions to convert
> enum values to integers and vice versa are also included.
>
> qmp-types is used both within QEMU and within libqmp
>
> Special alloc/free functions are provided to ensure that all structures are
> padded when allocated.  This makes sure that libqmp can provide a forward
> compatible interface since all additions to a structure will have a boolean
> enable flag.
>
> The free function is convenient since individual structures may have pointers
> that also require freeing.
>
> Signed-off-by: Anthony Liguori<aliguori@us.ibm.com>
>
> diff --git a/Makefile b/Makefile
> index 6b1d716..6b9fd69 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -4,6 +4,7 @@ GENERATED_HEADERS = config-host.h trace.h qemu-options.def
>   ifeq ($(TRACE_BACKEND),dtrace)
>   GENERATED_HEADERS += trace-dtrace.h
>   endif
> +GENERATED_HEADERS += qmp-types.h
>
>   ifneq ($(wildcard config-host.mak),)
>   # Put the all: rule here so that config-host.mak can contain dependencies.
> @@ -146,6 +147,14 @@ trace-dtrace.o: trace-dtrace.dtrace $(GENERATED_HEADERS)
>
>   simpletrace.o: simpletrace.c $(GENERATED_HEADERS)
>
> +qmp-types.c: $(SRC_PATH)/qmp-schema.json $(SRC_PATH)/qmp-gen.py
> +	$(call quiet-command,python $(SRC_PATH)/qmp-gen.py --types-body<  $<  >  $@, "  GEN   $@")
> +
> +qmp-types.h: $(SRC_PATH)/qmp-schema.json $(SRC_PATH)/qmp-gen.py
> +	$(call quiet-command,python $(SRC_PATH)/qmp-gen.py --types-header<  $<  >  $@, "  GEN   $@")
> +
> +qmp-types.o: qmp-types.c qmp-types.h
> +
>   version.o: $(SRC_PATH)/version.rc config-host.mak
>   	$(call quiet-command,$(WINDRES) -I. -o $@ $<,"  RC    $(TARGET_DIR)$@")
>
> diff --git a/Makefile.objs b/Makefile.objs
> index 69f0383..710d99f 100644
> --- a/Makefile.objs
> +++ b/Makefile.objs
> @@ -15,7 +15,7 @@ oslib-obj-$(CONFIG_POSIX) += oslib-posix.o
>
>   block-obj-y = cutils.o cache-utils.o qemu-malloc.o qemu-option.o module.o
>   block-obj-y += nbd.o block.o aio.o aes.o qemu-config.o
> -block-obj-y += error.o
> +block-obj-y += error.o qmp-types.o
>   block-obj-$(CONFIG_POSIX) += posix-aio-compat.o
>   block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o
>
> diff --git a/ordereddict.py b/ordereddict.py
> new file mode 100644
> index 0000000..e17269f
> --- /dev/null
> +++ b/ordereddict.py
>    

Python 2.7 introduces a standard OrderedDict and this is a compatibility 
wrapper for older versions of Python.  I should have mentioned this in 
the commit message.

Regards,

Anthony Liguori

Patch

diff --git a/Makefile b/Makefile
index 6b1d716..6b9fd69 100644
--- a/Makefile
+++ b/Makefile
@@ -4,6 +4,7 @@  GENERATED_HEADERS = config-host.h trace.h qemu-options.def
 ifeq ($(TRACE_BACKEND),dtrace)
 GENERATED_HEADERS += trace-dtrace.h
 endif
+GENERATED_HEADERS += qmp-types.h
 
 ifneq ($(wildcard config-host.mak),)
 # Put the all: rule here so that config-host.mak can contain dependencies.
@@ -146,6 +147,14 @@  trace-dtrace.o: trace-dtrace.dtrace $(GENERATED_HEADERS)
 
 simpletrace.o: simpletrace.c $(GENERATED_HEADERS)
 
+qmp-types.c: $(SRC_PATH)/qmp-schema.json $(SRC_PATH)/qmp-gen.py
+	$(call quiet-command,python $(SRC_PATH)/qmp-gen.py --types-body < $< > $@, "  GEN   $@")
+
+qmp-types.h: $(SRC_PATH)/qmp-schema.json $(SRC_PATH)/qmp-gen.py
+	$(call quiet-command,python $(SRC_PATH)/qmp-gen.py --types-header < $< > $@, "  GEN   $@")
+
+qmp-types.o: qmp-types.c qmp-types.h
+
 version.o: $(SRC_PATH)/version.rc config-host.mak
 	$(call quiet-command,$(WINDRES) -I. -o $@ $<,"  RC    $(TARGET_DIR)$@")
 
diff --git a/Makefile.objs b/Makefile.objs
index 69f0383..710d99f 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -15,7 +15,7 @@  oslib-obj-$(CONFIG_POSIX) += oslib-posix.o
 
 block-obj-y = cutils.o cache-utils.o qemu-malloc.o qemu-option.o module.o
 block-obj-y += nbd.o block.o aio.o aes.o qemu-config.o
-block-obj-y += error.o
+block-obj-y += error.o qmp-types.o
 block-obj-$(CONFIG_POSIX) += posix-aio-compat.o
 block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o
 
diff --git a/ordereddict.py b/ordereddict.py
new file mode 100644
index 0000000..e17269f
--- /dev/null
+++ b/ordereddict.py
@@ -0,0 +1,128 @@ 
+# Copyright (c) 2009 Raymond Hettinger
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+#     The above copyright notice and this permission notice shall be
+#     included in all copies or substantial portions of the Software.
+#
+#     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+#     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+#     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+#     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+#     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+#     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+#     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+#     OTHER DEALINGS IN THE SOFTWARE.
+
+from UserDict import DictMixin
+
+class OrderedDict(dict, DictMixin):
+
+    def __init__(self, *args, **kwds):
+        if len(args) > 1:
+            raise TypeError('expected at most 1 arguments, got %d' % len(args))
+        try:
+            self.__end
+        except AttributeError:
+            self.clear()
+        self.update(*args, **kwds)
+
+    def clear(self):
+        self.__end = end = []
+        end += [None, end, end]         # sentinel node for doubly linked list
+        self.__map = {}                 # key --> [key, prev, next]
+        dict.clear(self)
+
+    def __setitem__(self, key, value):
+        if key not in self:
+            end = self.__end
+            curr = end[1]
+            curr[2] = end[1] = self.__map[key] = [key, curr, end]
+        dict.__setitem__(self, key, value)
+
+    def __delitem__(self, key):
+        dict.__delitem__(self, key)
+        key, prev, next = self.__map.pop(key)
+        prev[2] = next
+        next[1] = prev
+
+    def __iter__(self):
+        end = self.__end
+        curr = end[2]
+        while curr is not end:
+            yield curr[0]
+            curr = curr[2]
+
+    def __reversed__(self):
+        end = self.__end
+        curr = end[1]
+        while curr is not end:
+            yield curr[0]
+            curr = curr[1]
+
+    def popitem(self, last=True):
+        if not self:
+            raise KeyError('dictionary is empty')
+        if last:
+            key = reversed(self).next()
+        else:
+            key = iter(self).next()
+        value = self.pop(key)
+        return key, value
+
+    def __reduce__(self):
+        items = [[k, self[k]] for k in self]
+        tmp = self.__map, self.__end
+        del self.__map, self.__end
+        inst_dict = vars(self).copy()
+        self.__map, self.__end = tmp
+        if inst_dict:
+            return (self.__class__, (items,), inst_dict)
+        return self.__class__, (items,)
+
+    def keys(self):
+        return list(self)
+
+    setdefault = DictMixin.setdefault
+    update = DictMixin.update
+    pop = DictMixin.pop
+    values = DictMixin.values
+    items = DictMixin.items
+    iterkeys = DictMixin.iterkeys
+    itervalues = DictMixin.itervalues
+    iteritems = DictMixin.iteritems
+
+    def __repr__(self):
+        if not self:
+            return '%s()' % (self.__class__.__name__,)
+        return '%s(%r)' % (self.__class__.__name__, self.items())
+
+    def copy(self):
+        return self.__class__(self)
+
+    @classmethod
+    def fromkeys(cls, iterable, value=None):
+        d = cls()
+        for key in iterable:
+            d[key] = value
+        return d
+
+    def __eq__(self, other):
+        if isinstance(other, OrderedDict):
+            if len(self) != len(other):
+                return False
+            for p, q in  zip(self.items(), other.items()):
+                if p != q:
+                    return False
+            return True
+        return dict.__eq__(self, other)
+
+    def __ne__(self, other):
+        return not self == other
+
diff --git a/qmp-gen.py b/qmp-gen.py
new file mode 100644
index 0000000..0889fa3
--- /dev/null
+++ b/qmp-gen.py
@@ -0,0 +1,393 @@ 
+##
+# QAPI Code Generator
+#
+# Copyright IBM, Corp. 2011
+#
+# Authors:
+#  Anthony Liguori   <aliguori@us.ibm.com>
+#
+# This work is licensed under the terms of the GNU GPL, version 2.  See
+# the COPYING file in the top-level directory.
+##
+import sys
+from ordereddict import OrderedDict
+
+enum_types = []
+event_types = {}
+
+def c_var(name):
+    return '_'.join(name.split('-'))
+
+def genindent(count):
+    ret = ""
+    for i in range(count):
+        ret += " "
+    return ret
+
+def is_dict(obj):
+    if type(obj) in [dict, OrderedDict]:
+        return True
+    return False
+
+def qmp_array_type_to_c(typename):
+    if type(typename) == list or is_dict(typename):
+        return qmp_type_to_c(typename)
+    elif typename == 'int':
+        return 'IntArray *'
+    elif typename == 'str':
+        return 'StringArray *'
+    elif typename == 'bool':
+        return 'BoolArray *'
+    elif typename == 'number':
+        return 'DoubleArray *'
+    else:
+        return qmp_type_to_c(typename)
+
+def qmp_type_should_free(typename):
+    if (type(typename) == list or
+        typename == 'str' or
+        (typename not in ['int', 'bool', 'number'] and
+         typename not in enum_types and not typename.isupper())):
+        return True
+    return False
+
+def qmp_free_func(typename):
+    if type(typename) == list:
+        return qmp_free_func(typename[0])
+    elif typename == 'str':
+        return 'qemu_free'
+    else:
+        return 'qmp_free_%s' % (de_camel_case(typename))
+
+def qmp_type_is_event(typename):
+    if type(typename) == str and typename.isupper():
+        return True
+    return False
+
+def qmp_type_to_c(typename, retval=False, indent=0):
+    if type(typename) == list:
+        return qmp_array_type_to_c(typename[0])
+    elif is_dict(typename):
+        string = 'struct {\n'
+        for key in typename:
+            name = key
+            if key.startswith('*'):
+                name = key[1:]
+                string += "%sbool has_%s;\n" % (genindent(indent + 4), c_var(name))
+            string += "%s%s %s;\n" % (genindent(indent + 4),
+                                      qmp_type_to_c(typename[key],
+                                                    True,
+                                                    indent=(indent + 4)),
+                                      c_var(name))
+        string += "%s}" % genindent(indent)
+        return string
+    elif typename == 'int':
+        return 'int64_t'
+    elif not retval and typename == 'str':
+        return 'const char *'
+    elif retval and typename == 'str':
+        return 'char *'
+    elif typename == 'bool':
+        return 'bool'
+    elif typename == 'number':
+        return 'double'
+    elif typename == 'none':
+        return 'void'
+    elif typename in enum_types:
+        return typename
+    elif qmp_type_is_event(typename):
+        return 'struct %s *' % qmp_event_to_c(typename)
+    else:
+        return 'struct %s *' % typename
+
+def qmp_type_to_qobj_ctor(typename):
+    return 'qmp_marshal_type_%s' % typename
+
+def qmp_type_from_qobj(typename):
+    return qobj_to_c(typename)
+
+def parse_args(typeinfo):
+    for member in typeinfo:
+        argname = member
+        argtype = typeinfo[member]
+        optional = False
+        if member.startswith('*'):
+            argname = member[1:]
+            optional = True
+        yield (argname, argtype, optional)
+
+def de_camel_case(name):
+    new_name = ''
+    for ch in name:
+        if ch.isupper() and new_name:
+            new_name += '_'
+        new_name += ch.lower()
+    return new_name
+
+def camel_case(name):
+    new_name = ''
+    first = True
+    for ch in name:
+        if ch == '_':
+            first = True
+        elif first:
+            new_name += ch.upper()
+            first = False
+        else:
+            new_name += ch.lower()
+    return new_name
+
+def qmp_event_to_c(name):
+    return '%sEvent' % camel_case(name)
+
+def qmp_event_func_to_c(name):
+    return '%sFunc' % camel_case(name)
+
+def inprint(string, indent):
+    print '%s%s' % (genindent(indent), string)
+
+def enum_abbreviation(name):
+    ab = ''
+    for ch in name:
+        if ch.isupper():
+            ab += ch
+    return ab
+
+def print_enum_declaration(name, entries):
+    print
+    print 'typedef enum %s {' % name
+    i = 0
+    for entry in entries:
+        print '    %s_%s = %d,' % (enum_abbreviation(name), entry.upper(), i)
+        i += 1
+    print '} %s;' % name
+    print
+    print '%s qmp_type_%s_from_str(const char *str, Error **errp);' % (name, de_camel_case(name))
+    print 'const char *qmp_type_%s_to_str(%s value, Error **errp);' % (de_camel_case(name), name)
+
+def print_enum_definition(name, entries):
+    print '''
+%s qmp_type_%s_from_str(const char *str, Error **errp)
+{''' % (name, de_camel_case(name))
+    first = True
+    for entry in entries:
+        if first:
+            print '    if (strcmp(str, "%s") == 0) {' % entry
+            first = False
+        else:
+            print '    } else if (strcmp(str, "%s") == 0) {' % entry
+        print '        return %s_%s;' % (enum_abbreviation(name), entry.upper())
+    print '''    } else {
+        error_set(errp, QERR_ENUM_VALUE_INVALID, "%s", str);
+        return %s_%s;
+    }
+}''' % (name, enum_abbreviation(name), entries[0].upper())
+
+    print '''
+const char *qmp_type_%s_to_str(%s value, Error **errp)
+{''' % (de_camel_case(name), name)
+    first = True
+    for entry in entries:
+        enum = '%s_%s' % (enum_abbreviation(name), entry.upper())
+        if first:
+            print '    if (value == %s) {' % enum
+            first = False
+        else:
+            print '    } else if (value == %s) {' % enum
+        print '        return "%s";' % entry
+    print '''    } else {
+        char buf[32];
+        snprintf(buf, sizeof(buf), "%%d", value);
+        error_set(errp, QERR_ENUM_VALUE_INVALID, "%s", buf);
+        return NULL;
+    }
+}''' % name
+
+def print_type_declaration(name, typeinfo):
+    if type(typeinfo) == str:
+        print
+        print "typedef %s %s;" % (qmp_type_to_c(typeinfo), name)
+    elif is_dict(typeinfo) and not name.isupper():
+        print
+        print "typedef struct %s %s;" % (name, name)
+        print "struct %s {" % name
+        for key in typeinfo:
+            member = key
+            if key.startswith('*'):
+                member = key[1:]
+                print "    bool has_%s;" % c_var(member)
+            print "    %s %s;" % (qmp_type_to_c(typeinfo[key], True, indent=4), c_var(member))
+        print "    %s *next;" % c_var(name)
+        print "};"
+        print
+        print "%s *qmp_alloc_%s(void);" % (name, de_camel_case(name))
+        print "void qmp_free_%s(%s *obj);" % (de_camel_case(name), name)
+    elif is_dict(typeinfo) and name.isupper():
+        arglist = ['void *opaque']
+        for argname, argtype, optional in parse_args(typeinfo):
+            arglist.append('%s %s' % (qmp_type_to_c(argtype), argname))
+        print
+        print 'typedef void (%s)(%s);' % (qmp_event_func_to_c(name), ', '.join(arglist))
+        print
+        print 'typedef struct %s {' % qmp_event_to_c(name)
+        print '    QmpSignal *signal;'
+        print '    %s *func;' % qmp_event_func_to_c(name)
+        print '} %s;' % qmp_event_to_c(name)
+
+def print_type_free(typeinfo, prefix, indent=4):
+    for argname, argtype, optional in parse_args(typeinfo):
+        if type(argtype) == list:
+            argtype = argtype[0]
+
+        if is_dict(argtype):
+            if optional:
+                inprint('if (%shas_%s) {' % (prefix, argname), indent)
+                print_type_free(argtype, '%s%s.' % (prefix, argname), indent + 4)
+                inprint('}', indent)
+            else:
+                print_type_free(argtype, '%s%s.' % (prefix, argname), indent)
+        elif qmp_type_should_free(argtype):
+            if optional:
+                inprint('if (%shas_%s) {' % (prefix, argname), indent)
+                inprint('    %s(%s%s);' % (qmp_free_func(argtype), prefix, argname), indent)
+                inprint('}', indent)
+            else:
+                inprint('%s(%s%s);' % (qmp_free_func(argtype), prefix, argname), indent)
+
+def print_type_definition(name, typeinfo):
+    if qmp_type_is_event(name):
+        return
+
+    c_var_name = de_camel_case(name)
+
+    print '''
+void qmp_free_%s(%s *obj)
+{
+    if (!obj) {
+        return;
+    }''' % (c_var_name, name)
+
+    print_type_free(typeinfo, 'obj->')
+
+    print '''
+    %s(obj->next);
+    qemu_free(obj);
+}''' % (qmp_free_func(name))
+
+    print '''
+%s *qmp_alloc_%s(void)
+{
+    BUILD_ASSERT(sizeof(%s) < 512);
+    return qemu_mallocz(512);
+}''' % (name, c_var_name, name)
+
+def tokenize(data):
+    while len(data):
+        if data[0] in ['{', '}', ':', ',', '[', ']']:
+            yield data[0]
+            data = data[1:]
+        elif data[0] in ' \n':
+            data = data[1:]
+        elif data[0] == "'":
+            data = data[1:]
+            string = ''
+            while data[0] != "'":
+                string += data[0]
+                data = data[1:]
+            data = data[1:]
+            yield string
+
+def parse_value(tokens):
+    if tokens[0] == '{':
+        ret = OrderedDict()
+        tokens = tokens[1:]
+        while tokens[0] != '}':
+            key = tokens[0]
+            tokens = tokens[1:] 
+
+            tokens = tokens[1:] # :
+
+            value, tokens = parse_value(tokens)
+
+            if tokens[0] == ',':
+                tokens = tokens[1:]
+
+            ret[key] = value
+        tokens = tokens[1:]
+        return ret, tokens
+    elif tokens[0] == '[':
+        ret = []
+        tokens = tokens[1:]
+        while tokens[0] != ']':
+            value, tokens = parse_value(tokens)
+            if tokens[0] == ',':
+                tokens = tokens[1:]
+            ret.append(value)
+        tokens = tokens[1:]
+        return ret, tokens
+    else:
+        return tokens[0], tokens[1:]
+
+def ordered_eval(string):
+    return parse_value(map(lambda x: x, tokenize(string)))[0]
+
+if len(sys.argv) == 2:
+    if sys.argv[1] == '--types-body':
+        kind = 'types-body'
+    elif sys.argv[1] == '--types-header':
+        kind = 'types-header'
+
+if kind == 'types-header':
+    print '''/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT */
+#ifndef QMP_TYPES_H
+#define QMP_TYPES_H
+
+#include "qmp-types-core.h"
+
+'''
+elif kind == 'types-body':
+    print '''/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT */
+
+#include "qmp-types.h"
+#include "qemu-common.h"
+'''
+
+exprs = []
+expr = ''
+
+for line in sys.stdin:
+    if line.startswith('#') or line == '\n':
+        continue
+
+    if line.startswith(' '):
+        expr += line
+    elif expr:
+        s = ordered_eval(expr)
+        exprs.append(s)
+        expr = line
+    else:
+        expr += line
+
+if expr:
+    s = ordered_eval(expr)
+    exprs.append(s)
+
+for s in exprs:
+    if is_dict(s):
+        key = s.keys()[0]
+        if is_dict(s[key]):
+            if qmp_type_is_event(key):
+                event_types[key] = s[key]
+            if kind == 'types-body':
+                print_type_definition(key, s[key])
+            elif kind == 'types-header':
+                print_type_declaration(key, s[key])
+        else:
+            enum_types.append(key)
+            if kind == 'types-header':
+                print_enum_declaration(key, s[key])
+            elif kind == 'types-body':
+                print_enum_definition(key, s[key])
+
+if kind.endswith('header'):
+    print '#endif'
diff --git a/qmp-schema.json b/qmp-schema.json
new file mode 100644
index 0000000..e69de29
diff --git a/qmp-types-core.h b/qmp-types-core.h
new file mode 100644
index 0000000..dbf1fd8
--- /dev/null
+++ b/qmp-types-core.h
@@ -0,0 +1,27 @@ 
+/*
+ * QAPI
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ *  Anthony Liguori   <aliguori@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.  See
+ * the COPYING.LIB file in the top-level directory.
+ */
+#ifndef QMP_TYPES_CORE_H
+#define QMP_TYPES_CORE_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "error.h"
+
+typedef struct QmpSignal QmpSignal;
+
+#define BUILD_ASSERT(cond) do {     \
+    (void)sizeof(int[-1+!!(cond)]); \
+} while (0)
+
+#define BUILD_BUG() BUILD_ASSERT(0)
+
+#endif