[PATCH v3 05/74] qapi: add type-infos generator

Marc-André Lureau <[email protected]>
Newsgroups org.nongnu.qemu-devel
Message-ID <[email protected]>
New QAPISchemaGenTypeInfoVisitor produces per-module
qapi-type-infos-*.h/c files. Each file declares QAPITypeInfo constants
pairing the QAPI type name with its masked introspection name.

Signed-off-by: Marc-André Lureau <[email protected]>
---
 docs/devel/qapi-code-gen.rst | 107 +++++++++++++++++++++++++-
 meson.build                  |   1 +
 scripts/qapi/backend.py      |   2 +
 scripts/qapi/introspect.py   |   2 +-
 scripts/qapi/type_infos.py   | 178 +++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 285 insertions(+), 5 deletions(-)

diff --git a/docs/devel/qapi-code-gen.rst b/docs/devel/qapi-code-gen.rst
index b1cc5b5f0db1..d176238fc2ef 100644
--- a/docs/devel/qapi-code-gen.rst
+++ b/docs/devel/qapi-code-gen.rst
@@ -1247,10 +1247,10 @@ meaningful type names instead.
 Optional member "features" exposes the entity's feature strings as a
 JSON array of strings.
 
-To examine a type, start with a command or event using it, then follow
-references by name.
-
-QAPI schema definitions not reachable that way are omitted.
+To examine a type used by QMP, start with a command or event using it,
+then follow references by name. User-defined and array types are also
+included when they are not reachable from a command or event, as they
+may also be used by QOM.
 
 The SchemaInfo for a command has meta-type "command", and variant
 members "arg-type", "ret-type" and "allow-oob".  On the wire, the
@@ -2101,3 +2101,102 @@ Example::
     }));
 
     [Uninteresting stuff omitted...]
