[PATCH v3 04/74] qapi: register all introspectable types, not just QMP-reachable ones

Marc-André Lureau <[email protected]>
Newsgroups org.nongnu.qemu-devel
Message-ID <[email protected]>
Rename QAPISchemaUsedTypes to QAPISchemaTypeAnalysis and broaden type
collection: instead of discovering types transitively from commands and
events, register every non-implicit type upfront in visit_needed() and
let visit_end() resolve their dependencies.

This is needed so that QOM property types that are defined in the QAPI
schema but not referenced by any command or event still appear in
query-qmp-schema output, making them introspectable by management tools.

Commands and events still register their (often implicit) argument and
return types, which visit_needed() intentionally skips.

This makes the x86-64 schema grow from 244K to 265K.

Signed-off-by: Marc-André Lureau <[email protected]>
---
 scripts/qapi/backend.py         |  4 +--
 scripts/qapi/introspect.py      |  6 ++---
 scripts/qapi/schema_analysis.py | 55 +++++++++++++++++++++--------------------
 3 files changed, 33 insertions(+), 32 deletions(-)

diff --git a/scripts/qapi/backend.py b/scripts/qapi/backend.py
index 59329965890f..24717be48c3c 100644
--- a/scripts/qapi/backend.py
+++ b/scripts/qapi/backend.py
@@ -8,7 +8,7 @@
 from .features import gen_features
 from .introspect import gen_introspect
 from .schema import QAPISchema
-from .schema_analysis import QAPISchemaUsedTypes
+from .schema_analysis import QAPISchemaTypeAnalysis
 from .types import gen_types
 from .visit import gen_visit
 
