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