| # encoding=utf-8 |
| |
| # Copyright © 2022 Imagination Technologies Ltd. |
| |
| # based on anv driver gen_pack_header.py which is: |
| # Copyright © 2016 Intel Corporation |
| |
| # based on v3dv driver gen_pack_header.py which is: |
| # Copyright (C) 2016 Broadcom |
| |
| # Permission is hereby granted, free of charge, to any person obtaining a copy |
| # of this software and associated documentation files (the "Software"), to deal |
| # in the Software without restriction, including without limitation the rights |
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| # copies of the Software, and to permit persons to whom the Software is |
| # furnished to do so, subject to the following conditions: |
| |
| # The above copyright notice and this permission notice (including the next |
| # paragraph) shall be included in all copies or substantial portions of the |
| # Software. |
| |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| # SOFTWARE. |
| |
| from __future__ import annotations |
| |
| import copy |
| import os |
| import textwrap |
| import typing as t |
| import xml.parsers.expat as expat |
| from abc import ABC |
| from ast import literal_eval |
| |
| |
| MIT_LICENSE_COMMENT = """/* |
| * Copyright © %(copyright)s |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the next |
| * paragraph) shall be included in all copies or substantial portions of the |
| * Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */""" |
| |
| PACK_FILE_HEADER = """%(license)s |
| |
| /* Enums, structures and pack functions for %(platform)s. |
| * |
| * This file has been generated, do not hand edit. |
| */ |
| |
| #ifndef %(guard)s |
| #define %(guard)s |
| |
| #include "csbgen/pvr_packet_helpers.h" |
| |
| """ |
| |
| |
| def safe_name(name: str) -> str: |
| if not name[0].isalpha(): |
| name = "_" + name |
| |
| return name |
| |
| |
| def num_from_str(num_str: str) -> int: |
| if num_str.lower().startswith("0x"): |
| return int(num_str, base=16) |
| |
| if num_str.startswith("0") and len(num_str) > 1: |
| raise ValueError("Octal numbers not allowed") |
| |
| return int(num_str) |
| |
| |
| class Node(ABC): |
| __slots__ = ["parent", "name"] |
| |
| parent: Node |
| name: str |
| |
| def __init__(self, parent: Node, name: str, *, name_is_safe: bool = False) -> None: |
| self.parent = parent |
| if name_is_safe: |
| self.name = name |
| else: |
| self.name = safe_name(name) |
| |
| @property |
| def full_name(self) -> str: |
| if self.name[0] == "_": |
| return self.parent.prefix + self.name.upper() |
| |
| return self.parent.prefix + "_" + self.name.upper() |
| |
| @property |
| def prefix(self) -> str: |
| return self.parent.prefix |
| |
| def add(self, element: Node) -> None: |
| raise RuntimeError("Element cannot be nested in %s. Element Type: %s" |
| % (type(self).__name__.lower(), type(element).__name__)) |
| |
| |
| class Csbgen(Node): |
| __slots__ = ["prefix_field", "filename", "_defines", "_enums", "_structs"] |
| |
| prefix_field: str |
| filename: str |
| _defines: t.List[Define] |
| _enums: t.Dict[str, Enum] |
| _structs: t.Dict[str, Struct] |
| |
| def __init__(self, name: str, prefix: str, filename: str) -> None: |
| super().__init__(None, name.upper()) |
| self.prefix_field = safe_name(prefix.upper()) |
| self.filename = filename |
| |
| self._defines = [] |
| self._enums = {} |
| self._structs = {} |
| |
| @property |
| def full_name(self) -> str: |
| return self.name + "_" + self.prefix_field |
| |
| @property |
| def prefix(self) -> str: |
| return self.full_name |
| |
| def add(self, element: Node) -> None: |
| if isinstance(element, Enum): |
| if element.name in self._enums: |
| raise RuntimeError("Enum redefined. Enum: %s" % element.name) |
| |
| self._enums[element.name] = element |
| elif isinstance(element, Struct): |
| if element.name in self._structs: |
| raise RuntimeError("Struct redefined. Struct: %s" % element.name) |
| |
| self._structs[element.name] = element |
| elif isinstance(element, Define): |
| define_names = [d.full_name for d in self._defines] |
| if element.full_name in define_names: |
| raise RuntimeError("Define redefined. Define: %s" % element.full_name) |
| |
| self._defines.append(element) |
| else: |
| super().add(element) |
| |
| def _gen_guard(self) -> str: |
| return os.path.basename(self.filename).replace(".xml", "_h").upper() |
| |
| def emit(self) -> None: |
| print(PACK_FILE_HEADER % { |
| "license": MIT_LICENSE_COMMENT % {"copyright": "2022 Imagination Technologies Ltd."}, |
| "platform": self.name, |
| "guard": self._gen_guard(), |
| }) |
| |
| for define in self._defines: |
| define.emit() |
| |
| print() |
| |
| for enum in self._enums.values(): |
| enum.emit() |
| |
| for struct in self._structs.values(): |
| struct.emit(self) |
| |
| print("#endif /* %s */" % self._gen_guard()) |
| |
| def is_known_struct(self, struct_name: str) -> bool: |
| return struct_name in self._structs.keys() |
| |
| def is_known_enum(self, enum_name: str) -> bool: |
| return enum_name in self._enums.keys() |
| |
| def get_enum(self, enum_name: str) -> Enum: |
| return self._enums[enum_name] |
| |
| def get_struct(self, struct_name: str) -> Struct: |
| return self._structs[struct_name] |
| |
| |
| class Enum(Node): |
| __slots__ = ["_values"] |
| |
| _values: t.Dict[str, Value] |
| |
| def __init__(self, parent: Node, name: str) -> None: |
| super().__init__(parent, name) |
| |
| self._values = {} |
| |
| self.parent.add(self) |
| |
| # We override prefix so that the values will contain the enum's name too. |
| @property |
| def prefix(self) -> str: |
| return self.full_name |
| |
| def get_value(self, value_name: str) -> Value: |
| return self._values[value_name] |
| |
| def add(self, element: Node) -> None: |
| if not isinstance(element, Value): |
| super().add(element) |
| |
| if element.name in self._values: |
| raise RuntimeError("Value is being redefined. Value: '%s'" % element.name) |
| |
| self._values[element.name] = element |
| |
| def emit(self) -> None: |
| # This check is invalid if tags other than Value can be nested within an enum. |
| if not self._values.values(): |
| raise RuntimeError("Enum definition is empty. Enum: '%s'" % self.full_name) |
| |
| print("enum %s {" % self.full_name) |
| for value in self._values.values(): |
| value.emit() |
| print("};\n") |
| |
| |
| class Value(Node): |
| __slots__ = ["value"] |
| |
| value: int |
| |
| def __init__(self, parent: Node, name: str, value: int) -> None: |
| super().__init__(parent, name) |
| |
| self.value = value |
| |
| self.parent.add(self) |
| |
| def emit(self): |
| print(" %-36s = %6d," % (self.full_name, self.value)) |
| |
| |
| class Struct(Node): |
| __slots__ = ["length", "size", "_children"] |
| |
| length: int |
| size: int |
| _children: t.Dict[str, t.Union[Condition, Field]] |
| |
| def __init__(self, parent: Node, name: str, length: int) -> None: |
| super().__init__(parent, name) |
| |
| self.length = length |
| self.size = self.length * 32 |
| |
| if self.length <= 0: |
| raise ValueError("Struct length must be greater than 0. Struct: '%s'." % self.full_name) |
| |
| self._children = {} |
| |
| self.parent.add(self) |
| |
| @property |
| def fields(self) -> t.List[Field]: |
| # TODO: Should we cache? See TODO in equivalent Condition getter. |
| |
| fields = [] |
| for child in self._children.values(): |
| if isinstance(child, Condition): |
| fields += child.fields |
| else: |
| fields.append(child) |
| |
| return fields |
| |
| @property |
| def prefix(self) -> str: |
| return self.full_name |
| |
| def add(self, element: Node) -> None: |
| # We don't support conditions and field having the same name. |
| if isinstance(element, Field): |
| if element.name in self._children.keys(): |
| raise ValueError("Field is being redefined. Field: '%s', Struct: '%s'" |
| % (element.name, self.full_name)) |
| |
| self._children[element.name] = element |
| |
| elif isinstance(element, Condition): |
| # We only save ifs, and ignore the rest. The rest will be linked to |
| # the if condition so we just need to call emit() on the if and the |
| # rest will also be emitted. |
| if element.type == "if": |
| self._children[element.name] = element |
| else: |
| if element.name not in self._children.keys(): |
| raise RuntimeError("Unknown condition: '%s'" % element.name) |
| |
| else: |
| super().add(element) |
| |
| def _emit_header(self, root: Csbgen) -> None: |
| default_fields = [] |
| for field in (f for f in self.fields if f.default is not None): |
| if field.is_builtin_type: |
| default_fields.append(" .%-35s = %6d" % (field.name, field.default)) |
| else: |
| if not root.is_known_enum(field.type): |
| # Default values should not apply to structures |
| raise RuntimeError( |
| "Unknown type. Field: '%s' Type: '%s'" |
| % (field.name, field.type) |
| ) |
| |
| enum = root.get_enum(field.type) |
| |
| try: |
| value = enum.get_value(field.default) |
| except KeyError: |
| raise ValueError("Unknown enum value. Value: '%s', Enum: '%s', Field: '%s'" |
| % (field.default, enum.full_name, field.name)) |
| |
| default_fields.append(" .%-35s = %s" % (field.name, value.full_name)) |
| |
| print("#define %-40s\\" % (self.full_name + "_header")) |
| print(", \\\n".join(default_fields)) |
| print("") |
| |
| def _emit_helper_macros(self) -> None: |
| for field in (f for f in self.fields if f.defines): |
| print("/* Helper macros for %s */" % field.name) |
| |
| for define in field.defines: |
| define.emit() |
| |
| print() |
| |
| def _emit_pack_function(self, root: Csbgen) -> None: |
| print(textwrap.dedent("""\ |
| static inline __attribute__((always_inline)) void |
| %s_pack(__attribute__((unused)) void * restrict dst, |
| %s__attribute__((unused)) const struct %s * restrict values) |
| {""") % (self.full_name, ' ' * len(self.full_name), self.full_name)) |
| |
| group = Group(0, 1, self.size, self.fields) |
| dwords, length = group.collect_dwords_and_length() |
| if length: |
| # Cast dst to make header C++ friendly |
| print(" uint32_t * restrict dw = (uint32_t * restrict) dst;") |
| |
| group.emit_pack_function(root, dwords, length) |
| |
| print("}\n") |
| |
| def _emit_unpack_function(self, root: Csbgen) -> None: |
| print(textwrap.dedent("""\ |
| static inline __attribute__((always_inline)) void |
| %s_unpack(__attribute__((unused)) const void * restrict src, |
| %s__attribute__((unused)) struct %s * restrict values) |
| {""") % (self.full_name, ' ' * len(self.full_name), self.full_name)) |
| |
| group = Group(0, 1, self.size, self.fields) |
| dwords, length = group.collect_dwords_and_length() |
| if length: |
| # Cast src to make header C++ friendly |
| print(" const uint32_t * restrict dw = (const uint32_t * restrict) src;") |
| |
| group.emit_unpack_function(root, dwords, length) |
| |
| print("}\n") |
| |
| def emit(self, root: Csbgen) -> None: |
| print("#define %-33s %6d" % (self.full_name + "_length", self.length)) |
| |
| self._emit_header(root) |
| |
| self._emit_helper_macros() |
| |
| print("struct %s {" % self.full_name) |
| for child in self._children.values(): |
| child.emit(root) |
| print("};\n") |
| |
| self._emit_pack_function(root) |
| self._emit_unpack_function(root) |
| |
| |
| class Field(Node): |
| __slots__ = ["start", "end", "type", "default", "shift", "_defines"] |
| |
| start: int |
| end: int |
| type: str |
| default: t.Optional[t.Union[str, int]] |
| shift: t.Optional[int] |
| _defines: t.Dict[str, Define] |
| |
| def __init__(self, parent: Node, name: str, start: int, end: int, ty: str, *, |
| default: t.Optional[str] = None, shift: t.Optional[int] = None) -> None: |
| super().__init__(parent, name) |
| |
| self.start = start |
| self.end = end |
| self.type = ty |
| |
| self._defines = {} |
| |
| self.parent.add(self) |
| |
| if self.start > self.end: |
| raise ValueError("Start cannot be after end. Start: %d, End: %d, Field: '%s'" |
| % (self.start, self.end, self.name)) |
| |
| if self.type == "bool" and self.end != self.start: |
| raise ValueError("Bool field can only be 1 bit long. Field '%s'" % self.name) |
| |
| if default is not None: |
| if not self.is_builtin_type: |
| # Assuming it's an enum type. |
| self.default = safe_name(default) |
| else: |
| self.default = num_from_str(default) |
| else: |
| self.default = None |
| |
| if shift is not None: |
| if self.type != "address": |
| raise RuntimeError("Only address fields can have a shift attribute. Field: '%s'" % self.name) |
| |
| self.shift = int(shift) |
| |
| Define(self, "ALIGNMENT", 2**self.shift) |
| else: |
| if self.type == "address": |
| raise RuntimeError("Field of address type requires a shift attribute. Field '%s'" % self.name) |
| |
| self.shift = None |
| |
| @property |
| def defines(self) -> t.Iterator[Define]: |
| return self._defines.values() |
| |
| # We override prefix so that the defines will contain the field's name too. |
| @property |
| def prefix(self) -> str: |
| return self.full_name |
| |
| @property |
| def is_builtin_type(self) -> bool: |
| builtins = {"address", "bool", "float", "mbo", "offset", "int", "uint"} |
| return self.type in builtins |
| |
| def _get_c_type(self, root: Csbgen) -> str: |
| if self.type == "address": |
| return "__pvr_address_type" |
| elif self.type == "bool": |
| return "bool" |
| elif self.type == "float": |
| return "float" |
| elif self.type == "offset": |
| return "uint64_t" |
| elif self.type == "int": |
| return "int32_t" |
| elif self.type == "uint": |
| if self.end - self.start < 32: |
| return "uint32_t" |
| elif self.end - self.start < 64: |
| return "uint64_t" |
| |
| raise RuntimeError("No known C type found to hold %d bit sized value. Field: '%s'" |
| % (self.end - self.start, self.name)) |
| elif root.is_known_struct(self.type): |
| return "struct " + self.type |
| elif root.is_known_enum(self.type): |
| return "enum " + root.get_enum(self.type).full_name |
| raise RuntimeError("Unknown type. Type: '%s', Field: '%s'" % (self.type, self.name)) |
| |
| def add(self, element: Node) -> None: |
| if self.type == "mbo": |
| raise RuntimeError("No element can be nested in an mbo field. Element Type: %s, Field: %s" |
| % (type(element).__name__, self.name)) |
| |
| if isinstance(element, Define): |
| if element.name in self._defines: |
| raise RuntimeError("Duplicate define. Define: '%s'" % element.name) |
| |
| self._defines[element.name] = element |
| else: |
| super().add(element) |
| |
| def emit(self, root: Csbgen) -> None: |
| if self.type == "mbo": |
| return |
| |
| print(" %-36s %s;" % (self._get_c_type(root), self.name)) |
| |
| |
| class Define(Node): |
| __slots__ = ["value"] |
| |
| value: int |
| |
| def __init__(self, parent: Node, name: str, value: int) -> None: |
| super().__init__(parent, name) |
| |
| self.value = value |
| |
| self.parent.add(self) |
| |
| def emit(self) -> None: |
| print("#define %-40s %d" % (self.full_name, self.value)) |
| |
| |
| class Condition(Node): |
| __slots__ = ["type", "_children", "_child_branch"] |
| |
| type: str |
| _children: t.Dict[str, t.Union[Condition, Field]] |
| _child_branch: t.Optional[Condition] |
| |
| def __init__(self, parent: Node, name: str, ty: str) -> None: |
| super().__init__(parent, name, name_is_safe=True) |
| |
| self.type = ty |
| if not Condition._is_valid_type(self.type): |
| raise RuntimeError("Unknown type: '%s'" % self.name) |
| |
| self._children = {} |
| |
| # This is the link to the next branch for the if statement so either |
| # elif, else, or endif. They themselves will also have a link to the |
| # next branch up until endif which terminates the chain. |
| self._child_branch = None |
| |
| self.parent.add(self) |
| |
| @property |
| def fields(self) -> t.List[Field]: |
| # TODO: Should we use some kind of state to indicate the all of the |
| # child nodes have been added and then cache the fields in here on the |
| # first call so that we don't have to traverse them again per each call? |
| # The state could be changed wither when we reach the endif and pop from |
| # the context, or when we start emitting. |
| |
| fields = [] |
| |
| for child in self._children.values(): |
| if isinstance(child, Condition): |
| fields += child.fields |
| else: |
| fields.append(child) |
| |
| if self._child_branch is not None: |
| fields += self._child_branch.fields |
| |
| return fields |
| |
| @staticmethod |
| def _is_valid_type(ty: str) -> bool: |
| types = {"if", "elif", "else", "endif"} |
| return ty in types |
| |
| def _is_compatible_child_branch(self, branch): |
| types = ["if", "elif", "else", "endif"] |
| idx = types.index(self.type) |
| return (branch.type in types[idx + 1:] or |
| self.type == "elif" and branch.type == "elif") |
| |
| def _add_branch(self, branch: Condition) -> None: |
| if branch.type == "elif" and branch.name == self.name: |
| raise RuntimeError("Elif branch cannot have same check as previous branch. Check: '%s'" % branch.name) |
| |
| if not self._is_compatible_child_branch(branch): |
| raise RuntimeError("Invalid branch. Check: '%s', Type: '%s'" % (branch.name, branch.type)) |
| |
| self._child_branch = branch |
| |
| # Returns the name of the if condition. This is used for elif branches since |
| # they have a different name than the if condition thus we have to traverse |
| # the chain of branches. |
| # This is used to discriminate nested if conditions from branches since |
| # branches like 'endif' and 'else' will have the same name as the 'if' (the |
| # elif is an exception) while nested conditions will have different names. |
| # |
| # TODO: Redo this to improve speed? Would caching this be helpful? We could |
| # just save the name of the if instead of having to walk towards it whenever |
| # a new condition is being added. |
| def _top_branch_name(self) -> str: |
| if self.type == "if": |
| return self.name |
| |
| # If we're not an 'if' condition, our parent must be another condition. |
| assert isinstance(self.parent, Condition) |
| return self.parent._top_branch_name() |
| |
| def add(self, element: Node) -> None: |
| if isinstance(element, Field): |
| if element.name in self._children.keys(): |
| raise ValueError("Duplicate field. Field: '%s'" % element.name) |
| |
| self._children[element.name] = element |
| elif isinstance(element, Condition): |
| if element.type == "elif" or self._top_branch_name() == element.name: |
| self._add_branch(element) |
| else: |
| if element.type != "if": |
| raise RuntimeError("Branch of an unopened if condition. Check: '%s', Type: '%s'." |
| % (element.name, element.type)) |
| |
| # This is a nested condition and we made sure that the name |
| # doesn't match _top_branch_name() so we can recognize the else |
| # and endif. |
| # We recognized the elif by its type however its name differs |
| # from the if condition thus when we add an if condition with |
| # the same name as the elif nested in it, the _top_branch_name() |
| # check doesn't hold true as the name matched the elif and not |
| # the if statement which the elif was a branch of, thus the |
| # nested if condition is not recognized as an invalid branch of |
| # the outer if statement. |
| # Sample: |
| # <condition type="if" check="ROGUEXE"/> |
| # <condition type="elif" check="COMPUTE"/> |
| # <condition type="if" check="COMPUTE"/> |
| # <condition type="endif" check="COMPUTE"/> |
| # <condition type="endif" check="COMPUTE"/> |
| # <condition type="endif" check="ROGUEXE"/> |
| # |
| # We fix this by checking the if condition name against its |
| # parent. |
| if element.name == self.name: |
| raise RuntimeError("Invalid if condition. Check: '%s'" % element.name) |
| |
| self._children[element.name] = element |
| else: |
| super().add(element) |
| |
| def emit(self, root: Csbgen) -> None: |
| if self.type == "if": |
| print("/* if %s is supported use: */" % self.name) |
| elif self.type == "elif": |
| print("/* else if %s is supported use: */" % self.name) |
| elif self.type == "else": |
| print("/* else %s is not-supported use: */" % self.name) |
| elif self.type == "endif": |
| print("/* endif %s */" % self.name) |
| return |
| else: |
| raise RuntimeError("Unknown condition type. Implementation error.") |
| |
| for child in self._children.values(): |
| child.emit(root) |
| |
| self._child_branch.emit(root) |
| |
| |
| class Group: |
| __slots__ = ["start", "count", "size", "fields"] |
| |
| start: int |
| count: int |
| size: int |
| fields: t.List[Field] |
| |
| def __init__(self, start: int, count: int, size: int, fields) -> None: |
| self.start = start |
| self.count = count |
| self.size = size |
| self.fields = fields |
| |
| class DWord: |
| __slots__ = ["size", "fields", "addresses"] |
| |
| size: int |
| fields: t.List[Field] |
| addresses: t.List[Field] |
| |
| def __init__(self) -> None: |
| self.size = 32 |
| self.fields = [] |
| self.addresses = [] |
| |
| def collect_dwords(self, dwords: t.Dict[int, Group.DWord], start: int) -> None: |
| for field in self.fields: |
| index = (start + field.start) // 32 |
| if index not in dwords: |
| dwords[index] = self.DWord() |
| |
| clone = copy.copy(field) |
| clone.start = clone.start + start |
| clone.end = clone.end + start |
| dwords[index].fields.append(clone) |
| |
| if field.type == "address": |
| # assert dwords[index].address == None |
| dwords[index].addresses.append(clone) |
| |
| # Coalesce all the dwords covered by this field. The two cases we |
| # handle are where multiple fields are in a 64 bit word (typically |
| # and address and a few bits) or where a single struct field |
| # completely covers multiple dwords. |
| while index < (start + field.end) // 32: |
| if index + 1 in dwords and not dwords[index] == dwords[index + 1]: |
| dwords[index].fields.extend(dwords[index + 1].fields) |
| dwords[index].addresses.extend(dwords[index + 1].addresses) |
| dwords[index].size = 64 |
| dwords[index + 1] = dwords[index] |
| index = index + 1 |
| |
| def collect_dwords_and_length(self) -> t.Tuple[t.Dict[int, Group.DWord], int]: |
| dwords = {} |
| self.collect_dwords(dwords, 0) |
| |
| # Determine number of dwords in this group. If we have a size, use |
| # that, since that'll account for MBZ dwords at the end of a group |
| # (like dword 8 on BDW+ 3DSTATE_HS). Otherwise, use the largest dword |
| # index we've seen plus one. |
| if self.size > 0: |
| length = self.size // 32 |
| elif dwords: |
| length = max(dwords.keys()) + 1 |
| else: |
| length = 0 |
| |
| return dwords, length |
| |
| def emit_pack_function(self, root: Csbgen, dwords: t.Dict[int, Group.DWord], length: int) -> None: |
| for index in range(length): |
| # Handle MBZ dwords |
| if index not in dwords: |
| print("") |
| print(" dw[%d] = 0;" % index) |
| continue |
| |
| # For 64 bit dwords, we aliased the two dword entries in the dword |
| # dict it occupies. Now that we're emitting the pack function, |
| # skip the duplicate entries. |
| dw = dwords[index] |
| if index > 0 and index - 1 in dwords and dw == dwords[index - 1]: |
| continue |
| |
| # Special case: only one field and it's a struct at the beginning |
| # of the dword. In this case we pack directly into the |
| # destination. This is the only way we handle embedded structs |
| # larger than 32 bits. |
| if len(dw.fields) == 1: |
| field = dw.fields[0] |
| if root.is_known_struct(field.type) and field.start % 32 == 0: |
| print("") |
| print(" %s_pack(data, &dw[%d], &values->%s);" |
| % (self.parser.gen_prefix(safe_name(field.type)), index, field.name)) |
| continue |
| |
| # Pack any fields of struct type first so we have integer values |
| # to the dword for those fields. |
| field_index = 0 |
| for field in dw.fields: |
| if root.is_known_struct(field.type): |
| print("") |
| print(" uint32_t v%d_%d;" % (index, field_index)) |
| print(" %s_pack(data, &v%d_%d, &values->%s);" |
| % (self.parser.gen_prefix(safe_name(field.type)), index, field_index, field.name)) |
| field_index = field_index + 1 |
| |
| print("") |
| dword_start = index * 32 |
| address_count = len(dw.addresses) |
| |
| if dw.size == 32 and not dw.addresses: |
| v = None |
| print(" dw[%d] =" % index) |
| elif len(dw.fields) > address_count: |
| v = "v%d" % index |
| print(" const uint%d_t %s =" % (dw.size, v)) |
| else: |
| v = "0" |
| |
| field_index = 0 |
| non_address_fields = [] |
| for field in dw.fields: |
| if field.type == "mbo": |
| non_address_fields.append("__pvr_mbo(%d, %d)" |
| % (field.start - dword_start, field.end - dword_start)) |
| elif field.type == "address": |
| pass |
| elif field.type == "uint": |
| non_address_fields.append("__pvr_uint(values->%s, %d, %d)" |
| % (field.name, field.start - dword_start, field.end - dword_start)) |
| elif root.is_known_enum(field.type): |
| non_address_fields.append("__pvr_uint(values->%s, %d, %d)" |
| % (field.name, field.start - dword_start, field.end - dword_start)) |
| elif field.type == "int": |
| non_address_fields.append("__pvr_sint(values->%s, %d, %d)" |
| % (field.name, field.start - dword_start, field.end - dword_start)) |
| elif field.type == "bool": |
| non_address_fields.append("__pvr_uint(values->%s, %d, %d)" |
| % (field.name, field.start - dword_start, field.end - dword_start)) |
| elif field.type == "float": |
| non_address_fields.append("__pvr_float(values->%s)" % field.name) |
| elif field.type == "offset": |
| non_address_fields.append("__pvr_offset(values->%s, %d, %d)" |
| % (field.name, field.start - dword_start, field.end - dword_start)) |
| elif field.is_struct_type(): |
| non_address_fields.append("__pvr_uint(v%d_%d, %d, %d)" |
| % (index, field_index, field.start - dword_start, |
| field.end - dword_start)) |
| field_index = field_index + 1 |
| else: |
| non_address_fields.append( |
| "/* unhandled field %s," " type %s */\n" % (field.name, field.type) |
| ) |
| |
| if non_address_fields: |
| print(" |\n".join(" " + f for f in non_address_fields) + ";") |
| |
| if dw.size == 32: |
| for addr in dw.addresses: |
| print(" dw[%d] = __pvr_address(values->%s, %d, %d, %d) | %s;" |
| % (index, addr.name, addr.shift, addr.start - dword_start, |
| addr.end - dword_start, v)) |
| continue |
| |
| v_accumulated_addr = "" |
| for i, addr in enumerate(dw.addresses): |
| v_address = "v%d_address" % i |
| v_accumulated_addr += "v%d_address" % i |
| print(" const uint64_t %s =" % v_address) |
| print(" __pvr_address(values->%s, %d, %d, %d);" |
| % (addr.name, addr.shift, addr.start - dword_start, addr.end - dword_start)) |
| if i < (address_count - 1): |
| v_accumulated_addr += " |\n " |
| |
| if dw.addresses: |
| if len(dw.fields) > address_count: |
| print(" dw[%d] = %s | %s;" % (index, v_accumulated_addr, v)) |
| print(" dw[%d] = (%s >> 32) | (%s >> 32);" % (index + 1, v_accumulated_addr, v)) |
| continue |
| else: |
| v = v_accumulated_addr |
| |
| print(" dw[%d] = %s;" % (index, v)) |
| print(" dw[%d] = %s >> 32;" % (index + 1, v)) |
| |
| def emit_unpack_function(self, root: Csbgen, dwords: t.Dict[int, Group.DWord], length: int) -> None: |
| for index in range(length): |
| # Ignore MBZ dwords |
| if index not in dwords: |
| continue |
| |
| # For 64 bit dwords, we aliased the two dword entries in the dword |
| # dict it occupies. Now that we're emitting the unpack function, |
| # skip the duplicate entries. |
| dw = dwords[index] |
| if index > 0 and index - 1 in dwords and dw == dwords[index - 1]: |
| continue |
| |
| # Special case: only one field and it's a struct at the beginning |
| # of the dword. In this case we unpack directly from the |
| # source. This is the only way we handle embedded structs |
| # larger than 32 bits. |
| if len(dw.fields) == 1: |
| field = dw.fields[0] |
| if root.is_known_struct(field.type) and field.start % 32 == 0: |
| prefix = root.get_struct(field.type) |
| print("") |
| print(" %s_unpack(data, &dw[%d], &values->%s);" % (prefix, index, field.name)) |
| continue |
| |
| dword_start = index * 32 |
| |
| if dw.size == 32: |
| v = "dw[%d]" % index |
| elif dw.size == 64: |
| v = "v%d" % index |
| print(" const uint%d_t %s = dw[%d] | ((uint64_t)dw[%d] << 32);" % (dw.size, v, index, index + 1)) |
| else: |
| raise RuntimeError("Unsupported dword size %d" % dw.size) |
| |
| # Unpack any fields of struct type first. |
| for field_index, field in enumerate(f for f in dw.fields if root.is_known_struct(f.type)): |
| prefix = root.get_struct(field.type).prefix |
| vname = "v%d_%d" % (index, field_index) |
| print("") |
| print(" uint32_t %s = __pvr_uint_unpack(%s, %d, %d);" |
| % (vname, v, field.start - dword_start, field.end - dword_start)) |
| print(" %s_unpack(data, &%s, &values->%s);" % (prefix, vname, field.name)) |
| |
| for field in dw.fields: |
| dword_field_start = field.start - dword_start |
| dword_field_end = field.end - dword_start |
| |
| if field.type == "mbo" or root.is_known_struct(field.type): |
| continue |
| elif field.type == "uint" or root.is_known_enum(field.type) or field.type == "bool": |
| print(" values->%s = __pvr_uint_unpack(%s, %d, %d);" |
| % (field.name, v, dword_field_start, dword_field_end)) |
| elif field.type == "int": |
| print(" values->%s = __pvr_sint_unpack(%s, %d, %d);" |
| % (field.name, v, dword_field_start, dword_field_end)) |
| elif field.type == "float": |
| print(" values->%s = __pvr_float_unpack(%s);" % (field.name, v)) |
| elif field.type == "offset": |
| print(" values->%s = __pvr_offset_unpack(%s, %d, %d);" |
| % (field.name, v, dword_field_start, dword_field_end)) |
| elif field.type == "address": |
| print(" values->%s = __pvr_address_unpack(%s, %d, %d, %d);" |
| % (field.name, v, field.shift, dword_field_start, dword_field_end)) |
| else: |
| print("/* unhandled field %s, type %s */" % (field.name, field.type)) |
| |
| |
| class Parser: |
| __slots__ = ["parser", "context", "filename"] |
| |
| parser: expat.XMLParserType |
| context: t.List[Node] |
| filename: str |
| |
| def __init__(self) -> None: |
| self.parser = expat.ParserCreate() |
| self.parser.StartElementHandler = self.start_element |
| self.parser.EndElementHandler = self.end_element |
| |
| self.context = [] |
| self.filename = "" |
| |
| def start_element(self, name: str, attrs: t.Dict[str, str]) -> None: |
| if name == "csbgen": |
| if self.context: |
| raise RuntimeError( |
| "Can only have 1 csbgen block and it has " |
| + "to contain all of the other elements." |
| ) |
| |
| csbgen = Csbgen(attrs["name"], attrs["prefix"], self.filename) |
| self.context.append(csbgen) |
| return |
| |
| parent = self.context[-1] |
| |
| if name == "struct": |
| struct = Struct(parent, attrs["name"], int(attrs["length"])) |
| self.context.append(struct) |
| |
| elif name == "field": |
| default = None |
| if "default" in attrs.keys(): |
| default = attrs["default"] |
| |
| shift = None |
| if "shift" in attrs.keys(): |
| shift = attrs["shift"] |
| |
| field = Field(parent, name=attrs["name"], start=int(attrs["start"]), end=int(attrs["end"]), |
| ty=attrs["type"], default=default, shift=shift) |
| self.context.append(field) |
| |
| elif name == "enum": |
| enum = Enum(parent, attrs["name"]) |
| self.context.append(enum) |
| |
| elif name == "value": |
| value = Value(parent, attrs["name"], int(literal_eval(attrs["value"]))) |
| self.context.append(value) |
| |
| elif name == "define": |
| define = Define(parent, attrs["name"], int(literal_eval(attrs["value"]))) |
| self.context.append(define) |
| |
| elif name == "condition": |
| condition = Condition(parent, name=attrs["check"], ty=attrs["type"]) |
| |
| # Starting with the if statement we push it in the context. For each |
| # branch following (elif, and else) we assign the top of stack as |
| # its parent, pop() and push the new condition. So per branch we end |
| # up having [..., struct, condition]. We don't push an endif since |
| # it's not supposed to have any children and it's supposed to close |
| # the whole if statement. |
| |
| if condition.type != "if": |
| # Remove the parent condition from the context. We were peeking |
| # before, now we pop(). |
| self.context.pop() |
| |
| if condition.type == "endif": |
| if not isinstance(parent, Condition): |
| raise RuntimeError("Cannot close unopened or already closed condition. Condition: '%s'" |
| % condition.name) |
| else: |
| self.context.append(condition) |
| |
| else: |
| raise RuntimeError("Unknown tag: '%s'" % name) |
| |
| def end_element(self, name: str) -> None: |
| if name == "condition": |
| element = self.context[-1] |
| if not isinstance(element, Condition) and not isinstance(element, Struct): |
| raise RuntimeError("Expected condition or struct tag to be closed.") |
| |
| return |
| |
| element = self.context.pop() |
| |
| if name == "struct": |
| if not isinstance(element, Struct): |
| raise RuntimeError("Expected struct tag to be closed.") |
| elif name == "field": |
| if not isinstance(element, Field): |
| raise RuntimeError("Expected field tag to be closed.") |
| elif name == "enum": |
| if not isinstance(element, Enum): |
| raise RuntimeError("Expected enum tag to be closed.") |
| elif name == "value": |
| if not isinstance(element, Value): |
| raise RuntimeError("Expected value tag to be closed.") |
| elif name == "define": |
| if not isinstance(element, Define): |
| raise RuntimeError("Expected define tag to be closed.") |
| elif name == "csbgen": |
| if not isinstance(element, Csbgen): |
| raise RuntimeError("Expected csbgen tag to be closed.\nSome tags may have not been closed") |
| |
| element.emit() |
| else: |
| raise RuntimeError("Unknown closing element: '%s'" % name) |
| |
| def parse(self, filename: str) -> None: |
| file = open(filename, "rb") |
| self.filename = filename |
| self.parser.ParseFile(file) |
| file.close() |
| |
| |
| if __name__ == "__main__": |
| import sys |
| |
| if len(sys.argv) < 2: |
| print("No input xml file specified") |
| sys.exit(1) |
| |
| input_file = sys.argv[1] |
| |
| p = Parser() |
| p.parse(input_file) |