mesa/src/intel/genxml/intel_genxml.py
Jordan Justen 549540ca7c intel/genxml: Add final newline to output when saving xml
Signed-off-by: Jordan Justen <jordan.l.justen@intel.com>
Reviewed-by: Dylan Baker <dylan@pnwbakers>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/24605>
2023-08-14 23:09:36 +00:00

220 lines
6.9 KiB
Python
Executable file

#!/usr/bin/env python3
# Copyright © 2019, 2022 Intel Corporation
# SPDX-License-Identifier: MIT
from __future__ import annotations
from collections import OrderedDict
import copy
import io
import pathlib
import re
import xml.etree.ElementTree as et
import typing
if typing.TYPE_CHECKING:
class Args(typing.Protocol):
files: typing.List[pathlib.Path]
validate: bool
quiet: bool
def get_filename(element: et.Element) -> str:
return element.attrib['filename']
def get_name(element: et.Element) -> str:
return element.attrib['name']
def get_value(element: et.Element) -> int:
return int(element.attrib['value'], 0)
def get_start(element: et.Element) -> int:
return int(element.attrib['start'], 0)
BASE_TYPES = {
'address',
'offset',
'int',
'uint',
'bool',
'float',
'mbz',
'mbo',
}
FIXED_PATTERN = re.compile(r"(s|u)(\d+)\.(\d+)")
def is_base_type(name: str) -> bool:
return name in BASE_TYPES or FIXED_PATTERN.match(name) is not None
def add_struct_refs(items: typing.OrderedDict[str, bool], node: et.Element) -> None:
if node.tag == 'field':
if 'type' in node.attrib and not is_base_type(node.attrib['type']):
t = node.attrib['type']
items[t] = True
return
if node.tag not in {'struct', 'group'}:
return
for c in node:
add_struct_refs(items, c)
class Struct(object):
def __init__(self, xml: et.Element):
self.xml = xml
self.name = xml.attrib['name']
self.deps: typing.OrderedDict[str, Struct] = OrderedDict()
def find_deps(self, struct_dict, enum_dict) -> None:
deps: typing.OrderedDict[str, bool] = OrderedDict()
add_struct_refs(deps, self.xml)
for d in deps.keys():
if d in struct_dict:
self.deps[d] = struct_dict[d]
else:
assert d in enum_dict
def add_xml(self, items: typing.OrderedDict[str, et.Element]) -> None:
for d in self.deps.values():
d.add_xml(items)
items[self.name] = self.xml
# ordering of the various tag attributes
GENXML_DESC = {
'genxml' : [ 'name', 'gen', ],
'enum' : [ 'name', 'value', 'prefix', ],
'struct' : [ 'name', 'length', ],
'field' : [ 'name', 'start', 'end', 'type', 'default', 'prefix', 'nonzero' ],
'instruction' : [ 'name', 'bias', 'length', 'engine', ],
'value' : [ 'name', 'value', 'dont_use', ],
'group' : [ 'count', 'start', 'size', ],
'register' : [ 'name', 'length', 'num', ],
}
def node_validator(old: et.Element, new: et.Element) -> bool:
"""Compare to ElementTree Element nodes.
There is no builtin equality method, so calling `et.Element == et.Element` is
equivalent to calling `et.Element is et.Element`. We instead want to compare
that the contents are the same, including the order of children and attributes
"""
return (
# Check that the attributes are the same
old.tag == new.tag and
old.text == new.text and
old.tail == new.tail and
list(old.attrib.items()) == list(new.attrib.items()) and
len(old) == len(new) and
# check that there are no unexpected attributes
set(new.attrib).issubset(GENXML_DESC[new.tag]) and
# check that the attributes are sorted
list(new.attrib) == list(old.attrib) and
all(node_validator(f, s) for f, s in zip(old, new))
)
def process_attribs(elem: et.Element) -> None:
valid = GENXML_DESC[elem.tag]
# sort and prune attributes
elem.attrib = OrderedDict(sorted(((k, v) for k, v in elem.attrib.items() if k in valid),
key=lambda x: valid.index(x[0])))
for e in elem:
process_attribs(e)
def sort_xml(xml: et.ElementTree) -> None:
genxml = xml.getroot()
enums = sorted(xml.findall('enum'), key=get_name)
enum_dict: typing.Dict[str, et.Element] = {}
for e in enums:
e[:] = sorted(e, key=get_value)
enum_dict[e.attrib['name']] = e
# Structs are a bit annoying because they can refer to each other. We sort
# them alphabetically and then build a graph of dependencies. Finally we go
# through the alphabetically sorted list and print out dependencies first.
structs = sorted(xml.findall('./struct'), key=get_name)
wrapped_struct_dict: typing.Dict[str, Struct] = {}
for s in structs:
s[:] = sorted(s, key=get_start)
ws = Struct(s)
wrapped_struct_dict[ws.name] = ws
for ws in wrapped_struct_dict.values():
ws.find_deps(wrapped_struct_dict, enum_dict)
sorted_structs: typing.OrderedDict[str, et.Element] = OrderedDict()
for s in structs:
_s = wrapped_struct_dict[s.attrib['name']]
_s.add_xml(sorted_structs)
instructions = sorted(xml.findall('./instruction'), key=get_name)
for i in instructions:
i[:] = sorted(i, key=get_start)
registers = sorted(xml.findall('./register'), key=get_name)
for r in registers:
r[:] = sorted(r, key=get_start)
new_elems = 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):
self.filename = pathlib.Path(filename)
self.et = et.parse(self.filename)
def filter_engines(self, engines):
changed = False
items = []
for item in self.et.getroot():
# When an instruction doesn't have the engine specified,
# it is considered to be for all engines. Otherwise, we
# check to see if it's tagged for the engines requested.
if item.tag == 'instruction' and 'engine' in item.attrib:
i_engines = set(item.attrib["engine"].split('|'))
if not (i_engines & engines):
# Drop this instruction because it doesn't support
# the requested engine types.
changed = True
continue
items.append(item)
if changed:
self.et.getroot()[:] = items
def sort(self):
sort_xml(self.et)
def sorted_copy(self):
clone = copy.deepcopy(self)
clone.sort()
return clone
def is_equivalent_xml(self, other):
return all(node_validator(old, new)
for old, new in zip(self.et.getroot(), other.et.getroot()))
def write_file(self):
try:
old_genxml = GenXml(self.filename)
if self.is_equivalent_xml(old_genxml):
return
except Exception:
pass
b_io = io.BytesIO()
et.indent(self.et, space=' ')
self.et.write(b_io, encoding="utf-8", xml_declaration=True)
b_io.write(b'\n')
tmp = self.filename.with_suffix(f'{self.filename.suffix}.tmp')
tmp.write_bytes(b_io.getvalue())
tmp.replace(self.filename)