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