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
