1065 lines
39 KiB
Python
1065 lines
39 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)
|
|
|
|
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)
|