mesa/src/asahi/genxml/gen_pack.py
Alyssa Rosenzweig cc04a65828 asahi: fix libwrap.dylib
libwrap.dylib is helpful to trace control streams on macOS. When it was
originally implemented, we..

* supported macOS in our OpenGL driver and needed to actually exercise these
  interfaces
* didn't have Linux support or hypervisor support or anything so needed the
  traces to be utterly thorough
* only had a single macOS version to worry about

The landscape today is very different

* no macOS support in our driver stack
* we can trace registers via the hypervisor - libwrap.dylib is no longer
  "correctness" bearing, it's just a convenience tool
* what counts is the hardware side - tracing all the macOS software structs is
  not actually useful, the hypervisor is the right place to grab control regs
* piles of macOS versions, this code only ever worked properly on 11.x and 12.x,
  but with m4 r/e coming up soon we need a lot more versions working.

So... we keep around libwrap.dylib, but slim it down to only decode the bare
minimum of macOS versioned structures, just enough to grab the control stream
pointer and dump that. This is a loss of functionality around CRs (but we have the
hypervisor as a much better way to grab CRs). In exchange it makes the code much
more manageable and less likely to break every 6 months.

So in exchange for all this deletion we also get things working again, this time
on 13.x. But porting back to 12.x or 11.x would be a very small diffstat given
the reduced focus of the new code.

Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/33682>
2025-02-22 02:24:28 +00:00

655 lines
23 KiB
Python