@@ -58,7 +58,7 @@ def generate(self,
 
         :raise QAPIError: On failures.
         """
-        schema_types = QAPISchemaUsedTypes(unmask)
+        schema_types = QAPISchemaTypeAnalysis(unmask)
         schema.visit(schema_types)
         gen_types(schema, output_dir, prefix, builtins)
         gen_features(schema, output_dir, prefix)
diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
index 9e76e3aa38a9..77c29425c42d 100644
--- a/scripts/qapi/introspect.py
+++ b/scripts/qapi/introspect.py
@@ -38,7 +38,7 @@
     QAPISchemaType,
     QAPISchemaVariant,
 )
-from .schema_analysis import QAPISchemaUsedTypes
+from .schema_analysis import QAPISchemaTypeAnalysis
 from .source import QAPISourceInfo
 
 
@@ -168,7 +168,7 @@ def to_c_string(string: str) -> str:
 
 class QAPISchemaGenIntrospectVisitor(QAPISchemaMonolithicCVisitor):
 
-    def __init__(self, prefix: str, schema_types: QAPISchemaUsedTypes):
+    def __init__(self, prefix: str, schema_types: QAPISchemaTypeAnalysis):
         super().__init__(
             prefix, 'qapi-introspect',
             ' * QAPI/QMP schema introspection', __doc__)
@@ -187,7 +187,7 @@ def visit_begin(self, schema: QAPISchema) -> None:
 
     def visit_end(self) -> None:
         # visit the types that are actually used
-        for typ in self._schema_types.used_types():
+        for typ in self._schema_types.types():
             typ.visit(self)
         # generate C
         name = c_name(self._prefix, protect=False) + 'qmp_schema_qlit'
diff --git a/scripts/qapi/schema_analysis.py b/scripts/qapi/schema_analysis.py
index 7e42abbc14e1..1d12306f61e2 100644
--- a/scripts/qapi/schema_analysis.py
+++ b/scripts/qapi/schema_analysis.py
@@ -35,37 +35,37 @@
 from .source import QAPISourceInfo
 
 
-class QAPISchemaUsedTypes(QAPISchemaVisitor):
-    """Collect the set of QMP-reachable types from a schema.
+class QAPISchemaTypeAnalysis(QAPISchemaVisitor):
+    """Analyze types from a QAPI schema.
 
-    Types are discovered transitively starting from commands and events.
-    Each type is also given a masked introspection name (an integer
-    string).
+    Every non-builtin, non-array type is given a masked introspection
+    name (an integer string).
     """
 
     def __init__(self, unmask: bool):
         self._unmask = unmask
         self._schema: Optional[QAPISchema] = None
         # Ordered list + set: insert during iteration + O(1) check
-        self._used_types: List[QAPISchemaType] = []
-        self._used_types_set: Set[QAPISchemaType] = set()
+        self._types: List[QAPISchemaType] = []
+        self._types_set: Set[QAPISchemaType] = set()
         self._name_map: Dict[str, str] = {}
 
     def visit_begin(self, schema: QAPISchema) -> None:
         self._schema = schema
-        self._used_types = []
-        self._used_types_set = set()
+        self._types = []
+        self._types_set = set()
         self._name_map = {}
 
     def visit_end(self) -> None:
         assert self._schema is not None
-        # Discover transitively-used types; the list grows as
+        # Discover type dependencies; the list grows as
         # visiting each type registers the types it references.
-        for typ in self._used_types:
+        for typ in self._types:
             typ.visit(self)
-        # Assign stable masked names now that all types are known
+
+        # Assign masked names now that all introspected types are known.
         counter = 0
-        for typ in self._used_types:
+        for typ in self._types:
             if isinstance(typ, (QAPISchemaBuiltinType, QAPISchemaArrayType)):
                 continue
             self._name_map[typ.name] = (
@@ -73,8 +73,14 @@ def visit_end(self) -> None:
             counter += 1
 
     def visit_needed(self, entity: QAPISchemaEntity) -> bool:
-        # Skip types during main traversal; visit_end() handles them
-        return not isinstance(entity, QAPISchemaType)
+        # Side effect: register all introspectable types now, so that
+        # visit_end() can traverse them to discover type dependencies.
+        if isinstance(entity, QAPISchemaType):
+            if (not entity.is_implicit() or
+                    isinstance(entity, QAPISchemaArrayType)):
+                self._register_type(entity)
+            return False
+        return True
 
     def visit_command(self, name: str, info: Optional[QAPISourceInfo],
                       ifcond: QAPISchemaIfCond,
@@ -107,11 +113,6 @@ def visit_object_type_flat(
             for v in branches.variants:
                 self._register_type(v.type)
 
-    def visit_array_type(self, name: str, info: Optional[QAPISourceInfo],
-                         ifcond: QAPISchemaIfCond,
-                         element_type: QAPISchemaType) -> None:
-        self._register_type(element_type)
-
     def visit_alternate_type(
             self, name: str, info: Optional[QAPISourceInfo],
             ifcond: QAPISchemaIfCond,
@@ -121,11 +122,11 @@ def visit_alternate_type(
             self._register_type(m.type)
 
     def _register_type(self, typ: QAPISchemaType) -> None:
-        """Record a type as QMP-reachable (idempotent)."""
+        """Record a type for introspection (idempotent)."""
         typ = self._canonicalize_type(typ)
-        if typ not in self._used_types_set:
-            self._used_types.append(typ)
-            self._used_types_set.add(typ)
+        if typ not in self._types_set:
+            self._types.append(typ)
+            self._types_set.add(typ)
             if isinstance(typ, QAPISchemaArrayType):
                 self._register_type(typ.element_type)
 
@@ -156,9 +157,9 @@ def introspection_name(self, typ: QAPISchemaType) -> str:
             return typ.name
         if isinstance(typ, QAPISchemaArrayType):
             return '[' + self.introspection_name(typ.element_type) + ']'
-        assert typ in self._used_types_set
+        assert typ in self._types_set
         return self.masked_name(typ.name)
 
-    def used_types(self) -> Sequence[QAPISchemaType]:
+    def types(self) -> Sequence[QAPISchemaType]:
         """Return the types to include in QAPI introspection."""
-        return self._used_types
+        return self._types

-- 
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.