mesa/src/imagination/csbgen/gen_pack_header.py
Matt Coster d51d79b450 pvr: csbgen: Generate enum-to-string functions for debugging
All enums should be unambiguous, so an error is raised when multiple
enum variants with the same value are encountered. When no enum
variants match the provided value, NULL is returned. This allows the
to-string functions to double as validators.

Signed-off-by: Matt Coster <matt.coster@imgtec.com>
Reviewed-by: Karmjit Mahil <Karmjit.Mahil@imgtec.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/18948>
2022-11-25 16:42:55 +00:00

1083 lines
40 KiB
Python

# 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)
if element.value in self._values.values():
raise RuntimeError("Ambiguous enum value detected. Value: '%s'" % element.value)
self._values[element.name] = element
def _emit_to_str(self) -> None:
print(textwrap.dedent("""\
static const char *
%s_to_str(const enum %s value)
{""") % (self.full_name, self.full_name))
print(" switch (value) {")
for value in self._values.values():
print(" case %s: return \"%s\";" % (value.full_name, value.name))
print(" default: return NULL;")
print(" }")
print("}\n")
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")
self._emit_to_str()
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)