xref: /openbmc/qemu/scripts/qapi/types.py (revision 9bafe07b)
1"""
2QAPI types generator
3
4Copyright IBM, Corp. 2011
5Copyright (c) 2013-2018 Red Hat Inc.
6
7Authors:
8 Anthony Liguori <aliguori@us.ibm.com>
9 Michael Roth <mdroth@linux.vnet.ibm.com>
10 Markus Armbruster <armbru@redhat.com>
11
12This work is licensed under the terms of the GNU GPL, version 2.
13# See the COPYING file in the top-level directory.
14"""
15
16from typing import List, Optional
17
18from .common import c_enum_const, c_name, mcgen
19from .gen import QAPISchemaModularCVisitor, ifcontext
20from .schema import (
21    QAPISchema,
22    QAPISchemaEnumMember,
23    QAPISchemaFeature,
24    QAPISchemaIfCond,
25    QAPISchemaObjectType,
26    QAPISchemaObjectTypeMember,
27    QAPISchemaType,
28    QAPISchemaVariants,
29)
30from .source import QAPISourceInfo
31
32
33# variants must be emitted before their container; track what has already
34# been output
35objects_seen = set()
36
37
38def gen_enum_lookup(name: str,
39                    members: List[QAPISchemaEnumMember],
40                    prefix: Optional[str] = None) -> str:
41    max_index = c_enum_const(name, '_MAX', prefix)
42    flags = ''
43    ret = mcgen('''
44
45const QEnumLookup %(c_name)s_lookup = {
46    .array = (const char *const[]) {
47''',
48                c_name=c_name(name))
49    for memb in members:
50        ret += memb.ifcond.gen_if()
51        index = c_enum_const(name, memb.name, prefix)
52        ret += mcgen('''
53        [%(index)s] = "%(name)s",
54''',
55                     index=index, name=memb.name)
56        ret += memb.ifcond.gen_endif()
57        if 'deprecated' in (f.name for f in memb.features):
58            flags += mcgen('''
59        [%(index)s] = QAPI_ENUM_DEPRECATED,
60''',
61                           index=index)
62
63    if flags:
64        ret += mcgen('''
65    },
66    .flags = (const unsigned char[%(max_index)s]) {
67''',
68                     max_index=max_index)
69        ret += flags
70
71    ret += mcgen('''
72    },
73    .size = %(max_index)s
74};
75''',
76                 max_index=max_index)
77    return ret
78
79
80def gen_enum(name: str,
81             members: List[QAPISchemaEnumMember],
82             prefix: Optional[str] = None) -> str:
83    # append automatically generated _MAX value
84    enum_members = members + [QAPISchemaEnumMember('_MAX', None)]
85
86    ret = mcgen('''
87
88typedef enum %(c_name)s {
89''',
90                c_name=c_name(name))
91
92    for memb in enum_members:
93        ret += memb.ifcond.gen_if()
94        ret += mcgen('''
95    %(c_enum)s,
96''',
97                     c_enum=c_enum_const(name, memb.name, prefix))
98        ret += memb.ifcond.gen_endif()
99
100    ret += mcgen('''
101} %(c_name)s;
102''',
103                 c_name=c_name(name))
104
105    ret += mcgen('''
106
107#define %(c_name)s_str(val) \\
108    qapi_enum_lookup(&%(c_name)s_lookup, (val))
109
110extern const QEnumLookup %(c_name)s_lookup;
111''',
112                 c_name=c_name(name))
113    return ret
114
115
116def gen_fwd_object_or_array(name: str) -> str:
117    return mcgen('''
118
119typedef struct %(c_name)s %(c_name)s;
120''',
121                 c_name=c_name(name))
122
123
124def gen_array(name: str, element_type: QAPISchemaType) -> str:
125    return mcgen('''
126
127struct %(c_name)s {
128    %(c_name)s *next;
129    %(c_type)s value;
130};
131''',
132                 c_name=c_name(name), c_type=element_type.c_type())
133
134
135def gen_struct_members(members: List[QAPISchemaObjectTypeMember]) -> str:
136    ret = ''
137    for memb in members:
138        ret += memb.ifcond.gen_if()
139        if memb.optional:
140            ret += mcgen('''
141    bool has_%(c_name)s;
142''',
143                         c_name=c_name(memb.name))
144        ret += mcgen('''
145    %(c_type)s %(c_name)s;
146''',
147                     c_type=memb.type.c_type(), c_name=c_name(memb.name))
148        ret += memb.ifcond.gen_endif()
149    return ret
150
151
152def gen_object(name: str, ifcond: QAPISchemaIfCond,
153               base: Optional[QAPISchemaObjectType],
154               members: List[QAPISchemaObjectTypeMember],
155               variants: Optional[QAPISchemaVariants]) -> str:
156    if name in objects_seen:
157        return ''
158    objects_seen.add(name)
159
160    ret = ''
161    for var in variants.variants if variants else ():
162        obj = var.type
163        if not isinstance(obj, QAPISchemaObjectType):
164            continue
165        ret += gen_object(obj.name, obj.ifcond, obj.base,
166                          obj.local_members, obj.variants)
167
168    ret += mcgen('''
169
170''')
171    ret += ifcond.gen_if()
172    ret += mcgen('''
173struct %(c_name)s {
174''',
175                 c_name=c_name(name))
176
177    if base:
178        if not base.is_implicit():
179            ret += mcgen('''
180    /* Members inherited from %(c_name)s: */
181''',
182                         c_name=base.c_name())
183        ret += gen_struct_members(base.members)
184        if not base.is_implicit():
185            ret += mcgen('''
186    /* Own members: */
187''')
188    ret += gen_struct_members(members)
189
190    if variants:
191        ret += gen_variants(variants)
192
193    # Make sure that all structs have at least one member; this avoids
194    # potential issues with attempting to malloc space for zero-length
195    # structs in C, and also incompatibility with C++ (where an empty
196    # struct is size 1).
197    if (not base or base.is_empty()) and not members and not variants:
198        ret += mcgen('''
199    char qapi_dummy_for_empty_struct;
200''')
201
202    ret += mcgen('''
203};
204''')
205    ret += ifcond.gen_endif()
206
207    return ret
208
209
210def gen_upcast(name: str, base: QAPISchemaObjectType) -> str:
211    # C makes const-correctness ugly.  We have to cast away const to let
212    # this function work for both const and non-const obj.
213    return mcgen('''
214
215static inline %(base)s *qapi_%(c_name)s_base(const %(c_name)s *obj)
216{
217    return (%(base)s *)obj;
218}
219''',
220                 c_name=c_name(name), base=base.c_name())
221
222
223def gen_variants(variants: QAPISchemaVariants) -> str:
224    ret = mcgen('''
225    union { /* union tag is @%(c_name)s */
226''',
227                c_name=c_name(variants.tag_member.name))
228
229    for var in variants.variants:
230        if var.type.name == 'q_empty':
231            continue
232        ret += var.ifcond.gen_if()
233        ret += mcgen('''
234        %(c_type)s %(c_name)s;
235''',
236                     c_type=var.type.c_unboxed_type(),
237                     c_name=c_name(var.name))
238        ret += var.ifcond.gen_endif()
239
240    ret += mcgen('''
241    } u;
242''')
243
244    return ret
245
246
247def gen_type_cleanup_decl(name: str) -> str:
248    ret = mcgen('''
249
250void qapi_free_%(c_name)s(%(c_name)s *obj);
251G_DEFINE_AUTOPTR_CLEANUP_FUNC(%(c_name)s, qapi_free_%(c_name)s)
252''',
253                c_name=c_name(name))
254    return ret
255
256
257def gen_type_cleanup(name: str) -> str:
258    ret = mcgen('''
259
260void qapi_free_%(c_name)s(%(c_name)s *obj)
261{
262    Visitor *v;
263
264    if (!obj) {
265        return;
266    }
267
268    v = qapi_dealloc_visitor_new();
269    visit_type_%(c_name)s(v, NULL, &obj, NULL);
270    visit_free(v);
271}
272''',
273                c_name=c_name(name))
274    return ret
275
276
277class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor):
278
279    def __init__(self, prefix: str):
280        super().__init__(
281            prefix, 'qapi-types', ' * Schema-defined QAPI types',
282            ' * Built-in QAPI types', __doc__)
283
284    def _begin_builtin_module(self) -> None:
285        self._genc.preamble_add(mcgen('''
286#include "qemu/osdep.h"
287#include "qapi/dealloc-visitor.h"
288#include "qapi/qapi-builtin-types.h"
289#include "qapi/qapi-builtin-visit.h"
290'''))
291        self._genh.preamble_add(mcgen('''
292#include "qapi/util.h"
293'''))
294
295    def _begin_user_module(self, name: str) -> None:
296        types = self._module_basename('qapi-types', name)
297        visit = self._module_basename('qapi-visit', name)
298        self._genc.preamble_add(mcgen('''
299#include "qemu/osdep.h"
300#include "qapi/dealloc-visitor.h"
301#include "%(types)s.h"
302#include "%(visit)s.h"
303''',
304                                      types=types, visit=visit))
305        self._genh.preamble_add(mcgen('''
306#include "qapi/qapi-builtin-types.h"
307'''))
308
309    def visit_begin(self, schema: QAPISchema) -> None:
310        # gen_object() is recursive, ensure it doesn't visit the empty type
311        objects_seen.add(schema.the_empty_object_type.name)
312
313    def _gen_type_cleanup(self, name: str) -> None:
314        self._genh.add(gen_type_cleanup_decl(name))
315        self._genc.add(gen_type_cleanup(name))
316
317    def visit_enum_type(self,
318                        name: str,
319                        info: Optional[QAPISourceInfo],
320                        ifcond: QAPISchemaIfCond,
321                        features: List[QAPISchemaFeature],
322                        members: List[QAPISchemaEnumMember],
323                        prefix: Optional[str]) -> None:
324        with ifcontext(ifcond, self._genh, self._genc):
325            self._genh.preamble_add(gen_enum(name, members, prefix))
326            self._genc.add(gen_enum_lookup(name, members, prefix))
327
328    def visit_array_type(self,
329                         name: str,
330                         info: Optional[QAPISourceInfo],
331                         ifcond: QAPISchemaIfCond,
332                         element_type: QAPISchemaType) -> None:
333        with ifcontext(ifcond, self._genh, self._genc):
334            self._genh.preamble_add(gen_fwd_object_or_array(name))
335            self._genh.add(gen_array(name, element_type))
336            self._gen_type_cleanup(name)
337
338    def visit_object_type(self,
339                          name: str,
340                          info: Optional[QAPISourceInfo],
341                          ifcond: QAPISchemaIfCond,
342                          features: List[QAPISchemaFeature],
343                          base: Optional[QAPISchemaObjectType],
344                          members: List[QAPISchemaObjectTypeMember],
345                          variants: Optional[QAPISchemaVariants]) -> None:
346        # Nothing to do for the special empty builtin
347        if name == 'q_empty':
348            return
349        with ifcontext(ifcond, self._genh):
350            self._genh.preamble_add(gen_fwd_object_or_array(name))
351        self._genh.add(gen_object(name, ifcond, base, members, variants))
352        with ifcontext(ifcond, self._genh, self._genc):
353            if base and not base.is_implicit():
354                self._genh.add(gen_upcast(name, base))
355            # TODO Worth changing the visitor signature, so we could
356            # directly use rather than repeat type.is_implicit()?
357            if not name.startswith('q_'):
358                # implicit types won't be directly allocated/freed
359                self._gen_type_cleanup(name)
360
361    def visit_alternate_type(self,
362                             name: str,
363                             info: Optional[QAPISourceInfo],
364                             ifcond: QAPISchemaIfCond,
365                             features: List[QAPISchemaFeature],
366                             variants: QAPISchemaVariants) -> None:
367        with ifcontext(ifcond, self._genh):
368            self._genh.preamble_add(gen_fwd_object_or_array(name))
369        self._genh.add(gen_object(name, ifcond, None,
370                                  [variants.tag_member], variants))
371        with ifcontext(ifcond, self._genh, self._genc):
372            self._gen_type_cleanup(name)
373
374
375def gen_types(schema: QAPISchema,
376              output_dir: str,
377              prefix: str,
378              opt_builtins: bool) -> None:
379    vis = QAPISchemaGenTypeVisitor(prefix)
380    schema.visit(vis)
381    vis.write(output_dir, opt_builtins)
382