2b162ccbe8
Fixes flat unions to get the base's base members. Test case is from
commit 2fc0043
, in qapi-schema-test.json:
{ 'union': 'UserDefFlatUnion',
'base': 'UserDefUnionBase',
'discriminator': 'enum1',
'data': { 'value1' : 'UserDefA',
'value2' : 'UserDefB',
'value3' : 'UserDefB' } }
{ 'struct': 'UserDefUnionBase',
'base': 'UserDefZero',
'data': { 'string': 'str', 'enum1': 'EnumOne' } }
{ 'struct': 'UserDefZero',
'data': { 'integer': 'int' } }
Patch's effect on UserDefFlatUnion:
struct UserDefFlatUnion {
/* Members inherited from UserDefUnionBase: */
+ int64_t integer;
char *string;
EnumOne enum1;
/* Own members: */
union { /* union tag is @enum1 */
void *data;
UserDefA *value1;
UserDefB *value2;
UserDefB *value3;
};
};
Flat union visitors remain broken. They'll be fixed next.
Code is generated in a different order now, but that doesn't matter.
The two guards QAPI_TYPES_BUILTIN_STRUCT_DECL and
QAPI_TYPES_BUILTIN_CLEANUP_DECL are replaced by just
QAPI_TYPES_BUILTIN.
Two ugly special cases for simple unions now stand out like sore
thumbs:
1. The type tag is named 'type' everywhere, except in generated C,
where it's 'kind'.
2. QAPISchema lowers simple unions to semantically equivalent flat
unions. However, the C generated for a simple unions differs from
the C generated for its equivalent flat union, and we therefore
need special code to preserve that pointless difference for now.
Mark both TODO.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Daniel P. Berrange <berrange@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
385 lines
9.9 KiB
Python
385 lines
9.9 KiB
Python
#
|
|
# QAPI types generator
|
|
#
|
|
# Copyright IBM, Corp. 2011
|
|
# Copyright (c) 2013-2015 Red Hat Inc.
|
|
#
|
|
# Authors:
|
|
# Anthony Liguori <aliguori@us.ibm.com>
|
|
# Markus Armbruster <armbru@redhat.com>
|
|
#
|
|
# This work is licensed under the terms of the GNU GPL, version 2.
|
|
# See the COPYING file in the top-level directory.
|
|
|
|
from qapi import *
|
|
|
|
def gen_fwd_object_or_array(name):
|
|
return mcgen('''
|
|
|
|
typedef struct %(name)s %(name)s;
|
|
''',
|
|
name=c_name(name))
|
|
|
|
def gen_array(name, element_type):
|
|
return mcgen('''
|
|
|
|
struct %(name)s {
|
|
union {
|
|
%(c_type)s value;
|
|
uint64_t padding;
|
|
};
|
|
struct %(name)s *next;
|
|
};
|
|
''',
|
|
name=c_name(name), c_type=element_type.c_type())
|
|
|
|
def gen_struct_field(name, typ, optional):
|
|
ret = ''
|
|
|
|
if optional:
|
|
ret += mcgen('''
|
|
bool has_%(c_name)s;
|
|
''',
|
|
c_name=c_name(name))
|
|
ret += mcgen('''
|
|
%(c_type)s %(c_name)s;
|
|
''',
|
|
c_type=typ.c_type(), c_name=c_name(name))
|
|
return ret
|
|
|
|
def generate_struct_fields(members):
|
|
ret = ''
|
|
|
|
for memb in members:
|
|
ret += gen_struct_field(memb.name, memb.type, memb.optional)
|
|
return ret
|
|
|
|
def gen_struct(name, base, members):
|
|
ret = mcgen('''
|
|
|
|
struct %(name)s {
|
|
''',
|
|
name=c_name(name))
|
|
|
|
if base:
|
|
ret += gen_struct_field('base', base, False)
|
|
|
|
ret += generate_struct_fields(members)
|
|
|
|
# Make sure that all structs have at least one field; this avoids
|
|
# potential issues with attempting to malloc space for zero-length structs
|
|
# in C, and also incompatibility with C++ (where an empty struct is size 1).
|
|
if not base and not members:
|
|
ret += mcgen('''
|
|
char qapi_dummy_field_for_empty_struct;
|
|
''')
|
|
|
|
ret += mcgen('''
|
|
};
|
|
''')
|
|
|
|
return ret
|
|
|
|
def generate_enum_lookup(name, values, prefix=None):
|
|
ret = mcgen('''
|
|
|
|
const char *const %(name)s_lookup[] = {
|
|
''',
|
|
name=c_name(name))
|
|
for value in values:
|
|
index = c_enum_const(name, value, prefix)
|
|
ret += mcgen('''
|
|
[%(index)s] = "%(value)s",
|
|
''',
|
|
index = index, value = value)
|
|
|
|
max_index = c_enum_const(name, 'MAX', prefix)
|
|
ret += mcgen('''
|
|
[%(max_index)s] = NULL,
|
|
};
|
|
''',
|
|
max_index=max_index)
|
|
return ret
|
|
|
|
def generate_enum(name, values, prefix=None):
|
|
name = c_name(name)
|
|
lookup_decl = mcgen('''
|
|
|
|
extern const char *const %(name)s_lookup[];
|
|
''',
|
|
name=name)
|
|
|
|
enum_decl = mcgen('''
|
|
|
|
typedef enum %(name)s {
|
|
''',
|
|
name=name)
|
|
|
|
# append automatically generated _MAX value
|
|
enum_values = values + [ 'MAX' ]
|
|
|
|
i = 0
|
|
for value in enum_values:
|
|
enum_full_value = c_enum_const(name, value, prefix)
|
|
enum_decl += mcgen('''
|
|
%(enum_full_value)s = %(i)d,
|
|
''',
|
|
enum_full_value = enum_full_value,
|
|
i=i)
|
|
i += 1
|
|
|
|
enum_decl += mcgen('''
|
|
} %(name)s;
|
|
''',
|
|
name=name)
|
|
|
|
return enum_decl + lookup_decl
|
|
|
|
def gen_alternate_qtypes_decl(name):
|
|
return mcgen('''
|
|
|
|
extern const int %(c_name)s_qtypes[];
|
|
''',
|
|
c_name=c_name(name))
|
|
|
|
def gen_alternate_qtypes(name, variants):
|
|
ret = mcgen('''
|
|
|
|
const int %(name)s_qtypes[QTYPE_MAX] = {
|
|
''',
|
|
name=c_name(name))
|
|
|
|
for var in variants.variants:
|
|
qtype = var.type.alternate_qtype()
|
|
assert qtype
|
|
|
|
ret += mcgen('''
|
|
[%(qtype)s] = %(enum_const)s,
|
|
''',
|
|
qtype = qtype,
|
|
enum_const=c_enum_const(variants.tag_member.type.name,
|
|
var.name))
|
|
|
|
ret += mcgen('''
|
|
};
|
|
''')
|
|
return ret
|
|
|
|
def gen_union(name, base, variants):
|
|
name = c_name(name)
|
|
|
|
ret = mcgen('''
|
|
|
|
struct %(name)s {
|
|
''',
|
|
name=name)
|
|
if base:
|
|
ret += mcgen('''
|
|
/* Members inherited from %(c_name)s: */
|
|
''',
|
|
c_name=c_name(base.name))
|
|
ret += generate_struct_fields(base.members)
|
|
ret += mcgen('''
|
|
/* Own members: */
|
|
''')
|
|
else:
|
|
ret += mcgen('''
|
|
%(discriminator_type_name)s kind;
|
|
''',
|
|
discriminator_type_name=c_name(variants.tag_member.type.name))
|
|
|
|
# FIXME: What purpose does data serve, besides preventing a union that
|
|
# has a branch named 'data'? We use it in qapi-visit.py to decide
|
|
# whether to bypass the switch statement if visiting the discriminator
|
|
# failed; but since we 0-initialize structs, and cannot tell what
|
|
# branch of the union is in use if the discriminator is invalid, there
|
|
# should not be any data leaks even without a data pointer. Or, if
|
|
# 'data' is merely added to guarantee we don't have an empty union,
|
|
# shouldn't we enforce that at .json parse time?
|
|
ret += mcgen('''
|
|
union { /* union tag is @%(c_name)s */
|
|
void *data;
|
|
''',
|
|
# TODO ugly special case for simple union
|
|
# Use same tag name in C as on the wire to get rid of
|
|
# it, then: c_name=c_name(variants.tag_member.name)
|
|
c_name=c_name(variants.tag_name or 'kind'))
|
|
|
|
for var in variants.variants:
|
|
# Ugly special case for simple union TODO get rid of it
|
|
typ = var.simple_union_type() or var.type
|
|
ret += mcgen('''
|
|
%(c_type)s %(c_name)s;
|
|
''',
|
|
c_type=typ.c_type(),
|
|
c_name=c_name(var.name))
|
|
|
|
ret += mcgen('''
|
|
};
|
|
};
|
|
''')
|
|
|
|
return ret
|
|
|
|
def generate_type_cleanup_decl(name):
|
|
ret = mcgen('''
|
|
|
|
void qapi_free_%(name)s(%(name)s *obj);
|
|
''',
|
|
name=c_name(name))
|
|
return ret
|
|
|
|
def generate_type_cleanup(name):
|
|
ret = mcgen('''
|
|
|
|
void qapi_free_%(name)s(%(name)s *obj)
|
|
{
|
|
QapiDeallocVisitor *md;
|
|
Visitor *v;
|
|
|
|
if (!obj) {
|
|
return;
|
|
}
|
|
|
|
md = qapi_dealloc_visitor_new();
|
|
v = qapi_dealloc_get_visitor(md);
|
|
visit_type_%(name)s(v, &obj, NULL, NULL);
|
|
qapi_dealloc_visitor_cleanup(md);
|
|
}
|
|
''',
|
|
name=c_name(name))
|
|
return ret
|
|
|
|
|
|
class QAPISchemaGenTypeVisitor(QAPISchemaVisitor):
|
|
def __init__(self):
|
|
self.decl = None
|
|
self.defn = None
|
|
self._fwdecl = None
|
|
self._fwdefn = None
|
|
self._btin = None
|
|
|
|
def visit_begin(self, schema):
|
|
self.decl = ''
|
|
self.defn = ''
|
|
self._fwdecl = ''
|
|
self._fwdefn = ''
|
|
self._btin = guardstart('QAPI_TYPES_BUILTIN')
|
|
|
|
def visit_end(self):
|
|
self.decl = self._fwdecl + self.decl
|
|
self._fwdecl = None
|
|
self.defn = self._fwdefn + self.defn
|
|
self._fwdefn = None
|
|
# To avoid header dependency hell, we always generate
|
|
# declarations for built-in types in our header files and
|
|
# simply guard them. See also do_builtins (command line
|
|
# option -b).
|
|
self._btin += guardend('QAPI_TYPES_BUILTIN')
|
|
self.decl = self._btin + self.decl
|
|
self._btin = None
|
|
|
|
def _gen_type_cleanup(self, name):
|
|
self.decl += generate_type_cleanup_decl(name)
|
|
self.defn += generate_type_cleanup(name)
|
|
|
|
def visit_enum_type(self, name, info, values, prefix):
|
|
self._fwdecl += generate_enum(name, values, prefix)
|
|
self._fwdefn += generate_enum_lookup(name, values, prefix)
|
|
|
|
def visit_array_type(self, name, info, element_type):
|
|
if isinstance(element_type, QAPISchemaBuiltinType):
|
|
self._btin += gen_fwd_object_or_array(name)
|
|
self._btin += gen_array(name, element_type)
|
|
self._btin += generate_type_cleanup_decl(name)
|
|
if do_builtins:
|
|
self.defn += generate_type_cleanup(name)
|
|
else:
|
|
self._fwdecl += gen_fwd_object_or_array(name)
|
|
self.decl += gen_array(name, element_type)
|
|
self._gen_type_cleanup(name)
|
|
|
|
def visit_object_type(self, name, info, base, members, variants):
|
|
if info:
|
|
self._fwdecl += gen_fwd_object_or_array(name)
|
|
if variants:
|
|
assert not members # not implemented
|
|
self.decl += gen_union(name, base, variants)
|
|
else:
|
|
self.decl += gen_struct(name, base, members)
|
|
self._gen_type_cleanup(name)
|
|
|
|
def visit_alternate_type(self, name, info, variants):
|
|
self._fwdecl += gen_fwd_object_or_array(name)
|
|
self._fwdefn += gen_alternate_qtypes(name, variants)
|
|
self.decl += gen_union(name, None, variants)
|
|
self.decl += gen_alternate_qtypes_decl(name)
|
|
self._gen_type_cleanup(name)
|
|
|
|
# If you link code generated from multiple schemata, you want only one
|
|
# instance of the code for built-in types. Generate it only when
|
|
# do_builtins, enabled by command line option -b. See also
|
|
# QAPISchemaGenTypeVisitor.visit_end().
|
|
do_builtins = False
|
|
|
|
(input_file, output_dir, do_c, do_h, prefix, opts) = \
|
|
parse_command_line("b", ["builtins"])
|
|
|
|
for o, a in opts:
|
|
if o in ("-b", "--builtins"):
|
|
do_builtins = True
|
|
|
|
c_comment = '''
|
|
/*
|
|
* deallocation functions for schema-defined QAPI types
|
|
*
|
|
* Copyright IBM, Corp. 2011
|
|
*
|
|
* Authors:
|
|
* Anthony Liguori <aliguori@us.ibm.com>
|
|
* Michael Roth <mdroth@linux.vnet.ibm.com>
|
|
*
|
|
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
|
|
* See the COPYING.LIB file in the top-level directory.
|
|
*
|
|
*/
|
|
'''
|
|
h_comment = '''
|
|
/*
|
|
* schema-defined QAPI types
|
|
*
|
|
* Copyright IBM, Corp. 2011
|
|
*
|
|
* Authors:
|
|
* Anthony Liguori <aliguori@us.ibm.com>
|
|
*
|
|
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
|
|
* See the COPYING.LIB file in the top-level directory.
|
|
*
|
|
*/
|
|
'''
|
|
|
|
(fdef, fdecl) = open_output(output_dir, do_c, do_h, prefix,
|
|
'qapi-types.c', 'qapi-types.h',
|
|
c_comment, h_comment)
|
|
|
|
fdef.write(mcgen('''
|
|
#include "qapi/dealloc-visitor.h"
|
|
#include "%(prefix)sqapi-types.h"
|
|
#include "%(prefix)sqapi-visit.h"
|
|
''',
|
|
prefix=prefix))
|
|
|
|
fdecl.write(mcgen('''
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
'''))
|
|
|
|
schema = QAPISchema(input_file)
|
|
gen = QAPISchemaGenTypeVisitor()
|
|
schema.visit(gen)
|
|
fdef.write(gen.defn)
|
|
fdecl.write(gen.decl)
|
|
|
|
close_output(fdef, fdecl)
|