intel/genxml: Support importing from another genxml file
authorJordan Justen <jordan.l.justen@intel.com>
Sun, 25 Dec 2022 10:16:24 +0000 (02:16 -0800)
committerJordan Justen <jordan.l.justen@intel.com>
Thu, 14 Sep 2023 18:05:15 +0000 (11:05 -0700)
Signed-off-by: Jordan Justen <jordan.l.justen@intel.com>
Reviewed-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/20593>

src/intel/genxml/gen_bits_header.py
src/intel/genxml/gen_pack_header.py
src/intel/genxml/intel_genxml.py

index f6b485e..02ca6b3 100644 (file)
@@ -266,7 +266,7 @@ class XmlParser(object):
             self.container_stack.pop()
         elif name == 'field':
             self.process_field(attrs)
-        elif name == 'enum':
+        elif name in ('enum', 'import'):
             pass
         else:
             assert False
@@ -330,6 +330,7 @@ def main():
         p = XmlParser(containers)
         genxml = intel_genxml.GenXml(source)
         genxml.filter_engines(engines)
+        genxml.merge_imported()
         p.emit_genxml(genxml)
 
     included_symbols_list = pargs.include_symbols.split(',')
index 19ff279..323fa85 100644 (file)
@@ -486,6 +486,8 @@ class Parser(object):
                 self.prefix = None
         elif name == "value":
             self.values.append(Value(attrs))
+        elif name == "import":
+            pass
         else:
             assert False
 
@@ -512,7 +514,7 @@ class Parser(object):
         elif name  == "enum":
             self.emit_enum()
             self.enum = None
-        elif name  == "value":
+        elif name in ("import", "value"):
             pass
         else:
             assert False
@@ -641,6 +643,7 @@ def main():
 
     genxml = intel_genxml.GenXml(pargs.xml_source)
     genxml.filter_engines(engines)
+    genxml.merge_imported()
     p = Parser()
     p.emit_genxml(genxml)
 
index 9cb9021..682721e 100755 (executable)
@@ -7,6 +7,7 @@ from collections import OrderedDict
 import copy
 import io
 import pathlib
+import os.path
 import re
 import xml.etree.ElementTree as et
 import typing
@@ -84,6 +85,7 @@ class Struct(object):
 # ordering of the various tag attributes
 GENXML_DESC = {
     'genxml'      : [ 'name', 'gen', ],
+    'import'      : [ 'name', ],
     'enum'        : [ 'name', 'value', 'prefix', ],
     'struct'      : [ 'name', 'length', ],
     'field'       : [ 'name', 'start', 'end', 'type', 'default', 'prefix', 'nonzero' ],
@@ -129,6 +131,9 @@ def process_attribs(elem: et.Element) -> None:
 
 def sort_xml(xml: et.ElementTree) -> None:
     genxml = xml.getroot()
+
+    imports = xml.findall('import')
+
     enums = sorted(xml.findall('enum'), key=get_name)
     enum_dict: typing.Dict[str, et.Element] = {}
     for e in enums:
@@ -161,16 +166,106 @@ def sort_xml(xml: et.ElementTree) -> None:
     for r in registers:
         r[:] = sorted(r, key=get_start)
 
-    new_elems = enums + list(sorted_structs.values()) + instructions + registers
+    new_elems = (imports + enums + list(sorted_structs.values()) +
+                 instructions + registers)
     for n in new_elems:
         process_attribs(n)
     genxml[:] = new_elems
 
 
 class GenXml(object):
-    def __init__(self, filename):
+    def __init__(self, filename, import_xml=False, files=None):
+        if files is not None:
+            self.files = files
+        else:
+            self.files = set()
         self.filename = pathlib.Path(filename)
+
+        # Assert that the file hasn't already been loaded which would
+        # indicate a loop in genxml imports, and lead to infinite
+        # recursion.
+        assert self.filename not in self.files
+
+        self.files.add(self.filename)
         self.et = et.parse(self.filename)
+        if import_xml:
+            self.merge_imported()
+
+    def merge_imported(self):
+        """Merge imported items from genxml imports.
+
+        Genxml <import> tags specify that elements should be brought
+        in from another genxml source file. After this function is
+        called, these elements will become part of the `self.et` data
+        structure as if the elements had been directly included in the
+        genxml directly.
+
+        Items from imported genxml files will be completely ignore if
+        an item with the same name is already defined in the genxml
+        file.
+
+        """
+        orig_elements = set(self.et.getroot())
+        name_and_obj = lambda i: (get_name(i), i)
+        filter_ty = lambda s: filter(lambda i: i.tag == s, orig_elements)
+        filter_ty_item = lambda s: dict(map(name_and_obj, filter_ty(s)))
+
+        # orig_by_tag stores items defined directly in the genxml
+        # file. If a genxml item is defined in the genxml directly,
+        # then any imported items of the same name are ignored.
+        orig_by_tag = {
+            'enum': filter_ty_item('enum'),
+            'struct': filter_ty_item('struct'),
+            'instruction': filter_ty_item('instruction'),
+            'register': filter_ty_item('register'),
+        }
+
+        for item in orig_elements:
+            if item.tag == 'import':
+                assert 'name' in item.attrib
+                filename = os.path.split(item.attrib['name'])
+                # We should be careful to restrict loaded files to
+                # those under the source or build trees. For now, only
+                # allow siblings of the current xml file.
+                assert filename[0] == '', 'Directories not allowed with import'
+                filename = os.path.join(os.path.dirname(self.filename),
+                                        filename[1])
+                assert os.path.exists(filename), f'{self.filename} {filename}'
+
+                # Here we load the imported genxml file. We set
+                # `import_xml` to true so that any imports in the
+                # imported genxml will be merged during the loading
+                # process.
+                #
+                # The `files` parameter is a set of files that have
+                # been loaded, and it is used to prevent any cycles
+                # (infinite recursion) while loading imported genxml
+                # files.
+                genxml = GenXml(filename, import_xml=True, files=self.files)
+                imported_elements = set(genxml.et.getroot())
+
+                # `to_add` is a set of items that were imported an
+                # should be merged into the `self.et` data structure.
+                to_add = set()
+                for i in imported_elements:
+                    if i.tag not in orig_by_tag:
+                        continue
+                    if i.attrib['name'] in orig_by_tag[i.tag]:
+                        # An item with this same name was defined in
+                        # the genxml directly. There we should ignore
+                        # (not merge) the imported item.
+                        continue
+                    to_add.add(i)
+
+                # Now that we have scanned through all the items in
+                # the imported genxml file, if any items were found
+                # which should be merged, we add them into our
+                # `self.et` data structure. After this it will be as
+                # if the items had been directly present in the genxml
+                # file.
+                if len(to_add) > 0:
+                    self.et.getroot().extend(list(to_add))
+                    sort_xml(self.et)
 
     def filter_engines(self, engines):
         changed = False