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