+
+
+Code generated for type information
+-----------------------------------
+
+Type info is generated for user-defined and array types. With option
+``--builtins``, it is also generated for built-in types and the internal
+``QType`` enum. Other implicit types are skipped.
+
+The following files are created:
+
+ ``$(prefix)qapi-type-infos.c``
+     A ``QAPITypeInfo`` instance for each type listed above, providing
+     a mapping between the QAPI type name and the masked name used by
+     introspection, along with optional enum lookup table and array
+     type pointers.
+
+ ``$(prefix)qapi-type-infos.h``
+     Declarations for the above type info instances
+
+Each ``QAPITypeInfo`` struct has the following fields:
+
+``name``
+    The QAPI type name (e.g. ``"UserDefOne"``). QOM uses this as the
+    property type string.
+
+``masked_name``
+    For user-defined types, the masked name used in
+    ``query-qmp-schema`` output. For array types, the corresponding
+    bracketed introspection name. Built-in types use their QAPI name;
+    note that introspection canonicalizes the integer built-in types
+    to ``int``. Internal types not present in introspection use
+    ``NULL``.
+
+``lookup``
+    For enum types, a pointer to the corresponding ``QEnumLookup``
+    table.  ``NULL`` for non-enum types.
+
+``list``
+    For types that have an array variant, a pointer to the array type's
+    ``QAPITypeInfo``.  ``NULL`` when no array type exists.
+
+These type info instances are used by QOM property registration
+functions (``object_property_add_qapi()``,
+``object_class_property_add_qapi_enum()``, etc.) to associate each
+property with its QAPI type.  The ``qom-list`` and
+``device-list-properties`` QMP commands then expose the ``qapi-type``
+field, giving management tools a formal type reference they can look
+up in the introspection schema.
+
+All user-defined and array types are included in introspection, even when
+they are not reachable from a command or event.  Their type info therefore
+always provides the introspection cross-reference.
+
+Example::
+
+    $ cat qapi-generated/example-qapi-type-infos.h
+    [Uninteresting stuff omitted...]
+
+    #ifndef EXAMPLE_QAPI_TYPE_INFOS_H
+    #define EXAMPLE_QAPI_TYPE_INFOS_H
+
+    #include "qapi/qapi-builtin-type-infos.h"
+
+    extern const QAPITypeInfo UserDefOne_type_info;
+
+    extern const QAPITypeInfo UserDefOneList_type_info;
+
+    #endif /* EXAMPLE_QAPI_TYPE_INFOS_H */
+    $ cat qapi-generated/example-qapi-type-infos.c
+    [Uninteresting stuff omitted...]
+
+    const QAPITypeInfo UserDefOne_type_info = {
+        .name = "UserDefOne",
+        .masked_name = "1",
+        .list = &UserDefOneList_type_info,
+    };
+
+    const QAPITypeInfo UserDefOneList_type_info = {
+        .name = "UserDefOneList",
+        .masked_name = "[1]",
+    };
+
+    [Uninteresting stuff omitted...]
+
+For a modular QAPI schema (see section `Include directives`_), code for
+each sub-module SUBDIR/SUBMODULE.json is actually generated into ::
+
+ SUBDIR/$(prefix)qapi-type-infos-SUBMODULE.h
+ SUBDIR/$(prefix)qapi-type-infos-SUBMODULE.c
+
+If qapi-gen.py is run with option --builtins, additional files are
+created:
+
+ ``qapi-builtin-type-infos.h``
+     Type info instances for built-in types
+
+ ``qapi-builtin-type-infos.c``
+     Definitions for the above type info instances
diff --git a/meson.build b/meson.build
index 6ddb323c13f5..adefc773c329 100644
--- a/meson.build
+++ b/meson.build
@@ -3501,6 +3501,7 @@ qapi_gen_depends = [ meson.current_source_dir() / 'scripts/qapi/__init__.py',
                      meson.current_source_dir() / 'scripts/qapi/schema.py',
                      meson.current_source_dir() / 'scripts/qapi/schema_analysis.py',
                      meson.current_source_dir() / 'scripts/qapi/source.py',
+                     meson.current_source_dir() / 'scripts/qapi/type_infos.py',
                      meson.current_source_dir() / 'scripts/qapi/types.py',
                      meson.current_source_dir() / 'scripts/qapi/visit.py',
                      meson.current_source_dir() / 'scripts/qapi-gen.py'
diff --git a/scripts/qapi/backend.py b/scripts/qapi/backend.py
index 24717be48c3c..7c2b47a41ae6 100644
--- a/scripts/qapi/backend.py
+++ b/scripts/qapi/backend.py
@@ -9,6 +9,7 @@
 from .introspect import gen_introspect
 from .schema import QAPISchema
 from .schema_analysis import QAPISchemaTypeAnalysis
+from .type_infos import gen_type_infos
 from .types import gen_types
 from .visit import gen_visit
 
@@ -66,3 +67,4 @@ def generate(self,
         gen_commands(schema, output_dir, prefix, gen_tracing)
         gen_events(schema, output_dir, prefix)
         gen_introspect(schema, output_dir, prefix, schema_types)
+        gen_type_infos(schema, output_dir, prefix, builtins, schema_types)
diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
index 77c29425c42d..ce37ce787567 100644
--- a/scripts/qapi/introspect.py
+++ b/scripts/qapi/introspect.py
@@ -351,7 +351,7 @@ def visit_event(self, name: str, info: Optional[QAPISourceInfo],
 
 
 def gen_introspect(schema: QAPISchema, output_dir: str, prefix: str,
-                   schema_types: QAPISchemaUsedTypes) -> None:
+                   schema_types: QAPISchemaTypeAnalysis) -> None:
     vis = QAPISchemaGenIntrospectVisitor(prefix, schema_types)
     schema.visit(vis)
     vis.write(output_dir)
diff --git a/scripts/qapi/type_infos.py b/scripts/qapi/type_infos.py
new file mode 100644
index 000000000000..b9e249584e72
--- /dev/null
+++ b/scripts/qapi/type_infos.py
@@ -0,0 +1,178 @@
+"""
+QAPI type info generator
+
+SPDX-License-Identifier: GPL-2.0-or-later
+"""
+
+from typing import List, Optional
+
+from .common import c_name, mcgen
+from .gen import QAPISchemaModularCVisitor, ifcontext
+from .schema import (
+    QAPISchema,
+    QAPISchemaAlternatives,
+    QAPISchemaBranches,
+    QAPISchemaEnumMember,
+    QAPISchemaFeature,
+    QAPISchemaIfCond,
+    QAPISchemaObjectType,
+    QAPISchemaObjectTypeMember,
+    QAPISchemaType,
+)
+from .schema_analysis import QAPISchemaTypeAnalysis
+from .source import QAPISourceInfo
+
+
+class QAPISchemaGenTypeInfoVisitor(QAPISchemaModularCVisitor):
+
+    def __init__(self, prefix: str, schema_types: QAPISchemaTypeAnalysis):
+        super().__init__(
+            prefix, 'qapi-type-infos',
+            ' * Schema-defined QAPI type info',
+            ' * Built-in QAPI type info', __doc__)
+        self._schema_types = schema_types
+        self._schema: Optional[QAPISchema] = None
+
+    def visit_begin(self, schema: QAPISchema) -> None:
+        super().visit_begin(schema)
+        self._schema = schema
+
+    def _begin_builtin_module(self) -> None:
+        self._genc.preamble_add(mcgen('''
+#include "qemu/osdep.h"
+#include "qapi/qapi-builtin-types.h"
+#include "qapi/qapi-builtin-type-infos.h"
+'''))
+        self._genh.preamble_add(mcgen('''
+#include "qapi/qapi-type-info.h"
+'''))
+
+    def _begin_user_module(self, name: str) -> None:
+        type_infos = self._module_basename('qapi-type-infos', name)
+        types = self._module_basename('qapi-types', name)
+        self._genc.preamble_add(mcgen('''
+#include "qemu/osdep.h"
+#include "%(types)s.h"
+#include "%(type_infos)s.h"
+''',
+                                      types=types,
+                                      type_infos=type_infos))
+        self._genh.preamble_add(mcgen('''
+#include "qapi/qapi-builtin-type-infos.h"
+'''))
+
+    def _gen_type_info(self, name: str,
+                       ifcond: Optional[QAPISchemaIfCond] = None,
+                       with_lookup: bool = False,
+                       with_list: bool = False,
+                       masked_name: Optional[str] = None) -> None:
+        c_id = c_name(name + '_type_info')
+        if masked_name is not None:
+            masked_name_str = '"%s"' % masked_name
+        else:
+            masked_name_str = 'NULL'
+        with ifcontext(ifcond or QAPISchemaIfCond(),
+                       self._genh, self._genc):
+            self._genh.add(mcgen('''
+
+extern const QAPITypeInfo %(c_id)s;
+''',
+                                 c_id=c_id))
+            self._genc.add(mcgen('''
+
+const QAPITypeInfo %(c_id)s = {
+    .name = "%(name)s",
+    .masked_name = %(masked_name)s,
+''',
+                                 c_id=c_id, name=name,
+                                 masked_name=masked_name_str))
+            if with_lookup:
+                self._genc.add(mcgen('''
+    .lookup = &%(c_name)s_lookup,
+''',
+                                     c_name=c_name(name)))
+            if with_list:
+                self._genc.add(mcgen('''
+    .list = &%(list_id)s,
+''',
+                                     list_id=c_name(name + 'List_type_info')))
+            self._genc.add(mcgen('''
+};
+'''))
+
+    def _has_list(self, name: str) -> bool:
+        assert self._schema is not None
+        return self._schema.lookup_type(name + 'List') is not None
+
+    def _masked_name(self, name: str) -> Optional[str]:
+        assert self._schema is not None
+        typ = self._schema.lookup_type(name)
+        assert typ is not None
+        if typ.is_implicit():
+            return None
+        return self._schema_types.masked_name(name)
+
+    def visit_builtin_type(self,
+                           name: str,
+                           info: Optional[QAPISourceInfo],
+                           json_type: str) -> None:
+        assert self._schema is not None
+        typ = self._schema.lookup_type(name)
+        assert typ is not None
+        masked_name = self._schema_types.introspection_name(typ)
+        self._gen_type_info(name, with_list=self._has_list(name),
+                            masked_name=masked_name)
+
+    def visit_enum_type(self,
+                        name: str,
+                        info: Optional[QAPISourceInfo],
+                        ifcond: QAPISchemaIfCond,
+                        features: List[QAPISchemaFeature],
+                        members: List[QAPISchemaEnumMember],
+                        prefix: Optional[str]) -> None:
+        self._gen_type_info(name, ifcond, with_lookup=True,
+                            with_list=self._has_list(name),
+                            masked_name=self._masked_name(name))
+
+    def visit_object_type(self,
+                          name: str,
+                          info: Optional[QAPISourceInfo],
+                          ifcond: QAPISchemaIfCond,
+                          features: List[QAPISchemaFeature],
+                          base: Optional[QAPISchemaObjectType],
+                          members: List[QAPISchemaObjectTypeMember],
+                          branches: Optional[QAPISchemaBranches]) -> None:
+        if name.startswith('q_'):
+            return
+        self._gen_type_info(name, ifcond,
+                            with_list=self._has_list(name),
+                            masked_name=self._masked_name(name))
+
+    def visit_array_type(self,
+                         name: str,
+                         info: Optional[QAPISourceInfo],
+                         ifcond: QAPISchemaIfCond,
+                         element_type: QAPISchemaType) -> None:
+        elem_schema = self._schema_types.introspection_name(element_type)
+        masked_name = '[' + elem_schema + ']'
+        self._gen_type_info(name, ifcond, masked_name=masked_name)
+
+    def visit_alternate_type(self,
+                             name: str,
+                             info: Optional[QAPISourceInfo],
+                             ifcond: QAPISchemaIfCond,
+                             features: List[QAPISchemaFeature],
+                             alternatives: QAPISchemaAlternatives) -> None:
+        self._gen_type_info(name, ifcond,
+                            with_list=self._has_list(name),
+                            masked_name=self._masked_name(name))
+
+
+def gen_type_infos(schema: QAPISchema,
+                   output_dir: str,
+                   prefix: str,
+                   opt_builtins: bool,
+                   schema_types: QAPISchemaTypeAnalysis) -> None:
+    vis = QAPISchemaGenTypeInfoVisitor(prefix, schema_types)
+    schema.visit(vis)
+    vis.write(output_dir, opt_builtins)

-- 
2.55.0.543.g5ebe2ebe4ea8
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.