#encoding=utf-8
# Copyright 2016 Intel Corporation
# Copyright 2016 Broadcom
# Copyright 2020 Collabora, Ltd.
# SPDX-License-Identifier: MIT
import xml.parsers.expat
import sys
import operator
import math
import platform
from functools import reduce
global_prefix = "agx"
def to_alphanum(name):
substitutions = {
' ': '_',
'/': '_',
'[': '',
']': '',
'(': '',
')': '',
'-': '_',
':': '',
'.': '',
',': '',
'=': '',
'>': '',
'#': '',
'&': '',
'*': '',
'"': '',
'+': '',
'\'': '',
'?': '',
}
for i, j in substitutions.items():
name = name.replace(i, j)
return name
def safe_name(name):
name = to_alphanum(name)
if not name[0].isalpha():
name = '_' + name
return name
def prefixed_upper_name(prefix, name):
if prefix:
name = prefix + "_" + name
return safe_name(name).upper()
def enum_name(name):
return f"{global_prefix}_{safe_name(name)}".lower()
MODIFIERS = ["shr", "minus", "align", "log2", "groups"]
def parse_modifier(modifier):
if modifier is None:
return None
for mod in MODIFIERS:
if modifier[0:len(mod)] == mod:
if mod == "log2":
assert(len(mod) == len(modifier))
return [mod]
if modifier[len(mod)] == '(' and modifier[-1] == ')':
ret = [mod, int(modifier[(len(mod) + 1):-1])]
if ret[0] == 'align':
align = ret[1]
# Make sure the alignment is a power of 2
assert(align > 0 and not(align & (align - 1)));
return ret
print("Invalid modifier")
assert(False)
class Field(object):
def __init__(self, parser, attrs):
self.parser = parser
if "name" in attrs:
self.name = safe_name(attrs["name"]).lower()
self.human_name = attrs["name"]
if ":" in str(attrs["start"]):
(word, bit) = attrs["start"].split(":")
self.start = (int(word) * 32) + int(bit)
else:
self.start = int(attrs["start"])
self.end = self.start + int(attrs["size"]) - 1
self.type = attrs["type"]
if self.type == 'bool' and self.start != self.end:
print(f"#error Field {self.name} has bool type but more than one bit of size");
if "prefix" in attrs:
self.prefix = safe_name(attrs["prefix"]).upper()
else:
self.prefix = None
self.modifier = parse_modifier(attrs.get("modifier"))
self.exact = attrs.get("exact")
self.default = None
if self.exact is not None:
self.default = self.exact
elif self.modifier is not None:
# Set the default value to encode to zero
mod = self.modifier
if mod[0] == 'log2':
self.default = 1
elif mod[0] == 'minus':
self.default = mod[1]
elif mod[0] == 'groups':
# The zero encoding means "all"
self.default = (1 << int(attrs["size"])) * mod[1]
elif mod[0] in ['shr', 'align']:
# Zero encodes to zero
pass
else:
assert(0)
# Map enum values
if self.type in self.parser.enums and self.default is not None:
self.default = safe_name(f'{global_prefix}_{self.type}_{self.default}').upper()
def emit_template_struct(self, dim):
if self.type == 'address':
type = 'uint64_t'
elif self.type == 'bool':
type = 'bool'
elif self.type in ['float', 'half', 'lod']:
type = 'float'
elif self.type in ['uint', 'hex'] and self.end - self.start > 32:
type = 'uint64_t'
elif self.type == 'int':
type = 'int32_t'
elif self.type in ['uint', 'hex']:
type = 'uint32_t'
elif self.type in self.parser.structs:
type = 'struct ' + self.parser.gen_prefix(safe_name(self.type.upper()))
elif self.type in self.parser.enums:
type = 'enum ' + enum_name(self.type)
else:
print(f"#error unhandled type: {self.type}")
type = "uint32_t"
print(" %-36s %s%s;" % (type, self.name, dim))
for value in self.values:
name = prefixed_upper_name(self.prefix, value.name)
print("#define %-40s %d" % (name, value.value))
def overlaps(self, field):
return self != field and max(self.start, field.start) <= min(self.end, field.end)
class Group(object):
def __init__(self, parser, parent, start, count, label):
self.parser = parser
self.parent = parent
self.start = start
self.count = count
self.label = label
self.size = 0
self.length = 0
self.fields = []
def get_length(self):
# Determine number of bytes in this group.
calculated = max(field.end // 8 for field in self.fields) + 1 if len(self.fields) > 0 else 0
if self.length > 0:
assert(self.length >= calculated)
else:
self.length = calculated
return self.length
def emit_template_struct(self, dim):
if self.count == 0:
print(" /* variable length fields follow */")
else:
if self.count > 1:
dim = "%s[%d]" % (dim, self.count)
any_fields = False
for field in self.fields:
if not field.exact:
field.emit_template_struct(dim)
any_fields = True
if not any_fields:
print(" int dummy;")
class Word:
def __init__(self):
self.size = 32
self.contributors = []
class FieldRef:
def __init__(self, field, path, start, end):
self.field = field
self.path = path
self.start = start
self.end = end
def collect_fields(self, fields, offset, path, all_fields):
for field in fields:
field_path = f'{path}{field.name}'
field_offset = offset + field.start
if field.type in self.parser.structs:
sub_struct = self.parser.structs[field.type]
self.collect_fields(sub_struct.fields, field_offset, field_path + '.', all_fields)
continue
start = field_offset
end = offset + field.end
all_fields.append(self.FieldRef(field, field_path, start, end))
def collect_words(self, fields, offset, path, words):
for field in fields:
field_path = f'{path}{field.name}'
start = offset + field.start
if field.type in self.parser.structs:
sub_fields = self.parser.structs[field.type].fields
self.collect_words(sub_fields, start, field_path + '.', words)
continue
end = offset + field.end
contributor = self.FieldRef(field, field_path, start, end)
first_word = contributor.start // 32
last_word = contributor.end // 32
for b in range(first_word, last_word + 1):
if not b in words:
words[b] = self.Word()
words[b].contributors.append(contributor)
def emit_pack_function(self):
self.get_length()
words = {}
self.collect_words(self.fields, 0, '', words)
# Validate the modifier is lossless
for field in self.fields:
if field.modifier is None:
continue
if field.modifier[0] == "shr":
shift = field.modifier[1]
mask = hex((1 << shift) - 1)
print(f" assert((values->{field.name} & {mask}) == 0);")
elif field.modifier[0] == "minus":
print(f" assert(values->{field.name} >= {field.modifier[1]});")
elif field.modifier[0] == "log2":
print(f" assert(IS_POT_NONZERO(values->{field.name}));")
for index in range(math.ceil(self.length / 4)):
# Handle MBZ words
if not index in words:
print(" cl[%2d] = 0;" % index)
continue
word = words[index]
word_start = index * 32
v = None
prefix = " cl[%2d] =" % index
lines = []
for contributor in word.contributors:
field = contributor.field
name = field.name
start = contributor.start
end = contributor.end
contrib_word_start = (start // 32) * 32
start -= contrib_word_start
end -= contrib_word_start
value = f"values->{contributor.path}"
if field.exact:
value = field.default
# These types all use util_bitpack_uint
pack_as_uint = field.type in ["uint", "hex", "address", "bool"]
pack_as_uint |= field.type in self.parser.enums
start_adjusted = start
value_unshifted = None
if field.modifier is not None:
if field.modifier[0] == "shr":
if pack_as_uint and start >= field.modifier[1]:
# For uint, we fast path. If we do `(a >> 2) << 2`,
# clang will generate a mask in release builds, even
# though we know we're aligned. So don't generate
# that to avoid the masking.
start_adjusted = start - field.modifier[1]
else:
value = f"{value} >> {field.modifier[1]}"
elif field.modifier[0] == "minus":
value = f"{value} - {field.modifier[1]}"
elif field.modifier[0] == "align":
value = f"ALIGN_POT({value}, {field.modifier[1]})"
elif field.modifier[0] == "log2":
value = f"util_logbase2({value})"
elif field.modifier[0] == "groups":
value = "__gen_to_groups({}, {}, {})".format(value,
field.modifier[1], end - start + 1)
if pack_as_uint:
bits = (end - start_adjusted + 1)
if bits < 64 and not field.exact:
# Add some nicer error checking
label = f"{self.label}::{name}"
bound = hex(1 << bits)
print(f" agx_genxml_validate_bounds(\"{label}\", {value}, {bound}ull);")
s = f"util_bitpack_uint({value}, {start_adjusted}, {end})"
elif field.type == "int":
s = "util_bitpack_sint(%s, %d, %d)" % \
(value, start, end)
elif field.type == "float":
assert(start == 0 and end == 31)
s = f"util_bitpack_float({value})"
elif field.type == "half":
assert(start == 0 and end == 15)
s = f"_mesa_float_to_half({value})"
elif field.type == "lod":
assert(end - start + 1 == 10)
s = "__gen_pack_lod(%s, %d, %d)" % (value, start, end)
else:
s = f"#error unhandled field {contributor.path}, type {field.type}"
if not s == None:
shift = word_start - contrib_word_start
if shift:
s = "%s >> %d" % (s, shift)
if contributor == word.contributors[-1]:
lines.append(f"{prefix} {s};")
else:
lines.append(f"{prefix} {s} |")
prefix = " "
for ln in lines:
print(ln)
continue
# Given a field (start, end) contained in word `index`, generate the 32-bit
# mask of present bits relative to the word
def mask_for_word(self, index, start, end):
field_word_start = index * 32
start -= field_word_start
end -= field_word_start
# Cap multiword at one word
start = max(start, 0)
end = min(end, 32 - 1)
count = (end - start + 1)
return (((1 << count) - 1) << start)
def emit_unpack_function(self):
# First, verify there is no garbage in unused bits
words = {}
self.collect_words(self.fields, 0, '', words)
validation = []
for index in range(self.length // 4):
base = index * 32
word = words.get(index, self.Word())
masks = [self.mask_for_word(index, c.start, c.end) for c in word.contributors]
mask = reduce(lambda x,y: x | y, masks, 0)
ALL_ONES = 0xffffffff
if mask != ALL_ONES:
bad_mask = hex(mask ^ ALL_ONES)
validation.append(f'agx_genxml_validate_mask(fp, \"{self.label}\", cl, {index}, {bad_mask})')
fieldrefs = []
self.collect_fields(self.fields, 0, '', fieldrefs)
for fieldref in fieldrefs:
field = fieldref.field
convert = None
args = []
args.append('(CONST uint32_t *) cl')
args.append(str(fieldref.start))
args.append(str(fieldref.end))
if field.type in set(["uint", "address", "hex"]) | self.parser.enums:
convert = "__gen_unpack_uint"
elif field.type == "int":
convert = "__gen_unpack_sint"
elif field.type == "bool":
convert = "__gen_unpack_uint"
elif field.type == "float":
convert = "__gen_unpack_float"
elif field.type == "half":
convert = "__gen_unpack_half"
elif field.type == "lod":
convert = "__gen_unpack_lod"
else:
s = f"/* unhandled field {field.name}, type {field.type} */\n"
suffix = ""
prefix = ""
if field.modifier:
if field.modifier[0] == "minus":
suffix = f" + {field.modifier[1]}"
elif field.modifier[0] == "shr":
suffix = f" << {field.modifier[1]}"
if field.modifier[0] == "log2":
prefix = "1 << "
elif field.modifier[0] == "groups":
prefix = "__gen_from_groups("
suffix = ", {}, {})".format(field.modifier[1],
fieldref.end - fieldref.start + 1)
if field.type in self.parser.enums and not field.exact:
prefix = f"(enum {enum_name(field.type)}) {prefix}"
decoded = f"{prefix}{convert}({', '.join(args)}){suffix}"
if field.exact:
name = self.label
validation.append(f'agx_genxml_validate_exact(fp, \"{name}\", {decoded}, {field.default})')
else:
print(f' values->{fieldref.path} = {decoded};')
if field.modifier and field.modifier[0] == "align":
assert(not field.exact)
mask = hex(field.modifier[1] - 1)
print(f' assert(!(values->{fieldref.path} & {mask}));')
if len(validation) > 1:
print(' bool valid = true;')
for v in validation:
print(f' valid &= {v};')
print(" return valid;")
elif len(validation) == 1:
print(f" return {validation[0]};")
else:
print(" return true;")
def emit_print_function(self):
for field in self.fields:
convert = None
name, val = field.human_name, f'values->{field.name}'
if field.exact:
continue
if field.type in self.parser.structs:
pack_name = self.parser.gen_prefix(safe_name(field.type)).upper()
print(f' fprintf(fp, "%*s{field.human_name}:\\n", indent, "");')
print(f" {pack_name}_print(fp, &values->{field.name}, indent + 2);")
elif field.type == "address":
# TODO resolve to name
print(f' fprintf(fp, "%*s{name}: 0x%" PRIx64 "\\n", indent, "", {val});')
elif field.type in self.parser.enums:
print(f' if ({enum_name(field.type)}_as_str({val}))')
print(f' fprintf(fp, "%*s{name}: %s\\n", indent, "", {enum_name(field.type)}_as_str({val}));')
print(f' else')
print(f' fprintf(fp, "%*s{name}: unknown %X (XXX)\\n", indent, "", {val});')
elif field.type == "int":
print(f' fprintf(fp, "%*s{name}: %d\\n", indent, "", {val});')
elif field.type == "bool":
print(f' fprintf(fp, "%*s{name}: %s\\n", indent, "", {val} ? "true" : "false");')
elif field.type in ["float", "lod", "half"]:
print(f' fprintf(fp, "%*s{name}: %f\\n", indent, "", {val});')
elif field.type in ["uint", "hex"] and (field.end - field.start) >= 32:
print(f' fprintf(fp, "%*s{name}: 0x%" PRIx64 "\\n", indent, "", {val});')
elif field.type == "hex":
print(f' fprintf(fp, "%*s{name}: 0x%" PRIx32 "\\n", indent, "", {val});')
else:
print(f' fprintf(fp, "%*s{name}: %u\\n", indent, "", {val});')
class Value(object):
def __init__(self, attrs):
self.name = attrs["name"]
self.value = int(attrs["value"], 0)
class Parser(object):
def __init__(self):
self.parser = xml.parsers.expat.ParserCreate()
self.parser.StartElementHandler = self.start_element
self.parser.EndElementHandler = self.end_element
self.struct = None
self.structs = {}
# Set of enum names we've seen.
self.enums = set()
def gen_prefix(self, name):
return f'{global_prefix.upper()}_{name}'
def start_element(self, name, attrs):
if name == "genxml":
print(pack_header)
elif name == "struct":
name = attrs["name"]
object_name = self.gen_prefix(safe_name(name.upper()))
self.struct = object_name
self.group = Group(self, None, 0, 1, name)
if "size" in attrs:
self.group.length = int(attrs["size"])
self.group.align = int(attrs["align"]) if "align" in attrs else None
self.structs[attrs["name"]] = self.group
elif name == "field" and self.group is not None:
self.group.fields.append(Field(self, attrs))
self.values = []
elif name == "enum":
self.values = []
self.enum = safe_name(attrs["name"])
self.enums.add(attrs["name"])
if "prefix" in attrs:
self.prefix = attrs["prefix"]
else:
self.prefix= None
elif name == "value":
self.values.append(Value(attrs))
def end_element(self, name):
if name == "struct":
if self.struct is not None:
self.emit_struct()
self.struct = None
self.group = None
elif name == "field" and self.group is not None:
self.group.fields[-1].values = self.values
elif name == "enum":
self.emit_enum()
self.enum = None
def emit_header(self, name):
default_fields = []
for field in self.group.fields:
if not type(field) is Field or field.exact:
continue
if field.default is not None:
default_fields.append(f" .{field.name} = {field.default}")
elif field.type in self.structs:
default_fields.append(f" .{field.name} = {{ {self.gen_prefix(safe_name(field.type.upper()))}_header }}")
if default_fields:
print('#define %-40s\\' % (name + '_header'))
print(", \\\n".join(default_fields))
else:
print(f'#define {name}_header 0')
print('')
def emit_template_struct(self, name, group):
print("struct %s {" % name)
group.emit_template_struct("")
print("};\n")
def emit_pack_function(self, name, group):
print("static inline void\n%s_pack(GLOBAL uint32_t * restrict cl,\n%sconst struct %s * restrict values)\n{" %
(name, ' ' * (len(name) + 6), name))
group.emit_pack_function()
print("}\n")
print(f"#define {name + '_LENGTH'} {self.group.length}")
if self.group.align != None:
print(f"#define {name + '_ALIGN'} {self.group.align}")
# round up to handle 6 half-word USC structures
words = (self.group.length + 4 - 1) // 4
print(f'struct {name.lower()}_packed {{ uint32_t opaque[{words}];}};')
def emit_unpack_function(self, name, group):
print("static inline bool")
print("%s_unpack(FILE *fp, CONST uint8_t * restrict cl,\n%sstruct %s * restrict values)\n{" %
(name.upper(), ' ' * (len(name) + 8), name))
group.emit_unpack_function()
print("}\n")
def emit_print_function(self, name, group):
print("#ifndef __OPENCL_VERSION__")
print("static inline void")
print(f"{name.upper()}_print(FILE *fp, const struct {name} * values, unsigned indent)\n{{")
group.emit_print_function()
print("}")
print("#endif\n")
def emit_struct(self):
name = self.struct
self.emit_template_struct(self.struct, self.group)
self.emit_header(name)
self.emit_pack_function(self.struct, self.group)
self.emit_unpack_function(self.struct, self.group)
self.emit_print_function(self.struct, self.group)
def enum_prefix(self, name):
return
def emit_enum(self):
e_name = enum_name(self.enum)
prefix = e_name if self.enum != 'Format' else global_prefix
print(f'enum {e_name} {{')
for value in self.values:
name = f'{prefix}_{value.name}'
name = safe_name(name).upper()
print(f' {name} = {value.value},')
print('};\n')
print("#ifndef __OPENCL_VERSION__")
print("static inline const char *")
print(f"{e_name.lower()}_as_str(enum {e_name} imm)\n{{")
print(" switch (imm) {")
for value in self.values:
name = f'{prefix}_{value.name}'
name = safe_name(name).upper()
print(f' case {name}: return "{value.name}";')
print(' default: return NULL;')
print(" }")
print("}")
print("#endif\n")
def parse(self, filename):
file = open(filename, "rb")
self.parser.ParseFile(file)
file.close()
if len(sys.argv) < 3:
print("Missing input files file specified")
sys.exit(1)
input_file = sys.argv[1]
pack_header = open(sys.argv[2]).read()
p = Parser()
p.parse(input_file)